syscalls.scm 19 KB

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