;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès ;;; ;;; 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 . (define-module (guix scripts run) #:use-module (guix ui) #:use-module (guix utils) #:use-module (guix scripts) #:use-module (guix store) #:use-module (guix packages) #:use-module (guix derivations) #:use-module ((guix build utils) #:select (which mkdir-p)) #:use-module (gnu build linux-container) #:use-module (gnu system file-systems) #:use-module (gnu packages) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9 gnu) #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) #:use-module (srfi srfi-37) #:use-module (ice-9 match) #:export (guix-run)) (define %options (list (option '(#\h "help") #f #f (lambda args (show-help) (exit 0))) (option '(#\V "version") #f #f (lambda args (show-version-and-exit "guix run"))))) (define (show-help) (display (G_ "Usage: guix run PACKAGE COMMAND... Run COMMAND from PACKAGE in a container.\n")) (newline) (display (G_ " -h, --help display this help and exit")) (display (G_ " -V, --version display version information and exit")) (newline) (show-bug-report-information)) (define (bind-mount-spec/ro item) (and (file-exists? item) (file-system (device item) (mount-point item) (type "none") (flags '(bind-mount read-only)) (check? #f)))) (define (bind-mount-spec/rw item) (and (file-exists? item) (file-system (inherit (bind-mount-spec/ro item)) (flags '(bind-mount))))) ;; Safe in which applications run. (define-immutable-record-type (safe namespaces mappings) safe? (namespaces safe-namespaces) (mappings safe-mappings)) (define (application-safe items) "Return safe corresponding to the application whose dependencies are listed in ITEMS." (define packages (map (compose (cut package-name->name+version <> #\-) store-path-package-name) items)) (define x11? (member "libx11" packages)) (define dbus? (member "dbus" packages)) (define alsa? (member "alsa-lib" packages)) (define pulseaudio? (member "pulseaudio" packages)) (define mappings (let-syntax ((if (syntax-rules () ((_ condition body) (if condition (or (and=> body list) '()) '())))) (ro (identifier-syntax bind-mount-spec/ro)) (rw (identifier-syntax bind-mount-spec/rw))) `(,(rw "/var/run/nscd/socket") ,@(if x11? (rw (string-append (getenv "HOME") "/.Xauthority"))) ,@(if x11? (rw "/tmp/.X11-unix")) ,@(if x11? (rw (string-append "/run/user/" (number->string (getuid))))) ,@(if dbus? (ro "/etc/machine-id")) ,@(if alsa? (rw "/dev/snd")) ,@(if pulseaudio? (rw (string-append (getenv "HOME") "/.pulse")))))) (define namespaces ;; X11 applications need to run in the same IPC namespace as ;; the server. (if x11? (fold delq %namespaces '(ipc net)) %namespaces)) (safe namespaces mappings)) (define %not-colon (char-set-complement (char-set #\:))) (define (guix-run . args) (define (parse-options) ;; Return the alist of option values. With this hack, the first ;; non-option argument is considered to be the beginning of the command. (let-values (((args command) (span (cut string-prefix? "-" <>) args))) (args-fold* args %options (lambda (opt name arg result) (leave (G_ "~A: unrecognized option~%") name)) (lambda (arg result) (pk 'arg arg) (alist-cons 'argument arg result)) '()) command)) (with-error-handling (match (parse-options) ((command args ...) (with-store store (let* ((full (search-path (string-tokenize (getenv "PATH") %not-colon) command)) (resolved (and=> full readlink*)) (prefix (and=> resolved (lambda (file) (and (store-path? file) (direct-store-path file)))))) (unless full (leave (G_ "command '~a' not found~%") command)) (unless prefix (leave (G_ "command '~a' is not in '~a'~%") command (%store-prefix))) (let* ((items (requisites store (list prefix))) (safe (application-safe items)) (env (environ)) (cwd (getcwd))) (call-with-container (append (map bind-mount-spec/ro items) (safe-mappings safe) (list (bind-mount-spec/ro cwd))) ;XXX: avoid that? (lambda () (environ env) ;TODO: filter ENV (mkdir-p (getenv "HOME")) (chdir cwd) (newline) (catch #t (lambda () (apply execl resolved command args)) (lambda (key . args) (print-exception (current-error-port) #f key args) (exit 1)))) #:namespaces (safe-namespaces safe)))))))))