unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
@ 2022-06-16 18:30 Ken Brown
  2022-06-16 19:30 ` Sean Whitton
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-06-16 18:30 UTC (permalink / raw)
  To: 56025; +Cc: Sean Whitton

In commit 231a1ba3, Lars disabled the extpipe tests on EMBA "because they 
apparently time out".  The EMBA log he cited in the commit message shows that 
only em-extpipe-test-2 times out.  And this same test (but no others) also times 
out on Cygwin:

$ make -C test em-extpipe-tests
[...]
Test em-extpipe-test-2 backtrace:
   signal(error ("timed out waiting for subprocess(es)"))
   error("timed out waiting for subprocess(es)")
   eshell-wait-for-subprocess()
   eshell-command-result-p("echo \"bar\" | rev *>/tmp/emacs-test-Rw5ILv
   (let ((input (replace-regexp-in-string "temp\\([^>]\\|\\'\\)" temp (
   (unwind-protect (let ((input (replace-regexp-in-string "temp\\([^>]\
   (let ((temp-buffer (generate-new-buffer " *temp*" t))) (unwind-prote
   (progn (let ((temp-buffer (generate-new-buffer " *temp*" t))) (unwin
   (unwind-protect (progn (let ((temp-buffer (generate-new-buffer " *te
   (let* ((coding-system-for-write nil) (temp-file (identity (make-temp
   (save-current-buffer (set-buffer eshell-buffer) (let* ((fn-38 #'exec
   (unwind-protect (save-current-buffer (set-buffer eshell-buffer) (let
   (let* ((process-environment (cons "HISTFILE" process-environment)) (
   (progn (let* ((process-environment (cons "HISTFILE" process-environm
   (unwind-protect (progn (let* ((process-environment (cons "HISTFILE"
   (let* ((coding-system-for-write nil) (temp-file (file-name-as-direct
   (save-current-buffer (let* ((coding-system-for-write nil) (temp-file
   (let ((input "echo \"bar\" | rev *>temp")) (save-current-buffer (let
   (progn (let ((value-24 (gensym "ert-form-evaluation-aborted-"))) (le
   (closure (t) nil (progn (let ((value-24 (gensym "ert-form-evaluation
   ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
   ert-run-test(#s(ert-test :name em-extpipe-test-2 :documentation nil
   ert-run-or-rerun-test(#s(ert--stats :selector ... :tests ... :test-m
   ert-run-tests((not (or (tag :unstable) (tag :nativecomp))) #f(compil
   ert-run-tests-batch((not (or (tag :unstable) (tag :nativecomp))))
   ert-run-tests-batch-and-exit((not (or (tag :unstable) (tag :nativeco
   eval((ert-run-tests-batch-and-exit '(not (or (tag :unstable) (tag :n
   command-line-1(("-L" ":../../master/test" "-l" "ert" "-l" "lisp/eshe
   command-line()
   normal-top-level()
Test em-extpipe-test-2 condition:
     (error "timed out waiting for subprocess(es)")
    FAILED  10/17  em-extpipe-test-2 (5.237846 sec) at 
../../master/test/lisp/eshell/em-extpipe-tests.el:87

I don't see what's special about this test that would cause it to time out on 
some systems.  Sean, do you have any ideas?  If not, I suggest just skipping it 
on Cygwin.  And maybe we should skip just this extpipe test (rather than all of 
them) on EMBA.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-16 18:30 bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin Ken Brown
@ 2022-06-16 19:30 ` Sean Whitton
  2022-06-16 22:01   ` Ken Brown
  0 siblings, 1 reply; 64+ messages in thread
From: Sean Whitton @ 2022-06-16 19:30 UTC (permalink / raw)
  To: Ken Brown, 56025

Hello Ken,

On Thu 16 Jun 2022 at 02:30pm -04, Ken Brown wrote:

> In commit 231a1ba3, Lars disabled the extpipe tests on EMBA "because they
> apparently time out".  The EMBA log he cited in the commit message shows that
> only em-extpipe-test-2 times out.  And this same test (but no others) also times
> out on Cygwin:

That test invokes `sh -c "rev >temp"` as its only subprocess, so that's
probably what is timing out.  Is there something different about
Cygwin's sh?  Something about EOFs?

-- 
Sean Whitton





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-16 19:30 ` Sean Whitton
@ 2022-06-16 22:01   ` Ken Brown
  2022-06-17 13:39     ` Ken Brown
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-06-16 22:01 UTC (permalink / raw)
  To: Sean Whitton, 56025

On 6/16/2022 3:30 PM, Sean Whitton wrote:
> Hello Ken,
> 
> On Thu 16 Jun 2022 at 02:30pm -04, Ken Brown wrote:
> 
>> In commit 231a1ba3, Lars disabled the extpipe tests on EMBA "because they
>> apparently time out".  The EMBA log he cited in the commit message shows that
>> only em-extpipe-test-2 times out.  And this same test (but no others) also times
>> out on Cygwin:
> 
> That test invokes `sh -c "rev >temp"` as its only subprocess, so that's
> probably what is timing out.  Is there something different about
> Cygwin's sh?  Something about EOFs?

I'm not aware of anything different.  Here's what happens when I run the test 
interactively:

$ time echo \"bar\" | sh -c "rev >temp"

real    0m0.100s
user    0m0.030s
sys     0m0.030s

$ cat temp
"rab"

And keep in mind that the test also times out on EMBA.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-16 22:01   ` Ken Brown
@ 2022-06-17 13:39     ` Ken Brown
  2022-06-18  0:57       ` Sean Whitton
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-06-17 13:39 UTC (permalink / raw)
  To: Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/16/2022 6:01 PM, Ken Brown wrote:
> On 6/16/2022 3:30 PM, Sean Whitton wrote:
>> Hello Ken,
>>
>> On Thu 16 Jun 2022 at 02:30pm -04, Ken Brown wrote:
>>
>>> In commit 231a1ba3, Lars disabled the extpipe tests on EMBA "because they
>>> apparently time out".  The EMBA log he cited in the commit message shows that
>>> only em-extpipe-test-2 times out.  And this same test (but no others) also times
>>> out on Cygwin:
>>
>> That test invokes `sh -c "rev >temp"` as its only subprocess, so that's
>> probably what is timing out.  Is there something different about
>> Cygwin's sh?  Something about EOFs?
> 
> I'm not aware of anything different.  Here's what happens when I run the test 
> interactively:
> 
> $ time echo \"bar\" | sh -c "rev >temp"
> 
> real    0m0.100s
> user    0m0.030s
> sys     0m0.030s
> 
> $ cat temp
> "rab"
> 
> And keep in mind that the test also times out on EMBA.

I just tried a different experiment: In an interactive emacs-29 session, I 
started eshell and typed

   echo \"bar\" | rev *>temp

Nothing visible happens until I type 'C-c C-c'.  Then a prompt appears again, 
and 'ls -l' shows that temp exists and is empty.

Prior to typing 'C-c C-c', 'M-x list-processes' (or 'C-c C-s') shows a bash 
process running but it doesn't show 'rev'.  But running 'ps' outside of emacs 
shows both 'rev' and its parent 'bash' process.

It does seem that there's an actual bug here, not just a test that should be 
skipped because it times out.  It could be a Cygwin bug, of course, but that 
doesn't explain the EMBA failure.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-17 13:39     ` Ken Brown
@ 2022-06-18  0:57       ` Sean Whitton
  2022-06-18  2:07         ` Ken Brown
  0 siblings, 1 reply; 64+ messages in thread
From: Sean Whitton @ 2022-06-18  0:57 UTC (permalink / raw)
  To: Ken Brown, 56025, Lars Magne Ingebrigtsen

Hello,

On Fri 17 Jun 2022 at 09:39am -04, Ken Brown wrote:

> On 6/16/2022 6:01 PM, Ken Brown wrote:
>> On 6/16/2022 3:30 PM, Sean Whitton wrote:
>>> Hello Ken,
>>>
>>> On Thu 16 Jun 2022 at 02:30pm -04, Ken Brown wrote:
>>>
>>>> In commit 231a1ba3, Lars disabled the extpipe tests on EMBA "because they
>>>> apparently time out".  The EMBA log he cited in the commit message shows that
>>>> only em-extpipe-test-2 times out.  And this same test (but no others) also times
>>>> out on Cygwin:
>>>
>>> That test invokes `sh -c "rev >temp"` as its only subprocess, so that's
>>> probably what is timing out.  Is there something different about
>>> Cygwin's sh?  Something about EOFs?
>>
>> I'm not aware of anything different.  Here's what happens when I run the test
>> interactively:
>>
>> $ time echo \"bar\" | sh -c "rev >temp"
>>
>> real    0m0.100s
>> user    0m0.030s
>> sys     0m0.030s
>>
>> $ cat temp
>> "rab"
>>
>> And keep in mind that the test also times out on EMBA.
>
> I just tried a different experiment: In an interactive emacs-29 session, I
> started eshell and typed
>
>    echo \"bar\" | rev *>temp
>
> Nothing visible happens until I type 'C-c C-c'.  Then a prompt appears again,
> and 'ls -l' shows that temp exists and is empty.
>
> Prior to typing 'C-c C-c', 'M-x list-processes' (or 'C-c C-s') shows a bash
> process running but it doesn't show 'rev'.  But running 'ps' outside of emacs
> shows both 'rev' and its parent 'bash' process.
>
> It does seem that there's an actual bug here, not just a test that should be
> skipped because it times out.  It could be a Cygwin bug, of course, but that
> doesn't explain the EMBA failure.

Could you see if the same thing happens if you type

    echo "bar" | sh -c "rev >temp"

into an interactive session, please?

If it's the same then extpipe has uncovered a general Eshell bug.

-- 
Sean Whitton





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-18  0:57       ` Sean Whitton
@ 2022-06-18  2:07         ` Ken Brown
  2022-06-18  2:35           ` Ken Brown
  2022-06-18  3:50           ` Jim Porter
  0 siblings, 2 replies; 64+ messages in thread
From: Ken Brown @ 2022-06-18  2:07 UTC (permalink / raw)
  To: Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/17/2022 8:57 PM, Sean Whitton wrote:
> On Fri 17 Jun 2022 at 09:39am -04, Ken Brown wrote:
>> I just tried a different experiment: In an interactive emacs-29 session, I
>> started eshell and typed
>>
>>     echo \"bar\" | rev *>temp
>>
>> Nothing visible happens until I type 'C-c C-c'.  Then a prompt appears again,
>> and 'ls -l' shows that temp exists and is empty.
>>
>> Prior to typing 'C-c C-c', 'M-x list-processes' (or 'C-c C-s') shows a bash
>> process running but it doesn't show 'rev'.  But running 'ps' outside of emacs
>> shows both 'rev' and its parent 'bash' process.
>>
>> It does seem that there's an actual bug here, not just a test that should be
>> skipped because it times out.  It could be a Cygwin bug, of course, but that
>> doesn't explain the EMBA failure.
> 
> Could you see if the same thing happens if you type
> 
>      echo "bar" | sh -c "rev >temp"
> 
> into an interactive session, please?
> 
> If it's the same then extpipe has uncovered a general Eshell bug.

Yes, it's the same.  And it's even the same if I remove the quotation marks 
around "rev >temp".

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-18  2:07         ` Ken Brown
@ 2022-06-18  2:35           ` Ken Brown
  2022-06-18  3:50           ` Jim Porter
  1 sibling, 0 replies; 64+ messages in thread
From: Ken Brown @ 2022-06-18  2:35 UTC (permalink / raw)
  To: Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/17/2022 10:07 PM, Ken Brown wrote:
> On 6/17/2022 8:57 PM, Sean Whitton wrote:
>> On Fri 17 Jun 2022 at 09:39am -04, Ken Brown wrote:
>>> I just tried a different experiment: In an interactive emacs-29 session, I
>>> started eshell and typed
>>>
>>>     echo \"bar\" | rev *>temp
>>>
>>> Nothing visible happens until I type 'C-c C-c'.  Then a prompt appears again,
>>> and 'ls -l' shows that temp exists and is empty.
>>>
>>> Prior to typing 'C-c C-c', 'M-x list-processes' (or 'C-c C-s') shows a bash
>>> process running but it doesn't show 'rev'.  But running 'ps' outside of emacs
>>> shows both 'rev' and its parent 'bash' process.
>>>
>>> It does seem that there's an actual bug here, not just a test that should be
>>> skipped because it times out.  It could be a Cygwin bug, of course, but that
>>> doesn't explain the EMBA failure.
>>
>> Could you see if the same thing happens if you type
>>
>>      echo "bar" | sh -c "rev >temp"
>>
>> into an interactive session, please?
>>
>> If it's the same then extpipe has uncovered a general Eshell bug.
> 
> Yes, it's the same.  And it's even the same if I remove the quotation marks 
> around "rev >temp".

And it's also the same if remove the output redirection, i.e., if I type 'echo 
bar | sh -c rev'.  On the other hand, if I type 'echo bar | rev', then I see the 
output 'rab'; but rev doesn't exit, and I don't get the eshell prompt, until I 
type 'C-c C-c'.  On the third hand, 'echo bar | cat' almost works; I see the 
output 'bar' followed immediately by the eshell prompt (with no newline after 
'bar').  Is eshell stripping newlines in some circumstances?  If so, I wonder if 
this accounts for the failure of rev to exit in some of the earlier examples, 
since it operates on lines?

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-18  2:07         ` Ken Brown
  2022-06-18  2:35           ` Ken Brown
@ 2022-06-18  3:50           ` Jim Porter
  2022-06-18 17:52             ` Ken Brown
  1 sibling, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-06-18  3:50 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/17/2022 7:07 PM, Ken Brown wrote:
> On 6/17/2022 8:57 PM, Sean Whitton wrote:
>> Could you see if the same thing happens if you type
>>
>>      echo "bar" | sh -c "rev >temp"
>>
>> into an interactive session, please?
>>
>> If it's the same then extpipe has uncovered a general Eshell bug.
> 
> Yes, it's the same.  And it's even the same if I remove the quotation 
> marks around "rev >temp".

Does the above command also fail on Emacs 28? I changed some aspects of 
process management for Eshell in Emacs 29, so it's possible this is a 
regression. If it works correctly under Emacs 28, I'd be very interested 
to see the results of bisecting to find the breaking commit.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-18  3:50           ` Jim Porter
@ 2022-06-18 17:52             ` Ken Brown
  2022-06-18 19:02               ` Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-06-18 17:52 UTC (permalink / raw)
  To: Jim Porter, Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/17/2022 11:50 PM, Jim Porter wrote:
> On 6/17/2022 7:07 PM, Ken Brown wrote:
>> On 6/17/2022 8:57 PM, Sean Whitton wrote:
>>> Could you see if the same thing happens if you type
>>>
>>>      echo "bar" | sh -c "rev >temp"
>>>
>>> into an interactive session, please?
>>>
>>> If it's the same then extpipe has uncovered a general Eshell bug.
>>
>> Yes, it's the same.  And it's even the same if I remove the quotation marks 
>> around "rev >temp".
> 
> Does the above command also fail on Emacs 28? I changed some aspects of process 
> management for Eshell in Emacs 29, so it's possible this is a regression. If it 
> works correctly under Emacs 28, I'd be very interested to see the results of 
> bisecting to find the breaking commit.

No, I'm seeing the same results on Emacs 28.  On both Emacs 28 and Emacs 29, rev 
is apparently not seeing EOF unless echo outputs a newline, so rev keeps waiting 
for input.

[Side note: It took me a while to sort this out because (a) Eshell's echo does 
not output a newline by default, in contrast to Bash's builtin echo; (b) in 
Eshell in Emacs 28, you use '-n' to add a newline, while in Bash '-n' suppresses 
the newline; and (c) in Eshell in Emacs 29, you use '-N' to add a newline.]

Here's my simplest reproduction recipe for the bug: Type 'echo bar | rev' into 
Eshell.  In both Emacs 28 and Emacs 29, 'rab' is output but rev keeps running.

But if I make echo output a newline (by '-n' in Emacs 28 and '-N' in Emacs 29), 
then rev exits after outputting 'rab' (followed by newline).

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-18 17:52             ` Ken Brown
@ 2022-06-18 19:02               ` Jim Porter
  2022-06-18 20:51                 ` Ken Brown
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-06-18 19:02 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/18/2022 10:52 AM, Ken Brown wrote:
> No, I'm seeing the same results on Emacs 28.  On both Emacs 28 and Emacs 
> 29, rev is apparently not seeing EOF unless echo outputs a newline, so 
> rev keeps waiting for input.

Ah ha! Thanks for debugging this. The minimal fix then would be to 
change the command in em-extpipe-test-2 to either of these:

   echo -N "bar" | rev *>temp
   *echo "bar" | rev *>temp

One last[1] question: if you ran "echo -n bar | rev" in Cygwin Bash, 
does it hang there too? Maybe this is just a Cygwin limitation, or maybe 
Eshell is doing something wrong with its built-in pipelines in this 
situation.

> [Side note: It took me a while to sort this out because (a) Eshell's 
> echo does not output a newline by default, in contrast to Bash's builtin 
> echo; (b) in Eshell in Emacs 28, you use '-n' to add a newline, while in 
> Bash '-n' suppresses the newline; and (c) in Eshell in Emacs 29, you use 
> '-N' to add a newline.]

This is one of the parts of Eshell that's always bothered me a bit. 
Eshell's echo is different enough from other echo implementations that 
it's easy to get tripped up. There's some further discussion of this in 
bug#12689 as well. I'm hesitant to change Eshell's echo too much, since 
it could break user scripts, but it would be nice if we could find a 
reasonably-compatible way of making it work more like /bin/echo.

[1] Well, probably last.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-18 19:02               ` Jim Porter
@ 2022-06-18 20:51                 ` Ken Brown
  2022-06-18 22:00                   ` Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-06-18 20:51 UTC (permalink / raw)
  To: Jim Porter, Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/18/2022 3:02 PM, Jim Porter wrote:
> On 6/18/2022 10:52 AM, Ken Brown wrote:
>> No, I'm seeing the same results on Emacs 28.  On both Emacs 28 and Emacs 29, 
>> rev is apparently not seeing EOF unless echo outputs a newline, so rev keeps 
>> waiting for input.
> 
> Ah ha! Thanks for debugging this. The minimal fix then would be to change the 
> command in em-extpipe-test-2 to either of these:
> 
>    echo -N "bar" | rev *>temp

This doesn't work.  It still hangs when run interactively, as does the equivalent

      echo -N bar | sh -c "rev >temp"

>    *echo "bar" | rev *>temp

This works interactively, but I don't know the appropriate syntax for modifying 
the test.  Naively replacing each 'echo' by '*echo' caused the 'should-parse' to 
fail.
> One last[1] question: if you ran "echo -n bar | rev" in Cygwin Bash, does it 
> hang there too?

No.

> Maybe this is just a Cygwin limitation, or maybe Eshell is doing 
> something wrong with its built-in pipelines in this situation.

My guess is that it's the latter, but I don't know if it's worth pursuing this 
if Cygwin and EMBA are the only platforms on which there's a problem.  Of 
course, there might be other platforms and no one has reported it.

Once the test is fixed to succeed on Cygwin, we should probably revert the 
change that caused the extpipe tests to be skipped on EMBA, just to make sure 
that the same fix works there.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-18 20:51                 ` Ken Brown
@ 2022-06-18 22:00                   ` Jim Porter
  2022-06-18 23:46                     ` Sean Whitton
  2022-06-19 16:02                     ` Ken Brown
  0 siblings, 2 replies; 64+ messages in thread
From: Jim Porter @ 2022-06-18 22:00 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/18/2022 1:51 PM, Ken Brown wrote:
> On 6/18/2022 3:02 PM, Jim Porter wrote:
>> On 6/18/2022 10:52 AM, Ken Brown wrote:
>>> No, I'm seeing the same results on Emacs 28.  On both Emacs 28 and 
>>> Emacs 29, rev is apparently not seeing EOF unless echo outputs a 
>>> newline, so rev keeps waiting for input.
>>
>> Ah ha! Thanks for debugging this. The minimal fix then would be to 
>> change the command in em-extpipe-test-2 to either of these:
>>
>>    echo -N "bar" | rev *>temp
> 
> This doesn't work.  It still hangs when run interactively...

Just to confirm, the above command hangs, but the following works, correct?

   echo -N "bar" | rev

>>    *echo "bar" | rev *>temp
> 
> This works interactively...

All this makes me think that we could be dealing with a race condition 
in how Eshell pipes I/O around. Maybe there's a timing issue in 
`eshell-close-target' where we end up not sending EOF to the "rev" (or 
"sh") process?

I'd be interested to see the results if you ran `M-x trace-function' for 
`eshell-close-target' and `process-status' before trying these commands. 
`process-status' should return `run' when called from inside 
`eshell-close-target'. If it doesn't, then we'd neglect to send EOF to 
"rev" (or "sh"), which would cause a hang like what you're seeing.

If that's not the issue, then I'm not sure what the issue would be 
exactly, but poking around in `eshell-close-target', 
`eshell-insertion-filter', and `eshell-sentinel' might yield some useful 
info.

> My guess is that it's the latter, but I don't know if it's worth 
> pursuing this if Cygwin and EMBA are the only platforms on which there's 
> a problem.  Of course, there might be other platforms and no one has 
> reported it.

I think if we could figure out the real issue, it would be great to fix 
it. Though if we can't, it would probably be ok to just fix the test by 
avoiding the issue.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-18 22:00                   ` Jim Porter
@ 2022-06-18 23:46                     ` Sean Whitton
  2022-06-19 16:02                     ` Ken Brown
  1 sibling, 0 replies; 64+ messages in thread
From: Sean Whitton @ 2022-06-18 23:46 UTC (permalink / raw)
  To: Jim Porter, Ken Brown, 56025, Lars Magne Ingebrigtsen

Hello,

On Sat 18 Jun 2022 at 03:00PM -07, Jim Porter wrote:

> I think if we could figure out the real issue, it would be great to fix
> it. Though if we can't, it would probably be ok to just fix the test by
> avoiding the issue.

We might first want to add another general Eshell test which shows up
the problem, though?

-- 
Sean Whitton





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-18 22:00                   ` Jim Porter
  2022-06-18 23:46                     ` Sean Whitton
@ 2022-06-19 16:02                     ` Ken Brown
  2022-06-24  1:18                       ` Ken Brown
  1 sibling, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-06-19 16:02 UTC (permalink / raw)
  To: Jim Porter, Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/18/2022 6:00 PM, Jim Porter wrote:
> On 6/18/2022 1:51 PM, Ken Brown wrote:
>> On 6/18/2022 3:02 PM, Jim Porter wrote:
>>> On 6/18/2022 10:52 AM, Ken Brown wrote:
>>>> No, I'm seeing the same results on Emacs 28.  On both Emacs 28 and Emacs 29, 
>>>> rev is apparently not seeing EOF unless echo outputs a newline, so rev keeps 
>>>> waiting for input.
>>>
>>> Ah ha! Thanks for debugging this. The minimal fix then would be to change the 
>>> command in em-extpipe-test-2 to either of these:
>>>
>>>    echo -N "bar" | rev *>temp
>>
>> This doesn't work.  It still hangs when run interactively...
> 
> Just to confirm, the above command hangs, but the following works, correct?
> 
>    echo -N "bar" | rev

Correct.

>>>    *echo "bar" | rev *>temp
>>
>> This works interactively...
> 
> All this makes me think that we could be dealing with a race condition in how 
> Eshell pipes I/O around. Maybe there's a timing issue in `eshell-close-target' 
> where we end up not sending EOF to the "rev" (or "sh") process?

I think I've just discovered an anomaly in "rev" on Cygwin that could partially 
explain what I'm seeing.  I'll investigate that before proceeding further.

> I'd be interested to see the results if you ran `M-x trace-function' for 
> `eshell-close-target' and `process-status' before trying these commands. 
> `process-status' should return `run' when called from inside 
> `eshell-close-target'. If it doesn't, then we'd neglect to send EOF to "rev" (or 
> "sh"), which would cause a hang like what you're seeing.
> 
> If that's not the issue, then I'm not sure what the issue would be exactly, but 
> poking around in `eshell-close-target', `eshell-insertion-filter', and 
> `eshell-sentinel' might yield some useful info.
> 
>> My guess is that it's the latter, but I don't know if it's worth pursuing this 
>> if Cygwin and EMBA are the only platforms on which there's a problem.  Of 
>> course, there might be other platforms and no one has reported it.
> 
> I think if we could figure out the real issue, it would be great to fix it. 
> Though if we can't, it would probably be ok to just fix the test by avoiding the 
> issue.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-19 16:02                     ` Ken Brown
@ 2022-06-24  1:18                       ` Ken Brown
  2022-06-24  4:40                         ` Sean Whitton
  2022-06-24  6:07                         ` Eli Zaretskii
  0 siblings, 2 replies; 64+ messages in thread
From: Ken Brown @ 2022-06-24  1:18 UTC (permalink / raw)
  To: Jim Porter, Sean Whitton, 56025, Lars Magne Ingebrigtsen

On 6/19/2022 12:02 PM, Ken Brown wrote:
> On 6/18/2022 6:00 PM, Jim Porter wrote:
>> On 6/18/2022 1:51 PM, Ken Brown wrote:
>>> On 6/18/2022 3:02 PM, Jim Porter wrote:
>>>> On 6/18/2022 10:52 AM, Ken Brown wrote:
>>>>> No, I'm seeing the same results on Emacs 28.  On both Emacs 28 and Emacs 
>>>>> 29, rev is apparently not seeing EOF unless echo outputs a newline, so rev 
>>>>> keeps waiting for input.
>>>>
>>>> Ah ha! Thanks for debugging this. The minimal fix then would be to change 
>>>> the command in em-extpipe-test-2 to either of these:
>>>>
>>>>    echo -N "bar" | rev *>temp
>>>
>>> This doesn't work.  It still hangs when run interactively...
>>
>> Just to confirm, the above command hangs, but the following works, correct?
>>
>>    echo -N "bar" | rev
> 
> Correct.
> 
>>>>    *echo "bar" | rev *>temp
>>>
>>> This works interactively...
>>
>> All this makes me think that we could be dealing with a race condition in how 
>> Eshell pipes I/O around. Maybe there's a timing issue in `eshell-close-target' 
>> where we end up not sending EOF to the "rev" (or "sh") process?
> 
> I think I've just discovered an anomaly in "rev" on Cygwin that could partially 
> explain what I'm seeing.  I'll investigate that before proceeding further.

OK, I think I've got it sorted out now. The anomaly I referred to above is 
actually an anomaly in the stdio routines, not in "rev". It's discussed in item 
2 below. There are two issues.

1. I think there's a bug in eshell-close-target, in which it's assumed that 
sending C-d indicates end-of-file. This is only true if there's no input waiting 
to be read.  [In an interactive situation, this means we're at the beginning of 
a line.]  Otherwise, it takes a second C-d to indicate EOF.  So one C-d should 
suffice in the "echo -N bar" situation, but two are needed after "echo bar".

This bug probably went unnoticed because eshell-close-target was called twice in 
the case we were discussing, so process-send-eof was called twice.

2. On Cygwin and some other platforms, including Solaris 11.4 I think, it 
actually takes a third C-d, for reasons explained in the email thread starting 
at https://cygwin.com/pipermail/cygwin/2022-June/251672.html.  We're probably 
going to change this on Cygwin, but that still leaves other platforms.

The following patch resolves both issues:

diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 3644c1a18b..1c4131cb07 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -276,8 +276,8 @@ eshell-close-target
     ;; 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)))
+    (while (eq (process-status target) 'run)
+      (process-send-eof target)))

     ;; A plain function redirection needs no additional arguments
     ;; passed.

I'm about to go AFK for a few days.  If the eshell people agree that something 
like this patch should be installed, please go ahead.  I think it would then be 
worth re-enabling the extpipe tests on EMBA to see if the problem is fixed there 
too.

Ken





^ permalink raw reply related	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-24  1:18                       ` Ken Brown
@ 2022-06-24  4:40                         ` Sean Whitton
  2022-06-24  6:07                         ` Eli Zaretskii
  1 sibling, 0 replies; 64+ messages in thread
From: Sean Whitton @ 2022-06-24  4:40 UTC (permalink / raw)
  To: Ken Brown, Jim Porter, 56025, Lars Magne Ingebrigtsen

Hello,

On Thu 23 Jun 2022 at 09:18pm -04, Ken Brown wrote:

> diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
> index 3644c1a18b..1c4131cb07 100644
> --- a/lisp/eshell/esh-io.el
> +++ b/lisp/eshell/esh-io.el
> @@ -276,8 +276,8 @@ eshell-close-target
>      ;; 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)))
> +    (while (eq (process-status target) 'run)
> +      (process-send-eof target)))
>
>      ;; A plain function redirection needs no additional arguments
>      ;; passed.
>
> I'm about to go AFK for a few days.  If the eshell people agree that something
> like this patch should be installed, please go ahead.  I think it would then be
> worth re-enabling the extpipe tests on EMBA to see if the problem is fixed there
> too.

I'm a bit queasy about an unbounded loop here.  Why not just try three
times?  Or, better, try twice, and a third time only if we're on a
platform where we know it's needed.

Many thanks for the investigative work.

-- 
Sean Whitton





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-24  1:18                       ` Ken Brown
  2022-06-24  4:40                         ` Sean Whitton
@ 2022-06-24  6:07                         ` Eli Zaretskii
  2022-06-24 16:53                           ` Jim Porter
  1 sibling, 1 reply; 64+ messages in thread
From: Eli Zaretskii @ 2022-06-24  6:07 UTC (permalink / raw)
  To: Ken Brown; +Cc: jporterbugs, larsi, 56025, spwhitton

> Date: Thu, 23 Jun 2022 21:18:24 -0400
> From: Ken Brown <kbrown@cornell.edu>
> 
> 2. On Cygwin and some other platforms, including Solaris 11.4 I think, it 
> actually takes a third C-d, for reasons explained in the email thread starting 
> at https://cygwin.com/pipermail/cygwin/2022-June/251672.html.  We're probably 
> going to change this on Cygwin, but that still leaves other platforms.
> 
> The following patch resolves both issues:
> 
> diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
> index 3644c1a18b..1c4131cb07 100644
> --- a/lisp/eshell/esh-io.el
> +++ b/lisp/eshell/esh-io.el
> @@ -276,8 +276,8 @@ eshell-close-target
>      ;; 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)))
> +    (while (eq (process-status target) 'run)
> +      (process-send-eof target)))

Please add there comments explaining why this is done, or at least
point to relevant messages in this bug's discussion (NOT just to the
bug number, as the discussion is long and it will be hard to
understand what part of it is relevant).  Such "tricky" code should
always have comments explaining it.

Thanks.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-24  6:07                         ` Eli Zaretskii
@ 2022-06-24 16:53                           ` Jim Porter
  2022-06-24 22:23                             ` Sean Whitton
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-06-24 16:53 UTC (permalink / raw)
  To: Eli Zaretskii, Ken Brown; +Cc: larsi, 56025, spwhitton

[-- Attachment #1: Type: text/plain, Size: 1718 bytes --]

On 6/23/2022 9:40 PM, Sean Whitton wrote:
 > I'm a bit queasy about an unbounded loop here.  Why not just try three
 > times?  Or, better, try twice, and a third time only if we're on a
 > platform where we know it's needed.

How about the attached patch? I didn't check for specific platforms to 
enable the "third EOF" behavior, since a) it's hard to say for sure 
which platforms might have this issue (especially since Cygwin will be 
fixing it), and b) this lets us avoid worrying about Tramp compatibility.

 > Many thanks for the investigative work.

Agreed, this turned out to be a much subtler problem than I had 
initially suspected. Thanks!

On 6/23/2022 11:07 PM, Eli Zaretskii wrote:
> Please add there comments explaining why this is done, or at least
> point to relevant messages in this bug's discussion (NOT just to the
> bug number, as the discussion is long and it will be hard to
> understand what part of it is relevant).  Such "tricky" code should
> always have comments explaining it.

I added a comment explaining this to the best of my knowledge. There's 
one additional caveat I didn't mention there though, since it's only 
somewhat related. I believe this was mentioned earlier in the thread, 
but when Eshell creates a pipe, it routes both stdout and stderr to the 
next process's stdin (there's no way to control this behavior yet). When 
closing the handles from the initial process, it then calls 
`eshell-close-target' twice: once for stdout and once for stderr. Thus, 
with this patch, we'll call `process-send-eof' up to six times.

I'm not sure this is really a problem in practice today, but it might 
come up if Eshell gains the ability to redirect stdout and stderr 
separately.

[-- Attachment #2: 0001-When-closing-an-Eshell-process-target-send-EOF-three.patch --]
[-- Type: text/plain, Size: 2419 bytes --]

From a5f8dc8af6d199be8e3b09803835efa9d70b76a6 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Fri, 24 Jun 2022 09:14:38 -0700
Subject: [PATCH] When closing an Eshell process target, send EOF three times

* lisp/eshell/esh-io.el (eshell-close-target): Send EOF 3 times.

* test/lisp/eshell/em-extpipe-tests.el (em-extpipe-tests--deftest):
Re-enable these tests on EMBA.

This patch is adapted by one from Ken Brown, who uncovered the reason
for this bug (bug#56025).
---
 lisp/eshell/esh-io.el                | 15 +++++++++++++--
 test/lisp/eshell/em-extpipe-tests.el |  1 -
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 3644c1a18b..2d25186de7 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -276,8 +276,19 @@ eshell-close-target
    ;; 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 the POSIX standards, 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.  However, some platforms
+    ;; (e.g. Solaris) actually require a *third* EOF.  Since sending
+    ;; extra EOFs while the process is running shouldn't break
+    ;; anything, we'll just send the maximum we'd ever need.  See
+    ;; bug#56025 for further details.
+    (let ((i 0))
+      (while (and (<= (cl-incf i) 3)
+                  (eq (process-status target) 'run))
+        (process-send-eof target))))
 
    ;; A plain function redirection needs no additional arguments
    ;; passed.
diff --git a/test/lisp/eshell/em-extpipe-tests.el b/test/lisp/eshell/em-extpipe-tests.el
index 3b84d763ac..29f5dc0551 100644
--- a/test/lisp/eshell/em-extpipe-tests.el
+++ b/test/lisp/eshell/em-extpipe-tests.el
@@ -71,7 +71,6 @@ em-extpipe-tests--deftest
        (skip-unless shell-file-name)
        (skip-unless shell-command-switch)
        (skip-unless (executable-find shell-file-name))
-       (skip-unless (not (getenv "EMACS_EMBA_CI")))
        (let ((input ,input))
          (with-temp-eshell ,@body)))))
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-24 16:53                           ` Jim Porter
@ 2022-06-24 22:23                             ` Sean Whitton
  2022-06-24 23:03                               ` Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Sean Whitton @ 2022-06-24 22:23 UTC (permalink / raw)
  To: Jim Porter, Eli Zaretskii, Ken Brown; +Cc: larsi, 56025

Hello,

On Fri 24 Jun 2022 at 09:53AM -07, Jim Porter wrote:

> On 6/23/2022 9:40 PM, Sean Whitton wrote:
>  > I'm a bit queasy about an unbounded loop here.  Why not just try three
>  > times?  Or, better, try twice, and a third time only if we're on a
>  > platform where we know it's needed.
>
> How about the attached patch? I didn't check for specific platforms to
> enable the "third EOF" behavior, since a) it's hard to say for sure
> which platforms might have this issue (especially since Cygwin will be
> fixing it), and b) this lets us avoid worrying about Tramp compatibility.

Avoiding the TRAMP issues makes sense, but could you explain why you
don't think there could be an issue with sending a process too many
EOFs?  It's not immediately obvious to me.

-- 
Sean Whitton





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-24 22:23                             ` Sean Whitton
@ 2022-06-24 23:03                               ` Jim Porter
  2022-06-25  5:34                                 ` Eli Zaretskii
  2022-06-26 17:12                                 ` Sean Whitton
  0 siblings, 2 replies; 64+ messages in thread
From: Jim Porter @ 2022-06-24 23:03 UTC (permalink / raw)
  To: Sean Whitton, Eli Zaretskii, Ken Brown; +Cc: larsi, 56025

On 6/24/2022 3:23 PM, Sean Whitton wrote:
> On Fri 24 Jun 2022 at 09:53AM -07, Jim Porter wrote:
> 
>> How about the attached patch? I didn't check for specific platforms to
>> enable the "third EOF" behavior, since a) it's hard to say for sure
>> which platforms might have this issue (especially since Cygwin will be
>> fixing it), and b) this lets us avoid worrying about Tramp compatibility.
> 
> Avoiding the TRAMP issues makes sense, but could you explain why you
> don't think there could be an issue with sending a process too many
> EOFs?  It's not immediately obvious to me.

Eshell was already sending too many EOFs in some cases, and we haven't 
seen any issues with it (that I know of). For example, consider the command:

   *echo hi | rev

In this case, we send the string "hi\n" over the pipe, followed by 2 
EOFs (one from the stdout handle and one from the stderr handle). The 
POSIX standard says[1] (thanks to Eliot Moss on the Cygwin mailing list 
for citing this passage):

   When [EOF is] received, all the bytes waiting to be read are
   immediately passed to the process without waiting for a <newline>, and
   the EOF is discarded. Thus, if there are no bytes waiting (that is,
   the EOF occurred at the beginning of a line), a byte count of zero
   shall be returned from the read(), representing an end-of-file
   indication.

I interpret that to mean that the preferred way to indicate end-of-file 
to `rev' in this case is to send it "hi [NL] [EOF]". The second EOF that 
Eshell sends when closing the stderr output handle is superfluous, but 
it works fine as far as I can tell.

[1] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-24 23:03                               ` Jim Porter
@ 2022-06-25  5:34                                 ` Eli Zaretskii
  2022-06-25 16:13                                   ` Jim Porter
  2022-06-26 17:12                                 ` Sean Whitton
  1 sibling, 1 reply; 64+ messages in thread
