diff options
Diffstat (limited to 'lisp/eshell/esh-io.el')
-rw-r--r-- | lisp/eshell/esh-io.el | 101 |
1 files changed, 60 insertions, 41 deletions
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el index 5179947da76..e5977c95807 100644 --- a/lisp/eshell/esh-io.el +++ b/lisp/eshell/esh-io.el @@ -147,9 +147,10 @@ not be added to this variable." function (choice (const :tag "Func returns output-func" t) (const :tag "Func is output-func" nil)))) + :risky t :group 'eshell-io) -(put 'eshell-virtual-targets 'risky-local-variable t) +(define-error 'eshell-pipe-broken "Pipe broken") ;;; Internal Variables: @@ -253,6 +254,30 @@ a nil value of mode defaults to `insert'." (setq idx (1+ idx)))) handles) +(defun eshell-close-handles (&optional exit-code result handles) + "Close all of the current HANDLES, taking refcounts into account. +If HANDLES is nil, use `eshell-current-handles'. + +EXIT-CODE is the process exit code (zero, if the command +completed successfully). If nil, then use the exit code already +set in `eshell-last-command-status'. + +RESULT is the quoted value of the last command. If nil, then use +the value already set in `eshell-last-command-result'." + (when exit-code + (setq eshell-last-command-status exit-code)) + (when result + (cl-assert (eq (car result) 'quote)) + (setq eshell-last-command-result (cadr result))) + (let ((handles (or handles eshell-current-handles))) + (dotimes (idx eshell-number-of-handles) + (when-let ((handle (aref handles idx))) + (setcdr handle (1- (cdr handle))) + (when (= (cdr handle) 0) + (dolist (target (ensure-list (car (aref handles idx)))) + (eshell-close-target target (= eshell-last-command-status 0))) + (setcar handle nil)))))) + (defun eshell-close-target (target status) "Close an output TARGET, passing STATUS as the result. STATUS should be non-nil on successful termination of the output." @@ -275,8 +300,23 @@ STATUS should be non-nil on successful termination of the output." ;; If we're redirecting to a process (via a pipe, or process ;; redirection), send it EOF so that it knows we're finished. ((eshell-processp target) - (if (eq (process-status target) 'run) - (process-send-eof target))) + ;; According to POSIX.1-2017, section 11.1.9, when communicating + ;; via terminal, sending EOF causes all bytes waiting to be read + ;; to be sent to the process immediately. Thus, if there are any + ;; bytes waiting, we need to send EOF twice: once to flush the + ;; buffer, and a second time to cause the next read() to return a + ;; size of 0, indicating end-of-file to the reading process. + ;; However, some platforms (e.g. Solaris) actually require sending + ;; a *third* EOF. Since sending extra EOFs while the process is + ;; running are a no-op, we'll just send the maximum we'd ever + ;; need. See bug#56025 for further details. + (let ((i 0) + ;; Only call `process-send-eof' once if communicating via a + ;; pipe (in truth, this just closes the pipe). + (max-attempts (if (process-tty-name target 'stdin) 3 1))) + (while (and (<= (cl-incf i) max-attempts) + (eq (process-status target) 'run)) + (process-send-eof target)))) ;; A plain function redirection needs no additional arguments ;; passed. @@ -289,32 +329,6 @@ STATUS should be non-nil on successful termination of the output." ((consp target) (apply (car target) status (cdr target))))) -(defun eshell-close-handles (exit-code &optional result handles) - "Close all of the current handles, taking refcounts into account. -EXIT-CODE is the process exit code; mainly, it is zero, if the command -completed successfully. RESULT is the quoted value of the last -command. If nil, then the meta variables for keeping track of the -last execution result should not be changed." - (let ((idx 0)) - (cl-assert (or (not result) (eq (car result) 'quote))) - (setq eshell-last-command-status exit-code - eshell-last-command-result (cadr result)) - (while (< idx eshell-number-of-handles) - (let ((handles (or handles eshell-current-handles))) - (when (aref handles idx) - (setcdr (aref handles idx) - (1- (cdr (aref handles idx)))) - (when (= (cdr (aref handles idx)) 0) - (let ((target (car (aref handles idx)))) - (if (not (listp target)) - (eshell-close-target target (= exit-code 0)) - (while target - (eshell-close-target (car target) (= exit-code 0)) - (setq target (cdr target))))) - (setcar (aref handles idx) nil)))) - (setq idx (1+ idx))) - nil)) - (defun eshell-kill-append (string) "Call `kill-append' with STRING, if it is indeed a string." (if (stringp string) @@ -376,8 +390,6 @@ it defaults to `insert'." (error "Invalid redirection target: %s" (eshell-stringify target))))) -(defvar grep-null-device) - (defun eshell-set-output-handle (index mode &optional target) "Set handle INDEX, using MODE, to point to TARGET." (when target @@ -484,24 +496,31 @@ Returns what was actually sent, or nil if nothing was sent." (goto-char target)))))) ((eshell-processp target) - (when (eq (process-status target) 'run) - (unless (stringp object) - (setq object (eshell-stringify object))) - (process-send-string target object))) + (unless (stringp object) + (setq object (eshell-stringify object))) + (condition-case nil + (process-send-string target object) + ;; If `process-send-string' raises an error, treat it as a broken pipe. + (error (signal 'eshell-pipe-broken (list target))))) ((consp target) (apply (car target) object (cdr target)))) object) (defun eshell-output-object (object &optional handle-index handles) - "Insert OBJECT, using HANDLE-INDEX specifically)." + "Insert OBJECT, using HANDLE-INDEX specifically. +If HANDLE-INDEX is nil, output to `eshell-output-handle'. +HANDLES is the set of file handles to use; if nil, use +`eshell-current-handles'." (let ((target (car (aref (or handles eshell-current-handles) (or handle-index eshell-output-handle))))) - (if (and target (not (listp target))) - (eshell-output-object-to-target object target) - (while target - (eshell-output-object-to-target object (car target)) - (setq target (cdr target)))))) + (if (listp target) + (while target + (eshell-output-object-to-target object (car target)) + (setq target (cdr target))) + (eshell-output-object-to-target object target) + ;; Explicitly return nil to match the list case above. + nil))) (provide 'esh-io) ;;; esh-io.el ends here |