aboutsummaryrefslogtreecommitdiff
path: root/tests/gremlin.scm
blob: 3dbb8d3643aeea71d9b5edeada1c84798decbb2d (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2015, 2018, 2020, 2022 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2022 Chris Marusich <cmmarusich@gmail.com>
;;; Copyright © 2022 Pierre Langlois <pierre.langlois@gmx.com>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (test-gremlin)
  #:use-module (guix elf)
  #:use-module (guix tests)
  #:use-module ((guix utils) #:select (call-with-temporary-directory
                                       target-aarch64?))
  #:use-module (guix build utils)
  #:use-module (guix build gremlin)
  #:use-module (gnu packages bootstrap)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:use-module (srfi srfi-34)
  #:use-module (srfi srfi-64)
  #:use-module (rnrs io ports)
  #:use-module (ice-9 popen)
  #:use-module (ice-9 rdelim)
  #:use-module (ice-9 regex)
  #:use-module (ice-9 match))

(define %guile-executable
  (match (false-if-exception (readlink "/proc/self/exe"))
    ((? string? program)
     (and (file-exists? program) (elf-file? program)
          program))
    (_
     #f)))

(define read-elf
  (compose parse-elf get-bytevector-all))

(define c-compiler
  (or (which "gcc") (which "cc") (which "g++")))


(test-begin "gremlin")

(unless %guile-executable (test-skip 1))
(test-assert "elf-dynamic-info-needed, executable"
  (let* ((elf     (call-with-input-file %guile-executable read-elf))
         (dyninfo (elf-dynamic-info elf)))
    (or (not dyninfo)                             ;static executable
        (lset<= string=?
                (list (string-append "libguile-" (effective-version))
                      "libc")
                (map (lambda (lib)
                       (string-take lib (string-contains lib ".so")))
                     (elf-dynamic-info-needed dyninfo))))))

(unless (and %guile-executable (not (getenv "LD_LIBRARY_PATH"))
             (file-needed %guile-executable) ;statically linked?
             ;; When Guix has been built on a foreign distro, using a
             ;; toolchain and libraries from that foreign distro, it is not
             ;; unusual for the runpath to be empty.
             (pair? (file-runpath %guile-executable)))
  (test-skip 1))
(test-assert "file-needed/recursive"
  (let* ((needed (file-needed/recursive %guile-executable))
         (pipe   (dynamic-wind
                   (lambda ()
                     ;; Tell ld.so to list loaded objects, like 'ldd' does.
                     (setenv "LD_TRACE_LOADED_OBJECTS" "yup"))
                   (lambda ()
                     (open-pipe* OPEN_READ %guile-executable))
                   (lambda ()
                     (unsetenv "LD_TRACE_LOADED_OBJECTS")))))
    (define ldd-rx
      (make-regexp "^[[:blank:]]+([[:graph:]]+ => )?([[:graph:]]+) .*$"))

    (define (read-ldd-output port)
      ;; Read from PORT output in GNU ldd format.
      (let loop ((result '()))
        (match (read-line port)
          ((? eof-object?)
           (reverse result))
          ((= (cut regexp-exec ldd-rx <>) m)
           (if m
               (loop (cons (match:substring m 2) result))
               (loop result))))))
    (define ground-truth
      (remove (lambda (entry)
                ;; See vdso(7) for the list of vDSO names across
                ;; architectures.
                (or (string-prefix? "linux-vdso.so" entry)
                    (string-prefix? "linux-vdso32.so" entry) ;32-bit powerpc
                    (string-prefix? "linux-vdso64.so" entry) ;64-bit powerpc
                    (string-prefix? "linux-gate.so" entry)   ;i386
                    ;; FIXME: ELF files on aarch64 do not always include a
                    ;; NEEDED entry for the dynamic linker, and it is unclear
                    ;; if that is OK.  See: https://issues.guix.gnu.org/52943
                    (and (target-aarch64?)
                         (string-contains entry (glibc-dynamic-linker)))))
              (read-ldd-output pipe)))

    (and (zero? (close-pipe pipe))
         ;; It's OK if file-needed/recursive returns multiple entries that are
         ;; different strings referring to the same file.  This appears to be a
         ;; benign edge case.  See: https://issues.guix.gnu.org/52940
         (lset= file=? (pk 'truth ground-truth) (pk 'needed needed)))))

(test-equal "expand-origin"
  '("OOO/../lib"
    "OOO"
    "../OOO/bar/OOO/baz"
    "ORIGIN/foo")
  (map (cut expand-origin <> "OOO")
       '("$ORIGIN/../lib"
         "${ORIGIN}"
         "../${ORIGIN}/bar/$ORIGIN/baz"
         "ORIGIN/foo")))

(unless c-compiler
  (test-skip 1))
(test-equal "strip-runpath"
  "hello\n"
  (call-with-temporary-directory
   (lambda (directory)
     (with-directory-excursion directory
       (call-with-output-file "t.c"
         (lambda (port)
           (display "int main () { puts(\"hello\"); }" port)))
       (invoke c-compiler "t.c"
               "-Wl,--enable-new-dtags" "-Wl,-rpath=/foo" "-Wl,-rpath=/bar")
       (let* ((dyninfo (elf-dynamic-info
                        (parse-elf (call-with-input-file "a.out"
                                     get-bytevector-all))))
              (old     (elf-dynamic-info-runpath dyninfo))
              (new     (strip-runpath "a.out"))
              (new*    (strip-runpath "a.out")))
         (validate-needed-in-runpath "a.out")
         (and (member "/foo" old) (member "/bar" old)
              (not (member "/foo" new))
              (not (member "/bar" new))
              (equal? new* new)
              (let* ((pipe (open-input-pipe "./a.out"))
                     (str  (get-string-all pipe)))
                (close-pipe pipe)
                str)))))))

(unless c-compiler
  (test-skip 1))
(test-equal "set-file-runpath + file-runpath"
  "hello\n"
  (call-with-temporary-directory
   (lambda (directory)
     (with-directory-excursion directory
       (call-with-output-file "t.c"
         (lambda (port)
           (display "int main () { puts(\"hello\"); }" port)))

       (invoke c-compiler "t.c"
               "-Wl,--enable-new-dtags" "-Wl,-rpath=/xxxxxxxxx")

       (let ((original-runpath (file-runpath "a.out")))
         (and (member "/xxxxxxxxx" original-runpath)
              (guard (c ((runpath-too-long-error? c)
                         (string=? "a.out" (runpath-too-long-error-file c))))
                (set-file-runpath "a.out" (list (make-string 777 #\y))))
              (let ((runpath (delete "/xxxxxxxxx" original-runpath)))
                (set-file-runpath "a.out" runpath)
                (equal? runpath (file-runpath "a.out")))
              (let* ((pipe (open-input-pipe "./a.out"))
                     (str  (get-string-all pipe)))
                (close-pipe pipe)
                str)))))))

(unless c-compiler
  (test-skip 1))
(test-equal "elf-dynamic-info-soname"
  "libfoo.so.2"
  (call-with-temporary-directory
   (lambda (directory)
     (with-directory-excursion directory
       (call-with-output-file "t.c"
         (lambda (port)
           (display "// empty file" port)))
       (invoke c-compiler "t.c"
               "-shared" "-Wl,-soname,libfoo.so.2")
       (let* ((dyninfo (elf-dynamic-info
                       (parse-elf (call-with-input-file "a.out"
                                    get-bytevector-all))))
              (soname  (elf-dynamic-info-soname dyninfo)))
	 soname)))))

(test-end "gremlin")
"\n") (with-input-from-string (string-append "substitute " (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo" " foo\n") (lambda () (guix-substitute "--substitute"))))) (test-equal "substitute, invalid hash" (string-append "hash-mismatch sha256 " (bytevector->nix-base32-string (sha256 #vu8())) " " (let-values (((port get-hash) (open-hash-port (hash-algorithm sha256))) ((content) "Substitutable data.")) (write-file-tree "foo" port #:file-type+size (lambda _ (values 'regular (string-length content))) #:file-port (lambda _ (open-input-string content))) (close-port port) (bytevector->nix-base32-string (get-hash))) "\n") ;; Arrange so the actual data hash does not match the 'NarHash' field in the ;; narinfo. (with-output-to-string (lambda () (let ((narinfo (string-append "StorePath: " (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-wrong-hash URL: example.nar Compression: none NarHash: sha256:" (bytevector->nix-base32-string (sha256 #vu8())) " NarSize: 42 References: Deriver: " (%store-prefix) "/foo.drv System: mips64el-linux\n"))) (with-narinfo (string-append narinfo "Signature: " (signature-field narinfo) "\n") (call-with-temporary-directory (lambda (directory) (with-input-from-string (string-append "substitute " (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-wrong-hash " directory "/wrong-hash\n") (lambda () (guix-substitute "--substitute")))))))))) (test-quit "substitute, unauthorized key" "no valid substitute" (with-narinfo (string-append %narinfo "Signature: " (signature-field %narinfo #:public-key %wrong-public-key) "\n") (with-input-from-string (string-append "substitute " (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo" " foo\n") (lambda () (guix-substitute "--substitute"))))) (test-equal "substitute, authorized key" '("Substitutable data." 1 #o444) (with-narinfo (string-append %narinfo "Signature: " (signature-field %narinfo)) (dynamic-wind (const #t) (lambda () (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved") (list (call-with-input-file "substitute-retrieved" get-string-all) (stat:mtime (lstat "substitute-retrieved")) (stat:perms (lstat "substitute-retrieved")))) (lambda () (false-if-exception (delete-file "substitute-retrieved")))))) (test-equal "substitute, authorized key, first substitute URL is unroutable" '("Substitutable data." 1 #o444) (with-narinfo (string-append %narinfo "Signature: " (signature-field %narinfo)) (dynamic-wind (const #t) (lambda () ;; Pick an unroutable URL as the first one. This shouldn't be a ;; problem. (parameterize ((substitute-urls (list %unroutable-substitute-url (string-append "file://" %main-substitute-directory)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved") (list (call-with-input-file "substitute-retrieved" get-string-all) (stat:mtime (lstat "substitute-retrieved")) (stat:perms (lstat "substitute-retrieved"))))) (lambda () (false-if-exception (delete-file "substitute-retrieved")))))) (test-equal "substitute, unauthorized narinfo comes first" "Substitutable data." (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo #:public-key %wrong-public-key)) %alternate-substitute-directory (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo)) %main-substitute-directory (dynamic-wind (const #t) (lambda () ;; Remove this file so that the substitute can only be retrieved ;; from %ALTERNATE-SUBSTITUTE-DIRECTORY. (delete-file (string-append %main-substitute-directory "/example.nar")) (parameterize ((substitute-urls (map (cut string-append "file://" <>) (list %alternate-substitute-directory %main-substitute-directory)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))) (test-equal "substitute, unsigned narinfo comes first" "Substitutable data." (with-narinfo* %narinfo ;not signed! %alternate-substitute-directory (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo)) %main-substitute-directory (dynamic-wind (const #t) (lambda () ;; Remove this file so that the substitute can only be retrieved ;; from %ALTERNATE-SUBSTITUTE-DIRECTORY. (delete-file (string-append %main-substitute-directory "/example.nar")) (parameterize ((substitute-urls (map (cut string-append "file://" <>) (list %alternate-substitute-directory %main-substitute-directory)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))) (test-equal "substitute, first URL has narinfo but lacks nar, second URL unauthorized" "Substitutable data." (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo #:public-key %wrong-public-key)) %alternate-substitute-directory (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo)) %main-substitute-directory (dynamic-wind (const #t) (lambda () ;; Remove this file so that the substitute can only be retrieved ;; from %ALTERNATE-SUBSTITUTE-DIRECTORY. (delete-file (string-append %main-substitute-directory "/example.nar")) (parameterize ((substitute-urls (map (cut string-append "file://" <>) (list %main-substitute-directory %alternate-substitute-directory)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))) (test-equal "substitute, first URL has narinfo but nar is 404, both URLs authorized" "Substitutable data." (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo)) %main-substitute-directory (with-http-server `((200 ,(string-append %narinfo "Signature: " (signature-field %narinfo))) (404 "Sorry, nar is missing!")) (dynamic-wind (const #t) (lambda () (parameterize ((substitute-urls (list (%local-url) (string-append "file://" %main-substitute-directory)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))) (test-equal "substitute, first URL has narinfo but nar is 404, one URL authorized" "Substitutable data." (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo #:public-key %wrong-public-key)) %main-substitute-directory (with-http-server `((200 ,(string-append %narinfo "Signature: " (signature-field %narinfo #:public-key %wrong-public-key))) (404 "Sorry, nar is missing!")) (let ((url1 (%local-url))) (parameterize ((%http-server-port 0)) (with-http-server `((200 ,(string-append %narinfo "Signature: " (signature-field %narinfo))) (404 "Sorry, nar is missing!")) (let ((url2 (%local-url))) (dynamic-wind (const #t) (lambda () (parameterize ((substitute-urls (list url1 url2 (string-append "file://" %main-substitute-directory)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))))))) (test-equal "substitute, preferred nar URL is 404, other is 200" "Substitutable data." (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo)) %main-substitute-directory (with-http-server `((200 ,(string-append %narinfo "Signature: " (signature-field %narinfo) "\n" "URL: example.nar.lz\n" "Compression: lzip\n")) (404 "Sorry, nar.lz is missing!") (200 ,(call-with-input-file (string-append %main-substitute-directory "/example.nar") get-bytevector-all))) (dynamic-wind (const #t) (lambda () (parameterize ((substitute-urls (list (%local-url)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))) (test-equal "substitute, previous partial download around" "Substitutable data." (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo)) %main-substitute-directory (with-http-server `((200 ,(string-append %narinfo "Signature: " (signature-field %narinfo))) (200 ,(call-with-input-file (string-append %main-substitute-directory "/example.nar") get-bytevector-all))) (dynamic-wind (const #t) (lambda () (parameterize ((substitute-urls (list (%local-url)))) (mkdir-p "substitute-retrieved/a/b/c/d") ;add stale data (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))) (test-equal "substitute, narinfo is available but nar is missing" "not-found\n" (let ((port (open-output-string))) (parameterize ((current-output-port port)) (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo #:public-key %wrong-public-key)) %main-substitute-directory (with-http-server `((200 ,(string-append %narinfo "Signature: " (signature-field %narinfo))) (404 "Sorry, nar is missing!")) (parameterize ((substitute-urls (list (%local-url) (string-append "file://" %main-substitute-directory)))) (delete-file (string-append %main-substitute-directory "/example.nar")) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved") (and (not (file-exists? "substitute-retrieved")) (get-output-string port)))))))) (test-equal "substitute, first narinfo is unsigned and has wrong hash" "Substitutable data." (with-narinfo* (regexp-substitute #f (string-match "NarHash: [[:graph:]]+" %narinfo) 'pre "NarHash: sha256:" (bytevector->nix-base32-string (make-bytevector 32)) 'post) %alternate-substitute-directory (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo)) %main-substitute-directory (dynamic-wind (const #t) (lambda () ;; This time remove the file so that the substitute can only be ;; retrieved from %MAIN-SUBSTITUTE-DIRECTORY. (delete-file (string-append %alternate-substitute-directory "/example.nar")) (parameterize ((substitute-urls (map (cut string-append "file://" <>) (list %alternate-substitute-directory %main-substitute-directory)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))) (test-equal "substitute, first narinfo is unsigned and has wrong refs" "Substitutable data." (with-narinfo* (regexp-substitute #f (string-match "References: ([^\n]+)\n" %narinfo) 'pre "References: " 1 " wrong set of references\n" 'post) %alternate-substitute-directory (with-narinfo* (string-append %narinfo "Signature: " (signature-field %narinfo)) %main-substitute-directory (dynamic-wind (const #t) (lambda () ;; This time remove the file so that the substitute can only be ;; retrieved from %MAIN-SUBSTITUTE-DIRECTORY. (delete-file (string-append %alternate-substitute-directory "/example.nar")) (parameterize ((substitute-urls (map (cut string-append "file://" <>) (list %alternate-substitute-directory %main-substitute-directory)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))) (test-quit "substitute, two invalid narinfos" "no valid substitute" (with-narinfo* %narinfo ;not signed %alternate-substitute-directory (with-narinfo* (string-append %narinfo "Signature: " ;unauthorized (signature-field %narinfo #:public-key %wrong-public-key)) %main-substitute-directory (with-input-from-string (string-append "substitute " (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo" " substitute-retrieved\n") (lambda () (guix-substitute "--substitute")))))) (test-equal "substitute, narinfo with several URLs" "Substitutable data." (let ((narinfo (string-append "StorePath: " (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo URL: example.nar.gz Compression: gzip URL: example.nar.lz Compression: lzip URL: example.nar Compression: none NarHash: sha256:" (bytevector->nix-base32-string (sha256 (string->utf8 "Substitutable data."))) " NarSize: 42 References: bar baz Deriver: " (%store-prefix) "/foo.drv System: mips64el-linux\n"))) (with-narinfo (string-append narinfo "Signature: " (signature-field narinfo)) (dynamic-wind (const #t) (lambda () (define (compress input output compression) (call-with-output-file output (lambda (port) (call-with-compressed-output-port compression port (lambda (port) (call-with-input-file input (lambda (input) (dump-port input port)))))))) (let ((nar (string-append %main-substitute-directory "/example.nar"))) (compress nar (string-append nar ".gz") 'gzip) (compress nar (string-append nar ".lz") 'lzip)) (parameterize ((substitute-urls (list (string-append "file://" %main-substitute-directory)))) (request-substitution (string-append (%store-prefix) "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo") "substitute-retrieved")) (call-with-input-file "substitute-retrieved" get-string-all)) (lambda () (false-if-exception (delete-file "substitute-retrieved"))))))) (test-end "substitute") ;;; Local Variables: ;;; eval: (put 'with-narinfo 'scheme-indent-function 1) ;;; eval: (put 'with-narinfo* 'scheme-indent-function 2) ;;; eval: (put 'test-quit 'scheme-indent-function 2) ;;; End: