1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2018, 2019 Mathieu Othacehe <m.othacehe@gmail.com>
3 ;;; Copyright © 2019, 2020 Ludovic Courtès <ludo@gnu.org>
4 ;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr>
6 ;;; This file is part of GNU Guix.
8 ;;; GNU Guix is free software; you can redistribute it and/or modify it
9 ;;; under the terms of the GNU General Public License as published by
10 ;;; the Free Software Foundation; either version 3 of the License, or (at
11 ;;; your option) any later version.
13 ;;; GNU Guix is distributed in the hope that it will be useful, but
14 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;;; GNU General Public License for more details.
18 ;;; You should have received a copy of the GNU General Public License
19 ;;; 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
31 read-luks-partition-uuid))
32 #:use-module ((gnu build linux-boot)
33 #:select (linux-command-line
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 records)
42 #:use-module (guix utils)
43 #:use-module (guix i18n)
45 #:use-module (ice-9 format)
46 #:use-module (ice-9 match)
47 #:use-module (ice-9 regex)
48 #:use-module (rnrs io ports)
49 #:use-module (srfi srfi-1)
50 #:use-module (srfi srfi-19)
51 #:use-module (srfi srfi-26)
52 #:use-module (srfi srfi-34)
53 #:use-module (srfi srfi-35)
54 #:export (<user-partition>
60 user-partition-file-name
61 user-partition-disk-file-name
62 user-partition-crypt-label
63 user-partition-crypt-password
64 user-partition-fs-type
65 user-partition-bootable?
67 user-partition-bios-grub?
71 user-partition-mount-point
72 user-partition-need-formatting?
73 user-partition-parted-object
76 small-freespace-partition?
80 default-esp-mount-point
82 with-delay-device-in-use?
87 partition-filesystem-user-type
89 partition->user-partition
90 create-special-user-partitions
91 find-user-partition-by-parted-object
94 partition-end-formatted
95 partition-print-number
97 partitions-descriptions
98 user-partition-description
100 &max-primary-exceeded
101 max-primary-exceeded?
102 &extended-creation-error
103 extended-creation-error?
104 &logical-creation-error
105 logical-creation-error?
107 can-create-partition?
118 cannot-read-uuid-partition
120 check-user-partitions
121 set-user-partitions-file-name
122 format-user-partitions
123 mount-user-partitions
124 umount-user-partitions
125 with-mounted-partitions
126 user-partitions->file-systems
127 user-partitions->configuration
134 ;;; Partition record.
137 (define-record-type* <user-partition>
138 user-partition make-user-partition
140 (name user-partition-name ;string
142 (type user-partition-type
143 (default 'normal)) ; 'normal | 'logical | 'extended
144 (file-name user-partition-file-name
146 (disk-file-name user-partition-disk-file-name
148 (crypt-label user-partition-crypt-label
150 (crypt-password user-partition-crypt-password
152 (fs-type user-partition-fs-type
154 (bootable? user-partition-bootable?
156 (esp? user-partition-esp?
158 (bios-grub? user-partition-bios-grub?
160 (size user-partition-size
162 (start user-partition-start ;start as string (e.g. '11MB')
164 (end user-partition-end ;same as start
166 (mount-point user-partition-mount-point ;string
168 (need-formatting? user-partition-need-formatting? ; boolean
170 (parted-object user-partition-parted-object ; <partition> from parted
178 (define (find-esp-partition partitions)
179 "Find and return the ESP partition among PARTITIONS."
180 (find esp-partition? partitions))
182 (define* (small-freespace-partition? device
184 #:key (max-size MEBIBYTE-SIZE))
185 "Return #t is PARTITION is a free-space partition with less a size strictly
186 inferior to MAX-SIZE, #f otherwise."
187 (let ((size (partition-length partition))
188 (max-sector-size (/ max-size
189 (device-sector-size device))))
190 (< size max-sector-size)))
192 (define (partition-user-type partition)
193 "Return the type of PARTITION, to be stored in the TYPE field of
194 <user-partition> record. It can be 'normal, 'extended or 'logical."
195 (cond ((normal-partition? partition)
197 ((extended-partition? partition)
199 ((logical-partition? partition)
203 (define (esp-partition? partition)
204 "Return #t if partition has the ESP flag, return #f otherwise."
205 (let* ((disk (partition-disk partition))
206 (disk-type (disk-disk-type disk)))
207 (and (data-partition? partition)
208 (partition-is-flag-available? partition PARTITION-FLAG-ESP)
209 (partition-get-flag partition PARTITION-FLAG-ESP))))
211 (define (boot-partition? partition)
212 "Return #t if partition has the boot flag, return #f otherwise."
213 (and (data-partition? partition)
214 (partition-is-flag-available? partition PARTITION-FLAG-BOOT)
215 (partition-get-flag partition PARTITION-FLAG-BOOT)))
218 ;; The default mount point for ESP partitions.
219 (define default-esp-mount-point
220 (make-parameter "/boot/efi"))
222 (define (efi-installation?)
223 "Return #t if an EFI installation should be performed, #f otherwise."
224 (file-exists? "/sys/firmware/efi"))
226 (define (user-fs-type-name fs-type)
227 "Return the name of FS-TYPE as specified by libparted."
236 ((swap) "linux-swap")))
238 (define (user-fs-type->mount-type fs-type)
239 "Return the mount type of FS-TYPE."
249 (define (partition-filesystem-user-type partition)
250 "Return the filesystem type of PARTITION, to be stored in the FS-TYPE field
251 of <user-partition> record."
252 (let ((fs-type (partition-fs-type partition)))
254 (let ((name (filesystem-type-name fs-type)))
256 ((string=? name "ext4") 'ext4)
257 ((string=? name "btrfs") 'btrfs)
258 ((string=? name "fat16") 'fat16)
259 ((string=? name "fat32") 'fat32)
260 ((string=? name "jfs") 'jfs)
261 ((string=? name "ntfs") 'ntfs)
262 ((string=? name "xfs") 'xfs)
263 ((or (string=? name "swsusp")
264 (string=? name "linux-swap(v0)")
265 (string=? name "linux-swap(v1)"))
268 (error (format #f "Unhandled ~a fs-type~%" name))))))))
270 (define (partition-get-flags partition)
271 "Return the list of flags supported by the given PARTITION."
272 (filter-map (lambda (flag)
273 (and (partition-get-flag partition flag)
275 (partition-flags partition)))
277 (define (partition->user-partition partition)
278 "Convert PARTITION into a <user-partition> record and return it."
279 (let* ((disk (partition-disk partition))
280 (device (disk-device disk))
281 (disk-type (disk-disk-type disk))
282 (has-name? (disk-type-check-feature
284 DISK-TYPE-FEATURE-PARTITION-NAME))
286 (data-partition? partition)
287 (partition-get-name partition))))
290 (not (string=? name "")))
292 (type (or (partition-user-type partition)
294 (file-name (partition-get-path partition))
295 (disk-file-name (device-path device))
296 (fs-type (or (partition-filesystem-user-type partition)
298 (mount-point (and (esp-partition? partition)
299 (default-esp-mount-point)))
300 (bootable? (boot-partition? partition))
301 (esp? (esp-partition? partition))
302 (parted-object partition))))
304 (define (create-special-user-partitions partitions)
305 "Return a list with a <user-partition> record describing the ESP partition
306 found in PARTITIONS, if any."
307 (filter-map (lambda (partition)
308 (and (esp-partition? partition)
309 (partition->user-partition partition)))
312 (define (find-user-partition-by-parted-object user-partitions
314 "Find and return the <user-partition> record in USER-PARTITIONS list which
315 PARTED-OBJECT field equals PARTITION, return #f if not found."
316 (find (lambda (user-partition)
317 (equal? (user-partition-parted-object user-partition)
326 (define (with-delay-device-in-use? file-name)
327 "Call DEVICE-IN-USE? with a few retries, as the first re-read will often
328 fail. See rereadpt function in wipefs.c of util-linux for an explanation."
329 ;; Kernel always return EINVAL for BLKRRPART on loopdevices.
330 (and (not (string-match "/dev/loop*" file-name))
333 (let ((in-use? (device-in-use? file-name)))
334 (if (and in-use? (> try 0))
338 (define* (force-device-sync device)
339 "Force a flushing of the given DEVICE."
342 (device-close device))
344 (define (remove-logical-devices)
345 "Remove all active logical devices."
346 ((run-command-in-installer) "dmsetup" "remove_all"))
348 (define (installer-root-partition-path)
349 "Return the root partition path, or #f if it could not be detected."
350 (let* ((cmdline (linux-command-line))
351 (root (find-long-option "root" cmdline)))
353 (or (and (access? root F_OK) root)
354 (find-partition-by-label root)
356 find-partition-by-uuid)))))
358 ;; Minimal installation device size.
359 (define %min-device-size
360 (* 2 GIBIBYTE-SIZE)) ;2GiB
362 (define (eligible-devices)
363 "Return all the available devices except the install device and the devices
364 which are smaller than %MIN-DEVICE-SIZE."
366 (define the-installer-root-partition-path
367 (installer-root-partition-path))
369 (define (small-device? device)
370 (let ((length (device-length device))
371 (sector-size (device-sector-size device)))
372 (and (< (* length sector-size) %min-device-size)
373 (installer-log-line "~a is not eligible because it is smaller than \
376 (unit-format-custom-byte device
380 ;; Read partition table of device and compare each path to the one
381 ;; we're booting from to determine if it is the installation
383 (define (installation-device? device)
384 ;; When using CDROM based installation, the root partition path may be the
386 (and (or (string=? the-installer-root-partition-path
387 (device-path device))
388 (let ((disk (disk-new device)))
390 (any (lambda (partition)
391 (string=? the-installer-root-partition-path
392 (partition-get-path partition)))
393 (disk-partitions disk)))))
394 (installer-log-line "~a is not eligible because it is the \
395 installation device."
396 (device-path device))))
400 (or (installation-device? device)
401 (small-device? device)))
406 ;; Disk and partition printing.
409 (define* (device-description device #:optional disk)
410 "Return a string describing the given DEVICE."
411 (let* ((type (device-type device))
412 (file-name (device-path device))
413 (model (device-model device))
414 (type-str (device-type->string type))
416 (disk-disk-type disk)
417 (disk-probe device)))
418 (length (device-length device))
419 (sector-size (device-sector-size device))
420 (end (unit-format-custom-byte device
421 (* length sector-size)
424 `(,@(if (string=? model "")
426 `(,model ,(string-append "(" type-str ")")))
430 `(,(disk-type-name disk-type))
434 (define (partition-end-formatted device partition)
435 "Return as a string the end of PARTITION with the relevant unit."
439 (* (+ (partition-end partition) 1)
440 (device-sector-size device))
443 (define (partition-print-number partition)
444 "Convert the given partition NUMBER to string."
445 (let ((number (partition-number partition)))
446 (number->string number)))
448 (define (partition-description partition user-partition)
449 "Return a string describing the given PARTITION, located on the DISK of
452 (define (partition-print-type partition)
453 "Return the type of PARTITION as a string."
454 (if (freespace-partition? partition)
456 (let ((type (partition-type partition)))
459 (symbol->string type-symbol))))))
461 (define (partition-print-flags partition)
462 "Return the flags of PARTITION as a string of comma separated flags."
466 (and (partition-get-flag partition flag)
467 (partition-flag-get-name flag)))
468 (partition-flags partition))
471 (define (maybe-string-pad string length)
472 "Returned a string formatted by padding STRING of LENGTH characters to the
473 right. If STRING is #f use an empty string."
474 (if (and string (not (string=? string "")))
475 (string-pad-right string length)
478 (let* ((disk (partition-disk partition))
479 (device (disk-device disk))
480 (disk-type (disk-disk-type disk))
481 (has-name? (disk-type-check-feature
483 DISK-TYPE-FEATURE-PARTITION-NAME))
484 (has-extended? (disk-type-check-feature
486 DISK-TYPE-FEATURE-EXTENDED))
487 (part-type (partition-print-type partition))
488 (number (and (not (freespace-partition? partition))
489 (partition-print-number partition)))
491 (if (freespace-partition? partition)
493 (partition-get-name partition))))
494 (start (unit-format device
495 (partition-start partition)))
496 (end (partition-end-formatted device partition))
497 (size (unit-format device (partition-length partition)))
498 (fs-type (partition-fs-type partition))
499 (fs-type-name (and fs-type
500 (filesystem-type-name fs-type)))
501 (crypt-label (and user-partition
502 (user-partition-crypt-label user-partition)))
503 (flags (and (not (freespace-partition? partition))
504 (partition-print-flags partition)))
505 (mount-point (and user-partition
506 (user-partition-mount-point user-partition))))
512 ,(or fs-type-name "")
516 ,(maybe-string-pad name 30))))
518 (define (partitions-descriptions partitions user-partitions)
519 "Return a list of strings describing all the partitions found on
520 DEVICE. METADATA partitions are not described. The strings are padded to the
521 right so that they can be displayed as a table."
523 (define (max-length-column lists column-index)
524 "Return the maximum length of the string at position COLUMN-INDEX in the
525 list of string lists LISTS."
529 (list-ref list column-index)))
532 (define (pad-descriptions descriptions)
533 "Return a padded version of the list of string lists DESCRIPTIONS. The
534 strings are padded to the length of the longer string in a same column, as
535 determined by MAX-LENGTH-COLUMN procedure."
536 (let* ((description-length (length (car descriptions)))
537 (paddings (map (lambda (index)
538 (max-length-column descriptions index))
539 (iota description-length))))
540 (map (lambda (description)
541 (map string-pad-right description paddings))
547 (let ((user-partition
548 (find-user-partition-by-parted-object user-partitions
550 (partition-description partition user-partition)))
552 (padded-descriptions (if (null? partitions)
554 (pad-descriptions descriptions))))
555 (map (cut string-join <> " ") padded-descriptions)))
557 (define (user-partition-description user-partition)
558 "Return a string describing the given USER-PARTITION record."
559 (let* ((partition (user-partition-parted-object user-partition))
560 (disk (partition-disk partition))
561 (disk-type (disk-disk-type disk))
562 (device (disk-device disk))
563 (has-name? (disk-type-check-feature
565 DISK-TYPE-FEATURE-PARTITION-NAME))
566 (has-extended? (disk-type-check-feature
568 DISK-TYPE-FEATURE-EXTENDED))
569 (name (user-partition-name user-partition))
570 (type (user-partition-type user-partition))
571 (type-name (symbol->string type))
572 (fs-type (user-partition-fs-type user-partition))
573 (fs-type-name (user-fs-type-name fs-type))
574 (bootable? (user-partition-bootable? user-partition))
575 (esp? (user-partition-esp? user-partition))
576 (need-formatting? (user-partition-need-formatting? user-partition))
577 (crypt-label (user-partition-crypt-label user-partition))
578 (size (user-partition-size user-partition))
579 (mount-point (user-partition-mount-point user-partition)))
581 `((name . ,(format #f (G_ "Name: ~a")
582 (or name (G_ "None")))))
584 ,@(if (and has-extended?
585 (freespace-partition? partition)
586 (not (eq? type 'logical)))
587 `((type . ,(format #f (G_ "Type: ~a") type-name)))
589 ,@(if (eq? type 'extended)
591 `((fs-type . ,(format #f (G_ "File system type: ~a")
593 ,@(if (or (eq? type 'extended)
597 `((bootable . ,(format #f (G_ "Bootable flag: ~:[off~;on~]")
599 ,@(if (and (not has-extended?)
600 (not (eq? fs-type 'swap)))
601 `((esp? . ,(format #f (G_ "ESP flag: ~:[off~;on~]") esp?)))
603 ,@(if (freespace-partition? partition)
604 (let ((size-formatted
605 (or size (unit-format device ;XXX: i18n
606 (partition-length partition)))))
607 `((size . ,(format #f (G_ "Size: ~a") size-formatted))))
609 ,@(if (or (eq? type 'extended)
613 . ,(format #f (G_ "Encryption: ~:[No~a~;Yes (label '~a')~]")
614 crypt-label (or crypt-label "")))))
615 ,@(if (or (freespace-partition? partition)
619 . ,(format #f (G_ "Format the partition? ~:[No~;Yes~]")
621 ,@(if (or (eq? type 'extended)
625 . ,(format #f (G_ "Mount point: ~a")
627 (and esp? (default-esp-mount-point))
632 ;; Partition table creation.
635 (define (mklabel device type-name)
636 "Create a partition table on DEVICE. TYPE-NAME is the type of the partition
637 table, \"msdos\" or \"gpt\"."
638 (let* ((type (disk-type-get type-name))
639 (disk (disk-new-fresh device type)))
644 (&message (message (format #f "Cannot create partition table of type
645 ~a on device ~a." type-name (device-path device)))))))))
649 ;; Partition creation.
652 ;; The maximum count of primary partitions is exceeded.
653 (define-condition-type &max-primary-exceeded &condition
654 max-primary-exceeded?)
656 ;; It is not possible to create an extended partition.
657 (define-condition-type &extended-creation-error &condition
658 extended-creation-error?)
660 ;; It is not possible to create a logical partition.
661 (define-condition-type &logical-creation-error &condition
662 logical-creation-error?)
664 (define (can-create-primary? disk)
665 "Return #t if it is possible to create a primary partition on DISK, return
667 (let ((max-primary (disk-get-max-primary-partition-count disk)))
668 (find (lambda (number)
669 (not (disk-get-partition disk number)))
670 (iota max-primary 1))))
672 (define (can-create-extended? disk)
673 "Return #t if it is possible to create an extended partition on DISK, return
675 (let* ((disk-type (disk-disk-type disk))
676 (has-extended? (disk-type-check-feature
678 DISK-TYPE-FEATURE-EXTENDED)))
679 (and (can-create-primary? disk)
681 (not (disk-extended-partition disk)))))
683 (define (can-create-logical? disk)
684 "Return #t is it is possible to create a logical partition on DISK, return
686 (let* ((disk-type (disk-disk-type disk))
687 (has-extended? (disk-type-check-feature
689 DISK-TYPE-FEATURE-EXTENDED)))
691 (disk-extended-partition disk))))
693 (define (can-create-partition? user-part)
694 "Return #t if it is possible to create the given USER-PART record, return #f
696 (let* ((type (user-partition-type user-part))
697 (partition (user-partition-parted-object user-part))
698 (disk (partition-disk partition)))
701 (or (can-create-primary? disk)
703 (condition (&max-primary-exceeded)))))
705 (or (can-create-extended? disk)
707 (condition (&extended-creation-error)))))
709 (or (can-create-logical? disk)
711 (condition (&logical-creation-error))))))))
713 (define* (mkpart disk user-partition
714 #:key (previous-partition #f))
715 "Create the given USER-PARTITION on DISK. The PREVIOUS-PARTITION argument as
716 to be set to the partition preceding USER-PARTITION if any."
718 (define (parse-start-end start end)
719 "Parse start and end strings as positions on DEVICE expressed with a unit,
720 like '100GB' or '12.2KiB'. Return a list of 4 elements, the start sector, its
721 range (1 unit large area centered on start sector), the end sector and its
723 (let ((device (disk-device disk)))
726 (unit-parse start device))
727 (lambda (start-sector start-range)
730 (unit-parse end device))
731 (lambda (end-sector end-range)
732 (list start-sector start-range
733 end-sector end-range)))))))
735 (define* (extend-ranges! start-range end-range
737 "Try to extend START-RANGE by 1 MEBIBYTE to the right and END-RANGE by 1
738 MEBIBYTE to the left. This way, if the disk is aligned on 2048 sectors of
739 512KB (like frequently), we will have a chance for the
740 'optimal-align-constraint' to succeed. Do not extend ranges if that would
741 cause them to cross."
742 (let* ((device (disk-device disk))
743 (start-range-end (geometry-end start-range))
744 (end-range-start (geometry-start end-range))
745 (mebibyte-sector-size (/ MEBIBYTE-SIZE
746 (device-sector-size device)))
748 (+ start-range-end mebibyte-sector-size offset))
750 (- end-range-start mebibyte-sector-size offset)))
751 (when (< new-start-range-end new-end-range-start)
752 (geometry-set-end start-range new-start-range-end)
753 (geometry-set-start end-range new-end-range-start))))
755 (match (parse-start-end (user-partition-start user-partition)
756 (user-partition-end user-partition))
757 ((start-sector start-range end-sector end-range)
758 (let* ((prev-end (if previous-partition
759 (partition-end previous-partition)
761 (start-distance (- start-sector prev-end))
762 (type (user-partition-type user-partition))
763 ;; There should be at least 2 unallocated sectors in front of each
764 ;; logical partition, otherwise parted will fail badly:
765 ;; https://gparted.org/h2-fix-msdos-pt.php#apply-action-fail.
766 (start-offset (if previous-partition
769 (start-sector* (if (and (eq? type 'logical)
770 (< start-distance 3))
771 (+ start-sector start-offset)
773 ;; This is a hack. Parted almost always fails to create optimally
774 ;; aligned partitions (unless specifying percentages) because the
775 ;; default range of 1MB centered on the start sector is not enough when
776 ;; the optimal alignment is 2048 sectors of 512KB.
777 (extend-ranges! start-range end-range #:offset start-offset)
779 (let* ((device (disk-device disk))
780 (disk-type (disk-disk-type disk))
781 (length (device-length device))
782 (name (user-partition-name user-partition))
786 (user-partition-fs-type user-partition))))
787 (flags `(,@(if (user-partition-bootable? user-partition)
788 `(,PARTITION-FLAG-BOOT)
790 ,@(if (user-partition-esp? user-partition)
791 `(,PARTITION-FLAG-ESP)
793 ,@(if (user-partition-bios-grub? user-partition)
794 `(,PARTITION-FLAG-BIOS-GRUB)
796 (has-name? (disk-type-check-feature
798 DISK-TYPE-FEATURE-PARTITION-NAME))
799 (partition-type (partition-type->int type))
800 (partition (partition-new disk
801 #:type partition-type
802 #:filesystem-type filesystem-type
803 #:start start-sector*
805 (user-constraint (constraint-new
808 #:start-range start-range
809 #:end-range end-range
813 (device-get-optimal-aligned-constraint device))
814 (final-constraint (constraint-intersect user-constraint
816 (no-constraint (constraint-any device))
817 ;; Try to create a partition with an optimal alignment
818 ;; constraint. If it fails, fallback to creating a partition
819 ;; with no specific constraint.
820 (partition-constraint-ok?
821 (disk-add-partition disk partition final-constraint))
822 (partition-no-contraint-ok?
823 (or partition-constraint-ok?
824 (disk-add-partition disk partition no-constraint)))
826 (or partition-constraint-ok? partition-no-contraint-ok?)))
827 (installer-log-line "Creating partition:")
828 (installer-log-line "~/type: ~a" partition-type)
829 (installer-log-line "~/filesystem-type: ~a"
830 (filesystem-type-name filesystem-type))
831 (installer-log-line "~/start: ~a" start-sector*)
832 (installer-log-line "~/end: ~a" end-sector)
833 (installer-log-line "~/start-range: [~a, ~a]"
834 (geometry-start start-range)
835 (geometry-end start-range))
836 (installer-log-line "~/end-range: [~a, ~a]"
837 (geometry-start end-range)
838 (geometry-end end-range))
839 (installer-log-line "~/constraint: ~a"
840 partition-constraint-ok?)
841 (installer-log-line "~/no-constraint: ~a"
842 partition-no-contraint-ok?)
843 ;; Set the partition name if supported.
844 (when (and partition-ok? has-name? name)
845 (partition-set-name partition name))
847 ;; Set flags is required.
848 (for-each (lambda (flag)
849 (and (partition-is-flag-available? partition flag)
850 (partition-set-flag partition flag 1)))
854 (partition-set-system partition filesystem-type)
859 ;; Partition destruction.
862 (define (rmpart disk number)
863 "Remove the partition with the given NUMBER on DISK."
864 (let ((partition (disk-get-partition disk number)))
865 (disk-remove-partition* disk partition)))
869 ;; Auto partitionning.
872 (define* (create-adjacent-partitions! disk partitions
873 #:key (last-partition-end 0))
874 "Create the given PARTITIONS on DISK. LAST-PARTITION-END is the sector from
875 which we want to start creating partitions. The START and END of each created
876 partition are computed from its SIZE value and the position of the last
878 (let ((device (disk-device disk)))
879 (let loop ((partitions partitions)
880 (remaining-space (- (device-length device)
882 (start last-partition-end))
886 (let* ((size (user-partition-size partition))
887 (percentage-size (and (string? size)
888 (read-percentage size)))
889 (sector-size (device-sector-size device))
890 (partition-size (if percentage-size
892 (* (/ percentage-size 100)
895 (end-partition (min (- (device-length device) 1)
896 (nearest-exact-integer
897 (+ start partition-size 1))))
898 (name (user-partition-name partition))
899 (type (user-partition-type partition))
900 (fs-type (user-partition-fs-type partition))
901 (start-formatted (unit-format-custom device
904 (end-formatted (unit-format-custom device
907 (new-user-partition (user-partition
909 (start start-formatted)
910 (end end-formatted)))
912 (mkpart disk new-user-partition)))
914 (cons (user-partition
915 (inherit new-user-partition)
916 (file-name (partition-get-path new-partition))
917 (disk-file-name (device-path device))
918 (parted-object new-partition))
920 (if (eq? type 'extended)
923 (partition-length new-partition)))
924 (if (eq? type 'extended)
926 (+ (partition-end new-partition) 1))))
928 (format #f "Unable to create partition ~a~%" name)))))))))
930 (define (force-user-partitions-formatting user-partitions)
931 "Set the NEED-FORMATTING? fields to #t on all <user-partition> records of
932 USER-PARTITIONS list and return the updated list."
936 (need-formatting? #t)))
939 (define* (auto-partition! disk
941 (scheme 'entire-root))
942 "Automatically create partitions on DISK. All the previous
943 partitions (except the ESP on a GPT disk, if present) are wiped. SCHEME is the
944 desired partitioning scheme. It can be 'entire-root or
945 'entire-root-home. 'entire-root will create a swap partition and a root
946 partition occupying all the remaining space. 'entire-root-home will create a
947 swap partition, a root partition and a home partition.
949 Return the complete list of partitions on DISK, including the ESP when it
951 (let* ((device (disk-device disk))
952 (disk-type (disk-disk-type disk))
953 (has-extended? (disk-type-check-feature
955 DISK-TYPE-FEATURE-EXTENDED))
956 (partitions (filter data-partition? (disk-partitions disk)))
957 (esp-partition (find-esp-partition partitions))
959 ;; https://wiki.archlinux.org/index.php/EFI_system_partition, the ESP
960 ;; size should be at least 550MiB.
961 (new-esp-size (nearest-exact-integer
962 (/ (* 550 MEBIBYTE-SIZE)
963 (device-sector-size device))))
964 (end-esp-partition (and esp-partition
965 (partition-end esp-partition)))
966 (non-boot-partitions (remove esp-partition? partitions))
967 (bios-grub-size (/ (* 3 MEBIBYTE-SIZE)
968 (device-sector-size device)))
969 (five-percent-disk (nearest-exact-integer
970 (* 0.05 (device-length device))))
971 (default-swap-size (nearest-exact-integer
972 (/ (* 4 GIGABYTE-SIZE)
973 (device-sector-size device))))
974 ;; Use a 4GB size for the swap if it represents less than 5% of the
975 ;; disk space. Otherwise, set the swap size to 5% of the disk space.
976 (swap-size (min default-swap-size five-percent-disk)))
978 ;; Remove everything but esp if it exists.
981 (and (data-partition? partition)
982 (disk-remove-partition* disk partition)))
985 (let* ((start-partition
986 (if (efi-installation?)
987 (and (not esp-partition)
992 (mount-point (default-esp-mount-point))))
997 (size bios-grub-size))))
1000 ((or (eq? scheme 'entire-root)
1001 (eq? scheme 'entire-encrypted-root))
1002 (let ((encrypted? (eq? scheme 'entire-encrypted-root)))
1003 `(,@(if start-partition
1013 (bootable? has-extended?)
1014 (crypt-label (and encrypted? "cryptroot"))
1016 (mount-point "/")))))
1017 ((or (eq? scheme 'entire-root-home)
1018 (eq? scheme 'entire-encrypted-root-home))
1019 (let ((encrypted? (eq? scheme 'entire-encrypted-root-home)))
1020 `(,@(if start-partition
1025 (bootable? has-extended?)
1026 (crypt-label (and encrypted? "cryptroot"))
1037 (type (if has-extended?
1043 (type (if has-extended?
1047 (crypt-label (and encrypted? "crypthome"))
1049 (mount-point "/home")))))))
1050 (new-partitions* (force-user-partitions-formatting
1052 (append (if esp-partition
1053 (list (partition->user-partition esp-partition))
1055 (create-adjacent-partitions! disk
1057 #:last-partition-end
1058 (or end-esp-partition 0))))))
1062 ;; Convert user-partitions.
1065 ;; No root mount point found.
1066 (define-condition-type &no-root-mount-point &condition
1067 no-root-mount-point?)
1069 ;; Cannot not read the partition UUID.
1070 (define-condition-type &cannot-read-uuid &condition
1072 (partition cannot-read-uuid-partition))
1074 (define (check-user-partitions user-partitions)
1075 "Check the following statements:
1077 The USER-PARTITIONS list contains one <user-partition> record with a
1078 mount-point set to '/'. Raise &no-root-mount-point condition otherwise.
1080 All the USER-PARTITIONS with a mount point and that will not be formatted have
1081 a valid UUID. Raise a &cannot-read-uuid condition specifying the faulty
1082 partition otherwise.
1084 Return #t if all the statements are valid."
1085 (define (check-root)
1087 (map user-partition-mount-point user-partitions)))
1088 (or (member "/" mount-points)
1090 (condition (&no-root-mount-point))))))
1092 (define (check-uuid)
1093 (let ((mount-partitions
1094 (filter user-partition-mount-point user-partitions)))
1096 (lambda (user-partition)
1097 (let ((file-name (user-partition-file-name user-partition))
1099 (user-partition-need-formatting? user-partition)))
1100 (or need-formatting?
1101 (read-partition-uuid file-name)
1105 (partition file-name)))))))
1112 (define (set-user-partitions-file-name user-partitions)
1113 "Set the partition file-name of <user-partition> records in USER-PARTITIONS
1114 list and return the updated list."
1116 (let* ((partition (user-partition-parted-object p))
1117 (file-name (partition-get-path partition)))
1120 (file-name file-name))))
1123 (define (create-btrfs-file-system partition)
1124 "Create a btrfs file-system for PARTITION file-name."
1125 ((run-command-in-installer) "mkfs.btrfs" "-f" partition))
1127 (define (create-ext4-file-system partition)
1128 "Create an ext4 file-system for PARTITION file-name."
1129 ((run-command-in-installer) "mkfs.ext4" "-F" partition))
1131 (define (create-fat16-file-system partition)
1132 "Create a fat16 file-system for PARTITION file-name."
1133 ((run-command-in-installer) "mkfs.fat" "-F16" partition))
1135 (define (create-fat32-file-system partition)
1136 "Create a fat32 file-system for PARTITION file-name."
1137 ((run-command-in-installer) "mkfs.fat" "-F32" partition))
1139 (define (create-jfs-file-system partition)
1140 "Create a JFS file-system for PARTITION file-name."
1141 ((run-command-in-installer) "jfs_mkfs" "-f" partition))
1143 (define (create-ntfs-file-system partition)
1144 "Create a JFS file-system for PARTITION file-name."
1145 ((run-command-in-installer) "mkfs.ntfs" "-F" "-f" partition))
1147 (define (create-xfs-file-system partition)
1148 "Create an XFS file-system for PARTITION file-name."
1149 ((run-command-in-installer) "mkfs.xfs" "-f" partition))
1151 (define (create-swap-partition partition)
1152 "Set up swap area on PARTITION file-name."
1153 ((run-command-in-installer) "mkswap" "-f" partition))
1155 (define (call-with-luks-key-file password proc)
1156 "Write PASSWORD in a temporary file and pass it to PROC as argument."
1157 (call-with-temporary-output-file
1159 (put-string port password)
1163 (define (user-partition-upper-file-name user-partition)
1164 "Return the file-name of the virtual block device corresponding to
1165 USER-PARTITION if it is encrypted, or the plain file-name otherwise."
1166 (let ((crypt-label (user-partition-crypt-label user-partition))
1167 (file-name (user-partition-file-name user-partition)))
1169 (string-append "/dev/mapper/" crypt-label)
1172 (define (luks-format-and-open user-partition)
1173 "Format and open the encrypted partition pointed by USER-PARTITION."
1174 (let* ((file-name (user-partition-file-name user-partition))
1175 (label (user-partition-crypt-label user-partition))
1176 (password (user-partition-crypt-password user-partition)))
1177 (call-with-luks-key-file
1180 (installer-log-line "formatting and opening LUKS entry ~s at ~s"
1182 ((run-command-in-installer) "cryptsetup" "-q" "luksFormat"
1184 ((run-command-in-installer) "cryptsetup" "open" "--type" "luks"
1185 "--key-file" key-file file-name label)))))
1187 (define (luks-close user-partition)
1188 "Close the encrypted partition pointed by USER-PARTITION."
1189 (let ((label (user-partition-crypt-label user-partition)))
1190 (installer-log-line "closing LUKS entry ~s" label)
1191 ((run-command-in-installer) "cryptsetup" "close" label)))
1193 (define (format-user-partitions user-partitions)
1194 "Format the <user-partition> records in USER-PARTITIONS list with
1195 NEED-FORMATTING? field set to #t."
1197 (lambda (user-partition)
1198 (let* ((need-formatting?
1199 (user-partition-need-formatting? user-partition))
1200 (type (user-partition-type user-partition))
1201 (crypt-label (user-partition-crypt-label user-partition))
1202 (file-name (user-partition-upper-file-name user-partition))
1203 (fs-type (user-partition-fs-type user-partition)))
1205 (luks-format-and-open user-partition))
1209 (and need-formatting?
1210 (not (eq? type 'extended))
1211 (create-btrfs-file-system file-name)))
1213 (and need-formatting?
1214 (not (eq? type 'extended))
1215 (create-ext4-file-system file-name)))
1217 (and need-formatting?
1218 (not (eq? type 'extended))
1219 (create-fat16-file-system file-name)))
1221 (and need-formatting?
1222 (not (eq? type 'extended))
1223 (create-fat32-file-system file-name)))
1225 (and need-formatting?
1226 (not (eq? type 'extended))
1227 (create-jfs-file-system file-name)))
1229 (and need-formatting?
1230 (not (eq? type 'extended))
1231 (create-ntfs-file-system file-name)))
1233 (and need-formatting?
1234 (not (eq? type 'extended))
1235 (create-xfs-file-system file-name)))
1237 (create-swap-partition file-name))
1239 ;; TODO: Add support for other file-system types.
1243 (define (sort-partitions user-partitions)
1244 "Sort USER-PARTITIONS by mount-points, so that the more nested mount-point
1245 comes last. This is useful to mount/umount partitions in a coherent order."
1246 (sort user-partitions
1248 (let ((mount-point-a (user-partition-mount-point a))
1249 (mount-point-b (user-partition-mount-point b)))
1250 (string-prefix? mount-point-a mount-point-b)))))
1252 (define (mount-user-partitions user-partitions)
1253 "Mount the <user-partition> records in USER-PARTITIONS list on their
1254 respective mount-points."
1255 (let* ((mount-partitions (filter user-partition-mount-point user-partitions))
1256 (sorted-partitions (sort-partitions mount-partitions)))
1257 (for-each (lambda (user-partition)
1259 (user-partition-mount-point user-partition))
1261 (string-append (%installer-target-dir)
1264 (user-partition-fs-type user-partition))
1266 (user-partition-crypt-label user-partition))
1268 (user-fs-type->mount-type fs-type))
1270 (user-partition-upper-file-name user-partition)))
1272 (installer-log-line "mounting ~s on ~s" file-name target)
1273 (mount file-name target mount-type)))
1274 sorted-partitions)))
1276 (define (umount-user-partitions user-partitions)
1277 "Unmount all the <user-partition> records in USER-PARTITIONS list."
1278 (let* ((mount-partitions (filter user-partition-mount-point user-partitions))
1279 (sorted-partitions (sort-partitions mount-partitions)))
1280 (for-each (lambda (user-partition)
1282 (user-partition-mount-point user-partition))
1284 (user-partition-crypt-label user-partition))
1286 (string-append (%installer-target-dir)
1288 (installer-log-line "unmounting ~s" target)
1291 (luks-close user-partition))))
1292 (reverse sorted-partitions))))
1294 (define (find-swap-user-partitions user-partitions)
1295 "Return the subset of <user-partition> records in USER-PARTITIONS list with
1296 the FS-TYPE field set to 'swap, return the empty list if none found."
1297 (filter (lambda (user-partition)
1298 (let ((fs-type (user-partition-fs-type user-partition)))
1299 (eq? fs-type 'swap)))
1302 (define (start-swapping user-partitions)
1303 "Start swapping on <user-partition> records with FS-TYPE equal to 'swap."
1304 (let* ((swap-user-partitions (find-swap-user-partitions user-partitions))
1305 (swap-devices (map user-partition-file-name swap-user-partitions)))
1306 (for-each swapon swap-devices)))
1308 (define (stop-swapping user-partitions)
1309 "Stop swapping on <user-partition> records with FS-TYPE equal to 'swap."
1310 (let* ((swap-user-partitions (find-swap-user-partitions user-partitions))
1311 (swap-devices (map user-partition-file-name swap-user-partitions)))
1312 (for-each swapoff swap-devices)))
1314 (define-syntax-rule (with-mounted-partitions user-partitions exp ...)
1315 "Mount USER-PARTITIONS and start swapping within the dynamic extent of EXP."
1318 (mount-user-partitions user-partitions)
1319 (start-swapping user-partitions))
1323 (umount-user-partitions user-partitions)
1324 (stop-swapping user-partitions)
1327 (define (user-partition->file-system user-partition)
1328 "Convert the given USER-PARTITION record in a FILE-SYSTEM record from
1329 (gnu system file-systems) module and return it."
1330 (let* ((mount-point (user-partition-mount-point user-partition))
1331 (fs-type (user-partition-fs-type user-partition))
1332 (crypt-label (user-partition-crypt-label user-partition))
1333 (mount-type (user-fs-type->mount-type fs-type))
1334 (file-name (user-partition-file-name user-partition))
1335 (upper-file-name (user-partition-upper-file-name user-partition))
1336 ;; Only compute uuid if partition is not encrypted.
1337 (uuid (or crypt-label
1338 (uuid->string (read-partition-uuid file-name) fs-type))))
1340 (mount-point ,mount-point)
1341 (device ,@(if crypt-label
1343 `((uuid ,uuid (quote ,fs-type)))))
1346 '((dependencies mapped-devices))
1349 (define (user-partitions->file-systems user-partitions)
1350 "Convert the given USER-PARTITIONS list of <user-partition> records into a
1351 list of <file-system> records."
1353 (lambda (user-partition)
1355 (user-partition-mount-point user-partition)))
1357 (user-partition->file-system user-partition))))
1360 (define (user-partition->mapped-device user-partition)
1361 "Convert the given USER-PARTITION record into a MAPPED-DEVICE record
1362 from (gnu system mapped-devices) and return it."
1363 (let ((label (user-partition-crypt-label user-partition))
1364 (file-name (user-partition-file-name user-partition)))
1366 (source (uuid ,(uuid->string
1367 (read-luks-partition-uuid file-name)
1370 (type luks-device-mapping))))
1372 (define (root-user-partition? partition)
1373 "Return true if PARTITION is the root partition."
1374 (let ((mount-point (user-partition-mount-point partition)))
1376 (string=? mount-point "/"))))
1378 (define (bootloader-configuration user-partitions)
1379 "Return the bootloader configuration field for USER-PARTITIONS."
1380 (let* ((root-partition (find root-user-partition?
1382 (root-partition-disk (user-partition-disk-file-name root-partition)))
1383 `((bootloader-configuration
1384 ,@(if (efi-installation?)
1385 `((bootloader grub-efi-bootloader)
1386 (targets (list ,(default-esp-mount-point))))
1387 `((bootloader grub-bootloader)
1388 (targets (list ,root-partition-disk))))
1390 ;; XXX: Assume we defined the 'keyboard-layout' field of
1391 ;; <operating-system> right above.
1392 (keyboard-layout keyboard-layout)))))
1394 (define (user-partition-missing-modules user-partitions)
1395 "Return the list of kernel modules missing from the default set of kernel
1396 modules to access USER-PARTITIONS."
1397 (let ((devices (filter user-partition-crypt-label user-partitions))
1398 (root (find root-user-partition? user-partitions)))
1400 (append-map (lambda (device)
1401 (catch 'system-error
1403 (missing-modules device %base-initrd-modules))
1406 (map user-partition-file-name
1407 (cons root devices)))))))
1409 (define (initrd-configuration user-partitions)
1410 "Return an 'initrd-modules' field with everything needed for
1411 USER-PARTITIONS, or return nothing."
1412 (match (user-partition-missing-modules user-partitions)
1416 `((initrd-modules (append ',modules
1417 %base-initrd-modules))))))
1419 (define (user-partitions->configuration user-partitions)
1420 "Return the configuration field for USER-PARTITIONS."
1421 (let* ((swap-user-partitions (find-swap-user-partitions user-partitions))
1422 (swap-devices (map user-partition-file-name swap-user-partitions))
1423 (encrypted-partitions
1424 (filter user-partition-crypt-label user-partitions)))
1425 `((bootloader ,@(bootloader-configuration user-partitions))
1426 ,@(initrd-configuration user-partitions)
1427 ,@(if (null? swap-devices)
1429 (let* ((uuids (map (lambda (file)
1430 (uuid->string (read-partition-uuid file)))
1433 (list ,@(map (lambda (uuid)
1435 (target (uuid ,uuid))))
1437 ,@(if (null? encrypted-partitions)
1440 (list ,@(map user-partition->mapped-device
1441 encrypted-partitions)))))
1442 (file-systems (cons*
1443 ,@(user-partitions->file-systems user-partitions)
1444 %base-file-systems)))))
1451 (define (init-parted)
1452 "Initialize libparted support."
1453 (probe-all-devices!)
1454 ;; Remove all logical devices, otherwise "device-is-busy?" will report true
1455 ;; on all devices containaing active logical volumes.
1456 (remove-logical-devices)
1457 (exception-set-handler (lambda (exception)
1458 EXCEPTION-OPTION-UNHANDLED)))
1460 (define (free-parted devices)
1461 "Deallocate memory used for DEVICES in parted, force sync them and wait for
1462 the devices not to be used before returning."
1463 ;; XXX: Formatting and further operations on disk partition table may fail
1464 ;; because the partition table changes are not synced, or because the device
1465 ;; is still in use, even if parted should have finished editing
1466 ;; partitions. This is not well understood, but syncing devices and waiting
1467 ;; them to stop returning EBUSY to BLKRRPART ioctl seems to be enough. The
1468 ;; same kind of issue is described here:
1469 ;; https://mail.gnome.org/archives/commits-list/2013-March/msg18423.html.
1470 (let ((device-file-names (map device-path devices)))
1471 (for-each force-device-sync devices)
1472 (for-each (lambda (file-name)
1473 (let/time ((time in-use?
1474 (with-delay-device-in-use? file-name)))
1477 (format #f (G_ "Device ~a is still in use.")
1479 (installer-log-line "Syncing ~a took ~a seconds."
1480 file-name (time-second time)))))
1481 device-file-names)))