Created
March 2, 2021 04:07
-
-
Save wallentx/a4581391996a45ee721343ce30032453 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| shopt -s extglob | |
| # generated from util-linux source: libmount/src/utils.c | |
| declare -A pseudofs_types=([anon_inodefs]=1 | |
| [autofs]=1 | |
| [bdev]=1 | |
| [bpf]=1 | |
| [binfmt_misc]=1 | |
| [cgroup]=1 | |
| [cgroup2]=1 | |
| [configfs]=1 | |
| [cpuset]=1 | |
| [debugfs]=1 | |
| [devfs]=1 | |
| [devpts]=1 | |
| [devtmpfs]=1 | |
| [dlmfs]=1 | |
| [efivarfs]=1 | |
| [fuse.gvfs-fuse-daemon]=1 | |
| [fusectl]=1 | |
| [hugetlbfs]=1 | |
| [mqueue]=1 | |
| [nfsd]=1 | |
| [none]=1 | |
| [pipefs]=1 | |
| [proc]=1 | |
| [pstore]=1 | |
| [ramfs]=1 | |
| [rootfs]=1 | |
| [rpc_pipefs]=1 | |
| [securityfs]=1 | |
| [sockfs]=1 | |
| [spufs]=1 | |
| [sysfs]=1 | |
| [tmpfs]=1) | |
| # generated from: pkgfile -vbr '/fsck\..+' | awk -F. '{ print $NF }' | sort | |
| declare -A fsck_types=([cramfs]=1 | |
| [exfat]=1 | |
| [ext2]=1 | |
| [ext3]=1 | |
| [ext4]=1 | |
| [ext4dev]=1 | |
| [jfs]=1 | |
| [minix]=1 | |
| [msdos]=1 | |
| [reiserfs]=1 | |
| [vfat]=1 | |
| [xfs]=1) | |
| out() { printf "$1 $2\n" "${@:3}"; } | |
| error() { out "==> ERROR:" "$@"; } >&2 | |
| warning() { out "==> WARNING:" "$@"; } >&2 | |
| msg() { out "==>" "$@"; } | |
| msg2() { out " ->" "$@";} | |
| die() { error "$@"; exit 1; } | |
| ignore_error() { | |
| "$@" 2>/dev/null | |
| return 0 | |
| } | |
| in_array() { | |
| local i | |
| for i in "${@:2}"; do | |
| [[ $1 = "$i" ]] && return 0 | |
| done | |
| return 1 | |
| } | |
| chroot_add_mount() { | |
| mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}") | |
| } | |
| chroot_maybe_add_mount() { | |
| 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" --bind && | |
| 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 | |
| } | |
| try_cast() ( | |
| _=$(( $1#$2 )) | |
| ) 2>/dev/null | |
| valid_number_of_base() { | |
| local base=$1 len=${#2} i= | |
| for (( i = 0; i < len; i++ )); do | |
| try_cast "$base" "${2:i:1}" || return 1 | |
| done | |
| return 0 | |
| } | |
| mangle() { | |
| local i= chr= out= | |
| local {a..f}= {A..F}= | |
| for (( i = 0; i < ${#1}; i++ )); do | |
| chr=${1:i:1} | |
| case $chr in | |
| [[:space:]\\]) | |
| printf -v chr '%03o' "'$chr" | |
| out+=\\ | |
| ;; | |
| esac | |
| out+=$chr | |
| done | |
| printf '%s' "$out" | |
| } | |
| unmangle() { | |
| local i= chr= out= len=$(( ${#1} - 4 )) | |
| local {a..f}= {A..F}= | |
| for (( i = 0; i < len; i++ )); do | |
| chr=${1:i:1} | |
| case $chr in | |
| \\) | |
| if valid_number_of_base 8 "${1:i+1:3}" || | |
| valid_number_of_base 16 "${1:i+1:3}"; then | |
| printf -v chr '%b' "${1:i:4}" | |
| (( i += 3 )) | |
| fi | |
| ;; | |
| esac | |
| out+=$chr | |
| done | |
| printf '%s' "$out${1:i}" | |
| } | |
| optstring_match_option() { | |
| local candidate pat patterns | |
| 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 | |
| [[ $pat = "$candidate" ]] && return 0 | |
| done | |
| return 1 | |
| } | |
| optstring_remove_option() { | |
| local o options_ remove=$2 IFS=, | |
| read -ra options_ <<<"${!1}" | |
| for o in "${!options_[@]}"; do | |
| optstring_match_option "$remove" "${options_[o]}" && unset 'options_[o]' | |
| done | |
| declare -g "$1=${options_[*]}" | |
| } | |
| optstring_normalize() { | |
| local o options_ norm IFS=, | |
| read -ra options_ <<<"${!1}" | |
| # remove empty fields | |
| for o in "${options_[@]}"; do | |
| [[ $o ]] && norm+=("$o") | |
| 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" | |
| fi | |
| optstring_normalize "$1" | |
| } | |
| optstring_prepend_option() { | |
| local options_=$1 | |
| if ! optstring_has_option "$1" "$2"; then | |
| declare -g "$1=$2,${!1}" | |
| fi | |
| 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 | |
| done | |
| return 1 | |
| } | |
| optstring_has_option() { | |
| local "${2%%=*}" | |
| optstring_get_option "$1" "$2" | |
| } | |
| dm_name_for_devnode() { | |
| read dm_name <"/sys/class/block/${1#/dev/}/dm/name" | |
| if [[ $dm_name ]]; then | |
| printf '/dev/mapper/%s' "$dm_name" | |
| else | |
| # don't leave the caller hanging, just print the original name | |
| # along with the failure. | |
| print '%s' "$1" | |
| error 'Failed to resolve device mapper name for: %s' "$1" | |
| fi | |
| } | |
| fstype_is_pseudofs() { | |
| (( pseudofs_types["$1"] )) | |
| } | |
| fstype_has_fsck() { | |
| (( fsck_types["$1"] )) | |
| } | |
| write_source() { | |
| local src=$1 spec= label= uuid= comment=() | |
| label=$(lsblk -rno LABEL "$1" 2>/dev/null) | |
| uuid=$(lsblk -rno UUID "$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")" | |
| else | |
| printf '%-20s' "$(mangle "$src")" | |
| fi | |
| } | |
| optstring_apply_quirks() { | |
| local varname=$1 fstype=$2 | |
| # 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 | |
| # 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 | |
| fi | |
| case $fstype in | |
| 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 | |
| ;; | |
| esac | |
| } | |
| usage() { | |
| cat <<EOF | |
| usage: ${0##*/} [options] root | |
| Options: | |
| -f <filter> Restrict output to mountpoints matching the prefix FILTER | |
| -L Use labels for source identifiers (shortcut for -t LABEL) | |
| -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) | |
| -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. | |
| EOF | |
| } | |
| if [[ -z $1 || $1 = @(-h|--help) ]]; then | |
| usage | |
| exit $(( $# ? 0 : 1 )) | |
| fi | |
| while getopts ':f:LPpt:U' flag; do | |
| case $flag in | |
| L) | |
| bytag=LABEL | |
| ;; | |
| U) | |
| bytag=UUID | |
| ;; | |
| f) | |
| prefixfilter=$OPTARG | |
| ;; | |
| P) | |
| pseudofs=1 | |
| ;; | |
| p) | |
| pseudofs=0 | |
| ;; | |
| t) | |
| bytag=${OPTARG^^} | |
| ;; | |
| :) | |
| die '%s: option requires an argument -- '\''%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 | |
| if ! mountpoint -q "$root"; then | |
| die "$root is not a mountpoint" | |
| 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 | |
| continue | |
| fi | |
| [[ $target = "$prefixfilter"* ]] || continue | |
| # default 5th and 6th columns | |
| dump=0 pass=2 | |
| src=$(unmangle "$src") | |
| target=$(unmangle "$target") | |
| target=${target#$root} | |
| if (( !foundroot )) && findmnt "$src" "$root" >/dev/null; then | |
| # this is root. we can't possibly have more than one... | |
| pass=1 foundroot=1 | |
| fi | |
| # if there's no fsck tool available, then only pass=0 makes sense. | |
| if ! fstype_has_fsck "$fstype"; then | |
| pass=0 | |
| fi | |
| if [[ $fsroot != / ]]; then | |
| if [[ $fstype = btrfs ]]; then | |
| opts+=,subvol=${fsroot#/} | |
| else | |
| # it's a bind mount | |
| src=$(findmnt -funcevo TARGET "$src")$fsroot | |
| if [[ $src -ef $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 | |
| pass=0 | |
| fi | |
| fi | |
| # filesystem quirks | |
| case $fstype in | |
| 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 | |
| # 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 | |
| fi | |
| ;; | |
| esac | |
| optstring_apply_quirks "opts" "$fstype" | |
| # write one line | |
| write_source "$src" | |
| printf '\t%-10s' "/$(mangle "${target#/}")" "$fstype" "$opts" | |
| printf '\t%s %s' "$dump" "$pass" | |
| printf '\n\n' | |
| done | |
| # handle swaps devices | |
| { | |
| # ignore header | |
| read | |
| while read -r device type _ _ prio; do | |
| options=defaults | |
| if (( prio >= 0 )); then | |
| options+=,pri=$prio | |
| fi | |
| # skip files marked deleted by the kernel | |
| [[ $device = *'\040(deleted)' ]] && continue | |
| 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. | |
| write_source "$(dm_name_for_devnode "$device")" | |
| else | |
| write_source "$(unmangle "$device")" | |
| fi | |
| printf '\t%-10s\t%-10s\t%-10s\t0 0\n\n' 'none' 'swap' "$options" | |
| done | |
| } </proc/swaps | |
| # vim: et ts=2 sw=2 ft=sh: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment