aboutsummaryrefslogtreecommitdiff
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2015 David Thompson <davet@gnu.org>
;;; Copyright © 2016, 2017, 2019, 2023 Ludovic Courtès <ludo@gnu.org>
;;;
;;; 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-containers)
  #:use-module (guix utils)
  #:use-module (guix build syscalls)
  #:use-module (gnu build linux-container)
  #:use-module ((gnu system linux-container)
                #:select (eval/container))
  #:use-module (gnu system file-systems)
  #:use-module (guix store)
  #:use-module (guix monads)
  #:use-module (guix gexp)
  #:use-module (guix derivations)
  #:use-module (guix tests)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-64)
  #:use-module (ice-9 match)
  #:use-module ((ice-9 ftw) #:select (scandir)))

(define (assert-exit x)
  (primitive-exit (if x 0 1)))

(test-begin "containers")

;; Skip these tests unless user namespaces are available and the setgroups
;; file (introduced in Linux 3.19 to address a security issue) exists.
(define (skip-if-unsupported)
  (unless (and (user-namespace-supported?)
               (unprivileged-user-namespace-supported?)
               (setgroups-supported?))
    (test-skip 1)))

(skip-if-unsupported)
(test-assert "call-with-container, exit with 0 when there is no error"
  (zero?
   (call-with-container '() (const #t) #:namespaces '(user))))

