summaryrefslogtreecommitdiff
path: root/bin/genfstab
blob: 530fb0ba4e1b562604f1fbcc1d448d5aecd85e6c (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
112
113
114
115
116
117
#!/bin/sh
#
# genfstab - Generate /etc/fstab from current mounts
# rawnix.org - https://rawnix.org
#
# Usage: genfstab [-d] <mountpoint>
#        genfstab /mnt > /mnt/etc/fstab
#
#   -d   emit device paths (/dev/nvme0n1p2) instead of UUIDs
#
# UUIDs are the default.  A device path in fstab is stable only
# until the next disk is plugged in: adding a USB drive can renumber
# /dev/sdX and the system boots into an emergency shell for a change
# that had nothing to do with it.  -d exists for the cases where the
# device really is fixed and you want to read the file at a glance.

set -e

die() { echo "ERROR: $*" >&2; exit 1; }

USE_UUID=1

while [ $# -gt 0 ]; do
    case "$1" in
        -d) USE_UUID=0; shift ;;
        -*) die "Unknown option: $1" ;;
        *)  break ;;
    esac
done

[ -n "$1" ] || die "Usage: genfstab [-d] <mountpoint>"
[ -d "$1" ] || die "Mountpoint not found: $1"

# Strip a trailing slash before it reaches the sed below.  With
# MNT=/mnt/ the substitution turns /mnt/boot/efi into "boot/efi"
# with no leading slash, and the resulting fstab is silently wrong.
MNT="${1%/}"
[ -n "$MNT" ] || MNT="/"

mountpoint -q "$MNT" || die "$MNT is not a mountpoint"

# Resolve a device to UUID=..., falling back to the device path if
# blkid cannot see it.  Never emits a bare UUID= with nothing after.
devspec() {
    _dev="$1"
    if [ "$USE_UUID" -eq 1 ]; then
        _uuid=$(blkid -o value -s UUID "$_dev" 2>/dev/null || true)
        if [ -n "$_uuid" ]; then
            printf 'UUID=%s' "$_uuid"
            return 0
        fi
        echo "genfstab: no UUID for $_dev, using device path" >&2
    fi
    printf '%s' "$_dev"
}

cat << 'EOF'
# /etc/fstab: static file system information
# rawnix.org - https://rawnix.org
#
# <file system>                             <dir>                <type>    <options>                       <dump> <pass>
EOF

# -l is not optional: without it findmnt draws a tree, and every
# submount's TARGET arrives prefixed with box-drawing characters
# ("|-/mnt/boot/efi").  The $MNT strip below then does not match, and
# the entry lands in fstab as /mnt/boot/efi -- which mounts nothing at
# boot and drops the installed system into an emergency shell.  The
# root line has no prefix, so root is correct and only the submounts
# are wrong, which is what makes it look like a UUID problem.
findmnt -l -R -n -o TARGET,SOURCE,FSTYPE "$MNT" | sort | while read target source fstype; do
    case "$fstype" in
        tmpfs|devtmpfs|devpts|sysfs|proc|cgroup*|efivarfs|hugetlbfs|mqueue|securityfs|pstore|bpf|fusectl|configfs) continue ;;
    esac
    case "$source" in
        /dev/loop*) continue ;;
    esac

    dir=${target#"$MNT"}
    [ -n "$dir" ] || dir="/"

    case "$dir" in
        /)         opts="defaults,noatime"               ; dump=1; pass=1 ;;
        /boot/efi) opts="defaults,noatime,nodev,noexec"  ; dump=0; pass=2 ;;
        /tmp)      opts="defaults,noatime,nosuid,nodev"  ; dump=1; pass=2 ;;
        /home)     opts="defaults,noatime"               ; dump=1; pass=2 ;;
        /var)      opts="defaults,noatime"               ; dump=1; pass=2 ;;
        *)         opts="defaults,noatime"               ; dump=1; pass=2 ;;
    esac

    printf "%-42s %-20s %-9s %-31s %s      %s\n" \
        "$(devspec "$source")" "$dir" "$fstype" "$opts" "$dump" "$pass"
done

# Every swap signature the *running* system can see, which in an
# installer is only the target's.  Run from a live system that has its
# own swap, this writes that swap into the target's fstab.
for dev in $(blkid -t TYPE=swap -o device 2>/dev/null); do
    printf "%-42s %-20s %-9s %-31s %s      %s\n" \
        "$(devspec "$dev")" "swap" "swap" "defaults" "0" "0"
done

cat << 'EOF'
devpts                                     /dev/pts             devpts    noexec,nosuid,gid=tty,mode=0620 0      0
tmpfs                                      /dev/shm             tmpfs     mode=1777,nosuid,nodev          0      0
EOF

# efivarfs only on a machine booted through UEFI.  Under legacy BIOS
# there is no /sys/firmware/efi to mount it on: nofail covers a missing
# device, not a missing mount point, so mount -a fails at boot and
# stage 1 drops to the emergency shell.  The installer runs on the
# target machine, so its own /sys/firmware/efi says which firmware the
# installed system will boot with.
if [ -d /sys/firmware/efi ]; then
    echo 'efivarfs                                   /sys/firmware/efi/efivars efivarfs nodev,noexec,nosuid,nofail   0      0'
fi
echo '# End of file'