parted.scm 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566
  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. (define (read-partition-uuid/retry file-name)
  292. "Call READ-PARTITION-UUID with 5 retries spaced by 1 second. This is useful
  293. if the partition table is updated by the kernel at the time this function is
  294. called, causing the underlying /dev to be absent."
  295. (define max-retries 5)
  296. (let loop ((retry max-retries))
  297. (catch #t
  298. (lambda ()
  299. (read-partition-uuid file-name))
  300. (lambda _
  301. (if (> retry 0)
  302. (begin
  303. (sleep 1)
  304. (loop (- retry 1)))
  305. (error
  306. (format #f (G_ "Could not open ~a after ~a retries~%.")
  307. file-name max-retries)))))))
  308. ;;
  309. ;; Devices
  310. ;;
  311. (define (with-delay-device-in-use? file-name)
  312. "Call DEVICE-IN-USE? with a few retries, as the first re-read will often
  313. fail. See rereadpt function in wipefs.c of util-linux for an explanation."
  314. ;; Kernel always return EINVAL for BLKRRPART on loopdevices.
  315. (and (not (string-match "/dev/loop*" file-name))
  316. (let loop ((try 16))
  317. (usleep 250000)
  318. (let ((in-use? (device-in-use? file-name)))
  319. (if (and in-use? (> try 0))
  320. (loop (- try 1))
  321. in-use?)))))
  322. (define* (force-device-sync device)
  323. "Force a flushing of the given DEVICE."
  324. (device-open device)
  325. (device-sync device)
  326. (device-close device))
  327. (define (remove-logical-devices)
  328. "Remove all active logical devices."
  329. ((run-command-in-installer) "dmsetup" "remove_all"))
  330. (define (installer-root-partition-path)
  331. "Return the root partition path, or #f if it could not be detected."
  332. (let* ((cmdline (linux-command-line))
  333. (root (find-long-option "root" cmdline)))
  334. (and root
  335. (or (and (access? root F_OK) root)
  336. (find-partition-by-label root)
  337. (and=> (uuid root)
  338. find-partition-by-uuid)))))
  339. ;; Minimal installation device size.
  340. (define %min-device-size
  341. (* 2 GIBIBYTE-SIZE)) ;2GiB
  342. (define (mapped-device? device)
  343. "Return #true if DEVICE is a mapped device, false otherwise."
  344. (string-prefix? "/dev/dm-" device))
  345. ;; TODO: Use DM_TABLE_DEPS ioctl instead of dmsetup.
  346. (define (mapped-device-parent-partition device)
  347. "Return the parent partition path of the mapped DEVICE."
  348. (let* ((command `("dmsetup" "deps" ,device "-o" "devname"))
  349. (parent #f)
  350. (handler
  351. (lambda (input)
  352. ;; We are parsing an output that should look like:
  353. ;; 1 dependencies : (sda2)
  354. (let ((result
  355. (string-match "\\(([^\\)]+)\\)"
  356. (get-string-all input))))
  357. (and result
  358. (set! parent
  359. (format #f "/dev/~a"
  360. (match:substring result 1))))))))
  361. (run-external-command-with-handler handler command)
  362. parent))
  363. (define (eligible-devices)
  364. "Return all the available devices except the install device and the devices
  365. which are smaller than %MIN-DEVICE-SIZE."
  366. (define the-installer-root-partition-path
  367. (let ((root (installer-root-partition-path)))
  368. (cond
  369. ((mapped-device? root)
  370. ;; If the partition is a mapped device (/dev/dm-X), locate the parent
  371. ;; partition. It is the case when Ventoy is used to host the
  372. ;; installation image.
  373. (let ((parent (mapped-device-parent-partition root)))
  374. (installer-log-line "mapped device ~a -> ~a" parent root)
  375. parent))
  376. (else root))))
  377. (define (small-device? device)
  378. (let ((length (device-length device))
  379. (sector-size (device-sector-size device)))
  380. (and (< (* length sector-size) %min-device-size)
  381. (installer-log-line "~a is not eligible because it is smaller than \
  382. ~a."
  383. (device-path device)
  384. (unit-format-custom-byte device
  385. %min-device-size
  386. UNIT-GIGABYTE)))))
  387. ;; Read partition table of device and compare each path to the one
  388. ;; we're booting from to determine if it is the installation
  389. ;; device.
  390. (define (installation-device? device)
  391. ;; When using CDROM based installation, the root partition path may be the
  392. ;; device path.
  393. (and (or (string=? the-installer-root-partition-path
  394. (device-path device))
  395. (let ((disk (disk-new device)))
  396. (and disk
  397. (any (lambda (partition)
  398. (string=? the-installer-root-partition-path
  399. (partition-get-path partition)))
  400. (disk-partitions disk)))))
  401. (installer-log-line "~a is not eligible because it is the \
  402. installation device."
  403. (device-path device))))
  404. (remove
  405. (lambda (device)
  406. (or (installation-device? device)
  407. (small-device? device)))
  408. (devices)))
  409. ;;
  410. ;; Disk and partition printing.
  411. ;;
  412. (define* (device-description device #:optional disk)
  413. "Return a string describing the given DEVICE."
  414. (let* ((type (device-type device))
  415. (file-name (device-path device))
  416. (model (device-model device))
  417. (type-str (device-type->string type))
  418. (disk-type (if disk
  419. (disk-disk-type disk)
  420. (disk-probe device)))
  421. (length (device-length device))
  422. (sector-size (device-sector-size device))
  423. (end (unit-format-custom-byte device
  424. (* length sector-size)
  425. UNIT-GIGABYTE)))
  426. (string-join
  427. `(,@(if (string=? model "")
  428. `(,type-str)
  429. `(,model ,(string-append "(" type-str ")")))
  430. ,file-name
  431. ,end
  432. ,@(if disk-type
  433. `(,(disk-type-name disk-type))
  434. '()))
  435. " ")))
  436. (define (partition-end-formatted device partition)
  437. "Return as a string the end of PARTITION with the relevant unit."
  438. (unit-format-byte
  439. device
  440. (-
  441. (* (+ (partition-end partition) 1)
  442. (device-sector-size device))
  443. 1)))
  444. (define (partition-print-number partition)
  445. "Convert the given partition NUMBER to string."
  446. (let ((number (partition-number partition)))
  447. (number->string number)))
  448. (define (partition-description partition user-partition)
  449. "Return a string describing the given PARTITION, located on the DISK of
  450. DEVICE."
  451. (define (partition-print-type partition)
  452. "Return the type of PARTITION as a string."
  453. (if (freespace-partition? partition)
  454. (G_ "Free space")
  455. (let ((type (partition-type partition)))
  456. (match type
  457. ((type-symbol)
  458. (symbol->string type-symbol))))))
  459. (define (partition-print-flags partition)
  460. "Return the flags of PARTITION as a string of comma separated flags."
  461. (string-join
  462. (filter-map
  463. (lambda (flag)
  464. (and (partition-get-flag partition flag)
  465. (partition-flag-get-name flag)))
  466. (partition-flags partition))
  467. ","))
  468. (define (maybe-string-pad string length)
  469. "Returned a string formatted by padding STRING of LENGTH characters to the
  470. right. If STRING is #f use an empty string."
  471. (if (and string (not (string=? string "")))
  472. (string-pad-right string length)
  473. ""))
  474. (let* ((disk (partition-disk partition))
  475. (device (disk-device disk))
  476. (disk-type (disk-disk-type disk))
  477. (has-name? (disk-type-check-feature
  478. disk-type
  479. DISK-TYPE-FEATURE-PARTITION-NAME))
  480. (has-extended? (disk-type-check-feature
  481. disk-type
  482. DISK-TYPE-FEATURE-EXTENDED))
  483. (part-type (partition-print-type partition))
  484. (number (and (not (freespace-partition? partition))
  485. (partition-print-number partition)))
  486. (name (and has-name?
  487. (if (freespace-partition? partition)
  488. (G_ "Free space")
  489. (partition-get-name partition))))
  490. (start (unit-format device
  491. (partition-start partition)))
  492. (end (partition-end-formatted device partition))
  493. (size (unit-format device (partition-length partition)))
  494. (fs-type (partition-fs-type partition))
  495. (fs-type-name (and fs-type
  496. (filesystem-type-name fs-type)))
  497. (crypt-label (and user-partition
  498. (user-partition-crypt-label user-partition)))
  499. (flags (and (not (freespace-partition? partition))
  500. (partition-print-flags partition)))
  501. (mount-point (and user-partition
  502. (user-partition-mount-point user-partition))))
  503. `(,(or number "")
  504. ,@(if has-extended?
  505. (list part-type)
  506. '())
  507. ,size
  508. ,(or fs-type-name "")
  509. ,(or flags "")
  510. ,(or mount-point "")
  511. ,(or crypt-label "")
  512. ,(maybe-string-pad name 30))))
  513. (define (partitions-descriptions partitions user-partitions)
  514. "Return a list of strings describing all the partitions found on
  515. DEVICE. METADATA partitions are not described. The strings are padded to the
  516. right so that they can be displayed as a table."
  517. (define (max-length-column lists column-index)
  518. "Return the maximum length of the string at position COLUMN-INDEX in the
  519. list of string lists LISTS."
  520. (apply max
  521. (map (lambda (list)
  522. (string-length
  523. (list-ref list column-index)))
  524. lists)))
  525. (define (pad-descriptions descriptions)
  526. "Return a padded version of the list of string lists DESCRIPTIONS. The
  527. strings are padded to the length of the longer string in a same column, as
  528. determined by MAX-LENGTH-COLUMN procedure."
  529. (let* ((description-length (length (car descriptions)))
  530. (paddings (map (lambda (index)
  531. (max-length-column descriptions index))
  532. (iota description-length))))
  533. (map (lambda (description)
  534. (map string-pad-right description paddings))
  535. descriptions)))
  536. (let* ((descriptions
  537. (map
  538. (lambda (partition)
  539. (let ((user-partition
  540. (find-user-partition-by-parted-object user-partitions
  541. partition)))
  542. (partition-description partition user-partition)))
  543. partitions))
  544. (padded-descriptions (if (null? partitions)
  545. '()
  546. (pad-descriptions descriptions))))
  547. (map (cut string-join <> " ") padded-descriptions)))
  548. (define (user-partition-description user-partition)
  549. "Return a string describing the given USER-PARTITION record."
  550. (let* ((partition (user-partition-parted-object user-partition))
  551. (disk (partition-disk partition))
  552. (disk-type (disk-disk-type disk))
  553. (device (disk-device disk))
  554. (has-name? (disk-type-check-feature
  555. disk-type
  556. DISK-TYPE-FEATURE-PARTITION-NAME))
  557. (has-extended? (disk-type-check-feature
  558. disk-type
  559. DISK-TYPE-FEATURE-EXTENDED))
  560. (name (user-partition-name user-partition))
  561. (type (user-partition-type user-partition))
  562. (type-name (symbol->string type))
  563. (fs-type (user-partition-fs-type user-partition))
  564. (fs-type-name (user-fs-type-name fs-type))
  565. (bootable? (user-partition-bootable? user-partition))
  566. (esp? (user-partition-esp? user-partition))
  567. (need-formatting? (user-partition-need-formatting? user-partition))
  568. (crypt-label (user-partition-crypt-label user-partition))
  569. (size (user-partition-size user-partition))
  570. (mount-point (user-partition-mount-point user-partition)))
  571. `(,@(if has-name?
  572. `((name . ,(format #f (G_ "Name: ~a")
  573. (or name (G_ "None")))))
  574. '())
  575. ,@(if (and has-extended?
  576. (freespace-partition? partition)
  577. (not (eq? type 'logical)))
  578. `((type . ,(format #f (G_ "Type: ~a") type-name)))
  579. '())
  580. ,@(if (eq? type 'extended)
  581. '()
  582. `((fs-type . ,(format #f (G_ "File system type: ~a")
  583. fs-type-name))))
  584. ,@(if (or (eq? type 'extended)
  585. (eq? fs-type 'swap)
  586. (not has-extended?))
  587. '()
  588. `((bootable . ,(format #f (G_ "Bootable flag: ~:[off~;on~]")
  589. bootable?))))
  590. ,@(if (and (not has-extended?)
  591. (not (eq? fs-type 'swap)))
  592. `((esp? . ,(format #f (G_ "ESP flag: ~:[off~;on~]") esp?)))
  593. '())
  594. ,@(if (freespace-partition? partition)
  595. (let ((size-formatted
  596. (or size (unit-format device ;XXX: i18n
  597. (partition-length partition)))))
  598. `((size . ,(format #f (G_ "Size: ~a") size-formatted))))
  599. '())
  600. ,@(if (or (eq? type 'extended)
  601. (eq? fs-type 'swap))
  602. '()
  603. `((crypt-label
  604. . ,(format #f (G_ "Encryption: ~:[No~a~;Yes (label '~a')~]")
  605. crypt-label (or crypt-label "")))))
  606. ,@(if (or (freespace-partition? partition)
  607. (eq? fs-type 'swap))
  608. '()
  609. `((need-formatting?
  610. . ,(format #f (G_ "Format the partition? ~:[No~;Yes~]")
  611. need-formatting?))))
  612. ,@(if (or (eq? type 'extended)
  613. (eq? fs-type 'swap))
  614. '()
  615. `((mount-point
  616. . ,(format #f (G_ "Mount point: ~a")
  617. (or mount-point
  618. (and esp? (default-esp-mount-point))
  619. (G_ "None")))))))))
  620. ;;
  621. ;; Partition table creation.
  622. ;;
  623. (define (mklabel device type-name)
  624. "Create a partition table on DEVICE. TYPE-NAME is the type of the partition
  625. table, \"msdos\" or \"gpt\"."
  626. (let* ((type (disk-type-get type-name))
  627. (disk (disk-new-fresh device type)))
  628. (or disk
  629. (raise
  630. (condition
  631. (&error)
  632. (&message (message (format #f "Cannot create partition table of type
  633. ~a on device ~a." type-name (device-path device)))))))))
  634. ;;
  635. ;; Partition creation.
  636. ;;
  637. ;; The maximum count of primary partitions is exceeded.
  638. (define-condition-type &max-primary-exceeded &condition
  639. max-primary-exceeded?)
  640. ;; It is not possible to create an extended partition.
  641. (define-condition-type &extended-creation-error &condition
  642. extended-creation-error?)
  643. ;; It is not possible to create a logical partition.
  644. (define-condition-type &logical-creation-error &condition
  645. logical-creation-error?)
  646. (define (can-create-primary? disk)
  647. "Return #t if it is possible to create a primary partition on DISK, return
  648. #f otherwise."
  649. (let ((max-primary (disk-get-max-primary-partition-count disk)))
  650. (find (lambda (number)
  651. (not (disk-get-partition disk number)))
  652. (iota max-primary 1))))
  653. (define (can-create-extended? disk)
  654. "Return #t if it is possible to create an extended partition on DISK, return
  655. #f otherwise."
  656. (let* ((disk-type (disk-disk-type disk))
  657. (has-extended? (disk-type-check-feature
  658. disk-type
  659. DISK-TYPE-FEATURE-EXTENDED)))
  660. (and (can-create-primary? disk)
  661. has-extended?
  662. (not (disk-extended-partition disk)))))
  663. (define (can-create-logical? disk)
  664. "Return #t is it is possible to create a logical partition on DISK, return
  665. #f otherwise."
  666. (let* ((disk-type (disk-disk-type disk))
  667. (has-extended? (disk-type-check-feature
  668. disk-type
  669. DISK-TYPE-FEATURE-EXTENDED)))
  670. (and has-extended?
  671. (disk-extended-partition disk))))
  672. (define (can-create-partition? user-part)
  673. "Return #t if it is possible to create the given USER-PART record, return #f
  674. otherwise."
  675. (let* ((type (user-partition-type user-part))
  676. (partition (user-partition-parted-object user-part))
  677. (disk (partition-disk partition)))
  678. (case type
  679. ((normal)
  680. (or (can-create-primary? disk)
  681. (raise
  682. (condition (&max-primary-exceeded)))))
  683. ((extended)
  684. (or (can-create-extended? disk)
  685. (raise
  686. (condition (&extended-creation-error)))))
  687. ((logical)
  688. (or (can-create-logical? disk)
  689. (raise
  690. (condition (&logical-creation-error))))))))
  691. (define* (mkpart disk user-partition
  692. #:key (previous-partition #f))
  693. "Create the given USER-PARTITION on DISK. The PREVIOUS-PARTITION argument as
  694. to be set to the partition preceding USER-PARTITION if any."
  695. (define (parse-start-end start end)
  696. "Parse start and end strings as positions on DEVICE expressed with a unit,
  697. like '100GB' or '12.2KiB'. Return a list of 4 elements, the start sector, its
  698. range (1 unit large area centered on start sector), the end sector and its
  699. range."
  700. (let ((device (disk-device disk)))
  701. (call-with-values
  702. (lambda ()
  703. (unit-parse start device))
  704. (lambda (start-sector start-range)
  705. (call-with-values
  706. (lambda ()
  707. (unit-parse end device))
  708. (lambda (end-sector end-range)
  709. (list start-sector start-range
  710. end-sector end-range)))))))
  711. (define* (extend-ranges! start-range end-range
  712. #:key (offset 0))
  713. "Try to extend START-RANGE by 1 MEBIBYTE to the right and END-RANGE by 1
  714. MEBIBYTE to the left. This way, if the disk is aligned on 2048 sectors of
  715. 512KB (like frequently), we will have a chance for the
  716. 'optimal-align-constraint' to succeed. Do not extend ranges if that would
  717. cause them to cross."
  718. (let* ((device (disk-device disk))
  719. (start-range-end (geometry-end start-range))
  720. (end-range-start (geometry-start end-range))
  721. (mebibyte-sector-size (/ MEBIBYTE-SIZE
  722. (device-sector-size device)))
  723. (new-start-range-end
  724. (+ start-range-end mebibyte-sector-size offset))
  725. (new-end-range-start
  726. (- end-range-start mebibyte-sector-size offset)))
  727. (when (< new-start-range-end new-end-range-start)
  728. (geometry-set-end start-range new-start-range-end)
  729. (geometry-set-start end-range new-end-range-start))))
  730. (match (parse-start-end (user-partition-start user-partition)
  731. (user-partition-end user-partition))
  732. ((start-sector start-range end-sector end-range)
  733. (let* ((prev-end (if previous-partition
  734. (partition-end previous-partition)
  735. 0))
  736. (start-distance (- start-sector prev-end))
  737. (type (user-partition-type user-partition))
  738. ;; There should be at least 2 unallocated sectors in front of each
  739. ;; logical partition, otherwise parted will fail badly:
  740. ;; https://gparted.org/h2-fix-msdos-pt.php#apply-action-fail.
  741. (start-offset (if previous-partition
  742. (- 3 start-distance)
  743. 0))
  744. (start-sector* (if (and (eq? type 'logical)
  745. (< start-distance 3))
  746. (+ start-sector start-offset)
  747. start-sector)))
  748. ;; This is a hack. Parted almost always fails to create optimally
  749. ;; aligned partitions (unless specifying percentages) because the
  750. ;; default range of 1MB centered on the start sector is not enough when
  751. ;; the optimal alignment is 2048 sectors of 512KB.
  752. (extend-ranges! start-range end-range #:offset start-offset)
  753. (let* ((device (disk-device disk))
  754. (disk-type (disk-disk-type disk))
  755. (length (device-length device))
  756. (name (user-partition-name user-partition))
  757. (filesystem-type
  758. (filesystem-type-get
  759. (user-fs-type-name
  760. (user-partition-fs-type user-partition))))
  761. (flags `(,@(if (user-partition-bootable? user-partition)
  762. `(,PARTITION-FLAG-BOOT)
  763. '())
  764. ,@(if (user-partition-esp? user-partition)
  765. `(,PARTITION-FLAG-ESP)
  766. '())
  767. ,@(if (user-partition-bios-grub? user-partition)
  768. `(,PARTITION-FLAG-BIOS-GRUB)
  769. '())))
  770. (has-name? (disk-type-check-feature
  771. disk-type
  772. DISK-TYPE-FEATURE-PARTITION-NAME))
  773. (partition-type (partition-type->int type))
  774. (partition (partition-new disk
  775. #:type partition-type
  776. #:filesystem-type filesystem-type
  777. #:start start-sector*
  778. #:end end-sector))
  779. (user-constraint (constraint-new
  780. #:start-align 'any
  781. #:end-align 'any
  782. #:start-range start-range
  783. #:end-range end-range
  784. #:min-size 1
  785. #:max-size length))
  786. (dev-constraint
  787. (device-get-optimal-aligned-constraint device))
  788. (final-constraint (constraint-intersect user-constraint
  789. dev-constraint))
  790. (no-constraint (constraint-any device))
  791. ;; Try to create a partition with an optimal alignment
  792. ;; constraint. If it fails, fallback to creating a partition
  793. ;; with no specific constraint.
  794. (partition-constraint-ok?
  795. (disk-add-partition disk partition final-constraint))
  796. (partition-no-contraint-ok?
  797. (or partition-constraint-ok?
  798. (disk-add-partition disk partition no-constraint)))
  799. (partition-ok?
  800. (or partition-constraint-ok? partition-no-contraint-ok?)))
  801. (installer-log-line "Creating partition:")
  802. (installer-log-line "~/type: ~a" partition-type)
  803. (installer-log-line "~/filesystem-type: ~a"
  804. (filesystem-type-name filesystem-type))
  805. (installer-log-line "~/flags: ~a" flags)
  806. (installer-log-line "~/start: ~a" start-sector*)
  807. (installer-log-line "~/end: ~a" end-sector)
  808. (installer-log-line "~/start-range: [~a, ~a]"
  809. (geometry-start start-range)
  810. (geometry-end start-range))
  811. (installer-log-line "~/end-range: [~a, ~a]"
  812. (geometry-start end-range)
  813. (geometry-end end-range))
  814. (installer-log-line "~/constraint: ~a"
  815. partition-constraint-ok?)
  816. (installer-log-line "~/no-constraint: ~a"
  817. partition-no-contraint-ok?)
  818. ;; Set the partition name if supported.
  819. (when (and partition-ok? has-name? name)
  820. (partition-set-name partition name))
  821. ;; Both partition-set-system and partition-set-flag calls can affect
  822. ;; the partition type. Their order is important, see:
  823. ;; https://issues.guix.gnu.org/55549.
  824. (partition-set-system partition filesystem-type)
  825. ;; Set flags if required.
  826. (for-each (lambda (flag)
  827. (and (partition-is-flag-available? partition flag)
  828. (partition-set-flag partition flag 1)))
  829. flags)
  830. (and partition-ok? partition))))))
  831. ;;
  832. ;; Partition destruction.
  833. ;;
  834. (define (rmpart disk number)
  835. "Remove the partition with the given NUMBER on DISK."
  836. (let ((partition (disk-get-partition disk number)))
  837. (disk-remove-partition* disk partition)))
  838. ;;
  839. ;; Auto partitionning.
  840. ;;
  841. (define* (create-adjacent-partitions! disk partitions
  842. #:key (last-partition-end 0))
  843. "Create the given PARTITIONS on DISK. LAST-PARTITION-END is the sector from
  844. which we want to start creating partitions. The START and END of each created
  845. partition are computed from its SIZE value and the position of the last
  846. partition."
  847. (let ((device (disk-device disk)))
  848. (let loop ((partitions partitions)
  849. (remaining-space (- (device-length device)
  850. last-partition-end))
  851. (start last-partition-end))
  852. (match partitions
  853. (() '())
  854. ((partition . rest)
  855. (let* ((size (user-partition-size partition))
  856. (percentage-size (and (string? size)
  857. (read-percentage size)))
  858. (sector-size (device-sector-size device))
  859. (partition-size (if percentage-size
  860. (exact->inexact
  861. (* (/ percentage-size 100)
  862. remaining-space))
  863. size))
  864. (end-partition (min (- (device-length device) 1)
  865. (nearest-exact-integer
  866. (+ start partition-size 1))))
  867. (name (user-partition-name partition))
  868. (type (user-partition-type partition))
  869. (fs-type (user-partition-fs-type partition))
  870. (start-formatted (unit-format-custom device
  871. start
  872. UNIT-SECTOR))
  873. (end-formatted (unit-format-custom device
  874. end-partition
  875. UNIT-SECTOR))
  876. (new-user-partition (user-partition
  877. (inherit partition)
  878. (start start-formatted)
  879. (end end-formatted)))
  880. (new-partition
  881. (mkpart disk new-user-partition)))
  882. (if new-partition
  883. (cons (user-partition
  884. (inherit new-user-partition)
  885. (file-name (partition-get-path new-partition))
  886. (disk-file-name (device-path device))
  887. (parted-object new-partition))
  888. (loop rest
  889. (if (eq? type 'extended)
  890. remaining-space
  891. (- remaining-space
  892. (partition-length new-partition)))
  893. (if (eq? type 'extended)
  894. (+ start 1)
  895. (+ (partition-end new-partition) 1))))
  896. (error
  897. (format #f "Unable to create partition ~a~%" name)))))))))
  898. (define (force-user-partitions-formatting user-partitions)
  899. "Set the NEED-FORMATTING? fields to #t on all <user-partition> records of
  900. USER-PARTITIONS list and return the updated list."
  901. (map (lambda (p)
  902. (user-partition
  903. (inherit p)
  904. (need-formatting? #t)))
  905. user-partitions))
  906. (define* (auto-partition! disk
  907. #:key
  908. (scheme 'entire-root))
  909. "Automatically create partitions on DISK. All the previous
  910. partitions (except the ESP on a GPT disk, if present) are wiped. SCHEME is the
  911. desired partitioning scheme. It can be 'entire-root or
  912. 'entire-root-home. 'entire-root will create a swap partition and a root
  913. partition occupying all the remaining space. 'entire-root-home will create a
  914. swap partition, a root partition and a home partition.
  915. Return the complete list of partitions on DISK, including the ESP when it
  916. exists."
  917. (let* ((device (disk-device disk))
  918. (disk-type (disk-disk-type disk))
  919. (has-extended? (disk-type-check-feature
  920. disk-type
  921. DISK-TYPE-FEATURE-EXTENDED))
  922. (partitions (filter data-partition? (disk-partitions disk)))
  923. (esp-partition (find-esp-partition partitions))
  924. ;; According to
  925. ;; https://wiki.archlinux.org/index.php/EFI_system_partition, the ESP
  926. ;; size should be at least 550MiB.
  927. (new-esp-size (nearest-exact-integer
  928. (/ (* 550 MEBIBYTE-SIZE)
  929. (device-sector-size device))))
  930. (end-esp-partition (and esp-partition
  931. (partition-end esp-partition)))
  932. (non-boot-partitions (remove esp-partition? partitions))
  933. (bios-grub-size (/ (* 3 MEBIBYTE-SIZE)
  934. (device-sector-size device)))
  935. (five-percent-disk (nearest-exact-integer
  936. (* 0.05 (device-length device))))
  937. (default-swap-size (nearest-exact-integer
  938. (/ (* 4 GIGABYTE-SIZE)
  939. (device-sector-size device))))
  940. ;; Use a 4GB size for the swap if it represents less than 5% of the
  941. ;; disk space. Otherwise, set the swap size to 5% of the disk space.
  942. (swap-size (min default-swap-size five-percent-disk)))
  943. ;; Remove everything but esp if it exists.
  944. (for-each
  945. (lambda (partition)
  946. (and (data-partition? partition)
  947. ;; Do not remove logical partitions ourselves, since
  948. ;; disk-remove-partition* will remove all the logical partitions
  949. ;; residing on an extended partition, which would lead to a
  950. ;; double-remove and ensuing SEGFAULT.
  951. (not (logical-partition? partition))
  952. (disk-remove-partition* disk partition)))
  953. non-boot-partitions)
  954. (let* ((start-partition
  955. (if (efi-installation?)
  956. (and (not esp-partition)
  957. (user-partition
  958. (fs-type 'fat32)
  959. (esp? #t)
  960. (size new-esp-size)
  961. (mount-point (default-esp-mount-point))))
  962. (user-partition
  963. (fs-type 'ext4)
  964. (bootable? #t)
  965. (bios-grub? #t)
  966. (size bios-grub-size))))
  967. (new-partitions
  968. (cond
  969. ((or (eq? scheme 'entire-root)
  970. (eq? scheme 'entire-encrypted-root))
  971. (let ((encrypted? (eq? scheme 'entire-encrypted-root)))
  972. `(,@(if start-partition
  973. `(,start-partition)
  974. '())
  975. ,@(if encrypted?
  976. '()
  977. `(,(user-partition
  978. (fs-type 'swap)
  979. (size swap-size))))
  980. ,(user-partition
  981. (fs-type 'ext4)
  982. (bootable? has-extended?)
  983. (crypt-label (and encrypted? "cryptroot"))
  984. (size "100%")
  985. (mount-point "/")))))
  986. ((or (eq? scheme 'entire-root-home)
  987. (eq? scheme 'entire-encrypted-root-home))
  988. (let ((encrypted? (eq? scheme 'entire-encrypted-root-home)))
  989. `(,@(if start-partition
  990. `(,start-partition)
  991. '())
  992. ,(user-partition
  993. (fs-type 'ext4)
  994. (bootable? has-extended?)
  995. (crypt-label (and encrypted? "cryptroot"))
  996. (size "33%")
  997. (mount-point "/"))
  998. ,@(if has-extended?
  999. `(,(user-partition
  1000. (type 'extended)
  1001. (size "100%")))
  1002. '())
  1003. ,@(if encrypted?
  1004. '()
  1005. `(,(user-partition
  1006. (type (if has-extended?
  1007. 'logical
  1008. 'normal))
  1009. (fs-type 'swap)
  1010. (size swap-size))))
  1011. ,(user-partition
  1012. (type (if has-extended?
  1013. 'logical
  1014. 'normal))
  1015. (fs-type 'ext4)
  1016. (crypt-label (and encrypted? "crypthome"))
  1017. (size "100%")
  1018. (mount-point "/home")))))))
  1019. (new-partitions* (force-user-partitions-formatting
  1020. new-partitions)))
  1021. (append (if esp-partition
  1022. (list (partition->user-partition esp-partition))
  1023. '())
  1024. (create-adjacent-partitions! disk
  1025. new-partitions*
  1026. #:last-partition-end
  1027. (or end-esp-partition 0))))))
  1028. ;;
  1029. ;; Convert user-partitions.
  1030. ;;
  1031. ;; No root mount point found.
  1032. (define-condition-type &no-root-mount-point &condition
  1033. no-root-mount-point?)
  1034. ;; Cannot not read the partition UUID.
  1035. (define-condition-type &cannot-read-uuid &condition
  1036. cannot-read-uuid?
  1037. (partition cannot-read-uuid-partition))
  1038. (define (check-user-partitions user-partitions)
  1039. "Check the following statements:
  1040. The USER-PARTITIONS list contains one <user-partition> record with a
  1041. mount-point set to '/'. Raise &no-root-mount-point condition otherwise.
  1042. All the USER-PARTITIONS with a mount point and that will not be formatted have
  1043. a valid UUID. Raise a &cannot-read-uuid condition specifying the faulty
  1044. partition otherwise.
  1045. Return #t if all the statements are valid."
  1046. (define (check-root)
  1047. (let ((mount-points
  1048. (map user-partition-mount-point user-partitions)))
  1049. (or (member "/" mount-points)
  1050. (raise
  1051. (condition (&no-root-mount-point))))))
  1052. (define (check-uuid)
  1053. (let ((mount-partitions
  1054. (filter user-partition-mount-point user-partitions)))
  1055. (every
  1056. (lambda (user-partition)
  1057. (let ((file-name (user-partition-file-name user-partition))
  1058. (need-formatting?
  1059. (user-partition-need-formatting? user-partition)))
  1060. (or need-formatting?
  1061. (read-partition-uuid/retry file-name)
  1062. (raise
  1063. (condition
  1064. (&cannot-read-uuid
  1065. (partition file-name)))))))
  1066. mount-partitions)))
  1067. (and (check-root)
  1068. (check-uuid)
  1069. #t))
  1070. (define (set-user-partitions-file-name user-partitions)
  1071. "Set the partition file-name of <user-partition> records in USER-PARTITIONS
  1072. list and return the updated list."
  1073. (map (lambda (p)
  1074. (let* ((partition (user-partition-parted-object p))
  1075. (file-name (partition-get-path partition)))
  1076. (user-partition
  1077. (inherit p)
  1078. (file-name file-name))))
  1079. user-partitions))
  1080. (define (create-btrfs-file-system partition)
  1081. "Create a btrfs file-system for PARTITION file-name."
  1082. ((run-command-in-installer) "mkfs.btrfs" "-f" partition))
  1083. (define (create-ext4-file-system partition)
  1084. "Create an ext4 file-system for PARTITION file-name."
  1085. ((run-command-in-installer) "mkfs.ext4" "-F" partition))
  1086. (define (create-fat16-file-system partition)
  1087. "Create a fat16 file-system for PARTITION file-name."
  1088. ((run-command-in-installer) "mkfs.fat" "-F16" partition))
  1089. (define (create-fat32-file-system partition)
  1090. "Create a fat32 file-system for PARTITION file-name."
  1091. ((run-command-in-installer) "mkfs.fat" "-F32" partition))
  1092. (define (create-jfs-file-system partition)
  1093. "Create a JFS file-system for PARTITION file-name."
  1094. ((run-command-in-installer) "jfs_mkfs" "-f" partition))
  1095. (define (create-ntfs-file-system partition)
  1096. "Create a JFS file-system for PARTITION file-name."
  1097. ((run-command-in-installer) "mkfs.ntfs" "-F" "-f" partition))
  1098. (define (create-xfs-file-system partition)
  1099. "Create an XFS file-system for PARTITION file-name."
  1100. ((run-command-in-installer) "mkfs.xfs" "-f" partition))
  1101. (define (create-swap-partition partition)
  1102. "Set up swap area on PARTITION file-name."
  1103. ((run-command-in-installer) "mkswap" "-f" partition))
  1104. (define (call-with-luks-key-file password proc)
  1105. "Write PASSWORD in a temporary file and pass it to PROC as argument."
  1106. (call-with-temporary-output-file
  1107. (lambda (file port)
  1108. (put-string port password)
  1109. (close port)
  1110. (proc file))))
  1111. (define (user-partition-upper-file-name user-partition)
  1112. "Return the file-name of the virtual block device corresponding to
  1113. USER-PARTITION if it is encrypted, or the plain file-name otherwise."
  1114. (let ((crypt-label (user-partition-crypt-label user-partition))
  1115. (file-name (user-partition-file-name user-partition)))
  1116. (if crypt-label
  1117. (string-append "/dev/mapper/" crypt-label)
  1118. file-name)))
  1119. (define (luks-format-and-open user-partition)
  1120. "Format and open the encrypted partition pointed by USER-PARTITION."
  1121. (let* ((file-name (user-partition-file-name user-partition))
  1122. (label (user-partition-crypt-label user-partition))
  1123. (password (secret-content (user-partition-crypt-password user-partition))))
  1124. (call-with-luks-key-file
  1125. password
  1126. (lambda (key-file)
  1127. (installer-log-line "formatting and opening LUKS entry ~s at ~s"
  1128. label file-name)
  1129. ((run-command-in-installer) "cryptsetup" "-q" "luksFormat"
  1130. file-name key-file)
  1131. ((run-command-in-installer) "cryptsetup" "open" "--type" "luks"
  1132. "--key-file" key-file file-name label)))))
  1133. (define (luks-ensure-open user-partition)
  1134. "Ensure partition pointed by USER-PARTITION is opened."
  1135. (unless (file-exists? (user-partition-upper-file-name user-partition))
  1136. (let* ((file-name (user-partition-file-name user-partition))
  1137. (label (user-partition-crypt-label user-partition))
  1138. (password (secret-content (user-partition-crypt-password user-partition))))
  1139. (call-with-luks-key-file
  1140. password
  1141. (lambda (key-file)
  1142. (installer-log-line "opening LUKS entry ~s at ~s"
  1143. label file-name)
  1144. ((run-command-in-installer) "cryptsetup" "open" "--type" "luks"
  1145. "--key-file" key-file file-name label))))))
  1146. (define (luks-close user-partition)
  1147. "Close the encrypted partition pointed by USER-PARTITION."
  1148. (let ((label (user-partition-crypt-label user-partition)))
  1149. (installer-log-line "closing LUKS entry ~s" label)
  1150. ((run-command-in-installer) "cryptsetup" "close" label)))
  1151. (define (format-user-partitions user-partitions)
  1152. "Format the <user-partition> records in USER-PARTITIONS list with
  1153. NEED-FORMATTING? field set to #t."
  1154. (for-each
  1155. (lambda (user-partition)
  1156. (let* ((need-formatting?
  1157. (user-partition-need-formatting? user-partition))
  1158. (type (user-partition-type user-partition))
  1159. (crypt-label (user-partition-crypt-label user-partition))
  1160. (file-name (user-partition-upper-file-name user-partition))
  1161. (fs-type (user-partition-fs-type user-partition)))
  1162. (when crypt-label
  1163. (luks-format-and-open user-partition))
  1164. (case fs-type
  1165. ((btrfs)
  1166. (and need-formatting?
  1167. (not (eq? type 'extended))
  1168. (create-btrfs-file-system file-name)))
  1169. ((ext4)
  1170. (and need-formatting?
  1171. (not (eq? type 'extended))
  1172. (create-ext4-file-system file-name)))
  1173. ((fat16)
  1174. (and need-formatting?
  1175. (not (eq? type 'extended))
  1176. (create-fat16-file-system file-name)))
  1177. ((fat32)
  1178. (and need-formatting?
  1179. (not (eq? type 'extended))
  1180. (create-fat32-file-system file-name)))
  1181. ((jfs)
  1182. (and need-formatting?
  1183. (not (eq? type 'extended))
  1184. (create-jfs-file-system file-name)))
  1185. ((ntfs)
  1186. (and need-formatting?
  1187. (not (eq? type 'extended))
  1188. (create-ntfs-file-system file-name)))
  1189. ((xfs)
  1190. (and need-formatting?
  1191. (not (eq? type 'extended))
  1192. (create-xfs-file-system file-name)))
  1193. ((swap)
  1194. (create-swap-partition file-name))
  1195. (else
  1196. ;; TODO: Add support for other file-system types.
  1197. #t))))
  1198. user-partitions))
  1199. (define (sort-partitions user-partitions)
  1200. "Sort USER-PARTITIONS by mount-points, so that the more nested mount-point
  1201. comes last. This is useful to mount/umount partitions in a coherent order."
  1202. (sort user-partitions
  1203. (lambda (a b)
  1204. (let ((mount-point-a (user-partition-mount-point a))
  1205. (mount-point-b (user-partition-mount-point b)))
  1206. (string-prefix? mount-point-a mount-point-b)))))
  1207. (define (mount-user-partitions user-partitions)
  1208. "Mount the <user-partition> records in USER-PARTITIONS list on their
  1209. respective mount-points."
  1210. (let* ((mount-partitions (filter user-partition-mount-point user-partitions))
  1211. (sorted-partitions (sort-partitions mount-partitions)))
  1212. (for-each (lambda (user-partition)
  1213. (let* ((mount-point
  1214. (user-partition-mount-point user-partition))
  1215. (target
  1216. (string-append (%installer-target-dir)
  1217. mount-point))
  1218. (fs-type
  1219. (user-partition-fs-type user-partition))
  1220. (crypt-label
  1221. (user-partition-crypt-label user-partition))
  1222. (mount-type
  1223. (user-fs-type->mount-type fs-type))
  1224. (file-name
  1225. (user-partition-upper-file-name user-partition)))
  1226. (when crypt-label
  1227. (luks-ensure-open user-partition))
  1228. (mkdir-p target)
  1229. (installer-log-line "mounting ~s on ~s" file-name target)
  1230. (mount file-name target mount-type)))
  1231. sorted-partitions)))
  1232. (define (umount-user-partitions user-partitions)
  1233. "Unmount all the <user-partition> records in USER-PARTITIONS list."
  1234. (let* ((mount-partitions (filter user-partition-mount-point user-partitions))
  1235. (sorted-partitions (sort-partitions mount-partitions)))
  1236. (for-each (lambda (user-partition)
  1237. (let* ((mount-point
  1238. (user-partition-mount-point user-partition))
  1239. (crypt-label
  1240. (user-partition-crypt-label user-partition))
  1241. (target
  1242. (string-append (%installer-target-dir)
  1243. mount-point)))
  1244. (installer-log-line "unmounting ~s" target)
  1245. (umount target)
  1246. (when crypt-label
  1247. (luks-close user-partition))))
  1248. (reverse sorted-partitions))))
  1249. (define (find-swap-user-partitions user-partitions)
  1250. "Return the subset of <user-partition> records in USER-PARTITIONS list with
  1251. the FS-TYPE field set to 'swap, return the empty list if none found."
  1252. (filter (lambda (user-partition)
  1253. (let ((fs-type (user-partition-fs-type user-partition)))
  1254. (eq? fs-type 'swap)))
  1255. user-partitions))
  1256. (define (start-swapping user-partitions)
  1257. "Start swapping on <user-partition> records with FS-TYPE equal to 'swap."
  1258. (let* ((swap-user-partitions (find-swap-user-partitions user-partitions))
  1259. (swap-devices (map user-partition-file-name swap-user-partitions)))
  1260. (for-each swapon swap-devices)))
  1261. (define (stop-swapping user-partitions)
  1262. "Stop swapping on <user-partition> records with FS-TYPE equal to 'swap."
  1263. (let* ((swap-user-partitions (find-swap-user-partitions user-partitions))
  1264. (swap-devices (map user-partition-file-name swap-user-partitions)))
  1265. (for-each swapoff swap-devices)))
  1266. (define-syntax-rule (with-mounted-partitions user-partitions exp ...)
  1267. "Mount USER-PARTITIONS and start swapping within the dynamic extent of EXP."
  1268. (dynamic-wind
  1269. (lambda ()
  1270. (mount-user-partitions user-partitions)
  1271. (start-swapping user-partitions))
  1272. (lambda ()
  1273. exp ...)
  1274. (lambda ()
  1275. (umount-user-partitions user-partitions)
  1276. (stop-swapping user-partitions)
  1277. #f)))
  1278. (define (user-partition->file-system user-partition)
  1279. "Convert the given USER-PARTITION record in a FILE-SYSTEM record from
  1280. (gnu system file-systems) module and return it."
  1281. (let* ((mount-point (user-partition-mount-point user-partition))
  1282. (fs-type (user-partition-fs-type user-partition))
  1283. (crypt-label (user-partition-crypt-label user-partition))
  1284. (mount-type (user-fs-type->mount-type fs-type))
  1285. (file-name (user-partition-file-name user-partition))
  1286. (upper-file-name (user-partition-upper-file-name user-partition))
  1287. ;; Only compute uuid if partition is not encrypted.
  1288. (uuid (or crypt-label
  1289. (uuid->string (read-partition-uuid file-name) fs-type))))
  1290. `(file-system
  1291. (mount-point ,mount-point)
  1292. (device ,@(if crypt-label
  1293. `(,upper-file-name)
  1294. `((uuid ,uuid (quote ,fs-type)))))
  1295. (type ,mount-type)
  1296. ,@(if crypt-label
  1297. '((dependencies mapped-devices))
  1298. '()))))
  1299. (define (user-partitions->file-systems user-partitions)
  1300. "Convert the given USER-PARTITIONS list of <user-partition> records into a
  1301. list of <file-system> records."
  1302. (filter-map
  1303. (lambda (user-partition)
  1304. (let ((mount-point
  1305. (user-partition-mount-point user-partition)))
  1306. (and mount-point
  1307. (user-partition->file-system user-partition))))
  1308. user-partitions))
  1309. (define (user-partition->mapped-device user-partition)
  1310. "Convert the given USER-PARTITION record into a MAPPED-DEVICE record
  1311. from (gnu system mapped-devices) and return it."
  1312. (let ((label (user-partition-crypt-label user-partition))
  1313. (file-name (user-partition-file-name user-partition)))
  1314. `(mapped-device
  1315. (source (uuid ,(uuid->string
  1316. (read-luks-partition-uuid file-name)
  1317. 'luks)))
  1318. (target ,label)
  1319. (type luks-device-mapping))))
  1320. (define (root-user-partition? partition)
  1321. "Return true if PARTITION is the root partition."
  1322. (let ((mount-point (user-partition-mount-point partition)))
  1323. (and mount-point
  1324. (string=? mount-point "/"))))
  1325. (define (bootloader-configuration user-partitions)
  1326. "Return the bootloader configuration field for USER-PARTITIONS."
  1327. (let* ((root-partition (find root-user-partition?
  1328. user-partitions))
  1329. (root-partition-disk (user-partition-disk-file-name root-partition)))
  1330. `((bootloader-configuration
  1331. ,@(if (efi-installation?)
  1332. `((bootloader grub-efi-bootloader)
  1333. (targets (list ,(default-esp-mount-point))))
  1334. `((bootloader grub-bootloader)
  1335. (targets (list ,root-partition-disk))))
  1336. ;; XXX: Assume we defined the 'keyboard-layout' field of
  1337. ;; <operating-system> right above.
  1338. (keyboard-layout keyboard-layout)))))
  1339. (define (user-partition-missing-modules user-partitions)
  1340. "Return the list of kernel modules missing from the default set of kernel
  1341. modules to access USER-PARTITIONS."
  1342. (let ((devices (filter user-partition-crypt-label user-partitions))
  1343. (root (find root-user-partition? user-partitions)))
  1344. (delete-duplicates
  1345. (append-map (lambda (device)
  1346. (catch 'system-error
  1347. (lambda ()
  1348. (missing-modules device %base-initrd-modules))
  1349. (const '())))
  1350. (delete-duplicates
  1351. (map user-partition-file-name
  1352. (cons root devices)))))))
  1353. (define (initrd-configuration user-partitions)
  1354. "Return an 'initrd-modules' field with everything needed for
  1355. USER-PARTITIONS, or return nothing."
  1356. (match (user-partition-missing-modules user-partitions)
  1357. (()
  1358. '())
  1359. ((modules ...)
  1360. `((initrd-modules (append ',modules
  1361. %base-initrd-modules))))))
  1362. (define (user-partitions->configuration user-partitions)
  1363. "Return the configuration field for USER-PARTITIONS."
  1364. (let* ((swap-user-partitions (find-swap-user-partitions user-partitions))
  1365. (swap-devices (map user-partition-file-name swap-user-partitions))
  1366. (encrypted-partitions
  1367. (filter user-partition-crypt-label user-partitions)))
  1368. `((bootloader ,@(bootloader-configuration user-partitions))
  1369. ,@(initrd-configuration user-partitions)
  1370. ,@(if (null? swap-devices)
  1371. '()
  1372. (let* ((uuids (map (lambda (file)
  1373. (uuid->string (read-partition-uuid file)))
  1374. swap-devices)))
  1375. `((swap-devices
  1376. (list ,@(map (lambda (uuid)
  1377. `(swap-space
  1378. (target (uuid ,uuid))))
  1379. uuids))))))
  1380. ,@(if (null? encrypted-partitions)
  1381. '()
  1382. `((mapped-devices
  1383. (list ,@(map user-partition->mapped-device
  1384. encrypted-partitions)))))
  1385. ,(vertical-space 1)
  1386. ,(let-syntax ((G_ (syntax-rules () ((_ str) str))))
  1387. (comment (G_ "\
  1388. ;; The list of file systems that get \"mounted\". The unique
  1389. ;; file system identifiers there (\"UUIDs\") can be obtained
  1390. ;; by running 'blkid' in a terminal.\n")))
  1391. (file-systems (cons*
  1392. ,@(user-partitions->file-systems user-partitions)
  1393. %base-file-systems)))))
  1394. ;;
  1395. ;; Initialization.
  1396. ;;
  1397. (define (init-parted)
  1398. "Initialize libparted support."
  1399. (probe-all-devices!)
  1400. ;; Remove all logical devices, otherwise "device-is-busy?" will report true
  1401. ;; on all devices containaing active logical volumes.
  1402. (remove-logical-devices)
  1403. (exception-set-handler (lambda (exception)
  1404. EXCEPTION-OPTION-UNHANDLED)))
  1405. (define (free-parted devices)
  1406. "Deallocate memory used for DEVICES in parted, force sync them and wait for
  1407. the devices not to be used before returning."
  1408. ;; XXX: Formatting and further operations on disk partition table may fail
  1409. ;; because the partition table changes are not synced, or because the device
  1410. ;; is still in use, even if parted should have finished editing
  1411. ;; partitions. This is not well understood, but syncing devices and waiting
  1412. ;; them to stop returning EBUSY to BLKRRPART ioctl seems to be enough. The
  1413. ;; same kind of issue is described here:
  1414. ;; https://mail.gnome.org/archives/commits-list/2013-March/msg18423.html.
  1415. (let ((device-file-names (map device-path devices)))
  1416. (for-each force-device-sync devices)
  1417. (for-each (lambda (file-name)
  1418. (let/time ((time in-use?
  1419. (with-delay-device-in-use? file-name)))
  1420. (if in-use?
  1421. (error
  1422. (format #f (G_ "Device ~a is still in use.")
  1423. file-name))
  1424. (installer-log-line "Syncing ~a took ~a seconds."
  1425. file-name (time-second time)))))
  1426. device-file-names)))