(skip-if-unsupported)
(test-assert "call-with-container, user namespace"
  (zero?
   (call-with-container '()
     (lambda ()
       ;; The user is root within the new user namespace.
       (assert-exit (and (zero? (getuid)) (zero? (getgid)))))
     #:namespaces '(user))))

(skip-if-unsupported)
(test-assert "call-with-container, user namespace, guest UID/GID"
  (zero?
   (call-with-container '()
     (lambda ()
       (assert-exit (and (= 42 (getuid)) (= 77 (getgid)))))
     #:guest-uid 42
     #:guest-gid 77
     #:namespaces '(user))))

(skip-if-unsupported)
(test-assert "call-with-container, uts namespace"
  (zero?
   (call-with-container '()
     (lambda ()
       ;; The user is root within the container and should be able to change
       ;; the hostname of that container.
       (sethostname "test-container")
       (primitive-exit 0))
     #:namespaces '(user uts))))

(skip-if-unsupported)
(test-assert "call-with-container, pid namespace"
  (zero?
   (call-with-container '()
     (lambda ()
       (match (primitive-fork)
         (0
          ;; The first forked process in the new pid namespace is pid 2.
          (assert-exit (= 2 (getpid))))
         (pid
          (primitive-exit
           (match (waitpid pid)
             ((_ . status)
              (status:exit-val status)))))))
     #:namespaces '(user pid))))

(skip-if-unsupported)
(test-assert "call-with-container, mnt namespace"
  (zero?
   (call-with-container (list (file-system
                                (device "none")
                                (mount-point "/testing")
                                (type "tmpfs")
                                (check? #f)))
     (lambda ()
       (assert-exit (file-exists? "/testing")))
     #:namespaces '(user mnt))))

(skip-if-unsupported)
(test-equal "call-with-container, mnt namespace, wrong bind mount"
  `(system-error ,ENOENT)
  ;; An exception should be raised; see <http://bugs.gnu.org/23306>.
  (catch 'system-error
    (lambda ()
      (call-with-container (list (file-system
                                   (device "/does-not-exist")
                                   (mount-point "/foo")
                                   (type "none")
                                   (flags '(bind-mount))
                                   (check? #f)))
        (const #t)
        #:namespaces '(user mnt)))
    (lambda args
      (list 'system-error (system-error-errno args)))))

(skip-if-unsupported)
(test-assert "call-with-container, all namespaces"
  (zero?
   (call-with-container '()
     (lambda ()
       (primitive-exit 0)))))

(skip-if-unsupported)
(test-assert "call-with-container, mnt namespace, root permissions"
  (zero?
   (call-with-container '()
     (lambda ()
       (assert-exit (= #o755 (stat:perms (lstat "/")))))
     #:namespaces '(user mnt))))

(skip-if-unsupported)
(test-assert "container-excursion"
  (call-with-temporary-directory
   (lambda (root)
     ;; Two pipes: One for the container to signal that the test can begin,
     ;; and one for the parent to signal to the container that the test is
     ;; over.
     (match (list (pipe) (pipe))
       (((start-in . start-out) (end-in . end-out))
        (define (container)
          (close end-out)
          (close start-in)
          ;; Signal for the test to start.
          (write 'ready start-out)
          (close start-out)
          ;; Wait for test completion.
          (read end-in)
          (close end-in))

        (define (namespaces pid)
          (let ((pid (number->string pid)))
            (map (lambda (ns)
                   (readlink (string-append "/proc/" pid "/ns/" ns)))
                 '("user" "ipc" "uts" "net" "pid" "mnt"))))

        (let* ((pid (run-container root '() %namespaces 1 container))
               (container-namespaces (namespaces pid))
               (result
                (begin
                  (close start-out)
                  ;; Wait for container to be ready.
                  (read start-in)
                  (close start-in)
                  (container-excursion pid
                    (lambda ()
                      ;; Check that all of the namespace identifiers are
                      ;; the same as the container process.
                      (assert-exit
                       (equal? container-namespaces
                               (namespaces (getpid)))))))))
          (close end-in)
          ;; Stop the container.
          (write 'done end-out)
          (close end-out)
          (waitpid pid)
          (zero? result)))))))

(skip-if-unsupported)
(test-equal "container-excursion, same namespaces"
  42
  ;; The parent and child are in the same namespaces.  'container-excursion'
  ;; should notice that and avoid calling 'setns' since that would fail.
  (status:exit-val
   (container-excursion (getpid)
     (lambda ()
       (primitive-exit 42)))))

(skip-if-unsupported)
(test-assert "container-excursion*"
  (call-with-temporary-directory
   (lambda (root)
     (define (namespaces pid)
       (let ((pid (number->string pid)))
         (map (lambda (ns)
                (readlink (string-append "/proc/" pid "/ns/" ns)))
              '("user" "ipc" "uts" "net" "pid" "mnt"))))

     (let* ((pid    (run-container root '()
                                   %namespaces 1
                                   (lambda ()
                                     (sleep 100))))
            (expected (namespaces pid))
            (result (container-excursion* pid
                      (lambda ()
                        (namespaces 1)))))
       (kill pid SIGKILL)
       (equal? result expected)))))

(skip-if-unsupported)
(test-equal "container-excursion*, same namespaces"
  42
  (container-excursion* (getpid)
    (lambda ()
      (* 6 7))))

(skip-if-unsupported)
(test-equal "container-excursion*, /proc"
  '("1" "2")
  (call-with-temporary-directory
   (lambda (root)
     (let* ((pid    (run-container root '()
                                   %namespaces 1
                                   (lambda ()
                                     (sleep 100))))
            (result (container-excursion* pid
                      (lambda ()
                        ;; We expect to see exactly two processes in this
                        ;; namespace.
                        (scandir "/proc"
                                 (lambda (file)
                                   (char-set-contains?
                                    char-set:digit
                                    (string-ref file 0))))))))
       (kill pid SIGKILL)
       result))))

(skip-if-unsupported)
(test-equal "eval/container, exit status"
  42
  (let* ((store  (open-connection-for-tests))
         (status (run-with-store store
                   (eval/container #~(exit 42)))))
    (close-connection store)
    (status:exit-val status)))

(skip-if-unsupported)
(test-assert "eval/container, writable user mapping"
  (call-with-temporary-directory
   (lambda (directory)
     (define store
       (open-connection-for-tests))
     (define result
       (string-append directory "/r"))
     (define requisites*
       (store-lift requisites))

     (call-with-output-file result (const #t))
     (run-with-store store
       (mlet %store-monad ((status (eval/container
                                    #~(begin
                                        (use-modules (ice-9 ftw))
                                        (call-with-output-file "/result"
                                          (lambda (port)
                                            (write (scandir #$(%store-prefix))
                                                   port))))
                                    #:mappings
                                    (list (file-system-mapping
                                           (source result)
                                           (target "/result")
                                           (writable? #t)))))
                           (reqs   (requisites*
                                    (list (derivation->output-path
                                           (%guile-for-build))))))
         (close-connection store)
         (return (and (zero? (pk 'status status))
                      (lset= string=? (cons* "." ".." (map basename reqs))
                             (pk (call-with-input-file result read))))))))))

(skip-if-unsupported)
(test-assert "eval/container, non-empty load path"
  (call-with-temporary-directory
   (lambda (directory)
     (define store
       (open-connection-for-tests))
     (define result
       (string-append directory "/r"))
     (define requisites*
       (store-lift requisites))

     (mkdir result)
     (run-with-store store
       (mlet %store-monad ((status (eval/container
                                    (with-imported-modules '((guix build utils))
                                      #~(begin
                                          (use-modules (guix build utils))
                                          (mkdir-p "/result/a/b/c")))
                                    #:mappings
                                    (list (file-system-mapping
                                           (source result)
                                           (target "/result")
                                           (writable? #t))))))
         (close-connection store)
         (return (and (zero? status)
                      (file-is-directory?
                       (string-append result "/a/b/c")))))))))

(test-end)
ools, xsensors, perf, mdadm, libaio, module-init-tools) [arguments]: Likewise. * gnu/packages/lisp.scm (ccl)[arguments]: Likewise. * gnu/packages/lout.scm (lout)[arguments]: Likewise. * gnu/packages/lua.scm (luajit)[arguments]: Likewise. * gnu/packages/machine-learning.scm (shogun)[arguments]: Likewise. * gnu/packages/mail.scm (exim)[arguments]: Likewise. * gnu/packages/man.scm (man-pages, txt2man)[arguments]: Likewise. * gnu/packages/maths.scm (lapack, superlu-dist, openlibm, openspecfun) [arguments]: Likewise. * gnu/packages/messaging.scm (bitlbee)[arguments]: Likewise. * gnu/packages/mp3.scm (libmad, id3lib, mp3info)[arguments]: Likewise. * gnu/packages/music.scm (solfege)[arguments]: Likewise. * gnu/packages/noweb.scm (noweb)[arguments]: Likewise. * gnu/packages/patchutils.scm (patchutils, quilt, colordiff)[arguments]: Likewise. * gnu/packages/pdf.scm (xpdf, zathura-cb, zathura-ps, zathura-djvu, zathura-pdf-poppler, zathura, podofo, fbida)[arguments]: Likewise. * gnu/packages/perl.scm (perl-file-list, perl-test-harness)[arguments]: Likewise. * gnu/packages/photo.scm (gphoto2)[arguments]: Likewise. * gnu/packages/popt.scm (popt)[arguments]: Likewise. * gnu/packages/pretty-print.scm (source-highlight, astyle)[arguments]: Likewise. * gnu/packages/pumpio.scm (pumpa)[arguments]: Likewise. * gnu/packages/python.scm (python-passlib, python-pycrypto, python2-empy, python-sqlalchemy, python-docopt)[arguments]: Likewise. * gnu/packages/rdf.scm (lrdf)[arguments]: Likewise. * gnu/packages/regex.scm (tre)[arguments]: Likewise. * gnu/packages/rrdtool.scm (rrdtool)[arguments]: Likewise. * gnu/packages/ruby.scm (ruby-2.1, ruby-1.8)[arguments]: Likewise. * gnu/packages/sawfish.scm (sawfish)[arguments]: Likewise. * gnu/packages/scheme.scm (racket)[arguments]: Likewise. * gnu/packages/sdl.scm (guile-sdl)[arguments]: Likewise. * gnu/packages/serveez.scm (serveez)[arguments]: Likewise. * gnu/packages/skribilo.scm (skribilo)[arguments]: Likewise. * gnu/packages/smalltalk.scm (smalltalk)[arguments]: Likewise. * gnu/packages/suckless.scm (dmenu, slock)[arguments]: Likewise. * gnu/packages/tcl.scm (expect)[arguments]: Likewise. * gnu/packages/telephony.scm (commoncpp)[arguments]: Likewise. * gnu/packages/textutils.scm (recode, libgtextutils)[arguments]: Likewise. * gnu/packages/time.scm (time)[arguments]: Likewise. * gnu/packages/tor.scm (privoxy)[arguments]: Likewise. * gnu/packages/uucp.scm (uucp)[arguments]: Likewise. * gnu/packages/video.scm (libdvdnav-4)[arguments]: Likewise. * gnu/packages/web-browsers.scm (lynx)[arguments]: Likewise. * gnu/packages/web.scm (tinyproxy)[arguments]: Likewise. * gnu/packages/wicd.scm (wicd)[arguments]: Likewise. * gnu/packages/wm.scm (bspwm)[arguments]: Likewise. * gnu/packages/xdisorg.scm (sxhkd, xcape)[arguments]: Likewise. * gnu/packages/xfce.scm (xfconf)[arguments]: Likewise. * gnu/packages/xfig.scm (transfig)[arguments]: Likewise. * gnu/packages/xorg.scm (imake)[arguments]: Likewise. Kei Kebreau 2017-09-04gnu: Put autoconf-related phases immediately after the 'unpack phase....* gnu/packages/audio.scm (audacity, rtmidi)[arguments]: Correct phases accordingly. * gnu/packages/bioinformatics.scm (mash, seek, vsearch, emboss, htslib-for-sambamba)[arguments]: Likewise. * gnu/packages/ci.scm (cuirass)[arguments]: Likewise. * gnu/packages/compression.scm (minizip, xdelta)[arguments]: Likewise. * gnu/packages/cpp.scm (libzen)[arguments]: Likewise. * gnu/packages/crypto.scm (opendht)[arguments]: Likewise. * gnu/packages/databases.scm (4store, mdbtools)[arguments]: Likewise. * gnu/packages/debug.scm (stress-make)[arguments]: Likewise. * gnu/packages/dns.scm (dnscrypt-proxy, dnscrypt-wrapper)[arguments]: Likewise. * gnu/packages/emacs.scm (emacs-pdf-tools)[arguments]: Likewise. * gnu/packages/embedded.scm (libjaylink, openocd)[arguments]: Likewise. * gnu/packages/engineering.scm (gerbv)[arguments]: Likewise. * gnu/packages/erlang.scm (erlang)[arguments]: Likewise. * gnu/packages/ftp.scm (weex)[arguments]: Likewise. * gnu/packages/gnome.scm (dia)[arguments]: Likewise. * gnu/packages/gnunet.scm (gnurl, guile-gnunet)[arguments]: Likewise. * gnu/packages/gtk.scm (guile-rsvg, graphene)[arguments]: Likewise. * gnu/packages/guile.scm (guildhall, guile-ics, guile-sqlite3)[arguments]: Likewise. * gnu/packages/ibus.scm (ibus-libpinyin)[arguments]: Likewise. * gnu/packages/irc.scm (weechat)[arguments]: Likewise. * gnu/packages/java.scm (classpath-devel)[arguments]: Likewise. * gnu/packages/libreoffice.scm (libetonyek)[arguments]: Likewise. * gnu/packages/libusb.scm (hidapi)[arguments]: Likewise. * gnu/packages/linux.scm (gpm)[arguments]: Likewise. * gnu/packages/logging.scm (glog)[arguments]: Likewise. * gnu/packages/machine-learning.scm (ghmm)[arguments]: Likewise. * gnu/packages/mail.scm (dovecot-trees, dovecot-libsodium-plugin, esmtp) [arguments]: Likewise. * gnu/packages/messaging.scm (freetalk, libmesode, libstrophe)[arguments]: Likewise. * gnu/packages/microcom.scm (microcom)[arguments]: Likewise. * gnu/packages/ocaml.scm (ocaml-ssl)[arguments]: Likewise. * gnu/packages/parallel.scm (slurm)[arguments]: Likewise. * gnu/packages/pdf.scm (libharu)[arguments]: Likewise. * gnu/packages/samba.scm (cifs-utils)[arguments]: Likewise. * gnu/packages/serialization.scm (msgpack)[arguments]: Likewise. * gnu/packages/shells.scm (scsh)[arguments]: Likewise. * gnu/packages/telephony.scm (libiax2)[arguments]: Likewise. * gnu/packages/textutils.scm (dotconf)[arguments]: Likewise. * gnu/packages/version-control.scm (findnewest)[arguments]: Likewise. * gnu/packages/video.scm (libmediainfo, mediainfo)[arguments]: Likewise. Kei Kebreau 2017-08-17gnu: american-fuzzy-lop: Update to 2.49b....* gnu/packages/debug.scm (american-fuzzy-lop): Update to 2.49b. Efraim Flashner 2017-08-17gnu: american-fuzzy-lop: Add support for non-Intel architectures....* gnu/packages/debug.scm (americal-fuzzy-lop): Add qemu target for non-Intel targets. [arguments]: On non-Intel systems, add phase to disable non-Intel architecture warning and remove incompatible binaries. Efraim Flashner 2017-08-09gnu: stress-make: Patch default shell....* gnu/packages/debug.scm (stress-make)[arguments]: Add 'set-default-shell phase. Eric Bavier 2017-07-15gnu: Rename (gnu packages qemu) to (gnu packages virtualization)....* gnu/packages/qemu.scm: Rename this ... * gnu/packages/virtualization.scm: ... to this. * gnu/local.mk (GNU_SYSTEM_MODULES), gnu/packages/bootloaders.scm, gnu/packages/debug.scm, gnu/packages/gnome.scm, gnu/system/vm.scm, gnu/tests/install.scm: Adjust accordingly. 宋文武