growpart.go 29 KB

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