build: image: Add support for EXT2 and EXT3 file-systems.
[jackhill/guix/guix.git] / gnu / build / file-systems.scm
CommitLineData
e2f4b305 1;;; GNU Guix --- Functional package management for GNU
a5acc17a 2;;; Copyright © 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
ab4e939c 3;;; Copyright © 2016, 2017 David Craven <david@craven.ch>
a5e13c3b 4;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
0dc5c856 5;;; Copyright © 2019 Guillaume Le Vaillant <glv@posteo.net>
1abbe7c6 6;;; Copyright © 2019 Tobias Geerinckx-Rice <me@tobias.gr>
7aa28eb3 7;;; Copyright © 2019 David C. Trudgian <dave@trudgian.net>
85a7466e 8;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
e2f4b305
LC
9;;;
10;;; This file is part of GNU Guix.
11;;;
12;;; GNU Guix is free software; you can redistribute it and/or modify it
13;;; under the terms of the GNU General Public License as published by
14;;; the Free Software Foundation; either version 3 of the License, or (at
15;;; your option) any later version.
16;;;
17;;; GNU Guix is distributed in the hope that it will be useful, but
18;;; WITHOUT ANY WARRANTY; without even the implied warranty of
19;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;;; GNU General Public License for more details.
21;;;
22;;; You should have received a copy of the GNU General Public License
23;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
24
25(define-module (gnu build file-systems)
47cef4ec 26 #:use-module (gnu system uuid)
1c65cca5 27 #:use-module (gnu system file-systems)
e2f4b305 28 #:use-module (guix build utils)
6eb43907 29 #:use-module (guix build bournish)
1c65cca5
LC
30 #:use-module ((guix build syscalls)
31 #:hide (file-system-type))
e2f4b305
LC
32 #:use-module (rnrs io ports)
33 #:use-module (rnrs bytevectors)
34 #:use-module (ice-9 match)
35 #:use-module (ice-9 rdelim)
36 #:use-module (system foreign)
37 #:autoload (system repl repl) (start-repl)
38 #:use-module (srfi srfi-1)
39 #:use-module (srfi srfi-26)
40 #:export (disk-partitions
41 partition-label-predicate
0ec5ee94 42 partition-uuid-predicate
a1ccefaa 43 partition-luks-uuid-predicate
e2f4b305 44 find-partition-by-label
0ec5ee94 45 find-partition-by-uuid
a1ccefaa 46 find-partition-by-luks-uuid
e2f4b305
LC
47 canonicalize-device-spec
48
983abd2c
MO
49 read-partition-label
50 read-partition-uuid
8ae7044f 51 read-luks-partition-uuid
983abd2c 52
e2f4b305
LC
53 bind-mount
54
55 mount-flags->bit-mask
56 check-file-system
a5e13c3b 57 mount-file-system))
e2f4b305
LC
58
59;;; Commentary:
60;;;
61;;; This modules provides tools to deal with disk partitions, and to mount and
62;;; check file systems.
63;;;
64;;; Code:
65
e2f4b305
LC
66(define (bind-mount source target)
67 "Bind-mount SOURCE at TARGET."
68 (mount source target "" MS_BIND))
69
2fe4ceee
LC
70(define (seek* fd/port offset whence)
71 "Like 'seek' but return -1 instead of throwing to 'system-error' upon
72EINVAL. This makes it easier to catch cases like OFFSET being too large for
73FD/PORT."
74 (catch 'system-error
75 (lambda ()
76 (seek fd/port offset whence))
77 (lambda args
78 (if (= EINVAL (system-error-errno args))
79 -1
80 (apply throw args)))))
81
974e02da
DC
82(define (read-superblock device offset size magic?)
83 "Read a superblock of SIZE from OFFSET and DEVICE. Return the raw
84superblock on success, and #f if no valid superblock was found. MAGIC?
85takes a bytevector and returns #t when it's a valid superblock."
86 (call-with-input-file device
87 (lambda (port)
2fe4ceee
LC
88 (and (= offset (seek* port offset SEEK_SET))
89 (let ((block (make-bytevector size)))
90 (match (get-bytevector-n! port block 0 (bytevector-length block))
91 ((? eof-object?)
92 #f)
93 ((? number? len)
94 (and (= len (bytevector-length block))
95 (and (magic? block)
96 block)))))))))
974e02da 97
b0377e58
DC
98(define null-terminated-latin1->string
99 (cut latin1->string <> zero?))
100
f73f4b3a
DM
101(define (bytevector-utf16-length bv)
102 "Given a bytevector BV containing a NUL-terminated UTF16-encoded string,
103determine where the NUL terminator is and return its index. If there's no
104NUL terminator, return the size of the bytevector."
105 (let ((length (bytevector-length bv)))
106 (let loop ((index 0))
107 (if (< index length)
108 (if (zero? (bytevector-u16-ref bv index 'little))
109 index
110 (loop (+ index 2)))
111 length))))
112
bb357c50
DM
113(define* (bytevector->u16-list bv endianness #:optional (index 0))
114 (if (< index (bytevector-length bv))
115 (cons (bytevector-u16-ref bv index endianness)
116 (bytevector->u16-list bv endianness (+ index 2)))
117 '()))
118
119;; The initrd doesn't have iconv data, so do the conversion ourselves.
120(define (utf16->string bv endianness)
121 (list->string
122 (map integer->char
123 (reverse
124 (let loop ((remainder (bytevector->u16-list bv endianness))
125 (result '()))
126 (match remainder
127 (() result)
128 ((a) (cons a result))
129 ((a b x ...)
130 (if (and (>= a #xD800) (< a #xDC00) ; high surrogate
131 (>= b #xDC00) (< b #xE000)) ; low surrogate
132 (loop x (cons (+ #x10000
133 (* #x400 (- a #xD800))
134 (- b #xDC00))
135 result))
136 (loop (cons b x) (cons a result))))))))))
137
f73f4b3a
DM
138(define (null-terminated-utf16->string bv endianness)
139 (utf16->string (sub-bytevector bv 0 (bytevector-utf16-length bv))
140 endianness))
141
a1ccefaa
LC
142\f
143;;;
144;;; Ext2 file systems.
145;;;
146
974e02da
DC
147;; <http://www.nongnu.org/ext2-doc/ext2.html#DEF-SUPERBLOCK>.
148;; TODO: Use "packed structs" from Guile-OpenGL or similar.
149
e2f4b305
LC
150(define-syntax %ext2-endianness
151 ;; Endianness of ext2 file systems.
152 (identifier-syntax (endianness little)))
153
974e02da
DC
154(define (ext2-superblock? sblock)
155 "Return #t when SBLOCK is an ext2 superblock."
156 (let ((magic (bytevector-u16-ref sblock 56 %ext2-endianness)))
157 (= magic #xef53)))
e2f4b305
LC
158
159(define (read-ext2-superblock device)
160 "Return the raw contents of DEVICE's ext2 superblock as a bytevector, or #f
161if DEVICE does not contain an ext2 file system."
974e02da 162 (read-superblock device 1024 264 ext2-superblock?))
e2f4b305
LC
163
164(define (ext2-superblock-uuid sblock)
165 "Return the UUID of ext2 superblock SBLOCK as a 16-byte bytevector."
974e02da 166 (sub-bytevector sblock 104 16))
e2f4b305
LC
167
168(define (ext2-superblock-volume-name sblock)
169 "Return the volume name of SBLOCK as a string of at most 16 characters, or
170#f if SBLOCK has no volume name."
974e02da 171 (null-terminated-latin1->string (sub-bytevector sblock 120 16)))
e2f4b305 172
26905ec8
DC
173(define (check-ext2-file-system device)
174 "Return the health of an ext2 file system on DEVICE."
175 (match (status:exit-val
176 (system* "e2fsck" "-v" "-p" "-C" "0" device))
177 (0 'pass)
178 (1 'errors-corrected)
179 (2 'reboot-required)
180 (_ 'fatal-error)))
e2f4b305 181
a1ccefaa 182\f
b1a505ba
DC
183;;;
184;;; Btrfs file systems.
185;;;
186
187;; <https://btrfs.wiki.kernel.org/index.php/On-disk_Format#Superblock>.
188
189(define-syntax %btrfs-endianness
190 ;; Endianness of btrfs file systems.
191 (identifier-syntax (endianness little)))
192
193(define (btrfs-superblock? sblock)
194 "Return #t when SBLOCK is a btrfs superblock."
195 (bytevector=? (sub-bytevector sblock 64 8)
196 (string->utf8 "_BHRfS_M")))
197
198(define (read-btrfs-superblock device)
199 "Return the raw contents of DEVICE's btrfs superblock as a bytevector, or #f
200if DEVICE does not contain a btrfs file system."
201 (read-superblock device 65536 4096 btrfs-superblock?))
202
203(define (btrfs-superblock-uuid sblock)
204 "Return the UUID of a btrfs superblock SBLOCK as a 16-byte bytevector."
205 (sub-bytevector sblock 32 16))
206
207(define (btrfs-superblock-volume-name sblock)
208 "Return the volume name of SBLOCK as a string of at most 256 characters, or
209#f if SBLOCK has no volume name."
210 (null-terminated-latin1->string (sub-bytevector sblock 299 256)))
211
212(define (check-btrfs-file-system device)
213 "Return the health of a btrfs file system on DEVICE."
214 (match (status:exit-val
215 (system* "btrfs" "device" "scan"))
216 (0 'pass)
217 (_ 'fatal-error)))
218
219\f
b0377e58
DC
220;;;
221;;; FAT32 file systems.
222;;;
223
224;; <http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-107.pdf>.
225
b0377e58
DC
226(define (fat32-superblock? sblock)
227 "Return #t when SBLOCK is a fat32 superblock."
228 (bytevector=? (sub-bytevector sblock 82 8)
229 (string->utf8 "FAT32 ")))
230
231(define (read-fat32-superblock device)
232 "Return the raw contents of DEVICE's fat32 superblock as a bytevector, or
233#f if DEVICE does not contain a fat32 file system."
234 (read-superblock device 0 90 fat32-superblock?))
235
236(define (fat32-superblock-uuid sblock)
237 "Return the Volume ID of a fat superblock SBLOCK as a 4-byte bytevector."
238 (sub-bytevector sblock 67 4))
239
b0377e58
DC
240(define (fat32-superblock-volume-name sblock)
241 "Return the volume name of SBLOCK as a string of at most 11 characters, or
242#f if SBLOCK has no volume name. The volume name is a latin1 string.
243Trailing spaces are trimmed."
244 (string-trim-right (latin1->string (sub-bytevector sblock 71 11) (lambda (c) #f)) #\space))
245
88235675 246(define (check-fat-file-system device)
b0377e58
DC
247 "Return the health of a fat file system on DEVICE."
248 (match (status:exit-val
249 (system* "fsck.vfat" "-v" "-a" device))
250 (0 'pass)
251 (1 'errors-corrected)
252 (_ 'fatal-error)))
253
254\f
88235675
LC
255;;;
256;;; FAT16 file systems.
257;;;
258
259(define (fat16-superblock? sblock)
260 "Return #t when SBLOCK is a fat16 boot record."
261 (bytevector=? (sub-bytevector sblock 54 8)
262 (string->utf8 "FAT16 ")))
263
264(define (read-fat16-superblock device)
265 "Return the raw contents of DEVICE's fat16 superblock as a bytevector, or
266#f if DEVICE does not contain a fat16 file system."
267 (read-superblock device 0 62 fat16-superblock?))
268
269(define (fat16-superblock-uuid sblock)
270 "Return the Volume ID of a fat superblock SBLOCK as a 4-byte bytevector."
271 (sub-bytevector sblock 39 4))
272
273(define (fat16-superblock-volume-name sblock)
274 "Return the volume name of SBLOCK as a string of at most 11 characters, or
275#f if SBLOCK has no volume name. The volume name is a latin1 string.
276Trailing spaces are trimmed."
277 (string-trim-right (latin1->string (sub-bytevector sblock 43 11)
278 (lambda (c) #f))
279 #\space))
280
281\f
06110559
DM
282;;;
283;;; ISO9660 file systems.
284;;;
285
286;; <http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf>.
287
288(define (iso9660-superblock? sblock)
3dba9b37 289 "Return #t when SBLOCK is an iso9660 volume descriptor."
06110559
DM
290 (bytevector=? (sub-bytevector sblock 1 6)
291 ;; Note: "\x01" is the volume descriptor format version
292 (string->utf8 "CD001\x01")))
293
294(define (read-iso9660-primary-volume-descriptor device offset)
295 "Find and read the first primary volume descriptor, starting at OFFSET.
296 Return #f if not found."
297 (let* ((sblock (read-superblock device offset 2048 iso9660-superblock?))
203a9455
DM
298 (type-code (if sblock
299 (bytevector-u8-ref sblock 0)
300 (error (format #f
301 "Could not read ISO9660 primary
302volume descriptor from ~s"
303 device)))))
06110559
DM
304 (match type-code
305 (255 #f) ; Volume Descriptor Set Terminator.
306 (1 sblock) ; Primary Volume Descriptor
307 (_ (read-iso9660-primary-volume-descriptor device (+ offset 2048))))))
308
309(define (read-iso9660-superblock device)
3dba9b37
DM
310 "Return the raw contents of DEVICE's iso9660 primary volume descriptor
311as a bytevector, or #f if DEVICE does not contain an iso9660 file system."
06110559 312 ;; Start reading at sector 16.
162a1374 313 ;; Since we are not sure that the device contains an ISO9660 file system,
fb03f44b
DM
314 ;; we have to find that out first.
315 (if (read-superblock device (* 2048 16) 2048 iso9660-superblock?)
316 (read-iso9660-primary-volume-descriptor device (* 2048 16))
162a1374 317 #f)) ; Device does not contain an iso9660 file system.
06110559
DM
318
319(define (iso9660-superblock-uuid sblock)
3dba9b37 320 "Return the modification time of an iso9660 primary volume descriptor
c6aee77e 321SBLOCK as a bytevector. If that's not set, returns the creation time."
06110559
DM
322 ;; Drops GMT offset for compatibility with Grub, blkid and /dev/disk/by-uuid.
323 ;; Compare Grub: "2014-12-02-19-30-23-00".
324 ;; Compare blkid result: "2014-12-02-19-30-23-00".
325 ;; Compare /dev/disk/by-uuid entry: "2014-12-02-19-30-23-00".
c6aee77e
DM
326 (let* ((creation-time (sub-bytevector sblock 813 17))
327 (modification-time (sub-bytevector sblock 830 17))
328 (unset-time (make-bytevector 17 0))
329 (time (if (bytevector=? unset-time modification-time)
330 creation-time
331 modification-time)))
332 (sub-bytevector time 0 16))) ; strips GMT offset.
06110559 333
06110559
DM
334(define (iso9660-superblock-volume-name sblock)
335 "Return the volume name of SBLOCK as a string. The volume name is an ASCII
336string. Trailing spaces are trimmed."
cdc701ea 337 ;; Note: Valid characters are of the set "[0-9][A-Z]_" (ECMA-119 Appendix A)
06110559
DM
338 (string-trim-right (latin1->string (sub-bytevector sblock 40 32)
339 (lambda (c) #f)) #\space))
340
341\f
1abbe7c6
TGR
342;;;
343;;; JFS file systems.
344;;;
345
346;; Taken from <linux-libre>/fs/jfs/jfs_superblock.h.
347
348(define-syntax %jfs-endianness
349 ;; Endianness of JFS file systems.
350 (identifier-syntax (endianness little)))
351
352(define (jfs-superblock? sblock)
353 "Return #t when SBLOCK is a JFS superblock."
354 (bytevector=? (sub-bytevector sblock 0 4)
355 (string->utf8 "JFS1")))
356
357(define (read-jfs-superblock device)
358 "Return the raw contents of DEVICE's JFS superblock as a bytevector, or #f
359if DEVICE does not contain a JFS file system."
360 (read-superblock device 32768 184 jfs-superblock?))
361
362(define (jfs-superblock-uuid sblock)
363 "Return the UUID of JFS superblock SBLOCK as a 16-byte bytevector."
364 (sub-bytevector sblock 136 16))
365
366(define (jfs-superblock-volume-name sblock)
367 "Return the volume name of SBLOCK as a string of at most 16 characters, or
368#f if SBLOCK has no volume name."
369 (null-terminated-latin1->string (sub-bytevector sblock 152 16)))
370
371(define (check-jfs-file-system device)
372 "Return the health of a JFS file system on DEVICE."
373 (match (status:exit-val
374 (system* "jfs_fsck" "-p" "-v" device))
375 (0 'pass)
376 (1 'errors-corrected)
377 (2 'reboot-required)
378 (_ 'fatal-error)))
379
380\f
23b37c3d 381;;;
382;;; F2FS (Flash-Friendly File System)
383;;;
384
385;;; https://git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs.git/tree/include/linux/f2fs_fs.h
386;;; (but using xxd proved to be simpler)
387
388(define-syntax %f2fs-endianness
389 ;; Endianness of F2FS file systems
390 (identifier-syntax (endianness little)))
391
392;; F2FS actually stores two adjacent copies of the superblock.
393;; should we read both?
394(define (f2fs-superblock? sblock)
395 "Return #t when SBLOCK is an F2FS superblock."
396 (let ((magic (bytevector-u32-ref sblock 0 %f2fs-endianness)))
397 (= magic #xF2F52010)))
398
399(define (read-f2fs-superblock device)
400 "Return the raw contents of DEVICE's F2FS superblock as a bytevector, or #f
401if DEVICE does not contain an F2FS file system."
402 (read-superblock device
403 ;; offset of magic in first copy
404 #x400
405 ;; difference between magic of second
406 ;; and first copies
407 (- #x1400 #x400)
408 f2fs-superblock?))
409
410(define (f2fs-superblock-uuid sblock)
411 "Return the UUID of F2FS superblock SBLOCK as a 16-byte bytevector."
412 (sub-bytevector sblock
413 (- (+ #x460 12)
414 ;; subtract superblock offset
415 #x400)
416 16))
417
418(define (f2fs-superblock-volume-name sblock)
419 "Return the volume name of SBLOCK as a string of at most 512 characters, or
420#f if SBLOCK has no volume name."
f73f4b3a
DM
421 (null-terminated-utf16->string
422 (sub-bytevector sblock (- (+ #x470 12) #x400) 512)
423 %f2fs-endianness))
23b37c3d 424
425(define (check-f2fs-file-system device)
426 "Return the health of a F2FS file system on DEVICE."
427 (match (status:exit-val
428 (system* "fsck.f2fs" "-p" device))
429 ;; 0 and -1 are the only two possibilities
430 ;; (according to the manpage)
431 (0 'pass)
432 (_ 'fatal-error)))
433
434\f
a1ccefaa
LC
435;;;
436;;; LUKS encrypted devices.
437;;;
438
439;; The LUKS header format is described in "LUKS On-Disk Format Specification":
de975de3 440;; <https://gitlab.com/cryptsetup/cryptsetup/wikis/Specification>. We follow
a1ccefaa
LC
441;; version 1.2.1 of this document.
442
7aa28eb3
DT
443;; The LUKS2 header format is described in "LUKS2 On-Disk Format Specification":
444;; <https://gitlab.com/cryptsetup/LUKS2-docs/blob/master/luks2_doc_wip.pdf>.
445;; It is a WIP document.
446
a1ccefaa
LC
447(define-syntax %luks-endianness
448 ;; Endianness of LUKS headers.
449 (identifier-syntax (endianness big)))
450
974e02da
DC
451(define (luks-superblock? sblock)
452 "Return #t when SBLOCK is a luks superblock."
453 (define %luks-magic
454 ;; The 'LUKS_MAGIC' constant.
455 (u8-list->bytevector (append (map char->integer (string->list "LUKS"))
456 (list #xba #xbe))))
457 (let ((magic (sub-bytevector sblock 0 6))
458 (version (bytevector-u16-ref sblock 6 %luks-endianness)))
459 (and (bytevector=? magic %luks-magic)
7aa28eb3 460 (or (= version 1) (= version 2)))))
a1ccefaa
LC
461
462(define (read-luks-header file)
463 "Read a LUKS header from FILE. Return the raw header on success, and #f if
464not valid header was found."
7aa28eb3
DT
465 ;; Size in bytes of the LUKS binary header, which includes key slots in
466 ;; LUKS1. In LUKS2 the binary header is partially backward compatible, so
467 ;; that UUID can be extracted as for LUKS1. Keyslots and other metadata are
468 ;; not part of this header in LUKS2, but are included in the JSON metadata
469 ;; area that follows.
974e02da 470 (read-superblock file 0 592 luks-superblock?))
a1ccefaa
LC
471
472(define (luks-header-uuid header)
473 "Return the LUKS UUID from HEADER, as a 16-byte bytevector."
474 ;; 40 bytes are reserved for the UUID, but in practice, it contains the 36
475 ;; bytes of its ASCII representation.
476 (let ((uuid (sub-bytevector header 168 36)))
477 (string->uuid (utf8->string uuid))))
478
479\f
480;;;
481;;; Partition lookup.
482;;;
483
e2f4b305
LC
484(define (disk-partitions)
485 "Return the list of device names corresponding to valid disk partitions."
49baaff4 486 (define (partition? name major minor)
9833bcfc
DM
487 ;; grub-mkrescue does some funny things for EFI support which
488 ;; makes it a lot more difficult than one would expect to support
489 ;; booting an ISO-9660 image from an USB flash drive.
490 ;; For example there's a buggy (too small) hidden partition in it
491 ;; which Linux mounts and then proceeds to fail while trying to
492 ;; fall off the edge.
493 ;; In any case, partition tables are supposed to be optional so
494 ;; here we allow checking entire disks for file systems, too.
495 (> major 2)) ;ignore RAM disks and floppy disks
e2f4b305
LC
496
497 (call-with-input-file "/proc/partitions"
498 (lambda (port)
499 ;; Skip the two header lines.
500 (read-line port)
501 (read-line port)
502
503 ;; Read each subsequent line, and extract the last space-separated
504 ;; field.
505 (let loop ((parts '()))
506 (let ((line (read-line port)))
507 (if (eof-object? line)
508 (reverse parts)
509 (match (string-tokenize line)
510 (((= string->number major) (= string->number minor)
511 blocks name)
49baaff4 512 (if (partition? name major minor)
e2f4b305
LC
513 (loop (cons name parts))
514 (loop parts))))))))))
515
24473356
LC
516(define (ENOENT-safe proc)
517 "Wrap the one-argument PROC such that ENOENT errors are caught and lead to a
518warning and #f as the result."
519 (lambda (device)
520 (catch 'system-error
521 (lambda ()
522 (proc device))
523 (lambda args
524 ;; When running on the hand-made /dev,
525 ;; 'disk-partitions' could return partitions for which
526 ;; we have no /dev node. Handle that gracefully.
49baaff4
LC
527 (let ((errno (system-error-errno args)))
528 (cond ((= ENOENT errno)
529 (format (current-error-port)
530 "warning: device '~a' not found~%" device)
531 #f)
532 ((= ENOMEDIUM errno) ;for removable media
533 #f)
b53510e0
AVY
534 ((= EIO errno) ;unreadable hardware like audio CDs
535 (format (current-error-port)
536 "warning: failed to read from device '~a'~%" device)
537 #f)
49baaff4
LC
538 (else
539 (apply throw args))))))))
24473356 540
ab4e939c
DC
541(define (partition-field-reader read field)
542 "Return a procedure that takes a device and returns the value of a FIELD in
543the partition superblock or #f."
544 (let ((read (ENOENT-safe read)))
545 (lambda (device)
546 (let ((sblock (read device)))
547 (and sblock
548 (field sblock))))))
549
550(define (read-partition-field device partition-field-readers)
551 "Returns the value of a FIELD in the partition superblock of DEVICE or #f. It
552takes a list of PARTITION-FIELD-READERS and returns the result of the first
553partition field reader that returned a value."
554 (match (filter-map (cut apply <> (list device)) partition-field-readers)
555 ((field . _) field)
556 (_ #f)))
557
558(define %partition-label-readers
06110559
DM
559 (list (partition-field-reader read-iso9660-superblock
560 iso9660-superblock-volume-name)
561 (partition-field-reader read-ext2-superblock
b1a505ba
DC
562 ext2-superblock-volume-name)
563 (partition-field-reader read-btrfs-superblock
b0377e58
DC
564 btrfs-superblock-volume-name)
565 (partition-field-reader read-fat32-superblock
88235675
LC
566 fat32-superblock-volume-name)
567 (partition-field-reader read-fat16-superblock
1abbe7c6
TGR
568 fat16-superblock-volume-name)
569 (partition-field-reader read-jfs-superblock
23b37c3d 570 jfs-superblock-volume-name)
571 (partition-field-reader read-f2fs-superblock
572 f2fs-superblock-volume-name)))
ab4e939c
DC
573
574(define %partition-uuid-readers
06110559
DM
575 (list (partition-field-reader read-iso9660-superblock
576 iso9660-superblock-uuid)
577 (partition-field-reader read-ext2-superblock
b1a505ba
DC
578 ext2-superblock-uuid)
579 (partition-field-reader read-btrfs-superblock
b0377e58
DC
580 btrfs-superblock-uuid)
581 (partition-field-reader read-fat32-superblock
88235675
LC
582 fat32-superblock-uuid)
583 (partition-field-reader read-fat16-superblock
1abbe7c6
TGR
584 fat16-superblock-uuid)
585 (partition-field-reader read-jfs-superblock
23b37c3d 586 jfs-superblock-uuid)
587 (partition-field-reader read-f2fs-superblock
588 f2fs-superblock-uuid)))
ab4e939c
DC
589
590(define read-partition-label
591 (cut read-partition-field <> %partition-label-readers))
592
593(define read-partition-uuid
594 (cut read-partition-field <> %partition-uuid-readers))
595
8ae7044f
MO
596(define luks-partition-field-reader
597 (partition-field-reader read-luks-header luks-header-uuid))
598
599(define read-luks-partition-uuid
600 (cut read-partition-field <> (list luks-partition-field-reader)))
601
ab4e939c 602(define (partition-predicate reader =)
a1ccefaa
LC
603 "Return a predicate that returns true if the FIELD of partition header that
604was READ is = to the given value."
ab4e939c
DC
605 (lambda (expected)
606 (lambda (device)
607 (let ((actual (reader device)))
608 (and actual
609 (= actual expected))))))
0ec5ee94
LC
610
611(define partition-label-predicate
ab4e939c 612 (partition-predicate read-partition-label string=?))
0ec5ee94
LC
613
614(define partition-uuid-predicate
aed1f1b0 615 (partition-predicate read-partition-uuid uuid=?))
a1ccefaa 616
974e02da 617(define luks-partition-uuid-predicate
8ae7044f 618 (partition-predicate luks-partition-field-reader uuid=?))
e2f4b305 619
ab4e939c
DC
620(define (find-partition predicate)
621 "Return the first partition found that matches PREDICATE, or #f if none
e2f4b305 622were found."
ab4e939c
DC
623 (lambda (expected)
624 (find (predicate expected)
625 (map (cut string-append "/dev/" <>)
626 (disk-partitions)))))
627
628(define find-partition-by-label
629 (find-partition partition-label-predicate))
630
631(define find-partition-by-uuid
632 (find-partition partition-uuid-predicate))
633
634(define find-partition-by-luks-uuid
635 (find-partition luks-partition-uuid-predicate))
a1ccefaa 636
f8865db6 637\f
a5acc17a
LC
638(define (canonicalize-device-spec spec)
639 "Return the device name corresponding to SPEC, which can be a <uuid>, a
640<file-system-label>, or a string (typically a /dev file name)."
e2f4b305
LC
641 (define max-trials
642 ;; Number of times we retry partition label resolution, 1 second per
643 ;; trial. Note: somebody reported a delay of 16 seconds (!) before their
644 ;; USB key would be detected by the kernel, so we must wait for at least
645 ;; this long.
646 20)
647
0ec5ee94
LC
648 (define (resolve find-partition spec fmt)
649 (let loop ((count 0))
650 (let ((device (find-partition spec)))
651 (or device
652 ;; Some devices take a bit of time to appear, most notably USB
653 ;; storage devices. Thus, wait for the device to appear.
654 (if (> count max-trials)
655 (error "failed to resolve partition" (fmt spec))
656 (begin
657 (format #t "waiting for partition '~a' to appear...~%"
658 (fmt spec))
659 (sleep 1)
660 (loop (+ 1 count))))))))
661
a5acc17a
LC
662 (match spec
663 ((? string?)
281d80d8
MC
664 (if (string-contains spec ":/")
665 spec ; do not resolve NFS devices
666 ;; Nothing to do, but wait until SPEC shows up.
667 (resolve identity spec identity)))
a5acc17a 668 ((? file-system-label?)
e2f4b305 669 ;; Resolve the label.
a5acc17a
LC
670 (resolve find-partition-by-label
671 (file-system-label->string spec)
672 identity))
673 ((? uuid?)
f453f637 674 (resolve find-partition-by-uuid
a5acc17a
LC
675 (uuid-bytevector spec)
676 uuid->string))))
e2f4b305
LC
677
678(define (check-file-system device type)
679 "Run a file system check of TYPE on DEVICE."
26905ec8
DC
680 (define check-procedure
681 (cond
682 ((string-prefix? "ext" type) check-ext2-file-system)
b1a505ba 683 ((string-prefix? "btrfs" type) check-btrfs-file-system)
88235675 684 ((string-suffix? "fat" type) check-fat-file-system)
1abbe7c6 685 ((string-prefix? "jfs" type) check-jfs-file-system)
23b37c3d 686 ((string-prefix? "f2fs" type) check-f2fs-file-system)
85a7466e 687 ((string-prefix? "nfs" type) (const 'pass))
26905ec8
DC
688 (else #f)))
689
690 (if check-procedure
691 (match (check-procedure device)
692 ('pass
693 #t)
694 ('errors-corrected
695 (format (current-error-port)
696 "File system check corrected errors on ~a; continuing~%"
697 device))
698 ('reboot-required
699 (format (current-error-port)
700 "File system check corrected errors on ~a; rebooting~%"
701 device)
702 (sleep 3)
703 (reboot))
704 ('fatal-error
6ea6e147 705 (format (current-error-port) "File system check on ~a failed~%"
26905ec8 706 device)
6ea6e147
LC
707
708 ;; Spawn a REPL only if someone would be able to interact with it.
709 (when (isatty? (current-input-port))
710 (format (current-error-port) "Spawning Bourne-like REPL.~%")
5de5f818
LC
711
712 ;; 'current-output-port' is typically connected to /dev/klog (in
713 ;; PID 1), but here we want to make sure we talk directly to the
714 ;; user.
715 (with-output-to-file "/dev/console"
716 (lambda ()
717 (start-repl %bournish-language))))))
26905ec8
DC
718 (format (current-error-port)
719 "No file system check procedure for ~a; skipping~%"
720 device)))
e2f4b305
LC
721
722(define (mount-flags->bit-mask flags)
723 "Return the number suitable for the 'flags' argument of 'mount' that
724corresponds to the symbols listed in FLAGS."
725 (let loop ((flags flags))
726 (match flags
727 (('read-only rest ...)
728 (logior MS_RDONLY (loop rest)))
729 (('bind-mount rest ...)
730 (logior MS_BIND (loop rest)))
731 (('no-suid rest ...)
732 (logior MS_NOSUID (loop rest)))
733 (('no-dev rest ...)
734 (logior MS_NODEV (loop rest)))
735 (('no-exec rest ...)
736 (logior MS_NOEXEC (loop rest)))
9d305381 737 (('no-atime rest ...)
738 (logior MS_NOATIME (loop rest)))
0dc5c856
GLV
739 (('strict-atime rest ...)
740 (logior MS_STRICTATIME (loop rest)))
741 (('lazy-time rest ...)
742 (logior MS_LAZYTIME (loop rest)))
e2f4b305
LC
743 (()
744 0))))
745
1c65cca5 746(define* (mount-file-system fs #:key (root "/root"))
d2ae8a25 747 "Mount the file system described by FS, a <file-system> object, under ROOT."
0c85db79
JD
748
749 (define (mount-nfs source mount-point type flags options)
750 (let* ((idx (string-rindex source #\:))
751 (host-part (string-take source idx))
752 ;; Strip [] from around host if present
753 (host (match (string-split host-part (string->char-set "[]"))
754 (("" h "") h)
755 ((h) h)))
756 (aa (match (getaddrinfo host "nfs") ((x . _) x)))
757 (sa (addrinfo:addr aa))
758 (inet-addr (inet-ntop (sockaddr:fam sa)
759 (sockaddr:addr sa))))
760
761 ;; Mounting an NFS file system requires passing the address
762 ;; of the server in the addr= option
763 (mount source mount-point type flags
764 (string-append "addr="
765 inet-addr
766 (if options
767 (string-append "," options)
768 "")))))
1c65cca5
LC
769 (let ((type (file-system-type fs))
770 (options (file-system-options fs))
a5acc17a 771 (source (canonicalize-device-spec (file-system-device fs)))
1c65cca5
LC
772 (mount-point (string-append root "/"
773 (file-system-mount-point fs)))
774 (flags (mount-flags->bit-mask (file-system-flags fs))))
775 (when (file-system-check? fs)
776 (check-file-system source type))
777
778 ;; Create the mount point. Most of the time this is a directory, but
779 ;; in the case of a bind mount, a regular file or socket may be needed.
780 (if (and (= MS_BIND (logand flags MS_BIND))
781 (not (file-is-directory? source)))
782 (unless (file-exists? mount-point)
783 (mkdir-p (dirname mount-point))
784 (call-with-output-file mount-point (const #t)))
785 (mkdir-p mount-point))
786
787 (cond
788 ((string-prefix? "nfs" type)
789 (mount-nfs source mount-point type flags options))
790 (else
791 (mount source mount-point type flags options)))
792
793 ;; For read-only bind mounts, an extra remount is needed, as per
794 ;; <http://lwn.net/Articles/281157/>, which still applies to Linux 4.0.
795 (when (and (= MS_BIND (logand flags MS_BIND))
796 (= MS_RDONLY (logand flags MS_RDONLY)))
797 (let ((flags (logior MS_BIND MS_REMOUNT MS_RDONLY)))
798 (mount source mount-point type flags #f)))))
e2f4b305
LC
799
800;;; file-systems.scm ends here