syscalls.scm 21 KB

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