parted.scm 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018, 2019 Mathieu Othacehe <m.othacehe@gmail.com>
  3. ;;; Copyright © 2019, 2020, 2022 Ludovic Courtès <ludo@gnu.org>
  4. ;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr>
  5. ;;; Copyright © 2022 Josselin Poiret <dev@jpoiret.xyz>
  6. ;;;
  7. ;;; This file is part of GNU Guix.
  8. ;;;
  9. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  10. ;;; under the terms of the GNU General Public License as published by
  11. ;;; the Free Software Foundation; either version 3 of the License, or (at
  12. ;;; your option) any later version.
  13. ;;;
  14. ;;; GNU Guix is distributed in the hope that it will be useful, but
  15. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;;; GNU General Public License for more details.
  18. ;;;
  19. ;;; You should have received a copy of the GNU General Public License
  20. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  21. (define-module (gnu installer parted)
  22. #:use-module (gnu installer steps)
  23. #:use-module (gnu installer utils)
  24. #:use-module (gnu installer newt page)
  25. #:use-module (gnu system uuid)
  26. #:use-module ((gnu build file-systems)
  27. #:select (canonicalize-device-spec
  28. find-partition-by-label
  29. find-partition-by-uuid
  30. read-partition-uuid
  31. read-luks-partition-uuid))
  32. #:use-module ((gnu build linux-boot)
  33. #:select (linux-command-line
  34. find-long-option))
  35. #:use-module ((gnu build linux-modules)
  36. #:select (missing-modules))
  37. #:use-module ((gnu system linux-initrd)
  38. #:select (%base-initrd-modules))
  39. #:use-module (guix build syscalls)
  40. #:use-module (guix build utils)
  41. #:use-module (guix read-print)
  42. #:use-module (guix records)
  43. #:use-module (guix utils)
  44. #:use-module (guix i18n)
  45. #:use-module (parted)
  46. #:use-module (ice-9 format)
  47. #:use-module (ice-9 match)
  48. #:use-module (ice-9 regex)
  49. #:use-module (rnrs io ports)
  50. #:use-module (srfi srfi-1)
  51. #:use-module (srfi srfi-19)
  52. #:use-module (srfi srfi-26)
  53. #:use-module (srfi srfi-34)
  54. #:use-module (srfi srfi-35)
  55. #:export (<user-partition>
  56. user-partition
  57. make-user-partition
  58. user-partition?
  59. user-partition-name
  60. user-partition-type
  61. user-partition-file-name
  62. user-partition-disk-file-name
  63. user-partition-crypt-label
  64. user-partition-crypt-password
  65. user-partition-fs-type
  66. user-partition-bootable?
  67. user-partition-esp?
  68. user-partition-bios-grub?
  69. user-partition-size
  70. user-partition-start
  71. user-partition-end
  72. user-partition-mount-point
  73. user-partition-need-formatting?
  74. user-partition-parted-object
  75. find-esp-partition
  76. small-freespace-partition?
  77. esp-partition?
  78. boot-partition?
  79. efi-installation?
  80. default-esp-mount-point
  81. force-device-sync
  82. eligible-devices
  83. partition-user-type
  84. user-fs-type-name
  85. partition-filesystem-user-type
  86. partition-get-flags
  87. partition->user-partition
  88. create-special-user-partitions
  89. find-user-partition-by-parted-object
  90. device-description
  91. partition-end-formatted
  92. partition-print-number
  93. partition-description
  94. partitions-descriptions
  95. user-partition-description
  96. &max-primary-exceeded
  97. max-primary-exceeded?
  98. &extended-creation-error
  99. extended-creation-error?
  100. &logical-creation-error
  101. logical-creation-error?
  102. can-create-partition?
  103. mklabel
  104. mkpart
  105. rmpart
  106. auto-partition!
  107. &no-root-mount-point
  108. no-root-mount-point?
  109. &cannot-read-uuid
  110. cannot-read-uuid?
  111. cannot-read-uuid-partition
  112. check-user-partitions
  113. set-user-partitions-file-name
  114. format-user-partitions
  115. mount-user-partitions
  116. umount-user-partitions
  117. with-mounted-partitions
  118. user-partitions->file-systems
  119. user-partitions->configuration
  120. init-parted
  121. free-parted))
  122. ;;;
  123. ;;; Partition record.
  124. ;;;
  125. (define-record-type* <user-partition>
  126. user-partition make-user-partition
  127. user-partition?
  128. (name user-partition-name ;string
  129. (default #f))
  130. (type user-partition-type
  131. (default 'normal)) ; 'normal | 'logical | 'extended
  132. (file-name user-partition-file-name
  133. (default #f))
  134. (disk-file-name user-partition-disk-file-name
  135. (default #f))
  136. (crypt-label user-partition-crypt-label
  137. (default #f))
  138. (crypt-password user-partition-crypt-password ; <secret>
  139. (default #f))
  140. (fs-type user-partition-fs-type
  141. (default 'ext4))
  142. (bootable? user-partition-bootable?
  143. (default #f))
  144. (esp? user-partition-esp?
  145. (default #f))
  146. (bios-grub? user-partition-bios-grub?
  147. (default #f))
  148. (size user-partition-size
  149. (default #f))
  150. (start user-partition-start ;start as string (e.g. '11MB')
  151. (default #f))
  152. (end user-partition-end ;same as start
  153. (default #f))
  154. (mount-point user-partition-mount-point ;string
  155. (default #f))
  156. (need-formatting? user-partition-need-formatting? ; boolean
  157. (default #f))
  158. (parted-object user-partition-parted-object ; <partition> from parted
  159. (default #f)))
  160. ;;
  161. ;; Utilities.
  162. ;;
  163. (define (find-esp-partition partitions)
  164. "Find and return the ESP partition among PARTITIONS."
  165. (find esp-partition? partitions))
  166. (define* (small-freespace-partition? device
  167. partition
  168. #:key (max-size MEBIBYTE-SIZE))
  169. "Return #t is PARTITION is a free-space partition with less a size strictly
  170. inferior to MAX-SIZE, #f otherwise."
  171. (let ((size (partition-length partition))
  172. (max-sector-size (/ max-size
  173. (device-sector-size device))))
  174. (< size max-sector-size)))
  175. (define (partition-user-type partition)
  176. "Return the type of PARTITION, to be stored in the TYPE field of
  177. <user-partition> record. It can be 'normal, 'extended or 'logical."
  178. (cond ((normal-partition? partition)
  179. 'normal)
  180. ((extended-partition? partition)
  181. 'extended)
  182. ((logical-partition? partition)
  183. 'logical)
  184. (else #f)))
  185. (define (esp-partition? partition)
  186. "Return #t if partition has the ESP flag, return #f otherwise."
  187. (let* ((disk (partition-disk partition))
  188. (disk-type (disk-disk-type disk)))
  189. (and (data-partition? partition)
  190. (partition-is-flag-available? partition PARTITION-FLAG-ESP)
  191. (partition-get-flag partition PARTITION-FLAG-ESP))))
  192. (define (boot-partition? partition)
  193. "Return #t if partition has the boot flag, return #f otherwise."
  194. (and (data-partition? partition)
  195. (partition-is-flag-available? partition PARTITION-FLAG-BOOT)
  196. (partition-get-flag partition PARTITION-FLAG-BOOT)))
  197. ;; The default mount point for ESP partitions.
  198. (define default-esp-mount-point
  199. (make-parameter "/boot/efi"))
  200. (define (efi-installation?)
  201. "Return #t if an EFI installation should be performed, #f otherwise."
  202. (file-exists? "/sys/firmware/efi"))
  203. (define (user-fs-type-name fs-type)
  204. "Return the name of FS-TYPE as specified by libparted."
  205. (case fs-type
  206. ((ext4) "ext4")
  207. ((btrfs) "btrfs")
  208. ((fat16) "fat16")
  209. ((fat32) "fat32")
  210. ((jfs) "jfs")
  211. ((ntfs) "ntfs")
  212. ((xfs) "xfs")
  213. ((swap) "linux-swap")))
  214. (define (user-fs-type->mount-type fs-type)
  215. "Return the mount type of FS-TYPE."
  216. (case fs-type
  217. ((ext4) "ext4")
  218. ((btrfs) "btrfs")
  219. ((fat16) "vfat")
  220. ((fat32) "vfat")
  221. ((jfs) "jfs")
  222. ((ntfs) "ntfs")
  223. ((xfs) "xfs")))
  224. (define (partition-filesystem-user-type partition)
  225. "Return the filesystem type of PARTITION, to be stored in the FS-TYPE field
  226. of <user-partition> record."
  227. (let ((fs-type (partition-fs-type partition)))
  228. (and fs-type
  229. (let ((name (filesystem-type-name fs-type)))
  230. (cond
  231. ((string=? name "ext4") 'ext4)
  232. ((string=? name "btrfs") 'btrfs)
  233. ((string=? name "fat16") 'fat16)
  234. ((string=? name "fat32") 'fat32)
  235. ((string=? name "jfs") 'jfs)
  236. ((string=? name "ntfs") 'ntfs)
  237. ((string=? name "xfs") 'xfs)
  238. ((or (string=? name "swsusp")
  239. (string=? name "linux-swap(v0)")
  240. (string=? name "linux-swap(v1)"))
  241. 'swap)
  242. (else
  243. (error (format #f "Unhandled ~a fs-type~%" name))))))))
  244. (define (partition-get-flags partition)
  245. "Return the list of flags supported by the given PARTITION."
  246. (filter-map (lambda (flag)
  247. (and (partition-get-flag partition flag)
  248. flag))
  249. (partition-flags partition)))
  250. (define (partition->user-partition partition)
  251. "Convert PARTITION into a <user-partition> record and return it."
  252. (let* ((disk (partition-disk partition))
  253. (device (disk-device disk))
  254. (disk-type (disk-disk-type disk))
  255. (has-name? (disk-type-check-feature
  256. disk-type
  257. DISK-TYPE-FEATURE-PARTITION-NAME))
  258. (name (and has-name?
  259. (data-partition? partition)
  260. (partition-get-name partition))))
  261. (user-partition
  262. (name (and (and name
  263. (not (string=? name "")))
  264. name))
  265. (type (or (partition-user-type partition)
  266. 'normal))
  267. (file-name (partition-get-path partition))
  268. (disk-file-name (device-path device))
  269. (fs-type (or (partition-filesystem-user-type partition)
  270. 'ext4))
  271. (mount-point (and (esp-partition? partition)
  272. (default-esp-mount-point)))
  273. (bootable? (boot-partition? partition))
  274. (esp? (esp-partition? partition))
  275. (parted-object partition))))
  276. (define (create-special-user-partitions partitions)
  277. "Return a list with a <user-partition> record describing the ESP partition
  278. found in PARTITIONS, if any."
  279. (filter-map (lambda (partition)
  280. (and (esp-partition? partition)
  281. (partition->user-partition partition)))
  282. partitions))
  283. (define (find-user-partition-by-parted-object user-partitions
  284. partition)
  285. "Find and return the <user-partition> record in USER-PARTITIONS list which
  286. PARTED-OBJECT field equals PARTITION, return #f if not found."
  287. (find (lambda (user-partition)
  288. (equal? (user-partition-parted-object user-partition)
  289. partition))
  290. user-partitions))
  291. ;;
  292. ;; Devices
  293. ;;
  294. (define (with-delay-device-in-use? file-name)
  295. "Call DEVICE-IN-USE? with a few retries, as the first re-read will often
  296. fail. See rereadpt function in wipefs.c of util-linux for an explanation."
  297. ;; Kernel always return EINVAL for BLKRRPART on loopdevices.
  298. (and (not (string-match "/dev/loop*" file-name))
  299. (let loop ((try 16))
  300. (usleep 250000)
  301. (let ((in-use? (device-in-use? file-name)))
  302. (if (and in-use? (> try 0))
  303. (loop (- try 1))
  304. in-use?)))))
  305. (define* (force-device-sync device)
  306. "Force a flushing of the given DEVICE."
  307. (device-open device)
  308. (device-sync device)
  309. (device-close device))
  310. (define (remove-logical-devices)
  311. "Remove all active logical devices."
  312. ((run-command-in-installer) "dmsetup" "remove_all"))
  313. (define (installer-root-partition-path)
  314. "Return the root partition path, or #f if it could not be detected."
  315. (let* ((cmdline (linux-command-line))
  316. (root (find-long-option "root" cmdline)))
  317. (and root
  318. (or (and (access? root F_OK) root)
  319. (find-partition-by-label root)
  320. (and=> (uuid root)
  321. find-partition-by-uuid)))))
  322. ;; Minimal installation device size.
  323. (define %min-device-size
  324. (* 2 GIBIBYTE-SIZE)) ;2GiB
  325. (define (eligible-devices)
  326. "Return all the available devices except the install device and the devices
  327. which are smaller than %MIN-DEVICE-SIZE."
  328. (define the-installer-root-partition-path
  329. (installer-root-partition-path))
  330. (define (small-device? device)
  331. (let ((length (device-length device))
  332. (sector-size (device-sector-size device)))
  333. (and (< (* length sector-size) %min-device-size)
  334. (installer-log-line "~a is not eligible because it is smaller than \
  335. ~a."
  336. (device-path device)
  337. (unit-format-custom-byte device
  338. %min-device-size
  339. UNIT-GIGABYTE)))))
  340. ;; Read partition table of device and compare each path to the one
  341. ;; we're booting from to determine if it is the installation
  342. ;; device.
  343. (define (installation-device? device)
  344. ;; When using CDROM based installation, the root partition path may be the
  345. ;; device path.
  346. (and (or (string=? the-installer-root-partition-path
  347. (device-path device))
  348. (let ((disk (disk-new device)))
  349. (and disk
  350. (any (lambda (partition)
  351. (string=? the-installer-root-partition-path
  352. (partition-get-path partition)))
  353. (disk-partitions disk)))))
  354. (installer-log-line "~a is not eligible because it is the \
  355. installation device."
  356. (device-path device))))
  357. (remove
  358. (lambda (device)
  359. (or (installation-device? device)
  360. (small-device? device)))
  361. (devices)))
  362. ;;
  363. ;; Disk and partition printing.
  364. ;;
  365. (define* (device-description device #:optional disk)
  366. "Return a string describing the given DEVICE."
  367. (let* ((type (device-type device))
  368. (file-name (device-path device))
  369. (model (device-model device))
  370. (type-str (device-type->string type))
  371. (disk-type (if disk
  372. (disk-disk-type disk)
  373. (disk-probe device)))
  374. (length (device-length device))
  375. (sector-size (device-sector-size device))
  376. (end (unit-format-custom-byte device
  377. (* length sector-size)
  378. UNIT-GIGABYTE)))
  379. (string-join
  380. `(,@(if (string=? model "")
  381. `(,type-str)
  382. `(,model ,(string-append "(" type-str ")")))
  383. ,file-name
  384. ,end
  385. ,@(if disk-type
  386. `(,(disk-type-name disk-type))
  387. '()))
  388. " ")))
  389. (define (partition-end-formatted device partition)
  390. "Return as a string the end of PARTITION with the relevant unit."
  391. (unit-format-byte
  392. device
  393. (-
  394. (* (+ (partition-end partition) 1)
  395. (device-sector-size device))
  396. 1)))
  397. (define (partition-print-number partition)
  398. "Convert the given partition NUMBER to string."
  399. (let ((number (partition-number partition)))
  400. (number->string number)))
  401. (define (partition-description partition user-partition)
  402. "Return a string describing the given PARTITION, located on the DISK of
  403. DEVICE."
  404. (define (partition-print-type partition)
  405. "Return the type of PARTITION as a string."
  406. (if (freespace-partition? partition)
  407. (G_ "Free space")
  408. (let ((type (partition-type partition)))
  409. (match type
  410. ((type-symbol)
  411. (symbol->string type-symbol))))))
  412. (define (partition-print-flags partition)
  413. "Return the flags of PARTITION as a string of comma separated flags."
  414. (string-join
  415. (filter-map
  416. (lambda (flag)
  417. (and (partition-get-flag partition flag)
  418. (partition-flag-get-name flag)))
  419. (partition-flags partition))
  420. ","))
  421. (define (maybe-string-pad string length)
  422. "Returned a string formatted by padding STRING of LENGTH characters to the
  423. right. If STRING is #f use an empty string."
  424. (if (and string (not (string=? string "")))
  425. (string-pad-right string length)
  426. ""))
  427. (let* ((disk (partition-disk partition))
  428. (device (disk-device disk))
  429. (disk-type (disk-disk-type disk))
  430. (has-name? (disk-type-check-feature
  431. disk-type
  432. DISK-TYPE-FEATURE-PARTITION-NAME))
  433. (has-extended? (disk-type-check-feature
  434. disk-type
  435. DISK-TYPE-FEATURE-EXTENDED))
  436. (part-type (partition-print-type partition))
  437. (number (and (not (freespace-partition? partition))
  438. (partition-print-number partition)))
  439. (name (and has-name?
  440. (if (freespace-partition? partition)
  441. (G_ "Free space")
  442. (partition-get-name partition))))
  443. (start (unit-format device
  444. (partition-start partition)))
  445. (end (partition-end-formatted device partition))
  446. (size (unit-format device (partition-length partition)))
  447. (fs-type (partition-fs-type partition))
  448. (fs-type-name (and fs-type
  449. (filesystem-type-name fs-type)))
  450. (crypt-label (and user-partition
  451. (user-partition-crypt-label user-partition)))
  452. (flags (and (not (freespace-partition? partition))
  453. (partition-print-flags partition)))
  454. (mount-point (and user-partition
  455. (user-partition-mount-point user-partition))))
  456. `(,(or number "")
  457. ,@(if has-extended?
  458. (list part-type)
  459. '())
  460. ,size
  461. ,(or fs-type-name "")
  462. ,(or flags "")
  463. ,(or mount-point "")
  464. ,(or crypt-label "")
  465. ,(maybe-string-pad name 30))))
  466. (define (partitions-descriptions partitions user-partitions)
  467. "Return a list of strings describing all the partitions found on
  468. DEVICE. METADATA partitions are not described. The strings are padded to the
  469. right so that they can be displayed as a table."
  470. (define (max-length-column lists column-index)
  471. "Return the maximum length of the string at position COLUMN-INDEX in the
  472. list of string lists LISTS."
  473. (apply max
  474. (map (lambda (list)
  475. (string-length
  476. (list-ref list column-index)))
  477. lists)))
  478. (define (pad-descriptions descriptions)
  479. "Return a padded version of the list of string lists DESCRIPTIONS. The
  480. strings are padded to the length of the longer string in a same column, as
  481. determined by MAX-LENGTH-COLUMN procedure."
  482. (let* ((description-length (length (car descriptions)))
  483. (paddings (map (lambda (index)
  484. (max-length-column descriptions index))
  485. (iota description-length))))
  486. (map (lambda (description)
  487. (map string-pad-right description paddings))
  488. descriptions)))
  489. (let* ((descriptions
  490. (map
  491. (lambda (partition)
  492. (let ((user-partition
  493. (find-user-partition-by-parted-object user-partitions
  494. partition)))
  495. (partition-description partition user-partition)))
  496. partitions))
  497. (padded-descriptions (if (null? partitions)
  498. '()
  499. (pad-descriptions descriptions))))
  500. (map (cut string-join <> " ") padded-descriptions)))
  501. (define (user-partition-description user-partition)
  502. "Return a string describing the given USER-PARTITION record."
  503. (let* ((partition (user-partition-parted-object user-partition))
  504. (disk (partition-disk partition))
  505. (disk-type (disk-disk-type disk))
  506. (device (disk-device disk))
  507. (has-name? (disk-type-check-feature
  508. disk-type
  509. DISK-TYPE-FEATURE-PARTITION-NAME))
  510. (has-extended? (disk-type-check-feature
  511. disk-type
  512. DISK-TYPE-FEATURE-EXTENDED))
  513. (name (user-partition-name user-partition))
  514. (type (user-partition-type user-partition))
  515. (type-name (symbol->string type))
  516. (fs-type (user-partition-fs-type user-partition))
  517. (fs-type-name (user-fs-type-name fs-type))
  518. (bootable? (user-partition-bootable? user-partition))
  519. (esp? (user-partition-esp? user-partition))
  520. (need-formatting? (user-partition-need-formatting? user-partition))
  521. (crypt-label (user-partition-crypt-label user-partition))
  522. (size (user-partition-size user-partition))
  523. (mount-point (user-partition-mount-point user-partition)))
  524. `(,@(if has-name?
  525. `((name . ,(format #f (G_ "Name: ~a")
  526. (or name (G_ "None")))))
  527. '())
  528. ,@(if (and has-extended?
  529. (freespace-partition? partition)
  530. (not (eq? type 'logical)))
  531. `((type . ,(format #f (G_ "Type: ~a") type-name)))
  532. '())
  533. ,@(if (eq? type 'extended)
  534. '()
  535. `((fs-type . ,(format #f (G_ "File system type: ~a")
  536. fs-type-name))))
  537. ,@(if (or (eq? type 'extended)
  538. (eq? fs-type 'swap)
  539. (not has-extended?))
  540. '()
  541. `((bootable . ,(format #f (G_ "Bootable flag: ~:[off~;on~]")
  542. bootable?))))
  543. ,@(if (and (not has-extended?)
  544. (not (eq? fs-type 'swap)))
  545. `((esp? . ,(format #f (G_ "ESP flag: ~:[off~;on~]") esp?)))
  546. '())
  547. ,@(if (freespace-partition? partition)
  548. (let ((size-formatted
  549. (or size (unit-format device ;XXX: i18n
  550. (partition-length partition)))))
  551. `((size . ,(format #f (G_ "Size: ~a") size-formatted))))
  552. '())
  553. ,@(if (or (eq? type 'extended)
  554. (eq? fs-type 'swap))
  555. '()
  556. `((crypt-label
  557. . ,(format #f (G_ "Encryption: ~:[No~a~;Yes (label '~a')~]")
  558. crypt-label (or crypt-label "")))))
  559. ,@(if (or (freespace-partition? partition)
  560. (eq? fs-type 'swap))
  561. '()
  562. `((need-formatting?
  563. . ,(format #f (G_ "Format the partition? ~:[No~;Yes~]")
  564. need-formatting?))))
  565. ,@(if (or (eq? type 'extended)
  566. (eq? fs-type 'swap))
  567. '()
  568. `((mount-point
  569. . ,(format #f (G_ "Mount point: ~a")
  570. (or mount-point
  571. (and esp? (default-esp-mount-point))
  572. (G_ "None")))))))))
  573. ;;
  574. ;; Partition table creation.
  575. ;;
  576. (define (mklabel device type-name)
  577. "Create a partition table on DEVICE. TYPE-NAME is the type of the partition
  578. table, \"msdos\" or \"gpt\"."
  579. (let* ((type (disk-type-get type-name))
  580. (disk (disk-new-fresh device type)))
  581. (or disk
  582. (raise
  583. (condition
  584. (&error)
  585. (&message (message (format #f "Cannot create partition table of type
  586. ~a on device ~a." type-name (device-path device)))))))))
  587. ;;
  588. ;; Partition creation.
  589. ;;
  590. ;; The maximum count of primary partitions is exceeded.
  591. (define-condition-type &max-primary-exceeded &condition
  592. max-primary-exceeded?)
  593. ;; It is not possible to create an extended partition.
  594. (define-condition-type &extended-creation-error &condition
  595. extended-creation-error?)
  596. ;; It is not possible to create a logical partition.
  597. (define-condition-type &logical-creation-error &condition
  598. logical-creation-error?)
  599. (define (can-create-primary? disk)
  600. "Return #t if it is possible to create a primary partition on DISK, return
  601. #f otherwise."
  602. (let ((max-primary (disk-get-max-primary-partition-count disk)))
  603. (find (lambda (number)
  604. (not (disk-get-partition disk number)))
  605. (iota max-primary 1))))
  606. (define (can-create-extended? disk)
  607. "Return #t if it is possible to create an extended partition on DISK, return
  608. #f otherwise."
  609. (let* ((disk-type (disk-disk-type disk))
  610. (has-extended? (disk-type-check-feature
  611. disk-type
  612. DISK-TYPE-FEATURE-EXTENDED)))
  613. (and (can-create-primary? disk)
  614. has-extended?
  615. (not (disk-extended-partition disk)))))
  616. (define (can-create-logical? disk)
  617. "Return #t is it is possible to create a logical partition on DISK, return
  618. #f otherwise."
  619. (let* ((disk-type (disk-disk-type disk))
  620. (has-extended? (disk-type-check-feature
  621. disk-type
  622. DISK-TYPE-FEATURE-EXTENDED)))
  623. (and has-extended?
  624. (disk-extended-partition disk))))
  625. (define (can-create-partition? user-part)
  626. "Return #t if it is possible to create the given USER-PART record, return #f
  627. otherwise."
  628. (let* ((type (user-partition-type user-part))
  629. (partition (user-partition-parted-object user-part))
  630. (disk (partition-disk partition)))
  631. (case type
  632. ((normal)
  633. (or (can-create-primary? disk)
  634. (raise
  635. (condition (&max-primary-exceeded)))))
  636. ((extended)
  637. (or (can-create-extended? disk)
  638. (raise
  639. (condition (&extended-creation-error)))))
  640. ((logical)
  641. (or (can-create-logical? disk)
  642. (raise
  643. (condition (&logical-creation-error))))))))
  644. (define* (mkpart disk user-partition
  645. #:key (previous-partition #f))
  646. "Create the given USER-PARTITION on DISK. The PREVIOUS-PARTITION argument as
  647. to be set to the partition preceding USER-PARTITION if any."
  648. (define (parse-start-end start end)
  649. "Parse start and end strings as positions on DEVICE expressed with a unit,
  650. like '100GB' or '12.2KiB'. Return a list of 4 elements, the start sector, its
  651. range (1 unit large area centered on start sector), the end sector and its
  652. range."
  653. (let ((device (disk-device disk)))
  654. (call-with-values
  655. (lambda ()
  656. (unit-parse start device))
  657. (lambda (start-sector start-range)
  658. (call-with-values
  659. (lambda ()
  660. (unit-parse end device))
  661. (lambda (end-sector end-range)
  662. (list start-sector start-range
  663. end-sector end-range)))))))
  664. (define* (extend-ranges! start-range end-range
  665. #:key (offset 0))
  666. "Try to extend START-RANGE by 1 MEBIBYTE to the right and END-RANGE by 1
  667. MEBIBYTE to the left. This way, if the disk is aligned on 2048 sectors of
  668. 512KB (like frequently), we will have a chance for the
  669. 'optimal-align-constraint' to succeed. Do not extend ranges if that would
  670. cause them to cross."
  671. (let* ((device (disk-device disk))
  672. (start-range-end (geometry-end start-range))
  673. (end-range-start (geometry-start end-range))
  674. (mebibyte-sector-size (/ MEBIBYTE-SIZE
  675. (device-sector-size device)))
  676. (new-start-range-end
  677. (+ start-range-end mebibyte-sector-size offset))
  678. (new-end-range-start
  679. (- end-range-start mebibyte-sector-size offset)))
  680. (when (< new-start-range-end new-end-range-start)
  681. (geometry-set-end start-range new-start-range-end)
  682. (geometry-set-start end-range new-end-range-start))))
  683. (match (parse-start-end (user-partition-start user-partition)
  684. (user-partition-end user-partition))
  685. ((start-sector start-range end-sector end-range)
  686. (let* ((prev-end (if previous-partition
  687. (partition-end previous-partition)
  688. 0))
  689. (start-distance (- start-sector prev-end))
  690. (type (user-partition-type user-partition))
  691. ;; There should be at least 2 unallocated sectors in front of each
  692. ;; logical partition, otherwise parted will fail badly:
  693. ;; https://gparted.org/h2-fix-msdos-pt.php#apply-action-fail.
  694. (start-offset (if previous-partition
  695. (- 3 start-distance)
  696. 0))
  697. (start-sector* (if (and (eq? type 'logical)
  698. (< start-distance 3))
  699. (+ start-sector start-offset)
  700. start-sector)))
  701. ;; This is a hack. Parted almost always fails to create optimally
  702. ;; aligned partitions (unless specifying percentages) because the
  703. ;; default range of 1MB centered on the start sector is not enough when
  704. ;; the optimal alignment is 2048 sectors of 512KB.
  705. (extend-ranges! start-range end-range #:offset start-offset)
  706. (let* ((device (disk-device disk))
  707. (disk-type (disk-disk-type disk))
  708. (length (device-length device))
  709. (name (user-partition-name user-partition))
  710. (filesystem-type
  711. (filesystem-type-get
  712. (user-fs-type-name
  713. (user-partition-fs-type user-partition))))
  714. (flags `(,@(if (user-partition-bootable? user-partition)
  715. `(,PARTITION-FLAG-BOOT)
  716. '())
  717. ,@(if (user-partition-esp? user-partition)
  718. `(,PARTITION-FLAG-ESP)
  719. '())
  720. ,@(if (user-partition-bios-grub? user-partition)
  721. `(,PARTITION-FLAG-BIOS-GRUB)
  722. '())))
  723. (has-name? (disk-type-check-feature
  724. disk-type
  725. DISK-TYPE-FEATURE-PARTITION-NAME))
  726. (partition-type (partition-type->int type))
  727. (partition (partition-new disk
  728. #:type partition-type
  729. #:filesystem-type filesystem-type
  730. #:start start-sector*
  731. #:end end-sector))
  732. (user-constraint (constraint-new
  733. #:start-align 'any
  734. #:end-align 'any
  735. #:start-range start-range
  736. #:end-range end-range
  737. #:min-size 1
  738. #:max-size length))
  739. (dev-constraint
  740. (device-get-optimal-aligned-constraint device))
  741. (final-constraint (constraint-intersect user-constraint
  742. dev-constraint))
  743. (no-constraint (constraint-any device))
  744. ;; Try to create a partition with an optimal alignment
  745. ;; constraint. If it fails, fallback to creating a partition
  746. ;; with no specific constraint.
  747. (partition-constraint-ok?
  748. (disk-add-partition disk partition final-constraint))
  749. (partition-no-contraint-ok?
  750. (or partition-constraint-ok?
  751. (disk-add-partition disk partition no-constraint)))
  752. (partition-ok?
  753. (or partition-constraint-ok? partition-no-contraint-ok?)))
  754. (installer-log-line "Creating partition:")
  755. (installer-log-line "~/type: ~a" partition-type)
  756. (installer-log-line "~/filesystem-type: ~a"
  757. (filesystem-type-name filesystem-type))
  758. (installer-log-line "~/flags: ~a" flags)
  759. (installer-log-line "~/start: ~a" start-sector*)
  760. (installer-log-line "~/end: ~a" end-sector)
  761. (installer-log-line "~/start-range: [~a, ~a]"
  762. (geometry-start start-range)
  763. (geometry-end start-range))
  764. (installer-log-line "~/end-range: [~a, ~a]"
  765. (geometry-start end-range)
  766. (geometry-end end-range))
  767. (installer-log-line "~/constraint: ~a"
  768. partition-constraint-ok?)
  769. (installer-log-line "~/no-constraint: ~a"
  770. partition-no-contraint-ok?)
  771. ;; Set the partition name if supported.
  772. (when (and partition-ok? has-name? name)
  773. (partition-set-name partition name))
  774. ;; Both partition-set-system and partition-set-flag calls can affect
  775. ;; the partition type. Their order is important, see:
  776. ;; https://issues.guix.gnu.org/55549.
  777. (partition-set-system partition filesystem-type)
  778. ;; Set flags if required.
  779. (for-each (lambda (flag)
  780. (and (partition-is-flag-available? partition flag)
  781. (partition-set-flag partition flag 1)))
  782. flags)
  783. (and partition-ok? partition))))))
  784. ;;
  785. ;; Partition destruction.
  786. ;;
  787. (define (rmpart disk number)
  788. "Remove the partition with the given NUMBER on DISK."
  789. (let ((partition (disk-get-partition disk number)))
  790. (disk-remove-partition* disk partition)))
  791. ;;
  792. ;; Auto partitionning.
  793. ;;
  794. (define* (create-adjacent-partitions! disk partitions
  795. #:key (last-partition-end 0))
  796. "Create the given PARTITIONS on DISK. LAST-PARTITION-END is the sector from
  797. which we want to start creating partitions. The START and END of each created
  798. partition are computed from its SIZE value and the position of the last
  799. partition."
  800. (let ((device (disk-device disk)))
  801. (let loop ((partitions partitions)
  802. (remaining-space (- (device-length device)
  803. last-partition-end))
  804. (start last-partition-end))
  805. (match partitions
  806. (() '())
  807. ((partition . rest)
  808. (let* ((size (user-partition-size partition))
  809. (percentage-size (and (string? size)
  810. (read-percentage size)))
  811. (sector-size (device-sector-size device))
  812. (partition-size (if percentage-size
  813. (exact->inexact
  814. (* (/ percentage-size 100)
  815. remaining-space))
  816. size))
  817. (end-partition (min (- (device-length device) 1)
  818. (nearest-exact-integer
  819. (+ start partition-size 1))))
  820. (name (user-partition-name partition))
  821. (type (user-partition-type partition))
  822. (fs-type (user-partition-fs-type partition))
  823. (start-formatted (unit-format-custom device
  824. start
  825. UNIT-SECTOR))
  826. (end-formatted (unit-format-custom device
  827. end-partition
  828. UNIT-SECTOR))
  829. (new-user-partition (user-partition
  830. (inherit partition)
  831. (start start-formatted)
  832. (end end-formatted)))
  833. (new-partition
  834. (mkpart disk new-user-partition)))
  835. (if new-partition
  836. (cons (user-partition
  837. (inherit new-user-partition)
  838. (file-name (partition-get-path new-partition))
  839. (disk-file-name (device-path device))
  840. (parted-object new-partition))
  841. (loop rest
  842. (if (eq? type 'extended)
  843. remaining-space
  844. (- remaining-space
  845. (partition-length new-partition)))
  846. (if (eq? type 'extended)
  847. (+ start 1)
  848. (+ (partition-end new-partition) 1))))
  849. (error
  850. (format #f "Unable to create partition ~a~%" name)))))))))
  851. (define (force-user-partitions-formatting user-partitions)
  852. "Set the NEED-FORMATTING? fields to #t on all <user-partition> records of
  853. USER-PARTITIONS list and return the updated list."
  854. (map (lambda (p)
  855. (user-partition
  856. (inherit p)
  857. (need-formatting? #t)))
  858. user-partitions))
  859. (define* (auto-partition! disk
  860. #:key
  861. (scheme 'entire-root))
  862. "Automatically create partitions on DISK. All the previous
  863. partitions (except the ESP on a GPT disk, if present) are wiped. SCHEME is the
  864. desired partitioning scheme. It can be 'entire-root or
  865. 'entire-root-home. 'entire-root will create a swap partition and a root
  866. partition occupying all the remaining space. 'entire-root-home will create a
  867. swap partition, a root partition and a home partition.
  868. Return the complete list of partitions on DISK, including the ESP when it
  869. exists."
  870. (let* ((device (disk-device disk))
  871. (disk-type (disk-disk-type disk))
  872. (has-extended? (disk-type-check-feature
  873. disk-type
  874. DISK-TYPE-FEATURE-EXTENDED))
  875. (partitions (filter data-partition? (disk-partitions disk)))
  876. (esp-partition (find-esp-partition partitions))
  877. ;; According to
  878. ;; https://wiki.archlinux.org/index.php/EFI_system_partition, the ESP
  879. ;; size should be at least 550MiB.
  880. (new-esp-size (nearest-exact-integer
  881. (/ (* 550 MEBIBYTE-SIZE)
  882. (device-sector-size device))))
  883. (end-esp-partition (and esp-partition
  884. (partition-end esp-partition)))
  885. (non-boot-partitions (remove esp-partition? partitions))
  886. (bios-grub-size (/ (* 3 MEBIBYTE-SIZE)
  887. (device-sector-size device)))
  888. (five-percent-disk (nearest-exact-integer
  889. (* 0.05 (device-length device))))
  890. (default-swap-size (nearest-exact-integer
  891. (/ (* 4 GIGABYTE-SIZE)
  892. (device-sector-size device))))
  893. ;; Use a 4GB size for the swap if it represents less than 5% of the
  894. ;; disk space. Otherwise, set the swap size to 5% of the disk space.
  895. (swap-size (min default-swap-size five-percent-disk)))
  896. ;; Remove everything but esp if it exists.
  897. (for-each
  898. (lambda (partition)
  899. (and (data-partition? partition)
  900. ;; Do not remove logical partitions ourselves, since
  901. ;; disk-remove-partition* will remove all the logical partitions
  902. ;; residing on an extended partition, which would lead to a
  903. ;; double-remove and ensuing SEGFAULT.
  904. (not (logical-partition? partition))
  905. (disk-remove-partition* disk partition)))
  906. non-boot-partitions)
  907. (let* ((start-partition
  908. (if (efi-installation?)
  909. (and (not esp-partition)
  910. (user-partition
  911. (fs-type 'fat32)
  912. (esp? #t)
  913. (size new-esp-size)
  914. (mount-point (default-esp-mount-point))))
  915. (user-partition
  916. (fs-type 'ext4)
  917. (bootable? #t)
  918. (bios-grub? #t)
  919. (size bios-grub-size))))
  920. (new-partitions
  921. (cond
  922. ((or (eq? scheme 'entire-root)
  923. (eq? scheme 'entire-encrypted-root))
  924. (let ((encrypted? (eq? scheme 'entire-encrypted-root)))
  925. `(,@(if start-partition
  926. `(,start-partition)
  927. '())
  928. ,@(if encrypted?
  929. '()
  930. `(,(user-partition
  931. (fs-type 'swap)
  932. (size swap-size))))
  933. ,(user-partition
  934. (fs-type 'ext4)
  935. (bootable? has-extended?)
  936. (crypt-label (and encrypted? "cryptroot"))
  937. (size "100%")
  938. (mount-point "/")))))
  939. ((or (eq? scheme 'entire-root-home)
  940. (eq? scheme 'entire-encrypted-root-home))
  941. (let ((encrypted? (eq? scheme 'entire-encrypted-root-home)))
  942. `(,@(if start-partition
  943. `(,start-partition)
  944. '())
  945. ,(user-partition
  946. (fs-type 'ext4)
  947. (bootable? has-extended?)
  948. (crypt-label (and encrypted? "cryptroot"))
  949. (size "33%")
  950. (mount-point "/"))
  951. ,@(if has-extended?
  952. `(,(user-partition
  953. (type 'extended)
  954. (size "100%")))
  955. '())
  956. ,@(if encrypted?
  957. '()
  958. `(,(user-partition
  959. (type (if has-extended?
  960. 'logical
  961. 'normal))
  962. (fs-type 'swap)
  963. (size swap-size))))
  964. ,(user-partition
  965. (type (if has-extended?
  966. 'logical
  967. 'normal))
  968. (fs-type 'ext4)
  969. (crypt-label (and encrypted? "crypthome"))
  970. (size "100%")
  971. (mount-point "/home")))))))
  972. (new-partitions* (force-user-partitions-formatting
  973. new-partitions)))
  974. (append (if esp-partition
  975. (list (partition->user-partition esp-partition))
  976. '())
  977. (create-adjacent-partitions! disk
  978. new-partitions*
  979. #:last-partition-end
  980. (or end-esp-partition 0))))))
  981. ;;
  982. ;; Convert user-partitions.
  983. ;;
  984. ;; No root mount point found.
  985. (define-condition-type &no-root-mount-point &condition
  986. no-root-mount-point?)
  987. ;; Cannot not read the partition UUID.
  988. (define-condition-type &cannot-read-uuid &condition
  989. cannot-read-uuid?
  990. (partition cannot-read-uuid-partition))
  991. (define (check-user-partitions user-partitions)
  992. "Check the following statements:
  993. The USER-PARTITIONS list contains one <user-partition> record with a
  994. mount-point set to '/'. Raise &no-root-mount-point condition otherwise.
  995. All the USER-PARTITIONS with a mount point and that will not be formatted have
  996. a valid UUID. Raise a &cannot-read-uuid condition specifying the faulty
  997. partition otherwise.
  998. Return #t if all the statements are valid."
  999. (define (check-root)
  1000. (let ((mount-points
  1001. (map user-partition-mount-point user-partitions)))
  1002. (or (member "/" mount-points)
  1003. (raise
  1004. (condition (&no-root-mount-point))))))
  1005. (define (check-uuid)
  1006. (let ((mount-partitions
  1007. (filter user-partition-mount-point user-partitions)))
  1008. (every
  1009. (lambda (user-partition)
  1010. (let ((file-name (user-partition-file-name user-partition))
  1011. (need-formatting?
  1012. (user-partition-need-formatting? user-partition)))
  1013. (or need-formatting?
  1014. (read-partition-uuid file-name)
  1015. (raise
  1016. (condition
  1017. (&cannot-read-uuid
  1018. (partition file-name)))))))
  1019. mount-partitions)))
  1020. (and (check-root)
  1021. (check-uuid)
  1022. #t))
  1023. (define (set-user-partitions-file-name user-partitions)
  1024. "Set the partition file-name of <user-partition> records in USER-PARTITIONS
  1025. list and return the updated list."
  1026. (map (lambda (p)
  1027. (let* ((partition (user-partition-parted-object p))
  1028. (file-name (partition-get-path partition)))
  1029. (user-partition
  1030. (inherit p)
  1031. (file-name file-name))))
  1032. user-partitions))
  1033. (define (create-btrfs-file-system partition)
  1034. "Create a btrfs file-system for PARTITION file-name."
  1035. ((run-command-in-installer) "mkfs.btrfs" "-f" partition))
  1036. (define (create-ext4-file-system partition)
  1037. "Create an ext4 file-system for PARTITION file-name."
  1038. ((run-command-in-installer) "mkfs.ext4" "-F" partition))
  1039. (define (create-fat16-file-system partition)
  1040. "Create a fat16 file-system for PARTITION file-name."
  1041. ((run-command-in-installer) "mkfs.fat" "-F16" partition))
  1042. (define (create-fat32-file-system partition)
  1043. "Create a fat32 file-system for PARTITION file-name."
  1044. ((run-command-in-installer) "mkfs.fat" "-F32" partition))
  1045. (define (create-jfs-file-system partition)
  1046. "Create a JFS file-system for PARTITION file-name."
  1047. ((run-command-in-installer) "jfs_mkfs" "-f" partition))
  1048. (define (create-ntfs-file-system partition)
  1049. "Create a JFS file-system for PARTITION file-name."
  1050. ((run-command-in-installer) "mkfs.ntfs" "-F" "-f" partition))
  1051. (define (create-xfs-file-system partition)
  1052. "Create an XFS file-system for PARTITION file-name."
  1053. ((run-command-in-installer) "mkfs.xfs" "-f" partition))
  1054. (define (create-swap-partition partition)
  1055. "Set up swap area on PARTITION file-name."
  1056. ((run-command-in-installer) "mkswap" "-f" partition))
  1057. (define (call-with-luks-key-file password proc)
  1058. "Write PASSWORD in a temporary file and pass it to PROC as argument."
  1059. (call-with-temporary-output-file
  1060. (lambda (file port)
  1061. (put-string port password)
  1062. (close port)
  1063. (proc file))))
  1064. (define (user-partition-upper-file-name user-partition)
  1065. "Return the file-name of the virtual block device corresponding to
  1066. USER-PARTITION if it is encrypted, or the plain file-name otherwise."
  1067. (let ((crypt-label (user-partition-crypt-label user-partition))
  1068. (file-name (user-partition-file-name user-partition)))
  1069. (if crypt-label
  1070. (string-append "/dev/mapper/" crypt-label)
  1071. file-name)))
  1072. (define (luks-format-and-open user-partition)
  1073. "Format and open the encrypted partition pointed by USER-PARTITION."
  1074. (let* ((file-name (user-partition-file-name user-partition))
  1075. (label (user-partition-crypt-label user-partition))
  1076. (password (secret-content (user-partition-crypt-password user-partition))))
  1077. (call-with-luks-key-file
  1078. password
  1079. (lambda (key-file)
  1080. (installer-log-line "formatting and opening LUKS entry ~s at ~s"
  1081. label file-name)
  1082. ((run-command-in-installer) "cryptsetup" "-q" "luksFormat"
  1083. file-name key-file)
  1084. ((run-command-in-installer) "cryptsetup" "open" "--type" "luks"
  1085. "--key-file" key-file file-name label)))))
  1086. (define (luks-ensure-open user-partition)
  1087. "Ensure partition pointed by USER-PARTITION is opened."
  1088. (unless (file-exists? (user-partition-upper-file-name user-partition))
  1089. (let* ((file-name (user-partition-file-name user-partition))
  1090. (label (user-partition-crypt-label user-partition))
  1091. (password (secret-content (user-partition-crypt-password user-partition))))
  1092. (call-with-luks-key-file
  1093. password
  1094. (lambda (key-file)
  1095. (installer-log-line "opening LUKS entry ~s at ~s"
  1096. label file-name)
  1097. ((run-command-in-installer) "cryptsetup" "open" "--type" "luks"
  1098. "--key-file" key-file file-name label))))))
  1099. (define (luks-close user-partition)
  1100. "Close the encrypted partition pointed by USER-PARTITION."
  1101. (let ((label (user-partition-crypt-label user-partition)))
  1102. (installer-log-line "closing LUKS entry ~s" label)
  1103. ((run-command-in-installer) "cryptsetup" "close" label)))
  1104. (define (format-user-partitions user-partitions)
  1105. "Format the <user-partition> records in USER-PARTITIONS list with
  1106. NEED-FORMATTING? field set to #t."
  1107. (for-each
  1108. (lambda (user-partition)
  1109. (let* ((need-formatting?
  1110. (user-partition-need-formatting? user-partition))
  1111. (type (user-partition-type user-partition))
  1112. (crypt-label (user-partition-crypt-label user-partition))
  1113. (file-name (user-partition-upper-file-name user-partition))
  1114. (fs-type (user-partition-fs-type user-partition)))
  1115. (when crypt-label
  1116. (luks-format-and-open user-partition))
  1117. (case fs-type
  1118. ((btrfs)
  1119. (and need-formatting?
  1120. (not (eq? type 'extended))
  1121. (create-btrfs-file-system file-name)))
  1122. ((ext4)
  1123. (and need-formatting?
  1124. (not (eq? type 'extended))
  1125. (create-ext4-file-system file-name)))
  1126. ((fat16)
  1127. (and need-formatting?
  1128. (not (eq? type 'extended))
  1129. (create-fat16-file-system file-name)))
  1130. ((fat32)
  1131. (and need-formatting?
  1132. (not (eq? type 'extended))
  1133. (create-fat32-file-system file-name)))
  1134. ((jfs)
  1135. (and need-formatting?
  1136. (not (eq? type 'extended))
  1137. (create-jfs-file-system file-name)))
  1138. ((ntfs)
  1139. (and need-formatting?
  1140. (not (eq? type 'extended))
  1141. (create-ntfs-file-system file-name)))
  1142. ((xfs)
  1143. (and need-formatting?
  1144. (not (eq? type 'extended))
  1145. (create-xfs-file-system file-name)))
  1146. ((swap)
  1147. (create-swap-partition file-name))
  1148. (else
  1149. ;; TODO: Add support for other file-system types.
  1150. #t))))
  1151. user-partitions))
  1152. (define (sort-partitions user-partitions)
  1153. "Sort USER-PARTITIONS by mount-points, so that the more nested mount-point
  1154. comes last. This is useful to mount/umount partitions in a coherent order."
  1155. (sort user-partitions
  1156. (lambda (a b)
  1157. (let ((mount-point-a (user-partition-mount-point a))
  1158. (mount-point-b (user-partition-mount-point b)))
  1159. (string-prefix? mount-point-a mount-point-b)))))
  1160. (define (mount-user-partitions user-partitions)
  1161. "Mount the <user-partition> records in USER-PARTITIONS list on their
  1162. respective mount-points."
  1163. (let* ((mount-partitions (filter user-partition-mount-point user-partitions))
  1164. (sorted-partitions (sort-partitions mount-partitions)))
  1165. (for-each (lambda (user-partition)
  1166. (let* ((mount-point
  1167. (user-partition-mount-point user-partition))
  1168. (target
  1169. (string-append (%installer-target-dir)
  1170. mount-point))
  1171. (fs-type
  1172. (user-partition-fs-type user-partition))
  1173. (crypt-label
  1174. (user-partition-crypt-label user-partition))
  1175. (mount-type
  1176. (user-fs-type->mount-type fs-type))
  1177. (file-name
  1178. (user-partition-upper-file-name user-partition)))
  1179. (when crypt-label
  1180. (luks-ensure-open user-partition))
  1181. (mkdir-p target)
  1182. (installer-log-line "mounting ~s on ~s" file-name target)
  1183. (mount file-name target mount-type)))
  1184. sorted-partitions)))
  1185. (define (umount-user-partitions user-partitions)
  1186. "Unmount all the <user-partition> records in USER-PARTITIONS list."
  1187. (let* ((mount-partitions (filter user-partition-mount-point user-partitions))
  1188. (sorted-partitions (sort-partitions mount-partitions)))
  1189. (for-each (lambda (user-partition)
  1190. (let* ((mount-point
  1191. (user-partition-mount-point user-partition))
  1192. (crypt-label
  1193. (user-partition-crypt-label user-partition))
  1194. (target
  1195. (string-append (%installer-target-dir)
  1196. mount-point)))
  1197. (installer-log-line "unmounting ~s" target)
  1198. (umount target)
  1199. (when crypt-label
  1200. (luks-close user-partition))))
  1201. (reverse sorted-partitions))))
  1202. (define (find-swap-user-partitions user-partitions)
  1203. "Return the subset of <user-partition> records in USER-PARTITIONS list with
  1204. the FS-TYPE field set to 'swap, return the empty list if none found."
  1205. (filter (lambda (user-partition)
  1206. (let ((fs-type (user-partition-fs-type user-partition)))
  1207. (eq? fs-type 'swap)))
  1208. user-partitions))
  1209. (define (start-swapping user-partitions)
  1210. "Start swapping on <user-partition> records with FS-TYPE equal to 'swap."
  1211. (let* ((swap-user-partitions (find-swap-user-partitions user-partitions))
  1212. (swap-devices (map user-partition-file-name swap-user-partitions)))
  1213. (for-each swapon swap-devices)))
  1214. (define (stop-swapping user-partitions)
  1215. "Stop swapping on <user-partition> records with FS-TYPE equal to 'swap."
  1216. (let* ((swap-user-partitions (find-swap-user-partitions user-partitions))
  1217. (swap-devices (map user-partition-file-name swap-user-partitions)))
  1218. (for-each swapoff swap-devices)))
  1219. (define-syntax-rule (with-mounted-partitions user-partitions exp ...)
  1220. "Mount USER-PARTITIONS and start swapping within the dynamic extent of EXP."
  1221. (dynamic-wind
  1222. (lambda ()
  1223. (mount-user-partitions user-partitions)
  1224. (start-swapping user-partitions))
  1225. (lambda ()
  1226. exp ...)
  1227. (lambda ()
  1228. (umount-user-partitions user-partitions)
  1229. (stop-swapping user-partitions)
  1230. #f)))
  1231. (define (user-partition->file-system user-partition)
  1232. "Convert the given USER-PARTITION record in a FILE-SYSTEM record from
  1233. (gnu system file-systems) module and return it."
  1234. (let* ((mount-point (user-partition-mount-point user-partition))
  1235. (fs-type (user-partition-fs-type user-partition))
  1236. (crypt-label (user-partition-crypt-label user-partition))
  1237. (mount-type (user-fs-type->mount-type fs-type))
  1238. (file-name (user-partition-file-name user-partition))
  1239. (upper-file-name (user-partition-upper-file-name user-partition))
  1240. ;; Only compute uuid if partition is not encrypted.
  1241. (uuid (or crypt-label
  1242. (uuid->string (read-partition-uuid file-name) fs-type))))
  1243. `(file-system
  1244. (mount-point ,mount-point)
  1245. (device ,@(if crypt-label
  1246. `(,upper-file-name)
  1247. `((uuid ,uuid (quote ,fs-type)))))
  1248. (type ,mount-type)
  1249. ,@(if crypt-label
  1250. '((dependencies mapped-devices))
  1251. '()))))
  1252. (define (user-partitions->file-systems user-partitions)
  1253. "Convert the given USER-PARTITIONS list of <user-partition> records into a
  1254. list of <file-system> records."
  1255. (filter-map
  1256. (lambda (user-partition)
  1257. (let ((mount-point
  1258. (user-partition-mount-point user-partition)))
  1259. (and mount-point
  1260. (user-partition->file-system user-partition))))
  1261. user-partitions))
  1262. (define (user-partition->mapped-device user-partition)
  1263. "Convert the given USER-PARTITION record into a MAPPED-DEVICE record
  1264. from (gnu system mapped-devices) and return it."
  1265. (let ((label (user-partition-crypt-label user-partition))
  1266. (file-name (user-partition-file-name user-partition)))
  1267. `(mapped-device
  1268. (source (uuid ,(uuid->string
  1269. (read-luks-partition-uuid file-name)
  1270. 'luks)))
  1271. (target ,label)
  1272. (type luks-device-mapping))))
  1273. (define (root-user-partition? partition)
  1274. "Return true if PARTITION is the root partition."
  1275. (let ((mount-point (user-partition-mount-point partition)))
  1276. (and mount-point
  1277. (string=? mount-point "/"))))
  1278. (define (bootloader-configuration user-partitions)
  1279. "Return the bootloader configuration field for USER-PARTITIONS."
  1280. (let* ((root-partition (find root-user-partition?
  1281. user-partitions))
  1282. (root-partition-disk (user-partition-disk-file-name root-partition)))
  1283. `((bootloader-configuration
  1284. ,@(if (efi-installation?)
  1285. `((bootloader grub-efi-bootloader)
  1286. (targets (list ,(default-esp-mount-point))))
  1287. `((bootloader grub-bootloader)
  1288. (targets (list ,root-partition-disk))))
  1289. ;; XXX: Assume we defined the 'keyboard-layout' field of
  1290. ;; <operating-system> right above.
  1291. (keyboard-layout keyboard-layout)))))
  1292. (define (user-partition-missing-modules user-partitions)
  1293. "Return the list of kernel modules missing from the default set of kernel
  1294. modules to access USER-PARTITIONS."
  1295. (let ((devices (filter user-partition-crypt-label user-partitions))
  1296. (root (find root-user-partition? user-partitions)))
  1297. (delete-duplicates
  1298. (append-map (lambda (device)
  1299. (catch 'system-error
  1300. (lambda ()
  1301. (missing-modules device %base-initrd-modules))
  1302. (const '())))
  1303. (delete-duplicates
  1304. (map user-partition-file-name
  1305. (cons root devices)))))))
  1306. (define (initrd-configuration user-partitions)
  1307. "Return an 'initrd-modules' field with everything needed for
  1308. USER-PARTITIONS, or return nothing."
  1309. (match (user-partition-missing-modules user-partitions)
  1310. (()
  1311. '())
  1312. ((modules ...)
  1313. `((initrd-modules (append ',modules
  1314. %base-initrd-modules))))))
  1315. (define (user-partitions->configuration user-partitions)
  1316. "Return the configuration field for USER-PARTITIONS."
  1317. (let* ((swap-user-partitions (find-swap-user-partitions user-partitions))
  1318. (swap-devices (map user-partition-file-name swap-user-partitions))
  1319. (encrypted-partitions
  1320. (filter user-partition-crypt-label user-partitions)))
  1321. `((bootloader ,@(bootloader-configuration user-partitions))
  1322. ,@(initrd-configuration user-partitions)
  1323. ,@(if (null? swap-devices)
  1324. '()
  1325. (let* ((uuids (map (lambda (file)
  1326. (uuid->string (read-partition-uuid file)))
  1327. swap-devices)))
  1328. `((swap-devices
  1329. (list ,@(map (lambda (uuid)
  1330. `(swap-space
  1331. (target (uuid ,uuid))))
  1332. uuids))))))
  1333. ,@(if (null? encrypted-partitions)
  1334. '()
  1335. `((mapped-devices
  1336. (list ,@(map user-partition->mapped-device
  1337. encrypted-partitions)))))
  1338. ,(vertical-space 1)
  1339. ,(let-syntax ((G_ (syntax-rules () ((_ str) str))))
  1340. (comment (G_ "\
  1341. ;; The list of file systems that get \"mounted\". The unique
  1342. ;; file system identifiers there (\"UUIDs\") can be obtained
  1343. ;; by running 'blkid' in a terminal.\n")))
  1344. (file-systems (cons*
  1345. ,@(user-partitions->file-systems user-partitions)
  1346. %base-file-systems)))))
  1347. ;;
  1348. ;; Initialization.
  1349. ;;
  1350. (define (init-parted)
  1351. "Initialize libparted support."
  1352. (probe-all-devices!)
  1353. ;; Remove all logical devices, otherwise "device-is-busy?" will report true
  1354. ;; on all devices containaing active logical volumes.
  1355. (remove-logical-devices)
  1356. (exception-set-handler (lambda (exception)
  1357. EXCEPTION-OPTION-UNHANDLED)))
  1358. (define (free-parted devices)
  1359. "Deallocate memory used for DEVICES in parted, force sync them and wait for
  1360. the devices not to be used before returning."
  1361. ;; XXX: Formatting and further operations on disk partition table may fail
  1362. ;; because the partition table changes are not synced, or because the device
  1363. ;; is still in use, even if parted should have finished editing
  1364. ;; partitions. This is not well understood, but syncing devices and waiting
  1365. ;; them to stop returning EBUSY to BLKRRPART ioctl seems to be enough. The
  1366. ;; same kind of issue is described here:
  1367. ;; https://mail.gnome.org/archives/commits-list/2013-March/msg18423.html.
  1368. (let ((device-file-names (map device-path devices)))
  1369. (for-each force-device-sync devices)
  1370. (for-each (lambda (file-name)
  1371. (let/time ((time in-use?
  1372. (with-delay-device-in-use? file-name)))
  1373. (if in-use?
  1374. (error
  1375. (format #f (G_ "Device ~a is still in use.")
  1376. file-name))
  1377. (installer-log-line "Syncing ~a took ~a seconds."
  1378. file-name (time-second time)))))
  1379. device-file-names)))