From: Eli Zaretskii @ 2022-06-25  5:34 UTC (permalink / raw)
  To: Jim Porter; +Cc: larsi, 56025, spwhitton, kbrown

> Cc: larsi@gnus.org, 56025@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
> Date: Fri, 24 Jun 2022 16:03:13 -0700
> 
> POSIX standard says[1] (thanks to Eliot Moss on the Cygwin mailing list 
> for citing this passage):
> 
>    When [EOF is] received, all the bytes waiting to be read are
>    immediately passed to the process without waiting for a <newline>, and
>    the EOF is discarded. Thus, if there are no bytes waiting (that is,
>    the EOF occurred at the beginning of a line), a byte count of zero
>    shall be returned from the read(), representing an end-of-file
>    indication.

Perhaps some reference to this should also be in the comments.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-25  5:34                                 ` Eli Zaretskii
@ 2022-06-25 16:13                                   ` Jim Porter
  2022-06-25 16:53                                     ` Eli Zaretskii
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-06-25 16:13 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, 56025, spwhitton, kbrown

[-- Attachment #1: Type: text/plain, Size: 914 bytes --]

On 6/24/2022 10:34 PM, Eli Zaretskii wrote:
>> Cc: larsi@gnus.org, 56025@debbugs.gnu.org
>> From: Jim Porter <jporterbugs@gmail.com>
>> Date: Fri, 24 Jun 2022 16:03:13 -0700
>>
>> POSIX standard says[1] (thanks to Eliot Moss on the Cygwin mailing list
>> for citing this passage):
>>
>>     When [EOF is] received, all the bytes waiting to be read are
>>     immediately passed to the process without waiting for a <newline>, and
>>     the EOF is discarded. Thus, if there are no bytes waiting (that is,
>>     the EOF occurred at the beginning of a line), a byte count of zero
>>     shall be returned from the read(), representing an end-of-file
>>     indication.
> 
> Perhaps some reference to this should also be in the comments.

How about this patch? I added a reference to the specific section of the 
POSIX specification so that it's (hopefully) easy for people to look up 
if they need further details.

[-- Attachment #2: 0001-When-closing-an-Eshell-process-target-send-EOF-three.patch --]
[-- Type: text/plain, Size: 2493 bytes --]

From 8664e6f3dc9e40d1fae8a44dc4c444ab40510a18 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Fri, 24 Jun 2022 09:14:38 -0700
Subject: [PATCH] When closing an Eshell process target, send EOF three times

* lisp/eshell/esh-io.el (eshell-close-target): Send EOF 3 times.

* test/lisp/eshell/em-extpipe-tests.el (em-extpipe-tests--deftest):
Re-enable these tests on EMBA.

This patch is adapted by one from Ken Brown, who uncovered the reason
for this bug (bug#56025).
---
 lisp/eshell/esh-io.el                | 16 ++++++++++++++--
 test/lisp/eshell/em-extpipe-tests.el |  1 -
 2 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 3644c1a18b..c035890ddf 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -276,8 +276,20 @@ eshell-close-target
    ;; 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, 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 shouldn't break
+    ;; anything, we'll just send the maximum we'd ever need.  See
+    ;; bug#56025 for further details.
+    (let ((i 0))
+      (while (and (<= (cl-incf i) 3)
+                  (eq (process-status target) 'run))
+        (process-send-eof target))))
 
    ;; A plain function redirection needs no additional arguments
    ;; passed.
diff --git a/test/lisp/eshell/em-extpipe-tests.el b/test/lisp/eshell/em-extpipe-tests.el
index 3b84d763ac..29f5dc0551 100644
--- a/test/lisp/eshell/em-extpipe-tests.el
+++ b/test/lisp/eshell/em-extpipe-tests.el
@@ -71,7 +71,6 @@ em-extpipe-tests--deftest
        (skip-unless shell-file-name)
        (skip-unless shell-command-switch)
        (skip-unless (executable-find shell-file-name))
-       (skip-unless (not (getenv "EMACS_EMBA_CI")))
        (let ((input ,input))
          (with-temp-eshell ,@body)))))
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-25 16:13                                   ` Jim Porter
@ 2022-06-25 16:53                                     ` Eli Zaretskii
  2022-06-26 16:27                                       ` Lars Ingebrigtsen
  0 siblings, 1 reply; 64+ messages in thread
From: Eli Zaretskii @ 2022-06-25 16:53 UTC (permalink / raw)
  To: Jim Porter; +Cc: larsi, 56025, spwhitton, kbrown

> Cc: larsi@gnus.org, 56025@debbugs.gnu.org, spwhitton@email.arizona.edu,
>  kbrown@cornell.edu
> From: Jim Porter <jporterbugs@gmail.com>
> Date: Sat, 25 Jun 2022 09:13:46 -0700
> 
> >>     When [EOF is] received, all the bytes waiting to be read are
> >>     immediately passed to the process without waiting for a <newline>, and
> >>     the EOF is discarded. Thus, if there are no bytes waiting (that is,
> >>     the EOF occurred at the beginning of a line), a byte count of zero
> >>     shall be returned from the read(), representing an end-of-file
> >>     indication.
> > 
> > Perhaps some reference to this should also be in the comments.
> 
> How about this patch? I added a reference to the specific section of the 
> POSIX specification so that it's (hopefully) easy for people to look up 
> if they need further details.

LGTM, thanks.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-25 16:53                                     ` Eli Zaretskii
@ 2022-06-26 16:27                                       ` Lars Ingebrigtsen
  0 siblings, 0 replies; 64+ messages in thread
From: Lars Ingebrigtsen @ 2022-06-26 16:27 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Jim Porter, 56025, spwhitton, kbrown

Eli Zaretskii <eliz@gnu.org> writes:

>> How about this patch? I added a reference to the specific section of the 
>> POSIX specification so that it's (hopefully) easy for people to look up 
>> if they need further details.
>
> LGTM, thanks.

Pushed to Emacs 29 now.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-24 23:03                               ` Jim Porter
  2022-06-25  5:34                                 ` Eli Zaretskii
@ 2022-06-26 17:12                                 ` Sean Whitton
  2022-06-26 17:22                                   ` Jim Porter
  1 sibling, 1 reply; 64+ messages in thread
From: Sean Whitton @ 2022-06-26 17:12 UTC (permalink / raw)
  To: Jim Porter, Eli Zaretskii, Ken Brown; +Cc: larsi, 56025

Hello,

On Fri 24 Jun 2022 at 04:03pm -07, Jim Porter wrote:

>    When [EOF is] received, all the bytes waiting to be read are
>    immediately passed to the process without waiting for a <newline>, and
>    the EOF is discarded. Thus, if there are no bytes waiting (that is,
>    the EOF occurred at the beginning of a line), a byte count of zero
>    shall be returned from the read(), representing an end-of-file
>    indication.
>
> I interpret that to mean that the preferred way to indicate end-of-file
> to `rev' in this case is to send it "hi [NL] [EOF]". The second EOF that
> Eshell sends when closing the stderr output handle is superfluous, but
> it works fine as far as I can tell.
>
> [1] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html

The text states unconditionally that when an EOF is received it is
discarded by the OS.  So we can infer that it's fine to send three,
according to the standard -- it's not just that it happens to work.

Thanks again for working on this.

-- 
Sean Whitton





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-26 17:12                                 ` Sean Whitton
@ 2022-06-26 17:22                                   ` Jim Porter
  2022-06-26 21:11                                     ` Sean Whitton
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-06-26 17:22 UTC (permalink / raw)
  To: Sean Whitton, Eli Zaretskii, Ken Brown; +Cc: larsi, 56025

On 6/26/2022 10:12 AM, Sean Whitton wrote:
> On Fri 24 Jun 2022 at 04:03pm -07, Jim Porter wrote:
> 
>>     When [EOF is] received, all the bytes waiting to be read are
>>     immediately passed to the process without waiting for a <newline>, and
>>     the EOF is discarded. Thus, if there are no bytes waiting (that is,
>>     the EOF occurred at the beginning of a line), a byte count of zero
>>     shall be returned from the read(), representing an end-of-file
>>     indication.
>>
>> I interpret that to mean that the preferred way to indicate end-of-file
>> to `rev' in this case is to send it "hi [NL] [EOF]". The second EOF that
>> Eshell sends when closing the stderr output handle is superfluous, but
>> it works fine as far as I can tell.
>>
>> [1] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html
> 
> The text states unconditionally that when an EOF is received it is
> discarded by the OS.  So we can infer that it's fine to send three,
> according to the standard -- it's not just that it happens to work.
> 
> Thanks again for working on this.

Ah, good catch. I glossed over the last sentence in that paragraph in 
the spec (hence why I didn't copy-paste it).





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-26 17:22                                   ` Jim Porter
@ 2022-06-26 21:11                                     ` Sean Whitton
  2022-06-27 13:25                                       ` Ken Brown
  0 siblings, 1 reply; 64+ messages in thread
From: Sean Whitton @ 2022-06-26 21:11 UTC (permalink / raw)
  To: Jim Porter, Eli Zaretskii, Ken Brown; +Cc: larsi, 56025

Hello,

On Sun 26 Jun 2022 at 10:22AM -07, Jim Porter wrote:

> On 6/26/2022 10:12 AM, Sean Whitton wrote:
>> On Fri 24 Jun 2022 at 04:03pm -07, Jim Porter wrote:
>>
>>>     When [EOF is] received, all the bytes waiting to be read are
>>>     immediately passed to the process without waiting for a <newline>, and
>>>     the EOF is discarded. Thus, if there are no bytes waiting (that is,
>>>     the EOF occurred at the beginning of a line), a byte count of zero
>>>     shall be returned from the read(), representing an end-of-file
>>>     indication.
>>>
>>> I interpret that to mean that the preferred way to indicate end-of-file
>>> to `rev' in this case is to send it "hi [NL] [EOF]". The second EOF that
>>> Eshell sends when closing the stderr output handle is superfluous, but
>>> it works fine as far as I can tell.
>>>
>>> [1] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html
>>
>> The text states unconditionally that when an EOF is received it is
>> discarded by the OS.  So we can infer that it's fine to send three,
>> according to the standard -- it's not just that it happens to work.
>>
>> Thanks again for working on this.
>
> Ah, good catch. I glossed over the last sentence in that paragraph in
> the spec (hence why I didn't copy-paste it).

I was actually thinking it was implied by the first sentence of what you
quoted.

-- 
Sean Whitton





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-26 21:11                                     ` Sean Whitton
@ 2022-06-27 13:25                                       ` Ken Brown
  2022-06-27 15:51                                         ` Michael Albinus
  2022-06-27 19:18                                         ` Jim Porter
  0 siblings, 2 replies; 64+ messages in thread
From: Ken Brown @ 2022-06-27 13:25 UTC (permalink / raw)
  To: Sean Whitton, Jim Porter, Eli Zaretskii; +Cc: larsi, 56025

Thanks to all of you for working on this while I was gone.  Unfortunately, the 
problem is still present on Cygwin.  In my haste to get away, I neglected to 
mention that there is apparently a timing issue in Eshell on Cygwin, so that 
even three EOFs do not always suffice to kill the process.

My test case is to run

   echo bar | sh -c rev

in Eshell.  For reasons I don't understand, EOF almost always has to be sent 
more than 3 times times before the "sh" process dies.  The maximum I've observed 
is 93.  Inserting "(sit-for 0.01)" after each EOF eliminates the need for extra 
EOFs; this is why I referred to the problem as a timing issue.

I propose the following workaround:

--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -284,10 +284,16 @@ eshell-close-target
      ;; 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 shouldn't break
-    ;; anything, we'll just send the maximum we'd ever need.  See
-    ;; bug#56025 for further details.
-    (let ((i 0))
-      (while (and (<= (cl-incf i) 3)
+    ;; anything, we'll send up to three on all platforms.
+
+    ;; There's an extra wrinkle on Cygwin where, apparently due to an
+    ;; unknown timing issue, it sometimes takes more than three EOFs
+    ;; to kill the process.  (This only happens in Eshell, not in an
+    ;; ordinary Cygwin shell.)  We work around this problem by sending
+    ;; up to 1000 EOFs on Cygwin.  See bug#56025 for further details.
+    (let ((i 0)
+          (n (if (eq system-type 'cygwin) 1000 3)))
+      (while (and (<= (cl-incf i) n)
                    (eq (process-status target) 'run))
          (process-send-eof target))))


Ken

P.S. Has anyone checked to see if the extpipe tests are now passing on EMBA?  If 
not, maybe the workaround is needed there too.  Alternatively, we could simply 
use the workaround on all platforms.  I don't see what harm it could do.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-27 13:25                                       ` Ken Brown
@ 2022-06-27 15:51                                         ` Michael Albinus
  2022-06-27 16:22                                           ` Ken Brown
  2022-06-27 19:18                                         ` Jim Porter
  1 sibling, 1 reply; 64+ messages in thread
From: Michael Albinus @ 2022-06-27 15:51 UTC (permalink / raw)
  To: Ken Brown; +Cc: larsi, Jim Porter, Eli Zaretskii, 56025, Sean Whitton

Ken Brown <kbrown@cornell.edu> writes:

Hi Ken,

> P.S. Has anyone checked to see if the extpipe tests are now passing on
> EMBA?

Seems to work there. <https://emba.gnu.org/emacs/emacs/-/jobs/48285>

Best regards, Michael.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-27 15:51                                         ` Michael Albinus
@ 2022-06-27 16:22                                           ` Ken Brown
  2022-06-27 19:13                                             ` bug#56025: [EXT]Re: " Sean Whitton
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-06-27 16:22 UTC (permalink / raw)
  To: Michael Albinus; +Cc: larsi, Jim Porter, Eli Zaretskii, 56025, Sean Whitton

On 6/27/2022 11:51 AM, Michael Albinus wrote:>> P.S. Has anyone checked to see 
if the extpipe tests are now passing on
>> EMBA?
> 
> Seems to work there. <https://emba.gnu.org/emacs/emacs/-/jobs/48285>

Thanks, Michael.  In that case we should probably go ahead with the Cygwin fix, 
but I'll wait to hear from Eli or Lars.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [EXT]Re: bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-27 16:22                                           ` Ken Brown
@ 2022-06-27 19:13                                             ` Sean Whitton
  2022-06-27 21:17                                               ` Ken Brown
  0 siblings, 1 reply; 64+ messages in thread
From: Sean Whitton @ 2022-06-27 19:13 UTC (permalink / raw)
  To: Ken Brown, Michael Albinus; +Cc: Jim Porter, Eli Zaretskii, 56025, larsi

Hello,

On Mon 27 Jun 2022 at 12:22pm -04, Ken Brown wrote:

> External Email
>
> On 6/27/2022 11:51 AM, Michael Albinus wrote:>> P.S. Has anyone checked to see
> if the extpipe tests are now passing on
>>> EMBA?
>>
>> Seems to work there. <https://emba.gnu.org/emacs/emacs/-/jobs/48285>
>
> Thanks, Michael.  In that case we should probably go ahead with the Cygwin fix,
> but I'll wait to hear from Eli or Lars.

The three times fix or the thousand times fix, do you mean?

-- 
Sean Whitton





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-27 13:25                                       ` Ken Brown
  2022-06-27 15:51                                         ` Michael Albinus
@ 2022-06-27 19:18                                         ` Jim Porter
  2022-06-27 21:19                                           ` Ken Brown
  1 sibling, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-06-27 19:18 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 6/27/2022 6:25 AM, Ken Brown wrote:
