diff options
| author | Maxim Cournoyer <maxim.cournoyer@gmail.com> | 2021-06-17 01:22:35 -0400 |
|---|---|---|
| committer | Maxim Cournoyer <maxim.cournoyer@gmail.com> | 2021-06-29 14:53:21 -0400 |
| commit | 4f3bdc8f21657dbda857027b3ec8754dd4c7c67b (patch) | |
| tree | c2a13dca9fdd301c8042696ea5f8f62dcfe43818 | |
| parent | 6b0e55cde901dd5f6eae72cee10723b7739cadf7 (diff) | |
pack: Prevent duplicate files in tar archives.
Tar translate duplicate files in the archive into hard links. These can cause
problems, as not every tool support them; for example dpkg doesn't.
* gnu/system/file-systems.scm (reduce-directories): New procedure.
(file-prefix?): Lift the restriction on file prefix. The procedure can be
useful for comparing relative file names. Adjust doc.
(file-name-depth): New procedure, extracted from ...
(btrfs-store-subvolume-file-name): ... here.
* guix/scripts/pack.scm (self-contained-tarball/builder): Use
reduce-directories.
* tests/file-systems.scm ("reduce-directories"): New test.
| -rw-r--r-- | gnu/system/file-systems.scm | 56 | ||||
| -rw-r--r-- | guix/scripts/pack.scm | 6 | ||||
| -rw-r--r-- | tests/file-systems.scm | 7 |
3 files changed, 48 insertions, 21 deletions
diff --git a/gnu/system/file-systems.scm b/gnu/system/file-systems.scm index 464e87cb181..fb87bfc85b9 100644 --- a/gnu/system/file-systems.scm +++ b/gnu/system/file-systems.scm | |||
| @@ -55,6 +55,7 @@ | |||
| 55 | file-system-dependencies | 55 | file-system-dependencies |
| 56 | file-system-location | 56 | file-system-location |
| 57 | 57 | ||
| 58 | reduce-directories | ||
| 58 | file-system-type-predicate | 59 | file-system-type-predicate |
| 59 | btrfs-subvolume? | 60 | btrfs-subvolume? |
| 60 | btrfs-store-subvolume-file-name | 61 | btrfs-store-subvolume-file-name |
| @@ -231,8 +232,8 @@ | |||
| 231 | (char-set-complement (char-set #\/))) | 232 | (char-set-complement (char-set #\/))) |
| 232 | 233 | ||
| 233 | (define (file-prefix? file1 file2) | 234 | (define (file-prefix? file1 file2) |
| 234 | "Return #t if FILE1 denotes the name of a file that is a parent of FILE2, | 235 | "Return #t if FILE1 denotes the name of a file that is a parent of FILE2. |
| 235 | where both FILE1 and FILE2 are absolute file name. For example: | 236 | For example: |
| 236 | 237 | ||
| 237 | (file-prefix? \"/gnu\" \"/gnu/store\") | 238 | (file-prefix? \"/gnu\" \"/gnu/store\") |
| 238 | => #t | 239 | => #t |
| @@ -240,19 +241,41 @@ where both FILE1 and FILE2 are absolute file name. For example: | |||
| 240 | (file-prefix? \"/gn\" \"/gnu/store\") | 241 | (file-prefix? \"/gn\" \"/gnu/store\") |
| 241 | => #f | 242 | => #f |
| 242 | " | 243 | " |
| 243 | (and (string-prefix? "/" file1) | 244 | (let loop ((file1 (string-tokenize file1 %not-slash)) |
| 244 | (string-prefix? "/" file2) | 245 | (file2 (string-tokenize file2 %not-slash))) |
| 245 | (let loop ((file1 (string-tokenize file1 %not-slash)) | 246 | (match file1 |
| 246 | (file2 (string-tokenize file2 %not-slash))) | 247 | (() |
| 247 | (match file1 | 248 | #t) |
| 248 | (() | 249 | ((head1 tail1 ...) |
| 249 | #t) | 250 | (match file2 |
| 250 | ((head1 tail1 ...) | 251 | ((head2 tail2 ...) |
| 251 | (match file2 | 252 | (and (string=? head1 head2) (loop tail1 tail2))) |
| 252 | ((head2 tail2 ...) | 253 | (() |
| 253 | (and (string=? head1 head2) (loop tail1 tail2))) | 254 | #f)))))) |
| 254 | (() | 255 | |
| 255 | #f))))))) | 256 | (define (file-name-depth file-name) |
| 257 | (length (string-tokenize file-name %not-slash))) | ||
| 258 | |||
| 259 | (define (reduce-directories file-names) | ||
| 260 | "Eliminate entries in FILE-NAMES that are children of other entries in | ||
| 261 | FILE-NAMES. This is for example useful when passing a list of files to GNU | ||
| 262 | tar, which would otherwise descend into each directory passed and archive the | ||
| 263 | duplicate files as hard links, which can be undesirable." | ||
| 264 | (let* ((file-names/sorted | ||
| 265 | ;; Ascending sort by file hierarchy depth, then by file name length. | ||
| 266 | (stable-sort (delete-duplicates file-names) | ||
| 267 | (lambda (f1 f2) | ||
| 268 | (let ((depth1 (file-name-depth f1)) | ||
| 269 | (depth2 (file-name-depth f2))) | ||
| 270 | (if (= depth1 depth2) | ||
| 271 | (string< f1 f2) | ||
| 272 | (< depth1 depth2))))))) | ||
| 273 | (reverse (fold (lambda (file-name results) | ||
| 274 | (if (find (cut file-prefix? <> file-name) results) | ||
| 275 | results ;parent found -- skipping | ||
| 276 | (cons file-name results))) | ||
| 277 | '() | ||
| 278 | file-names/sorted)))) | ||
| 256 | 279 | ||
| 257 | (define* (file-system-device->string device #:key uuid-type) | 280 | (define* (file-system-device->string device #:key uuid-type) |
| 258 | "Return the string representations of the DEVICE field of a <file-system> | 281 | "Return the string representations of the DEVICE field of a <file-system> |
| @@ -624,9 +647,6 @@ store is located, else #f." | |||
| 624 | s | 647 | s |
| 625 | (string-append "/" s))) | 648 | (string-append "/" s))) |
| 626 | 649 | ||
| 627 | (define (file-name-depth file-name) | ||
| 628 | (length (string-tokenize file-name %not-slash))) | ||
| 629 | |||
| 630 | (and-let* ((btrfs-subvolume-fs (filter btrfs-subvolume? file-systems)) | 650 | (and-let* ((btrfs-subvolume-fs (filter btrfs-subvolume? file-systems)) |
| 631 | (btrfs-subvolume-fs* | 651 | (btrfs-subvolume-fs* |
| 632 | (sort btrfs-subvolume-fs | 652 | (sort btrfs-subvolume-fs |
diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm index 952c1455bee..cee14441103 100644 --- a/guix/scripts/pack.scm +++ b/guix/scripts/pack.scm | |||
| @@ -230,13 +230,15 @@ its source property." | |||
| 230 | `((guix build pack) | 230 | `((guix build pack) |
| 231 | (guix build utils) | 231 | (guix build utils) |
| 232 | (guix build union) | 232 | (guix build union) |
| 233 | (gnu build install)) | 233 | (gnu build install) |
| 234 | (gnu system file-systems)) | ||
| 234 | #:select? import-module?) | 235 | #:select? import-module?) |
| 235 | #~(begin | 236 | #~(begin |
| 236 | (use-modules (guix build pack) | 237 | (use-modules (guix build pack) |
| 237 | (guix build utils) | 238 | (guix build utils) |
| 238 | ((guix build union) #:select (relative-file-name)) | 239 | ((guix build union) #:select (relative-file-name)) |
| 239 | (gnu build install) | 240 | (gnu build install) |
| 241 | ((gnu system file-systems) #:select (reduce-directories)) | ||
| 240 | (srfi srfi-1) | 242 | (srfi srfi-1) |
| 241 | (srfi srfi-26) | 243 | (srfi srfi-26) |
| 242 | (ice-9 match)) | 244 | (ice-9 match)) |
| @@ -303,7 +305,7 @@ its source property." | |||
| 303 | 305 | ||
| 304 | ,(string-append "." (%store-directory)) | 306 | ,(string-append "." (%store-directory)) |
| 305 | 307 | ||
| 306 | ,@(delete-duplicates | 308 | ,@(reduce-directories |
| 307 | (filter-map (match-lambda | 309 | (filter-map (match-lambda |
| 308 | (('directory directory) | 310 | (('directory directory) |
| 309 | (string-append "." directory)) | 311 | (string-append "." directory)) |
diff --git a/tests/file-systems.scm b/tests/file-systems.scm index 7f7c373884f..80acb6d5b91 100644 --- a/tests/file-systems.scm +++ b/tests/file-systems.scm | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2015, 2017 Ludovic Courtès <ludo@gnu.org> | 2 | ;;; Copyright © 2015, 2017 Ludovic Courtès <ludo@gnu.org> |
| 3 | ;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com> | 3 | ;;; Copyright © 2020, 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com> |
| 4 | ;;; | 4 | ;;; |
| 5 | ;;; This file is part of GNU Guix. | 5 | ;;; This file is part of GNU Guix. |
| 6 | ;;; | 6 | ;;; |
| @@ -50,6 +50,11 @@ | |||
| 50 | (device "/foo") | 50 | (device "/foo") |
| 51 | (flags '(bind-mount read-only))))))))) | 51 | (flags '(bind-mount read-only))))))))) |
| 52 | 52 | ||
| 53 | (test-equal "reduce-directories" | ||
| 54 | '("./opt/gnu/" "./opt/gnuism" "a/b/c") | ||
| 55 | (reduce-directories '("./opt/gnu/etc" "./opt/gnu/" "./opt/gnu/bin" | ||
| 56 | "./opt/gnu/lib/debug" "./opt/gnuism" "a/b/c" "a/b/c"))) | ||
| 57 | |||
| 53 | (test-assert "does not pull (guix config)" | 58 | (test-assert "does not pull (guix config)" |
| 54 | ;; This module is meant both for the host side and "build side", so make | 59 | ;; This module is meant both for the host side and "build side", so make |
| 55 | ;; sure it doesn't pull in (guix config), which depends on the user's | 60 | ;; sure it doesn't pull in (guix config), which depends on the user's |
