GNU bug report logs - #62326
tramp-container PATH logic is incorrect

Previous Next

Package: emacs;

Reported by: ParetoOptimalDev <pareto.optimal <at> mailfence.com>

Date: Tue, 21 Mar 2023 08:30:03 UTC

Severity: normal

To reply to this bug, email your comments to 62326 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#62326; Package emacs. (Tue, 21 Mar 2023 08:30:03 GMT) Full text and rfc822 format available.

Acknowledgement sent to ParetoOptimalDev <pareto.optimal <at> mailfence.com>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Tue, 21 Mar 2023 08:30:03 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: ParetoOptimalDev <pareto.optimal <at> mailfence.com>
To: bug-gnu-emacs <at> gnu.org
Subject: tramp-container PATH logic is incorrect
Date: Mon, 20 Mar 2023 16:58:22 -0500
For docker containers tramp should set the PATH equal to whatever `ENV
PATH=...` is in the Dockerfile. To be precise it should match the output of:

```
docker inspect --format='{{json .Config.Env}} $docker_container_id
```

Right now however, there is a piece of logic in tramp-get-remote-path
that removes non-existent directories:

(delq
 nil
 (mapcar
  (lambda (x)
    (and
     (stringp x)
     (file-directory-p (tramp-make-tramp-file-name vec x))
     x))
  remote-path))

This seems reasonable, but in practice it doesn't seem to work. Here is
a script to demonstrate with the haskell container from dockerhub:


(require 'tramp)

;; pre-require things that were making loading messages that clutter up
the batch output
(require 'em-alias)
(require 'em-banner)
(require 'em-basic)
(require 'em-cmpl)
(require 'em-extpipe)
(require 'em-glob)
(require 'em-hist)
(require 'em-ls)
(require 'em-pred)
(require 'em-prompt)
(require 'em-script)
(require 'em-term)
(require 'em-unix)

(setq edebug-print-length 500)
(setq tramp-verbose 7)

(setq existing-haskell-container-name "nifty_davinci")
(defun start-new-haskell-container ()
  """ returns container id"""
  (string-trim (shell-command-to-string "docker run -i --rm --detach haskell:slim <at> sha256:68280eb4fd3d4ee1b62cf40619fd6e956a741f693ef53e9dd4168d2c721880f5")))
(setq docker-container-id
      (or existing-haskell-container-name (start-new-haskell-container)))
(message (format "emacs-version: %s" emacs-version))
(message (format "tramp-version: %s" tramp-version))
(message "")
(message "using the docker haskell image")
(message "expected path is:")
(message (string-trim (shell-command-to-string (format "docker inspect --format='{{json .Config.Env}}' %s" docker-container-id))))

(let ((default-directory (concat "/docker:" docker-container-id ":"))
      (tramp-remote-path '(tramp-own-remote-path)))
  (tramp-cleanup-all-connections)
  (message (concat "path is: " (truncate-string-to-width (eshell-command-result "echo $PATH") 100))))


Here is what happens when I run the code above:

$ emacs -Q --batch --load config.el
emacs-version: 30.0.50
tramp-version: 2.7.0-pre

using the docker haskell image
expected path is:
["PATH=/root/.cabal/bin:/root/.local/bin:/opt/ghc/9.4.4/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","LANG=C.UTF-8"]
Tramp: Sending command ‘exec docker exec -it a896e8aacaf0adaedd49b9bcd6b8104d7b5c4343d08382b4912ffaa8192bbb81 /bin/sh  -i’
Tramp: Found remote shell prompt on ‘a896e8aacaf0adaedd49b9bcd6b8104d7b5c4343d08382b4912ffaa8192bbb81’
path is: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin


You can see that crucial directories containing the Haskell compiler and
build tool are missing.

I'm not sure why they don't exist. But I can verify If I remove the
logic and preserve the non-existent directories things work:


(defun my/tramp-get-remote-path (vec)
  "Compile list of remote directories for PATH.
Nonexistent directories are removed from spec."
  (with-current-buffer (tramp-get-connection-buffer vec)
    ;; Expand connection-local variables.
    (tramp-set-connection-local-variables vec)
    (with-tramp-connection-property
	;; When `tramp-own-remote-path' is in `tramp-remote-path', we
	;; cache the result for the session only.  Otherwise, the
	;; result is cached persistently.
	(if (memq 'tramp-own-remote-path tramp-remote-path)
	    (tramp-get-process vec) vec)
	"remote-path"
      (let* ((remote-path (copy-tree tramp-remote-path))
	     (elt1 (memq 'tramp-default-remote-path remote-path))
	     (elt2 (memq 'tramp-own-remote-path remote-path))
	     (default-remote-path
	      (when elt1
		(or
		 (tramp-send-command-and-read
		  vec
                  (format
                   "echo \\\"`getconf PATH 2>%s`\\\""
                   (tramp-get-remote-null-device vec))
                  'noerror)
		 ;; Default if "getconf" is not available.
		 (progn
		   (tramp-message
		    vec 3
		    "`getconf PATH' not successful, using default value \"%s\"."
		    "/bin:/usr/bin")
		   "/bin:/usr/bin"))))
	     (own-remote-path
	      ;; The login shell could return more than just the $PATH
	      ;; string.  So we use `tramp-end-of-heredoc' as marker.
	      (when elt2
		(or
		 (tramp-send-command-and-read
		  vec
		  (format
		   "%s %s %s 'echo %s \\\"$PATH\\\"'"
		   (tramp-get-method-parameter vec 'tramp-remote-shell)
		   (string-join
		    (tramp-get-method-parameter vec 'tramp-remote-shell-login)
		    " ")
		   (string-join
		    (tramp-get-method-parameter vec 'tramp-remote-shell-args)
		    " ")
		   (tramp-shell-quote-argument tramp-end-of-heredoc))
		  'noerror (rx (literal tramp-end-of-heredoc)))
		 (progn
		   (tramp-message
		    vec 2 "Could not retrieve `tramp-own-remote-path'")
		   nil)))))


	;; Replace place holder `tramp-default-remote-path'.
	(when elt1
	  (setcdr elt1
		  (append
                   (split-string (or default-remote-path "") ":" 'omit)
		   (cdr elt1)))
	  (setq remote-path (delq 'tramp-default-remote-path remote-path)))

	;; Replace place holder `tramp-own-remote-path'.
	(when elt2
	  (setcdr elt2
		  (append
                   (split-string (or own-remote-path "") ":" 'omit)
		   (cdr elt2)))
	  (setq remote-path (delq 'tramp-own-remote-path remote-path)))

	;; Remove double entries.
	(setq elt1 remote-path)
	(while (consp elt1)
	  (while (and (car elt1) (setq elt2 (member (car elt1) (cdr elt1))))
	    (setcar elt2 nil))
	  (setq elt1 (cdr elt1)))

	(delq
	 nil
	 remote-path)))))

(advice-add 'tramp-get-remote-path :override 'my/tramp-get-remote-path)


I'm not sure if `Remove non-existing directories.` exists for a
particular reason or not, but I'd argue it's not appropriate for docker
containers.

I'm surprised that there aren't more bug reports along these lines since
this should affect anyone working with tramp, eshell, and eglot or
lsp-mode.

Thanks





This bug report was last modified 1 year and 37 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.