> Thanks to all of you for working on this while I was gone.  
> Unfortunately, the problem is still present on Cygwin.  In my haste to 
> get away, I neglected to mention that there is apparently a timing issue 
> in Eshell on Cygwin, so that even three EOFs do not always suffice to 
> kill the process.
> 
> My test case is to run
> 
>    echo bar | sh -c rev
> 
> in Eshell.  For reasons I don't understand, EOF almost always has to be 
> sent more than 3 times times before the "sh" process dies.  The maximum 
> I've observed is 93.  Inserting "(sit-for 0.01)" after each EOF 
> eliminates the need for extra EOFs; this is why I referred to the 
> problem as a timing issue.
> 
> I propose the following workaround:
> 
> --- a/lisp/eshell/esh-io.el
> +++ b/lisp/eshell/esh-io.el
> @@ -284,10 +284,16 @@ eshell-close-target
>       ;; 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 shouldn't break
> -    ;; anything, we'll just send the maximum we'd ever need.  See
> -    ;; bug#56025 for further details.
> -    (let ((i 0))
> -      (while (and (<= (cl-incf i) 3)
> +    ;; anything, we'll send up to three on all platforms.
> +
> +    ;; There's an extra wrinkle on Cygwin where, apparently due to an
> +    ;; unknown timing issue, it sometimes takes more than three EOFs
> +    ;; to kill the process.  (This only happens in Eshell, not in an
> +    ;; ordinary Cygwin shell.)  We work around this problem by sending
> +    ;; up to 1000 EOFs on Cygwin.  See bug#56025 for further details.
> +    (let ((i 0)
> +          (n (if (eq system-type 'cygwin) 1000 3)))
> +      (while (and (<= (cl-incf i) n)
>                     (eq (process-status target) 'run))
>           (process-send-eof target))))

I'd be very hesitant to do this, since as you mention above, this seems 
like a timing issue, and it's entirely possible that there are other, 
more widespread issues on Cygwin here. We'd also want to check the 
system that the process is actually running on; otherwise, remoting into 
a Cygwin system (via Tramp) would still exhibit the problem. I'll see if 
I can get a Cygwin environment up to test things out in the next week-ish.

If there's no other way that we can come up with here, I'd lean towards 
a defcustom so that users can tweak this if needed.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [EXT]Re: bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-27 19:13                                             ` bug#56025: [EXT]Re: " Sean Whitton
@ 2022-06-27 21:17                                               ` Ken Brown
  0 siblings, 0 replies; 64+ messages in thread
From: Ken Brown @ 2022-06-27 21:17 UTC (permalink / raw)
  To: Sean Whitton, Michael Albinus; +Cc: Jim Porter, Eli Zaretskii, 56025, larsi

On 6/27/2022 3:13 PM, Sean Whitton wrote:
> Hello,
> 
> On Mon 27 Jun 2022 at 12:22pm -04, Ken Brown wrote:
> 
>> External Email
>>
>> On 6/27/2022 11:51 AM, Michael Albinus wrote:>> P.S. Has anyone checked to see
>> if the extpipe tests are now passing on
>>>> EMBA?
>>>
>>> Seems to work there. <https://emba.gnu.org/emacs/emacs/-/jobs/48285>
>>
>> Thanks, Michael.  In that case we should probably go ahead with the Cygwin fix,
>> but I'll wait to hear from Eli or Lars.
> 
> The three times fix or the thousand times fix, do you mean?

I meant the fix that's 1000 on Cygwin and 3 elsewhere.  But let's see if Jim 
comes up with something better.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-27 19:18                                         ` Jim Porter
@ 2022-06-27 21:19                                           ` Ken Brown
  2022-07-01  3:52                                             ` Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-06-27 21:19 UTC (permalink / raw)
  To: Jim Porter, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 6/27/2022 3:18 PM, Jim Porter wrote:
> On 6/27/2022 6:25 AM, Ken Brown wrote:
>> Thanks to all of you for working on this while I was gone. Unfortunately, the 
>> problem is still present on Cygwin.  In my haste to get away, I neglected to 
>> mention that there is apparently a timing issue in Eshell on Cygwin, so that 
>> even three EOFs do not always suffice to kill the process.
>>
>> My test case is to run
>>
>>    echo bar | sh -c rev
>>
>> in Eshell.  For reasons I don't understand, EOF almost always has to be sent 
>> more than 3 times times before the "sh" process dies.  The maximum I've 
>> observed is 93.  Inserting "(sit-for 0.01)" after each EOF eliminates the need 
>> for extra EOFs; this is why I referred to the problem as a timing issue.
>>
>> I propose the following workaround:
>>
>> --- a/lisp/eshell/esh-io.el
>> +++ b/lisp/eshell/esh-io.el
>> @@ -284,10 +284,16 @@ eshell-close-target
>>       ;; 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 shouldn't break
>> -    ;; anything, we'll just send the maximum we'd ever need.  See
>> -    ;; bug#56025 for further details.
>> -    (let ((i 0))
>> -      (while (and (<= (cl-incf i) 3)
>> +    ;; anything, we'll send up to three on all platforms.
>> +
>> +    ;; There's an extra wrinkle on Cygwin where, apparently due to an
>> +    ;; unknown timing issue, it sometimes takes more than three EOFs
>> +    ;; to kill the process.  (This only happens in Eshell, not in an
>> +    ;; ordinary Cygwin shell.)  We work around this problem by sending
>> +    ;; up to 1000 EOFs on Cygwin.  See bug#56025 for further details.
>> +    (let ((i 0)
>> +          (n (if (eq system-type 'cygwin) 1000 3)))
>> +      (while (and (<= (cl-incf i) n)
>>                     (eq (process-status target) 'run))
>>           (process-send-eof target))))
> 
> I'd be very hesitant to do this, since as you mention above, this seems like a 
> timing issue, and it's entirely possible that there are other, more widespread 
> issues on Cygwin here. We'd also want to check the system that the process is 
> actually running on; otherwise, remoting into a Cygwin system (via Tramp) would 
> still exhibit the problem. I'll see if I can get a Cygwin environment up to test 
> things out in the next week-ish.

OK, thanks.  Let me know if you need any help with that.

> If there's no other way that we can come up with here, I'd lean towards a 
> defcustom so that users can tweak this if needed.

Sounds good.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-06-27 21:19                                           ` Ken Brown
@ 2022-07-01  3:52                                             ` Jim Porter
  2022-07-01  3:58                                               ` Jim Porter
  2022-07-06 22:33                                               ` Ken Brown
  0 siblings, 2 replies; 64+ messages in thread
From: Jim Porter @ 2022-07-01  3:52 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 6/27/2022 2:19 PM, Ken Brown wrote:
> On 6/27/2022 3:18 PM, Jim Porter wrote:
>> I'd be very hesitant to do this, since as you mention above, this 
>> seems like a timing issue, and it's entirely possible that there are 
>> other, more widespread issues on Cygwin here. We'd also want to check 
>> the system that the process is actually running on; otherwise, 
>> remoting into a Cygwin system (via Tramp) would still exhibit the 
>> problem. I'll see if I can get a Cygwin environment up to test things 
>> out in the next week-ish.
> 
> OK, thanks.  Let me know if you need any help with that.

Ok, I've got Cygwin set up (though I'm just using the prebuilt Cygwin 
Emacs for now). I can confirm that the following hangs until I send 
another EOF via `C-c C-d':

   echo hi | rev

However, if I evaluate the following first, the above command works just 
fine:

   (add-to-list 'eshell-needs-pipe "rev")

Normally, Eshell starts each process using ptys to control them. 
However, the above Elisp code tells Eshell to use a pipe for "rev"[1]. 
I'm not totally clear on all the subtleties here, but it seems to me 
that it would make more sense for rev to use a pipe for its stdin and a 
pty for its stdout. That's not possible with subprocesses in Emacs 
though (as far as I know).

However, I don't think this fully answers things, since I also see 
inconsistent results if I run "echo hi | rev" a bunch of times. 
Sometimes it prints "ih" and then I need to hit `C-c C-d` once to stop 
it. Other times it doesn't print anything and I need to hit `C-c C-d' 
twice. There must be some kind of race condition, maybe in Emacs's 
src/process.c?

Hopefully this helps get us closer to a proper answer here though...

[1] Only when rev is being piped *to* in an Eshell pipeline.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-01  3:52                                             ` Jim Porter
@ 2022-07-01  3:58                                               ` Jim Porter
  2022-07-06 22:33                                               ` Ken Brown
  1 sibling, 0 replies; 64+ messages in thread
From: Jim Porter @ 2022-07-01  3:58 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 6/30/2022 8:52 PM, Jim Porter wrote:
> Normally, Eshell starts each process using ptys to control them. 
> However, the above Elisp code tells Eshell to use a pipe for "rev"[1]. 
> I'm not totally clear on all the subtleties here, but it seems to me 
> that it would make more sense for rev to use a pipe for its stdin and a 
> pty for its stdout. That's not possible with subprocesses in Emacs 
> though (as far as I know).

Oh, I forgot to mention that bug#56013 might be related to this in some 
way, since it exhibits some problems with ptys when used in Eshell 
pipelines too.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-01  3:52                                             ` Jim Porter
  2022-07-01  3:58                                               ` Jim Porter
@ 2022-07-06 22:33                                               ` Ken Brown
  2022-07-07  4:35                                                 ` Jim Porter
  1 sibling, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-07-06 22:33 UTC (permalink / raw)
  To: Jim Porter, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 6/30/2022 11:52 PM, Jim Porter wrote:
> On 6/27/2022 2:19 PM, Ken Brown wrote:
>> On 6/27/2022 3:18 PM, Jim Porter wrote:
>>> I'd be very hesitant to do this, since as you mention above, this seems like 
>>> a timing issue, and it's entirely possible that there are other, more 
>>> widespread issues on Cygwin here. We'd also want to check the system that the 
>>> process is actually running on; otherwise, remoting into a Cygwin system (via 
>>> Tramp) would still exhibit the problem. I'll see if I can get a Cygwin 
>>> environment up to test things out in the next week-ish.
>>
>> OK, thanks.  Let me know if you need any help with that.
> 
> Ok, I've got Cygwin set up (though I'm just using the prebuilt Cygwin Emacs for 
> now). I can confirm that the following hangs until I send another EOF via `C-c 
> C-d':
> 
>    echo hi | rev

Yes, but that's because of the behavior of certain platforms (e.g., Cygwin and 
Solaris) with respect to EOF, as I said in an earlier message.  We've changed 
that for Cygwin, so that Cygwin now behaves the same as GNU/Linux, but the 
change won't take effect until Cygwin 3.4.0 is released.  In any case, that 
issue has already been fixed on the master branch.

Aside from that issue, I never had an issue with

   echo hi | rev

but only with

   echo hi | sh -c rev

I have no idea why that should be different.

> However, if I evaluate the following first, the above command works just fine:
> 
>    (add-to-list 'eshell-needs-pipe "rev")
> 
> Normally, Eshell starts each process using ptys to control them. However, the 
> above Elisp code tells Eshell to use a pipe for "rev"[1].

That makes sense.  You're no longer relying on Eshell sending EOF to rev, but 
rather you're letting rev discover EOF because no process holds the pipe open 
for writing, forcing any pending read to stop blocking.

And, for the same reason, evaluating

   (add-to-list 'eshell-needs-pipe "sh")

solves the problem with "echo hi | sh -c rev".

> I'm not totally clear 
> on all the subtleties here, but it seems to me that it would make more sense for 
> rev to use a pipe for its stdin and a pty for its stdout. That's not possible 
> with subprocesses in Emacs though (as far as I know).
> 
> However, I don't think this fully answers things, since I also see inconsistent 
> results if I run "echo hi | rev" a bunch of times. Sometimes it prints "ih" and 
> then I need to hit `C-c C-d` once to stop it. Other times it doesn't print 
> anything and I need to hit `C-c C-d' twice.

Interesting.  I've never seen that.  It's as though "rev" just didn't get one of 
the EOFs.

> There must be some kind of race 
> condition, maybe in Emacs's src/process.c?

I'll poke around, but I'm no expert on how this all works.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-06 22:33                                               ` Ken Brown
@ 2022-07-07  4:35                                                 ` Jim Porter
  2022-07-07  4:42                                                   ` Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-07-07  4:35 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 7/6/2022 3:33 PM, Ken Brown wrote:
> On 6/30/2022 11:52 PM, Jim Porter wrote:
>> Ok, I've got Cygwin set up (though I'm just using the prebuilt Cygwin 
>> Emacs for now). I can confirm that the following hangs until I send 
>> another EOF via `C-c C-d':
>>
>>    echo hi | rev
> 
> Yes, but that's because of the behavior of certain platforms (e.g., 
> Cygwin and Solaris) with respect to EOF, as I said in an earlier 
> message.

Yeah, I think that's fine, and the change to send an extra EOF for 
compatibility with (non-master) Cygwin and Solaris makes sense to me. I 
just wanted to be sure to mention that I could see the issue too so that 
I can (hopefully) verify that it's fixed if/when we come up with a 
more-reliable fix.

>> However, if I evaluate the following first, the above command works 
>> just fine:
>>
>>    (add-to-list 'eshell-needs-pipe "rev")
>>
>> Normally, Eshell starts each process using ptys to control them. 
>> However, the above Elisp code tells Eshell to use a pipe for "rev"[1].
> 
> That makes sense.  You're no longer relying on Eshell sending EOF to 
> rev, but rather you're letting rev discover EOF because no process holds 
> the pipe open for writing, forcing any pending read to stop blocking.

Maybe it would be good to do it this way in general though, since this 
would let us completely avoid the behavioral differences of EOF on 
various platforms. I believe using a pipe should work consistently 
everywhere, right? (It would also probably fix some other issues with 
Eshell pipelines, but I'll need to read up on ptys, since it's been a 
long time since I've done anything with them.)

>> However, I don't think this fully answers things, since I also see 
>> inconsistent results if I run "echo hi | rev" a bunch of times. 
>> Sometimes it prints "ih" and then I need to hit `C-c C-d` once to stop 
>> it. Other times it doesn't print anything and I need to hit `C-c C-d' 
>> twice.
> 
> Interesting.  I've never seen that.  It's as though "rev" just didn't 
> get one of the EOFs.

Yeah, that's what it seems like to me too. I'm not able to reproduce 
this on GNU/Linux (at least not yet; I'll try some more things out). 
I'll keep poking at the Cygwin version too, and start experimenting with 
Emacs's src/process.c to try and allow using a pty for only stdin *or* 
stdout (instead of both). I think that would make Eshell's pipelines 
behaves more like other shells, which would squash a lot of bugs in this 
area.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-07  4:35                                                 ` Jim Porter
@ 2022-07-07  4:42                                                   ` Jim Porter
  2022-07-07 12:42                                                     ` Ken Brown
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-07-07  4:42 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 7/6/2022 9:35 PM, Jim Porter wrote:
> Maybe it would be good to do it this way in general though, since this 
> would let us completely avoid the behavioral differences of EOF on 
> various platforms. I believe using a pipe should work consistently 
> everywhere, right? (It would also probably fix some other issues with 
> Eshell pipelines, but I'll need to read up on ptys, since it's been a 
> long time since I've done anything with them.)

Just to clarify: by "do it this way in general", I only mean using a 
pipe when connecting commands in Eshell via "|", not using 
`eshell-needs-pipe'. This would necessitate enhancing `make-process' and 
friends to support what I described elsewhere.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-07  4:42                                                   ` Jim Porter
@ 2022-07-07 12:42                                                     ` Ken Brown
  2022-07-17  2:35                                                       ` bug#56025: [WIP PATCH] " Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-07-07 12:42 UTC (permalink / raw)
  To: Jim Porter, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 7/7/2022 12:42 AM, Jim Porter wrote:
> On 7/6/2022 9:35 PM, Jim Porter wrote:
>> Maybe it would be good to do it this way in general though, since this would 
>> let us completely avoid the behavioral differences of EOF on various 
>> platforms. I believe using a pipe should work consistently everywhere, right? 
>> (It would also probably fix some other issues with Eshell pipelines, but I'll 
>> need to read up on ptys, since it's been a long time since I've done anything 
>> with them.)
> 
> Just to clarify: by "do it this way in general", I only mean using a pipe when 
> connecting commands in Eshell via "|", not using `eshell-needs-pipe'. This would 
> necessitate enhancing `make-process' and friends to support what I described 
> elsewhere.

That makes sense to me.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-07 12:42                                                     ` Ken Brown
@ 2022-07-17  2:35                                                       ` Jim Porter
  2022-07-17  6:03                                                         ` Eli Zaretskii
  2022-07-17 21:59                                                         ` Ken Brown
  0 siblings, 2 replies; 64+ messages in thread
From: Jim Porter @ 2022-07-17  2:35 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

[-- Attachment #1: Type: text/plain, Size: 1504 bytes --]

On 7/7/2022 5:42 AM, Ken Brown wrote:
> On 7/7/2022 12:42 AM, Jim Porter wrote:
>> On 7/6/2022 9:35 PM, Jim Porter wrote:
>>> Maybe it would be good to do it this way in general though, since 
>>> this would let us completely avoid the behavioral differences of EOF 
>>> on various platforms. I believe using a pipe should work consistently 
>>> everywhere, right? (It would also probably fix some other issues with 
>>> Eshell pipelines, but I'll need to read up on ptys, since it's been a 
>>> long time since I've done anything with them.)
>>
>> Just to clarify: by "do it this way in general", I only mean using a 
>> pipe when connecting commands in Eshell via "|", not using 
>> `eshell-needs-pipe'. This would necessitate enhancing `make-process' 
>> and friends to support what I described elsewhere.
> 
> That makes sense to me.

Ok, attached is a WIP patch to do this. It seems to work for me under 
Cygwin, although I've only lightly tested it in that environment. If 
this works for you too, I'll finish cleaning this up and add 
tests/documentation for it.

Note that in my patch, I temporarily undid my previous patch to send EOF 
multiple times. This is just for testing purposes, but since we're using 
a pipe for this connection now, a single call to `process-send-eof' 
should be sufficient. (There are some obscure cases where we might want 
to keep the current behavior, like redirecting to a process created some 
other way, so I think it makes sense to keep that code. Probably...)

[-- Attachment #2: 0001-WIP-Allow-using-PTYs-for-just-stdin-or-stdout-in-mak.patch --]
[-- Type: text/plain, Size: 17269 bytes --]

From f82c12f07ffc7c6c9db1232c7f6721e0a1ce300b Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sat, 16 Jul 2022 16:49:43 -0700
Subject: [PATCH] WIP: Allow using PTYs for just stdin or stdout in
 make-process

---
 lisp/eshell/esh-io.el   |   2 +-
 lisp/eshell/esh-proc.el |  48 +++++++------------
 src/callproc.c          |  37 ++++++++------
 src/lisp.h              |   3 +-
 src/process.c           | 104 ++++++++++++++++++++++++----------------
 src/process.h           |   5 +-
 6 files changed, 108 insertions(+), 91 deletions(-)

diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index c035890ddf..2f5091000b 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -287,7 +287,7 @@ eshell-close-target
     ;; anything, we'll just send the maximum we'd ever need.  See
     ;; bug#56025 for further details.
     (let ((i 0))
-      (while (and (<= (cl-incf i) 3)
+      (while (and (<= (cl-incf i) 1)    ; FIXME: Put this back at 3.
                   (eq (process-status target) 'run))
         (process-send-eof target))))
 
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 70426ccaf2..f5421ddd28 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -250,30 +250,6 @@ eshell-last-sync-output-start
   "A marker that tracks the beginning of output of the last subprocess.
 Used only on systems which do not support async subprocesses.")
 
-(defvar eshell-needs-pipe
-  '("bc"
-    ;; xclip.el (in GNU ELPA) calls all of these with
-    ;; `process-connection-type' set to nil.
-    "pbpaste" "putclip" "xclip" "xsel" "wl-copy")
-  "List of commands which need `process-connection-type' to be nil.
-Currently only affects commands in pipelines, and not those at
-the front.  If an element contains a directory part it must match
-the full name of a command, otherwise just the nondirectory part must match.")
-
-(defun eshell-needs-pipe-p (command)
-  "Return non-nil if COMMAND needs `process-connection-type' to be nil.
-See `eshell-needs-pipe'."
-  (and (bound-and-true-p eshell-in-pipeline-p)
-       (not (eq eshell-in-pipeline-p 'first))
-       ;; FIXME should this return non-nil for anything that is
-       ;; neither 'first nor 'last?  See bug#1388 discussion.
-       (catch 'found
-	 (dolist (exe eshell-needs-pipe)
-	   (if (string-equal exe (if (string-search "/" exe)
-				     command
-				   (file-name-nondirectory command)))
-	       (throw 'found t))))))
-
 (defun eshell-gather-process-output (command args)
   "Gather the output from COMMAND + ARGS."
   (require 'esh-var)
@@ -290,12 +266,24 @@ eshell-gather-process-output
     (cond
      ((fboundp 'make-process)
       (setq proc
-	    (let ((process-connection-type
-		   (unless (eshell-needs-pipe-p command)
-		     process-connection-type))
-		  (command (file-local-name (expand-file-name command))))
-	      (apply #'start-file-process
-		     (file-name-nondirectory command) nil command args)))
+	    (let ((command (file-local-name (expand-file-name command)))
+                  ;; FIXME: This is a bad way to do this, but it
+                  ;; should suffice for a proof of concept...
+                  (conn-type (cond
+                              ((eq eshell-in-pipeline-p 'first) 1)
+                              ((eq eshell-in-pipeline-p 'last) 2)
+                              (eshell-in-pipeline-p 0)
+                              (t 3))))
+              (make-process
+               :name (file-name-nondirectory command)
+               :buffer (current-buffer)
+               :command (cons command args)
+               :filter (if (eshell-interactive-output-p)
+	                   #'eshell-output-filter
+                         #'eshell-insertion-filter)
+               :sentinel #'eshell-sentinel
+               :connection-type conn-type
+               :file-handler t)))
       (eshell-record-process-object proc)
       (set-process-buffer proc (current-buffer))
       (set-process-filter proc (if (eshell-interactive-output-p)
diff --git a/src/callproc.c b/src/callproc.c
index dd162f36a6..aec0a2f5a5 100644
--- a/src/callproc.c
+++ b/src/callproc.c
@@ -650,7 +650,7 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd,
 
   child_errno
     = emacs_spawn (&pid, filefd, fd_output, fd_error, new_argv, env,
-                   SSDATA (current_dir), NULL, &oldset);
+                   SSDATA (current_dir), NULL, false, false, &oldset);
   eassert ((child_errno == 0) == (0 < pid));
 
   if (pid > 0)
@@ -1412,14 +1412,15 @@ emacs_posix_spawn_init_attributes (posix_spawnattr_t *attributes,
 int
 emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
              char **argv, char **envp, const char *cwd,
-             const char *pty, const sigset_t *oldset)
+             const char *pty_name, bool pty_in, bool pty_out,
+             const sigset_t *oldset)
 {
 #if USABLE_POSIX_SPAWN
   /* Prefer the simpler `posix_spawn' if available.  `posix_spawn'
      doesn't yet support setting up pseudoterminals, so we fall back
      to `vfork' if we're supposed to use a pseudoterminal.  */
 
-  bool use_posix_spawn = pty == NULL;
+  bool use_posix_spawn = pty_name == NULL;
 
   posix_spawn_file_actions_t actions;
   posix_spawnattr_t attributes;
@@ -1473,7 +1474,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   /* vfork, and prevent local vars from being clobbered by the vfork.  */
   pid_t *volatile newpid_volatile = newpid;
   const char *volatile cwd_volatile = cwd;
-  const char *volatile pty_volatile = pty;
+  const char *volatile ptyname_volatile = pty_name;
+  bool volatile ptyin_volatile = pty_in;
+  bool volatile ptyout_volatile = pty_out;
   char **volatile argv_volatile = argv;
   int volatile stdin_volatile = std_in;
   int volatile stdout_volatile = std_out;
@@ -1495,7 +1498,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 
   newpid = newpid_volatile;
   cwd = cwd_volatile;
-  pty = pty_volatile;
+  pty_name = ptyname_volatile;
+  pty_in = ptyin_volatile;
+  pty_out = ptyout_volatile;
   argv = argv_volatile;
   std_in = stdin_volatile;
   std_out = stdout_volatile;
@@ -1506,13 +1511,12 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   if (pid == 0)
 #endif /* not WINDOWSNT */
     {
-      bool pty_flag = pty != NULL;
       /* Make the pty be the controlling terminal of the process.  */
 #ifdef HAVE_PTYS
       dissociate_controlling_tty ();
 
       /* Make the pty's terminal the controlling terminal.  */
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 #ifdef TIOCSCTTY
 	  /* We ignore the return value
@@ -1521,7 +1525,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 #endif
 	}
 #if defined (LDISC1)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  struct termios t;
 	  tcgetattr (std_in, &t);
@@ -1531,7 +1535,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 	}
 #else
 #if defined (NTTYDISC) && defined (TIOCSETD)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  /* Use new line discipline.  */
 	  int ldisc = NTTYDISC;
@@ -1548,18 +1552,21 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
      both TIOCSCTTY is defined.  */
 	/* Now close the pty (if we had it open) and reopen it.
 	   This makes the pty the controlling terminal of the subprocess.  */
-      if (pty_flag)
+      if (pty_name)
 	{
 
 	  /* I wonder if emacs_close (emacs_open (pty, ...))
 	     would work?  */
-	  if (std_in >= 0)
+	  if (pty_in && std_in >= 0)
 	    emacs_close (std_in);
-          std_out = std_in = emacs_open_noquit (pty, O_RDWR, 0);
-
+	  int ptyfd = emacs_open_noquit (pty_name, O_RDWR, 0);
+	  if (pty_in)
+	    std_in = ptyfd;
+	  if (pty_out)
+	    std_out = ptyfd;
 	  if (std_in < 0)
 	    {
-	      emacs_perror (pty);
+	      emacs_perror (pty_name);
 	      _exit (EXIT_CANCELED);
 	    }
 
@@ -1599,7 +1606,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
       /* Stop blocking SIGCHLD in the child.  */
       unblock_child_signal (oldset);
 
-      if (pty_flag)
+      if (pty_out)
 	child_setup_tty (std_out);
 #endif
 
diff --git a/src/lisp.h b/src/lisp.h
index dc496cc165..1a731c5dce 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4935,7 +4935,8 @@ #define DAEMON_RUNNING (w32_daemon_event != INVALID_HANDLE_VALUE)
 #endif
 
 extern int emacs_spawn (pid_t *, int, int, int, char **, char **,
-                        const char *, const char *, const sigset_t *);
+                        const char *, const char *, bool, bool,
+                        const sigset_t *);
 extern char **make_environment_block (Lisp_Object) ATTRIBUTE_RETURNS_NONNULL;
 extern void init_callproc_1 (void);
 extern void init_callproc (void);
diff --git a/src/process.c b/src/process.c
index d6d51b26e1..9858183ca0 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1846,20 +1846,23 @@ DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
 
   tem = plist_get (contact, QCconnection_type);
   if (EQ (tem, Qpty))
-    XPROCESS (proc)->pty_flag = true;
+    XPROCESS (proc)->pty_in = XPROCESS (proc)->pty_out = true;
   else if (EQ (tem, Qpipe))
-    XPROCESS (proc)->pty_flag = false;
+    XPROCESS (proc)->pty_in = XPROCESS (proc)->pty_out = false;
   else if (NILP (tem))
-    XPROCESS (proc)->pty_flag = !NILP (Vprocess_connection_type);
+    XPROCESS (proc)->pty_in = XPROCESS (proc)->pty_out =
+      !NILP (Vprocess_connection_type);
+  else if (FIXNUMP (tem))
+    {
+      /* FIXME: Provide a better way of setting these independently.  */
+      XPROCESS (proc)->pty_in = XFIXNAT(tem) & 1;
+      XPROCESS (proc)->pty_out = XFIXNAT(tem) & 2;
+    }
   else
     report_file_error ("Unknown connection type", tem);
 
   if (!NILP (stderrproc))
-    {
-      pset_stderrproc (XPROCESS (proc), stderrproc);
-
-      XPROCESS (proc)->pty_flag = false;
-    }
+    pset_stderrproc (XPROCESS (proc), stderrproc);
 
 #ifdef HAVE_GNUTLS
   /* AKA GNUTLS_INITSTAGE(proc).  */
@@ -2099,66 +2102,80 @@ verify (PROCESS_OPEN_FDS == EXEC_MONITOR_OUTPUT + 1);
 create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
 {
   struct Lisp_Process *p = XPROCESS (process);
-  int inchannel, outchannel;
+  int inchannel = -1, outchannel = -1;
   pid_t pid = -1;
   int vfork_errno;
   int forkin, forkout, forkerr = -1;
-  bool pty_flag = 0;
+  bool pty_in = false, pty_out = false;
   char pty_name[PTY_NAME_SIZE];
   Lisp_Object lisp_pty_name = Qnil;
+  int ptychannel = -1, pty_tty = -1;
   sigset_t oldset;
 
   /* Ensure that the SIGCHLD handler can notify
      `wait_reading_process_output'.  */
   child_signal_init ();
 
-  inchannel = outchannel = -1;
-
-  if (p->pty_flag)
-    outchannel = inchannel = allocate_pty (pty_name);
+  if (p->pty_in || p->pty_out)
+    ptychannel = allocate_pty (pty_name);
 
-  if (inchannel >= 0)
+  if (ptychannel >= 0)
     {
-      p->open_fd[READ_FROM_SUBPROCESS] = inchannel;
 #if ! defined (USG) || defined (USG_SUBTTY_WORKS)
       /* On most USG systems it does not work to open the pty's tty here,
 	 then close it and reopen it in the child.  */
       /* Don't let this terminal become our controlling terminal
 	 (in case we don't have one).  */
-      forkout = forkin = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
-      if (forkin < 0)
+      pty_tty = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
+      if (pty_tty < 0)
 	report_file_error ("Opening pty", Qnil);
-      p->open_fd[SUBPROCESS_STDIN] = forkin;
-#else
-      forkin = forkout = -1;
 #endif /* not USG, or USG_SUBTTY_WORKS */
-      pty_flag = 1;
+      pty_in = p->pty_in;
+      pty_out = p->pty_out;
       lisp_pty_name = build_string (pty_name);
     }
+
+  /* Set up stdin for the child process.  */
+  if (ptychannel >= 0 && p->pty_in)
+    {
+      p->open_fd[SUBPROCESS_STDIN] = forkin = pty_tty;
+      outchannel = ptychannel;
+    }
   else
     {
-      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0
-	  || emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0)
 	report_file_error ("Creating pipe", Qnil);
       forkin = p->open_fd[SUBPROCESS_STDIN];
       outchannel = p->open_fd[WRITE_TO_SUBPROCESS];
+    }
+
+  /* Set up stdout for the child process.  */
+  if (ptychannel >= 0 && p->pty_out)
+    {
+      forkout = pty_tty;
+      p->open_fd[READ_FROM_SUBPROCESS] = inchannel = ptychannel;
+    }
+  else
+    {
+      if (emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+	report_file_error ("Creating pipe", Qnil);
       inchannel = p->open_fd[READ_FROM_SUBPROCESS];
       forkout = p->open_fd[SUBPROCESS_STDOUT];
 
 #if defined(GNU_LINUX) && defined(F_SETPIPE_SZ)
       fcntl (inchannel, F_SETPIPE_SZ, read_process_output_max);
 #endif
+    }
 
-      if (!NILP (p->stderrproc))
-	{
-	  struct Lisp_Process *pp = XPROCESS (p->stderrproc);
+  if (!NILP (p->stderrproc))
+    {
+      struct Lisp_Process *pp = XPROCESS (p->stderrproc);
 
-	  forkerr = pp->open_fd[SUBPROCESS_STDOUT];
+      forkerr = pp->open_fd[SUBPROCESS_STDOUT];
 
-	  /* Close unnecessary file descriptors.  */
-	  close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
-	  close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
-	}
+      /* Close unnecessary file descriptors.  */
+      close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
+      close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
     }
 
   if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel)
@@ -2183,7 +2200,8 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
      we just reopen the device (see emacs_get_tty_pgrp) as this is
      more portable (see USG_SUBTTY_WORKS above).  */
 
-  p->pty_flag = pty_flag;
+  p->pty_in = pty_in;
+  p->pty_out = pty_out;
   pset_status (p, Qrun);
 
   if (!EQ (p->command, Qt)
@@ -2199,13 +2217,15 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
   block_input ();
   block_child_signal (&oldset);
 
-  pty_flag = p->pty_flag;
-  eassert (pty_flag == ! NILP (lisp_pty_name));
+  pty_in = p->pty_in;
+  pty_out = p->pty_out;
+  eassert ((pty_in || pty_out) == ! NILP (lisp_pty_name));
 
   vfork_errno
     = emacs_spawn (&pid, forkin, forkout, forkerr, new_argv, env,
                    SSDATA (current_dir),
-                   pty_flag ? SSDATA (lisp_pty_name) : NULL, &oldset);
+                   pty_in || pty_out ? SSDATA (lisp_pty_name) : NULL,
+                   pty_in, pty_out, &oldset);
 
   eassert ((vfork_errno == 0) == (0 < pid));
 
@@ -2263,7 +2283,7 @@ create_pty (Lisp_Object process)
 {
   struct Lisp_Process *p = XPROCESS (process);
   char pty_name[PTY_NAME_SIZE];
-  int pty_fd = !p->pty_flag ? -1 : allocate_pty (pty_name);
+  int pty_fd = !(p->pty_in || p->pty_out) ? -1 : allocate_pty (pty_name);
 
   if (pty_fd >= 0)
     {
@@ -2301,7 +2321,7 @@ create_pty (Lisp_Object process)
 	 we just reopen the device (see emacs_get_tty_pgrp) as this is
 	 more portable (see USG_SUBTTY_WORKS above).  */
 
-      p->pty_flag = 1;
+      p->pty_in = p->pty_out = true;
       pset_status (p, Qrun);
       setup_process_coding_systems (process);
 
@@ -2412,7 +2432,7 @@ DEFUN ("make-pipe-process", Fmake_pipe_process, Smake_pipe_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -3147,7 +3167,7 @@ DEFUN ("make-serial-process", Fmake_serial_process, Smake_serial_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -6798,7 +6818,7 @@ process_send_signal (Lisp_Object process, int signo, Lisp_Object current_group,
     error ("Process %s is not active",
 	   SDATA (p->name));
 
-  if (!p->pty_flag)
+  if (! p->pty_in)
     current_group = Qnil;
 
   /* If we are using pgrps, get a pgrp number and make it negative.  */
@@ -7167,7 +7187,7 @@ DEFUN ("process-send-eof", Fprocess_send_eof, Sprocess_send_eof, 0, 1, 0,
       send_process (proc, "", 0, Qnil);
     }
 
-  if (XPROCESS (proc)->pty_flag)
+  if (XPROCESS (proc)->pty_in)
     send_process (proc, "\004", 1, Qnil);
   else if (EQ (XPROCESS (proc)->type, Qserial))
     {
diff --git a/src/process.h b/src/process.h
index 392b661ce6..92baf0c4cb 100644
--- a/src/process.h
+++ b/src/process.h
@@ -156,8 +156,9 @@ #define EMACS_PROCESS_H
     /* True means kill silently if Emacs is exited.
        This is the inverse of the `query-on-exit' flag.  */
     bool_bf kill_without_query : 1;
-    /* True if communicating through a pty.  */
-    bool_bf pty_flag : 1;
+    /* True if communicating through a pty for input or output.  */
+    bool_bf pty_in : 1;
+    bool_bf pty_out : 1;
     /* Flag to set coding-system of the process buffer from the
        coding_system used to decode process output.  */
     bool_bf inherit_coding_system_flag : 1;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-17  2:35                                                       ` bug#56025: [WIP PATCH] " Jim Porter
@ 2022-07-17  6:03                                                         ` Eli Zaretskii
  2022-07-17 17:44                                                           ` Jim Porter
  2022-07-17 21:59                                                         ` Ken Brown
  1 sibling, 1 reply; 64+ messages in thread
From: Eli Zaretskii @ 2022-07-17  6:03 UTC (permalink / raw)
  To: Jim Porter; +Cc: larsi, 56025, spwhitton, kbrown

> Cc: larsi@gnus.org, 56025@debbugs.gnu.org
> From: Jim Porter <jporterbugs@gmail.com>
> Date: Sat, 16 Jul 2022 19:35:12 -0700
> 
> Ok, attached is a WIP patch to do this. It seems to work for me under 
> Cygwin, although I've only lightly tested it in that environment. If 
> this works for you too, I'll finish cleaning this up and add 
> tests/documentation for it.
> 
> Note that in my patch, I temporarily undid my previous patch to send EOF 
> multiple times. This is just for testing purposes, but since we're using 
> a pipe for this connection now, a single call to `process-send-eof' 
> should be sufficient. (There are some obscure cases where we might want 
> to keep the current behavior, like redirecting to a process created some 
> other way, so I think it makes sense to keep that code. Probably...)

Could you please describe the main ideas of the changeset?  It is hard
to be sure I understand what you are trying to do by reading the
patch.

Thanks.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-17  6:03                                                         ` Eli Zaretskii
@ 2022-07-17 17:44                                                           ` Jim Porter
  2022-07-17 18:26                                                             ` Eli Zaretskii
  2022-07-18  8:09                                                             ` Michael Albinus
  0 siblings, 2 replies; 64+ messages in thread
From: Jim Porter @ 2022-07-17 17:44 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, 56025, spwhitton, kbrown

On 7/16/2022 11:03 PM, Eli Zaretskii wrote:
> Could you please describe the main ideas of the changeset?  It is hard
> to be sure I understand what you are trying to do by reading the
> patch.

Sure. I mentioned it briefly earlier in the thread, but a more-complete 
summary would probably help.

Normally, Eshell connects programs in a pipeline like "foo | bar" by 
setting a process filter for "foo", and inside that filter, (eventually) 
calling `process-send-string' for "bar". In most shells, you'd expect 
that connection to be a pipe, but in Eshell, the processes are created 
with a PTY connection by default.

My patch adds support for `make-process' to use a PTY only for the child 
process's stdin or its stdout (in addition to the preexisting behaviors 
of PTY for both or neither). This then lets Eshell request a pipe for 
foo's stdout and bar's stdin, while using PTYs for foo's stdin and bar's 
stdout:

   Before:
     [pty 1] -> foo -> [pty 1] -> Eshell -> [pty 2] -> bar -> [pty 2]

   After:
     [pty 1] -> foo -> [pipe] -> Eshell -> [pipe] -> bar -> [pty 2]

This should make Eshell behave quite a bit more similarly to other 
shells, which will hopefully reduce the number of bugs like this one. 
This change also allowed me to remove the workaround for bug#1388. In 
that bug, there was an issue where this command didn't work[1]:

   *echo 1+1 | bc

Before the fix for bug#1388, "bc" would have seen that its stdin was a 
PTY, and then started an interactive session. Bug#1388 fixed this by 
adding `eshell-needs-pipe-p' to identify specific programs that need a 
pipe connection when being piped to like the above. With my patch here, 
that workaround won't be necessary anymore, since programs in a pipeline 
will be connected via pipes. (Note that technically, this pipe 
connection is indirect, since there's one pipe from foo to Emacs, and 
another pipe from Emacs to bar.)

This patch should hopefully fix the issues on Cygwin (as described in 
this bug) because, when using pipes to connect programs, the behavior 
should be more consistent across multiple platforms.

[1] I'm using "*echo" here to use /bin/echo so that it writes a newline. 
Eshell's built-in echo doesn't write a newline by default.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-17 17:44                                                           ` Jim Porter
@ 2022-07-17 18:26                                                             ` Eli Zaretskii
  2022-07-17 18:51                                                               ` Jim Porter
  2022-07-18  8:09                                                             ` Michael Albinus
  1 sibling, 1 reply; 64+ messages in thread
From: Eli Zaretskii @ 2022-07-17 18:26 UTC (permalink / raw)
  To: Jim Porter; +Cc: larsi, 56025, spwhitton, kbrown

> Cc: larsi@gnus.org, 56025@debbugs.gnu.org, spwhitton@email.arizona.edu,
>  kbrown@cornell.edu
> From: Jim Porter <jporterbugs@gmail.com>
> Date: Sun, 17 Jul 2022 10:44:26 -0700
> 
> My patch adds support for `make-process' to use a PTY only for the child 
> process's stdin or its stdout (in addition to the preexisting behaviors 
> of PTY for both or neither). This then lets Eshell request a pipe for 
> foo's stdout and bar's stdin, while using PTYs for foo's stdin and bar's 
> stdout:
> 
>    Before:
>      [pty 1] -> foo -> [pty 1] -> Eshell -> [pty 2] -> bar -> [pty 2]
> 
>    After:
>      [pty 1] -> foo -> [pipe] -> Eshell -> [pipe] -> bar -> [pty 2]

This assumes that we never want foo to behave as it does when
displaying on a terminal device.  Are we sure we will never want that?
E.g., what about the equivalent of "fgrep ... | less" -- don't we want
fgrep to produce colorized output as it does when it writes to a
terminal device?

Perhaps the use of pipes should be controllable?

Thanks.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-17 18:26                                                             ` Eli Zaretskii
@ 2022-07-17 18:51                                                               ` Jim Porter
  0 siblings, 0 replies; 64+ messages in thread
From: Jim Porter @ 2022-07-17 18:51 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, 56025, spwhitton, kbrown

On 7/17/2022 11:26 AM, Eli Zaretskii wrote:
>> Cc: larsi@gnus.org, 56025@debbugs.gnu.org, spwhitton@email.arizona.edu,
>>   kbrown@cornell.edu
>> From: Jim Porter <jporterbugs@gmail.com>
>> Date: Sun, 17 Jul 2022 10:44:26 -0700
>>
>> My patch adds support for `make-process' to use a PTY only for the child
>> process's stdin or its stdout (in addition to the preexisting behaviors
>> of PTY for both or neither). This then lets Eshell request a pipe for
>> foo's stdout and bar's stdin, while using PTYs for foo's stdin and bar's
>> stdout:
>>
>>     Before:
>>       [pty 1] -> foo -> [pty 1] -> Eshell -> [pty 2] -> bar -> [pty 2]
>>
>>     After:
>>       [pty 1] -> foo -> [pipe] -> Eshell -> [pipe] -> bar -> [pty 2]
> 
> This assumes that we never want foo to behave as it does when
> displaying on a terminal device.  Are we sure we will never want that?
> E.g., what about the equivalent of "fgrep ... | less" -- don't we want
> fgrep to produce colorized output as it does when it writes to a
> terminal device?

Well, for something like fgrep, the usual way to do this in a regular 
shell would be "fgrep --color=always ... | less", which should work the 
same in Eshell. There are a few caveats to this though:

1. "fgrep" is actually a built-in Eshell command that opens a 
compilation buffer and runs "grep -F ..." in it, so piping it to "less" 
normally isn't necessary. Still, you could always use the external fgrep 
program by specifying the full path or saying "*fgrep" though.

2. External commands see Eshell as a dumb terminal, and so they usually 
won't colorize their output in the first place without the user forcing 
it. Piping to "less" doesn't change the situation there.

3. Piping to "less" is probably going to have problems, even with this 
change. Eshell considers less to be a "visual command", so it opens it 
up in an M-x term buffer (and I don't think the Eshell->term code is 
able to support pipelines like this yet). Even if that were fixed, I 
think it would be tricky to get less working in Eshell. That said, I 
have a plan to make a built-in version of less for Eshell written in 
Elisp that should do pretty much what Eshell users would expect. This is 
a complex project though (I started it in February!), and I have a few 
more preliminary changes to make to Eshell to make this easier to do.

> Perhaps the use of pipes should be controllable?

However, with all the above said, I think we *do* want the use of pipes 
to be controllable in at least some cases. For example, due to the 
differences between Eshell and regular shells, commands like "xdg-open" 
don't work properly (this is bug#56013). It would be nice if Eshell 
could make those commands Just Work, but I'm not sure that's feasible 
given how Eshell works. I think the most straightforward way to resolve 
that would be to declare "xdg-open" (and similar commands) as *always* 
using pipes, no matter what. Maybe there are commands that always want a 
PTY too.

It wouldn't be too hard to have a mapping from command names to 
connection-types that would handle this. It would be sort of like the 
`eshell-needs-pipe-p' code that I removed in my WIP patch, but with 
finer-grained control.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-17  2:35                                                       ` bug#56025: [WIP PATCH] " Jim Porter
  2022-07-17  6:03                                                         ` Eli Zaretskii
@ 2022-07-17 21:59                                                         ` Ken Brown
  2022-07-18  5:26                                                           ` Jim Porter
  1 sibling, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-07-17 21:59 UTC (permalink / raw)
  To: Jim Porter, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 7/16/2022 10:35 PM, Jim Porter wrote:
> Ok, attached is a WIP patch to do this. It seems to work for me under Cygwin, 
> although I've only lightly tested it in that environment. If this works for you 
> too, I'll finish cleaning this up and add tests/documentation for it.

It does work for me too.  Thanks!

> Note that in my patch, I temporarily undid my previous patch to send EOF 
> multiple times. This is just for testing purposes, but since we're using a pipe 
> for this connection now, a single call to `process-send-eof' should be 
> sufficient.

There shouldn't be a need for any calls to process-send-eof.  This is a noop 
anyway when writing to a pipe, as it should be.  A process reading from a pipe 
automatically recognizes EOF when a read returns 0 bytes, which is supposed to 
happen when no process has the pipe open for writing.

> (There are some obscure cases where we might want to keep the 
> current behavior, like redirecting to a process created some other way, so I 
> think it makes sense to keep that code. Probably...)

Ideally, Eshell should know whether it's writing to a pipe or a pty.  It should 
send up to 3 EOFs in the latter case and 0 in the former case.  If it's too hard 
to arrange that, then it's probably harmless to send up to 3 EOFs in the pipe 
case too.  But then maybe a comment in the code would be useful, so that readers 
don't wonder why you're sending EOF to a pipe.

Thanks again for your work.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-17 21:59                                                         ` Ken Brown
@ 2022-07-18  5:26                                                           ` Jim Porter
  2022-07-22  4:16                                                             ` bug#56025: [PATCH v2] " Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-07-18  5:26 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 7/17/2022 2:59 PM, Ken Brown wrote:
> On 7/16/2022 10:35 PM, Jim Porter wrote:
>> Ok, attached is a WIP patch to do this. It seems to work for me under 
>> Cygwin, although I've only lightly tested it in that environment. If 
>> this works for you too, I'll finish cleaning this up and add 
>> tests/documentation for it.
> 
> It does work for me too.  Thanks!

Great! This should make Eshell behave a bit more similarly to other 
shells, so hopefully this will help prevent other issues in this area.

>> Note that in my patch, I temporarily undid my previous patch to send 
>> EOF multiple times. This is just for testing purposes, but since we're 
>> using a pipe for this connection now, a single call to 
>> `process-send-eof' should be sufficient.
> 
> There shouldn't be a need for any calls to process-send-eof.  This is a 
> noop anyway when writing to a pipe, as it should be.

Looking at the implementation of `process-send-eof', I think it's 
(somewhat misleadingly) also responsible for closing the file descriptor 
when writing to a pipe, so I believe we'll need at least one call to 
that function.

As you say though, it shouldn't be too hard for Eshell to check whether 
it's writing to a pipe and only try to call `process-send-eof' once in 
that case.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-17 17:44                                                           ` Jim Porter
  2022-07-17 18:26                                                             ` Eli Zaretskii
@ 2022-07-18  8:09                                                             ` Michael Albinus
  2022-07-19  1:58                                                               ` Jim Porter
  1 sibling, 1 reply; 64+ messages in thread
From: Michael Albinus @ 2022-07-18  8:09 UTC (permalink / raw)
  To: Jim Porter; +Cc: Eli Zaretskii, 56025, larsi, kbrown, spwhitton

Jim Porter <jporterbugs@gmail.com> writes:

Hi Jim,

> Sure. I mentioned it briefly earlier in the thread, but a
> more-complete summary would probably help.
>
> Normally, Eshell connects programs in a pipeline like "foo | bar" by
> setting a process filter for "foo", and inside that filter,
> (eventually) calling `process-send-string' for "bar". In most shells,
> you'd expect that connection to be a pipe, but in Eshell, the
> processes are created with a PTY connection by default.
>
> My patch adds support for `make-process' to use a PTY only for the
> child process's stdin or its stdout (in addition to the preexisting
> behaviors of PTY for both or neither). This then lets Eshell request a
> pipe for foo's stdout and bar's stdin, while using PTYs for foo's
> stdin and bar's stdout:
>
>   Before:
>     [pty 1] -> foo -> [pty 1] -> Eshell -> [pty 2] -> bar -> [pty 2]
>
>   After:
>     [pty 1] -> foo -> [pipe] -> Eshell -> [pipe] -> bar -> [pty 2]

I haven't tested, but it looks like it won't work with remote processes
foo and bar. Something like

--8<---------------cut here---------------start------------->8---
~ $ cd /ssh:remotehost:
/ssh:remotehost:~ $ *ls | *grep a
--8<---------------cut here---------------end--------------->8---

I have no idea whether such a usage pattern makes sense, but it seems to
work ATM. The alternative, calling "*ls *| *grep a", works as well, but
there are different results.

Best regards, Michael.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-18  8:09                                                             ` Michael Albinus
@ 2022-07-19  1:58                                                               ` Jim Porter
  2022-07-19  7:59                                                                 ` Michael Albinus
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-07-19  1:58 UTC (permalink / raw)
  To: Michael Albinus; +Cc: Eli Zaretskii, 56025, larsi, kbrown, spwhitton

On 7/18/2022 1:09 AM, Michael Albinus wrote:
> I haven't tested, but it looks like it won't work with remote processes
> foo and bar. Something like
> 
> --8<---------------cut here---------------start------------->8---
> ~ $ cd /ssh:remotehost:
> /ssh:remotehost:~ $ *ls | *grep a
> --8<---------------cut here---------------end--------------->8---

Hmm, yeah. We'll need to do something with the Tramp case. I think the 
minimal fix would be to update `tramp-sh-handle-make-process' so that it 
doesn't signal an error when :connection-type is a cons cell. That's 
pretty easy.

However, it seems that Tramp doesn't fully support :connection-type yet 
(testing on both Emacs 28.1 and 29). I used the following Python script, 
and tried a few varieties of `make-process' calls over Tramp. I'd expect 
that when I set :connection-type to `pipe', it would print False for all 
the streams, but it seems that the script always sees stdin and stdout 
as TTYs no matter what options I use.

------------------------------
#!/usr/bin/env python3

import sys
print('stdin: {}\nstdout: {}\nstderr: {}\n'.format(
     sys.stdin.isatty(),
     sys.stdout.isatty(),
     sys.stderr.isatty()
))
------------------------------

On the other hand, if I set :stderr to be a buffer, then the script 
*does* see stderr as a pipe, but that makes sense looking at the code: 
when using :stderr, Tramp redirects the process's stderr to a file (and 
then cats it back out).

Maybe there's a relatively easy way to get Tramp to respect 
:connection-type, but I tried a couple of things and they didn't work 
quite right. Still, that could be a followup for later. So long as it 
doesn't error out immediately when it's a cons cell, I think it would be ok.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [WIP PATCH] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-19  1:58                                                               ` Jim Porter
@ 2022-07-19  7:59                                                                 ` Michael Albinus
  0 siblings, 0 replies; 64+ messages in thread
From: Michael Albinus @ 2022-07-19  7:59 UTC (permalink / raw)
  To: Jim Porter; +Cc: Eli Zaretskii, 56025, larsi, kbrown, spwhitton

Jim Porter <jporterbugs@gmail.com> writes:

Hi Jim,

>> I haven't tested, but it looks like it won't work with remote processes
>> foo and bar. Something like
>> --8<---------------cut here---------------start------------->8---
>> ~ $ cd /ssh:remotehost:
>> /ssh:remotehost:~ $ *ls | *grep a
>> --8<---------------cut here---------------end--------------->8---
>
> Hmm, yeah. We'll need to do something with the Tramp case. I think the
> minimal fix would be to update `tramp-sh-handle-make-process' so that
> it doesn't signal an error when :connection-type is a cons
> cell. That's pretty easy.

FTR, there are three different implementations of `make-process' in
Tramp. A fourth one, in tramp-smb.el, waits for implementation.

> However, it seems that Tramp doesn't fully support :connection-type
> yet (testing on both Emacs 28.1 and 29).

Indeed. Tramp just checks that :connection-type is set to a proper
value, that's it. I was never urged to do more :-)

> Maybe there's a relatively easy way to get Tramp to respect
> :connection-type, but I tried a couple of things and they didn't work
> quite right. Still, that could be a followup for later. So long as it
> doesn't error out immediately when it's a cons cell, I think it would
> be ok.

Best would be you write a new bug report for Tramp, handling
:connection-type proper in make-process. At least it should accept the
cons cell (once the API has been stabilized, documented and pushed to
Emacs). And perhaps Tramp could do better in general wrt :connection-type.

Best regards, Michael.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v2] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-18  5:26                                                           ` Jim Porter
@ 2022-07-22  4:16                                                             ` Jim Porter
  2022-07-22 19:00                                                               ` Ken Brown
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-07-22  4:16 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

[-- Attachment #1: Type: text/plain, Size: 2276 bytes --]

On 7/17/2022 10:26 PM, Jim Porter wrote:
> On 7/17/2022 2:59 PM, Ken Brown wrote:
>> It does work for me too.  Thanks!
> 
> Great! This should make Eshell behave a bit more similarly to other 
> shells, so hopefully this will help prevent other issues in this area.

Ok, I *think* this is done. The patches have docs/tests that should 
hopefully explain everything in detail, but here's a high-level overview:

Patch 1:
--------

Add the ability to pass a cons cell for `:connection-type' to 
`make-process'. This lets you specify whether to use a pipe or pty 
independently for the input and output of the subprocess. This also 
removes the restriction that specifying `:stderr' forces 
`:connection-type' to be `pipe'. Now, it only makes stderr use a pipe.

This should be enough to fix the test failures mentioned in this bug, 
and should also make Eshell pipelines work more like in other shells: 
normally, when executing something like `foo | bar', foo's stdout and 
bar's stdin are pipes.[1]

I also removed the `eshell-needs-pipe-p' function since it's not 
necessary in its current form anymore. However, a new function along 
these lines might help to resolve bug#56013. I looked into this briefly 
and it's not terribly complicated, but it would take a bit of work to 
get right, so I think it'd be best to do it separately.

Patch 2:
--------

Add the ability to check whether each of a subprocess's `stdin', 
`stdout', or `stderr' are TTYs or pipes by passing one of those symbols 
as the second argument to `process-tty-name'. This lets us avoid the 
"send 3 EOFs" behavior most of the time in Eshell. (Note that if a user 
created a subprocess some other way and connected it via Eshell, they 
might need the 3 EOFs behavior, hence why I kept that code around.)

I debated whether `process-tty-name' was the right place to do this or 
if a new `process-connection-type' function would be better, but I went 
with this way in the end. I don't really have a strong preference though.

--------

I added tests for this, and they all pass for me, though admittedly I 
didn't run the entire Emacs test suite against these patches yet...

[1] Note that currently, Eshell always pipes both stdout and stderr (see 
bug#21605). I'm tinkering with a patch for this too.

[-- Attachment #2: 0001-Allow-creating-processes-where-only-one-of-stdin-or-.patch --]
[-- Type: text/plain, Size: 35623 bytes --]

From c871d4b8158acf40ddf0a02f9813679fdecf0296 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 17 Jul 2022 20:25:00 -0700
Subject: [PATCH 1/2] Allow creating processes where only one of stdin or
 stdout is a PTY

* src/lisp.h (emacs_spawn):
* src/callproc.c (emacs_spawn): Add PTY_IN and PTY_OUT arguments to
specify which streams should be set up as a PTY.
(call_process): Adjust call to 'emacs_spawn'.

* src/process.h (Lisp_Process): Replace 'pty_flag' with 'pty_in' and
'pty_out'.

* src/process.c (is_pty_from_symbol): New function.
(make-process): Allow :connection-type to be a cons cell, and allow
using a stderr process with a PTY for stdin/stdout.
(create_process): Handle creating a process where only one of stdin or
stdout is a PTY.

* lisp/eshell/esh-proc.el (eshell-needs-pipe, eshell-needs-pipe-p):
Remove.
(eshell-gather-process-output): Use 'make-process' and set
':connection-type' as needed by the value of 'eshell-in-pipeline-p'.

* lisp/net/tramp.el (tramp-handle-make-process):
* lisp/net/tramp-adb.el (tramp-adb-handle-make-process):
* lisp/net/tramp-sh.el (tramp-sh-handle-make-process): Don't signal an
error when ':connection-type' is a cons cell.

* test/src/process-tests.el
(process-test-sentinel-wait-function-working-p): Allow passing PROC
in, and rework into...
(process-test-wait-for-sentinel): ... this.
(process-test-sentinel-accept-process-output)
(process-test-sentinel-sit-for, process-test-quoted-batfile)
(process-test-stderr-filter): Use 'process-test-wait-for-sentinel'.
(make/process/test-connection-type): New function.
(make-process/connection-type/pty, make-process/connection-type/pty-2)
(make-process/connection-type/pipe)
(make-process/connection-type/pipe-2)
(make-process/connection-type/in-pty)
(make-process/connection-type/out-pty)
(make-process/connection-type/pty-with-stderr-buffer)
(make-process/connection-type/out-pty-with-stderr-buffer): New tests.

* doc/lispref/processes.texi (Asynchronous Processes): Document new
':connection-type' behavior.
(Output from Processes): Remove caveat about ':stderr' forcing
'make-process' to use pipes.

* etc/NEWS: Announce this change.
---
 doc/lispref/processes.texi |  28 +++-----
 etc/NEWS                   |  12 ++++
 lisp/eshell/esh-proc.el    |  55 ++++++----------
 lisp/net/tramp-adb.el      |   5 +-
 lisp/net/tramp-sh.el       |   5 +-
 lisp/net/tramp.el          |   5 +-
 src/callproc.c             |  37 ++++++-----
 src/lisp.h                 |   3 +-
 src/process.c              | 129 +++++++++++++++++++++++--------------
 src/process.h              |   5 +-
 test/src/process-tests.el  | 121 ++++++++++++++++++++++++----------
 11 files changed, 245 insertions(+), 160 deletions(-)

diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index 80c371e1c6..a7e08054c7 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -705,12 +705,13 @@ Asynchronous Processes
 Initialize the type of device used to communicate with the subprocess.
 Possible values are @code{pty} to use a pty, @code{pipe} to use a
 pipe, or @code{nil} to use the default derived from the value of the
-@code{process-connection-type} variable.  This parameter and the value
-of @code{process-connection-type} are ignored if a non-@code{nil}
-value is specified for the @code{:stderr} parameter; in that case, the
-type will always be @code{pipe}.  On systems where ptys are not
-available (MS-Windows), this parameter is likewise ignored, and pipes
-are used unconditionally.
+@code{process-connection-type} variable.  If @var{type} is a cons cell
+@w{@code{(@var{input} . @var{output})}}, then @var{input} will be used
+for standard input and @var{output} for standard output (and standard
+error if @code{:stderr} is @code{nil}).
+
+On systems where ptys are not available (MS-Windows), this parameter
+is ignored, and pipes are used unconditionally.
 
 @item :noquery @var{query-flag}
 Initialize the process query flag to @var{query-flag}.
@@ -1530,20 +1531,11 @@ Output from Processes
 default filter discards the output.
 
   If the subprocess writes to its standard error stream, by default
-the error output is also passed to the process filter function.  If
-Emacs uses a pseudo-TTY (pty) for communication with the subprocess,
-then it is impossible to separate the standard output and standard
-error streams of the subprocess, because a pseudo-TTY has only one
-output channel.  In that case, if you want to keep the output to those
-streams separate, you should redirect one of them to a file---for
-example, by using an appropriate shell command via
-@code{start-process-shell-command} or a similar function.
-
-  Alternatively, you could use the @code{:stderr} parameter with a
+the error output is also passed to the process filter function.
+Alternatively, you could use the @code{:stderr} parameter with a
 non-@code{nil} value in a call to @code{make-process}
 (@pxref{Asynchronous Processes, make-process}) to make the destination
-of the error output separate from the standard output; in that case,
-Emacs will use pipes for communicating with the subprocess.
+of the error output separate from the standard output.
 
   When a subprocess terminates, Emacs reads any pending output,
 then stops reading output from that subprocess.  Therefore, if the
diff --git a/etc/NEWS b/etc/NEWS
index 6d4fce1237..dc79f0826a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2229,6 +2229,12 @@ they will still be escaped, so the '.foo' symbol is still printed as
 and remapping parent of basic faces does not work reliably.
 Instead of remapping 'mode-line', you have to remap 'mode-line-active'.
 
++++
+** 'make-process' has been extended to support ptys when ':stderr' is set.
+Previously, setting ':stderr' to a non-nil value would force the
+process's connection to use pipes.  Now, Emacs will use a pty for
+stdin and stdout if requested no matter the value of ':stderr'.
+
 ---
 ** User option 'mail-source-ignore-errors' is now obsolete.
 The whole mechanism for prompting users to continue in case of
@@ -3188,6 +3194,12 @@ translation.
 This is useful when quoting shell arguments for a remote shell
 invocation.  Such shells are POSIX conformant by default.
 
++++
+** 'make-process' can set connection type independently for input and output.
+When calling 'make-process', communication via pty can be enabled
+selectively for just input or output by passing a cons cell for
+':connection-type', e.g. '(pipe . pty)'.
+
 +++
 ** 'signal-process' now consults the list 'signal-process-functions'.
 This is to determine which function has to be called in order to
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 70426ccaf2..99b43661f2 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -250,30 +250,6 @@ eshell-last-sync-output-start
   "A marker that tracks the beginning of output of the last subprocess.
 Used only on systems which do not support async subprocesses.")
 
-(defvar eshell-needs-pipe
-  '("bc"
-    ;; xclip.el (in GNU ELPA) calls all of these with
-    ;; `process-connection-type' set to nil.
-    "pbpaste" "putclip" "xclip" "xsel" "wl-copy")
-  "List of commands which need `process-connection-type' to be nil.
-Currently only affects commands in pipelines, and not those at
-the front.  If an element contains a directory part it must match
-the full name of a command, otherwise just the nondirectory part must match.")
-
-(defun eshell-needs-pipe-p (command)
-  "Return non-nil if COMMAND needs `process-connection-type' to be nil.
-See `eshell-needs-pipe'."
-  (and (bound-and-true-p eshell-in-pipeline-p)
-       (not (eq eshell-in-pipeline-p 'first))
-       ;; FIXME should this return non-nil for anything that is
-       ;; neither 'first nor 'last?  See bug#1388 discussion.
-       (catch 'found
-	 (dolist (exe eshell-needs-pipe)
-	   (if (string-equal exe (if (string-search "/" exe)
-				     command
-				   (file-name-nondirectory command)))
-	       (throw 'found t))))))
-
 (defun eshell-gather-process-output (command args)
   "Gather the output from COMMAND + ARGS."
   (require 'esh-var)
@@ -290,31 +266,36 @@ eshell-gather-process-output
     (cond
      ((fboundp 'make-process)
       (setq proc
-	    (let ((process-connection-type
-		   (unless (eshell-needs-pipe-p command)
-		     process-connection-type))
-		  (command (file-local-name (expand-file-name command))))
-	      (apply #'start-file-process
-		     (file-name-nondirectory command) nil command args)))
+            (let ((command (file-local-name (expand-file-name command)))
+                  (conn-type (pcase (bound-and-true-p eshell-in-pipeline-p)
+                               ('first '(nil . pipe))
+                               ('last  '(pipe . nil))
+                               ('t     'pipe)
+                               ('nil   nil))))
+              (make-process
+               :name (file-name-nondirectory command)
+               :buffer (current-buffer)
+               :command (cons command args)
+               :filter (if (eshell-interactive-output-p)
+                           #'eshell-output-filter
+                         #'eshell-insertion-filter)
+               :sentinel #'eshell-sentinel
+               :connection-type conn-type
+               :file-handler t)))
       (eshell-record-process-object proc)
-      (set-process-buffer proc (current-buffer))
-      (set-process-filter proc (if (eshell-interactive-output-p)
-	                           #'eshell-output-filter
-                                 #'eshell-insertion-filter))
-      (set-process-sentinel proc #'eshell-sentinel)
       (run-hook-with-args 'eshell-exec-hook proc)
       (when (fboundp 'process-coding-system)
 	(let ((coding-systems (process-coding-system proc)))
 	  (setq decoding (car coding-systems)
 		encoding (cdr coding-systems)))
-	;; If start-process decided to use some coding system for
+	;; If `make-process' decided to use some coding system for
 	;; decoding data sent from the process and the coding system
 	;; doesn't specify EOL conversion, we had better convert CRLF
 	;; to LF.
 	(if (vectorp (coding-system-eol-type decoding))
 	    (setq decoding (coding-system-change-eol-conversion decoding 'dos)
 		  changed t))
-	;; Even if start-process left the coding system for encoding
+	;; Even if `make-process' left the coding system for encoding
 	;; data sent from the process undecided, we had better use the
 	;; same one as what we use for decoding.  But, we should
 	;; suppress EOL conversion.
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index de55856830..451128ab20 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -904,7 +904,10 @@ tramp-adb-handle-make-process
 	    (signal 'wrong-type-argument (list #'symbolp coding)))
 	  (when (eq connection-type t)
 	    (setq connection-type 'pty))
-	  (unless (memq connection-type '(nil pipe pty))
+	  (unless (or (and (consp connection-type)
+			   (memq (car connection-type) '(nil pipe pty))
+			   (memq (cdr connection-type) '(nil pipe pty)))
+		      (memq connection-type '(nil pipe pty)))
 	    (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	  (unless (or (null filter) (eq filter t) (functionp filter))
 	    (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index e772af9e0a..8c48c3fc1e 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -2851,7 +2851,10 @@ tramp-sh-handle-make-process
 	    (signal 'wrong-type-argument (list #'symbolp coding)))
 	  (when (eq connection-type t)
 	    (setq connection-type 'pty))
-	  (unless (memq connection-type '(nil pipe pty))
+	  (unless (or (and (consp connection-type)
+			   (memq (car connection-type) '(nil pipe pty))
+			   (memq (cdr connection-type) '(nil pipe pty)))
+		      (memq connection-type '(nil pipe pty)))
 	    (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	  (unless (or (null filter) (eq filter t) (functionp filter))
 	    (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index b11fd293cc..8b654944fe 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -4605,7 +4605,10 @@ tramp-handle-make-process
 	  (signal 'wrong-type-argument (list #'symbolp coding)))
 	(when (eq connection-type t)
 	  (setq connection-type 'pty))
-	(unless (memq connection-type '(nil pipe pty))
+	(unless (or (and (consp connection-type)
+			 (memq (car connection-type) '(nil pipe pty))
+			 (memq (cdr connection-type) '(nil pipe pty)))
+		    (memq connection-type '(nil pipe pty)))
 	  (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	(unless (or (null filter) (eq filter t) (functionp filter))
 	  (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/src/callproc.c b/src/callproc.c
index dd162f36a6..aec0a2f5a5 100644
--- a/src/callproc.c
+++ b/src/callproc.c
@@ -650,7 +650,7 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd,
 
   child_errno
     = emacs_spawn (&pid, filefd, fd_output, fd_error, new_argv, env,
-                   SSDATA (current_dir), NULL, &oldset);
+                   SSDATA (current_dir), NULL, false, false, &oldset);
   eassert ((child_errno == 0) == (0 < pid));
 
   if (pid > 0)
@@ -1412,14 +1412,15 @@ emacs_posix_spawn_init_attributes (posix_spawnattr_t *attributes,
 int
 emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
              char **argv, char **envp, const char *cwd,
-             const char *pty, const sigset_t *oldset)
+             const char *pty_name, bool pty_in, bool pty_out,
+             const sigset_t *oldset)
 {
 #if USABLE_POSIX_SPAWN
   /* Prefer the simpler `posix_spawn' if available.  `posix_spawn'
      doesn't yet support setting up pseudoterminals, so we fall back
      to `vfork' if we're supposed to use a pseudoterminal.  */
 
-  bool use_posix_spawn = pty == NULL;
+  bool use_posix_spawn = pty_name == NULL;
 
   posix_spawn_file_actions_t actions;
   posix_spawnattr_t attributes;
@@ -1473,7 +1474,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   /* vfork, and prevent local vars from being clobbered by the vfork.  */
   pid_t *volatile newpid_volatile = newpid;
   const char *volatile cwd_volatile = cwd;
-  const char *volatile pty_volatile = pty;
+  const char *volatile ptyname_volatile = pty_name;
+  bool volatile ptyin_volatile = pty_in;
+  bool volatile ptyout_volatile = pty_out;
   char **volatile argv_volatile = argv;
   int volatile stdin_volatile = std_in;
   int volatile stdout_volatile = std_out;
@@ -1495,7 +1498,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 
   newpid = newpid_volatile;
   cwd = cwd_volatile;
-  pty = pty_volatile;
+  pty_name = ptyname_volatile;
+  pty_in = ptyin_volatile;
+  pty_out = ptyout_volatile;
   argv = argv_volatile;
   std_in = stdin_volatile;
   std_out = stdout_volatile;
@@ -1506,13 +1511,12 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   if (pid == 0)
 #endif /* not WINDOWSNT */
     {
-      bool pty_flag = pty != NULL;
       /* Make the pty be the controlling terminal of the process.  */
 #ifdef HAVE_PTYS
       dissociate_controlling_tty ();
 
       /* Make the pty's terminal the controlling terminal.  */
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 #ifdef TIOCSCTTY
 	  /* We ignore the return value
@@ -1521,7 +1525,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 #endif
 	}
 #if defined (LDISC1)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  struct termios t;
 	  tcgetattr (std_in, &t);
@@ -1531,7 +1535,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 	}
 #else
 #if defined (NTTYDISC) && defined (TIOCSETD)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  /* Use new line discipline.  */
 	  int ldisc = NTTYDISC;
@@ -1548,18 +1552,21 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
      both TIOCSCTTY is defined.  */
 	/* Now close the pty (if we had it open) and reopen it.
 	   This makes the pty the controlling terminal of the subprocess.  */
-      if (pty_flag)
+      if (pty_name)
 	{
 
 	  /* I wonder if emacs_close (emacs_open (pty, ...))
 	     would work?  */
-	  if (std_in >= 0)
+	  if (pty_in && std_in >= 0)
 	    emacs_close (std_in);
-          std_out = std_in = emacs_open_noquit (pty, O_RDWR, 0);
-
+	  int ptyfd = emacs_open_noquit (pty_name, O_RDWR, 0);
+	  if (pty_in)
+	    std_in = ptyfd;
+	  if (pty_out)
+	    std_out = ptyfd;
 	  if (std_in < 0)
 	    {
-	      emacs_perror (pty);
+	      emacs_perror (pty_name);
 	      _exit (EXIT_CANCELED);
 	    }
 
@@ -1599,7 +1606,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
       /* Stop blocking SIGCHLD in the child.  */
       unblock_child_signal (oldset);
 
-      if (pty_flag)
+      if (pty_out)
 	child_setup_tty (std_out);
 #endif
 
diff --git a/src/lisp.h b/src/lisp.h
index 2afe135674..264228618d 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4941,7 +4941,8 @@ #define DAEMON_RUNNING (w32_daemon_event != INVALID_HANDLE_VALUE)
 #endif
 
 extern int emacs_spawn (pid_t *, int, int, int, char **, char **,
-                        const char *, const char *, const sigset_t *);
+                        const char *, const char *, bool, bool,
+                        const sigset_t *);
 extern char **make_environment_block (Lisp_Object) ATTRIBUTE_RETURNS_NONNULL;
 extern void init_callproc_1 (void);
 extern void init_callproc (void);
diff --git a/src/process.c b/src/process.c
index d6d51b26e1..da5e9cb182 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1316,6 +1316,19 @@ set_process_filter_masks (struct Lisp_Process *p)
     add_process_read_fd (p->infd);
 }
 
+static bool
+is_pty_from_symbol (Lisp_Object symbol)
+{
+  if (EQ (symbol, Qpty))
+    return true;
+  else if (EQ (symbol, Qpipe))
+    return false;
+  else if (NILP (symbol))
+    return !NILP (Vprocess_connection_type);
+  else
+    report_file_error ("Unknown connection type", symbol);
+}
+
 DEFUN ("set-process-filter", Fset_process_filter, Sset_process_filter,
        2, 2, 0,
        doc: /* Give PROCESS the filter function FILTER; nil means default.
@@ -1741,15 +1754,18 @@ DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
 :connection-type TYPE -- TYPE is control type of device used to
 communicate with subprocesses.  Values are `pipe' to use a pipe, `pty'
 to use a pty, or nil to use the default specified through
-`process-connection-type'.
+`process-connection-type'.  If TYPE is a cons (INPUT . OUTPUT), then
+INPUT will be used for standard input and OUTPUT for standard output
+(and standard error if `:stderr' is nil).
 
 :filter FILTER -- Install FILTER as the process filter.
 
 :sentinel SENTINEL -- Install SENTINEL as the process sentinel.
 
 :stderr STDERR -- STDERR is either a buffer or a pipe process attached
-to the standard error of subprocess.  Specifying this implies
-`:connection-type' is set to `pipe'.  If STDERR is nil, standard error
+to the standard error of subprocess.  When specifying this, the
+subprocess's standard error will always communicate via a pipe, no
+matter the value of `:connection-type'.  If STDERR is nil, standard error
 is mixed with standard output and sent to BUFFER or FILTER.  (Note
 that specifying :stderr will create a new, separate (but associated)
 process, with its own filter and sentinel.  See
@@ -1845,22 +1861,20 @@ DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
   CHECK_TYPE (NILP (tem), Qnull, tem);
 
   tem = plist_get (contact, QCconnection_type);
-  if (EQ (tem, Qpty))
-    XPROCESS (proc)->pty_flag = true;
-  else if (EQ (tem, Qpipe))
-    XPROCESS (proc)->pty_flag = false;
-  else if (NILP (tem))
-    XPROCESS (proc)->pty_flag = !NILP (Vprocess_connection_type);
+  if (CONSP (tem))
+    {
+      XPROCESS (proc)->pty_in = is_pty_from_symbol (XCAR (tem));
+      XPROCESS (proc)->pty_out = is_pty_from_symbol (XCDR (tem));
+    }
   else
-    report_file_error ("Unknown connection type", tem);
-
-  if (!NILP (stderrproc))
     {
-      pset_stderrproc (XPROCESS (proc), stderrproc);
-
-      XPROCESS (proc)->pty_flag = false;
+      XPROCESS (proc)->pty_in = XPROCESS (proc)->pty_out =
+	is_pty_from_symbol (tem);
     }
 
+  if (!NILP (stderrproc))
+    pset_stderrproc (XPROCESS (proc), stderrproc);
+
 #ifdef HAVE_GNUTLS
   /* AKA GNUTLS_INITSTAGE(proc).  */
   verify (GNUTLS_STAGE_EMPTY == 0);
@@ -2099,66 +2113,80 @@ verify (PROCESS_OPEN_FDS == EXEC_MONITOR_OUTPUT + 1);
 create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
 {
   struct Lisp_Process *p = XPROCESS (process);
-  int inchannel, outchannel;
+  int inchannel = -1, outchannel = -1;
   pid_t pid = -1;
   int vfork_errno;
   int forkin, forkout, forkerr = -1;
-  bool pty_flag = 0;
+  bool pty_in = false, pty_out = false;
   char pty_name[PTY_NAME_SIZE];
   Lisp_Object lisp_pty_name = Qnil;
+  int ptychannel = -1, pty_tty = -1;
   sigset_t oldset;
 
   /* Ensure that the SIGCHLD handler can notify
      `wait_reading_process_output'.  */
   child_signal_init ();
 
-  inchannel = outchannel = -1;
-
-  if (p->pty_flag)
-    outchannel = inchannel = allocate_pty (pty_name);
+  if (p->pty_in || p->pty_out)
+    ptychannel = allocate_pty (pty_name);
 
-  if (inchannel >= 0)
+  if (ptychannel >= 0)
     {
-      p->open_fd[READ_FROM_SUBPROCESS] = inchannel;
 #if ! defined (USG) || defined (USG_SUBTTY_WORKS)
       /* On most USG systems it does not work to open the pty's tty here,
 	 then close it and reopen it in the child.  */
       /* Don't let this terminal become our controlling terminal
 	 (in case we don't have one).  */
-      forkout = forkin = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
-      if (forkin < 0)
+      pty_tty = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
+      if (pty_tty < 0)
 	report_file_error ("Opening pty", Qnil);
-      p->open_fd[SUBPROCESS_STDIN] = forkin;
-#else
-      forkin = forkout = -1;
 #endif /* not USG, or USG_SUBTTY_WORKS */
-      pty_flag = 1;
+      pty_in = p->pty_in;
+      pty_out = p->pty_out;
       lisp_pty_name = build_string (pty_name);
     }
+
+  /* Set up stdin for the child process.  */
+  if (ptychannel >= 0 && p->pty_in)
+    {
+      p->open_fd[SUBPROCESS_STDIN] = forkin = pty_tty;
+      outchannel = ptychannel;
+    }
   else
     {
-      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0
-	  || emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0)
 	report_file_error ("Creating pipe", Qnil);
       forkin = p->open_fd[SUBPROCESS_STDIN];
       outchannel = p->open_fd[WRITE_TO_SUBPROCESS];
+    }
+
+  /* Set up stdout for the child process.  */
+  if (ptychannel >= 0 && p->pty_out)
+    {
+      forkout = pty_tty;
+      p->open_fd[READ_FROM_SUBPROCESS] = inchannel = ptychannel;
+    }
+  else
+    {
+      if (emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+	report_file_error ("Creating pipe", Qnil);
       inchannel = p->open_fd[READ_FROM_SUBPROCESS];
       forkout = p->open_fd[SUBPROCESS_STDOUT];
 
 #if defined(GNU_LINUX) && defined(F_SETPIPE_SZ)
       fcntl (inchannel, F_SETPIPE_SZ, read_process_output_max);
 #endif
+    }
 
-      if (!NILP (p->stderrproc))
-	{
-	  struct Lisp_Process *pp = XPROCESS (p->stderrproc);
+  if (!NILP (p->stderrproc))
+    {
+      struct Lisp_Process *pp = XPROCESS (p->stderrproc);
 
-	  forkerr = pp->open_fd[SUBPROCESS_STDOUT];
+      forkerr = pp->open_fd[SUBPROCESS_STDOUT];
 
-	  /* Close unnecessary file descriptors.  */
-	  close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
-	  close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
-	}
+      /* Close unnecessary file descriptors.  */
+      close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
+      close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
     }
 
   if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel)
@@ -2183,7 +2211,8 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
      we just reopen the device (see emacs_get_tty_pgrp) as this is
      more portable (see USG_SUBTTY_WORKS above).  */
 
-  p->pty_flag = pty_flag;
+  p->pty_in = pty_in;
+  p->pty_out = pty_out;
   pset_status (p, Qrun);
 
   if (!EQ (p->command, Qt)
@@ -2199,13 +2228,15 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
   block_input ();
   block_child_signal (&oldset);
 
-  pty_flag = p->pty_flag;
-  eassert (pty_flag == ! NILP (lisp_pty_name));
+  pty_in = p->pty_in;
+  pty_out = p->pty_out;
+  eassert ((pty_in || pty_out) == ! NILP (lisp_pty_name));
 
   vfork_errno
     = emacs_spawn (&pid, forkin, forkout, forkerr, new_argv, env,
                    SSDATA (current_dir),
-                   pty_flag ? SSDATA (lisp_pty_name) : NULL, &oldset);
+                   pty_in || pty_out ? SSDATA (lisp_pty_name) : NULL,
+                   pty_in, pty_out, &oldset);
 
   eassert ((vfork_errno == 0) == (0 < pid));
 
@@ -2263,7 +2294,7 @@ create_pty (Lisp_Object process)
 {
   struct Lisp_Process *p = XPROCESS (process);
   char pty_name[PTY_NAME_SIZE];
-  int pty_fd = !p->pty_flag ? -1 : allocate_pty (pty_name);
+  int pty_fd = !(p->pty_in || p->pty_out) ? -1 : allocate_pty (pty_name);
 
   if (pty_fd >= 0)
     {
@@ -2301,7 +2332,7 @@ create_pty (Lisp_Object process)
 	 we just reopen the device (see emacs_get_tty_pgrp) as this is
 	 more portable (see USG_SUBTTY_WORKS above).  */
 
-      p->pty_flag = 1;
+      p->pty_in = p->pty_out = true;
       pset_status (p, Qrun);
       setup_process_coding_systems (process);
 
@@ -2412,7 +2443,7 @@ DEFUN ("make-pipe-process", Fmake_pipe_process, Smake_pipe_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -3147,7 +3178,7 @@ DEFUN ("make-serial-process", Fmake_serial_process, Smake_serial_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -6798,7 +6829,7 @@ process_send_signal (Lisp_Object process, int signo, Lisp_Object current_group,
     error ("Process %s is not active",
 	   SDATA (p->name));
 
-  if (!p->pty_flag)
+  if (! p->pty_in)
     current_group = Qnil;
 
   /* If we are using pgrps, get a pgrp number and make it negative.  */
@@ -7167,7 +7198,7 @@ DEFUN ("process-send-eof", Fprocess_send_eof, Sprocess_send_eof, 0, 1, 0,
       send_process (proc, "", 0, Qnil);
     }
 
-  if (XPROCESS (proc)->pty_flag)
+  if (XPROCESS (proc)->pty_in)
     send_process (proc, "\004", 1, Qnil);
   else if (EQ (XPROCESS (proc)->type, Qserial))
     {
diff --git a/src/process.h b/src/process.h
index 392b661ce6..92baf0c4cb 100644
--- a/src/process.h
+++ b/src/process.h
@@ -156,8 +156,9 @@ #define EMACS_PROCESS_H
     /* True means kill silently if Emacs is exited.
        This is the inverse of the `query-on-exit' flag.  */
     bool_bf kill_without_query : 1;
-    /* True if communicating through a pty.  */
-    bool_bf pty_flag : 1;
+    /* True if communicating through a pty for input or output.  */
+    bool_bf pty_in : 1;
+    bool_bf pty_out : 1;
     /* Flag to set coding-system of the process buffer from the
        coding_system used to decode process output.  */
     bool_bf inherit_coding_system_flag : 1;
diff --git a/test/src/process-tests.el b/test/src/process-tests.el
index f1ed7e18d5..41320672a0 100644
--- a/test/src/process-tests.el
+++ b/test/src/process-tests.el
@@ -38,10 +38,11 @@
 ;; Timeout in seconds; the test fails if the timeout is reached.
 (defvar process-test-sentinel-wait-timeout 2.0)
 
-;; Start a process that exits immediately.  Call WAIT-FUNCTION,
-;; possibly multiple times, to wait for the process to complete.
-(defun process-test-sentinel-wait-function-working-p (wait-function)
-  (let ((proc (start-process "test" nil "bash" "-c" "exit 20"))
+(defun process-test-wait-for-sentinel (proc exit-status &optional wait-function)
+  "Set a sentinel on PROC and wait for it to be called with EXIT-STATUS.
+Call WAIT-FUNCTION, possibly multiple times, to wait for the
+process to complete."
+  (let ((wait-function (or wait-function #'accept-process-output))
 	(sentinel-called nil)
 	(start-time (float-time)))
     (set-process-sentinel proc (lambda (_proc _msg)
@@ -50,21 +51,22 @@ process-test-sentinel-wait-function-working-p
 		    (> (- (float-time) start-time)
 		       process-test-sentinel-wait-timeout)))
       (funcall wait-function))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
-    sentinel-called))
+    (should sentinel-called)
+    (should (eq (process-status proc) 'exit))
+    (should (= (process-exit-status proc) exit-status))))
 
 (ert-deftest process-test-sentinel-accept-process-output ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (should (process-test-sentinel-wait-function-working-p
-           #'accept-process-output))))
+    (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
+      (should (process-test-wait-for-sentinel proc 20)))))
 
 (ert-deftest process-test-sentinel-sit-for ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (should
-   (process-test-sentinel-wait-function-working-p (lambda () (sit-for 0.01 t))))))
+    (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
+      (should (process-test-wait-for-sentinel
+               proc 20 (lambda () (sit-for 0.01 t)))))))
 
 (when (eq system-type 'windows-nt)
   (ert-deftest process-test-quoted-batfile ()
@@ -97,17 +99,8 @@ process-test-stderr-buffer
 						    "echo hello stderr! >&2; "
 						    "exit 20"))
 			     :buffer stdout-buffer
-			     :stderr stderr-buffer))
-	 (sentinel-called nil)
-	 (start-time (float-time)))
-    (set-process-sentinel proc (lambda (_proc _msg)
-				 (setq sentinel-called t)))
-    (while (not (or sentinel-called
-		    (> (- (float-time) start-time)
-		       process-test-sentinel-wait-timeout)))
-      (accept-process-output))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
+			     :stderr stderr-buffer)))
+    (process-test-wait-for-sentinel proc 20)
     (should (with-current-buffer stdout-buffer
 	      (goto-char (point-min))
 	      (looking-at "hello stdout!")))
@@ -118,8 +111,7 @@ process-test-stderr-buffer
 (ert-deftest process-test-stderr-filter ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (let* ((sentinel-called nil)
-	 (stderr-sentinel-called nil)
+  (let* ((stderr-sentinel-called nil)
 	 (stdout-output nil)
 	 (stderr-output nil)
 	 (stdout-buffer (generate-new-buffer "*stdout*"))
@@ -131,23 +123,14 @@ process-test-stderr-filter
 					    (concat "echo hello stdout!; "
 						    "echo hello stderr! >&2; "
 						    "exit 20"))
-			     :stderr stderr-proc))
-	 (start-time (float-time)))
+			     :stderr stderr-proc)))
     (set-process-filter proc (lambda (_proc input)
 			       (push input stdout-output)))
-    (set-process-sentinel proc (lambda (_proc _msg)
-				 (setq sentinel-called t)))
     (set-process-filter stderr-proc (lambda (_proc input)
 				      (push input stderr-output)))
     (set-process-sentinel stderr-proc (lambda (_proc _input)
 					(setq stderr-sentinel-called t)))
-    (while (not (or sentinel-called
-		    (> (- (float-time) start-time)
-		       process-test-sentinel-wait-timeout)))
-      (accept-process-output))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
-    (should sentinel-called)
+    (process-test-wait-for-sentinel proc 20)
     (should (equal 1 (with-current-buffer stdout-buffer
 		       (point-max))))
     (should (equal "hello stdout!\n"
@@ -289,6 +272,74 @@ make-process-w32-debug-spawn-error
                   (error :got-error))))
     (should have-called-debugger))))
 
+(defun make-process/test-connection-type (ttys &rest args)
+  "Make a process and check whether its standard streams match TTYS.
+This calls `make-process', passing ARGS to adjust how the process
+is created.  TTYS should be a list of 3 boolean values,
+indicating whether the subprocess's stdin, stdout, and stderr
+should be a TTY, respectively."
+  (declare (indent 1))
+  (let* (;; MS-Windows doesn't support communicating via pty.
+         (ttys (if (eq system-type 'windows-nt) '(nil nil nil) ttys))
+         (expected-output (concat (and (nth 0 ttys) "stdin\n")
+                                  (and (nth 1 ttys) "stdout\n")
+                                  (and (nth 2 ttys) "stderr\n")))
+         (stdout-buffer (generate-new-buffer "*stdout*"))
+         (proc (apply
+                #'make-process
+                :name "test"
+                :command (list "sh" "-c"
+                               (concat "if [ -t 0 ]; then echo stdin; fi; "
+                                       "if [ -t 1 ]; then echo stdout; fi; "
+                                       "if [ -t 2 ]; then echo stderr; fi"))
+                :buffer stdout-buffer
+                args)))
+    (process-test-wait-for-sentinel proc 0)
+    (should (equal (with-current-buffer stdout-buffer (buffer-string))
+                   expected-output))))
+
+(ert-deftest make-process/connection-type/pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t t t)
+    :connection-type 'pty))
+
+(ert-deftest make-process/connection-type/pty-2 ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t t t)
+    :connection-type '(pty . pty)))
+
+(ert-deftest make-process/connection-type/pipe ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil nil nil)
+    :connection-type 'pipe))
+
+(ert-deftest make-process/connection-type/pipe-2 ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil nil nil)
+    :connection-type '(pipe . pipe)))
+
+(ert-deftest make-process/connection-type/in-pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t nil nil)
+    :connection-type '(pty . pipe)))
+
+(ert-deftest make-process/connection-type/out-pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil t t)
+    :connection-type '(pipe . pty)))
+
+(ert-deftest make-process/connection-type/pty-with-stderr-buffer ()
+  (skip-unless (executable-find "sh"))
+  (let ((stderr-buffer (generate-new-buffer "*stderr*")))
+    (make-process/test-connection-type '(t t nil)
+      :connection-type 'pty :stderr stderr-buffer)))
+
+(ert-deftest make-process/connection-type/out-pty-with-stderr-buffer ()
+  (skip-unless (executable-find "sh"))
+  (let ((stderr-buffer (generate-new-buffer "*stderr*")))
+    (make-process/test-connection-type '(nil t nil)
+      :connection-type '(pipe . pty) :stderr stderr-buffer)))
+
 (ert-deftest make-process/file-handler/found ()
   "Check that the `:file-handler’ argument of `make-process’
 works as expected if a file name handler is found."
-- 
2.25.1


[-- Attachment #3: 0002-Add-STREAM-argument-to-process-tty-name.patch --]
[-- Type: text/plain, Size: 7521 bytes --]

From 9ce8299419a1e674ba44433f8ba0ba4150e912e8 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Tue, 19 Jul 2022 21:36:54 -0700
Subject: [PATCH 2/2] Add STREAM argument to 'process-tty-name'

* src/process.c (process-tty-name): Add STREAM argument.

* lisp/eshell/esh-io.el (eshell-close-target): Only call
'process-send-eof' once if the process's stdin is a pipe.

* test/src/process-tests.el (make-process/test-connection-type): Check
behavior of 'process-tty-name'.

* doc/lispref/processes.texi (Process Information): Document the new
argument.

* etc/NEWS: Announce this change.
---
 doc/lispref/processes.texi | 17 +++++++++++------
 etc/NEWS                   |  5 ++++-
 lisp/eshell/esh-io.el      | 27 +++++++++++++++------------
 src/process.c              | 23 +++++++++++++++++++----
 test/src/process-tests.el  |  3 +++
 5 files changed, 52 insertions(+), 23 deletions(-)

diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index a7e08054c7..bbca48e6c5 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -1243,15 +1243,20 @@ Process Information
 whether the connection was closed normally or abnormally.
 @end defun
 
-@defun process-tty-name process
+@defun process-tty-name process &optional stream
 This function returns the terminal name that @var{process} is using for
 its communication with Emacs---or @code{nil} if it is using pipes
 instead of a pty (see @code{process-connection-type} in
-@ref{Asynchronous Processes}).  If @var{process} represents a program
-running on a remote host, the terminal name used by that program on
-the remote host is provided as process property @code{remote-tty}.  If
-@var{process} represents a network, serial, or pipe connection, the
-value is @code{nil}.
+@ref{Asynchronous Processes}).  If @var{stream} is one of @code{stdin},
+@code{stdout}, or @code{stderr}, this function returns the terminal
+name (or @code{nil}, as above) that @var{process} uses for that stream
+specifically.  You can use this to determine whether a particular
+stream uses a pipe or a pty.
+
+If @var{process} represents a program running on a remote host, the
+terminal name used by that program on the remote host is provided as
+process property @code{remote-tty}.  If @var{process} represents a
+network, serial, or pipe connection, the value is @code{nil}.
 @end defun
 
 @defun process-coding-system process
diff --git a/etc/NEWS b/etc/NEWS
index dc79f0826a..23777d349e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3198,7 +3198,10 @@ invocation.  Such shells are POSIX conformant by default.
 ** 'make-process' can set connection type independently for input and output.
 When calling 'make-process', communication via pty can be enabled
 selectively for just input or output by passing a cons cell for
-':connection-type', e.g. '(pipe . pty)'.
+':connection-type', e.g. '(pipe . pty)'.  When examining a process
+later, you can determine whether a particular stream for a process
+uses a pty by passing one of 'stdin', 'stdout', or 'stderr' as the
+second argument to 'process-tty-name'.
 
 +++
 ** 'signal-process' now consults the list 'signal-process-functions'.
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index c035890ddf..68e52a2c9c 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -276,18 +276,21 @@ eshell-close-target
    ;; 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)
-    ;; According to POSIX.1-2017, section 11.1.9, 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 shouldn't break
-    ;; anything, we'll just send the maximum we'd ever need.  See
-    ;; bug#56025 for further details.
-    (let ((i 0))
-      (while (and (<= (cl-incf i) 3)
+    ;; 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))))
 
diff --git a/src/process.c b/src/process.c
index da5e9cb182..adc508156f 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1243,14 +1243,29 @@ DEFUN ("process-command", Fprocess_command, Sprocess_command, 1, 1, 0,
   return XPROCESS (process)->command;
 }
 
-DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 1, 0,
+DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 2, 0,
        doc: /* Return the name of the terminal PROCESS uses, or nil if none.
 This is the terminal that the process itself reads and writes on,
-not the name of the pty that Emacs uses to talk with that terminal.  */)
-  (register Lisp_Object process)
+not the name of the pty that Emacs uses to talk with that terminal.
+
+If STREAM is one of `stdin', `stdout', or `stderr', return the name of
+the terminal PROCESS uses for that stream.  This can be used to detect
+whether a particular stream is connected via a pipe or a pty.  */)
+  (register Lisp_Object process, Lisp_Object stream)
 {
   CHECK_PROCESS (process);
-  return XPROCESS (process)->tty_name;
+  register struct Lisp_Process *p = XPROCESS (process);
+
+  if (NILP (stream))
+    return p->tty_name;
+  else if (EQ (stream, Qstdin))
+    return p->pty_in ? p->tty_name : Qnil;
+  else if (EQ (stream, Qstdout))
+    return p->pty_out ? p->tty_name : Qnil;
+  else if (EQ (stream, Qstderr))
+    return p->pty_out && NILP (p->stderrproc) ? p->tty_name : Qnil;
+  else
+    signal_error ("Unknown stream", stream);
 }
 
 static void
diff --git a/test/src/process-tests.el b/test/src/process-tests.el
index 41320672a0..6ba5930ee6 100644
--- a/test/src/process-tests.el
+++ b/test/src/process-tests.el
@@ -294,6 +294,9 @@ make-process/test-connection-type
                                        "if [ -t 2 ]; then echo stderr; fi"))
                 :buffer stdout-buffer
                 args)))
+    (should (eq (and (process-tty-name proc 'stdin) t) (nth 0 ttys)))
+    (should (eq (and (process-tty-name proc 'stdout) t) (nth 1 ttys)))
+    (should (eq (and (process-tty-name proc 'stderr) t) (nth 2 ttys)))
     (process-test-wait-for-sentinel proc 0)
     (should (equal (with-current-buffer stdout-buffer (buffer-string))
                    expected-output))))
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v2] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-22  4:16                                                             ` bug#56025: [PATCH v2] " Jim Porter
@ 2022-07-22 19:00                                                               ` Ken Brown
  2022-07-24  4:05                                                                 ` Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-07-22 19:00 UTC (permalink / raw)
  To: Jim Porter, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 7/22/2022 12:16 AM, Jim Porter wrote:
> Ok, I *think* this is done.

I can confirm that the em-extpipe tests now all pass on Cygwin, as do the 
process tests.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v2] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-22 19:00                                                               ` Ken Brown
@ 2022-07-24  4:05                                                                 ` Jim Porter
  2022-07-24  5:19                                                                   ` bug#56025: [PATCH v3] " Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-07-24  4:05 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

On 7/22/2022 12:00 PM, Ken Brown wrote:
> On 7/22/2022 12:16 AM, Jim Porter wrote:
>> Ok, I *think* this is done.
> 
> I can confirm that the em-extpipe tests now all pass on Cygwin, as do 
> the process tests.

Thanks for testing. I'm glad everything seems to be working for you too.

I did a bit more testing on my end to check out performance, since I 
figured we'd see at least some improvement from switching to pipes for 
passing data between processes. I wasn't prepared for just how much of 
an improvement though. On my system (GNU/Linux), this change makes 
piping in Eshell faster by a factor of 35x![1]

I'll repeat that since I'm pretty shocked myself: Eshell pipes are 
*thirty-five* times faster now!

For some details: I tested this by running "time *cat config.log | wc" 
in Eshell (n=20), and it went from an average of 4.80s to an average of 
0.134s. (Note that `time' in Eshell only times the first command, not 
the whole pipeline.) I chose this to test since config.log is reasonably 
big (1.13MiB on my system), the external cat program is pretty simple 
and mostly just does I/O, and wc's output is short so we don't have to 
worry about it writing a bunch of output to its (slow) PTY.

However, to put just a bit of a damper on things, this is still 5-10x 
slower than doing it in Bash, or with Eshell extpipes (which is really 
the same as just doing it in Bash, ultimately).

[1] YMMV, of course.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v3] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-24  4:05                                                                 ` Jim Porter
@ 2022-07-24  5:19                                                                   ` Jim Porter
  2022-07-24  5:29                                                                     ` bug#56025: [PATCH v4] " Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-07-24  5:19 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

[-- Attachment #1: Type: text/plain, Size: 318 bytes --]

I thought about it some more, and just to be sure the Eshell bits don't 
regress at some point in the future, I added some new unit tests in 
test/lisp/eshell/esh-proc-tests.el to make sure that Eshell sets the 
`:connection-type' properly. (They're pretty similar to the tests in 
test/src/process-tests.el, really.)

[-- Attachment #2: 0001-Allow-creating-processes-where-only-one-of-stdin-or-.patch --]
[-- Type: text/plain, Size: 38442 bytes --]

From d9438e4b9ad2ec9b4a301c1539edc163ca0a678b Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 17 Jul 2022 20:25:00 -0700
Subject: [PATCH 1/2] Allow creating processes where only one of stdin or
 stdout is a PTY

* src/lisp.h (emacs_spawn):
* src/callproc.c (emacs_spawn): Add PTY_IN and PTY_OUT arguments to
specify which streams should be set up as a PTY.
(call_process): Adjust call to 'emacs_spawn'.

* src/process.h (Lisp_Process): Replace 'pty_flag' with 'pty_in' and
'pty_out'.

* src/process.c (is_pty_from_symbol): New function.
(make-process): Allow :connection-type to be a cons cell, and allow
using a stderr process with a PTY for stdin/stdout.
(create_process): Handle creating a process where only one of stdin or
stdout is a PTY.

* lisp/eshell/esh-proc.el (eshell-needs-pipe, eshell-needs-pipe-p):
Remove.
(eshell-gather-process-output): Use 'make-process' and set
':connection-type' as needed by the value of 'eshell-in-pipeline-p'.

* lisp/net/tramp.el (tramp-handle-make-process):
* lisp/net/tramp-adb.el (tramp-adb-handle-make-process):
* lisp/net/tramp-sh.el (tramp-sh-handle-make-process): Don't signal an
error when ':connection-type' is a cons cell.

* test/src/process-tests.el
(process-test-sentinel-wait-function-working-p): Allow passing PROC
in, and rework into...
(process-test-wait-for-sentinel): ... this.
(process-test-sentinel-accept-process-output)
(process-test-sentinel-sit-for, process-test-quoted-batfile)
(process-test-stderr-filter): Use 'process-test-wait-for-sentinel'.
(make/process/test-connection-type): New function.
(make-process/connection-type/pty, make-process/connection-type/pty-2)
(make-process/connection-type/pipe)
(make-process/connection-type/pipe-2)
(make-process/connection-type/in-pty)
(make-process/connection-type/out-pty)
(make-process/connection-type/pty-with-stderr-buffer)
(make-process/connection-type/out-pty-with-stderr-buffer): New tests.

* test/lisp/eshell/esh-proc-tests.el (esh-proc-test--detect-pty-cmd):
New variable.
(esh-proc-test/pipeline-connection-type/no-pipeline)
(esh-proc-test/pipeline-connection-type/first)
(esh-proc-test/pipeline-connection-type/middle)
(esh-proc-test/pipeline-connection-type/last): New tests.

* doc/lispref/processes.texi (Asynchronous Processes): Document new
':connection-type' behavior.
(Output from Processes): Remove caveat about ':stderr' forcing
'make-process' to use pipes.

* etc/NEWS: Announce this change.
---
 doc/lispref/processes.texi         |  28 +++----
 etc/NEWS                           |  12 +++
 lisp/eshell/esh-proc.el            |  55 ++++--------
 lisp/net/tramp-adb.el              |   5 +-
 lisp/net/tramp-sh.el               |   5 +-
 lisp/net/tramp.el                  |   5 +-
 src/callproc.c                     |  37 +++++----
 src/lisp.h                         |   3 +-
 src/process.c                      | 129 ++++++++++++++++++-----------
 src/process.h                      |   5 +-
 test/lisp/eshell/esh-proc-tests.el |  37 +++++++++
 test/src/process-tests.el          | 121 +++++++++++++++++++--------
 12 files changed, 282 insertions(+), 160 deletions(-)

diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index 80c371e1c6..a7e08054c7 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -705,12 +705,13 @@ Asynchronous Processes
 Initialize the type of device used to communicate with the subprocess.
 Possible values are @code{pty} to use a pty, @code{pipe} to use a
 pipe, or @code{nil} to use the default derived from the value of the
-@code{process-connection-type} variable.  This parameter and the value
-of @code{process-connection-type} are ignored if a non-@code{nil}
-value is specified for the @code{:stderr} parameter; in that case, the
-type will always be @code{pipe}.  On systems where ptys are not
-available (MS-Windows), this parameter is likewise ignored, and pipes
-are used unconditionally.
+@code{process-connection-type} variable.  If @var{type} is a cons cell
+@w{@code{(@var{input} . @var{output})}}, then @var{input} will be used
+for standard input and @var{output} for standard output (and standard
+error if @code{:stderr} is @code{nil}).
+
+On systems where ptys are not available (MS-Windows), this parameter
+is ignored, and pipes are used unconditionally.
 
 @item :noquery @var{query-flag}
 Initialize the process query flag to @var{query-flag}.
@@ -1530,20 +1531,11 @@ Output from Processes
 default filter discards the output.
 
   If the subprocess writes to its standard error stream, by default
-the error output is also passed to the process filter function.  If
-Emacs uses a pseudo-TTY (pty) for communication with the subprocess,
-then it is impossible to separate the standard output and standard
-error streams of the subprocess, because a pseudo-TTY has only one
-output channel.  In that case, if you want to keep the output to those
-streams separate, you should redirect one of them to a file---for
-example, by using an appropriate shell command via
-@code{start-process-shell-command} or a similar function.
-
-  Alternatively, you could use the @code{:stderr} parameter with a
+the error output is also passed to the process filter function.
+Alternatively, you could use the @code{:stderr} parameter with a
 non-@code{nil} value in a call to @code{make-process}
 (@pxref{Asynchronous Processes, make-process}) to make the destination
-of the error output separate from the standard output; in that case,
-Emacs will use pipes for communicating with the subprocess.
+of the error output separate from the standard output.
 
   When a subprocess terminates, Emacs reads any pending output,
 then stops reading output from that subprocess.  Therefore, if the
diff --git a/etc/NEWS b/etc/NEWS
index 6d4fce1237..dc79f0826a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2229,6 +2229,12 @@ they will still be escaped, so the '.foo' symbol is still printed as
 and remapping parent of basic faces does not work reliably.
 Instead of remapping 'mode-line', you have to remap 'mode-line-active'.
 
++++
+** 'make-process' has been extended to support ptys when ':stderr' is set.
+Previously, setting ':stderr' to a non-nil value would force the
+process's connection to use pipes.  Now, Emacs will use a pty for
+stdin and stdout if requested no matter the value of ':stderr'.
+
 ---
 ** User option 'mail-source-ignore-errors' is now obsolete.
 The whole mechanism for prompting users to continue in case of
@@ -3188,6 +3194,12 @@ translation.
 This is useful when quoting shell arguments for a remote shell
 invocation.  Such shells are POSIX conformant by default.
 
++++
+** 'make-process' can set connection type independently for input and output.
+When calling 'make-process', communication via pty can be enabled
+selectively for just input or output by passing a cons cell for
+':connection-type', e.g. '(pipe . pty)'.
+
 +++
 ** 'signal-process' now consults the list 'signal-process-functions'.
 This is to determine which function has to be called in order to
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 70426ccaf2..99b43661f2 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -250,30 +250,6 @@ eshell-last-sync-output-start
   "A marker that tracks the beginning of output of the last subprocess.
 Used only on systems which do not support async subprocesses.")
 
-(defvar eshell-needs-pipe
-  '("bc"
-    ;; xclip.el (in GNU ELPA) calls all of these with
-    ;; `process-connection-type' set to nil.
-    "pbpaste" "putclip" "xclip" "xsel" "wl-copy")
-  "List of commands which need `process-connection-type' to be nil.
-Currently only affects commands in pipelines, and not those at
-the front.  If an element contains a directory part it must match
-the full name of a command, otherwise just the nondirectory part must match.")
-
-(defun eshell-needs-pipe-p (command)
-  "Return non-nil if COMMAND needs `process-connection-type' to be nil.
-See `eshell-needs-pipe'."
-  (and (bound-and-true-p eshell-in-pipeline-p)
-       (not (eq eshell-in-pipeline-p 'first))
-       ;; FIXME should this return non-nil for anything that is
-       ;; neither 'first nor 'last?  See bug#1388 discussion.
-       (catch 'found
-	 (dolist (exe eshell-needs-pipe)
-	   (if (string-equal exe (if (string-search "/" exe)
-				     command
-				   (file-name-nondirectory command)))
-	       (throw 'found t))))))
-
 (defun eshell-gather-process-output (command args)
   "Gather the output from COMMAND + ARGS."
   (require 'esh-var)
@@ -290,31 +266,36 @@ eshell-gather-process-output
     (cond
      ((fboundp 'make-process)
       (setq proc
-	    (let ((process-connection-type
-		   (unless (eshell-needs-pipe-p command)
-		     process-connection-type))
-		  (command (file-local-name (expand-file-name command))))
-	      (apply #'start-file-process
-		     (file-name-nondirectory command) nil command args)))
+            (let ((command (file-local-name (expand-file-name command)))
+                  (conn-type (pcase (bound-and-true-p eshell-in-pipeline-p)
+                               ('first '(nil . pipe))
+                               ('last  '(pipe . nil))
+                               ('t     'pipe)
+                               ('nil   nil))))
+              (make-process
+               :name (file-name-nondirectory command)
+               :buffer (current-buffer)
+               :command (cons command args)
+               :filter (if (eshell-interactive-output-p)
+                           #'eshell-output-filter
+                         #'eshell-insertion-filter)
+               :sentinel #'eshell-sentinel
+               :connection-type conn-type
+               :file-handler t)))
       (eshell-record-process-object proc)
-      (set-process-buffer proc (current-buffer))
-      (set-process-filter proc (if (eshell-interactive-output-p)
-	                           #'eshell-output-filter
-                                 #'eshell-insertion-filter))
-      (set-process-sentinel proc #'eshell-sentinel)
       (run-hook-with-args 'eshell-exec-hook proc)
       (when (fboundp 'process-coding-system)
 	(let ((coding-systems (process-coding-system proc)))
 	  (setq decoding (car coding-systems)
 		encoding (cdr coding-systems)))
-	;; If start-process decided to use some coding system for
+	;; If `make-process' decided to use some coding system for
 	;; decoding data sent from the process and the coding system
 	;; doesn't specify EOL conversion, we had better convert CRLF
 	;; to LF.
 	(if (vectorp (coding-system-eol-type decoding))
 	    (setq decoding (coding-system-change-eol-conversion decoding 'dos)
 		  changed t))
-	;; Even if start-process left the coding system for encoding
+	;; Even if `make-process' left the coding system for encoding
 	;; data sent from the process undecided, we had better use the
 	;; same one as what we use for decoding.  But, we should
 	;; suppress EOL conversion.
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index de55856830..451128ab20 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -904,7 +904,10 @@ tramp-adb-handle-make-process
 	    (signal 'wrong-type-argument (list #'symbolp coding)))
 	  (when (eq connection-type t)
 	    (setq connection-type 'pty))
-	  (unless (memq connection-type '(nil pipe pty))
+	  (unless (or (and (consp connection-type)
+			   (memq (car connection-type) '(nil pipe pty))
+			   (memq (cdr connection-type) '(nil pipe pty)))
+		      (memq connection-type '(nil pipe pty)))
 	    (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	  (unless (or (null filter) (eq filter t) (functionp filter))
 	    (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index e772af9e0a..8c48c3fc1e 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -2851,7 +2851,10 @@ tramp-sh-handle-make-process
 	    (signal 'wrong-type-argument (list #'symbolp coding)))
 	  (when (eq connection-type t)
 	    (setq connection-type 'pty))
-	  (unless (memq connection-type '(nil pipe pty))
+	  (unless (or (and (consp connection-type)
+			   (memq (car connection-type) '(nil pipe pty))
+			   (memq (cdr connection-type) '(nil pipe pty)))
+		      (memq connection-type '(nil pipe pty)))
 	    (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	  (unless (or (null filter) (eq filter t) (functionp filter))
 	    (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index b11fd293cc..8b654944fe 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -4605,7 +4605,10 @@ tramp-handle-make-process
 	  (signal 'wrong-type-argument (list #'symbolp coding)))
 	(when (eq connection-type t)
 	  (setq connection-type 'pty))
-	(unless (memq connection-type '(nil pipe pty))
+	(unless (or (and (consp connection-type)
+			 (memq (car connection-type) '(nil pipe pty))
+			 (memq (cdr connection-type) '(nil pipe pty)))
+		    (memq connection-type '(nil pipe pty)))
 	  (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	(unless (or (null filter) (eq filter t) (functionp filter))
 	  (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/src/callproc.c b/src/callproc.c
index dd162f36a6..aec0a2f5a5 100644
--- a/src/callproc.c
+++ b/src/callproc.c
@@ -650,7 +650,7 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd,
 
   child_errno
     = emacs_spawn (&pid, filefd, fd_output, fd_error, new_argv, env,
-                   SSDATA (current_dir), NULL, &oldset);
+                   SSDATA (current_dir), NULL, false, false, &oldset);
   eassert ((child_errno == 0) == (0 < pid));
 
   if (pid > 0)
@@ -1412,14 +1412,15 @@ emacs_posix_spawn_init_attributes (posix_spawnattr_t *attributes,
 int
 emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
              char **argv, char **envp, const char *cwd,
-             const char *pty, const sigset_t *oldset)
+             const char *pty_name, bool pty_in, bool pty_out,
+             const sigset_t *oldset)
 {
 #if USABLE_POSIX_SPAWN
   /* Prefer the simpler `posix_spawn' if available.  `posix_spawn'
      doesn't yet support setting up pseudoterminals, so we fall back
      to `vfork' if we're supposed to use a pseudoterminal.  */
 
-  bool use_posix_spawn = pty == NULL;
+  bool use_posix_spawn = pty_name == NULL;
 
   posix_spawn_file_actions_t actions;
   posix_spawnattr_t attributes;
@@ -1473,7 +1474,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   /* vfork, and prevent local vars from being clobbered by the vfork.  */
   pid_t *volatile newpid_volatile = newpid;
   const char *volatile cwd_volatile = cwd;
-  const char *volatile pty_volatile = pty;
+  const char *volatile ptyname_volatile = pty_name;
+  bool volatile ptyin_volatile = pty_in;
+  bool volatile ptyout_volatile = pty_out;
   char **volatile argv_volatile = argv;
   int volatile stdin_volatile = std_in;
   int volatile stdout_volatile = std_out;
@@ -1495,7 +1498,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 
   newpid = newpid_volatile;
   cwd = cwd_volatile;
-  pty = pty_volatile;
+  pty_name = ptyname_volatile;
+  pty_in = ptyin_volatile;
+  pty_out = ptyout_volatile;
   argv = argv_volatile;
   std_in = stdin_volatile;
   std_out = stdout_volatile;
@@ -1506,13 +1511,12 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   if (pid == 0)
 #endif /* not WINDOWSNT */
     {
-      bool pty_flag = pty != NULL;
       /* Make the pty be the controlling terminal of the process.  */
 #ifdef HAVE_PTYS
       dissociate_controlling_tty ();
 
       /* Make the pty's terminal the controlling terminal.  */
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 #ifdef TIOCSCTTY
 	  /* We ignore the return value
@@ -1521,7 +1525,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 #endif
 	}
 #if defined (LDISC1)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  struct termios t;
 	  tcgetattr (std_in, &t);
@@ -1531,7 +1535,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 	}
 #else
 #if defined (NTTYDISC) && defined (TIOCSETD)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  /* Use new line discipline.  */
 	  int ldisc = NTTYDISC;
@@ -1548,18 +1552,21 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
      both TIOCSCTTY is defined.  */
 	/* Now close the pty (if we had it open) and reopen it.
 	   This makes the pty the controlling terminal of the subprocess.  */
-      if (pty_flag)
+      if (pty_name)
 	{
 
 	  /* I wonder if emacs_close (emacs_open (pty, ...))
 	     would work?  */
-	  if (std_in >= 0)
+	  if (pty_in && std_in >= 0)
 	    emacs_close (std_in);
-          std_out = std_in = emacs_open_noquit (pty, O_RDWR, 0);
-
+	  int ptyfd = emacs_open_noquit (pty_name, O_RDWR, 0);
+	  if (pty_in)
+	    std_in = ptyfd;
+	  if (pty_out)
+	    std_out = ptyfd;
 	  if (std_in < 0)
 	    {
-	      emacs_perror (pty);
+	      emacs_perror (pty_name);
 	      _exit (EXIT_CANCELED);
 	    }
 
@@ -1599,7 +1606,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
       /* Stop blocking SIGCHLD in the child.  */
       unblock_child_signal (oldset);
 
-      if (pty_flag)
+      if (pty_out)
 	child_setup_tty (std_out);
 #endif
 
diff --git a/src/lisp.h b/src/lisp.h
index 2afe135674..264228618d 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4941,7 +4941,8 @@ #define DAEMON_RUNNING (w32_daemon_event != INVALID_HANDLE_VALUE)
 #endif
 
 extern int emacs_spawn (pid_t *, int, int, int, char **, char **,
-                        const char *, const char *, const sigset_t *);
+                        const char *, const char *, bool, bool,
+                        const sigset_t *);
 extern char **make_environment_block (Lisp_Object) ATTRIBUTE_RETURNS_NONNULL;
 extern void init_callproc_1 (void);
 extern void init_callproc (void);
diff --git a/src/process.c b/src/process.c
index d6d51b26e1..da5e9cb182 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1316,6 +1316,19 @@ set_process_filter_masks (struct Lisp_Process *p)
     add_process_read_fd (p->infd);
 }
 
+static bool
+is_pty_from_symbol (Lisp_Object symbol)
+{
+  if (EQ (symbol, Qpty))
+    return true;
+  else if (EQ (symbol, Qpipe))
+    return false;
+  else if (NILP (symbol))
+    return !NILP (Vprocess_connection_type);
+  else
+    report_file_error ("Unknown connection type", symbol);
+}
+
 DEFUN ("set-process-filter", Fset_process_filter, Sset_process_filter,
        2, 2, 0,
        doc: /* Give PROCESS the filter function FILTER; nil means default.
@@ -1741,15 +1754,18 @@ DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
 :connection-type TYPE -- TYPE is control type of device used to
 communicate with subprocesses.  Values are `pipe' to use a pipe, `pty'
 to use a pty, or nil to use the default specified through
-`process-connection-type'.
+`process-connection-type'.  If TYPE is a cons (INPUT . OUTPUT), then
+INPUT will be used for standard input and OUTPUT for standard output
+(and standard error if `:stderr' is nil).
 
 :filter FILTER -- Install FILTER as the process filter.
 
 :sentinel SENTINEL -- Install SENTINEL as the process sentinel.
 
 :stderr STDERR -- STDERR is either a buffer or a pipe process attached
-to the standard error of subprocess.  Specifying this implies
-`:connection-type' is set to `pipe'.  If STDERR is nil, standard error
+to the standard error of subprocess.  When specifying this, the
+subprocess's standard error will always communicate via a pipe, no
+matter the value of `:connection-type'.  If STDERR is nil, standard error
 is mixed with standard output and sent to BUFFER or FILTER.  (Note
 that specifying :stderr will create a new, separate (but associated)
 process, with its own filter and sentinel.  See
@@ -1845,22 +1861,20 @@ DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
   CHECK_TYPE (NILP (tem), Qnull, tem);
 
   tem = plist_get (contact, QCconnection_type);
-  if (EQ (tem, Qpty))
-    XPROCESS (proc)->pty_flag = true;
-  else if (EQ (tem, Qpipe))
-    XPROCESS (proc)->pty_flag = false;
-  else if (NILP (tem))
-    XPROCESS (proc)->pty_flag = !NILP (Vprocess_connection_type);
+  if (CONSP (tem))
+    {
+      XPROCESS (proc)->pty_in = is_pty_from_symbol (XCAR (tem));
+      XPROCESS (proc)->pty_out = is_pty_from_symbol (XCDR (tem));
+    }
   else
-    report_file_error ("Unknown connection type", tem);
-
-  if (!NILP (stderrproc))
     {
-      pset_stderrproc (XPROCESS (proc), stderrproc);
-
-      XPROCESS (proc)->pty_flag = false;
+      XPROCESS (proc)->pty_in = XPROCESS (proc)->pty_out =
+	is_pty_from_symbol (tem);
     }
 
+  if (!NILP (stderrproc))
+    pset_stderrproc (XPROCESS (proc), stderrproc);
+
 #ifdef HAVE_GNUTLS
   /* AKA GNUTLS_INITSTAGE(proc).  */
   verify (GNUTLS_STAGE_EMPTY == 0);
@@ -2099,66 +2113,80 @@ verify (PROCESS_OPEN_FDS == EXEC_MONITOR_OUTPUT + 1);
 create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
 {
   struct Lisp_Process *p = XPROCESS (process);
-  int inchannel, outchannel;
+  int inchannel = -1, outchannel = -1;
   pid_t pid = -1;
   int vfork_errno;
   int forkin, forkout, forkerr = -1;
-  bool pty_flag = 0;
+  bool pty_in = false, pty_out = false;
   char pty_name[PTY_NAME_SIZE];
   Lisp_Object lisp_pty_name = Qnil;
+  int ptychannel = -1, pty_tty = -1;
   sigset_t oldset;
 
   /* Ensure that the SIGCHLD handler can notify
      `wait_reading_process_output'.  */
   child_signal_init ();
 
-  inchannel = outchannel = -1;
-
-  if (p->pty_flag)
-    outchannel = inchannel = allocate_pty (pty_name);
+  if (p->pty_in || p->pty_out)
+    ptychannel = allocate_pty (pty_name);
 
-  if (inchannel >= 0)
+  if (ptychannel >= 0)
     {
-      p->open_fd[READ_FROM_SUBPROCESS] = inchannel;
 #if ! defined (USG) || defined (USG_SUBTTY_WORKS)
       /* On most USG systems it does not work to open the pty's tty here,
 	 then close it and reopen it in the child.  */
       /* Don't let this terminal become our controlling terminal
 	 (in case we don't have one).  */
-      forkout = forkin = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
-      if (forkin < 0)
+      pty_tty = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
+      if (pty_tty < 0)
 	report_file_error ("Opening pty", Qnil);
-      p->open_fd[SUBPROCESS_STDIN] = forkin;
-#else
-      forkin = forkout = -1;
 #endif /* not USG, or USG_SUBTTY_WORKS */
-      pty_flag = 1;
+      pty_in = p->pty_in;
+      pty_out = p->pty_out;
       lisp_pty_name = build_string (pty_name);
     }
+
+  /* Set up stdin for the child process.  */
+  if (ptychannel >= 0 && p->pty_in)
+    {
+      p->open_fd[SUBPROCESS_STDIN] = forkin = pty_tty;
+      outchannel = ptychannel;
+    }
   else
     {
-      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0
-	  || emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0)
 	report_file_error ("Creating pipe", Qnil);
       forkin = p->open_fd[SUBPROCESS_STDIN];
       outchannel = p->open_fd[WRITE_TO_SUBPROCESS];
+    }
+
+  /* Set up stdout for the child process.  */
+  if (ptychannel >= 0 && p->pty_out)
+    {
+      forkout = pty_tty;
+      p->open_fd[READ_FROM_SUBPROCESS] = inchannel = ptychannel;
+    }
+  else
+    {
+      if (emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+	report_file_error ("Creating pipe", Qnil);
       inchannel = p->open_fd[READ_FROM_SUBPROCESS];
       forkout = p->open_fd[SUBPROCESS_STDOUT];
 
 #if defined(GNU_LINUX) && defined(F_SETPIPE_SZ)
       fcntl (inchannel, F_SETPIPE_SZ, read_process_output_max);
 #endif
+    }
 
-      if (!NILP (p->stderrproc))
-	{
-	  struct Lisp_Process *pp = XPROCESS (p->stderrproc);
+  if (!NILP (p->stderrproc))
+    {
+      struct Lisp_Process *pp = XPROCESS (p->stderrproc);
 
-	  forkerr = pp->open_fd[SUBPROCESS_STDOUT];
+      forkerr = pp->open_fd[SUBPROCESS_STDOUT];
 
-	  /* Close unnecessary file descriptors.  */
-	  close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
-	  close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
-	}
+      /* Close unnecessary file descriptors.  */
+      close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
+      close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
     }
 
   if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel)
@@ -2183,7 +2211,8 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
      we just reopen the device (see emacs_get_tty_pgrp) as this is
      more portable (see USG_SUBTTY_WORKS above).  */
 
-  p->pty_flag = pty_flag;
+  p->pty_in = pty_in;
+  p->pty_out = pty_out;
   pset_status (p, Qrun);
 
   if (!EQ (p->command, Qt)
@@ -2199,13 +2228,15 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
   block_input ();
   block_child_signal (&oldset);
 
-  pty_flag = p->pty_flag;
-  eassert (pty_flag == ! NILP (lisp_pty_name));
+  pty_in = p->pty_in;
+  pty_out = p->pty_out;
+  eassert ((pty_in || pty_out) == ! NILP (lisp_pty_name));
 
   vfork_errno
     = emacs_spawn (&pid, forkin, forkout, forkerr, new_argv, env,
                    SSDATA (current_dir),
-                   pty_flag ? SSDATA (lisp_pty_name) : NULL, &oldset);
+                   pty_in || pty_out ? SSDATA (lisp_pty_name) : NULL,
+                   pty_in, pty_out, &oldset);
 
   eassert ((vfork_errno == 0) == (0 < pid));
 
@@ -2263,7 +2294,7 @@ create_pty (Lisp_Object process)
 {
   struct Lisp_Process *p = XPROCESS (process);
   char pty_name[PTY_NAME_SIZE];
-  int pty_fd = !p->pty_flag ? -1 : allocate_pty (pty_name);
+  int pty_fd = !(p->pty_in || p->pty_out) ? -1 : allocate_pty (pty_name);
 
   if (pty_fd >= 0)
     {
@@ -2301,7 +2332,7 @@ create_pty (Lisp_Object process)
 	 we just reopen the device (see emacs_get_tty_pgrp) as this is
 	 more portable (see USG_SUBTTY_WORKS above).  */
 
-      p->pty_flag = 1;
+      p->pty_in = p->pty_out = true;
       pset_status (p, Qrun);
       setup_process_coding_systems (process);
 
@@ -2412,7 +2443,7 @@ DEFUN ("make-pipe-process", Fmake_pipe_process, Smake_pipe_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -3147,7 +3178,7 @@ DEFUN ("make-serial-process", Fmake_serial_process, Smake_serial_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -6798,7 +6829,7 @@ process_send_signal (Lisp_Object process, int signo, Lisp_Object current_group,
     error ("Process %s is not active",
 	   SDATA (p->name));
 
-  if (!p->pty_flag)
+  if (! p->pty_in)
     current_group = Qnil;
 
   /* If we are using pgrps, get a pgrp number and make it negative.  */
@@ -7167,7 +7198,7 @@ DEFUN ("process-send-eof", Fprocess_send_eof, Sprocess_send_eof, 0, 1, 0,
       send_process (proc, "", 0, Qnil);
     }
 
-  if (XPROCESS (proc)->pty_flag)
+  if (XPROCESS (proc)->pty_in)
     send_process (proc, "\004", 1, Qnil);
   else if (EQ (XPROCESS (proc)->type, Qserial))
     {
diff --git a/src/process.h b/src/process.h
index 392b661ce6..92baf0c4cb 100644
--- a/src/process.h
+++ b/src/process.h
@@ -156,8 +156,9 @@ #define EMACS_PROCESS_H
     /* True means kill silently if Emacs is exited.
        This is the inverse of the `query-on-exit' flag.  */
     bool_bf kill_without_query : 1;
-    /* True if communicating through a pty.  */
-    bool_bf pty_flag : 1;
+    /* True if communicating through a pty for input or output.  */
+    bool_bf pty_in : 1;
+    bool_bf pty_out : 1;
     /* Flag to set coding-system of the process buffer from the
        coding_system used to decode process output.  */
     bool_bf inherit_coding_system_flag : 1;
diff --git a/test/lisp/eshell/esh-proc-tests.el b/test/lisp/eshell/esh-proc-tests.el
index 7f461d1813..564be70ff0 100644
--- a/test/lisp/eshell/esh-proc-tests.el
+++ b/test/lisp/eshell/esh-proc-tests.el
@@ -28,6 +28,15 @@
                            (file-name-directory (or load-file-name
                                                     default-directory))))
 
+(defvar esh-proc-test--detect-pty-cmd
+  (concat "sh -c '"
+          "if [ -t 0 ]; then echo stdin; fi; "
+          "if [ -t 1 ]; then echo stdout; fi; "
+          "if [ -t 2 ]; then echo stderr; fi"
+          "'"))
+
+;;; Tests:
+
 (ert-deftest esh-proc-test/sigpipe-exits-process ()
   "Test that a SIGPIPE is properly sent to a process if a pipe closes"
   (skip-unless (and (executable-find "sh")
@@ -44,6 +53,34 @@ esh-proc-test/sigpipe-exits-process
    (eshell-wait-for-subprocess t)
    (should (eq (process-list) nil))))
 
+(ert-deftest esh-proc-test/pipeline-connection-type/no-pipeline ()
+  "Test that all streams are PTYs when a command is not in a pipeline."
+  (should (equal (eshell-test-command-result esh-proc-test--detect-pty-cmd)
+                 ;; PTYs aren't supported on MS-Windows.
+                 (unless (eq system-type 'windows-nt)
+                   "stdin\nstdout\nstderr\n"))))
+
+(ert-deftest esh-proc-test/pipeline-connection-type/first ()
+  "Test that only stdin is a PTY when a command starts a pipeline."
+  (should (equal (eshell-test-command-result
+                  (concat esh-proc-test--detect-pty-cmd " | cat"))
+                 (unless (eq system-type 'windows-nt)
+                   "stdin\n"))))
+
+(ert-deftest esh-proc-test/pipeline-connection-type/middle ()
+  "Test that all streams are pipes when a command is in the middle of a
+pipeline."
+  (should (equal (eshell-test-command-result
+                  (concat "echo | " esh-proc-test--detect-pty-cmd " | cat"))
+                 nil)))
+
+(ert-deftest esh-proc-test/pipeline-connection-type/last ()
+  "Test that only output streams are PTYs when a command ends a pipeline."
+  (should (equal (eshell-test-command-result
+                  (concat "echo | " esh-proc-test--detect-pty-cmd))
+                 (unless (eq system-type 'windows-nt)
+                   "stdout\nstderr\n"))))
+
 (ert-deftest esh-proc-test/kill-pipeline ()
   "Test that killing a pipeline of processes only emits a single
 prompt.  See bug#54136."
diff --git a/test/src/process-tests.el b/test/src/process-tests.el
index f1ed7e18d5..41320672a0 100644
--- a/test/src/process-tests.el
+++ b/test/src/process-tests.el
@@ -38,10 +38,11 @@
 ;; Timeout in seconds; the test fails if the timeout is reached.
 (defvar process-test-sentinel-wait-timeout 2.0)
 
-;; Start a process that exits immediately.  Call WAIT-FUNCTION,
-;; possibly multiple times, to wait for the process to complete.
-(defun process-test-sentinel-wait-function-working-p (wait-function)
-  (let ((proc (start-process "test" nil "bash" "-c" "exit 20"))
+(defun process-test-wait-for-sentinel (proc exit-status &optional wait-function)
+  "Set a sentinel on PROC and wait for it to be called with EXIT-STATUS.
+Call WAIT-FUNCTION, possibly multiple times, to wait for the
+process to complete."
+  (let ((wait-function (or wait-function #'accept-process-output))
 	(sentinel-called nil)
 	(start-time (float-time)))
     (set-process-sentinel proc (lambda (_proc _msg)
@@ -50,21 +51,22 @@ process-test-sentinel-wait-function-working-p
 		    (> (- (float-time) start-time)
 		       process-test-sentinel-wait-timeout)))
       (funcall wait-function))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
-    sentinel-called))
+    (should sentinel-called)
+    (should (eq (process-status proc) 'exit))
+    (should (= (process-exit-status proc) exit-status))))
 
 (ert-deftest process-test-sentinel-accept-process-output ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (should (process-test-sentinel-wait-function-working-p
-           #'accept-process-output))))
+    (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
+      (should (process-test-wait-for-sentinel proc 20)))))
 
 (ert-deftest process-test-sentinel-sit-for ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (should
-   (process-test-sentinel-wait-function-working-p (lambda () (sit-for 0.01 t))))))
+    (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
+      (should (process-test-wait-for-sentinel
+               proc 20 (lambda () (sit-for 0.01 t)))))))
 
 (when (eq system-type 'windows-nt)
   (ert-deftest process-test-quoted-batfile ()
@@ -97,17 +99,8 @@ process-test-stderr-buffer
 						    "echo hello stderr! >&2; "
 						    "exit 20"))
 			     :buffer stdout-buffer
-			     :stderr stderr-buffer))
-	 (sentinel-called nil)
-	 (start-time (float-time)))
-    (set-process-sentinel proc (lambda (_proc _msg)
-				 (setq sentinel-called t)))
-    (while (not (or sentinel-called
-		    (> (- (float-time) start-time)
-		       process-test-sentinel-wait-timeout)))
-      (accept-process-output))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
+			     :stderr stderr-buffer)))
+    (process-test-wait-for-sentinel proc 20)
     (should (with-current-buffer stdout-buffer
 	      (goto-char (point-min))
 	      (looking-at "hello stdout!")))
@@ -118,8 +111,7 @@ process-test-stderr-buffer
 (ert-deftest process-test-stderr-filter ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (let* ((sentinel-called nil)
-	 (stderr-sentinel-called nil)
+  (let* ((stderr-sentinel-called nil)
 	 (stdout-output nil)
 	 (stderr-output nil)
 	 (stdout-buffer (generate-new-buffer "*stdout*"))
@@ -131,23 +123,14 @@ process-test-stderr-filter
 					    (concat "echo hello stdout!; "
 						    "echo hello stderr! >&2; "
 						    "exit 20"))
-			     :stderr stderr-proc))
-	 (start-time (float-time)))
+			     :stderr stderr-proc)))
     (set-process-filter proc (lambda (_proc input)
 			       (push input stdout-output)))
-    (set-process-sentinel proc (lambda (_proc _msg)
-				 (setq sentinel-called t)))
     (set-process-filter stderr-proc (lambda (_proc input)
 				      (push input stderr-output)))
     (set-process-sentinel stderr-proc (lambda (_proc _input)
 					(setq stderr-sentinel-called t)))
-    (while (not (or sentinel-called
-		    (> (- (float-time) start-time)
-		       process-test-sentinel-wait-timeout)))
-      (accept-process-output))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
-    (should sentinel-called)
+    (process-test-wait-for-sentinel proc 20)
     (should (equal 1 (with-current-buffer stdout-buffer
 		       (point-max))))
     (should (equal "hello stdout!\n"
@@ -289,6 +272,74 @@ make-process-w32-debug-spawn-error
                   (error :got-error))))
     (should have-called-debugger))))
 
+(defun make-process/test-connection-type (ttys &rest args)
+  "Make a process and check whether its standard streams match TTYS.
+This calls `make-process', passing ARGS to adjust how the process
+is created.  TTYS should be a list of 3 boolean values,
+indicating whether the subprocess's stdin, stdout, and stderr
+should be a TTY, respectively."
+  (declare (indent 1))
+  (let* (;; MS-Windows doesn't support communicating via pty.
+         (ttys (if (eq system-type 'windows-nt) '(nil nil nil) ttys))
+         (expected-output (concat (and (nth 0 ttys) "stdin\n")
+                                  (and (nth 1 ttys) "stdout\n")
+                                  (and (nth 2 ttys) "stderr\n")))
+         (stdout-buffer (generate-new-buffer "*stdout*"))
+         (proc (apply
+                #'make-process
+                :name "test"
+                :command (list "sh" "-c"
+                               (concat "if [ -t 0 ]; then echo stdin; fi; "
+                                       "if [ -t 1 ]; then echo stdout; fi; "
+                                       "if [ -t 2 ]; then echo stderr; fi"))
+                :buffer stdout-buffer
+                args)))
+    (process-test-wait-for-sentinel proc 0)
+    (should (equal (with-current-buffer stdout-buffer (buffer-string))
+                   expected-output))))
+
+(ert-deftest make-process/connection-type/pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t t t)
+    :connection-type 'pty))
+
+(ert-deftest make-process/connection-type/pty-2 ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t t t)
+    :connection-type '(pty . pty)))
+
+(ert-deftest make-process/connection-type/pipe ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil nil nil)
+    :connection-type 'pipe))
+
+(ert-deftest make-process/connection-type/pipe-2 ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil nil nil)
+    :connection-type '(pipe . pipe)))
+
+(ert-deftest make-process/connection-type/in-pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t nil nil)
+    :connection-type '(pty . pipe)))
+
+(ert-deftest make-process/connection-type/out-pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil t t)
+    :connection-type '(pipe . pty)))
+
+(ert-deftest make-process/connection-type/pty-with-stderr-buffer ()
+  (skip-unless (executable-find "sh"))
+  (let ((stderr-buffer (generate-new-buffer "*stderr*")))
+    (make-process/test-connection-type '(t t nil)
+      :connection-type 'pty :stderr stderr-buffer)))
+
+(ert-deftest make-process/connection-type/out-pty-with-stderr-buffer ()
+  (skip-unless (executable-find "sh"))
+  (let ((stderr-buffer (generate-new-buffer "*stderr*")))
+    (make-process/test-connection-type '(nil t nil)
+      :connection-type '(pipe . pty) :stderr stderr-buffer)))
+
 (ert-deftest make-process/file-handler/found ()
   "Check that the `:file-handler’ argument of `make-process’
 works as expected if a file name handler is found."
-- 
2.25.1


[-- Attachment #3: 0002-Add-STREAM-argument-to-process-tty-name.patch --]
[-- Type: text/plain, Size: 7521 bytes --]

From 56331ff0577b12ade7e95d774d35946d3f50d1b7 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Tue, 19 Jul 2022 21:36:54 -0700
Subject: [PATCH 2/2] Add STREAM argument to 'process-tty-name'

* src/process.c (process-tty-name): Add STREAM argument.

* lisp/eshell/esh-io.el (eshell-close-target): Only call
'process-send-eof' once if the process's stdin is a pipe.

* test/src/process-tests.el (make-process/test-connection-type): Check
behavior of 'process-tty-name'.

* doc/lispref/processes.texi (Process Information): Document the new
argument.

* etc/NEWS: Announce this change.
---
 doc/lispref/processes.texi | 17 +++++++++++------
 etc/NEWS                   |  5 ++++-
 lisp/eshell/esh-io.el      | 27 +++++++++++++++------------
 src/process.c              | 23 +++++++++++++++++++----
 test/src/process-tests.el  |  3 +++
 5 files changed, 52 insertions(+), 23 deletions(-)

diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index a7e08054c7..bbca48e6c5 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -1243,15 +1243,20 @@ Process Information
 whether the connection was closed normally or abnormally.
 @end defun
 
-@defun process-tty-name process
+@defun process-tty-name process &optional stream
 This function returns the terminal name that @var{process} is using for
 its communication with Emacs---or @code{nil} if it is using pipes
 instead of a pty (see @code{process-connection-type} in
-@ref{Asynchronous Processes}).  If @var{process} represents a program
-running on a remote host, the terminal name used by that program on
-the remote host is provided as process property @code{remote-tty}.  If
-@var{process} represents a network, serial, or pipe connection, the
-value is @code{nil}.
+@ref{Asynchronous Processes}).  If @var{stream} is one of @code{stdin},
+@code{stdout}, or @code{stderr}, this function returns the terminal
+name (or @code{nil}, as above) that @var{process} uses for that stream
+specifically.  You can use this to determine whether a particular
+stream uses a pipe or a pty.
+
+If @var{process} represents a program running on a remote host, the
+terminal name used by that program on the remote host is provided as
+process property @code{remote-tty}.  If @var{process} represents a
+network, serial, or pipe connection, the value is @code{nil}.
 @end defun
 
 @defun process-coding-system process
diff --git a/etc/NEWS b/etc/NEWS
index dc79f0826a..23777d349e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3198,7 +3198,10 @@ invocation.  Such shells are POSIX conformant by default.
 ** 'make-process' can set connection type independently for input and output.
 When calling 'make-process', communication via pty can be enabled
 selectively for just input or output by passing a cons cell for
-':connection-type', e.g. '(pipe . pty)'.
+':connection-type', e.g. '(pipe . pty)'.  When examining a process
+later, you can determine whether a particular stream for a process
+uses a pty by passing one of 'stdin', 'stdout', or 'stderr' as the
+second argument to 'process-tty-name'.
 
 +++
 ** 'signal-process' now consults the list 'signal-process-functions'.
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index c035890ddf..68e52a2c9c 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -276,18 +276,21 @@ eshell-close-target
    ;; 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)
-    ;; According to POSIX.1-2017, section 11.1.9, 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 shouldn't break
-    ;; anything, we'll just send the maximum we'd ever need.  See
-    ;; bug#56025 for further details.
-    (let ((i 0))
-      (while (and (<= (cl-incf i) 3)
+    ;; 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))))
 
diff --git a/src/process.c b/src/process.c
index da5e9cb182..adc508156f 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1243,14 +1243,29 @@ DEFUN ("process-command", Fprocess_command, Sprocess_command, 1, 1, 0,
   return XPROCESS (process)->command;
 }
 
-DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 1, 0,
+DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 2, 0,
        doc: /* Return the name of the terminal PROCESS uses, or nil if none.
 This is the terminal that the process itself reads and writes on,
-not the name of the pty that Emacs uses to talk with that terminal.  */)
-  (register Lisp_Object process)
+not the name of the pty that Emacs uses to talk with that terminal.
+
+If STREAM is one of `stdin', `stdout', or `stderr', return the name of
+the terminal PROCESS uses for that stream.  This can be used to detect
+whether a particular stream is connected via a pipe or a pty.  */)
+  (register Lisp_Object process, Lisp_Object stream)
 {
   CHECK_PROCESS (process);
-  return XPROCESS (process)->tty_name;
+  register struct Lisp_Process *p = XPROCESS (process);
+
+  if (NILP (stream))
+    return p->tty_name;
+  else if (EQ (stream, Qstdin))
+    return p->pty_in ? p->tty_name : Qnil;
+  else if (EQ (stream, Qstdout))
+    return p->pty_out ? p->tty_name : Qnil;
+  else if (EQ (stream, Qstderr))
+    return p->pty_out && NILP (p->stderrproc) ? p->tty_name : Qnil;
+  else
+    signal_error ("Unknown stream", stream);
 }
 
 static void
diff --git a/test/src/process-tests.el b/test/src/process-tests.el
index 41320672a0..6ba5930ee6 100644
--- a/test/src/process-tests.el
+++ b/test/src/process-tests.el
@@ -294,6 +294,9 @@ make-process/test-connection-type
                                        "if [ -t 2 ]; then echo stderr; fi"))
                 :buffer stdout-buffer
                 args)))
+    (should (eq (and (process-tty-name proc 'stdin) t) (nth 0 ttys)))
+    (should (eq (and (process-tty-name proc 'stdout) t) (nth 1 ttys)))
+    (should (eq (and (process-tty-name proc 'stderr) t) (nth 2 ttys)))
     (process-test-wait-for-sentinel proc 0)
     (should (equal (with-current-buffer stdout-buffer (buffer-string))
                    expected-output))))
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v4] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-24  5:19                                                                   ` bug#56025: [PATCH v3] " Jim Porter
@ 2022-07-24  5:29                                                                     ` Jim Porter
  2022-07-24  9:08                                                                       ` Lars Ingebrigtsen
  2022-07-24  9:47                                                                       ` Eli Zaretskii
  0 siblings, 2 replies; 64+ messages in thread
From: Jim Porter @ 2022-07-24  5:29 UTC (permalink / raw)
  To: Ken Brown, Sean Whitton, Eli Zaretskii; +Cc: larsi, 56025

[-- Attachment #1: Type: text/plain, Size: 383 bytes --]

On 7/23/2022 10:19 PM, Jim Porter wrote:
> I thought about it some more, and just to be sure the Eshell bits don't 
> regress at some point in the future, I added some new unit tests in 
> test/lisp/eshell/esh-proc-tests.el...

Oops. I forgot to add some `(skip-unless ...)' forms for these tests, 
so... here they are. Hopefully this will be the last message from me for 
a bit. :C

[-- Attachment #2: 0001-Allow-creating-processes-where-only-one-of-stdin-or-.patch --]
[-- Type: text/plain, Size: 38705 bytes --]

From ba745998d7f26262a721413b7dc49549e673387c Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 17 Jul 2022 20:25:00 -0700
Subject: [PATCH 1/2] Allow creating processes where only one of stdin or
 stdout is a PTY

* src/lisp.h (emacs_spawn):
* src/callproc.c (emacs_spawn): Add PTY_IN and PTY_OUT arguments to
specify which streams should be set up as a PTY.
(call_process): Adjust call to 'emacs_spawn'.

* src/process.h (Lisp_Process): Replace 'pty_flag' with 'pty_in' and
'pty_out'.

* src/process.c (is_pty_from_symbol): New function.
(make-process): Allow :connection-type to be a cons cell, and allow
using a stderr process with a PTY for stdin/stdout.
(create_process): Handle creating a process where only one of stdin or
stdout is a PTY.

* lisp/eshell/esh-proc.el (eshell-needs-pipe, eshell-needs-pipe-p):
Remove.
(eshell-gather-process-output): Use 'make-process' and set
':connection-type' as needed by the value of 'eshell-in-pipeline-p'.

* lisp/net/tramp.el (tramp-handle-make-process):
* lisp/net/tramp-adb.el (tramp-adb-handle-make-process):
* lisp/net/tramp-sh.el (tramp-sh-handle-make-process): Don't signal an
error when ':connection-type' is a cons cell.

* test/src/process-tests.el
(process-test-sentinel-wait-function-working-p): Allow passing PROC
in, and rework into...
(process-test-wait-for-sentinel): ... this.
(process-test-sentinel-accept-process-output)
(process-test-sentinel-sit-for, process-test-quoted-batfile)
(process-test-stderr-filter): Use 'process-test-wait-for-sentinel'.
(make/process/test-connection-type): New function.
(make-process/connection-type/pty, make-process/connection-type/pty-2)
(make-process/connection-type/pipe)
(make-process/connection-type/pipe-2)
(make-process/connection-type/in-pty)
(make-process/connection-type/out-pty)
(make-process/connection-type/pty-with-stderr-buffer)
(make-process/connection-type/out-pty-with-stderr-buffer): New tests.

* test/lisp/eshell/esh-proc-tests.el (esh-proc-test--detect-pty-cmd):
New variable.
(esh-proc-test/pipeline-connection-type/no-pipeline)
(esh-proc-test/pipeline-connection-type/first)
(esh-proc-test/pipeline-connection-type/middle)
(esh-proc-test/pipeline-connection-type/last): New tests.

* doc/lispref/processes.texi (Asynchronous Processes): Document new
':connection-type' behavior.
(Output from Processes): Remove caveat about ':stderr' forcing
'make-process' to use pipes.

* etc/NEWS: Announce this change.
---
 doc/lispref/processes.texi         |  28 +++----
 etc/NEWS                           |  12 +++
 lisp/eshell/esh-proc.el            |  55 ++++--------
 lisp/net/tramp-adb.el              |   5 +-
 lisp/net/tramp-sh.el               |   5 +-
 lisp/net/tramp.el                  |   5 +-
 src/callproc.c                     |  37 +++++----
 src/lisp.h                         |   3 +-
 src/process.c                      | 129 ++++++++++++++++++-----------
 src/process.h                      |   5 +-
 test/lisp/eshell/esh-proc-tests.el |  43 ++++++++++
 test/src/process-tests.el          | 121 +++++++++++++++++++--------
 12 files changed, 288 insertions(+), 160 deletions(-)

diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index 80c371e1c6..a7e08054c7 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -705,12 +705,13 @@ Asynchronous Processes
 Initialize the type of device used to communicate with the subprocess.
 Possible values are @code{pty} to use a pty, @code{pipe} to use a
 pipe, or @code{nil} to use the default derived from the value of the
-@code{process-connection-type} variable.  This parameter and the value
-of @code{process-connection-type} are ignored if a non-@code{nil}
-value is specified for the @code{:stderr} parameter; in that case, the
-type will always be @code{pipe}.  On systems where ptys are not
-available (MS-Windows), this parameter is likewise ignored, and pipes
-are used unconditionally.
+@code{process-connection-type} variable.  If @var{type} is a cons cell
+@w{@code{(@var{input} . @var{output})}}, then @var{input} will be used
+for standard input and @var{output} for standard output (and standard
+error if @code{:stderr} is @code{nil}).
+
+On systems where ptys are not available (MS-Windows), this parameter
+is ignored, and pipes are used unconditionally.
 
 @item :noquery @var{query-flag}
 Initialize the process query flag to @var{query-flag}.
@@ -1530,20 +1531,11 @@ Output from Processes
 default filter discards the output.
 
   If the subprocess writes to its standard error stream, by default
-the error output is also passed to the process filter function.  If
-Emacs uses a pseudo-TTY (pty) for communication with the subprocess,
-then it is impossible to separate the standard output and standard
-error streams of the subprocess, because a pseudo-TTY has only one
-output channel.  In that case, if you want to keep the output to those
-streams separate, you should redirect one of them to a file---for
-example, by using an appropriate shell command via
-@code{start-process-shell-command} or a similar function.
-
-  Alternatively, you could use the @code{:stderr} parameter with a
+the error output is also passed to the process filter function.
+Alternatively, you could use the @code{:stderr} parameter with a
 non-@code{nil} value in a call to @code{make-process}
 (@pxref{Asynchronous Processes, make-process}) to make the destination
-of the error output separate from the standard output; in that case,
-Emacs will use pipes for communicating with the subprocess.
+of the error output separate from the standard output.
 
   When a subprocess terminates, Emacs reads any pending output,
 then stops reading output from that subprocess.  Therefore, if the
diff --git a/etc/NEWS b/etc/NEWS
index 6d4fce1237..dc79f0826a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2229,6 +2229,12 @@ they will still be escaped, so the '.foo' symbol is still printed as
 and remapping parent of basic faces does not work reliably.
 Instead of remapping 'mode-line', you have to remap 'mode-line-active'.
 
++++
+** 'make-process' has been extended to support ptys when ':stderr' is set.
+Previously, setting ':stderr' to a non-nil value would force the
+process's connection to use pipes.  Now, Emacs will use a pty for
+stdin and stdout if requested no matter the value of ':stderr'.
+
 ---
 ** User option 'mail-source-ignore-errors' is now obsolete.
 The whole mechanism for prompting users to continue in case of
@@ -3188,6 +3194,12 @@ translation.
 This is useful when quoting shell arguments for a remote shell
 invocation.  Such shells are POSIX conformant by default.
 
++++
+** 'make-process' can set connection type independently for input and output.
+When calling 'make-process', communication via pty can be enabled
+selectively for just input or output by passing a cons cell for
+':connection-type', e.g. '(pipe . pty)'.
+
 +++
 ** 'signal-process' now consults the list 'signal-process-functions'.
 This is to determine which function has to be called in order to
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 70426ccaf2..99b43661f2 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -250,30 +250,6 @@ eshell-last-sync-output-start
   "A marker that tracks the beginning of output of the last subprocess.
 Used only on systems which do not support async subprocesses.")
 
-(defvar eshell-needs-pipe
-  '("bc"
-    ;; xclip.el (in GNU ELPA) calls all of these with
-    ;; `process-connection-type' set to nil.
-    "pbpaste" "putclip" "xclip" "xsel" "wl-copy")
-  "List of commands which need `process-connection-type' to be nil.
-Currently only affects commands in pipelines, and not those at
-the front.  If an element contains a directory part it must match
-the full name of a command, otherwise just the nondirectory part must match.")
-
-(defun eshell-needs-pipe-p (command)
-  "Return non-nil if COMMAND needs `process-connection-type' to be nil.
-See `eshell-needs-pipe'."
-  (and (bound-and-true-p eshell-in-pipeline-p)
-       (not (eq eshell-in-pipeline-p 'first))
-       ;; FIXME should this return non-nil for anything that is
-       ;; neither 'first nor 'last?  See bug#1388 discussion.
-       (catch 'found
-	 (dolist (exe eshell-needs-pipe)
-	   (if (string-equal exe (if (string-search "/" exe)
-				     command
-				   (file-name-nondirectory command)))
-	       (throw 'found t))))))
-
 (defun eshell-gather-process-output (command args)
   "Gather the output from COMMAND + ARGS."
   (require 'esh-var)
@@ -290,31 +266,36 @@ eshell-gather-process-output
     (cond
      ((fboundp 'make-process)
       (setq proc
-	    (let ((process-connection-type
-		   (unless (eshell-needs-pipe-p command)
-		     process-connection-type))
-		  (command (file-local-name (expand-file-name command))))
-	      (apply #'start-file-process
-		     (file-name-nondirectory command) nil command args)))
+            (let ((command (file-local-name (expand-file-name command)))
+                  (conn-type (pcase (bound-and-true-p eshell-in-pipeline-p)
+                               ('first '(nil . pipe))
+                               ('last  '(pipe . nil))
+                               ('t     'pipe)
+                               ('nil   nil))))
+              (make-process
+               :name (file-name-nondirectory command)
+               :buffer (current-buffer)
+               :command (cons command args)
+               :filter (if (eshell-interactive-output-p)
+                           #'eshell-output-filter
+                         #'eshell-insertion-filter)
+               :sentinel #'eshell-sentinel
+               :connection-type conn-type
+               :file-handler t)))
       (eshell-record-process-object proc)
-      (set-process-buffer proc (current-buffer))
-      (set-process-filter proc (if (eshell-interactive-output-p)
-	                           #'eshell-output-filter
-                                 #'eshell-insertion-filter))
-      (set-process-sentinel proc #'eshell-sentinel)
       (run-hook-with-args 'eshell-exec-hook proc)
       (when (fboundp 'process-coding-system)
 	(let ((coding-systems (process-coding-system proc)))
 	  (setq decoding (car coding-systems)
 		encoding (cdr coding-systems)))
-	;; If start-process decided to use some coding system for
+	;; If `make-process' decided to use some coding system for
 	;; decoding data sent from the process and the coding system
 	;; doesn't specify EOL conversion, we had better convert CRLF
 	;; to LF.
 	(if (vectorp (coding-system-eol-type decoding))
 	    (setq decoding (coding-system-change-eol-conversion decoding 'dos)
 		  changed t))
-	;; Even if start-process left the coding system for encoding
+	;; Even if `make-process' left the coding system for encoding
 	;; data sent from the process undecided, we had better use the
 	;; same one as what we use for decoding.  But, we should
 	;; suppress EOL conversion.
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index de55856830..451128ab20 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -904,7 +904,10 @@ tramp-adb-handle-make-process
 	    (signal 'wrong-type-argument (list #'symbolp coding)))
 	  (when (eq connection-type t)
 	    (setq connection-type 'pty))
-	  (unless (memq connection-type '(nil pipe pty))
+	  (unless (or (and (consp connection-type)
+			   (memq (car connection-type) '(nil pipe pty))
+			   (memq (cdr connection-type) '(nil pipe pty)))
+		      (memq connection-type '(nil pipe pty)))
 	    (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	  (unless (or (null filter) (eq filter t) (functionp filter))
 	    (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index e772af9e0a..8c48c3fc1e 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -2851,7 +2851,10 @@ tramp-sh-handle-make-process
 	    (signal 'wrong-type-argument (list #'symbolp coding)))
 	  (when (eq connection-type t)
 	    (setq connection-type 'pty))
-	  (unless (memq connection-type '(nil pipe pty))
+	  (unless (or (and (consp connection-type)
+			   (memq (car connection-type) '(nil pipe pty))
+			   (memq (cdr connection-type) '(nil pipe pty)))
+		      (memq connection-type '(nil pipe pty)))
 	    (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	  (unless (or (null filter) (eq filter t) (functionp filter))
 	    (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index b11fd293cc..8b654944fe 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -4605,7 +4605,10 @@ tramp-handle-make-process
 	  (signal 'wrong-type-argument (list #'symbolp coding)))
 	(when (eq connection-type t)
 	  (setq connection-type 'pty))
-	(unless (memq connection-type '(nil pipe pty))
+	(unless (or (and (consp connection-type)
+			 (memq (car connection-type) '(nil pipe pty))
+			 (memq (cdr connection-type) '(nil pipe pty)))
+		    (memq connection-type '(nil pipe pty)))
 	  (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	(unless (or (null filter) (eq filter t) (functionp filter))
 	  (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/src/callproc.c b/src/callproc.c
index dd162f36a6..aec0a2f5a5 100644
--- a/src/callproc.c
+++ b/src/callproc.c
@@ -650,7 +650,7 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd,
 
   child_errno
     = emacs_spawn (&pid, filefd, fd_output, fd_error, new_argv, env,
-                   SSDATA (current_dir), NULL, &oldset);
+                   SSDATA (current_dir), NULL, false, false, &oldset);
   eassert ((child_errno == 0) == (0 < pid));
 
   if (pid > 0)
@@ -1412,14 +1412,15 @@ emacs_posix_spawn_init_attributes (posix_spawnattr_t *attributes,
 int
 emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
              char **argv, char **envp, const char *cwd,
-             const char *pty, const sigset_t *oldset)
+             const char *pty_name, bool pty_in, bool pty_out,
+             const sigset_t *oldset)
 {
 #if USABLE_POSIX_SPAWN
   /* Prefer the simpler `posix_spawn' if available.  `posix_spawn'
      doesn't yet support setting up pseudoterminals, so we fall back
      to `vfork' if we're supposed to use a pseudoterminal.  */
 
-  bool use_posix_spawn = pty == NULL;
+  bool use_posix_spawn = pty_name == NULL;
 
   posix_spawn_file_actions_t actions;
   posix_spawnattr_t attributes;
@@ -1473,7 +1474,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   /* vfork, and prevent local vars from being clobbered by the vfork.  */
   pid_t *volatile newpid_volatile = newpid;
   const char *volatile cwd_volatile = cwd;
-  const char *volatile pty_volatile = pty;
+  const char *volatile ptyname_volatile = pty_name;
+  bool volatile ptyin_volatile = pty_in;
+  bool volatile ptyout_volatile = pty_out;
   char **volatile argv_volatile = argv;
   int volatile stdin_volatile = std_in;
   int volatile stdout_volatile = std_out;
@@ -1495,7 +1498,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 
   newpid = newpid_volatile;
   cwd = cwd_volatile;
-  pty = pty_volatile;
+  pty_name = ptyname_volatile;
+  pty_in = ptyin_volatile;
+  pty_out = ptyout_volatile;
   argv = argv_volatile;
   std_in = stdin_volatile;
   std_out = stdout_volatile;
@@ -1506,13 +1511,12 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   if (pid == 0)
 #endif /* not WINDOWSNT */
     {
-      bool pty_flag = pty != NULL;
       /* Make the pty be the controlling terminal of the process.  */
 #ifdef HAVE_PTYS
       dissociate_controlling_tty ();
 
       /* Make the pty's terminal the controlling terminal.  */
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 #ifdef TIOCSCTTY
 	  /* We ignore the return value
@@ -1521,7 +1525,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 #endif
 	}
 #if defined (LDISC1)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  struct termios t;
 	  tcgetattr (std_in, &t);
@@ -1531,7 +1535,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 	}
 #else
 #if defined (NTTYDISC) && defined (TIOCSETD)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  /* Use new line discipline.  */
 	  int ldisc = NTTYDISC;
@@ -1548,18 +1552,21 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
      both TIOCSCTTY is defined.  */
 	/* Now close the pty (if we had it open) and reopen it.
 	   This makes the pty the controlling terminal of the subprocess.  */
-      if (pty_flag)
+      if (pty_name)
 	{
 
 	  /* I wonder if emacs_close (emacs_open (pty, ...))
 	     would work?  */
-	  if (std_in >= 0)
+	  if (pty_in && std_in >= 0)
 	    emacs_close (std_in);
-          std_out = std_in = emacs_open_noquit (pty, O_RDWR, 0);
-
+	  int ptyfd = emacs_open_noquit (pty_name, O_RDWR, 0);
+	  if (pty_in)
+	    std_in = ptyfd;
+	  if (pty_out)
+	    std_out = ptyfd;
 	  if (std_in < 0)
 	    {
-	      emacs_perror (pty);
+	      emacs_perror (pty_name);
 	      _exit (EXIT_CANCELED);
 	    }
 
@@ -1599,7 +1606,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
       /* Stop blocking SIGCHLD in the child.  */
       unblock_child_signal (oldset);
 
-      if (pty_flag)
+      if (pty_out)
 	child_setup_tty (std_out);
 #endif
 
diff --git a/src/lisp.h b/src/lisp.h
index 2afe135674..264228618d 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4941,7 +4941,8 @@ #define DAEMON_RUNNING (w32_daemon_event != INVALID_HANDLE_VALUE)
 #endif
 
 extern int emacs_spawn (pid_t *, int, int, int, char **, char **,
-                        const char *, const char *, const sigset_t *);
+                        const char *, const char *, bool, bool,
+                        const sigset_t *);
 extern char **make_environment_block (Lisp_Object) ATTRIBUTE_RETURNS_NONNULL;
 extern void init_callproc_1 (void);
 extern void init_callproc (void);
diff --git a/src/process.c b/src/process.c
index d6d51b26e1..da5e9cb182 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1316,6 +1316,19 @@ set_process_filter_masks (struct Lisp_Process *p)
     add_process_read_fd (p->infd);
 }
 
+static bool
+is_pty_from_symbol (Lisp_Object symbol)
+{
+  if (EQ (symbol, Qpty))
+    return true;
+  else if (EQ (symbol, Qpipe))
+    return false;
+  else if (NILP (symbol))
+    return !NILP (Vprocess_connection_type);
+  else
+    report_file_error ("Unknown connection type", symbol);
+}
+
 DEFUN ("set-process-filter", Fset_process_filter, Sset_process_filter,
        2, 2, 0,
        doc: /* Give PROCESS the filter function FILTER; nil means default.
@@ -1741,15 +1754,18 @@ DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
 :connection-type TYPE -- TYPE is control type of device used to
 communicate with subprocesses.  Values are `pipe' to use a pipe, `pty'
 to use a pty, or nil to use the default specified through
-`process-connection-type'.
+`process-connection-type'.  If TYPE is a cons (INPUT . OUTPUT), then
+INPUT will be used for standard input and OUTPUT for standard output
+(and standard error if `:stderr' is nil).
 
 :filter FILTER -- Install FILTER as the process filter.
 
 :sentinel SENTINEL -- Install SENTINEL as the process sentinel.
 
 :stderr STDERR -- STDERR is either a buffer or a pipe process attached
-to the standard error of subprocess.  Specifying this implies
-`:connection-type' is set to `pipe'.  If STDERR is nil, standard error
+to the standard error of subprocess.  When specifying this, the
+subprocess's standard error will always communicate via a pipe, no
+matter the value of `:connection-type'.  If STDERR is nil, standard error
 is mixed with standard output and sent to BUFFER or FILTER.  (Note
 that specifying :stderr will create a new, separate (but associated)
 process, with its own filter and sentinel.  See
@@ -1845,22 +1861,20 @@ DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
   CHECK_TYPE (NILP (tem), Qnull, tem);
 
   tem = plist_get (contact, QCconnection_type);
-  if (EQ (tem, Qpty))
-    XPROCESS (proc)->pty_flag = true;
-  else if (EQ (tem, Qpipe))
-    XPROCESS (proc)->pty_flag = false;
-  else if (NILP (tem))
-    XPROCESS (proc)->pty_flag = !NILP (Vprocess_connection_type);
+  if (CONSP (tem))
+    {
+      XPROCESS (proc)->pty_in = is_pty_from_symbol (XCAR (tem));
+      XPROCESS (proc)->pty_out = is_pty_from_symbol (XCDR (tem));
+    }
   else
-    report_file_error ("Unknown connection type", tem);
-
-  if (!NILP (stderrproc))
     {
-      pset_stderrproc (XPROCESS (proc), stderrproc);
-
-      XPROCESS (proc)->pty_flag = false;
+      XPROCESS (proc)->pty_in = XPROCESS (proc)->pty_out =
+	is_pty_from_symbol (tem);
     }
 
+  if (!NILP (stderrproc))
+    pset_stderrproc (XPROCESS (proc), stderrproc);
+
 #ifdef HAVE_GNUTLS
   /* AKA GNUTLS_INITSTAGE(proc).  */
   verify (GNUTLS_STAGE_EMPTY == 0);
@@ -2099,66 +2113,80 @@ verify (PROCESS_OPEN_FDS == EXEC_MONITOR_OUTPUT + 1);
 create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
 {
   struct Lisp_Process *p = XPROCESS (process);
-  int inchannel, outchannel;
+  int inchannel = -1, outchannel = -1;
   pid_t pid = -1;
   int vfork_errno;
   int forkin, forkout, forkerr = -1;
-  bool pty_flag = 0;
+  bool pty_in = false, pty_out = false;
   char pty_name[PTY_NAME_SIZE];
   Lisp_Object lisp_pty_name = Qnil;
+  int ptychannel = -1, pty_tty = -1;
   sigset_t oldset;
 
   /* Ensure that the SIGCHLD handler can notify
      `wait_reading_process_output'.  */
   child_signal_init ();
 
-  inchannel = outchannel = -1;
-
-  if (p->pty_flag)
-    outchannel = inchannel = allocate_pty (pty_name);
+  if (p->pty_in || p->pty_out)
+    ptychannel = allocate_pty (pty_name);
 
-  if (inchannel >= 0)
+  if (ptychannel >= 0)
     {
-      p->open_fd[READ_FROM_SUBPROCESS] = inchannel;
 #if ! defined (USG) || defined (USG_SUBTTY_WORKS)
       /* On most USG systems it does not work to open the pty's tty here,
 	 then close it and reopen it in the child.  */
       /* Don't let this terminal become our controlling terminal
 	 (in case we don't have one).  */
-      forkout = forkin = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
-      if (forkin < 0)
+      pty_tty = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
+      if (pty_tty < 0)
 	report_file_error ("Opening pty", Qnil);
-      p->open_fd[SUBPROCESS_STDIN] = forkin;
-#else
-      forkin = forkout = -1;
 #endif /* not USG, or USG_SUBTTY_WORKS */
-      pty_flag = 1;
+      pty_in = p->pty_in;
+      pty_out = p->pty_out;
       lisp_pty_name = build_string (pty_name);
     }
+
+  /* Set up stdin for the child process.  */
+  if (ptychannel >= 0 && p->pty_in)
+    {
+      p->open_fd[SUBPROCESS_STDIN] = forkin = pty_tty;
+      outchannel = ptychannel;
+    }
   else
     {
-      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0
-	  || emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0)
 	report_file_error ("Creating pipe", Qnil);
       forkin = p->open_fd[SUBPROCESS_STDIN];
       outchannel = p->open_fd[WRITE_TO_SUBPROCESS];
+    }
+
+  /* Set up stdout for the child process.  */
+  if (ptychannel >= 0 && p->pty_out)
+    {
+      forkout = pty_tty;
+      p->open_fd[READ_FROM_SUBPROCESS] = inchannel = ptychannel;
+    }
+  else
+    {
+      if (emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+	report_file_error ("Creating pipe", Qnil);
       inchannel = p->open_fd[READ_FROM_SUBPROCESS];
       forkout = p->open_fd[SUBPROCESS_STDOUT];
 
 #if defined(GNU_LINUX) && defined(F_SETPIPE_SZ)
       fcntl (inchannel, F_SETPIPE_SZ, read_process_output_max);
 #endif
+    }
 
-      if (!NILP (p->stderrproc))
-	{
-	  struct Lisp_Process *pp = XPROCESS (p->stderrproc);
+  if (!NILP (p->stderrproc))
+    {
+      struct Lisp_Process *pp = XPROCESS (p->stderrproc);
 
-	  forkerr = pp->open_fd[SUBPROCESS_STDOUT];
+      forkerr = pp->open_fd[SUBPROCESS_STDOUT];
 
-	  /* Close unnecessary file descriptors.  */
-	  close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
-	  close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
-	}
+      /* Close unnecessary file descriptors.  */
+      close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
+      close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
     }
 
   if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel)
@@ -2183,7 +2211,8 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
      we just reopen the device (see emacs_get_tty_pgrp) as this is
      more portable (see USG_SUBTTY_WORKS above).  */
 
-  p->pty_flag = pty_flag;
+  p->pty_in = pty_in;
+  p->pty_out = pty_out;
   pset_status (p, Qrun);
 
   if (!EQ (p->command, Qt)
@@ -2199,13 +2228,15 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
   block_input ();
   block_child_signal (&oldset);
 
-  pty_flag = p->pty_flag;
-  eassert (pty_flag == ! NILP (lisp_pty_name));
+  pty_in = p->pty_in;
+  pty_out = p->pty_out;
+  eassert ((pty_in || pty_out) == ! NILP (lisp_pty_name));
 
   vfork_errno
     = emacs_spawn (&pid, forkin, forkout, forkerr, new_argv, env,
                    SSDATA (current_dir),
-                   pty_flag ? SSDATA (lisp_pty_name) : NULL, &oldset);
+                   pty_in || pty_out ? SSDATA (lisp_pty_name) : NULL,
+                   pty_in, pty_out, &oldset);
 
   eassert ((vfork_errno == 0) == (0 < pid));
 
@@ -2263,7 +2294,7 @@ create_pty (Lisp_Object process)
 {
   struct Lisp_Process *p = XPROCESS (process);
   char pty_name[PTY_NAME_SIZE];
-  int pty_fd = !p->pty_flag ? -1 : allocate_pty (pty_name);
+  int pty_fd = !(p->pty_in || p->pty_out) ? -1 : allocate_pty (pty_name);
 
   if (pty_fd >= 0)
     {
@@ -2301,7 +2332,7 @@ create_pty (Lisp_Object process)
 	 we just reopen the device (see emacs_get_tty_pgrp) as this is
 	 more portable (see USG_SUBTTY_WORKS above).  */
 
-      p->pty_flag = 1;
+      p->pty_in = p->pty_out = true;
       pset_status (p, Qrun);
       setup_process_coding_systems (process);
 
@@ -2412,7 +2443,7 @@ DEFUN ("make-pipe-process", Fmake_pipe_process, Smake_pipe_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -3147,7 +3178,7 @@ DEFUN ("make-serial-process", Fmake_serial_process, Smake_serial_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -6798,7 +6829,7 @@ process_send_signal (Lisp_Object process, int signo, Lisp_Object current_group,
     error ("Process %s is not active",
 	   SDATA (p->name));
 
-  if (!p->pty_flag)
+  if (! p->pty_in)
     current_group = Qnil;
 
   /* If we are using pgrps, get a pgrp number and make it negative.  */
@@ -7167,7 +7198,7 @@ DEFUN ("process-send-eof", Fprocess_send_eof, Sprocess_send_eof, 0, 1, 0,
       send_process (proc, "", 0, Qnil);
     }
 
-  if (XPROCESS (proc)->pty_flag)
+  if (XPROCESS (proc)->pty_in)
     send_process (proc, "\004", 1, Qnil);
   else if (EQ (XPROCESS (proc)->type, Qserial))
     {
diff --git a/src/process.h b/src/process.h
index 392b661ce6..92baf0c4cb 100644
--- a/src/process.h
+++ b/src/process.h
@@ -156,8 +156,9 @@ #define EMACS_PROCESS_H
     /* True means kill silently if Emacs is exited.
        This is the inverse of the `query-on-exit' flag.  */
     bool_bf kill_without_query : 1;
-    /* True if communicating through a pty.  */
-    bool_bf pty_flag : 1;
+    /* True if communicating through a pty for input or output.  */
+    bool_bf pty_in : 1;
+    bool_bf pty_out : 1;
     /* Flag to set coding-system of the process buffer from the
        coding_system used to decode process output.  */
     bool_bf inherit_coding_system_flag : 1;
diff --git a/test/lisp/eshell/esh-proc-tests.el b/test/lisp/eshell/esh-proc-tests.el
index 7f461d1813..734bb91a6a 100644
--- a/test/lisp/eshell/esh-proc-tests.el
+++ b/test/lisp/eshell/esh-proc-tests.el
@@ -28,6 +28,15 @@
                            (file-name-directory (or load-file-name
                                                     default-directory))))
 
+(defvar esh-proc-test--detect-pty-cmd
+  (concat "sh -c '"
+          "if [ -t 0 ]; then echo stdin; fi; "
+          "if [ -t 1 ]; then echo stdout; fi; "
+          "if [ -t 2 ]; then echo stderr; fi"
+          "'"))
+
+;;; Tests:
+
 (ert-deftest esh-proc-test/sigpipe-exits-process ()
   "Test that a SIGPIPE is properly sent to a process if a pipe closes"
   (skip-unless (and (executable-find "sh")
@@ -44,6 +53,40 @@ esh-proc-test/sigpipe-exits-process
    (eshell-wait-for-subprocess t)
    (should (eq (process-list) nil))))
 
+(ert-deftest esh-proc-test/pipeline-connection-type/no-pipeline ()
+  "Test that all streams are PTYs when a command is not in a pipeline."
+  (skip-unless (executable-find "sh"))
+  (should (equal (eshell-test-command-result esh-proc-test--detect-pty-cmd)
+                 ;; PTYs aren't supported on MS-Windows.
+                 (unless (eq system-type 'windows-nt)
+                   "stdin\nstdout\nstderr\n"))))
+
+(ert-deftest esh-proc-test/pipeline-connection-type/first ()
+  "Test that only stdin is a PTY when a command starts a pipeline."
+  (skip-unless (and (executable-find "sh")
+                    (executable-find "cat")))
+  (should (equal (eshell-test-command-result
+                  (concat esh-proc-test--detect-pty-cmd " | cat"))
+                 (unless (eq system-type 'windows-nt)
+                   "stdin\n"))))
+
+(ert-deftest esh-proc-test/pipeline-connection-type/middle ()
+  "Test that all streams are pipes when a command is in the middle of a
+pipeline."
+  (skip-unless (and (executable-find "sh")
+                    (executable-find "cat")))
+  (should (equal (eshell-test-command-result
+                  (concat "echo | " esh-proc-test--detect-pty-cmd " | cat"))
+                 nil)))
+
+(ert-deftest esh-proc-test/pipeline-connection-type/last ()
+  "Test that only output streams are PTYs when a command ends a pipeline."
+  (skip-unless (executable-find "sh"))
+  (should (equal (eshell-test-command-result
+                  (concat "echo | " esh-proc-test--detect-pty-cmd))
+                 (unless (eq system-type 'windows-nt)
+                   "stdout\nstderr\n"))))
+
 (ert-deftest esh-proc-test/kill-pipeline ()
   "Test that killing a pipeline of processes only emits a single
 prompt.  See bug#54136."
diff --git a/test/src/process-tests.el b/test/src/process-tests.el
index f1ed7e18d5..41320672a0 100644
--- a/test/src/process-tests.el
+++ b/test/src/process-tests.el
@@ -38,10 +38,11 @@
 ;; Timeout in seconds; the test fails if the timeout is reached.
 (defvar process-test-sentinel-wait-timeout 2.0)
 
-;; Start a process that exits immediately.  Call WAIT-FUNCTION,
-;; possibly multiple times, to wait for the process to complete.
-(defun process-test-sentinel-wait-function-working-p (wait-function)
-  (let ((proc (start-process "test" nil "bash" "-c" "exit 20"))
+(defun process-test-wait-for-sentinel (proc exit-status &optional wait-function)
+  "Set a sentinel on PROC and wait for it to be called with EXIT-STATUS.
+Call WAIT-FUNCTION, possibly multiple times, to wait for the
+process to complete."
+  (let ((wait-function (or wait-function #'accept-process-output))
 	(sentinel-called nil)
 	(start-time (float-time)))
     (set-process-sentinel proc (lambda (_proc _msg)
@@ -50,21 +51,22 @@ process-test-sentinel-wait-function-working-p
 		    (> (- (float-time) start-time)
 		       process-test-sentinel-wait-timeout)))
       (funcall wait-function))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
-    sentinel-called))
+    (should sentinel-called)
+    (should (eq (process-status proc) 'exit))
+    (should (= (process-exit-status proc) exit-status))))
 
 (ert-deftest process-test-sentinel-accept-process-output ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (should (process-test-sentinel-wait-function-working-p
-           #'accept-process-output))))
+    (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
+      (should (process-test-wait-for-sentinel proc 20)))))
 
 (ert-deftest process-test-sentinel-sit-for ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (should
-   (process-test-sentinel-wait-function-working-p (lambda () (sit-for 0.01 t))))))
+    (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
+      (should (process-test-wait-for-sentinel
+               proc 20 (lambda () (sit-for 0.01 t)))))))
 
 (when (eq system-type 'windows-nt)
   (ert-deftest process-test-quoted-batfile ()
@@ -97,17 +99,8 @@ process-test-stderr-buffer
 						    "echo hello stderr! >&2; "
 						    "exit 20"))
 			     :buffer stdout-buffer
-			     :stderr stderr-buffer))
-	 (sentinel-called nil)
-	 (start-time (float-time)))
-    (set-process-sentinel proc (lambda (_proc _msg)
-				 (setq sentinel-called t)))
-    (while (not (or sentinel-called
-		    (> (- (float-time) start-time)
-		       process-test-sentinel-wait-timeout)))
-      (accept-process-output))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
+			     :stderr stderr-buffer)))
+    (process-test-wait-for-sentinel proc 20)
     (should (with-current-buffer stdout-buffer
 	      (goto-char (point-min))
 	      (looking-at "hello stdout!")))
@@ -118,8 +111,7 @@ process-test-stderr-buffer
 (ert-deftest process-test-stderr-filter ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (let* ((sentinel-called nil)
-	 (stderr-sentinel-called nil)
+  (let* ((stderr-sentinel-called nil)
 	 (stdout-output nil)
 	 (stderr-output nil)
 	 (stdout-buffer (generate-new-buffer "*stdout*"))
@@ -131,23 +123,14 @@ process-test-stderr-filter
 					    (concat "echo hello stdout!; "
 						    "echo hello stderr! >&2; "
 						    "exit 20"))
-			     :stderr stderr-proc))
-	 (start-time (float-time)))
+			     :stderr stderr-proc)))
     (set-process-filter proc (lambda (_proc input)
 			       (push input stdout-output)))
-    (set-process-sentinel proc (lambda (_proc _msg)
-				 (setq sentinel-called t)))
     (set-process-filter stderr-proc (lambda (_proc input)
 				      (push input stderr-output)))
     (set-process-sentinel stderr-proc (lambda (_proc _input)
 					(setq stderr-sentinel-called t)))
-    (while (not (or sentinel-called
-		    (> (- (float-time) start-time)
-		       process-test-sentinel-wait-timeout)))
-      (accept-process-output))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
-    (should sentinel-called)
+    (process-test-wait-for-sentinel proc 20)
     (should (equal 1 (with-current-buffer stdout-buffer
 		       (point-max))))
     (should (equal "hello stdout!\n"
@@ -289,6 +272,74 @@ make-process-w32-debug-spawn-error
                   (error :got-error))))
     (should have-called-debugger))))
 
+(defun make-process/test-connection-type (ttys &rest args)
+  "Make a process and check whether its standard streams match TTYS.
+This calls `make-process', passing ARGS to adjust how the process
+is created.  TTYS should be a list of 3 boolean values,
+indicating whether the subprocess's stdin, stdout, and stderr
+should be a TTY, respectively."
+  (declare (indent 1))
+  (let* (;; MS-Windows doesn't support communicating via pty.
+         (ttys (if (eq system-type 'windows-nt) '(nil nil nil) ttys))
+         (expected-output (concat (and (nth 0 ttys) "stdin\n")
+                                  (and (nth 1 ttys) "stdout\n")
+                                  (and (nth 2 ttys) "stderr\n")))
+         (stdout-buffer (generate-new-buffer "*stdout*"))
+         (proc (apply
+                #'make-process
+                :name "test"
+                :command (list "sh" "-c"
+                               (concat "if [ -t 0 ]; then echo stdin; fi; "
+                                       "if [ -t 1 ]; then echo stdout; fi; "
+                                       "if [ -t 2 ]; then echo stderr; fi"))
+                :buffer stdout-buffer
+                args)))
+    (process-test-wait-for-sentinel proc 0)
+    (should (equal (with-current-buffer stdout-buffer (buffer-string))
+                   expected-output))))
+
+(ert-deftest make-process/connection-type/pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t t t)
+    :connection-type 'pty))
+
+(ert-deftest make-process/connection-type/pty-2 ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t t t)
+    :connection-type '(pty . pty)))
+
+(ert-deftest make-process/connection-type/pipe ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil nil nil)
+    :connection-type 'pipe))
+
+(ert-deftest make-process/connection-type/pipe-2 ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil nil nil)
+    :connection-type '(pipe . pipe)))
+
+(ert-deftest make-process/connection-type/in-pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t nil nil)
+    :connection-type '(pty . pipe)))
+
+(ert-deftest make-process/connection-type/out-pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil t t)
+    :connection-type '(pipe . pty)))
+
+(ert-deftest make-process/connection-type/pty-with-stderr-buffer ()
+  (skip-unless (executable-find "sh"))
+  (let ((stderr-buffer (generate-new-buffer "*stderr*")))
+    (make-process/test-connection-type '(t t nil)
+      :connection-type 'pty :stderr stderr-buffer)))
+
+(ert-deftest make-process/connection-type/out-pty-with-stderr-buffer ()
+  (skip-unless (executable-find "sh"))
+  (let ((stderr-buffer (generate-new-buffer "*stderr*")))
+    (make-process/test-connection-type '(nil t nil)
+      :connection-type '(pipe . pty) :stderr stderr-buffer)))
+
 (ert-deftest make-process/file-handler/found ()
   "Check that the `:file-handler’ argument of `make-process’
 works as expected if a file name handler is found."
-- 
2.25.1


[-- Attachment #3: 0002-Add-STREAM-argument-to-process-tty-name.patch --]
[-- Type: text/plain, Size: 7521 bytes --]

From 186be9c581e052f365dc219a5e2e0245f5434348 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Tue, 19 Jul 2022 21:36:54 -0700
Subject: [PATCH 2/2] Add STREAM argument to 'process-tty-name'

* src/process.c (process-tty-name): Add STREAM argument.

* lisp/eshell/esh-io.el (eshell-close-target): Only call
'process-send-eof' once if the process's stdin is a pipe.

* test/src/process-tests.el (make-process/test-connection-type): Check
behavior of 'process-tty-name'.

* doc/lispref/processes.texi (Process Information): Document the new
argument.

* etc/NEWS: Announce this change.
---
 doc/lispref/processes.texi | 17 +++++++++++------
 etc/NEWS                   |  5 ++++-
 lisp/eshell/esh-io.el      | 27 +++++++++++++++------------
 src/process.c              | 23 +++++++++++++++++++----
 test/src/process-tests.el  |  3 +++
 5 files changed, 52 insertions(+), 23 deletions(-)

diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index a7e08054c7..bbca48e6c5 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -1243,15 +1243,20 @@ Process Information
 whether the connection was closed normally or abnormally.
 @end defun
 
-@defun process-tty-name process
+@defun process-tty-name process &optional stream
 This function returns the terminal name that @var{process} is using for
 its communication with Emacs---or @code{nil} if it is using pipes
 instead of a pty (see @code{process-connection-type} in
-@ref{Asynchronous Processes}).  If @var{process} represents a program
-running on a remote host, the terminal name used by that program on
-the remote host is provided as process property @code{remote-tty}.  If
-@var{process} represents a network, serial, or pipe connection, the
-value is @code{nil}.
+@ref{Asynchronous Processes}).  If @var{stream} is one of @code{stdin},
+@code{stdout}, or @code{stderr}, this function returns the terminal
+name (or @code{nil}, as above) that @var{process} uses for that stream
+specifically.  You can use this to determine whether a particular
+stream uses a pipe or a pty.
+
+If @var{process} represents a program running on a remote host, the
+terminal name used by that program on the remote host is provided as
+process property @code{remote-tty}.  If @var{process} represents a
+network, serial, or pipe connection, the value is @code{nil}.
 @end defun
 
 @defun process-coding-system process
diff --git a/etc/NEWS b/etc/NEWS
index dc79f0826a..23777d349e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3198,7 +3198,10 @@ invocation.  Such shells are POSIX conformant by default.
 ** 'make-process' can set connection type independently for input and output.
 When calling 'make-process', communication via pty can be enabled
 selectively for just input or output by passing a cons cell for
-':connection-type', e.g. '(pipe . pty)'.
+':connection-type', e.g. '(pipe . pty)'.  When examining a process
+later, you can determine whether a particular stream for a process
+uses a pty by passing one of 'stdin', 'stdout', or 'stderr' as the
+second argument to 'process-tty-name'.
 
 +++
 ** 'signal-process' now consults the list 'signal-process-functions'.
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index c035890ddf..68e52a2c9c 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -276,18 +276,21 @@ eshell-close-target
    ;; 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)
-    ;; According to POSIX.1-2017, section 11.1.9, 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 shouldn't break
-    ;; anything, we'll just send the maximum we'd ever need.  See
-    ;; bug#56025 for further details.
-    (let ((i 0))
-      (while (and (<= (cl-incf i) 3)
+    ;; 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))))
 
diff --git a/src/process.c b/src/process.c
index da5e9cb182..adc508156f 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1243,14 +1243,29 @@ DEFUN ("process-command", Fprocess_command, Sprocess_command, 1, 1, 0,
   return XPROCESS (process)->command;
 }
 
-DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 1, 0,
+DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 2, 0,
        doc: /* Return the name of the terminal PROCESS uses, or nil if none.
 This is the terminal that the process itself reads and writes on,
-not the name of the pty that Emacs uses to talk with that terminal.  */)
-  (register Lisp_Object process)
+not the name of the pty that Emacs uses to talk with that terminal.
+
+If STREAM is one of `stdin', `stdout', or `stderr', return the name of
+the terminal PROCESS uses for that stream.  This can be used to detect
+whether a particular stream is connected via a pipe or a pty.  */)
+  (register Lisp_Object process, Lisp_Object stream)
 {
   CHECK_PROCESS (process);
-  return XPROCESS (process)->tty_name;
+  register struct Lisp_Process *p = XPROCESS (process);
+
+  if (NILP (stream))
+    return p->tty_name;
+  else if (EQ (stream, Qstdin))
+    return p->pty_in ? p->tty_name : Qnil;
+  else if (EQ (stream, Qstdout))
+    return p->pty_out ? p->tty_name : Qnil;
+  else if (EQ (stream, Qstderr))
+    return p->pty_out && NILP (p->stderrproc) ? p->tty_name : Qnil;
+  else
+    signal_error ("Unknown stream", stream);
 }
 
 static void
diff --git a/test/src/process-tests.el b/test/src/process-tests.el
index 41320672a0..6ba5930ee6 100644
--- a/test/src/process-tests.el
+++ b/test/src/process-tests.el
@@ -294,6 +294,9 @@ make-process/test-connection-type
                                        "if [ -t 2 ]; then echo stderr; fi"))
                 :buffer stdout-buffer
                 args)))
+    (should (eq (and (process-tty-name proc 'stdin) t) (nth 0 ttys)))
+    (should (eq (and (process-tty-name proc 'stdout) t) (nth 1 ttys)))
+    (should (eq (and (process-tty-name proc 'stderr) t) (nth 2 ttys)))
     (process-test-wait-for-sentinel proc 0)
     (should (equal (with-current-buffer stdout-buffer (buffer-string))
                    expected-output))))
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v4] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-24  5:29                                                                     ` bug#56025: [PATCH v4] " Jim Porter
@ 2022-07-24  9:08                                                                       ` Lars Ingebrigtsen
  2022-07-24  9:48                                                                         ` Eli Zaretskii
  2022-07-24 21:04                                                                         ` Ken Brown
  2022-07-24  9:47                                                                       ` Eli Zaretskii
  1 sibling, 2 replies; 64+ messages in thread
From: Lars Ingebrigtsen @ 2022-07-24  9:08 UTC (permalink / raw)
  To: Jim Porter; +Cc: Eli Zaretskii, 56025, Sean Whitton, Ken Brown

Jim Porter <jporterbugs@gmail.com> writes:

> Oops. I forgot to add some `(skip-unless ...)' forms for these tests,
> so... here they are. Hopefully this will be the last message from me
> for a bit. :C

:-)

Since this (mainly) affects Cygwin builds, could someone who uses
Windows give the patch a look-over and apply it?






^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v4] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-24  5:29                                                                     ` bug#56025: [PATCH v4] " Jim Porter
  2022-07-24  9:08                                                                       ` Lars Ingebrigtsen
@ 2022-07-24  9:47                                                                       ` Eli Zaretskii
  2022-07-24 17:36                                                                         ` bug#56025: [PATCH v5] " Jim Porter
  1 sibling, 1 reply; 64+ messages in thread
From: Eli Zaretskii @ 2022-07-24  9:47 UTC (permalink / raw)
  To: Jim Porter; +Cc: larsi, 56025, spwhitton, kbrown

> From: Jim Porter <jporterbugs@gmail.com>
> Cc: larsi@gnus.org, 56025@debbugs.gnu.org
> Date: Sat, 23 Jul 2022 22:29:28 -0700
> 
> -@defun process-tty-name process
> +@defun process-tty-name process &optional stream
>  This function returns the terminal name that @var{process} is using for
>  its communication with Emacs---or @code{nil} if it is using pipes
>  instead of a pty (see @code{process-connection-type} in
> -@ref{Asynchronous Processes}).  If @var{process} represents a program
> -running on a remote host, the terminal name used by that program on
> -the remote host is provided as process property @code{remote-tty}.  If
> -@var{process} represents a network, serial, or pipe connection, the
> -value is @code{nil}.
> +@ref{Asynchronous Processes}).  If @var{stream} is one of @code{stdin},
> +@code{stdout}, or @code{stderr}, this function returns the terminal
> +name (or @code{nil}, as above) that @var{process} uses for that stream
> +specifically.  You can use this to determine whether a particular
> +stream uses a pipe or a pty.

This text doesn't tell what happens if STREAM is nil or omitted.

> +If @var{process} represents a program running on a remote host, the
> +terminal name used by that program on the remote host is provided as
> +process property @code{remote-tty}.  If @var{process} represents a
> +network, serial, or pipe connection, the value is @code{nil}.

If the previous paragraph is only for local subprocesses, the text
there should say so.

>  @end defun
>  
>  @defun process-coding-system process
> diff --git a/etc/NEWS b/etc/NEWS
> index dc79f0826a..23777d349e 100644
> --- a/etc/NEWS
> +++ b/etc/NEWS
> @@ -3198,7 +3198,10 @@ invocation.  Such shells are POSIX conformant by default.
>  ** 'make-process' can set connection type independently for input and output.
>  When calling 'make-process', communication via pty can be enabled
>  selectively for just input or output by passing a cons cell for
> -':connection-type', e.g. '(pipe . pty)'.
> +':connection-type', e.g. '(pipe . pty)'.  When examining a process
> +later, you can determine whether a particular stream for a process
> +uses a pty by passing one of 'stdin', 'stdout', or 'stderr' as the
> +second argument to 'process-tty-name'.
>  
>  +++
>  ** 'signal-process' now consults the list 'signal-process-functions'.
> diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
> index c035890ddf..68e52a2c9c 100644
> --- a/lisp/eshell/esh-io.el
> +++ b/lisp/eshell/esh-io.el
> @@ -276,18 +276,21 @@ eshell-close-target
>     ;; 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)
> -    ;; According to POSIX.1-2017, section 11.1.9, 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 shouldn't break
> -    ;; anything, we'll just send the maximum we'd ever need.  See
> -    ;; bug#56025 for further details.
> -    (let ((i 0))
> -      (while (and (<= (cl-incf i) 3)
> +    ;; 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))))
>  
> diff --git a/src/process.c b/src/process.c
> index da5e9cb182..adc508156f 100644
> --- a/src/process.c
> +++ b/src/process.c
> @@ -1243,14 +1243,29 @@ DEFUN ("process-command", Fprocess_command, Sprocess_command, 1, 1, 0,
>    return XPROCESS (process)->command;
>  }
>  
> -DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 1, 0,
> +DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 2, 0,
>         doc: /* Return the name of the terminal PROCESS uses, or nil if none.
>  This is the terminal that the process itself reads and writes on,
> -not the name of the pty that Emacs uses to talk with that terminal.  */)
> -  (register Lisp_Object process)
> +not the name of the pty that Emacs uses to talk with that terminal.
> +
> +If STREAM is one of `stdin', `stdout', or `stderr', return the name of
> +the terminal PROCESS uses for that stream.  This can be used to detect
> +whether a particular stream is connected via a pipe or a pty.  */)
> +  (register Lisp_Object process, Lisp_Object stream)

Same here: the call without the optional argument returns something
whose relation to the value when STREAM is non-nil is not clear from
the doc string.

Thanks.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v4] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-24  9:08                                                                       ` Lars Ingebrigtsen
@ 2022-07-24  9:48                                                                         ` Eli Zaretskii
  2022-07-24 21:04                                                                         ` Ken Brown
  1 sibling, 0 replies; 64+ messages in thread
From: Eli Zaretskii @ 2022-07-24  9:48 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: jporterbugs, 56025, spwhitton, kbrown

> From: Lars Ingebrigtsen <larsi@gnus.org>
> Cc: Ken Brown <kbrown@cornell.edu>,  Sean Whitton
>  <spwhitton@email.arizona.edu>,  Eli Zaretskii <eliz@gnu.org>,
>   56025@debbugs.gnu.org
> Date: Sun, 24 Jul 2022 11:08:25 +0200
> 
> Jim Porter <jporterbugs@gmail.com> writes:
> 
> > Oops. I forgot to add some `(skip-unless ...)' forms for these tests,
> > so... here they are. Hopefully this will be the last message from me
> > for a bit. :C
> 
> :-)
> 
> Since this (mainly) affects Cygwin builds, could someone who uses
> Windows give the patch a look-over and apply it?

I did the review and tested on native MS-Windows, but I think we
should wait for Ken to try this on Cygwin.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v5] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-24  9:47                                                                       ` Eli Zaretskii
@ 2022-07-24 17:36                                                                         ` Jim Porter
  2022-07-24 20:30                                                                           ` Ken Brown
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-07-24 17:36 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, 56025, spwhitton, kbrown

[-- Attachment #1: Type: text/plain, Size: 2656 bytes --]

On 7/24/2022 2:47 AM, Eli Zaretskii wrote:
>> From: Jim Porter <jporterbugs@gmail.com>
>> Cc: larsi@gnus.org, 56025@debbugs.gnu.org
>> Date: Sat, 23 Jul 2022 22:29:28 -0700
>>
>> -@defun process-tty-name process
>> +@defun process-tty-name process &optional stream
>>   This function returns the terminal name that @var{process} is using for
>>   its communication with Emacs---or @code{nil} if it is using pipes
>>   instead of a pty (see @code{process-connection-type} in
>> -@ref{Asynchronous Processes}).  If @var{process} represents a program
>> -running on a remote host, the terminal name used by that program on
>> -the remote host is provided as process property @code{remote-tty}.  If
>> -@var{process} represents a network, serial, or pipe connection, the
>> -value is @code{nil}.
>> +@ref{Asynchronous Processes}).  If @var{stream} is one of @code{stdin},
>> +@code{stdout}, or @code{stderr}, this function returns the terminal
>> +name (or @code{nil}, as above) that @var{process} uses for that stream
>> +specifically.  You can use this to determine whether a particular
>> +stream uses a pipe or a pty.
> 
> This text doesn't tell what happens if STREAM is nil or omitted.

Ok, I expanded this to clarify things. (Same for the docstring.) 
Hopefully that provides enough detail. I tried to explain the behavior 
without going overly in-depth and explaining all the implementation 
details of how PTYs get set up. Let me know if it needs any further tweaks.

>> +If @var{process} represents a program running on a remote host, the
>> +terminal name used by that program on the remote host is provided as
>> +process property @code{remote-tty}.  If @var{process} represents a
>> +network, serial, or pipe connection, the value is @code{nil}.
> 
> If the previous paragraph is only for local subprocesses, the text
> there should say so.

I've added an explanation of what (I think) this means for remote 
processes: `process-tty-name' returns the name of the local TTY (so, the 
TTY used by ssh, for example), whereas the `remote-tty' property returns 
the name of, well... the remote TTY. I'm pretty sure that's what the 
behavior is at least, based on my reading of the code.

On 7/24/2022 2:48 AM, Eli Zaretskii wrote:
 >> From: Lars Ingebrigtsen <larsi@gnus.org>
 >>
 >> Since this (mainly) affects Cygwin builds, could someone who uses
 >> Windows give the patch a look-over and apply it?
 >
 > I did the review and tested on native MS-Windows, but I think we
 > should wait for Ken to try this on Cygwin.

I tested the v4 patch on Cygwin (and GNU/Linux) and all the new tests I 
added passed. Ken also tested patch v2 and things worked.

[-- Attachment #2: 0001-Allow-creating-processes-where-only-one-of-stdin-or-.patch --]
[-- Type: text/plain, Size: 38705 bytes --]

From ba745998d7f26262a721413b7dc49549e673387c Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 17 Jul 2022 20:25:00 -0700
Subject: [PATCH 1/2] Allow creating processes where only one of stdin or
 stdout is a PTY

* src/lisp.h (emacs_spawn):
* src/callproc.c (emacs_spawn): Add PTY_IN and PTY_OUT arguments to
specify which streams should be set up as a PTY.
(call_process): Adjust call to 'emacs_spawn'.

* src/process.h (Lisp_Process): Replace 'pty_flag' with 'pty_in' and
'pty_out'.

* src/process.c (is_pty_from_symbol): New function.
(make-process): Allow :connection-type to be a cons cell, and allow
using a stderr process with a PTY for stdin/stdout.
(create_process): Handle creating a process where only one of stdin or
stdout is a PTY.

* lisp/eshell/esh-proc.el (eshell-needs-pipe, eshell-needs-pipe-p):
Remove.
(eshell-gather-process-output): Use 'make-process' and set
':connection-type' as needed by the value of 'eshell-in-pipeline-p'.

* lisp/net/tramp.el (tramp-handle-make-process):
* lisp/net/tramp-adb.el (tramp-adb-handle-make-process):
* lisp/net/tramp-sh.el (tramp-sh-handle-make-process): Don't signal an
error when ':connection-type' is a cons cell.

* test/src/process-tests.el
(process-test-sentinel-wait-function-working-p): Allow passing PROC
in, and rework into...
(process-test-wait-for-sentinel): ... this.
(process-test-sentinel-accept-process-output)
(process-test-sentinel-sit-for, process-test-quoted-batfile)
(process-test-stderr-filter): Use 'process-test-wait-for-sentinel'.
(make/process/test-connection-type): New function.
(make-process/connection-type/pty, make-process/connection-type/pty-2)
(make-process/connection-type/pipe)
(make-process/connection-type/pipe-2)
(make-process/connection-type/in-pty)
(make-process/connection-type/out-pty)
(make-process/connection-type/pty-with-stderr-buffer)
(make-process/connection-type/out-pty-with-stderr-buffer): New tests.

* test/lisp/eshell/esh-proc-tests.el (esh-proc-test--detect-pty-cmd):
New variable.
(esh-proc-test/pipeline-connection-type/no-pipeline)
(esh-proc-test/pipeline-connection-type/first)
(esh-proc-test/pipeline-connection-type/middle)
(esh-proc-test/pipeline-connection-type/last): New tests.

* doc/lispref/processes.texi (Asynchronous Processes): Document new
':connection-type' behavior.
(Output from Processes): Remove caveat about ':stderr' forcing
'make-process' to use pipes.

* etc/NEWS: Announce this change.
---
 doc/lispref/processes.texi         |  28 +++----
 etc/NEWS                           |  12 +++
 lisp/eshell/esh-proc.el            |  55 ++++--------
 lisp/net/tramp-adb.el              |   5 +-
 lisp/net/tramp-sh.el               |   5 +-
 lisp/net/tramp.el                  |   5 +-
 src/callproc.c                     |  37 +++++----
 src/lisp.h                         |   3 +-
 src/process.c                      | 129 ++++++++++++++++++-----------
 src/process.h                      |   5 +-
 test/lisp/eshell/esh-proc-tests.el |  43 ++++++++++
 test/src/process-tests.el          | 121 +++++++++++++++++++--------
 12 files changed, 288 insertions(+), 160 deletions(-)

diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index 80c371e1c6..a7e08054c7 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -705,12 +705,13 @@ Asynchronous Processes
 Initialize the type of device used to communicate with the subprocess.
 Possible values are @code{pty} to use a pty, @code{pipe} to use a
 pipe, or @code{nil} to use the default derived from the value of the
-@code{process-connection-type} variable.  This parameter and the value
-of @code{process-connection-type} are ignored if a non-@code{nil}
-value is specified for the @code{:stderr} parameter; in that case, the
-type will always be @code{pipe}.  On systems where ptys are not
-available (MS-Windows), this parameter is likewise ignored, and pipes
-are used unconditionally.
+@code{process-connection-type} variable.  If @var{type} is a cons cell
+@w{@code{(@var{input} . @var{output})}}, then @var{input} will be used
+for standard input and @var{output} for standard output (and standard
+error if @code{:stderr} is @code{nil}).
+
+On systems where ptys are not available (MS-Windows), this parameter
+is ignored, and pipes are used unconditionally.
 
 @item :noquery @var{query-flag}
 Initialize the process query flag to @var{query-flag}.
@@ -1530,20 +1531,11 @@ Output from Processes
 default filter discards the output.
 
   If the subprocess writes to its standard error stream, by default
-the error output is also passed to the process filter function.  If
-Emacs uses a pseudo-TTY (pty) for communication with the subprocess,
-then it is impossible to separate the standard output and standard
-error streams of the subprocess, because a pseudo-TTY has only one
-output channel.  In that case, if you want to keep the output to those
-streams separate, you should redirect one of them to a file---for
-example, by using an appropriate shell command via
-@code{start-process-shell-command} or a similar function.
-
-  Alternatively, you could use the @code{:stderr} parameter with a
+the error output is also passed to the process filter function.
+Alternatively, you could use the @code{:stderr} parameter with a
 non-@code{nil} value in a call to @code{make-process}
 (@pxref{Asynchronous Processes, make-process}) to make the destination
-of the error output separate from the standard output; in that case,
-Emacs will use pipes for communicating with the subprocess.
+of the error output separate from the standard output.
 
   When a subprocess terminates, Emacs reads any pending output,
 then stops reading output from that subprocess.  Therefore, if the
diff --git a/etc/NEWS b/etc/NEWS
index 6d4fce1237..dc79f0826a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2229,6 +2229,12 @@ they will still be escaped, so the '.foo' symbol is still printed as
 and remapping parent of basic faces does not work reliably.
 Instead of remapping 'mode-line', you have to remap 'mode-line-active'.
 
++++
+** 'make-process' has been extended to support ptys when ':stderr' is set.
+Previously, setting ':stderr' to a non-nil value would force the
+process's connection to use pipes.  Now, Emacs will use a pty for
+stdin and stdout if requested no matter the value of ':stderr'.
+
 ---
 ** User option 'mail-source-ignore-errors' is now obsolete.
 The whole mechanism for prompting users to continue in case of
@@ -3188,6 +3194,12 @@ translation.
 This is useful when quoting shell arguments for a remote shell
 invocation.  Such shells are POSIX conformant by default.
 
++++
+** 'make-process' can set connection type independently for input and output.
+When calling 'make-process', communication via pty can be enabled
+selectively for just input or output by passing a cons cell for
+':connection-type', e.g. '(pipe . pty)'.
+
 +++
 ** 'signal-process' now consults the list 'signal-process-functions'.
 This is to determine which function has to be called in order to
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 70426ccaf2..99b43661f2 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -250,30 +250,6 @@ eshell-last-sync-output-start
   "A marker that tracks the beginning of output of the last subprocess.
 Used only on systems which do not support async subprocesses.")
 
-(defvar eshell-needs-pipe
-  '("bc"
-    ;; xclip.el (in GNU ELPA) calls all of these with
-    ;; `process-connection-type' set to nil.
-    "pbpaste" "putclip" "xclip" "xsel" "wl-copy")
-  "List of commands which need `process-connection-type' to be nil.
-Currently only affects commands in pipelines, and not those at
-the front.  If an element contains a directory part it must match
-the full name of a command, otherwise just the nondirectory part must match.")
-
-(defun eshell-needs-pipe-p (command)
-  "Return non-nil if COMMAND needs `process-connection-type' to be nil.
-See `eshell-needs-pipe'."
-  (and (bound-and-true-p eshell-in-pipeline-p)
-       (not (eq eshell-in-pipeline-p 'first))
-       ;; FIXME should this return non-nil for anything that is
-       ;; neither 'first nor 'last?  See bug#1388 discussion.
-       (catch 'found
-	 (dolist (exe eshell-needs-pipe)
-	   (if (string-equal exe (if (string-search "/" exe)
-				     command
-				   (file-name-nondirectory command)))
-	       (throw 'found t))))))
-
 (defun eshell-gather-process-output (command args)
   "Gather the output from COMMAND + ARGS."
   (require 'esh-var)
@@ -290,31 +266,36 @@ eshell-gather-process-output
     (cond
      ((fboundp 'make-process)
       (setq proc
-	    (let ((process-connection-type
-		   (unless (eshell-needs-pipe-p command)
-		     process-connection-type))
-		  (command (file-local-name (expand-file-name command))))
-	      (apply #'start-file-process
-		     (file-name-nondirectory command) nil command args)))
+            (let ((command (file-local-name (expand-file-name command)))
+                  (conn-type (pcase (bound-and-true-p eshell-in-pipeline-p)
+                               ('first '(nil . pipe))
+                               ('last  '(pipe . nil))
+                               ('t     'pipe)
+                               ('nil   nil))))
+              (make-process
+               :name (file-name-nondirectory command)
+               :buffer (current-buffer)
+               :command (cons command args)
+               :filter (if (eshell-interactive-output-p)
+                           #'eshell-output-filter
+                         #'eshell-insertion-filter)
+               :sentinel #'eshell-sentinel
+               :connection-type conn-type
+               :file-handler t)))
       (eshell-record-process-object proc)
-      (set-process-buffer proc (current-buffer))
-      (set-process-filter proc (if (eshell-interactive-output-p)
-	                           #'eshell-output-filter
-                                 #'eshell-insertion-filter))
-      (set-process-sentinel proc #'eshell-sentinel)
       (run-hook-with-args 'eshell-exec-hook proc)
       (when (fboundp 'process-coding-system)
 	(let ((coding-systems (process-coding-system proc)))
 	  (setq decoding (car coding-systems)
 		encoding (cdr coding-systems)))
-	;; If start-process decided to use some coding system for
+	;; If `make-process' decided to use some coding system for
 	;; decoding data sent from the process and the coding system
 	;; doesn't specify EOL conversion, we had better convert CRLF
 	;; to LF.
 	(if (vectorp (coding-system-eol-type decoding))
 	    (setq decoding (coding-system-change-eol-conversion decoding 'dos)
 		  changed t))
-	;; Even if start-process left the coding system for encoding
+	;; Even if `make-process' left the coding system for encoding
 	;; data sent from the process undecided, we had better use the
 	;; same one as what we use for decoding.  But, we should
 	;; suppress EOL conversion.
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index de55856830..451128ab20 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -904,7 +904,10 @@ tramp-adb-handle-make-process
 	    (signal 'wrong-type-argument (list #'symbolp coding)))
 	  (when (eq connection-type t)
 	    (setq connection-type 'pty))
-	  (unless (memq connection-type '(nil pipe pty))
+	  (unless (or (and (consp connection-type)
+			   (memq (car connection-type) '(nil pipe pty))
+			   (memq (cdr connection-type) '(nil pipe pty)))
+		      (memq connection-type '(nil pipe pty)))
 	    (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	  (unless (or (null filter) (eq filter t) (functionp filter))
 	    (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index e772af9e0a..8c48c3fc1e 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -2851,7 +2851,10 @@ tramp-sh-handle-make-process
 	    (signal 'wrong-type-argument (list #'symbolp coding)))
 	  (when (eq connection-type t)
 	    (setq connection-type 'pty))
-	  (unless (memq connection-type '(nil pipe pty))
+	  (unless (or (and (consp connection-type)
+			   (memq (car connection-type) '(nil pipe pty))
+			   (memq (cdr connection-type) '(nil pipe pty)))
+		      (memq connection-type '(nil pipe pty)))
 	    (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	  (unless (or (null filter) (eq filter t) (functionp filter))
 	    (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index b11fd293cc..8b654944fe 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -4605,7 +4605,10 @@ tramp-handle-make-process
 	  (signal 'wrong-type-argument (list #'symbolp coding)))
 	(when (eq connection-type t)
 	  (setq connection-type 'pty))
-	(unless (memq connection-type '(nil pipe pty))
+	(unless (or (and (consp connection-type)
+			 (memq (car connection-type) '(nil pipe pty))
+			 (memq (cdr connection-type) '(nil pipe pty)))
+		    (memq connection-type '(nil pipe pty)))
 	  (signal 'wrong-type-argument (list #'symbolp connection-type)))
 	(unless (or (null filter) (eq filter t) (functionp filter))
 	  (signal 'wrong-type-argument (list #'functionp filter)))
diff --git a/src/callproc.c b/src/callproc.c
index dd162f36a6..aec0a2f5a5 100644
--- a/src/callproc.c
+++ b/src/callproc.c
@@ -650,7 +650,7 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd,
 
   child_errno
     = emacs_spawn (&pid, filefd, fd_output, fd_error, new_argv, env,
-                   SSDATA (current_dir), NULL, &oldset);
+                   SSDATA (current_dir), NULL, false, false, &oldset);
   eassert ((child_errno == 0) == (0 < pid));
 
   if (pid > 0)
@@ -1412,14 +1412,15 @@ emacs_posix_spawn_init_attributes (posix_spawnattr_t *attributes,
 int
 emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
              char **argv, char **envp, const char *cwd,
-             const char *pty, const sigset_t *oldset)
+             const char *pty_name, bool pty_in, bool pty_out,
+             const sigset_t *oldset)
 {
 #if USABLE_POSIX_SPAWN
   /* Prefer the simpler `posix_spawn' if available.  `posix_spawn'
      doesn't yet support setting up pseudoterminals, so we fall back
      to `vfork' if we're supposed to use a pseudoterminal.  */
 
-  bool use_posix_spawn = pty == NULL;
+  bool use_posix_spawn = pty_name == NULL;
 
   posix_spawn_file_actions_t actions;
   posix_spawnattr_t attributes;
@@ -1473,7 +1474,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   /* vfork, and prevent local vars from being clobbered by the vfork.  */
   pid_t *volatile newpid_volatile = newpid;
   const char *volatile cwd_volatile = cwd;
-  const char *volatile pty_volatile = pty;
+  const char *volatile ptyname_volatile = pty_name;
+  bool volatile ptyin_volatile = pty_in;
+  bool volatile ptyout_volatile = pty_out;
   char **volatile argv_volatile = argv;
   int volatile stdin_volatile = std_in;
   int volatile stdout_volatile = std_out;
@@ -1495,7 +1498,9 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 
   newpid = newpid_volatile;
   cwd = cwd_volatile;
-  pty = pty_volatile;
+  pty_name = ptyname_volatile;
+  pty_in = ptyin_volatile;
+  pty_out = ptyout_volatile;
   argv = argv_volatile;
   std_in = stdin_volatile;
   std_out = stdout_volatile;
@@ -1506,13 +1511,12 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
   if (pid == 0)
 #endif /* not WINDOWSNT */
     {
-      bool pty_flag = pty != NULL;
       /* Make the pty be the controlling terminal of the process.  */
 #ifdef HAVE_PTYS
       dissociate_controlling_tty ();
 
       /* Make the pty's terminal the controlling terminal.  */
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 #ifdef TIOCSCTTY
 	  /* We ignore the return value
@@ -1521,7 +1525,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 #endif
 	}
 #if defined (LDISC1)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  struct termios t;
 	  tcgetattr (std_in, &t);
@@ -1531,7 +1535,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
 	}
 #else
 #if defined (NTTYDISC) && defined (TIOCSETD)
-      if (pty_flag && std_in >= 0)
+      if (pty_in && std_in >= 0)
 	{
 	  /* Use new line discipline.  */
 	  int ldisc = NTTYDISC;
@@ -1548,18 +1552,21 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
      both TIOCSCTTY is defined.  */
 	/* Now close the pty (if we had it open) and reopen it.
 	   This makes the pty the controlling terminal of the subprocess.  */
-      if (pty_flag)
+      if (pty_name)
 	{
 
 	  /* I wonder if emacs_close (emacs_open (pty, ...))
 	     would work?  */
-	  if (std_in >= 0)
+	  if (pty_in && std_in >= 0)
 	    emacs_close (std_in);
-          std_out = std_in = emacs_open_noquit (pty, O_RDWR, 0);
-
+	  int ptyfd = emacs_open_noquit (pty_name, O_RDWR, 0);
+	  if (pty_in)
+	    std_in = ptyfd;
+	  if (pty_out)
+	    std_out = ptyfd;
 	  if (std_in < 0)
 	    {
-	      emacs_perror (pty);
+	      emacs_perror (pty_name);
 	      _exit (EXIT_CANCELED);
 	    }
 
@@ -1599,7 +1606,7 @@ emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err,
       /* Stop blocking SIGCHLD in the child.  */
       unblock_child_signal (oldset);
 
-      if (pty_flag)
+      if (pty_out)
 	child_setup_tty (std_out);
 #endif
 
diff --git a/src/lisp.h b/src/lisp.h
index 2afe135674..264228618d 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4941,7 +4941,8 @@ #define DAEMON_RUNNING (w32_daemon_event != INVALID_HANDLE_VALUE)
 #endif
 
 extern int emacs_spawn (pid_t *, int, int, int, char **, char **,
-                        const char *, const char *, const sigset_t *);
+                        const char *, const char *, bool, bool,
+                        const sigset_t *);
 extern char **make_environment_block (Lisp_Object) ATTRIBUTE_RETURNS_NONNULL;
 extern void init_callproc_1 (void);
 extern void init_callproc (void);
diff --git a/src/process.c b/src/process.c
index d6d51b26e1..da5e9cb182 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1316,6 +1316,19 @@ set_process_filter_masks (struct Lisp_Process *p)
     add_process_read_fd (p->infd);
 }
 
+static bool
+is_pty_from_symbol (Lisp_Object symbol)
+{
+  if (EQ (symbol, Qpty))
+    return true;
+  else if (EQ (symbol, Qpipe))
+    return false;
+  else if (NILP (symbol))
+    return !NILP (Vprocess_connection_type);
+  else
+    report_file_error ("Unknown connection type", symbol);
+}
+
 DEFUN ("set-process-filter", Fset_process_filter, Sset_process_filter,
        2, 2, 0,
        doc: /* Give PROCESS the filter function FILTER; nil means default.
@@ -1741,15 +1754,18 @@ DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
 :connection-type TYPE -- TYPE is control type of device used to
 communicate with subprocesses.  Values are `pipe' to use a pipe, `pty'
 to use a pty, or nil to use the default specified through
-`process-connection-type'.
+`process-connection-type'.  If TYPE is a cons (INPUT . OUTPUT), then
+INPUT will be used for standard input and OUTPUT for standard output
+(and standard error if `:stderr' is nil).
 
 :filter FILTER -- Install FILTER as the process filter.
 
 :sentinel SENTINEL -- Install SENTINEL as the process sentinel.
 
 :stderr STDERR -- STDERR is either a buffer or a pipe process attached
-to the standard error of subprocess.  Specifying this implies
-`:connection-type' is set to `pipe'.  If STDERR is nil, standard error
+to the standard error of subprocess.  When specifying this, the
+subprocess's standard error will always communicate via a pipe, no
+matter the value of `:connection-type'.  If STDERR is nil, standard error
 is mixed with standard output and sent to BUFFER or FILTER.  (Note
 that specifying :stderr will create a new, separate (but associated)
 process, with its own filter and sentinel.  See
@@ -1845,22 +1861,20 @@ DEFUN ("make-process", Fmake_process, Smake_process, 0, MANY, 0,
   CHECK_TYPE (NILP (tem), Qnull, tem);
 
   tem = plist_get (contact, QCconnection_type);
-  if (EQ (tem, Qpty))
-    XPROCESS (proc)->pty_flag = true;
-  else if (EQ (tem, Qpipe))
-    XPROCESS (proc)->pty_flag = false;
-  else if (NILP (tem))
-    XPROCESS (proc)->pty_flag = !NILP (Vprocess_connection_type);
+  if (CONSP (tem))
+    {
+      XPROCESS (proc)->pty_in = is_pty_from_symbol (XCAR (tem));
+      XPROCESS (proc)->pty_out = is_pty_from_symbol (XCDR (tem));
+    }
   else
-    report_file_error ("Unknown connection type", tem);
-
-  if (!NILP (stderrproc))
     {
-      pset_stderrproc (XPROCESS (proc), stderrproc);
-
-      XPROCESS (proc)->pty_flag = false;
+      XPROCESS (proc)->pty_in = XPROCESS (proc)->pty_out =
+	is_pty_from_symbol (tem);
     }
 
+  if (!NILP (stderrproc))
+    pset_stderrproc (XPROCESS (proc), stderrproc);
+
 #ifdef HAVE_GNUTLS
   /* AKA GNUTLS_INITSTAGE(proc).  */
   verify (GNUTLS_STAGE_EMPTY == 0);
@@ -2099,66 +2113,80 @@ verify (PROCESS_OPEN_FDS == EXEC_MONITOR_OUTPUT + 1);
 create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
 {
   struct Lisp_Process *p = XPROCESS (process);
-  int inchannel, outchannel;
+  int inchannel = -1, outchannel = -1;
   pid_t pid = -1;
   int vfork_errno;
   int forkin, forkout, forkerr = -1;
-  bool pty_flag = 0;
+  bool pty_in = false, pty_out = false;
   char pty_name[PTY_NAME_SIZE];
   Lisp_Object lisp_pty_name = Qnil;
+  int ptychannel = -1, pty_tty = -1;
   sigset_t oldset;
 
   /* Ensure that the SIGCHLD handler can notify
      `wait_reading_process_output'.  */
   child_signal_init ();
 
-  inchannel = outchannel = -1;
-
-  if (p->pty_flag)
-    outchannel = inchannel = allocate_pty (pty_name);
+  if (p->pty_in || p->pty_out)
+    ptychannel = allocate_pty (pty_name);
 
-  if (inchannel >= 0)
+  if (ptychannel >= 0)
     {
-      p->open_fd[READ_FROM_SUBPROCESS] = inchannel;
 #if ! defined (USG) || defined (USG_SUBTTY_WORKS)
       /* On most USG systems it does not work to open the pty's tty here,
 	 then close it and reopen it in the child.  */
       /* Don't let this terminal become our controlling terminal
 	 (in case we don't have one).  */
-      forkout = forkin = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
-      if (forkin < 0)
+      pty_tty = emacs_open (pty_name, O_RDWR | O_NOCTTY, 0);
+      if (pty_tty < 0)
 	report_file_error ("Opening pty", Qnil);
-      p->open_fd[SUBPROCESS_STDIN] = forkin;
-#else
-      forkin = forkout = -1;
 #endif /* not USG, or USG_SUBTTY_WORKS */
-      pty_flag = 1;
+      pty_in = p->pty_in;
+      pty_out = p->pty_out;
       lisp_pty_name = build_string (pty_name);
     }
+
+  /* Set up stdin for the child process.  */
+  if (ptychannel >= 0 && p->pty_in)
+    {
+      p->open_fd[SUBPROCESS_STDIN] = forkin = pty_tty;
+      outchannel = ptychannel;
+    }
   else
     {
-      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0
-	  || emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+      if (emacs_pipe (p->open_fd + SUBPROCESS_STDIN) != 0)
 	report_file_error ("Creating pipe", Qnil);
       forkin = p->open_fd[SUBPROCESS_STDIN];
       outchannel = p->open_fd[WRITE_TO_SUBPROCESS];
+    }
+
+  /* Set up stdout for the child process.  */
+  if (ptychannel >= 0 && p->pty_out)
+    {
+      forkout = pty_tty;
+      p->open_fd[READ_FROM_SUBPROCESS] = inchannel = ptychannel;
+    }
+  else
+    {
+      if (emacs_pipe (p->open_fd + READ_FROM_SUBPROCESS) != 0)
+	report_file_error ("Creating pipe", Qnil);
       inchannel = p->open_fd[READ_FROM_SUBPROCESS];
       forkout = p->open_fd[SUBPROCESS_STDOUT];
 
 #if defined(GNU_LINUX) && defined(F_SETPIPE_SZ)
       fcntl (inchannel, F_SETPIPE_SZ, read_process_output_max);
 #endif
+    }
 
-      if (!NILP (p->stderrproc))
-	{
-	  struct Lisp_Process *pp = XPROCESS (p->stderrproc);
+  if (!NILP (p->stderrproc))
+    {
+      struct Lisp_Process *pp = XPROCESS (p->stderrproc);
 
-	  forkerr = pp->open_fd[SUBPROCESS_STDOUT];
+      forkerr = pp->open_fd[SUBPROCESS_STDOUT];
 
-	  /* Close unnecessary file descriptors.  */
-	  close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
-	  close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
-	}
+      /* Close unnecessary file descriptors.  */
+      close_process_fd (&pp->open_fd[WRITE_TO_SUBPROCESS]);
+      close_process_fd (&pp->open_fd[SUBPROCESS_STDIN]);
     }
 
   if (FD_SETSIZE <= inchannel || FD_SETSIZE <= outchannel)
@@ -2183,7 +2211,8 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
      we just reopen the device (see emacs_get_tty_pgrp) as this is
      more portable (see USG_SUBTTY_WORKS above).  */
 
-  p->pty_flag = pty_flag;
+  p->pty_in = pty_in;
+  p->pty_out = pty_out;
   pset_status (p, Qrun);
 
   if (!EQ (p->command, Qt)
@@ -2199,13 +2228,15 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
   block_input ();
   block_child_signal (&oldset);
 
-  pty_flag = p->pty_flag;
-  eassert (pty_flag == ! NILP (lisp_pty_name));
+  pty_in = p->pty_in;
+  pty_out = p->pty_out;
+  eassert ((pty_in || pty_out) == ! NILP (lisp_pty_name));
 
   vfork_errno
     = emacs_spawn (&pid, forkin, forkout, forkerr, new_argv, env,
                    SSDATA (current_dir),
-                   pty_flag ? SSDATA (lisp_pty_name) : NULL, &oldset);
+                   pty_in || pty_out ? SSDATA (lisp_pty_name) : NULL,
+                   pty_in, pty_out, &oldset);
 
   eassert ((vfork_errno == 0) == (0 < pid));
 
@@ -2263,7 +2294,7 @@ create_pty (Lisp_Object process)
 {
   struct Lisp_Process *p = XPROCESS (process);
   char pty_name[PTY_NAME_SIZE];
-  int pty_fd = !p->pty_flag ? -1 : allocate_pty (pty_name);
+  int pty_fd = !(p->pty_in || p->pty_out) ? -1 : allocate_pty (pty_name);
 
   if (pty_fd >= 0)
     {
@@ -2301,7 +2332,7 @@ create_pty (Lisp_Object process)
 	 we just reopen the device (see emacs_get_tty_pgrp) as this is
 	 more portable (see USG_SUBTTY_WORKS above).  */
 
-      p->pty_flag = 1;
+      p->pty_in = p->pty_out = true;
       pset_status (p, Qrun);
       setup_process_coding_systems (process);
 
@@ -2412,7 +2443,7 @@ DEFUN ("make-pipe-process", Fmake_pipe_process, Smake_pipe_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -3147,7 +3178,7 @@ DEFUN ("make-serial-process", Fmake_serial_process, Smake_serial_process,
     p->kill_without_query = 1;
   if (tem = plist_get (contact, QCstop), !NILP (tem))
     pset_command (p, Qt);
-  eassert (! p->pty_flag);
+  eassert (! p->pty_in && ! p->pty_out);
 
   if (!EQ (p->command, Qt)
       && !EQ (p->filter, Qt))
@@ -6798,7 +6829,7 @@ process_send_signal (Lisp_Object process, int signo, Lisp_Object current_group,
     error ("Process %s is not active",
 	   SDATA (p->name));
 
-  if (!p->pty_flag)
+  if (! p->pty_in)
     current_group = Qnil;
 
   /* If we are using pgrps, get a pgrp number and make it negative.  */
@@ -7167,7 +7198,7 @@ DEFUN ("process-send-eof", Fprocess_send_eof, Sprocess_send_eof, 0, 1, 0,
       send_process (proc, "", 0, Qnil);
     }
 
-  if (XPROCESS (proc)->pty_flag)
+  if (XPROCESS (proc)->pty_in)
     send_process (proc, "\004", 1, Qnil);
   else if (EQ (XPROCESS (proc)->type, Qserial))
     {
diff --git a/src/process.h b/src/process.h
index 392b661ce6..92baf0c4cb 100644
--- a/src/process.h
+++ b/src/process.h
@@ -156,8 +156,9 @@ #define EMACS_PROCESS_H
     /* True means kill silently if Emacs is exited.
        This is the inverse of the `query-on-exit' flag.  */
     bool_bf kill_without_query : 1;
-    /* True if communicating through a pty.  */
-    bool_bf pty_flag : 1;
+    /* True if communicating through a pty for input or output.  */
+    bool_bf pty_in : 1;
+    bool_bf pty_out : 1;
     /* Flag to set coding-system of the process buffer from the
        coding_system used to decode process output.  */
     bool_bf inherit_coding_system_flag : 1;
diff --git a/test/lisp/eshell/esh-proc-tests.el b/test/lisp/eshell/esh-proc-tests.el
index 7f461d1813..734bb91a6a 100644
--- a/test/lisp/eshell/esh-proc-tests.el
+++ b/test/lisp/eshell/esh-proc-tests.el
@@ -28,6 +28,15 @@
                            (file-name-directory (or load-file-name
                                                     default-directory))))
 
+(defvar esh-proc-test--detect-pty-cmd
+  (concat "sh -c '"
+          "if [ -t 0 ]; then echo stdin; fi; "
+          "if [ -t 1 ]; then echo stdout; fi; "
+          "if [ -t 2 ]; then echo stderr; fi"
+          "'"))
+
+;;; Tests:
+
 (ert-deftest esh-proc-test/sigpipe-exits-process ()
   "Test that a SIGPIPE is properly sent to a process if a pipe closes"
   (skip-unless (and (executable-find "sh")
@@ -44,6 +53,40 @@ esh-proc-test/sigpipe-exits-process
    (eshell-wait-for-subprocess t)
    (should (eq (process-list) nil))))
 
+(ert-deftest esh-proc-test/pipeline-connection-type/no-pipeline ()
+  "Test that all streams are PTYs when a command is not in a pipeline."
+  (skip-unless (executable-find "sh"))
+  (should (equal (eshell-test-command-result esh-proc-test--detect-pty-cmd)
+                 ;; PTYs aren't supported on MS-Windows.
+                 (unless (eq system-type 'windows-nt)
+                   "stdin\nstdout\nstderr\n"))))
+
+(ert-deftest esh-proc-test/pipeline-connection-type/first ()
+  "Test that only stdin is a PTY when a command starts a pipeline."
+  (skip-unless (and (executable-find "sh")
+                    (executable-find "cat")))
+  (should (equal (eshell-test-command-result
+                  (concat esh-proc-test--detect-pty-cmd " | cat"))
+                 (unless (eq system-type 'windows-nt)
+                   "stdin\n"))))
+
+(ert-deftest esh-proc-test/pipeline-connection-type/middle ()
+  "Test that all streams are pipes when a command is in the middle of a
+pipeline."
+  (skip-unless (and (executable-find "sh")
+                    (executable-find "cat")))
+  (should (equal (eshell-test-command-result
+                  (concat "echo | " esh-proc-test--detect-pty-cmd " | cat"))
+                 nil)))
+
+(ert-deftest esh-proc-test/pipeline-connection-type/last ()
+  "Test that only output streams are PTYs when a command ends a pipeline."
+  (skip-unless (executable-find "sh"))
+  (should (equal (eshell-test-command-result
+                  (concat "echo | " esh-proc-test--detect-pty-cmd))
+                 (unless (eq system-type 'windows-nt)
+                   "stdout\nstderr\n"))))
+
 (ert-deftest esh-proc-test/kill-pipeline ()
   "Test that killing a pipeline of processes only emits a single
 prompt.  See bug#54136."
diff --git a/test/src/process-tests.el b/test/src/process-tests.el
index f1ed7e18d5..41320672a0 100644
--- a/test/src/process-tests.el
+++ b/test/src/process-tests.el
@@ -38,10 +38,11 @@
 ;; Timeout in seconds; the test fails if the timeout is reached.
 (defvar process-test-sentinel-wait-timeout 2.0)
 
-;; Start a process that exits immediately.  Call WAIT-FUNCTION,
-;; possibly multiple times, to wait for the process to complete.
-(defun process-test-sentinel-wait-function-working-p (wait-function)
-  (let ((proc (start-process "test" nil "bash" "-c" "exit 20"))
+(defun process-test-wait-for-sentinel (proc exit-status &optional wait-function)
+  "Set a sentinel on PROC and wait for it to be called with EXIT-STATUS.
+Call WAIT-FUNCTION, possibly multiple times, to wait for the
+process to complete."
+  (let ((wait-function (or wait-function #'accept-process-output))
 	(sentinel-called nil)
 	(start-time (float-time)))
     (set-process-sentinel proc (lambda (_proc _msg)
@@ -50,21 +51,22 @@ process-test-sentinel-wait-function-working-p
 		    (> (- (float-time) start-time)
 		       process-test-sentinel-wait-timeout)))
       (funcall wait-function))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
-    sentinel-called))
+    (should sentinel-called)
+    (should (eq (process-status proc) 'exit))
+    (should (= (process-exit-status proc) exit-status))))
 
 (ert-deftest process-test-sentinel-accept-process-output ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (should (process-test-sentinel-wait-function-working-p
-           #'accept-process-output))))
+    (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
+      (should (process-test-wait-for-sentinel proc 20)))))
 
 (ert-deftest process-test-sentinel-sit-for ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (should
-   (process-test-sentinel-wait-function-working-p (lambda () (sit-for 0.01 t))))))
+    (let ((proc (start-process "test" nil "bash" "-c" "exit 20")))
+      (should (process-test-wait-for-sentinel
+               proc 20 (lambda () (sit-for 0.01 t)))))))
 
 (when (eq system-type 'windows-nt)
   (ert-deftest process-test-quoted-batfile ()
@@ -97,17 +99,8 @@ process-test-stderr-buffer
 						    "echo hello stderr! >&2; "
 						    "exit 20"))
 			     :buffer stdout-buffer
-			     :stderr stderr-buffer))
-	 (sentinel-called nil)
-	 (start-time (float-time)))
-    (set-process-sentinel proc (lambda (_proc _msg)
-				 (setq sentinel-called t)))
-    (while (not (or sentinel-called
-		    (> (- (float-time) start-time)
-		       process-test-sentinel-wait-timeout)))
-      (accept-process-output))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
+			     :stderr stderr-buffer)))
+    (process-test-wait-for-sentinel proc 20)
     (should (with-current-buffer stdout-buffer
 	      (goto-char (point-min))
 	      (looking-at "hello stdout!")))
@@ -118,8 +111,7 @@ process-test-stderr-buffer
 (ert-deftest process-test-stderr-filter ()
   (skip-unless (executable-find "bash"))
   (with-timeout (60 (ert-fail "Test timed out"))
-  (let* ((sentinel-called nil)
-	 (stderr-sentinel-called nil)
+  (let* ((stderr-sentinel-called nil)
 	 (stdout-output nil)
 	 (stderr-output nil)
 	 (stdout-buffer (generate-new-buffer "*stdout*"))
@@ -131,23 +123,14 @@ process-test-stderr-filter
 					    (concat "echo hello stdout!; "
 						    "echo hello stderr! >&2; "
 						    "exit 20"))
-			     :stderr stderr-proc))
-	 (start-time (float-time)))
+			     :stderr stderr-proc)))
     (set-process-filter proc (lambda (_proc input)
 			       (push input stdout-output)))
-    (set-process-sentinel proc (lambda (_proc _msg)
-				 (setq sentinel-called t)))
     (set-process-filter stderr-proc (lambda (_proc input)
 				      (push input stderr-output)))
     (set-process-sentinel stderr-proc (lambda (_proc _input)
 					(setq stderr-sentinel-called t)))
-    (while (not (or sentinel-called
-		    (> (- (float-time) start-time)
-		       process-test-sentinel-wait-timeout)))
-      (accept-process-output))
-    (cl-assert (eq (process-status proc) 'exit))
-    (cl-assert (= (process-exit-status proc) 20))
-    (should sentinel-called)
+    (process-test-wait-for-sentinel proc 20)
     (should (equal 1 (with-current-buffer stdout-buffer
 		       (point-max))))
     (should (equal "hello stdout!\n"
@@ -289,6 +272,74 @@ make-process-w32-debug-spawn-error
                   (error :got-error))))
     (should have-called-debugger))))
 
+(defun make-process/test-connection-type (ttys &rest args)
+  "Make a process and check whether its standard streams match TTYS.
+This calls `make-process', passing ARGS to adjust how the process
+is created.  TTYS should be a list of 3 boolean values,
+indicating whether the subprocess's stdin, stdout, and stderr
+should be a TTY, respectively."
+  (declare (indent 1))
+  (let* (;; MS-Windows doesn't support communicating via pty.
+         (ttys (if (eq system-type 'windows-nt) '(nil nil nil) ttys))
+         (expected-output (concat (and (nth 0 ttys) "stdin\n")
+                                  (and (nth 1 ttys) "stdout\n")
+                                  (and (nth 2 ttys) "stderr\n")))
+         (stdout-buffer (generate-new-buffer "*stdout*"))
+         (proc (apply
+                #'make-process
+                :name "test"
+                :command (list "sh" "-c"
+                               (concat "if [ -t 0 ]; then echo stdin; fi; "
+                                       "if [ -t 1 ]; then echo stdout; fi; "
+                                       "if [ -t 2 ]; then echo stderr; fi"))
+                :buffer stdout-buffer
+                args)))
+    (process-test-wait-for-sentinel proc 0)
+    (should (equal (with-current-buffer stdout-buffer (buffer-string))
+                   expected-output))))
+
+(ert-deftest make-process/connection-type/pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t t t)
+    :connection-type 'pty))
+
+(ert-deftest make-process/connection-type/pty-2 ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t t t)
+    :connection-type '(pty . pty)))
+
+(ert-deftest make-process/connection-type/pipe ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil nil nil)
+    :connection-type 'pipe))
+
+(ert-deftest make-process/connection-type/pipe-2 ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil nil nil)
+    :connection-type '(pipe . pipe)))
+
+(ert-deftest make-process/connection-type/in-pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(t nil nil)
+    :connection-type '(pty . pipe)))
+
+(ert-deftest make-process/connection-type/out-pty ()
+  (skip-unless (executable-find "sh"))
+  (make-process/test-connection-type '(nil t t)
+    :connection-type '(pipe . pty)))
+
+(ert-deftest make-process/connection-type/pty-with-stderr-buffer ()
+  (skip-unless (executable-find "sh"))
+  (let ((stderr-buffer (generate-new-buffer "*stderr*")))
+    (make-process/test-connection-type '(t t nil)
+      :connection-type 'pty :stderr stderr-buffer)))
+
+(ert-deftest make-process/connection-type/out-pty-with-stderr-buffer ()
+  (skip-unless (executable-find "sh"))
+  (let ((stderr-buffer (generate-new-buffer "*stderr*")))
+    (make-process/test-connection-type '(nil t nil)
+      :connection-type '(pipe . pty) :stderr stderr-buffer)))
+
 (ert-deftest make-process/file-handler/found ()
   "Check that the `:file-handler’ argument of `make-process’
 works as expected if a file name handler is found."
-- 
2.25.1


[-- Attachment #3: 0002-Add-STREAM-argument-to-process-tty-name.patch --]
[-- Type: text/plain, Size: 7843 bytes --]

From 705fcdb7b731020f1481694da3d985c6e7485b3f Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Tue, 19 Jul 2022 21:36:54 -0700
Subject: [PATCH 2/2] Add STREAM argument to 'process-tty-name'

* src/process.c (process-tty-name): Add STREAM argument.

* lisp/eshell/esh-io.el (eshell-close-target): Only call
'process-send-eof' once if the process's stdin is a pipe.

* test/src/process-tests.el (make-process/test-connection-type): Check
behavior of 'process-tty-name'.

* doc/lispref/processes.texi (Process Information): Document the new
argument.

* etc/NEWS: Announce this change.
---
 doc/lispref/processes.texi | 21 +++++++++++++++------
 etc/NEWS                   |  5 ++++-
 lisp/eshell/esh-io.el      | 27 +++++++++++++++------------
 src/process.c              | 25 +++++++++++++++++++++----
 test/src/process-tests.el  |  3 +++
 5 files changed, 58 insertions(+), 23 deletions(-)

diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index a7e08054c7..b7dd235699 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -1243,15 +1243,24 @@ Process Information
 whether the connection was closed normally or abnormally.
 @end defun
 
-@defun process-tty-name process
+@defun process-tty-name process &optional stream
 This function returns the terminal name that @var{process} is using for
 its communication with Emacs---or @code{nil} if it is using pipes
 instead of a pty (see @code{process-connection-type} in
-@ref{Asynchronous Processes}).  If @var{process} represents a program
-running on a remote host, the terminal name used by that program on
-the remote host is provided as process property @code{remote-tty}.  If
-@var{process} represents a network, serial, or pipe connection, the
-value is @code{nil}.
+@ref{Asynchronous Processes}).  By default, this function returns the
+terminal name if any of @var{process}'s standard streams use a
+terminal.  If @var{stream} is one of @code{stdin}, @code{stdout}, or
+@code{stderr}, this function returns the terminal name (or @code{nil},
+as above) that @var{process} uses for that stream specifically.  You
+can use this to determine whether a particular stream uses a pipe or a
+pty.
+
+If @var{process} represents a program running on a remote host, this
+function returns the @emph{local} terminal name that communicates with
+@var{process}; you can get the terminal name used by that program on
+the remote host with the process property @code{remote-tty}.  If
+@var{process} represents a network, serial, or pipe connection, this
+function always returns @code{nil}.
 @end defun
 
 @defun process-coding-system process
diff --git a/etc/NEWS b/etc/NEWS
index dc79f0826a..23777d349e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3198,7 +3198,10 @@ invocation.  Such shells are POSIX conformant by default.
 ** 'make-process' can set connection type independently for input and output.
 When calling 'make-process', communication via pty can be enabled
 selectively for just input or output by passing a cons cell for
-':connection-type', e.g. '(pipe . pty)'.
+':connection-type', e.g. '(pipe . pty)'.  When examining a process
+later, you can determine whether a particular stream for a process
+uses a pty by passing one of 'stdin', 'stdout', or 'stderr' as the
+second argument to 'process-tty-name'.
 
 +++
 ** 'signal-process' now consults the list 'signal-process-functions'.
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index c035890ddf..68e52a2c9c 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -276,18 +276,21 @@ eshell-close-target
    ;; 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)
-    ;; According to POSIX.1-2017, section 11.1.9, 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 shouldn't break
-    ;; anything, we'll just send the maximum we'd ever need.  See
-    ;; bug#56025 for further details.
-    (let ((i 0))
-      (while (and (<= (cl-incf i) 3)
+    ;; 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))))
 
diff --git a/src/process.c b/src/process.c
index da5e9cb182..dbd8c2d4e3 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1243,14 +1243,31 @@ DEFUN ("process-command", Fprocess_command, Sprocess_command, 1, 1, 0,
   return XPROCESS (process)->command;
 }
 
-DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 1, 0,
+DEFUN ("process-tty-name", Fprocess_tty_name, Sprocess_tty_name, 1, 2, 0,
        doc: /* Return the name of the terminal PROCESS uses, or nil if none.
 This is the terminal that the process itself reads and writes on,
-not the name of the pty that Emacs uses to talk with that terminal.  */)
-  (register Lisp_Object process)
+not the name of the pty that Emacs uses to talk with that terminal.
+
+If STREAM is nil, return the terminal name if any of PROCESS's
+standard streams use a terminal for communication.  If STREAM is one
+of `stdin', `stdout', or `stderr', return the name of the terminal
+PROCESS uses for that stream specifically, or nil if that stream
+communicates via a pipe.  */)
+  (register Lisp_Object process, Lisp_Object stream)
 {
   CHECK_PROCESS (process);
-  return XPROCESS (process)->tty_name;
+  register struct Lisp_Process *p = XPROCESS (process);
+
+  if (NILP (stream))
+    return p->tty_name;
+  else if (EQ (stream, Qstdin))
+    return p->pty_in ? p->tty_name : Qnil;
+  else if (EQ (stream, Qstdout))
+    return p->pty_out ? p->tty_name : Qnil;
+  else if (EQ (stream, Qstderr))
+    return p->pty_out && NILP (p->stderrproc) ? p->tty_name : Qnil;
+  else
+    signal_error ("Unknown stream", stream);
 }
 
 static void
diff --git a/test/src/process-tests.el b/test/src/process-tests.el
index 41320672a0..6ba5930ee6 100644
--- a/test/src/process-tests.el
+++ b/test/src/process-tests.el
@@ -294,6 +294,9 @@ make-process/test-connection-type
                                        "if [ -t 2 ]; then echo stderr; fi"))
                 :buffer stdout-buffer
                 args)))
+    (should (eq (and (process-tty-name proc 'stdin) t) (nth 0 ttys)))
+    (should (eq (and (process-tty-name proc 'stdout) t) (nth 1 ttys)))
+    (should (eq (and (process-tty-name proc 'stderr) t) (nth 2 ttys)))
     (process-test-wait-for-sentinel proc 0)
     (should (equal (with-current-buffer stdout-buffer (buffer-string))
                    expected-output))))
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v5] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-24 17:36                                                                         ` bug#56025: [PATCH v5] " Jim Porter
@ 2022-07-24 20:30                                                                           ` Ken Brown
  2022-07-31  1:01                                                                             ` Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Ken Brown @ 2022-07-24 20:30 UTC (permalink / raw)
  To: Jim Porter, Eli Zaretskii; +Cc: larsi, 56025, spwhitton

On 7/24/2022 1:36 PM, Jim Porter wrote:
> On 7/24/2022 2:47 AM, Eli Zaretskii wrote:
>  > I did the review and tested on native MS-Windows, but I think we
>  > should wait for Ken to try this on Cygwin.
> 
> I tested the v4 patch on Cygwin (and GNU/Linux) and all the new tests I added 
> passed. Ken also tested patch v2 and things worked.

And now I've tested the latest version, and it still looks good.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v4] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-24  9:08                                                                       ` Lars Ingebrigtsen
  2022-07-24  9:48                                                                         ` Eli Zaretskii
@ 2022-07-24 21:04                                                                         ` Ken Brown
  1 sibling, 0 replies; 64+ messages in thread
From: Ken Brown @ 2022-07-24 21:04 UTC (permalink / raw)
  To: Lars Ingebrigtsen, Jim Porter; +Cc: Eli Zaretskii, 56025, Sean Whitton

On 7/24/2022 5:08 AM, Lars Ingebrigtsen wrote:
> Jim Porter <jporterbugs@gmail.com> writes:
> 
>> Oops. I forgot to add some `(skip-unless ...)' forms for these tests,
>> so... here they are. Hopefully this will be the last message from me
>> for a bit. :C
> 
> :-)
> 
> Since this (mainly) affects Cygwin builds, could someone who uses
> Windows give the patch a look-over and apply it?

It actually affects all builds, even though the motivation for it came from 
problems on Cygwin.  But it's now been tested on GNU/Linux, native MS-Windows, 
and Cygwin.  So I think it should be OK.

Ken





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v5] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-24 20:30                                                                           ` Ken Brown
@ 2022-07-31  1:01                                                                             ` Jim Porter
  2022-08-06  1:10                                                                               ` Jim Porter
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-07-31  1:01 UTC (permalink / raw)
  To: Ken Brown, Eli Zaretskii; +Cc: larsi, 56025, spwhitton

On 7/24/2022 1:30 PM, Ken Brown wrote:
> On 7/24/2022 1:36 PM, Jim Porter wrote:
>> On 7/24/2022 2:47 AM, Eli Zaretskii wrote:
>>  > I did the review and tested on native MS-Windows, but I think we
>>  > should wait for Ken to try this on Cygwin.
>>
>> I tested the v4 patch on Cygwin (and GNU/Linux) and all the new tests 
>> I added passed. Ken also tested patch v2 and things worked.
> 
> And now I've tested the latest version, and it still looks good.

Thanks for checking. Unless anyone has any objections, I'll merge this 
in a couple days then.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v5] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-07-31  1:01                                                                             ` Jim Porter
@ 2022-08-06  1:10                                                                               ` Jim Porter
  2022-08-06 12:17                                                                                 ` Lars Ingebrigtsen
  0 siblings, 1 reply; 64+ messages in thread
From: Jim Porter @ 2022-08-06  1:10 UTC (permalink / raw)
  To: Ken Brown, Eli Zaretskii; +Cc: larsi, 56025-done, spwhitton

On 7/30/2022 6:01 PM, Jim Porter wrote:
> Thanks for checking. Unless anyone has any objections, I'll merge this 
> in a couple days then.

Ok, I've merged these changes in 4e59830bc0ab17cdbd85748b133c97837bed99e3.

Hopefully I've done everything correctly, since this is the first time 
I've done the merge myself (I'm plenty familiar with git, but may have 
missed some Emacs-specific procedure). If I did miss something, just let 
me know so I can avoid issues in the future.

Thanks for all the reviews/testing.





^ permalink raw reply	[flat|nested] 64+ messages in thread

* bug#56025: [PATCH v5] 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin
  2022-08-06  1:10                                                                               ` Jim Porter
@ 2022-08-06 12:17                                                                                 ` Lars Ingebrigtsen
  0 siblings, 0 replies; 64+ messages in thread
From: Lars Ingebrigtsen @ 2022-08-06 12:17 UTC (permalink / raw)
  To: Jim Porter; +Cc: Eli Zaretskii, 56025, spwhitton, Ken Brown

Jim Porter <jporterbugs@gmail.com> writes:

> Hopefully I've done everything correctly, since this is the first time
> I've done the merge myself (I'm plenty familiar with git, but may have
> missed some Emacs-specific procedure). If I did miss something, just
> let me know so I can avoid issues in the future.

I had a quick look at the commit, and I don't see any problems, so I
think it worked fine.  (Note: I didn't look at the actual semantic
changes, since Eli has already done that in the code review, but only
the general commit.)






^ permalink raw reply	[flat|nested] 64+ messages in thread

end of thread, other threads:[~2022-08-06 12:17 UTC | newest]

Thread overview: 64+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-06-16 18:30 bug#56025: 29.0.50; em-extpipe-test-2 times out on EMBA and Cygwin Ken Brown
2022-06-16 19:30 ` Sean Whitton
2022-06-16 22:01   ` Ken Brown
2022-06-17 13:39     ` Ken Brown
2022-06-18  0:57       ` Sean Whitton
2022-06-18  2:07         ` Ken Brown
2022-06-18  2:35           ` Ken Brown
2022-06-18  3:50           ` Jim Porter
2022-06-18 17:52             ` Ken Brown
2022-06-18 19:02               ` Jim Porter
2022-06-18 20:51                 ` Ken Brown
2022-06-18 22:00                   ` Jim Porter
2022-06-18 23:46                     ` Sean Whitton
2022-06-19 16:02                     ` Ken Brown
2022-06-24  1:18                       ` Ken Brown
2022-06-24  4:40                         ` Sean Whitton
2022-06-24  6:07                         ` Eli Zaretskii
2022-06-24 16:53                           ` Jim Porter
2022-06-24 22:23                             ` Sean Whitton
2022-06-24 23:03                               ` Jim Porter
2022-06-25  5:34                                 ` Eli Zaretskii
2022-06-25 16:13                                   ` Jim Porter
2022-06-25 16:53                                     ` Eli Zaretskii
2022-06-26 16:27                                       ` Lars Ingebrigtsen
2022-06-26 17:12                                 ` Sean Whitton
2022-06-26 17:22                                   ` Jim Porter
2022-06-26 21:11                                     ` Sean Whitton
2022-06-27 13:25                                       ` Ken Brown
2022-06-27 15:51                                         ` Michael Albinus
2022-06-27 16:22                                           ` Ken Brown
2022-06-27 19:13                                             ` bug#56025: [EXT]Re: " Sean Whitton
2022-06-27 21:17                                               ` Ken Brown
2022-06-27 19:18                                         ` Jim Porter
2022-06-27 21:19                                           ` Ken Brown
2022-07-01  3:52                                             ` Jim Porter
2022-07-01  3:58                                               ` Jim Porter
2022-07-06 22:33                                               ` Ken Brown
2022-07-07  4:35                                                 ` Jim Porter
2022-07-07  4:42                                                   ` Jim Porter
2022-07-07 12:42                                                     ` Ken Brown
2022-07-17  2:35                                                       ` bug#56025: [WIP PATCH] " Jim Porter
2022-07-17  6:03                                                         ` Eli Zaretskii
2022-07-17 17:44                                                           ` Jim Porter
2022-07-17 18:26                                                             ` Eli Zaretskii
2022-07-17 18:51                                                               ` Jim Porter
2022-07-18  8:09                                                             ` Michael Albinus
2022-07-19  1:58                                                               ` Jim Porter
2022-07-19  7:59                                                                 ` Michael Albinus
2022-07-17 21:59                                                         ` Ken Brown
2022-07-18  5:26                                                           ` Jim Porter
2022-07-22  4:16                                                             ` bug#56025: [PATCH v2] " Jim Porter
2022-07-22 19:00                                                               ` Ken Brown
2022-07-24  4:05                                                                 ` Jim Porter
2022-07-24  5:19                                                                   ` bug#56025: [PATCH v3] " Jim Porter
2022-07-24  5:29                                                                     ` bug#56025: [PATCH v4] " Jim Porter
2022-07-24  9:08                                                                       ` Lars Ingebrigtsen
2022-07-24  9:48                                                                         ` Eli Zaretskii
2022-07-24 21:04                                                                         ` Ken Brown
2022-07-24  9:47                                                                       ` Eli Zaretskii
2022-07-24 17:36                                                                         ` bug#56025: [PATCH v5] " Jim Porter
2022-07-24 20:30                                                                           ` Ken Brown
2022-07-31  1:01                                                                             ` Jim Porter
2022-08-06  1:10                                                                               ` Jim Porter
2022-08-06 12:17                                                                                 ` Lars Ingebrigtsen

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).