;;; tramp-make-process.el --- Tramp alternative make-process -*- lexical-binding:t -*- ;; Copyright (C) 2020 Free Software Foundation, Inc. ;; Author: Michael Albinus ;; Keywords: comm, processes ;; Package: tramp ;; This file is part of GNU Emacs. ;; GNU Emacs 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 Emacs 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 Emacs. If not, see . ;;; Commentary: ;; An alternative implementation of `make-process' for methods in ;; tramp-sh.el and tramp-adb.el. It does not use shell commands for ;; execution of the asynchronous command. Instead, it calls the ;; command directly. This should result in a performance boost. ;; ;; Limitations of this approach: ;; ;; * It works only for connection methods defined in tramp-sh.el and ;; tramp-adb.el. ;; ;; * It does not support multi-hop methods. ;; ;; * It does not support user authentication, like password handling. ;; ;; * It does not support a separated error stream. ;; ;; * It cannot be killed via `interrupt-process'. ;; ;; * It does not report the remote terminal name via `process-tty-name'. ;; ;; * It does not set environment variable "INSIDE_EMACS". ;; ;; In order to gain even more performance, it is recommended to set or ;; bind `tramp-verbose' to 0 when running `make-process'. ;;; Code: ;; We use BUFFER also as connection buffer during setup. Because of ;; this, its original contents must be saved, and restored once ;; connection has been setup. (defun tramp-make-process (&rest args) "An alternative `make-process' implementation for Tramp files." (when args (with-parsed-tramp-file-name (expand-file-name default-directory) nil (let ((name (plist-get args :name)) (buffer (plist-get args :buffer)) (command (plist-get args :command)) (coding (plist-get args :coding)) (noquery (plist-get args :noquery)) (connection-type (plist-get args :connection-type)) (filter (plist-get args :filter)) (sentinel (plist-get args :sentinel)) (stderr (plist-get args :stderr))) (unless (stringp name) (signal 'wrong-type-argument (list #'stringp name))) (unless (or (null buffer) (bufferp buffer) (stringp buffer)) (signal 'wrong-type-argument (list #'stringp buffer))) (unless (consp command) (signal 'wrong-type-argument (list #'consp command))) (unless (or (null coding) (and (symbolp coding) (memq coding coding-system-list)) (and (consp coding) (memq (car coding) coding-system-list) (memq (cdr coding) coding-system-list))) (signal 'wrong-type-argument (list #'symbolp coding))) (unless (or (null connection-type) (memq connection-type '(pipe pty))) (signal 'wrong-type-argument (list #'symbolp connection-type))) (unless (or (null filter) (functionp filter)) (signal 'wrong-type-argument (list #'functionp filter))) (unless (or (null sentinel) (functionp sentinel)) (signal 'wrong-type-argument (list #'functionp sentinel))) (unless (or (null stderr) (bufferp stderr) (stringp stderr)) (signal 'wrong-type-argument (list #'stringp stderr))) (when (and (stringp stderr) (tramp-tramp-file-p stderr) (not (tramp-equal-remote default-directory stderr))) (signal 'file-error (list "Wrong stderr" stderr))) (let* ((buffer (if buffer (get-buffer-create buffer) ;; BUFFER can be nil. We use a temporary buffer. (generate-new-buffer tramp-temp-buffer-name))) (command (append `("cd" ,localname "&&") (mapcar #'tramp-shell-quote-argument command))) (bmp (and (buffer-live-p buffer) (buffer-modified-p buffer))) (name1 name) (i 0) ;; We do not want to raise an error when `make-process' ;; has been started several times in `eshell' and ;; friends. tramp-current-connection p) (while (get-process name1) ;; NAME must be unique as process name. (setq i (1+ i) name1 (format "%s<%d>" name i))) (setq name name1) ;; Set the new process properties. (tramp-set-connection-property v "process-name" name) (tramp-set-connection-property v "process-buffer" buffer) (with-current-buffer (tramp-get-connection-buffer v) (unwind-protect (let* ((login-program (or (tramp-get-method-parameter v 'tramp-login-program) "adb")) (login-args (or (tramp-get-method-parameter v 'tramp-login-args) '(("shell")))) (async-args (tramp-get-method-parameter v 'tramp-async-args)) ;; We don't create the temporary file. In ;; fact, it is just a prefix for the ;; ControlPath option of ssh; the real ;; temporary file has another name, and it is ;; created and protected by ssh. It is also ;; removed by ssh when the connection is ;; closed. The temporary file name is cached ;; in the main connection process, therefore ;; we cannot use `tramp-get-connection-process'. (tmpfile (with-tramp-connection-property (tramp-get-process v) "temp-file" (make-temp-name (expand-file-name tramp-temp-name-prefix (tramp-compat-temporary-file-directory))))) (options (tramp-ssh-controlmaster-options v)) spec) ;; Replace `login-args' place holders. (setq spec (format-spec-make ?t tmpfile) options (format-spec options spec) spec (format-spec-make ?h (or host "") ?u (or user "") ?p (or port "") ?c options ?l "") ;; Add arguments for asynchronous processes. login-args (append async-args login-args) ;; Expand format spec. login-args (tramp-compat-flatten-tree (mapcar (lambda (x) (setq x (mapcar (lambda (y) (format-spec y spec)) x)) (unless (member "" x) x)) login-args)) ;; Split ControlMaster options. login-args (tramp-compat-flatten-tree (mapcar (lambda (x) (split-string x " ")) login-args)) p (apply #'start-process name buffer login-program (append login-args command))) (tramp-message v 6 "%s" (string-join (process-command p) " ")) ;; Set sentinel and filter. (when sentinel (set-process-sentinel p sentinel)) (when filter (set-process-filter p filter)) ;; Set query flag and process marker for this ;; process. We ignore errors, because the ;; process could have finished already. (ignore-errors (set-process-query-on-exit-flag p (null noquery)) (set-marker (process-mark p) (point))) ;; We must flush them here already; otherwise ;; `rename-file', `delete-file' or ;; `insert-file-contents' will fail. (tramp-flush-connection-property v "process-name") (tramp-flush-connection-property v "process-buffer") ;; Return process. p) ;; Save exit. (if (string-match-p tramp-temp-buffer-name (buffer-name)) (ignore-errors (set-process-buffer p nil) (kill-buffer (current-buffer))) (set-buffer-modified-p bmp)) (tramp-flush-connection-property v "process-name") (tramp-flush-connection-property v "process-buffer")))))))) (with-eval-after-load 'tramp-adb (defalias 'tramp-adb-handle-make-process #'tramp-make-process)) (with-eval-after-load 'tramp-sh (defalias 'tramp-sh-handle-make-process #'tramp-make-process)) (add-hook 'tramp-unload-hook (lambda () (unload-feature 'tramp-make-process 'force))) (provide 'tramp-make-process) ;;; tramp-make-process.el ends here