diff --git a/parenting-parent.el b/parenting-parent.el index 30e4590..77e2164 100644 --- a/parenting-parent.el +++ b/parenting-parent.el @@ -205,10 +205,11 @@ The Nix store, when present, is bound separately." ("LANG" . "C.UTF-8")) "Environment set inside every `:sandbox' jail before pass-through. An alist of (NAME . VALUE). The jail starts from an empty -environment (bwrap --clearenv); these are set with --setenv, then -HOME is pointed at the jail's tmpfs, and finally the caller's -`:environment' pass-through names are copied in from this Emacs's -environment. Rebind or extend this to change the baseline." +environment (bwrap --clearenv); HOME is set to a directory created +per child under the read-write socket-directory bind, these are set +with --setenv, and finally the caller's `:environment' pass-through +names are copied in from this Emacs's environment. Rebind or extend +this to change the baseline." :type '(alist :key-type string :value-type string) :group 'parenting) @@ -243,7 +244,9 @@ SPEC keywords, all optional: The returned list is suitable as a `:command-wrapper' prefix: bwrap, a minimal read-only root, a private /tmp, /proc and /dev, the socket -and library binds, a cleared environment seeded from +and library binds, a HOME directory created per child under the +read-write socket-directory bind (removed with the socket directory +at shutdown), a cleared environment seeded from `parenting-sandbox-default-environment' plus the pass-through names, and network sharing only when asked." (let* ((home (expand-file-name "home" socket-directory)) @@ -275,8 +278,16 @@ and network sharing only when asked." ;; Extra caller binds. (parenting--sandbox-bind-args "--ro-bind" ro-binds) (parenting--sandbox-bind-args "--bind" rw-binds) - ;; A cleared environment: baseline, then a writable HOME on the - ;; tmpfs, then the caller's pass-through names. + ;; HOME: a directory bwrap creates in the jail under the read-write + ;; socket-directory bind, so it exists and is writable before the + ;; child starts (a daemon child writes savehist, recentf, its eln + ;; cache and `user-emacs-directory' there). One per child; it is + ;; removed with the socket directory at shutdown. This must come + ;; after the socket-directory bind so the mount exists when the + ;; directory is created inside it. + (list "--dir" home) + ;; A cleared environment: baseline, then HOME, then the caller's + ;; pass-through names. (list "--clearenv") (list "--setenv" "HOME" home) (cl-mapcan (lambda (pair) diff --git a/test/parenting-test.el b/test/parenting-test.el index 5eca0ad..a13174f 100644 --- a/test/parenting-test.el +++ b/test/parenting-test.el @@ -736,7 +736,23 @@ Single-argument bwrap flags: collects the one word after each FLAG." (should (parenting-test--bind-p args "--ro-bind" "/opt/parenting" "/opt/parenting")) ;; HOME is set (to a path under the socket directory). - (should (member "HOME" (parenting-test--flag-values args "--setenv"))))) + (should (member "HOME" (parenting-test--flag-values args "--setenv"))) + ;; That HOME directory is created in the jail with --dir, inside + ;; the socket-directory bind: after the bind (so the mount exists + ;; when the directory is made) and before HOME is set to it. + (let* ((home (expand-file-name "home" "/run/sock-dir")) + (bind-pos (cl-position "/run/sock-dir" args :test #'equal)) + (dir-pos (cl-position "--dir" args :test #'equal)) + (setenv-home-pos (cl-loop for i from 0 + for (flag name) on args + when (and (equal flag "--setenv") + (equal name "HOME")) + return i))) + (should (equal (list home) (parenting-test--flag-values args "--dir"))) + (should bind-pos) + (should dir-pos) + (should setenv-home-pos) + (should (< bind-pos dir-pos setenv-home-pos))))) (ert-deftest parenting-sandbox-wrapper-network-toggle () ;; No network by default; --share-net only when the spec asks. @@ -821,7 +837,12 @@ sandbox test skips rather than fails when it is unavailable." (parenting-with-child (conn :sandbox '(:network nil) :timeout 60) (should (equal 3 (parenting-eval conn '(+ 1 2)))) ;; The child really is jailed: no host network was shared. - (should (equal "sandboxed" (parenting-eval conn '"sandboxed"))))) + (should (equal "sandboxed" (parenting-eval conn '"sandboxed"))) + ;; HOME exists in the jail as a writable directory (created by + ;; --dir under the socket-directory bind), so a daemon child can + ;; write savehist, recentf and its eln cache there. + (should (parenting-eval conn '(file-directory-p (getenv "HOME")))) + (should (parenting-eval conn '(file-writable-p (getenv "HOME")))))) (provide 'parenting-test) ;;; parenting-test.el ends here