diff options
Diffstat (limited to 'gnu/build/file-systems.scm')
-rw-r--r-- | gnu/build/file-systems.scm | 318 |
1 files changed, 238 insertions, 80 deletions
diff --git a/gnu/build/file-systems.scm b/gnu/build/file-systems.scm index 4eeb81cf26..d8a5ddf1e5 100644 --- a/gnu/build/file-systems.scm +++ b/gnu/build/file-systems.scm @@ -166,14 +166,23 @@ if DEVICE does not contain an ext2 file system." (sub-bytevector sblock 104 16)) (define (ext2-superblock-volume-name sblock) - "Return the volume name of SBLOCK as a string of at most 16 characters, or -#f if SBLOCK has no volume name." + "Return the volume name of ext2 superblock SBLOCK as a string of at most 16 +characters, or #f if SBLOCK has no volume name." (null-terminated-latin1->string (sub-bytevector sblock 120 16))) -(define (check-ext2-file-system device) - "Return the health of an ext2 file system on DEVICE." +(define (check-ext2-file-system device force? repair) + "Return the health of an unmounted ext2 file system on DEVICE. If FORCE? is +true, check the file system even if it's marked as clean. If REPAIR is false, +do not write to the file system to fix errors. If it's #t, fix all +errors. Otherwise, fix only those considered safe to repair automatically." (match (status:exit-val - (system* "e2fsck" "-v" "-p" "-C" "0" device)) + (apply system* `("e2fsck" "-v" "-C" "0" + ,@(if force? '("-f") '()) + ,@(match repair + (#f '("-n")) + (#t '("-y")) + (_ '("-p"))) + ,device))) (0 'pass) (1 'errors-corrected) (2 'reboot-required) @@ -256,19 +265,27 @@ bytevector." (sub-bytevector sblock 56 16)) (define (bcachefs-superblock-volume-name sblock) - "Return the volume name of SBLOCK as a string of at most 32 characters, or -#f if SBLOCK has no volume name." + "Return the volume name of bcachefs superblock SBLOCK as a string of at most +32 characters, or #f if SBLOCK has no volume name." (null-terminated-latin1->string (sub-bytevector sblock 72 32))) -(define (check-bcachefs-file-system device) - "Return the health of a bcachefs file system on DEVICE." +(define (check-bcachefs-file-system device force? repair) + "Return the health of an unmounted bcachefs file system on DEVICE. If FORCE? +is true, check the file system even if it's marked as clean. If REPAIR is +false, do not write to the file system to fix errors. If it's #t, fix all +errors. Otherwise, fix only those considered safe to repair automatically." (let ((ignored-bits (logior 2)) ; DEVICE was mounted read-only (status ;; A number, or #f on abnormal termination (e.g., assertion failure). (status:exit-val - (apply system* "bcachefs" "fsck" "-p" "-v" - ;; Make each multi-device member a separate argument. - (string-split device #\:))))) + (apply system* `("bcachefs" "fsck" "-v" + ,@(if force? '("-f") '()) + ,@(match repair + (#f '("-n")) + (#t '("-y")) + (_ '("-p"))) + ;; Make each multi-device member a separate argument. + ,@(string-split device #\:)))))) (match (and=> status (cut logand <> (lognot ignored-bits))) (0 'pass) (1 'errors-corrected) @@ -300,16 +317,33 @@ if DEVICE does not contain a btrfs file system." (sub-bytevector sblock 32 16)) (define (btrfs-superblock-volume-name sblock) - "Return the volume name of SBLOCK as a string of at most 256 characters, or -#f if SBLOCK has no volume name." + "Return the volume name of btrfs superblock SBLOCK as a string of at most 256 +characters, or #f if SBLOCK has no volume name." (null-terminated-latin1->string (sub-bytevector sblock 299 256))) -(define (check-btrfs-file-system device) - "Return the health of a btrfs file system on DEVICE." - (match (status:exit-val - (system* "btrfs" "device" "scan")) - (0 'pass) - (_ 'fatal-error))) +(define (check-btrfs-file-system device force? repair) + "Return the health of an unmounted btrfs file system on DEVICE. If FORCE? is +false, return 'PASS unconditionally as btrfs claims no need for off-line checks. +When FORCE? is true, do perform a real check. This is not recommended! See +@uref{https://bugzilla.redhat.com/show_bug.cgi?id=625967#c8}. If REPAIR is +false, do not write to DEVICE. If it's #t, fix any errors found. Otherwise, +fix only those considered safe to repair automatically." + (if force? + (match (status:exit-val + (apply system* `("btrfs" "check" "--progress" + ;; Btrfs's ‘--force’ is not relevant to us here. + ,@(match repair + ;; Upstream considers ALL repairs dangerous + ;; and will warn the user at run time. + (#t '("--repair")) + (_ '("--readonly" ; a no-op for clarity + ;; A 466G file system with 180G used is + ;; enough to kill btrfs with 6G of RAM. + "--mode" "lowmem"))) + ,device))) + (0 'pass) + (_ 'fatal-error)) + 'pass)) ;;; @@ -333,15 +367,22 @@ if DEVICE does not contain a btrfs file system." (sub-bytevector sblock 67 4)) (define (fat32-superblock-volume-name sblock) - "Return the volume name of SBLOCK as a string of at most 11 characters, or -#f if SBLOCK has no volume name. The volume name is a latin1 string. -Trailing spaces are trimmed." + "Return the volume name of fat superblock SBLOCK as a string of at most 11 +characters, or #f if SBLOCK has no volume name. The volume name is a latin1 +string. Trailing spaces are trimmed." (string-trim-right (latin1->string (sub-bytevector sblock 71 11) (lambda (c) #f)) #\space)) -(define (check-fat-file-system device) - "Return the health of a fat file system on DEVICE." +(define (check-fat-file-system device force? repair) + "Return the health of an unmounted FAT file system on DEVICE. FORCE? is +ignored: a full file system scan is always performed. If REPAIR is false, do +not write to the file system to fix errors. Otherwise, automatically fix them +using the least destructive approach." (match (status:exit-val - (system* "fsck.vfat" "-v" "-a" device)) + (apply system* `("fsck.vfat" "-v" + ,@(match repair + (#f '("-n")) + (_ '("-a"))) ; no 'safe/#t distinction + ,device))) (0 'pass) (1 'errors-corrected) (_ 'fatal-error))) @@ -366,9 +407,9 @@ Trailing spaces are trimmed." (sub-bytevector sblock 39 4)) (define (fat16-superblock-volume-name sblock) - "Return the volume name of SBLOCK as a string of at most 11 characters, or -#f if SBLOCK has no volume name. The volume name is a latin1 string. -Trailing spaces are trimmed." + "Return the volume name of fat superblock SBLOCK as a string of at most 11 +characters, or #f if SBLOCK has no volume name. The volume name is a latin1 +string. Trailing spaces are trimmed." (string-trim-right (latin1->string (sub-bytevector sblock 43 11) (lambda (c) #f)) #\space)) @@ -427,8 +468,8 @@ SBLOCK as a bytevector. If that's not set, returns the creation time." (sub-bytevector time 0 16))) ; strips GMT offset. (define (iso9660-superblock-volume-name sblock) - "Return the volume name of SBLOCK as a string. The volume name is an ASCII -string. Trailing spaces are trimmed." + "Return the volume name of iso9660 superblock SBLOCK as a string. The volume +name is an ASCII string. Trailing spaces are trimmed." ;; Note: Valid characters are of the set "[0-9][A-Z]_" (ECMA-119 Appendix A) (string-trim-right (latin1->string (sub-bytevector sblock 40 32) (lambda (c) #f)) #\space)) @@ -459,14 +500,32 @@ if DEVICE does not contain a JFS file system." (sub-bytevector sblock 136 16)) (define (jfs-superblock-volume-name sblock) - "Return the volume name of SBLOCK as a string of at most 16 characters, or -#f if SBLOCK has no volume name." + "Return the volume name of JFS superblock SBLOCK as a string of at most 16 +characters, or #f if SBLOCK has no volume name." (null-terminated-latin1->string (sub-bytevector sblock 152 16))) -(define (check-jfs-file-system device) - "Return the health of a JFS file system on DEVICE." +(define (check-jfs-file-system device force? repair) + "Return the health of an unmounted JFS file system on DEVICE. If FORCE? is +true, check the file system even if it's marked as clean. If REPAIR is false, +do not write to the file system to fix errors, and replay the transaction log +only if FORCE? is true. Otherwise, replay the transaction log before checking +and automatically fix found errors." (match (status:exit-val - (system* "jfs_fsck" "-p" "-v" device)) + (apply system* + `("jfs_fsck" "-v" + ;; The ‘LEVEL’ logic is convoluted. To quote fsck/xchkdsk.c + ;; (‘-p’, ‘-a’, and ‘-r’ are aliases in every way): + ;; “If -f was chosen, have it override [-p] by [forcing] a + ;; check regardless of the outcome after the log is + ;; replayed”. + ;; “If -n is specified by itself, don't replay the journal. + ;; If -n is specified with [-p], replay the journal but + ;; don't make any other changes”. + ,@(if force? '("-f") '()) + ,@(match repair + (#f '("-n")) + (_ '("-p"))) ; no 'safe/#t distinction + ,device))) (0 'pass) (1 'errors-corrected) (2 'reboot-required) @@ -511,18 +570,28 @@ if DEVICE does not contain an F2FS file system." 16)) (define (f2fs-superblock-volume-name sblock) - "Return the volume name of SBLOCK as a string of at most 512 characters, or -#f if SBLOCK has no volume name." + "Return the volume name of F2FS superblock SBLOCK as a string of at most 512 +characters, or #f if SBLOCK has no volume name." (null-terminated-utf16->string (sub-bytevector sblock (- (+ #x470 12) #x400) 512) %f2fs-endianness)) -(define (check-f2fs-file-system device) - "Return the health of a F2FS file system on DEVICE." +(define (check-f2fs-file-system device force? repair) + "Return the health of an unmuounted F2FS file system on DEVICE. If FORCE? is +true, check the file system even if it's marked as clean. If either FORCE? or +REPAIR are true, automatically fix found errors." + ;; There's no ‘-n’ equivalent (‘--dry-run’ does not disable writes). + ;; ’-y’ is an alias of ‘-f’. The man page is bad: read main.c. + (when (and force? (not repair)) + (format (current-error-port) + "warning: forced check of F2FS ~a implies repairing any errors~%" + device)) (match (status:exit-val - (system* "fsck.f2fs" "-p" device)) - ;; 0 and -1 are the only two possibilities - ;; (according to the manpage) + (apply system* `("fsck.f2fs" + ,@(if force? '("-f") '()) + ,@(if repair '("-p") '("--dry-run")) + ,device))) + ;; 0 and -1 are the only two possibilities according to the man page. (0 'pass) (_ 'fatal-error))) @@ -600,14 +669,82 @@ if DEVICE does not contain a NTFS file system." ;; in the BOOT SECTOR like the UUID, but in the MASTER FILE TABLE, which seems ;; way harder to access. -(define (check-ntfs-file-system device) - "Return the health of a NTFS file system on DEVICE." +(define (check-ntfs-file-system device force? repair) + "Return the health of an unmounted NTFS file system on DEVICE. FORCE? is +ignored: a full check is always performed. Repair is not possible: if REPAIR is +true and the volume has been repaired by an external tool, clear the volume +dirty flag to indicate that it's now safe to mount." (match (status:exit-val - (system* "ntfsfix" device)) + (apply system* `("ntfsfix" + ,@(if repair '("--clear-dirty") '("--no-action")) + ,device))) (0 'pass) (_ 'fatal-error))) + +;;; +;;; XFS file systems. +;;; + +;; <https://git.kernel.org/pub/scm/fs/xfs/xfs-documentation.git/tree/design/XFS_Filesystem_Structure/allocation_groups.asciidoc> + +(define-syntax %xfs-endianness + ;; Endianness of XFS file systems. + (identifier-syntax (endianness big))) + +(define (xfs-superblock? sblock) + "Return #t when SBLOCK is an XFS superblock." + (bytevector=? (sub-bytevector sblock 0 4) + (string->utf8 "XFSB"))) + +(define (read-xfs-superblock device) + "Return the raw contents of DEVICE's XFS superblock as a bytevector, or #f +if DEVICE does not contain an XFS file system." + (read-superblock device 0 120 xfs-superblock?)) + +(define (xfs-superblock-uuid sblock) + "Return the UUID of XFS superblock SBLOCK as a 16-byte bytevector." + (sub-bytevector sblock 32 16)) + +(define (xfs-superblock-volume-name sblock) + "Return the volume name of XFS superblock SBLOCK as a string of at most 12 +characters, or #f if SBLOCK has no volume name." + (null-terminated-latin1->string (sub-bytevector sblock 108 12))) + +(define (check-xfs-file-system device force? repair) + "Return the health of an unmounted XFS file system on DEVICE. If FORCE? is +false, return 'PASS unconditionally as XFS claims no need for off-line checks. +When FORCE? is true, do perform a thorough check. If REPAIR is false, do not +write to DEVICE. If it's #t, replay the log, check, and fix any errors found. +Otherwise, only replay the log, and check without attempting further repairs." + (define (xfs_repair) + (status:exit-val + (apply system* `("xfs_repair" "-Pv" + ,@(match repair + (#t '("-e")) + (_ '("-n"))) ; will miss some errors + ,device)))) + (if force? + ;; xfs_repair fails with exit status 2 if the log is dirty, which is + ;; likely in situations where you're running xfs_repair. Only the kernel + ;; can replay the log by {,un}mounting it cleanly. + (match (let ((status (xfs_repair))) + (if (and repair (eq? 2 status)) + (let ((target "/replay-XFS-log")) + ;; The kernel helpfully prints a ‘Mounting…’ notice for us. + (mkdir target) + (mount device target "xfs") + (umount target) + (rmdir target) + (xfs_repair)) + status)) + (0 'pass) + (4 'errors-corrected) + (_ 'fatal-error)) + 'pass)) + + ;;; ;;; Partition lookup. ;;; @@ -700,7 +837,9 @@ partition field reader that returned a value." (partition-field-reader read-jfs-superblock jfs-superblock-volume-name) (partition-field-reader read-f2fs-superblock - f2fs-superblock-volume-name))) + f2fs-superblock-volume-name) + (partition-field-reader read-xfs-superblock + xfs-superblock-volume-name))) (define %partition-uuid-readers (list (partition-field-reader read-iso9660-superblock @@ -722,7 +861,9 @@ partition field reader that returned a value." (partition-field-reader read-f2fs-superblock f2fs-superblock-uuid) (partition-field-reader read-ntfs-superblock - ntfs-superblock-uuid))) + ntfs-superblock-uuid) + (partition-field-reader read-xfs-superblock + xfs-superblock-uuid))) (define read-partition-label (cut read-partition-field <> %partition-label-readers)) @@ -816,8 +957,13 @@ containing ':/')." (uuid-bytevector spec) uuid->string)))) -(define (check-file-system device type) - "Run a file system check of TYPE on DEVICE." +(define (check-file-system device type force? repair) + "Check an unmounted TYPE file system on DEVICE. Do nothing but warn if it is +mounted. If FORCE? is true, check even when considered unnecessary. If REPAIR +is false, try not to write to DEVICE at all. If it's #t, try to fix all errors +found. Otherwise, fix only those considered safe to repair automatically. Not +all TYPEs support all values or combinations of FORCE? and REPAIR. Don't throw +an exception in such cases but perform the nearest sane action." (define check-procedure (cond ((string-prefix? "ext" type) check-ext2-file-system) @@ -828,36 +974,44 @@ containing ':/')." ((string-prefix? "f2fs" type) check-f2fs-file-system) ((string-prefix? "ntfs" type) check-ntfs-file-system) ((string-prefix? "nfs" type) (const 'pass)) + ((string-prefix? "xfs" type) check-xfs-file-system) (else #f))) (if check-procedure - (match (check-procedure device) - ('pass - #t) - ('errors-corrected - (format (current-error-port) - "File system check corrected errors on ~a; continuing~%" - device)) - ('reboot-required - (format (current-error-port) - "File system check corrected errors on ~a; rebooting~%" - device) - (sleep 3) - (reboot)) - ('fatal-error - (format (current-error-port) "File system check on ~a failed~%" - device) - - ;; Spawn a REPL only if someone would be able to interact with it. - (when (isatty? (current-input-port)) - (format (current-error-port) "Spawning Bourne-like REPL.~%") - - ;; 'current-output-port' is typically connected to /dev/klog (in - ;; PID 1), but here we want to make sure we talk directly to the - ;; user. - (with-output-to-file "/dev/console" - (lambda () - (start-repl %bournish-language)))))) + (let ((mount (find (lambda (mount) + (string=? device (mount-source mount))) + (mounts)))) + (if mount + (format (current-error-port) + "Refusing to check ~a file system already mounted at ~a~%" + device (mount-point mount)) + (match (check-procedure device force? repair) + ('pass + #t) + ('errors-corrected + (format (current-error-port) + "File system check corrected errors on ~a; continuing~%" + device)) + ('reboot-required + (format (current-error-port) + "File system check corrected errors on ~a; rebooting~%" + device) + (sleep 3) + (reboot)) + ('fatal-error + (format (current-error-port) "File system check on ~a failed~%" + device) + + ;; Spawn a REPL only if someone might interact with it. + (when (isatty? (current-input-port)) + (format (current-error-port) "Spawning Bourne-like REPL.~%") + + ;; 'current-output-port' is typically connected to /dev/klog + ;; (in PID 1), but here we want to make sure we talk directly + ;; to the user. + (with-output-to-file "/dev/console" + (lambda () + (start-repl %bournish-language)))))))) (format (current-error-port) "No file system check procedure for ~a; skipping~%" device))) @@ -886,7 +1040,11 @@ corresponds to the symbols listed in FLAGS." (() 0)))) -(define* (mount-file-system fs #:key (root "/root")) +(define* (mount-file-system fs #:key (root "/root") + (check? (file-system-check? fs)) + (skip-check-if-clean? + (file-system-skip-check-if-clean? fs)) + (repair (file-system-repair fs))) "Mount the file system described by FS, a <file-system> object, under ROOT." (define (mount-nfs source mount-point type flags options) @@ -924,8 +1082,8 @@ corresponds to the symbols listed in FLAGS." (file-system-mount-flags (statfs source))) 0))) (options (file-system-options fs))) - (when (file-system-check? fs) - (check-file-system source type)) + (when check? + (check-file-system source type (not skip-check-if-clean?) repair)) (catch 'system-error (lambda () |