syscalls.scm 18 KB

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