grub-multi-install 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #!/bin/bash
  2. #
  3. # Install to multiple ESPs
  4. set -e
  5. # Most of this is copy-paste from grub postinst, sigh.
  6. . /usr/share/debconf/confmodule
  7. ###############################################################################
  8. # COPY FROM POSTINST
  9. ###############################################################################
  10. # This only works on a Linux system with udev running. This is probably the
  11. # vast majority of systems where we need any of this, though, and we fall
  12. # back reasonably gracefully if we don't have it.
  13. cached_available_ids=
  14. available_ids()
  15. {
  16. local id path
  17. if [ "$cached_available_ids" ]; then
  18. echo "$cached_available_ids"
  19. return
  20. fi
  21. [ -d /dev/disk/by-id ] || return
  22. cached_available_ids="$(
  23. for path in /dev/disk/by-id/*; do
  24. [ -e "$path" ] || continue
  25. printf '%s %s\n' "$path" "$(readlink -f "$path")"
  26. done | sort -k2 -s -u | cut -d' ' -f1
  27. )"
  28. echo "$cached_available_ids"
  29. }
  30. # Returns non-zero and no output if no mapping can be found.
  31. device_to_id()
  32. {
  33. local id
  34. for id in $(available_ids); do
  35. if [ "$(readlink -f "$id")" = "$(readlink -f "$1")" ]; then
  36. echo "$id"
  37. return 0
  38. fi
  39. done
  40. # Fall back to the plain device name if there's no by-id link for it.
  41. if [ -e "$1" ]; then
  42. echo "$1"
  43. return 0
  44. fi
  45. return 1
  46. }
  47. # for Linux
  48. sysfs_size()
  49. {
  50. local num_sectors sector_size size
  51. # Try to find out the size without relying on a partitioning tool being
  52. # installed. This isn't too hard on Linux 2.6 with sysfs, but we have to
  53. # try a couple of variants on detection of the sector size.
  54. if [ -e "$1/size" ]; then
  55. num_sectors="$(cat "$1/size")"
  56. sector_size=512
  57. if [ -e "$1/queue/logical_block_size" ]; then
  58. sector_size="$(cat "$1/queue/logical_block_size")"
  59. elif [ -e "$1/queue/hw_sector_size" ]; then
  60. sector_size="$(cat "$1/queue/hw_sector_size")"
  61. fi
  62. size="$(expr "$num_sectors" \* "$sector_size" / 1000 / 1000)"
  63. fi
  64. [ "$size" ] || size='???'
  65. echo "$size"
  66. }
  67. # for kFreeBSD
  68. camcontrol_size()
  69. {
  70. local num_sectors sector_size size=
  71. if num_sectors="$(camcontrol readcap "$1" -q -s -N)"; then
  72. sector_size="$(camcontrol readcap "$1" -q -b)"
  73. size="$(expr "$num_sectors" \* "$sector_size" / 1000 / 1000)"
  74. fi
  75. [ "$size" ] || size='???'
  76. echo "$size"
  77. }
  78. maybe_udevadm()
  79. {
  80. if which udevadm >/dev/null 2>&1; then
  81. udevadm "$@" || true
  82. fi
  83. }
  84. # Parse /proc/mounts and find out the mount for the given device.
  85. # The device must be a real device in /dev, not a symlink to one.
  86. get_mounted_device()
  87. {
  88. mountpoint="$1"
  89. cat /proc/mounts | while read -r line; do
  90. set -f
  91. set -- $line
  92. set +f
  93. if [ "$2" = "$mountpoint" ]; then
  94. echo "$1"
  95. break
  96. fi
  97. done
  98. }
  99. ###############################################################################
  100. # New or modified helpers
  101. ###############################################################################
  102. # Fixed: Return nothing if the argument is empty
  103. get_mountpoint()
  104. {
  105. local relpath boot_mountpoint
  106. if [ -z "$1" ]; then
  107. return
  108. fi
  109. relpath="$(grub-mkrelpath "$1")"
  110. boot_mountpoint="${1#$relpath}"
  111. echo "${boot_mountpoint:-/}"
  112. }
  113. # Returns value in $RET, like a debconf command.
  114. #
  115. # Merged version of describe_disk and describe_partition, as disks can't be
  116. # valid ESPs on their own, so we can't render them as an entry.
  117. describe_efi_system_partition()
  118. {
  119. local disk part id path sysfs_path diskbase partbase size
  120. local disk_basename disk_size model
  121. disk="$1"
  122. part="$2"
  123. id="$3"
  124. path="$4"
  125. # BEGIN: Stolen from describe_disk
  126. model=
  127. case $(uname -s) in
  128. Linux)
  129. sysfs_path="$(maybe_udevadm info -n "$disk" -q path)"
  130. if [ -z "$sysfs_path" ]; then
  131. sysfs_path="/block/$(printf %s "${disk#/dev/}" | sed 's,/,!,g')"
  132. fi
  133. disk_size="$(sysfs_size "/sys$sysfs_path")"
  134. model="$(maybe_udevadm info -n "$disk" -q property | sed -n 's/^ID_MODEL=//p')"
  135. if [ -z "$model" ]; then
  136. model="$(maybe_udevadm info -n "$disk" -q property | sed -n 's/^DM_NAME=//p')"
  137. if [ -z "$model" ]; then
  138. model="$(maybe_udevadm info -n "$disk" -q property | sed -n 's/^MD_NAME=//p')"
  139. if [ -z "$model" ] && which dmsetup >/dev/null 2>&1; then
  140. model="$(dmsetup info -c --noheadings -o name "$disk" 2>/dev/null || true)"
  141. fi
  142. fi
  143. fi
  144. ;;
  145. GNU/kFreeBSD)
  146. disk_basename=$(basename "$disk")
  147. disk_size="$(camcontrol_size "$disk_basename")"
  148. model="$(camcontrol inquiry "$disk_basename" | sed -ne "s/^pass0: <\([^>]*\)>.*/\1/p")"
  149. ;;
  150. esac
  151. [ "$model" ] || model='???'
  152. # END: Stolen from describe_disk
  153. sysfs_path="$(maybe_udevadm info -n "$part" -q path)"
  154. if [ -z "$sysfs_path" ]; then
  155. diskbase="${disk#/dev/}"
  156. diskbase="$(printf %s "$diskbase" | sed 's,/,!,g')"
  157. partbase="${part#/dev/}"
  158. partbase="$(printf %s "$partbase" | sed 's,/,!,g')"
  159. sysfs_path="/block/$diskbase/$partbase"
  160. fi
  161. size="$(sysfs_size "/sys$sysfs_path")"
  162. db_subst grub-efi/partition_description DEVICE "$part"
  163. db_subst grub-efi/partition_description SIZE "$size"
  164. db_subst grub-efi/partition_description PATH "$path"
  165. db_subst grub-efi/partition_description DISK_MODEL "$model"
  166. db_subst grub-efi/partition_description DISK_SIZE "$disk_size"
  167. db_metaget grub-efi/partition_description description
  168. }
  169. # Parse /proc/mounts and find out the mount for the given device.
  170. # The device must be a real device in /dev, not a symlink to one.
  171. find_mount_point()
  172. {
  173. real_device="$1"
  174. cat /proc/mounts | while read -r line; do
  175. set -f
  176. set -- $line
  177. set +f
  178. if [ "$1" = "$real_device" -a "$3" = "vfat" ]; then
  179. echo "$2"
  180. break
  181. fi
  182. done
  183. }
  184. # Return all devices that are a valid ESP
  185. usable_efi_system_partitions()
  186. {
  187. local last_partition path partition partition_id
  188. local ID_PART_ENTRY_TYPE ID_PART_ENTRY_SCHEME
  189. last_partition=
  190. (
  191. for partition in /dev/disk/by-id/*; do
  192. eval "$(udevadm info -q property -n "$partition" | grep -E '^ID_PART_ENTRY_(TYPE|SCHEME)=')"
  193. if [ -z "$ID_PART_ENTRY_TYPE" -o -z "$ID_PART_ENTRY_SCHEME" -o \
  194. \( "$ID_PART_ENTRY_SCHEME" != gpt -a "$ID_PART_ENTRY_SCHEME" != dos \) -o \
  195. \( "$ID_PART_ENTRY_SCHEME" = gpt -a "$ID_PART_ENTRY_TYPE" != c12a7328-f81f-11d2-ba4b-00a0c93ec93b \) -o \
  196. \( "$ID_PART_ENTRY_SCHEME" = dos -a "$ID_PART_ENTRY_TYPE" != 0xef \) ]; then
  197. continue
  198. fi
  199. # unify the partition id
  200. partition_id="$(device_to_id "$partition" || true)"
  201. real_device="$(readlink -f "$partition")"
  202. path="$(find_mount_point $real_device)"
  203. echo "$path:$partition_id"
  204. done
  205. ) | sort -t: -k2 -u
  206. }
  207. ###############################################################################
  208. # MAGIC SCRIPT
  209. ###############################################################################
  210. FALLBACK_MOUNTPOINT=/var/lib/grub/esp
  211. # Initial install/upgrade from /boot/efi?
  212. db_fget grub-efi/install_devices seen
  213. seen="$RET"
  214. # Get configured value
  215. question=grub-efi/install_devices
  216. priority=high
  217. db_get grub-efi/install_devices
  218. valid=1
  219. # We either migrate /boot/efi over, or we check if we have invalid devices
  220. if [ -z "$RET" ] && [ "$seen" != "true" ]; then
  221. echo "Trying to migrate /boot/efi into esp config"
  222. esp="$(get_mounted_device /boot/efi)"
  223. if [ "$esp" ]; then
  224. esp="$(device_to_id "$esp")"
  225. fi
  226. if [ "$esp" ]; then
  227. db_set grub-efi/install_devices "$esp"
  228. db_fset grub-efi/install_devices seen true
  229. RET="$esp"
  230. fi
  231. else
  232. for device in $RET; do
  233. if [ ! -e "${device%,}" ]; then
  234. valid=0
  235. break
  236. fi
  237. done
  238. fi
  239. # If /boot/efi points to a device that's not in the list, trigger the
  240. # install_devices_disks_changed prompt below, but add the device behind
  241. # /boot/efi to the defaults.
  242. boot_efi_device=$(get_mounted_device /boot/efi || true)
  243. if [ "$boot_efi_device" ]; then
  244. for device in $RET; do
  245. device="${device%,}"
  246. real_device="$(readlink -f "$device" || true)"
  247. if [ "$real_device" = "$boot_efi_device" ]; then
  248. boot_efi_device=""
  249. break
  250. fi
  251. done
  252. if [ "$boot_efi_device" ]; then
  253. boot_efi_device="$(device_to_id "$boot_efi_device" || true)"
  254. if [ "$RET" ]; then
  255. RET="$RET, $boot_efi_device"
  256. else
  257. RET="$boot_efi_device"
  258. fi
  259. valid=0
  260. fi
  261. fi
  262. if [ "$valid" = 0 ]; then
  263. question=grub-efi/install_devices_disks_changed
  264. priority=critical
  265. db_set "$question" "$RET"
  266. db_fset "$question" seen false
  267. db_fset grub-efi/install_devices_empty seen false
  268. fi
  269. while :; do
  270. ids=
  271. descriptions=
  272. partitions="$(usable_efi_system_partitions)"
  273. for partition_pair in $partitions; do
  274. partition_id="${partition_pair#*:}"
  275. device="${partition_id%%-part*}"
  276. ids="${ids:+$ids, }$partition_id"
  277. describe_efi_system_partition "$(readlink -f "$device")" "$(readlink -f "$partition_id")" "$partition_id" "$(get_mountpoint "${partition_pair%%:*}")"
  278. RET="$(printf %s "$RET" | sed 's/,/\\,/g')"
  279. descriptions="${descriptions:+$descriptions, }$RET"
  280. done
  281. db_subst "$question" RAW_CHOICES "$ids"
  282. db_subst "$question" CHOICES "$descriptions"
  283. db_input "$priority" "$question" || true
  284. db_go
  285. db_get "$question"
  286. # Run the installer
  287. failed_devices=
  288. for i in `echo $RET | sed -e 's/, / /g'` ; do
  289. real_device="$(readlink -f "$i")"
  290. mntpoint=$(find_mount_point $real_device)
  291. if [ -z "$mntpoint" ]; then
  292. mntpoint=$FALLBACK_MOUNTPOINT
  293. mount $real_device $mntpoint
  294. fi
  295. echo "Installing grub to $mntpoint." >&2
  296. if _UBUNTU_ALTERNATIVE_ESPS="$RET" grub-install --efi-directory=$mntpoint "$@" ; then
  297. # We just installed GRUB 2; then also generate grub.cfg.
  298. touch /boot/grub/grub.cfg
  299. else
  300. failed_devices="$failed_devices $real_device"
  301. fi
  302. if [ "$mntpoint" = "$FALLBACK_MOUNTPOINT" ]; then
  303. umount $mntpoint
  304. fi
  305. done
  306. if [ "$question" != grub-efi/install_devices ] && [ "$RET" ]; then
  307. # XXX cjwatson 2019-02-26: The description of
  308. # grub-efi/install_devices_disks_changed ought to explain that
  309. # selecting no devices will leave the configuration unchanged
  310. # so that you'll be prompted again next time, but it's a bit
  311. # close to the Debian 10 release to be introducing new
  312. # translatable text. For now, it should be sufficient to
  313. # avoid losing configuration data.
  314. db_set grub-efi/install_devices "$RET"
  315. db_fset grub-efi/install_devices seen true
  316. fi
  317. if [ "$failed_devices" ]; then
  318. db_subst grub-efi/install_devices_failed FAILED_DEVICES "$failed_devices"
  319. db_fset grub-efi/install_devices_failed seen false
  320. if db_input critical grub-efi/install_devices_failed; then
  321. db_go
  322. db_get grub-efi/install_devices_failed
  323. if [ "$RET" = true ]; then
  324. break
  325. else
  326. db_fset "$question" seen false
  327. db_fset grub-efi/install_devices_failed seen false
  328. continue
  329. fi
  330. else
  331. break # noninteractive
  332. fi
  333. fi
  334. db_get "$question"
  335. if [ -z "$RET" ]; then
  336. # Reset the seen flag if the current answer is false, since
  337. # otherwise we'll loop with no indication of why.
  338. db_get grub-efi/install_devices_empty
  339. if [ "$RET" = false ]; then
  340. db_fset grub-efi/install_devices_empty seen false
  341. fi
  342. if db_input critical grub-efi/install_devices_empty; then
  343. db_go
  344. db_get grub-efi/install_devices_empty
  345. if [ "$RET" = true ]; then
  346. break
  347. else
  348. db_fset "$question" seen false
  349. db_fset grub-efi/install_devices_empty seen false
  350. fi
  351. else
  352. break # noninteractive
  353. fi
  354. else
  355. break
  356. fi
  357. done