~sotirisp/bootsh

ref: 0ca6e8e3bc76413244a396323d599252fb3c0529 bootsh/bootsh -rwxr-xr-x 4.8 KiB
0ca6e8e3Sotiris Papatheodorou Improve code style 11 months ago
                                                                                
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/bin/sh
# bootsh - create a bootable removable device from an image file
# SPDX-FileCopyrightText: 2021 Sotiris Papatheodorou
# SPDX-License-Identifier: GPL-3.0-or-later
set -eu

# Usage: check_dependencies
# Check if the depndencies are present and exit with an appropriate error
# message otherwise.
check_dependencies() {
	printf 'column\nlsblk\nsync\n' | while IFS= read -r dep; do
		if ! command -v "$dep" > /dev/null 2>&1; then
			printf 'Error: dependency %s not found\n' "$dep"
			exit 1
		fi
	done
}

# Usage: trim_string STRING
# SPDX-FileCopyrightText: 2019 Dylan Araps
# SPDX-License-Identifier: MIT
# https://github.com/dylanaraps/pure-sh-bible#trim-leading-and-trailing-white-space-from-string
trim_string() {
    trim=${1#${1%%[![:space:]]*}}
    trim=${trim%${trim##*[![:space:]]}}
    printf '%s\n' "$trim"
}

# Usage: print_block_devices
# Pretty-print all block devices.
print_block_devices() {
	if command -v udevadm > /dev/null 2>&1; then
		udevadm settle > /dev/null 2>&1
	fi
	# The following command was copied from bootiso
	# SPDX-FileCopyrightText: 2018-2020 Jules Randolph
	# SPDX-License-Identifier: GPL-3.0-or-later
	lsblk --nodeps --list --output NAME,VENDOR,MODEL,SIZE,TRAN,HOTPLUG
}

# Usage: print_removable_devices
# Pretty-print all removable block devices.
print_removable_devices() {
	devices=$(print_block_devices)
	# Keep the header and all devices that are hotpluggable.
	printf '%s\n' "$devices" | head -n 1 | sed 's/ *HOTPLUG$//'
	printf '%s\n' "$devices" | grep ' *1$' | grep -v 'sata *1$' |  sed 's/ *1$//'
}

# Usage: removable_device_names
# Print the names of all removable devices, one per line.
removable_device_names() {
	print_removable_devices | tail -n +2 | awk '{ print $1 }'
}

# Usage: device_capacity NAME
# Print the capacity of device NAME in human-readable form.
device_capacity() {
	lsblk --nodeps --list --output NAME,SIZE | grep -F "$1" | awk '{ print $2 }'
}

# Usage: device_mount_points NAME
# Print the mounted partitions of device NAME and their respective mount
# points, separated by tabs, one per line.
device_mount_points() {
	mount | grep -F "$1" | sed 's|^\('"$1"'[0-9][0-9]*\) on \(/.*\) type .*$|\1\t\2|'
}

# Usage: valid_input INPUT VALID_INPUTS
# Return 0 when INPUT exactly matches one of the lines in VALID_INPUTS.
valid_input() {
	# Matching INPUT as a fixed pattern can match substrings, e.g. sd matches
	# sda. Matching INPUT as a regular expression can produce weird matches if
	# INPUT is a regular expression. Just do both matches to be more robust.
	printf '%s\n' "$2" | grep -qF "$1" && printf '%s\n' "$2" | grep -q "^$1"'$'
}



check_dependencies

# Check for valid input argument.
if [ "$#" -ne 1 ]; then
	printf 'Usage: %s IMAGE\n' "${0##*/}"
	exit 2
fi
if [ ! -s "$1" ]; then
	printf 'Error: %s must be a non-empty file\n' "$1"
	exit 1
fi

# Find removable devices.
names=$(removable_device_names)
if [ -z "$names" ]; then
	printf 'Error: no removable devices found\n'
	exit 1
fi
names_pretty=$(print_removable_devices)

# Ensure at least one removable device was found.
if [ "$(printf '%s\n' "$names_pretty" | wc -l)" -lt 2 ]; then
	printf 'No removable devices found\n'
	exit 1
fi

# Prompt the user to select the device to write the image to.
target=""
while [ -z "$target" ]; do
	printf '%s\n' "$names_pretty"
	printf 'Enter the NAME of one of the above removable devices or press Ctrl+C to exit:\n'
	read -r device_input
	device_input=$(trim_string "$device_input")
	if valid_input "$device_input" "$names"; then
		# Ask the user to confirm the device's capacity to avoid writing to the
		# wrong device.
		while true; do
			capacity=$(device_capacity "$device_input")
			printf '\nSelected removable device %s with capacity %s\n' \
				"$device_input" "$capacity"
			printf 'Enter the device capacity (%s) to continue or press Ctrl+C to exit:\n' \
				"$capacity"
			read -r capacity_input
			capacity_input=$(trim_string "$capacity_input")
			if [ "$capacity_input" = "$capacity" ]; then
				target=/dev/"$device_input"
				break
			fi
		done
	fi
done

# Unmount any mounted partitions of the target device.
device_mount_points "$target" | while IFS= read -r line; do
	partition=$(printf '%s\n' "$line" | awk 'BEGIN { FS="\t" }; { print $1 }')
	mount_point=$(printf '%s\n' "$line" | awk 'BEGIN { FS="\t" }; { print $2 }')
	sync
	if ! umount "$mount_point" > /dev/null 2>&1; then
		printf 'Error: could not unmount partition %s mounted on %s\n' \
			"$partition" "$mount_point"
		exit 1
	fi
done

# Test if privilege elevation is needed.
superuser_command=""
if [ "$(id -u)" != "0" ]; then
	if command -v sudo > /dev/null 2>&1; then
		superuser_command="sudo"
	elif command -v doas > /dev/null 2>&1; then
		superuser_command="doas"
	fi
fi

# Write the image to the device.
printf '\nWriting image %s to device %s\n' "$1" "$target"
"$superuser_command" dd bs=1024k if="$1" of="$target" > /dev/null
sync
printf 'Done! You may remove device %s\n' "$target"