Update NEWS.
[guix.git] / tests / syscalls.scm
blob3e267c9f011c71bea886284d2f420aaffbaf588f
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2015 David Thompson <davet@gnu.org>
4 ;;;
5 ;;; This file is part of GNU Guix.
6 ;;;
7 ;;; GNU Guix is free software; you can redistribute it and/or modify it
8 ;;; under the terms of the GNU General Public License as published by
9 ;;; the Free Software Foundation; either version 3 of the License, or (at
10 ;;; your option) any later version.
11 ;;;
12 ;;; GNU Guix is distributed in the hope that it will be useful, but
13 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ;;; GNU General Public License for more details.
16 ;;;
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
20 (define-module (test-syscalls)
21   #:use-module (guix utils)
22   #:use-module (guix build syscalls)
23   #:use-module (gnu build linux-container)
24   #:use-module (srfi srfi-1)
25   #:use-module (srfi srfi-26)
26   #:use-module (srfi srfi-64)
27   #:use-module (system foreign)
28   #:use-module ((ice-9 ftw) #:select (scandir))
29   #:use-module (ice-9 match))
31 ;; Test the (guix build syscalls) module, although there's not much that can
32 ;; actually be tested without being root.
34 (define temp-file
35   (string-append "t-utils-" (number->string (getpid))))
38 (test-begin "syscalls")
40 (test-equal "mount, ENOENT"
41   ENOENT
42   (catch 'system-error
43     (lambda ()
44       (mount "/dev/null" "/does-not-exist" "ext2")
45       #f)
46     (compose system-error-errno list)))
48 (test-assert "umount, ENOENT/EPERM"
49   (catch 'system-error
50     (lambda ()
51       (umount "/does-not-exist")
52       #f)
53     (lambda args
54       ;; Both return values have been encountered in the wild.
55       (memv (system-error-errno args) (list EPERM ENOENT)))))
57 (test-assert "mount-points"
58   ;; Reportedly "/" is not always listed as a mount point, so check a few
59   ;; others (see <http://bugs.gnu.org/20261>.)
60   (any (cute member <> (mount-points))
61        '("/" "/proc" "/sys" "/dev")))
63 (false-if-exception (delete-file temp-file))
64 (test-equal "utime with AT_SYMLINK_NOFOLLOW"
65   '(0 0)
66   (begin
67     ;; Test libguile's utime with AT_SYMLINK_NOFOLLOW, which libguile does not
68     ;; define as of Guile 2.2.4.
69     (symlink "/nowhere" temp-file)
70     (utime temp-file 0 0 0 0 AT_SYMLINK_NOFOLLOW)
71     (let ((st (lstat temp-file)))
72       (delete-file temp-file)
73       ;; Note: 'utimensat' does not change 'ctime'.
74       (list (stat:mtime st) (stat:atime st)))))
76 (test-assert "swapon, ENOENT/EPERM"
77   (catch 'system-error
78     (lambda ()
79       (swapon "/does-not-exist")
80       #f)
81     (lambda args
82       (memv (system-error-errno args) (list EPERM ENOENT)))))
84 (test-assert "swapoff, ENOENT/EINVAL/EPERM"
85   (catch 'system-error
86     (lambda ()
87       (swapoff "/does-not-exist")
88       #f)
89     (lambda args
90       (memv (system-error-errno args) (list EPERM EINVAL ENOENT)))))
92 (test-assert "mkdtemp!"
93   (let* ((tmp (or (getenv "TMPDIR") "/tmp"))
94          (dir (mkdtemp! (string-append tmp "/guix-test-XXXXXX"))))
95     (and (file-exists? dir)
96          (begin
97            (rmdir dir)
98            #t))))
100 (test-equal "statfs, ENOENT"
101   ENOENT
102   (catch 'system-error
103     (lambda ()
104       (statfs "/does-not-exist"))
105     (compose system-error-errno list)))
107 (test-assert "statfs"
108   (let ((fs (statfs "/")))
109     (and (file-system? fs)
110          (> (file-system-block-size fs) 0)
111          (>= (file-system-blocks-available fs) 0)
112          (>= (file-system-blocks-free fs)
113              (file-system-blocks-available fs)))))
115 (define (user-namespace pid)
116   (string-append "/proc/" (number->string pid) "/ns/user"))
118 (define perform-container-tests?
119   (and (user-namespace-supported?)
120        (unprivileged-user-namespace-supported?)))
122 (unless perform-container-tests?
123   (test-skip 1))
124 (test-assert "clone"
125   (match (clone (logior CLONE_NEWUSER SIGCHLD))
126     (0 (primitive-exit 42))
127     (pid
128      ;; Check if user namespaces are different.
129      (and (not (equal? (readlink (user-namespace pid))
130                        (readlink (user-namespace (getpid)))))
131           (match (waitpid pid)
132             ((_ . status)
133              (= 42 (status:exit-val status))))))))
135 (unless perform-container-tests?
136   (test-skip 1))
137 (test-assert "setns"
138   (match (clone (logior CLONE_NEWUSER SIGCHLD))
139     (0 (primitive-exit 0))
140     (clone-pid
141      (match (pipe)
142        ((in . out)
143         (match (primitive-fork)
144           (0
145            (close in)
146            ;; Join the user namespace.
147            (call-with-input-file (user-namespace clone-pid)
148              (lambda (port)
149                (setns (port->fdes port) 0)))
150            (write 'done out)
151            (close out)
152            (primitive-exit 0))
153           (fork-pid
154            (close out)
155            ;; Wait for the child process to join the namespace.
156            (read in)
157            (let ((result (and (equal? (readlink (user-namespace clone-pid))
158                                       (readlink (user-namespace fork-pid))))))
159              ;; Clean up.
160              (waitpid clone-pid)
161              (waitpid fork-pid)
162              result))))))))
164 ;; XXX: Skip this test when running Linux > 4.7.5 to work around
165 ;; <https://bugzilla.kernel.org/show_bug.cgi?id=183461>.
166 (when (or (not perform-container-tests?)
167           (version>? (utsname:release (uname)) "4.7.5")
169           ;; Skip on Ubuntu's 4.4 kernels, which contain a backport of the
170           ;; faulty code: <https://bugs.gnu.org/25476>.
171           (member (utsname:release (uname))
172                   '("4.4.0-21-generic" "4.4.0-59-generic"
173                     "4.4.0-116-generic")))
174   (test-skip 1))
175 (test-equal "pivot-root"
176   #t
177   (match (pipe)
178     ((in . out)
179      (match (clone (logior CLONE_NEWUSER CLONE_NEWNS SIGCHLD))
180        (0
181         (dynamic-wind
182           (const #t)
183           (lambda ()
184             (close in)
185             (call-with-temporary-directory
186              (lambda (root)
187                (let ((put-old (string-append root "/real-root")))
188                  (mount "none" root "tmpfs")
189                  (mkdir put-old)
190                  (call-with-output-file (string-append root "/test")
191                    (lambda (port)
192                      (display "testing\n" port)))
193                  (pivot-root root put-old)
194                  ;; The test file should now be located inside the root directory.
195                  (write (file-exists? "/test") out)
196                  (close out)))))
197           (lambda ()
198             (primitive-exit 0))))
199        (pid
200         (close out)
201         (let ((result (read in)))
202           (close in)
203           (and (zero? (match (waitpid pid)
204                         ((_ . status)
205                          (status:exit-val status))))
206                (eq? #t result))))))))
208 (test-equal "scandir*, ENOENT"
209   ENOENT
210   (catch 'system-error
211     (lambda ()
212       (scandir* "/does/not/exist"))
213     (lambda args
214       (system-error-errno args))))
216 (test-equal "scandir*, ASCII file names"
217   (scandir (dirname (search-path %load-path "guix/base32.scm"))
218            (const #t) string<?)
219   (match (scandir* (dirname (search-path %load-path "guix/base32.scm")))
220     (((names . properties) ...)
221      names)))
223 (test-equal "scandir*, UTF-8 file names"
224   '("." ".." "α" "λ")
225   (call-with-temporary-directory
226    (lambda (directory)
227      ;; Wrap 'creat' to make sure that we really pass a UTF-8-encoded file
228      ;; name to the system call.
229      (let ((creat (pointer->procedure int
230                                       (dynamic-func "creat" (dynamic-link))
231                                       (list '* int))))
232        (creat (string->pointer (string-append directory "/α")
233                                "UTF-8")
234               #o644)
235        (creat (string->pointer (string-append directory "/λ")
236                                "UTF-8")
237               #o644)
238        (let ((locale (setlocale LC_ALL)))
239          (dynamic-wind
240            (lambda ()
241              ;; Make sure that even in a C locale we get the right result.
242              (setlocale LC_ALL "C"))
243            (lambda ()
244              (match (scandir* directory)
245                (((names . properties) ...)
246                 names)))
247            (lambda ()
248              (setlocale LC_ALL locale))))))))
250 (test-assert "scandir*, properties"
251   (let ((directory (dirname (search-path %load-path "guix/base32.scm"))))
252     (every (lambda (entry name)
253              (match entry
254                ((name2 . properties)
255                 (and (string=? name2 name)
256                      (let* ((full  (string-append directory "/" name))
257                             (stat  (lstat full))
258                             (inode (assoc-ref properties 'inode))
259                             (type  (assoc-ref properties 'type)))
260                        (and (= inode (stat:ino stat))
261                             (or (eq? type 'unknown)
262                                 (eq? type (stat:type stat)))))))))
263            (scandir* directory)
264            (scandir directory (const #t) string<?))))
266 (false-if-exception (delete-file temp-file))
267 (test-equal "fcntl-flock wait"
268   42                                              ; the child's exit status
269   (let ((file (open-file temp-file "w0b")))
270     ;; Acquire an exclusive lock.
271     (fcntl-flock file 'write-lock)
272     (match (primitive-fork)
273       (0
274        (dynamic-wind
275          (const #t)
276          (lambda ()
277            ;; Reopen FILE read-only so we can have a read lock.
278            (let ((file (open-file temp-file "r0b")))
279              ;; Wait until we can acquire the lock.
280              (fcntl-flock file 'read-lock)
281              (primitive-exit (read file)))
282            (primitive-exit 1))
283          (lambda ()
284            (primitive-exit 2))))
285       (pid
286        ;; Write garbage and wait.
287        (display "hello, world!"  file)
288        (force-output file)
289        (sleep 1)
291        ;; Write the real answer.
292        (seek file 0 SEEK_SET)
293        (truncate-file file 0)
294        (write 42 file)
295        (force-output file)
297        ;; Unlock, which should let the child continue.
298        (fcntl-flock file 'unlock)
300        (match (waitpid pid)
301          ((_  . status)
302           (let ((result (status:exit-val status)))
303             (close-port file)
304             result)))))))
306 (test-equal "fcntl-flock non-blocking"
307   EAGAIN                                          ; the child's exit status
308   (match (pipe)
309     ((input . output)
310      (match (primitive-fork)
311        (0
312         (dynamic-wind
313           (const #t)
314           (lambda ()
315             (close-port output)
317             ;; Wait for the green light.
318             (read-char input)
320             ;; Open FILE read-only so we can have a read lock.
321             (let ((file (open-file temp-file "w0")))
322               (catch 'flock-error
323                 (lambda ()
324                   ;; This attempt should throw EAGAIN.
325                   (fcntl-flock file 'write-lock #:wait? #f))
326                 (lambda (key errno)
327                   (primitive-exit (pk 'errno errno)))))
328             (primitive-exit -1))
329           (lambda ()
330             (primitive-exit -2))))
331        (pid
332         (close-port input)
333         (let ((file (open-file temp-file "w0")))
334           ;; Acquire an exclusive lock.
335           (fcntl-flock file 'write-lock)
337           ;; Tell the child to continue.
338           (write 'green-light output)
339           (force-output output)
341           (match (waitpid pid)
342             ((_  . status)
343              (let ((result (status:exit-val status)))
344                (fcntl-flock file 'unlock)
345                (close-port file)
346                result)))))))))
348 (test-equal "set-thread-name"
349   "Syscall Test"
350   (let ((name (thread-name)))
351     (set-thread-name "Syscall Test")
352     (let ((new-name (thread-name)))
353       (set-thread-name name)
354       new-name)))
356 (test-assert "all-network-interface-names"
357   (match (all-network-interface-names)
358     (((? string? names) ..1)
359      (member "lo" names))))
361 (test-assert "network-interface-names"
362   (match (network-interface-names)
363     (((? string? names) ..1)
364      (lset<= string=? names (all-network-interface-names)))))
366 (test-assert "network-interface-flags"
367   (let* ((sock  (socket AF_INET SOCK_STREAM 0))
368          (flags (network-interface-flags sock "lo")))
369     (close-port sock)
370     (and (not (zero? (logand flags IFF_LOOPBACK)))
371          (not (zero? (logand flags IFF_UP))))))
373 (test-equal "loopback-network-interface?"
374   ENODEV
375   (and (loopback-network-interface? "lo")
376        (catch 'system-error
377          (lambda ()
378            (loopback-network-interface? "nonexistent")
379            #f)
380          (lambda args
381            (system-error-errno args)))))
383 (test-equal "loopback-network-interface-running?"
384   ENODEV
385   (and (network-interface-running? "lo")
386        (catch 'system-error
387          (lambda ()
388            (network-interface-running? "nonexistent")
389            #f)
390          (lambda args
391            (system-error-errno args)))))
393 (test-skip (if (zero? (getuid)) 1 0))
394 (test-assert "set-network-interface-flags"
395   (let ((sock (socket AF_INET SOCK_STREAM 0)))
396     (catch 'system-error
397       (lambda ()
398         (set-network-interface-flags sock "lo" IFF_UP))
399       (lambda args
400         (close-port sock)
401         ;; We get EPERM with Linux 3.18ish and EACCES with 2.6.32.
402         (memv (system-error-errno args) (list EPERM EACCES))))))
404 (test-equal "network-interface-address lo"
405   (make-socket-address AF_INET (inet-pton AF_INET "127.0.0.1") 0)
406   (let* ((sock (socket AF_INET SOCK_STREAM 0))
407          (addr (network-interface-address sock "lo")))
408     (close-port sock)
409     addr))
411 (test-skip (if (zero? (getuid)) 1 0))
412 (test-assert "set-network-interface-address"
413   (let ((sock (socket AF_INET SOCK_STREAM 0)))
414     (catch 'system-error
415       (lambda ()
416         (set-network-interface-address sock "nonexistent"
417                                        (make-socket-address
418                                         AF_INET
419                                         (inet-pton AF_INET "127.12.14.15")
420                                         0)))
421       (lambda args
422         (close-port sock)
423         ;; We get EPERM with Linux 3.18ish and EACCES with 2.6.32.
424         (memv (system-error-errno args) (list EPERM EACCES))))))
426 (test-equal "network-interface-netmask lo"
427   (make-socket-address AF_INET (inet-pton AF_INET "255.0.0.0") 0)
428   (let* ((sock (socket AF_INET SOCK_STREAM 0))
429          (addr (network-interface-netmask sock "lo")))
430     (close-port sock)
431     addr))
433 (test-skip (if (zero? (getuid)) 1 0))
434 (test-assert "set-network-interface-netmask"
435   (let ((sock (socket AF_INET SOCK_STREAM 0)))
436     (catch 'system-error
437       (lambda ()
438         (set-network-interface-netmask sock "nonexistent"
439                                        (make-socket-address
440                                         AF_INET
441                                         (inet-pton AF_INET "255.0.0.0")
442                                         0)))
443       (lambda args
444         (close-port sock)
445         (memv (system-error-errno args) (list EPERM EACCES))))))
447 (test-equal "network-interfaces returns one or more interfaces"
448   '(#t #t #t)
449   (match (network-interfaces)
450     ((interfaces ..1)
451      (list (every interface? interfaces)
452            (every string? (map interface-name interfaces))
453            (every (lambda (sockaddr)
454                     ;; Sometimes interfaces have no associated address.
455                     (or (vector? sockaddr)
456                         (not sockaddr)))
457                   (map interface-address interfaces))))))
459 (test-equal "network-interfaces returns \"lo\""
460   (list #t (make-socket-address AF_INET (inet-pton AF_INET "127.0.0.1") 0))
461   (match (filter (lambda (interface)
462                    (string=? "lo" (interface-name interface)))
463                  (network-interfaces))
464     ((loopbacks ..1)
465      (list (every (lambda (lo)
466                     (not (zero? (logand IFF_LOOPBACK (interface-flags lo)))))
467                   loopbacks)
468            (match (find (lambda (lo)
469                           (= AF_INET (sockaddr:fam (interface-address lo))))
470                         loopbacks)
471              (#f #f)
472              (lo (interface-address lo)))))))
474 (test-skip (if (zero? (getuid)) 1 0))
475 (test-assert "add-network-route/gateway"
476   (let ((sock    (socket AF_INET SOCK_STREAM 0))
477         (gateway (make-socket-address AF_INET
478                                       (inet-pton AF_INET "192.168.0.1")
479                                       0)))
480     (catch 'system-error
481       (lambda ()
482         (add-network-route/gateway sock gateway))
483       (lambda args
484         (close-port sock)
485         (memv (system-error-errno args) (list EPERM EACCES))))))
487 (test-skip (if (zero? (getuid)) 1 0))
488 (test-assert "delete-network-route"
489   (let ((sock        (socket AF_INET SOCK_STREAM 0))
490         (destination (make-socket-address AF_INET INADDR_ANY 0)))
491     (catch 'system-error
492       (lambda ()
493         (delete-network-route sock destination))
494       (lambda args
495         (close-port sock)
496         (memv (system-error-errno args) (list EPERM EACCES))))))
498 (test-equal "tcgetattr ENOTTY"
499   ENOTTY
500   (catch 'system-error
501     (lambda ()
502       (call-with-input-file "/dev/null"
503         (lambda (port)
504           (tcgetattr (fileno port)))))
505     (compose system-error-errno list)))
507 (test-skip (if (and (file-exists? "/proc/self/fd/0")
508                     (string-prefix? "/dev/pts/" (readlink "/proc/self/fd/0")))
509                0
510                2))
512 (test-assert "tcgetattr"
513   (let ((termios (tcgetattr 0)))
514     (and (termios? termios)
515          (> (termios-input-speed termios) 0)
516          (> (termios-output-speed termios) 0))))
518 (test-assert "tcsetattr"
519   (let ((first (tcgetattr 0)))
520     (tcsetattr 0 (tcsetattr-action TCSANOW) first)
521     (equal? first (tcgetattr 0))))
523 (test-assert "terminal-window-size ENOTTY"
524   (call-with-input-file "/dev/null"
525     (lambda (port)
526       (catch 'system-error
527         (lambda ()
528           (terminal-window-size port))
529         (lambda args
530           ;; Accept EINVAL, which some old Linux versions might return.
531           (memv (system-error-errno args)
532                 (list ENOTTY EINVAL)))))))
534 (test-assert "terminal-columns"
535   (> (terminal-columns) 0))
537 (test-assert "terminal-columns non-file port"
538   (> (terminal-columns (open-input-string "Join us now, share the software!"))
539      0))
541 (test-assert "utmpx-entries"
542   (match (utmpx-entries)
543     (((? utmpx? entries) ...)
544      (every (lambda (entry)
545               (match (utmpx-user entry)
546                 ((? string?)
547                  ;; Ensure we have a valid PID for those entries where it
548                  ;; makes sense.
549                  (or (not (memv (utmpx-login-type entry)
550                                 (list (login-type INIT_PROCESS)
551                                       (login-type LOGIN_PROCESS)
552                                       (login-type USER_PROCESS))))
553                      (> (utmpx-pid entry) 0)))
554                 (#f                               ;might be DEAD_PROCESS
555                  #t)))
556             entries))))
558 (test-assert "read-utmpx, EOF"
559   (eof-object? (read-utmpx (%make-void-port "r"))))
561 (unless (access? "/var/run/utmpx" O_RDONLY)
562   (test-skip 1))
563 (test-assert "read-utmpx"
564   (let ((result (call-with-input-file "/var/run/utmpx" read-utmpx)))
565     (or (utmpx? result) (eof-object? result))))
567 (test-end)
569 (false-if-exception (delete-file temp-file))