diff --git a/winsup/cygwin/fhandler/pipe.cc b/winsup/cygwin/fhandler/pipe.cc index ce6099d8b..33ab11744 100644 --- a/winsup/cygwin/fhandler/pipe.cc +++ b/winsup/cygwin/fhandler/pipe.cc @@ -13,6 +13,7 @@ details. */ #include "security.h" #include "path.h" #include "fhandler.h" +#include "select.h" #include "dtable.h" #include "cygheap.h" #include "pinfo.h" @@ -433,6 +434,7 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len) NTSTATUS status = STATUS_SUCCESS; IO_STATUS_BLOCK io; HANDLE evt; + bool short_write_once = false; if (!len) return 0; @@ -471,6 +473,7 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len) len1 = chunk; else len1 = (ULONG) left; + /* NtWriteFile returns success with # of bytes written == 0 if writing on a non-blocking pipe fails because the pipe buffer doesn't have sufficient space. @@ -517,6 +520,13 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len) } else cygwait (select_sem, 10, cw_cancel); + /* If we got a timeout in the blocking case, and we already + did a short write, we got a signal in the previous loop. */ + if (waitret == WAIT_TIMEOUT && short_write_once) + { + waitret = WAIT_SIGNALED; + break; + } } /* Loop in case of blocking write or SA_RESTART */ while (waitret == WAIT_TIMEOUT || waitret == WAIT_SIGNALED); @@ -533,13 +543,39 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len) status = STATUS_THREAD_CANCELED; else if (waitret == WAIT_SIGNALED) status = STATUS_THREAD_SIGNALED; + else if (waitret == WAIT_TIMEOUT && io.Status == STATUS_CANCELLED) + status = STATUS_SUCCESS; else status = io.Status; } - if (!is_nonblocking () || !NT_SUCCESS (status) || io.Information > 0 - || len <= PIPE_BUF) + if (status != STATUS_THREAD_SIGNALED && !NT_SUCCESS (status)) break; - len1 >>= 1; + if (io.Information > 0 || len <= PIPE_BUF || short_write_once) + break; + /* Independent of being blocking or non-blocking, if we're here, + the pipe has less space than requested. If the pipe is a + non-Cygwin pipe, just try the old strategy of trying a half + write. If the pipe has at + least PIPE_BUF bytes available, try to write all matching + PIPE_BUF sized blocks. If it's less than PIPE_BUF, try + the next less power of 2 bytes. This is not really the Linux + strategy because Linux is filling the pages of a pipe buffer + in a very implementation-defined way we can't emulate, but it + resembles it closely enough to get useful results. */ + ssize_t avail = pipe_data_available (-1, this, get_handle (), + PDA_WRITE); + if (avail < 1) /* error or pipe closed */ + break; + if (avail >= len1) /* somebody read from the pipe */ + avail = len1; + if (avail == 1) /* 1 byte left or non-Cygwin pipe */ + len1 >>= 1; + else if (avail >= PIPE_BUF) + len1 = avail & ~(PIPE_BUF - 1); + else + len1 = 1 << (31 - __builtin_clzl (avail)); + if (!is_nonblocking ()) + short_write_once = true; } if (isclosed ()) /* A signal handler might have closed the fd. */ { @@ -569,7 +605,7 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len) else __seterrno_from_nt_status (status); - if (nbytes_now == 0) + if (nbytes_now == 0 || short_write_once) break; } out: diff --git a/winsup/cygwin/local_includes/select.h b/winsup/cygwin/local_includes/select.h index 4e202128f..dea023343 100644 --- a/winsup/cygwin/local_includes/select.h +++ b/winsup/cygwin/local_includes/select.h @@ -139,4 +139,9 @@ public: extern "C" int cygwin_select (int , fd_set *, fd_set *, fd_set *, struct timeval *to); +ssize_t pipe_data_available (int, fhandler_base *, HANDLE, int); + +#define PDA_WRITE 0x01 +#define PDA_SELECT 0x02 + #endif /* _SELECT_H_ */ diff --git a/winsup/cygwin/select.cc b/winsup/cygwin/select.cc index db52ffccc..bc02c3f9d 100644 --- a/winsup/cygwin/select.cc +++ b/winsup/cygwin/select.cc @@ -584,13 +584,14 @@ no_verify (select_record *, fd_set *, fd_set *, fd_set *) return 0; } -static int -pipe_data_available (int fd, fhandler_base *fh, HANDLE h, bool writing) +ssize_t +pipe_data_available (int fd, fhandler_base *fh, HANDLE h, int flags) { if (fh->get_device () == FH_PIPER) { DWORD nbytes_in_pipe; - if (!writing && PeekNamedPipe (h, NULL, 0, NULL, &nbytes_in_pipe, NULL)) + if (!(flags & PDA_WRITE) + && PeekNamedPipe (h, NULL, 0, NULL, &nbytes_in_pipe, NULL)) return nbytes_in_pipe; return -1; } @@ -609,9 +610,17 @@ pipe_data_available (int fd, fhandler_base *fh, HANDLE h, bool writing) access on the write end. */ select_printf ("fd %d, %s, NtQueryInformationFile failed, status %y", fd, fh->get_name (), status); - return writing ? PIPE_BUF : -1; + switch (flags) + { + case PDA_WRITE: + return 1; + case PDA_SELECT | PDA_WRITE: + return PIPE_BUF; + default: + return -1; + } } - if (writing) + if (flags & PDA_WRITE) { /* If there is anything available in the pipe buffer then signal that. This means that a pipe could still block since you could @@ -638,20 +647,26 @@ pipe_data_available (int fd, fhandler_base *fh, HANDLE h, bool writing) /* Note: Do not use NtQueryInformationFile() for query_hdl because NtQueryInformationFile() seems to interfere with reading pipes in non-cygwin apps. Instead, use PeekNamedPipe() here. */ + /* Note 2: we return the number of available bytes. Select for writing + returns writable *only* if at least PIPE_BUF bytes are left in the + buffer. If we can't fetch the real number of available bytes, the + number of bytes returned depends on the caller. For select we return + PIPE_BUF to fake writability, for writing we return 1 to allow + handling this fact. */ if (fh->get_device () == FH_PIPEW && fpli.WriteQuotaAvailable == 0) { HANDLE query_hdl = ((fhandler_pipe *) fh)->get_query_handle (); if (!query_hdl) query_hdl = ((fhandler_pipe *) fh)->temporary_query_hdl (); - if (!query_hdl) - return PIPE_BUF; /* We cannot know actual write pipe space. */ + if (!query_hdl) /* We cannot know actual write pipe space. */ + return (flags & PDA_SELECT) ? PIPE_BUF : 1; DWORD nbytes_in_pipe; BOOL res = PeekNamedPipe (query_hdl, NULL, 0, NULL, &nbytes_in_pipe, NULL); if (!((fhandler_pipe *) fh)->get_query_handle ()) CloseHandle (query_hdl); /* Close temporary query_hdl */ - if (!res) - return PIPE_BUF; /* We cannot know actual write pipe space. */ + if (!res) /* We cannot know actual write pipe space. */ + return (flags & PDA_SELECT) ? PIPE_BUF : 1; fpli.WriteQuotaAvailable = fpli.InboundQuota - nbytes_in_pipe; } if (fpli.WriteQuotaAvailable > 0) @@ -715,10 +730,10 @@ peek_pipe (select_record *s, bool from_select) gotone = s->read_ready = true; goto out; } - int n = pipe_data_available (s->fd, fh, h, false); + ssize_t n = pipe_data_available (s->fd, fh, h, PDA_SELECT); /* On PTY masters, check if input from the echo pipe is available. */ if (n == 0 && fh->get_echo_handle ()) - n = pipe_data_available (s->fd, fh, fh->get_echo_handle (), false); + n = pipe_data_available (s->fd, fh, fh->get_echo_handle (), PDA_SELECT); if (n < 0) { @@ -759,7 +774,7 @@ out: gotone += s->except_ready = true; return gotone; } - int n = pipe_data_available (s->fd, fh, h, true); + ssize_t n = pipe_data_available (s->fd, fh, h, PDA_SELECT | PDA_WRITE); select_printf ("write: %s, n %d", fh->get_name (), n); gotone += s->write_ready = (n >= PIPE_BUF); if (n < 0 && s->except_selected) @@ -972,7 +987,8 @@ peek_fifo (select_record *s, bool from_select) out: if (s->write_selected) { - int n = pipe_data_available (s->fd, fh, fh->get_handle (), true); + ssize_t n = pipe_data_available (s->fd, fh, fh->get_handle (), + PDA_SELECT | PDA_WRITE); select_printf ("write: %s, n %d", fh->get_name (), n); gotone += s->write_ready = (n >= PIPE_BUF); if (n < 0 && s->except_selected) @@ -1398,7 +1414,7 @@ out: HANDLE h = ptys->get_output_handle (); if (s->write_selected) { - int n = pipe_data_available (s->fd, fh, h, true); + ssize_t n = pipe_data_available (s->fd, fh, h, PDA_SELECT | PDA_WRITE); select_printf ("write: %s, n %d", fh->get_name (), n); gotone += s->write_ready = (n >= PIPE_BUF); if (n < 0 && s->except_selected)