https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=940dbeffa7134dcd0795bd146f60edac3548e32e

commit 940dbeffa7134dcd0795bd146f60edac3548e32e
Author: Takashi Yano <takashi.y...@nifty.ne.jp>
Date:   Fri Jun 27 11:26:22 2025 +0900

    Cygwin: pipe: Fix unexpected blocking mode change by pipe_data_available()
    
    ipipe_data_available() is called from raw_write(). If the pipe is in
    real_non_blocking_mode at that time, calling pipe_data_available()
    can, in some cases, inadvertently revert the pipe to blocking mode.
    Here is the background: pipe_data_available() checks the amount of
    writable space in the pipe by calling NtQueryInformationFile() with
    the FilePipeLocalInformation parameter. However, if the read side of
    the pipe is simultaneously consuming data with a large buffer,
    NtQueryInformationFile() may return 0 for WriteQuotaAvailable.
    As a workaround for this behavior, pipe_data_available() temporarily
    attempts to change the pipe-mode to blocking. If the pipe contains
    data, this operation fails-indicating that the pipe is full. If it
    succeeds, the pipe is considered empty. The problem arises from the
    assumption that the pipe is always in real blocking mode before
    attempting to flip the mode. However, if raw_write() has already set
    the pipe to non-blocking mode due to its failure to determine available
    space, two issues occur:
    1) Changing to non-blocking mode in pipe_data_available() always
       succeeds, since the pipe is already in non-blocking mode.
    2) After this, pipe_data_available() sets the pipe back to blocking
       mode, unintentionally overriding the non-blocking state required
       by raw_write().
    
    This patch addresses the issue by having pipe_data_available() check
    the current real blocking mode, temporarily flip the pipe-mode and
    then restore the pipe-mode to its original state.
    
    Addresses: 
https://github.com/git-for-windows/git/issues/5682#issuecomment-2997428207
    Fixes: 7ed9adb356df ("Cygwin: pipe: Switch pipe mode to blocking mode by 
default")
    Reported-by: Andrew Ng <andrew...@sony.com>
    Reviewed-by: Johannes Schindelin <johannes.schinde...@gmx.de>
    Signed-off-by: Takashi Yano <takashi.y...@nifty.ne.jp>

Diff:
---
 winsup/cygwin/fhandler/pipe.cc          |  2 --
 winsup/cygwin/local_includes/fhandler.h |  3 +++
 winsup/cygwin/select.cc                 | 11 ++++++-----
 3 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/winsup/cygwin/fhandler/pipe.cc b/winsup/cygwin/fhandler/pipe.cc
index 7e2c1861b..3a0f8bfe9 100644
--- a/winsup/cygwin/fhandler/pipe.cc
+++ b/winsup/cygwin/fhandler/pipe.cc
@@ -326,7 +326,6 @@ fhandler_pipe::raw_read (void *ptr, size_t& len)
       ULONG_PTR nbytes_now = 0;
       ULONG len1 = (ULONG) (len - nbytes);
       DWORD select_sem_timeout = 0;
-      bool real_non_blocking_mode = false;
 
       FILE_PIPE_LOCAL_INFORMATION fpli;
       status = NtQueryInformationFile (get_handle (), &io,
@@ -453,7 +452,6 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len)
     return 0;
 
   ssize_t avail = pipe_buf_size;
-  bool real_non_blocking_mode = false;
 
   /* Workaround for native ninja. Native ninja creates pipe with size == 0,
      and starts cygwin process with that pipe. */
diff --git a/winsup/cygwin/local_includes/fhandler.h 
b/winsup/cygwin/local_includes/fhandler.h
index 3d9bc9fa5..04e2ca4c3 100644
--- a/winsup/cygwin/local_includes/fhandler.h
+++ b/winsup/cygwin/local_includes/fhandler.h
@@ -1203,6 +1203,7 @@ class fhandler_pipe_fifo: public fhandler_base
  protected:
   size_t pipe_buf_size;
   HANDLE pipe_mtx; /* Used only in the pipe case */
+  bool real_non_blocking_mode; /* Used only in the pipe case */
   virtual void release_select_sem (const char *) {};
 
   IMPLEMENT_STATUS_FLAG (bool, isclosed)
@@ -1212,6 +1213,8 @@ class fhandler_pipe_fifo: public fhandler_base
 
   virtual bool reader_closed () { return false; };
   ssize_t raw_write (const void *ptr, size_t len);
+
+  friend ssize_t pipe_data_available (int, fhandler_base *, HANDLE, int);
 };
 
 class fhandler_pipe: public fhandler_pipe_fifo
diff --git a/winsup/cygwin/select.cc b/winsup/cygwin/select.cc
index 050221a9f..a7e82a024 100644
--- a/winsup/cygwin/select.cc
+++ b/winsup/cygwin/select.cc
@@ -645,8 +645,8 @@ pipe_data_available (int fd, fhandler_base *fh, HANDLE h, 
int mode)
         and it is pending.
         In the latter case, the fact that the reader cannot read the data
         immediately means that the pipe is empty. In the former case,
-        NtSetInformationFile() in set_pipe_non_blocking(true) will fail
-        with STATUS_PIPE_BUSY, while it succeeds in the latter case.
+        NtSetInformationFile() in set_pipe_non_blocking(!orig_mode) will
+        fail with STATUS_PIPE_BUSY, while it succeeds in the latter case.
         Therefore, we can distinguish these cases by calling set_pipe_non_
         blocking(true). If it returns success, the pipe is empty, so we
         return the pipe buffer size. Otherwise, we return the value of
@@ -654,15 +654,16 @@ pipe_data_available (int fd, fhandler_base *fh, HANDLE h, 
int mode)
       if (fh->get_device () == FH_PIPEW
          && fpli.WriteQuotaAvailable < fpli.InboundQuota)
        {
+         bool orig_mode = ((fhandler_pipe *) fh)->real_non_blocking_mode;
          NTSTATUS status =
-           ((fhandler_pipe *) fh)->set_pipe_non_blocking (true);
+           ((fhandler_pipe *) fh)->set_pipe_non_blocking (!orig_mode);
          if (status == STATUS_PIPE_BUSY)
            return fpli.WriteQuotaAvailable; /* Not empty */
          else if (!NT_SUCCESS (status))
            /* We cannot know actual write pipe space. */
            return PDA_UNKNOWN;
-         /* Restore pipe mode to blocking mode */
-         ((fhandler_pipe *) fh)->set_pipe_non_blocking (false);
+         /* Restore pipe mode to original blocking mode */
+         ((fhandler_pipe *) fh)->set_pipe_non_blocking (orig_mode);
          /* Empty */
          fpli.WriteQuotaAvailable = fpli.InboundQuota;
        }

Reply via email to