~ruther/guix-local

c06f6db7a424fd47e3cd2625dbfda2367316f3bd — Ludovic Courtès 9 years ago 4c14d4e
container: Gracefully report mount errors in the child process.

Fixes <http://bugs.gnu.org/23306>.

* gnu/build/linux-container.scm (run-container): Use 'socketpair'
instead of 'pipe'.  Rename 'in' to 'child' and 'out' to 'parent'.  Send
a 'ready message or an exception argument list from the child to the
parent; adjust the parent accordingly.
* tests/containers.scm ("call-with-container, mnt namespace, wrong bind
mount"): New test.
* tests/guix-environment-container.sh: Add test with
--expose=/does-not-exist.
3 files changed, 52 insertions(+), 12 deletions(-)

M gnu/build/linux-container.scm
M tests/containers.scm
M tests/guix-environment-container.sh
M gnu/build/linux-container.scm => gnu/build/linux-container.scm +30 -12
@@ 205,35 205,53 @@ host user identifiers to map into the user namespace."
  ;; The parent process must initialize the user namespace for the child
  ;; before it can boot.  To negotiate this, a pipe is used such that the
  ;; child process blocks until the parent writes to it.
  (match (pipe)
    ((in . out)
  (match (socketpair PF_UNIX SOCK_STREAM 0)
    ((child . parent)
     (let ((flags (namespaces->bit-mask namespaces)))
       (match (clone flags)
         (0
          (call-with-clean-exit
           (lambda ()
             (close out)
             (close-port parent)
             ;; Wait for parent to set things up.
             (match (read in)
             (match (read child)
               ('ready
                (close in)
                (purify-environment)
                (when (memq 'mnt namespaces)
                  (mount-file-systems root mounts
                                      #:mount-/proc? (memq 'pid namespaces)
                                      #:mount-/sys?  (memq 'net namespaces)))
                  (catch #t
                    (lambda ()
                      (mount-file-systems root mounts
                                          #:mount-/proc? (memq 'pid namespaces)
                                          #:mount-/sys?  (memq 'net
                                                               namespaces)))
                    (lambda args
                      ;; Forward the exception to the parent process.
                      (write args child)
                      (primitive-exit 3))))
                ;; TODO: Manage capabilities.
                (write 'ready child)
                (close-port child)
                (thunk))
               (_                                 ;parent died or something
                (primitive-exit 2))))))
         (pid
          (close-port child)
          (when (memq 'user namespaces)
            (initialize-user-namespace pid host-uids))
          ;; TODO: Initialize cgroups.
          (close in)
          (write 'ready out)
          (close out)
          pid))))))
          (write 'ready parent)
          (newline parent)

          ;; Check whether the child process' setup phase succeeded.
          (let ((message (read parent)))
            (close-port parent)
            (match message
              ('ready                             ;success
               pid)
              (((? symbol? key) args ...)         ;exception
               (apply throw key args))
              (_                                  ;unexpected termination
               #f)))))))))

(define* (call-with-container mounts thunk #:key (namespaces %namespaces)
                              (host-uids 1))

M tests/containers.scm => tests/containers.scm +12 -0
@@ 79,6 79,18 @@
       (assert-exit (file-exists? "/testing")))
     #:namespaces '(user mnt))))

(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 '(("/does-not-exist" device "/foo"
                              "none" (bind-mount) #f #f))
        (const #t)
        #:namespaces '(user mnt)))
    (lambda args
      (list 'system-error (system-error-errno args)))))

(test-assert "call-with-container, all namespaces"
  (zero?
   (call-with-container '()

M tests/guix-environment-container.sh => tests/guix-environment-container.sh +10 -0
@@ 44,6 44,16 @@ else
    test $? = 42
fi

# Make sure file-not-found errors in mounts are reported.
if guix environment --container --ad-hoc --bootstrap guile-bootstrap \
	--expose=/does-not-exist -- guile -c 1 2> "$tmpdir/error"
then
    false
else
    grep "/does-not-exist" "$tmpdir/error"
    grep "[Nn]o such file" "$tmpdir/error"
fi

# Make sure that the right directories are mapped.
mount_test_code="
(use-modules (ice-9 rdelim)