genfstab

simple implementation of genfstab
git clone git://git.ckyln.com/~cem/genfstab.git
Log | Files | Refs | README | LICENSE

genfstab (6094B)


      1 #!/bin/sh
      2 # fstab generator similar in function to the Arch Linux tool that can be found
      3 # on arch-install-scripts.
      4 #
      5 # Copyright (C) 2020 Cem Keylan <cem@ckyln.com>
      6 
      7 out() { printf '%s\n' "$@" ;}
      8 die() { out "$@" >&2; exit 1 ;}
      9 
     10 find_tag() {
     11     # Function to find the tag of the given device. This eliminates the
     12     # requirement for programs such as lsblk/blkid.
     13     #
     14     # usage:  find_tag /dev/sdx1
     15 
     16     # If no identifiers are given, return with the plain partition name.
     17     [ "$ident" ] || { out "$1"; return 0 ;}
     18 
     19     [ -d "${tagdir:=/dev/disk/by-$(printf %s "$ident" | tr '[:upper:]' '[:lower:]')}" ] ||
     20         die "Directory '$dir' doesn't exist"
     21 
     22     for file in "$tagdir/"*; do
     23         [ "$(_readlinkf "$file")" = "$1" ] || continue
     24         tag="$ident=${file##*/}"
     25         break
     26     done
     27 
     28     # If the tag couldn't be found for some reason, fallback to the default
     29     # name instead of printing empty.
     30     out "${tag:-$1}"
     31 }
     32 
     33 print_mnt() {
     34     # Print fstab entry
     35     printf '%-23s %-15s %-15s %-15s %s %s\n\n' "$@"
     36 }
     37 
     38 _readlinkf() {
     39     # https://github.com/ko1nksm/readlinkf
     40     [ "${1:-}" ] || return 1
     41     max_symlinks=40
     42     CDPATH='' # to avoid changing to an unexpected directory
     43 
     44     target=$1
     45     [ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes
     46     [ -d "${target:-/}" ] && target="$target/"
     47 
     48     cd -P . 2>/dev/null || return 1
     49     while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do
     50         if [ ! "$target" = "${target%/*}" ]; then
     51             case $target in
     52                 /*) cd -P "${target%/*}/" 2>/dev/null || break ;;
     53                 *) cd -P "./${target%/*}" 2>/dev/null || break ;;
     54             esac
     55             target=${target##*/}
     56         fi
     57 
     58         if [ ! -L "$target" ]; then
     59             target="${PWD%/}${target:+/}${target}"
     60             printf '%s\n' "${target:-/}"
     61             return 0
     62         fi
     63 
     64         # `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n",
     65         #   <file mode>, <number of links>, <owner name>, <group name>,
     66         #   <size>, <date and time>, <pathname of link>, <contents of link>
     67         # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
     68         link=$(ls -dl -- "$target" 2>/dev/null) || break
     69         target=${link#*" $target -> "}
     70     done
     71     return 1
     72 }
     73 
     74 ispseudo() {
     75     # This function checks for pseudo-filesystems and returns 0 if it is.
     76     # There is no definitive way to know every single pseudo-filesystems,
     77     # there might be some issues with this function.
     78     #
     79     # If the filesystem is not a directory or a tag, we will assume that
     80     # it is a pseudofs. The only exception I have noticed so far are loop
     81     # devices.
     82     case "$fs" in /dev/loop*) return 0 ;; /*|*=*) ;; *) return 0; esac
     83 
     84     # These are the most common types of pseudo filesystems.
     85     case "$type" in squashfs|proc|sysfs|tmpfs|devtmpfs|devpts|debugfs) return 0; esac
     86 
     87     # As for the last check, we will look up /proc/filesystems which are
     88     # filesystems known by the running kernel.
     89     while read -r ps filesystem; do
     90         [ "$filesystem" = "$type" ] || continue
     91         [ "$ps" ] && return 0
     92         return 1
     93     done < /proc/filesystems
     94 
     95     # If we still haven't come up with an answer, let's assume that this is NOT
     96     # a pseudo-filesystem.
     97     return 1
     98 }
     99 
    100 usage() {
    101     out "usage: ${0##*/} [-f filter] [-t tag] [-LpPU] [root]" "" \
    102         "  Options:" \
    103         "    -f <filter>  Restrict output to mountpoints matching the prefix FILTER" \
    104         "    -L           Use labels for source identifiers (Shortcut for -t LABEL)" \
    105         "    -p           Exclude pseudofs mounts (default behaviour)" \
    106         "    -P           Include pseudofs mounts" \
    107         "    -t <tag>     Use tag for source identifiers (should be one of: LABEL," \
    108         "                 UUID, PARTLABEL, PARTUUID)" \
    109         "    -U           Use UUIDs for source identifiers (Shortcut for -t UUID)" "" \
    110         "    -h           Print this help message" "" \
    111         "${0##*/} generates output suitable for addition to an fstab file based on the" \
    112         "devices mounted under the mountpoint specified by the given root." "" \
    113         "This implementation of genfstab is from <https://git.ckyln.com/genfstab>"
    114     exit "${1:-0}"
    115 }
    116 
    117 
    118 # Print help information
    119 case " $* " in *' --help '*|*' -h '*) usage; esac
    120 
    121 ident='' filter='' pseudo=0
    122 while getopts hPpLUf:t: flag; do
    123     case $flag in
    124         h) usage       ;;
    125         P) pseudo=1    ;;
    126         p) pseudo=0    ;;
    127         L) ident=LABEL ;;
    128         U) ident=UUID  ;;
    129         f) [ -d "$OPTARG" ] || die "Not a directory '$OPTARG'"
    130             filter=$(_readlinkf "$OPTARG") ;;
    131         t) case "$OPTARG" in
    132                LABEL|UUID|PARTLABEL|PARTUUID) ident=$OPTARG ;;
    133                *) die "Unknown identifier '$OPTARG'"; esac
    134         ;;
    135         ?) usage 1
    136     esac
    137 done
    138 shift $((OPTIND - 1))
    139 
    140 [ "$1" ] && {
    141     [ -d "$1" ] || die "Not a directory '$1'"
    142     root="$(_readlinkf "$1")"
    143 }
    144 
    145 # It's a good thing that /proc/mounts and fstab share the same syntax. We will
    146 # always be defining dump and pass ourselves since they will always be '0 0' on
    147 # /proc/mounts.
    148 while read -r fs dir type options _; do
    149     if ispseudo; then
    150         pass=0
    151         [ "$pseudo" != 1 ] && continue
    152         [ "$ident" ] && out "# $fs"
    153     else
    154         case "$dir" in "${root:-/}"*) ;; *) continue; esac
    155         [ "$ident" ] && out "# $fs"
    156         fs="$(find_tag "$fs")"
    157         [ "$root" ] && dir=/${dir#$root}
    158 
    159         case "$dir" in /) pass=1 ;; *) pass=2; esac
    160     fi
    161     [ "$filter" ] && case "$dir" in ${filter%/}*) ;; *) continue; esac
    162 
    163     print_mnt "$fs" "${dir:-/}" "$type" "$options" "${dump:=0}" "$pass"
    164 done < /proc/mounts
    165 
    166 # Now print out mounted swaps. I am really not quite sure how swaps work as I
    167 # don't personally use them. If you have a better approach to this, please let
    168 # me know.
    169 while read -r file _; do
    170     [ -f "/$file" ] || continue
    171     [ "$root" ] && {
    172         case "$file" in "$root/"*) ;; *) continue; esac
    173         file=/${file#$root}
    174     }
    175     print_mnt "$file" none swap sw 0 0
    176 done < /proc/swaps