growpart 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. #!/bin/sh
  2. # Copyright (C) 2011 Canonical Ltd.
  3. # Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
  4. #
  5. # Authors: Scott Moser <[email protected]>
  6. # Juerg Haefliger <[email protected]>
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation, version 3 of the License.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. # the fudge factor. if within this many bytes dont bother
  20. FUDGE=${GROWPART_FUDGE:-$((1024*1024))}
  21. TEMP_D=""
  22. RESTORE_FUNC=""
  23. RESTORE_HUMAN=""
  24. VERBOSITY=0
  25. DISK=""
  26. PART=""
  27. PT_UPDATE=false
  28. DRY_RUN=0
  29. SFDISK_VERSION=""
  30. SFDISK_2_26="22600"
  31. SFDISK_V_WORKING_GPT="22603"
  32. MBR_BACKUP=""
  33. GPT_BACKUP=""
  34. _capture=""
  35. error() {
  36. echo "$@" 1>&2
  37. }
  38. fail() {
  39. [ $# -eq 0 ] || echo "FAILED:" "$@"
  40. exit 2
  41. }
  42. nochange() {
  43. echo "NOCHANGE:" "$@"
  44. exit 1
  45. }
  46. changed() {
  47. echo "CHANGED:" "$@"
  48. exit 0
  49. }
  50. change() {
  51. echo "CHANGE:" "$@"
  52. exit 0
  53. }
  54. cleanup() {
  55. if [ -n "${RESTORE_FUNC}" ]; then
  56. error "***** WARNING: Resize failed, attempting to revert ******"
  57. if ${RESTORE_FUNC} ; then
  58. error "***** Appears to have gone OK ****"
  59. else
  60. error "***** FAILED! ******"
  61. if [ -n "${RESTORE_HUMAN}" -a -f "${RESTORE_HUMAN}" ]; then
  62. error "**** original table looked like: ****"
  63. cat "${RESTORE_HUMAN}" 1>&2
  64. else
  65. error "We seem to have not saved the partition table!"
  66. fi
  67. fi
  68. fi
  69. [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
  70. }
  71. debug() {
  72. local level=${1}
  73. shift
  74. [ "${level}" -gt "${VERBOSITY}" ] && return
  75. if [ "${DEBUG_LOG}" ]; then
  76. echo "$@" >>"${DEBUG_LOG}"
  77. else
  78. error "$@"
  79. fi
  80. }
  81. debugcat() {
  82. local level="$1"
  83. shift;
  84. [ "${level}" -gt "$VERBOSITY" ] && return
  85. if [ "${DEBUG_LOG}" ]; then
  86. cat "$@" >>"${DEBUG_LOG}"
  87. else
  88. cat "$@" 1>&2
  89. fi
  90. }
  91. mktemp_d() {
  92. # just a mktemp -d that doens't need mktemp if its not there.
  93. _RET=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX" 2>/dev/null) &&
  94. return
  95. _RET=$(umask 077 && t="${TMPDIR:-/tmp}/${0##*/}.$$" &&
  96. mkdir "${t}" && echo "${t}")
  97. return
  98. }
  99. Usage() {
  100. cat <<EOF
  101. ${0##*/} disk partition
  102. rewrite partition table so that partition takes up all the space it can
  103. options:
  104. -h | --help print Usage and exit
  105. --fudge F if part could be resized, but change would be
  106. less than 'F' bytes, do not resize (default: ${FUDGE})
  107. -N | --dry-run only report what would be done, show new 'sfdisk -d'
  108. -v | --verbose increase verbosity / debug
  109. -u | --update R update the the kernel partition table info after growing
  110. this requires kernel support and 'partx --update'
  111. R is one of:
  112. - 'auto' : [default] update partition if possible
  113. - 'force' : try despite sanity checks (fail on failure)
  114. - 'off' : do not attempt
  115. - 'on' : fail if sanity checks indicate no support
  116. Example:
  117. - ${0##*/} /dev/sda 1
  118. Resize partition 1 on /dev/sda
  119. EOF
  120. }
  121. bad_Usage() {
  122. Usage 1>&2
  123. error "$@"
  124. exit 2
  125. }
  126. sfdisk_restore_legacy() {
  127. sfdisk --no-reread "${DISK}" -I "${MBR_BACKUP}"
  128. }
  129. sfdisk_restore() {
  130. # files are named: sfdisk-<device>-<offset>.bak
  131. local f="" offset="" fails=0
  132. for f in "${MBR_BACKUP}"*.bak; do
  133. [ -f "$f" ] || continue
  134. offset=${f##*-}
  135. offset=${offset%.bak}
  136. [ "$offset" = "$f" ] && {
  137. error "WARN: confused by file $f";
  138. continue;
  139. }
  140. dd "if=$f" "of=${DISK}" seek=$(($offset)) bs=1 conv=notrunc ||
  141. { error "WARN: failed restore from $f"; fails=$(($fails+1)); }
  142. done
  143. return $fails
  144. }
  145. sfdisk_worked_but_blkrrpart_failed() {
  146. local ret="$1" output="$2"
  147. # exit code found was just 1, but dont insist on that
  148. #[ $ret -eq 1 ] || return 1
  149. # Successfully wrote the new partition table
  150. grep -qi "Success.* wrote.* new.* partition" "$output" &&
  151. grep -qi "BLKRRPART: Device or resource busy" "$output"
  152. return
  153. }
  154. get_sfdisk_version() {
  155. # set SFDISK_VERSION to MAJOR*10000+MINOR*100+MICRO
  156. local out oifs="$IFS" ver=""
  157. [ -n "$SFDISK_VERSION" ] && return 0
  158. # expected output: sfdisk from util-linux 2.25.2
  159. out=$(sfdisk --version) ||
  160. { error "failed to get sfdisk version"; return 1; }
  161. set -- $out
  162. ver=$4
  163. case "$ver" in
  164. [0-9]*.[0-9]*.[0-9]|[0-9].[0-9]*)
  165. IFS="."; set -- $ver; IFS="$oifs"
  166. SFDISK_VERSION=$(($1*10000+$2*100+${3:-0}))
  167. return 0;;
  168. *) error "unexpected output in sfdisk --version [$out]"
  169. return 1;;
  170. esac
  171. }
  172. resize_sfdisk() {
  173. local humanpt="${TEMP_D}/recovery"
  174. local mbr_backup="${TEMP_D}/orig.save"
  175. local restore_func=""
  176. local format="$1"
  177. local change_out=${TEMP_D}/change.out
  178. local dump_out=${TEMP_D}/dump.out
  179. local new_out=${TEMP_D}/new.out
  180. local dump_mod=${TEMP_D}/dump.mod
  181. local tmp="${TEMP_D}/tmp.out"
  182. local err="${TEMP_D}/err.out"
  183. local mbr_max_512="4294967296"
  184. local pt_start pt_size pt_end max_end new_size change_info dpart
  185. local sector_num sector_size disk_size tot out
  186. rqe sfd_list sfdisk --list --unit=S "$DISK" >"$tmp" ||
  187. fail "failed: sfdisk --list $DISK"
  188. if [ "${SFDISK_VERSION}" -lt ${SFDISK_2_26} ]; then
  189. # exected output contains: Units: sectors of 512 bytes, ...
  190. out=$(awk '$1 == "Units:" && $5 ~ /bytes/ { print $4 }' "$tmp") ||
  191. fail "failed to read sfdisk output"
  192. if [ -z "$out" ]; then
  193. error "WARN: sector size not found in sfdisk output, assuming 512"
  194. sector_size=512
  195. else
  196. sector_size="$out"
  197. fi
  198. local _w _cyl _w1 _heads _w2 sectors _w3 t s
  199. # show-size is in units of 1024 bytes (same as /proc/partitions)
  200. t=$(sfdisk --show-size "${DISK}") ||
  201. fail "failed: sfdisk --show-size $DISK"
  202. disk_size=$((t*1024))
  203. sector_num=$(($disk_size/$sector_size))
  204. msg="disk size '$disk_size' not evenly div by sector size '$sector_size'"
  205. [ "$((${disk_size}%${sector_size}))" -eq 0 ] ||
  206. error "WARN: $msg"
  207. restore_func=sfdisk_restore_legacy
  208. else
  209. # --list first line output:
  210. # Disk /dev/vda: 20 GiB, 21474836480 bytes, 41943040 sectors
  211. local _x
  212. read _x _x _x _x disk_size _x sector_num _x < "$tmp"
  213. sector_size=$((disk_size/$sector_num))
  214. restore_func=sfdisk_restore
  215. fi
  216. debug 1 "$sector_num sectors of $sector_size. total size=${disk_size} bytes"
  217. [ $(($disk_size/512)) -gt $mbr_max_512 ] &&
  218. debug 1 "WARN: disk is larger than 2TB. additional space will go unused."
  219. rqe sfd_dump sfdisk --unit=S --dump "${DISK}" >"${dump_out}" ||
  220. fail "failed to dump sfdisk info for ${DISK}"
  221. RESTORE_HUMAN="$dump_out"
  222. {
  223. echo "## sfdisk --unit=S --dump ${DISK}"
  224. cat "${dump_out}"
  225. } >"$humanpt"
  226. [ $? -eq 0 ] || fail "failed to save sfdisk -d output"
  227. RESTORE_HUMAN="$humanpt"
  228. debugcat 1 "$humanpt"
  229. sed -e 's/,//g; s/start=/start /; s/size=/size /' "${dump_out}" \
  230. >"${dump_mod}" ||
  231. fail "sed failed on dump output"
  232. dpart="${DISK}${PART}" # disk and partition number
  233. if [ -b "${DISK}p${PART}" -a "${DISK%[0-9]}" != "${DISK}" ]; then
  234. # for block devices that end in a number (/dev/nbd0)
  235. # the partition is "<name>p<partition_number>" (/dev/nbd0p1)
  236. dpart="${DISK}p${PART}"
  237. elif [ "${DISK#/dev/loop[0-9]}" != "${DISK}" ]; then
  238. # for /dev/loop devices, sfdisk output will be <name>p<number>
  239. # format also, even though there is not a device there.
  240. dpart="${DISK}p${PART}"
  241. fi
  242. pt_start=$(awk '$1 == pt { print $4 }' "pt=${dpart}" <"${dump_mod}") &&
  243. pt_size=$(awk '$1 == pt { print $6 }' "pt=${dpart}" <"${dump_mod}") &&
  244. [ -n "${pt_start}" -a -n "${pt_size}" ] &&
  245. pt_end=$((${pt_size}+${pt_start})) ||
  246. fail "failed to get start and end for ${dpart} in ${DISK}"
  247. # find the minimal starting location that is >= pt_end
  248. max_end=$(awk '$3 == "start" { if($4 >= pt_end && $4 < min)
  249. { min = $4 } } END { printf("%s\n",min); }' \
  250. min=${sector_num} pt_end=${pt_end} "${dump_mod}") &&
  251. [ -n "${max_end}" ] ||
  252. fail "failed to get max_end for partition ${PART}"
  253. mbr_max_sectors=$((mbr_max_512*$((sector_size/512))))
  254. if [ "$max_end" -gt "$mbr_max_sectors" ]; then
  255. max_end=$mbr_max_sectors
  256. fi
  257. if [ "$format" = "gpt" ]; then
  258. # sfdisk respects 'last-lba' in input, and complains about
  259. # partitions that go past that. without it, it does the right thing.
  260. sed -i '/^last-lba:/d' "$dump_out" ||
  261. fail "failed to remove last-lba from output"
  262. fi
  263. local gpt_second_size="33"
  264. if [ "${max_end}" -gt "$((${sector_num}-${gpt_second_size}))" ]; then
  265. # if mbr allow subsequent conversion to gpt without shrinking the
  266. # partition. safety net at cost of 33 sectors, seems reasonable.
  267. # if gpt, we can't write there anyway.
  268. debug 1 "padding ${gpt_second_size} sectors for gpt secondary header"
  269. max_end=$((${sector_num}-${gpt_second_size}))
  270. fi
  271. debug 1 "max_end=${max_end} tot=${sector_num} pt_end=${pt_end}" \
  272. "pt_start=${pt_start} pt_size=${pt_size}"
  273. [ $((${pt_end})) -eq ${max_end} ] &&
  274. nochange "partition ${PART} is size ${pt_size}. it cannot be grown"
  275. [ $((${pt_end}+(${FUDGE}/$sector_size))) -gt ${max_end} ] &&
  276. nochange "partition ${PART} could only be grown by" \
  277. "$((${max_end}-${pt_end})) [fudge=$((${FUDGE}/$sector_size))]"
  278. # now, change the size for this partition in ${dump_out} to be the
  279. # new size
  280. new_size=$((${max_end}-${pt_start}))
  281. sed "\|^\s*${dpart} |s/${pt_size},/${new_size},/" "${dump_out}" \
  282. >"${new_out}" ||
  283. fail "failed to change size in output"
  284. change_info="partition=${PART} start=${pt_start} old: size=${pt_size} end=${pt_end} new: size=${new_size},end=${max_end}"
  285. if [ ${DRY_RUN} -ne 0 ]; then
  286. echo "CHANGE: ${change_info}"
  287. {
  288. echo "# === old sfdisk -d ==="
  289. cat "${dump_out}"
  290. echo "# === new sfdisk -d ==="
  291. cat "${new_out}"
  292. } 1>&2
  293. exit 0
  294. fi
  295. MBR_BACKUP="${mbr_backup}"
  296. LANG=C sfdisk --no-reread "${DISK}" --force \
  297. -O "${mbr_backup}" <"${new_out}" >"${change_out}" 2>&1
  298. ret=$?
  299. [ $ret -eq 0 ] || RESTORE_FUNC="${restore_func}"
  300. if [ $ret -eq 0 ]; then
  301. :
  302. elif $PT_UPDATE &&
  303. sfdisk_worked_but_blkrrpart_failed "$ret" "${change_out}"; then
  304. # if the command failed, but it looks like only because
  305. # the device was busy and we have pt_update, then go on
  306. debug 1 "sfdisk failed, but likely only because of blkrrpart"
  307. else
  308. error "attempt to resize ${DISK} failed. sfdisk output below:"
  309. sed 's,^,| ,' "${change_out}" 1>&2
  310. fail "failed to resize"
  311. fi
  312. rq pt_update pt_update "$DISK" "$PART" ||
  313. fail "pt_resize failed"
  314. RESTORE_FUNC=""
  315. changed "${change_info}"
  316. # dump_out looks something like:
  317. ## partition table of /tmp/out.img
  318. #unit: sectors
  319. #
  320. #/tmp/out.img1 : start= 1, size= 48194, Id=83
  321. #/tmp/out.img2 : start= 48195, size= 963900, Id=83
  322. #/tmp/out.img3 : start= 1012095, size= 305235, Id=82
  323. #/tmp/out.img4 : start= 1317330, size= 771120, Id= 5
  324. #/tmp/out.img5 : start= 1317331, size= 642599, Id=83
  325. #/tmp/out.img6 : start= 1959931, size= 48194, Id=83
  326. #/tmp/out.img7 : start= 2008126, size= 80324, Id=83
  327. }
  328. gpt_restore() {
  329. sgdisk -l "${GPT_BACKUP}" "${DISK}"
  330. }
  331. resize_sgdisk() {
  332. GPT_BACKUP="${TEMP_D}/pt.backup"
  333. local pt_info="${TEMP_D}/pt.info"
  334. local pt_pretend="${TEMP_D}/pt.pretend"
  335. local pt_data="${TEMP_D}/pt.data"
  336. local out="${TEMP_D}/out"
  337. local dev="disk=${DISK} partition=${PART}"
  338. local pt_start pt_end pt_size last pt_max code guid name new_size
  339. local old new change_info sector_size
  340. # Dump the original partition information and details to disk. This is
  341. # used in case something goes wrong and human interaction is required
  342. # to revert any changes.
  343. rqe sgd_info sgdisk "--info=${PART}" --print "${DISK}" >"${pt_info}" ||
  344. fail "${dev}: failed to dump original sgdisk info"
  345. RESTORE_HUMAN="${pt_info}"
  346. sector_size=$(awk '$0 ~ /^Logical sector size:.*bytes/ { print $4 }' \
  347. "$pt_info") && [ -n "$sector_size" ] || {
  348. sector_size=512
  349. error "WARN: did not find sector size, assuming 512"
  350. }
  351. debug 1 "$dev: original sgdisk info:"
  352. debugcat 1 "${pt_info}"
  353. # Pretend to move the backup GPT header to the end of the disk and dump
  354. # the resulting partition information. We use this info to determine if
  355. # we have to resize the partition.
  356. rqe sgd_pretend sgdisk --pretend --move-second-header \
  357. --print "${DISK}" >"${pt_pretend}" ||
  358. fail "${dev}: failed to dump pretend sgdisk info"
  359. debug 1 "$dev: pretend sgdisk info"
  360. debugcat 1 "${pt_pretend}"
  361. # Extract the partition data from the pretend dump
  362. awk 'found { print } ; $1 == "Number" { found = 1 }' \
  363. "${pt_pretend}" >"${pt_data}" ||
  364. fail "${dev}: failed to parse pretend sgdisk info"
  365. # Get the start and end sectors of the partition to be grown
  366. pt_start=$(awk '$1 == '"${PART}"' { print $2 }' "${pt_data}") &&
  367. [ -n "${pt_start}" ] ||
  368. fail "${dev}: failed to get start sector"
  369. pt_end=$(awk '$1 == '"${PART}"' { print $3 }' "${pt_data}") &&
  370. [ -n "${pt_end}" ] ||
  371. fail "${dev}: failed to get end sector"
  372. pt_size="$((${pt_end} - ${pt_start}))"
  373. # Get the last usable sector
  374. last=$(awk '/last usable sector is/ { print $NF }' \
  375. "${pt_pretend}") && [ -n "${last}" ] ||
  376. fail "${dev}: failed to get last usable sector"
  377. # Find the minimal start sector that is >= pt_end
  378. pt_max=$(awk '{ if ($2 >= pt_end && $2 < min) { min = $2 } } END \
  379. { print min }' min="${last}" pt_end="${pt_end}" \
  380. "${pt_data}") && [ -n "${pt_max}" ] ||
  381. fail "${dev}: failed to find max end sector"
  382. debug 1 "${dev}: pt_start=${pt_start} pt_end=${pt_end}" \
  383. "pt_size=${pt_size} pt_max=${pt_max} last=${last}"
  384. # Check if the partition can be grown
  385. [ "${pt_end}" -eq "${pt_max}" ] &&
  386. nochange "${dev}: size=${pt_size}, it cannot be grown"
  387. [ "$((${pt_end} + ${FUDGE}/${sector_size}))" -gt "${pt_max}" ] &&
  388. nochange "${dev}: could only be grown by" \
  389. "$((${pt_max} - ${pt_end})) [fudge=$((${FUDGE}/$sector_size))]"
  390. # The partition can be grown if we made it here. Get some more info
  391. # about it so we can do it properly.
  392. # FIXME: Do we care about the attribute flags?
  393. code=$(awk '/^Partition GUID code:/ { print $4 }' "${pt_info}")
  394. guid=$(awk '/^Partition unique GUID:/ { print $4 }' "${pt_info}")
  395. name=$(awk '/^Partition name:/ { gsub(/'"'"'/, "") ; \
  396. if (NF >= 3) print substr($0, index($0, $3)) }' "${pt_info}")
  397. [ -n "${code}" -a -n "${guid}" ] ||
  398. fail "${dev}: failed to parse sgdisk details"
  399. debug 1 "${dev}: code=${code} guid=${guid} name='${name}'"
  400. local wouldrun=""
  401. [ "$DRY_RUN" -ne 0 ] && wouldrun="would-run"
  402. # Calculate the new size of the partition
  403. new_size=$((${pt_max} - ${pt_start}))
  404. old="old: size=${pt_size},end=${pt_end}"
  405. new="new: size=${new_size},end=${pt_max}"
  406. change_info="${dev}: start=${pt_start} ${old} ${new}"
  407. # Backup the current partition table, we're about to modify it
  408. rq sgd_backup $wouldrun sgdisk "--backup=${GPT_BACKUP}" "${DISK}" ||
  409. fail "${dev}: failed to backup the partition table"
  410. # Modify the partition table. We do it all in one go (the order is
  411. # important!):
  412. # - move the GPT backup header to the end of the disk
  413. # - delete the partition
  414. # - recreate the partition with the new size
  415. # - set the partition code
  416. # - set the partition GUID
  417. # - set the partition name
  418. rq sgdisk_mod $wouldrun sgdisk --move-second-header "--delete=${PART}" \
  419. "--new=${PART}:${pt_start}:${pt_max}" \
  420. "--typecode=${PART}:${code}" \
  421. "--partition-guid=${PART}:${guid}" \
  422. "--change-name=${PART}:${name}" "${DISK}" &&
  423. rq pt_update $wouldrun pt_update "$DISK" "$PART" || {
  424. RESTORE_FUNC=gpt_restore
  425. fail "${dev}: failed to repartition"
  426. }
  427. # Dry run
  428. [ "${DRY_RUN}" -ne 0 ] && change "${change_info}"
  429. changed "${change_info}"
  430. }
  431. kver_to_num() {
  432. local kver="$1" maj="" min="" mic="0"
  433. kver=${kver%%-*}
  434. maj=${kver%%.*}
  435. min=${kver#${maj}.}
  436. min=${min%%.*}
  437. mic=${kver#${maj}.${min}.}
  438. [ "$kver" = "$mic" ] && mic=0
  439. _RET=$(($maj*1000*1000+$min*1000+$mic))
  440. }
  441. kver_cmp() {
  442. local op="$2" n1="" n2=""
  443. kver_to_num "$1"
  444. n1="$_RET"
  445. kver_to_num "$3"
  446. n2="$_RET"
  447. [ $n1 $op $n2 ]
  448. }
  449. rq() {
  450. # runquieterror(label, command)
  451. # gobble stderr of a command unless it errors
  452. local label="$1" ret="" efile=""
  453. efile="$TEMP_D/$label.err"
  454. shift;
  455. local rlabel="running"
  456. [ "$1" = "would-run" ] && rlabel="would-run" && shift
  457. local cmd="" x=""
  458. for x in "$@"; do
  459. [ "${x#* }" != "$x" -o "${x#* \"}" != "$x" ] && x="'$x'"
  460. cmd="$cmd $x"
  461. done
  462. cmd=${cmd# }
  463. debug 2 "$rlabel[$label][$_capture]" "$cmd"
  464. [ "$rlabel" = "would-run" ] && return 0
  465. if [ "${_capture}" = "erronly" ]; then
  466. "$@" 2>"$TEMP_D/$label.err"
  467. ret=$?
  468. else
  469. "$@" >"$TEMP_D/$label.err" 2>&1
  470. ret=$?
  471. fi
  472. if [ $ret -ne 0 ]; then
  473. error "failed [$label:$ret]" "$@"
  474. cat "$efile" 1>&2
  475. fi
  476. return $ret
  477. }
  478. rqe() {
  479. local _capture="erronly"
  480. rq "$@"
  481. }
  482. verify_ptupdate() {
  483. local input="$1" found="" reason="" kver=""
  484. # we can always satisfy 'off'
  485. if [ "$input" = "off" ]; then
  486. _RET="false";
  487. return 0;
  488. fi
  489. if command -v partx >/dev/null 2>&1; then
  490. local out="" ret=0
  491. out=$(partx --help 2>&1)
  492. ret=$?
  493. if [ $ret -eq 0 ]; then
  494. echo "$out" | grep -q -- --update || {
  495. reason="partx has no '--update' flag in usage."
  496. found="off"
  497. }
  498. else
  499. reason="'partx --help' returned $ret. assuming it is old."
  500. found="off"
  501. fi
  502. else
  503. reason="no 'partx' command"
  504. found="off"
  505. fi
  506. if [ -z "$found" ]; then
  507. if [ "$(uname)" != "Linux" ]; then
  508. reason="Kernel is not Linux per uname."
  509. found="off"
  510. fi
  511. fi
  512. if [ -z "$found" ]; then
  513. kver=$(uname -r) || debug 1 "uname -r failed!"
  514. if ! kver_cmp "${kver-0.0.0}" -ge 3.8.0; then
  515. reason="Kernel '$kver' < 3.8.0."
  516. found="off"
  517. fi
  518. fi
  519. if [ -z "$found" ]; then
  520. _RET="true"
  521. return 0
  522. fi
  523. case "$input" in
  524. on) error "$reason"; return 1;;
  525. auto)
  526. _RET="false";
  527. debug 1 "partition update disabled: $reason"
  528. return 0;;
  529. force)
  530. _RET="true"
  531. error "WARNING: ptupdate forced on even though: $reason"
  532. return 0;;
  533. esac
  534. error "unknown input '$input'";
  535. return 1;
  536. }
  537. pt_update() {
  538. local dev="$1" part="$2" update="${3:-$PT_UPDATE}"
  539. if ! $update; then
  540. return 0
  541. fi
  542. # partx only works on block devices (do not run on file)
  543. [ -b "$dev" ] || return 0
  544. partx --update "$part" "$dev"
  545. }
  546. has_cmd() {
  547. command -v "${1}" >/dev/null 2>&1
  548. }
  549. resize_sgdisk_gpt() {
  550. resize_sgdisk gpt
  551. }
  552. resize_sgdisk_dos() {
  553. fail "unable to resize dos label with sgdisk"
  554. }
  555. resize_sfdisk_gpt() {
  556. resize_sfdisk gpt
  557. }
  558. resize_sfdisk_dos() {
  559. resize_sfdisk dos
  560. }
  561. get_table_format() {
  562. local out="" disk="$1"
  563. if has_cmd blkid && out=$(blkid -o value -s PTTYPE "$disk") &&
  564. [ "$out" = "dos" -o "$out" = "gpt" ]; then
  565. _RET="$out"
  566. return
  567. fi
  568. _RET="dos"
  569. if [ ${SFDISK_VERSION} -lt ${SFDISK_2_26} ] &&
  570. out=$(sfdisk --id --force "$disk" 1 2>/dev/null); then
  571. if [ "$out" = "ee" ]; then
  572. _RET="gpt"
  573. else
  574. _RET="dos"
  575. fi
  576. return
  577. elif out=$(LANG=C sfdisk --list "$disk"); then
  578. out=$(echo "$out" | sed -e '/Disklabel type/!d' -e 's/.*: //')
  579. case "$out" in
  580. gpt|dos) _RET="$out";;
  581. *) error "WARN: unknown label $out";;
  582. esac
  583. fi
  584. }
  585. get_resizer() {
  586. local format="$1" user=${2:-"auto"}
  587. case "$user" in
  588. sgdisk) _RET="resize_sgdisk_$format"; return;;
  589. sfdisk) _RET="resize_sfdisk_$format"; return;;
  590. auto) :;;
  591. *) error "unexpected input: '$user'";;
  592. esac
  593. if [ "$format" = "dos" ]; then
  594. _RET="resize_sfdisk_dos"
  595. return 0
  596. fi
  597. if [ "${SFDISK_VERSION}" -ge ${SFDISK_V_WORKING_GPT} ]; then
  598. # sfdisk 2.26.2 works for resize but loses type (LP: #1474090)
  599. _RET="resize_sfdisk_gpt"
  600. elif has_cmd sgdisk; then
  601. _RET="resize_sgdisk_$format"
  602. else
  603. error "no tools available to resize disk with '$format'"
  604. return 1
  605. fi
  606. return 0
  607. }
  608. pt_update="auto"
  609. resizer=${GROWPART_RESIZER:-"auto"}
  610. while [ $# -ne 0 ]; do
  611. cur=${1}
  612. next=${2}
  613. case "$cur" in
  614. -h|--help)
  615. Usage
  616. exit 0
  617. ;;
  618. --fudge)
  619. FUDGE=${next}
  620. shift
  621. ;;
  622. -N|--dry-run)
  623. DRY_RUN=1
  624. ;;
  625. -u|--update|--update=*)
  626. if [ "${cur#--update=}" != "$cur" ]; then
  627. next="${cur#--update=}"
  628. else
  629. shift
  630. fi
  631. case "$next" in
  632. off|auto|force|on) pt_update=$next;;
  633. *) fail "unknown --update option: $next";;
  634. esac
  635. ;;
  636. -v|--verbose)
  637. VERBOSITY=$(($VERBOSITY+1))
  638. ;;
  639. --)
  640. shift
  641. break
  642. ;;
  643. -*)
  644. fail "unknown option ${cur}"
  645. ;;
  646. *)
  647. if [ -z "${DISK}" ]; then
  648. DISK=${cur}
  649. else
  650. [ -z "${PART}" ] || fail "confused by arg ${cur}"
  651. PART=${cur}
  652. fi
  653. ;;
  654. esac
  655. shift
  656. done
  657. [ -n "${DISK}" ] || bad_Usage "must supply disk and partition-number"
  658. [ -n "${PART}" ] || bad_Usage "must supply partition-number"
  659. has_cmd "sfdisk" || fail "sfdisk not found"
  660. get_sfdisk_version || fail
  661. [ -e "${DISK}" ] || fail "${DISK}: does not exist"
  662. [ "${PART#*[!0-9]}" = "${PART}" ] || fail "partition-number must be a number"
  663. verify_ptupdate "$pt_update" || fail
  664. PT_UPDATE=$_RET
  665. debug 1 "update-partition set to $PT_UPDATE"
  666. mktemp_d && TEMP_D="${_RET}" || fail "failed to make temp dir"
  667. trap cleanup 0 # EXIT - some shells may not like 'EXIT' but are ok with 0
  668. # get the ID of the first partition to determine if it's MBR or GPT
  669. get_table_format "$DISK" || fail
  670. format=$_RET
  671. get_resizer "$format" "$resizer" ||
  672. fail "failed to get a resizer for id '$id'"
  673. resizer=$_RET
  674. debug 1 "resizing $PART on $DISK using $resizer"
  675. "$resizer"
  676. # vi: ts=4 noexpandtab