>From 16dbbd2f59b0ab31078b9ac499f18070eabc783a Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Wed, 26 Oct 2022 21:10:21 -0400 Subject: [PATCH v2 1/2] Add the "doas" alias to eshell. * lisp/eshell/em-tramp.el (eshell/doas): new function. (eshell--method-wrap-directory): new function. (eshell/sudo): accept '-s'/'--shell' for interactive use. * test/lisp/eshell/em-tramp-tests.el (em-tramp-test/sudo-shell) (em-tramp-test/sudo-user-shell) (em-tramp-test/doas-basic) (em-tramp-test/doas-user) (em-tramp-test/doas-shell) (em-tramp-test/doas-user-shell): new tests. * etc/NEWS: mention new 'doas' eshell command. * doc/misc/eshell.texi: add 'doas' command documentation. --- doc/misc/eshell.texi | 9 ++-- etc/NEWS | 5 ++ lisp/eshell/em-tramp.el | 78 ++++++++++++++++++++---------- test/lisp/eshell/em-tramp-tests.el | 66 +++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 29 deletions(-) diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index ff368c9dc4..96873a3f9a 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -717,9 +717,12 @@ Built-ins @cmindex su @itemx sudo @cmindex sudo -Uses TRAMP's @command{su} or @command{sudo} method @pxref{Inline methods, , , tramp} -to run a command via @command{su} or @command{sudo}. These commands -are in the eshell-tramp module, which is disabled by default. +@itemx doas +@cmindex doas +Uses TRAMP's @command{su}, @command{sudo}, or @command{doas} method +@pxref{Inline methods, , , tramp} to run a command via @command{su}, +@command{sudo}, or @command{doas}. These commands are in the +eshell-tramp module, which is disabled by default. @item substitute diff --git a/etc/NEWS b/etc/NEWS index bf50c900ea..62093ada33 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -379,6 +379,11 @@ node in the Eshell manual for more details. *** Eshell pipelines now only pipe stdout by default. To pipe both stdout and stderr, use the '|&' operator instead of '|'. +*** New eshell built-in command 'doas'. +The privilege-escalation program 'doas' has been added to the existing +'su' and 'sudo' commands from the 'eshell-tramp' module. The external +command may still be accessed by using '*doas'. + --- ** The 'delete-forward-char' command now deletes by grapheme clusters. This command is by default bound to the function key diff --git a/lisp/eshell/em-tramp.el b/lisp/eshell/em-tramp.el index aebbc36e71..fb5d5b86a2 100644 --- a/lisp/eshell/em-tramp.el +++ b/lisp/eshell/em-tramp.el @@ -40,9 +40,10 @@ (defgroup eshell-tramp nil "This module defines commands that use TRAMP in a way that is not transparent to the user. So far, this includes only the - built-in su and sudo commands, which are not compatible with - the full, external su and sudo commands, and require the user - to understand how to use the TRAMP sudo method." + built-in su, sudo and doas commands, which are not compatible + with the full, external su, sudo, and doas commands, and + require the user to understand how to use the TRAMP sudo + method." :tag "TRAMP Eshell features" :group 'eshell-module)) @@ -52,7 +53,7 @@ eshell-tramp-initialize (add-hook 'pcomplete-try-first-hook 'eshell-complete-host-reference nil t)) (setq-local eshell-complex-commands - (append '("su" "sudo") + (append '("su" "sudo" "doas") eshell-complex-commands))) (autoload 'eshell-parse-command "esh-cmd") @@ -91,6 +92,21 @@ eshell/su (put 'eshell/su 'eshell-no-numeric-conversions t) +(defun eshell--method-wrap-directory (directory method &optional user) + "Return DIRECTORY as accessed by a Tramp METHOD for USER." + (let ((user (or user "root")) + (dir (file-local-name (expand-file-name directory))) + (prefix (file-remote-p directory)) + (host (or (file-remote-p directory 'host) + tramp-default-host)) + (rmethod (file-remote-p directory 'method)) + (ruser (file-remote-p directory 'user))) + (if (and prefix (or (not (string-equal rmethod method)) + (not (string-equal ruser user)))) + (format "%s|%s:%s@%s:%s" + (substring prefix 0 -1) method user host dir) + (format "/%s:%s@%s:%s" method user host dir)))) + (defun eshell/sudo (&rest args) "Alias \"sudo\" to call Tramp. @@ -99,34 +115,44 @@ eshell/sudo "sudo" args '((?h "help" nil nil "show this usage screen") (?u "user" t user "execute a command as another USER") + (?s "shell" nil shell "start a shell instead of executing COMMAND") :show-usage :parse-leading-options-only - :usage "[(-u | --user) USER] COMMAND + :usage "[(-u | --user) USER] (-s | --shell) | COMMANDq Execute a COMMAND as the superuser or another USER.") - (throw 'eshell-external - (let* ((user (or user "root")) - (host (or (file-remote-p default-directory 'host) - tramp-default-host)) - (dir (file-local-name (expand-file-name default-directory))) - (prefix (file-remote-p default-directory)) - (default-directory - (if (and prefix - (or - (not - (string-equal - "sudo" - (file-remote-p default-directory 'method))) - (not - (string-equal - user - (file-remote-p default-directory 'user))))) - (format "%s|sudo:%s@%s:%s" - (substring prefix 0 -1) user host dir) - (format "/sudo:%s@%s:%s" user host dir)))) - (eshell-named-command (car args) (cdr args)))))) + (let ((dir (eshell--method-wrap-directory default-directory "sudo" user))) + (if shell + (throw 'eshell-replace-command + (eshell-parse-command "cd" (list dir))) + (throw 'eshell-external + (let ((default-directory dir)) + (eshell-named-command (car args) (cdr args)))))))) (put 'eshell/sudo 'eshell-no-numeric-conversions t) +(defun eshell/doas (&rest args) + "Call Tramp’s doas method with ARGS. + +Uses the system doas through Tramp's doas method." + (eshell-eval-using-options + "doas" args + '((?h "help" nil nil "show this usage screen") + (?u "user" t user "execute a command as another USER") + (?s "shell" nil shell "start a shell instead of executing COMMAND") + :show-usage + :parse-leading-options-only + :usage "[(-u | --user) USER] (-s | --shell) | COMMAND +Execute a COMMAND as the superuser or another USER.") + (let ((dir (eshell--method-wrap-directory default-directory "doas" user))) + (if shell + (throw 'eshell-replace-command + (eshell-parse-command "cd" (list dir))) + (throw 'eshell-external + (let ((default-directory dir)) + (eshell-named-command (car args) (cdr args)))))))) + +(put 'eshell/doas 'eshell-no-numeric-conversions t) + (provide 'em-tramp) ;; Local Variables: diff --git a/test/lisp/eshell/em-tramp-tests.el b/test/lisp/eshell/em-tramp-tests.el index 8969c1e229..9d28d6be82 100644 --- a/test/lisp/eshell/em-tramp-tests.el +++ b/test/lisp/eshell/em-tramp-tests.el @@ -85,4 +85,70 @@ em-tramp-test/sudo-user `(,(format "/sudo:USER@%s:%s" tramp-default-host default-directory) ("echo" ("-u" "hi"))))))) +(ert-deftest em-tramp-test/sudo-shell () + "Test Eshell `sudo' command with -s/--shell option." + (dolist (args '(("--shell") + ("-s"))) + (should (equal + (catch 'eshell-replace-command (apply #'eshell/sudo args)) + `(eshell-trap-errors + (eshell-named-command + "cd" + (list ,(format "/sudo:root@%s:%s" tramp-default-host default-directory)))))))) + +(ert-deftest em-tramp-test/sudo-user-shell () + "Test Eshell `sudo' command with -s and -u options." + (should (equal + (catch 'eshell-replace-command (eshell/sudo "-u" "USER" "-s")) + `(eshell-trap-errors + (eshell-named-command + "cd" + (list ,(format "/sudo:USER@%s:%s" tramp-default-host default-directory))))))) + +(ert-deftest em-tramp-test/doas-basic () + "Test Eshell `doas' command with default user." + (cl-letf (((symbol-function 'eshell-named-command) + #'mock-eshell-named-command)) + (should (equal + (catch 'eshell-external (eshell/doas "echo" "hi")) + `(,(format "/doas:root@%s:%s" tramp-default-host default-directory) + ("echo" ("hi"))))) + (should (equal + (catch 'eshell-external (eshell/doas "echo" "-u" "hi")) + `(,(format "/doas:root@%s:%s" tramp-default-host default-directory) + ("echo" ("-u" "hi"))))))) + +(ert-deftest em-tramp-test/doas-user () + "Test Eshell `doas' command with specified user." + (cl-letf (((symbol-function 'eshell-named-command) + #'mock-eshell-named-command)) + (should (equal + (catch 'eshell-external (eshell/doas "-u" "USER" "echo" "hi")) + `(,(format "/doas:USER@%s:%s" tramp-default-host default-directory) + ("echo" ("hi"))))) + (should (equal + (catch 'eshell-external (eshell/doas "-u" "USER" "echo" "-u" "hi")) + `(,(format "/doas:USER@%s:%s" tramp-default-host default-directory) + ("echo" ("-u" "hi"))))))) + +(ert-deftest em-tramp-test/doas-shell () + "Test Eshell `doas' command with -s/--shell option." + (dolist (args '(("--shell") + ("-s"))) + (should (equal + (catch 'eshell-replace-command (apply #'eshell/doas args)) + `(eshell-trap-errors + (eshell-named-command + "cd" + (list ,(format "/doas:root@%s:%s" tramp-default-host default-directory)))))))) + +(ert-deftest em-tramp-test/doas-user-shell () + "Test Eshell `doas' command with -s and -u options." + (should (equal + (catch 'eshell-replace-command (eshell/doas "-u" "USER" "-s")) + `(eshell-trap-errors + (eshell-named-command + "cd" + (list ,(format "/doas:USER@%s:%s" tramp-default-host default-directory))))))) + ;;; em-tramp-tests.el ends here -- 2.38.0