Cygwin: pipe: Switch pipe mode to blocking mode by default

Previously, cygwin read pipe used non-blocking mode although non-
cygwin app uses blocking-mode by default. Despite this requirement,
if a cygwin app is executed from a non-cygwin app and the cygwin
app exits, read pipe remains on non-blocking mode because of the
commit fc691d0246. Due to this behaviour, the non-cygwin app
cannot read the pipe correctly after that. Similarly, if a non-
cygwin app is executed from a cygwin app and the non-cygwin app
exits, the read pipe mode remains on blocking mode although cygwin
read pipe should be non-blocking mode.

These bugs were provoked by pipe mode toggling between cygwin and
non-cygwin apps. To make management of pipe mode simpler, this
patch has re-designed the pipe implementation. In this new
implementation, both read and write pipe basically use only blocking
mode and the behaviour corresponding to the pipe mode is simulated
in raw_read() and raw_write(). Only when NtQueryInformationFile
(FilePipeLocalInformation) fails for some reasons, the raw_read()/
raw_write() cannot simulate non-blocking access. Therefore, the pipe
mode is temporarily changed to non-blocking mode.

Moreover, because the fact that NtSetInformationFile() in
set_pipe_non_blocking(true) fails with STATUS_PIPE_BUSY if the pipe
is not empty has been found, query handle is not necessary anymore.
This allows the implementation much simpler than before.

Addresses: https://github.com/git-for-windows/git/issues/5115
Fixes: fc691d0246 ("Cygwin: pipe: Make sure to set read pipe non-blocking for cygwin apps.");
Reported-by: isaacag, Johannes Schindelin <Johannes.Schindelin@gmx.de>
Reviewed-by: Corinna Vinschen <corinna@vinschen.de>, Ken Brown <kbrown@cornell.edu>
Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp>
This commit is contained in:
Takashi Yano 2024-09-05 11:36:27 +09:00
parent 1f05c04059
commit 7ed9adb356
8 changed files with 260 additions and 520 deletions

View File

@ -410,9 +410,8 @@ dtable::init_std_file_from_handle (int fd, HANDLE handle)
{ {
fhandler_pipe *fhp = (fhandler_pipe *) fh; fhandler_pipe *fhp = (fhandler_pipe *) fh;
fhp->set_pipe_buf_size (); fhp->set_pipe_buf_size ();
/* Set read pipe always to nonblocking */ /* Set pipe always blocking */
fhp->set_pipe_non_blocking (fhp->get_device () == FH_PIPER ? fhp->set_pipe_non_blocking (false);
true : fhp->is_nonblocking ());
} }
if (!fh->open_setup (openflags)) if (!fh->open_setup (openflags))

View File

@ -31,7 +31,7 @@ STATUS_PIPE_EMPTY simply means there's no data to be read. */
|| _s == STATUS_PIPE_EMPTY; }) || _s == STATUS_PIPE_EMPTY; })
fhandler_pipe_fifo::fhandler_pipe_fifo () fhandler_pipe_fifo::fhandler_pipe_fifo ()
: fhandler_base (), pipe_buf_size (DEFAULT_PIPEBUFSIZE) : fhandler_base (), pipe_buf_size (DEFAULT_PIPEBUFSIZE), pipe_mtx (NULL)
{ {
} }
@ -48,7 +48,7 @@ fhandler_pipe::fhandler_pipe ()
In addition to setting the blocking mode of the pipe handle, it In addition to setting the blocking mode of the pipe handle, it
also sets the pipe's read mode to byte_stream unconditionally. */ also sets the pipe's read mode to byte_stream unconditionally. */
void NTSTATUS
fhandler_pipe::set_pipe_non_blocking (bool nonblocking) fhandler_pipe::set_pipe_non_blocking (bool nonblocking)
{ {
NTSTATUS status; NTSTATUS status;
@ -62,6 +62,7 @@ fhandler_pipe::set_pipe_non_blocking (bool nonblocking)
FilePipeInformation); FilePipeInformation);
if (!NT_SUCCESS (status)) if (!NT_SUCCESS (status))
debug_printf ("NtSetInformationFile(FilePipeInformation): %y", status); debug_printf ("NtSetInformationFile(FilePipeInformation): %y", status);
return status;
} }
int int
@ -91,24 +92,10 @@ fhandler_pipe::init (HANDLE f, DWORD a, mode_t mode, int64_t uniq_id)
set_ino (uniq_id); set_ino (uniq_id);
set_unique_id (uniq_id | !!(mode & GENERIC_WRITE)); set_unique_id (uniq_id | !!(mode & GENERIC_WRITE));
if (opened_properly) if (opened_properly)
/* Set read pipe always nonblocking to allow signal handling /* Set pipe always blocking and simulate non-blocking mode in
even with FILE_SYNCHRONOUS_IO_NONALERT. */ raw_read()/raw_write() to allow signal handling even with
set_pipe_non_blocking (get_device () == FH_PIPER ? FILE_SYNCHRONOUS_IO_NONALERT. */
true : is_nonblocking ()); set_pipe_non_blocking (false);
/* Store pipe name to path_conv pc for query_hdl check */
if (get_dev () == FH_PIPEW)
{
UNICODE_STRING name;
WCHAR pipename_buf[MAX_PATH];
__small_swprintf (pipename_buf, L"%S%S-%u-pipe-nt-%p",
&ro_u_npfs, &cygheap->installation_key,
GetCurrentProcessId (), unique_id >> 32);
name.Length = wcslen (pipename_buf) * sizeof (WCHAR);
name.MaximumLength = sizeof (pipename_buf);
name.Buffer = pipename_buf;
pc.set_nt_native_path (&name);
}
return 1; return 1;
} }
@ -211,42 +198,51 @@ out:
return 0; return 0;
} }
extern "C" int swscanf (const wchar_t *, const wchar_t *, ...);
static char *
get_mtx_name (HANDLE h, const char *io, char *name)
{
ULONG len;
NTSTATUS status;
tmp_pathbuf tp;
OBJECT_NAME_INFORMATION *ntfn = (OBJECT_NAME_INFORMATION *) tp.w_get ();
DWORD pid;
LONG uniq_id;
status = NtQueryObject (h, ObjectNameInformation, ntfn, 65536, &len);
if (!NT_SUCCESS (status) || !ntfn->Name.Buffer)
return NULL;
ntfn->Name.Buffer[ntfn->Name.Length / sizeof (WCHAR)] = L'\0';
WCHAR *p = wcschr (ntfn->Name.Buffer, L'-');
if (p == NULL)
return NULL;
if (2 != swscanf (p, L"-%u-pipe-nt-0x%x", &pid, &uniq_id))
return NULL;
__small_sprintf (name, "cygpipe.%s.mutex.%u-%p",
io, pid, (intptr_t) uniq_id);
return name;
}
bool bool
fhandler_pipe::open_setup (int flags) fhandler_pipe::open_setup (int flags)
{ {
bool read_mtx_created = false;
if (!fhandler_base::open_setup (flags)) if (!fhandler_base::open_setup (flags))
goto err; return false;
if (get_dev () == FH_PIPER && !read_mtx) if (!pipe_mtx)
{ {
SECURITY_ATTRIBUTES *sa = sec_none_cloexec (flags); SECURITY_ATTRIBUTES *sa = sec_none_cloexec (flags);
read_mtx = CreateMutex (sa, FALSE, NULL); char name[MAX_PATH];
if (read_mtx) const char *io = get_device () == FH_PIPER ? "input" : "output";
read_mtx_created = true; char *mtx_name = get_mtx_name (get_handle (), io, name);
else pipe_mtx = CreateMutex (sa, FALSE, mtx_name);
if (!pipe_mtx)
{ {
debug_printf ("CreateMutex read_mtx failed: %E"); debug_printf ("CreateMutex pipe_mtx failed: %E");
goto err; return false;
}
}
if (!hdl_cnt_mtx)
{
SECURITY_ATTRIBUTES *sa = sec_none_cloexec (flags);
hdl_cnt_mtx = CreateMutex (sa, FALSE, NULL);
if (!hdl_cnt_mtx)
{
debug_printf ("CreateMutex hdl_cnt_mtx failed: %E");
goto err_close_read_mtx;
} }
} }
return true; return true;
err_close_read_mtx:
if (read_mtx_created)
CloseHandle (read_mtx);
err:
return false;
} }
off_t off_t
@ -279,12 +275,7 @@ fhandler_pipe::get_proc_fd_name (char *buf)
void void
fhandler_pipe::release_select_sem (const char *from) fhandler_pipe::release_select_sem (const char *from)
{ {
LONG n_release; LONG n_release = get_obj_handle_count (select_sem)
if (get_dev () == FH_PIPER) /* Number of select() and writer */
n_release = get_obj_handle_count (select_sem)
- get_obj_handle_count (read_mtx);
else /* Number of select() call and reader */
n_release = get_obj_handle_count (select_sem)
- get_obj_handle_count (get_handle ()); - get_obj_handle_count (get_handle ());
debug_printf("%s(%s) release %d", from, debug_printf("%s(%s) release %d", from,
get_dev () == FH_PIPER ? "PIPER" : "PIPEW", n_release); get_dev () == FH_PIPER ? "PIPER" : "PIPEW", n_release);
@ -305,7 +296,7 @@ fhandler_pipe::raw_read (void *ptr, size_t& len)
return; return;
DWORD timeout = is_nonblocking () ? 0 : INFINITE; DWORD timeout = is_nonblocking () ? 0 : INFINITE;
DWORD waitret = cygwait (read_mtx, timeout); DWORD waitret = cygwait (pipe_mtx, timeout);
switch (waitret) switch (waitret)
{ {
case WAIT_OBJECT_0: case WAIT_OBJECT_0:
@ -332,6 +323,7 @@ fhandler_pipe::raw_read (void *ptr, size_t& len)
ULONG_PTR nbytes_now = 0; ULONG_PTR nbytes_now = 0;
ULONG len1 = (ULONG) (len - nbytes); ULONG len1 = (ULONG) (len - nbytes);
DWORD select_sem_timeout = 0; DWORD select_sem_timeout = 0;
bool real_non_blocking_mode = false;
FILE_PIPE_LOCAL_INFORMATION fpli; FILE_PIPE_LOCAL_INFORMATION fpli;
status = NtQueryInformationFile (get_handle (), &io, status = NtQueryInformationFile (get_handle (), &io,
@ -339,37 +331,11 @@ fhandler_pipe::raw_read (void *ptr, size_t& len)
FilePipeLocalInformation); FilePipeLocalInformation);
if (NT_SUCCESS (status)) if (NT_SUCCESS (status))
{ {
if (fpli.ReadDataAvailable == 0 && nbytes != 0) if (fpli.ReadDataAvailable == 0)
break;
}
else if (nbytes != 0)
break;
status = NtReadFile (get_handle (), NULL, NULL, NULL, &io, ptr,
len1, NULL, NULL);
if (isclosed ()) /* A signal handler might have closed the fd. */
{
set_errno (EBADF);
nbytes = (size_t) -1;
}
else if (NT_SUCCESS (status) || status == STATUS_BUFFER_OVERFLOW)
{
nbytes_now = io.Information;
ptr = ((char *) ptr) + nbytes_now;
nbytes += nbytes_now;
if (select_sem && nbytes_now > 0)
release_select_sem ("raw_read");
}
else
{
/* Some errors are not really errors. Detect such cases here. */
switch (status)
{ {
case STATUS_END_OF_FILE: if (fpli.NamedPipeState == FILE_PIPE_CLOSING_STATE)
case STATUS_PIPE_BROKEN: /* EOF */
/* This is really EOF. */ break;
break;
case STATUS_PIPE_LISTENING:
case STATUS_PIPE_EMPTY:
if (nbytes != 0) if (nbytes != 0)
break; break;
if (is_nonblocking ()) if (is_nonblocking ())
@ -399,6 +365,62 @@ fhandler_pipe::raw_read (void *ptr, size_t& len)
break; break;
} }
continue; continue;
}
}
else if (nbytes != 0)
break;
else if (status == STATUS_END_OF_FILE || status == STATUS_PIPE_BROKEN)
/* EOF */
break;
else if (!NT_SUCCESS (status) && is_nonblocking ())
{
status = set_pipe_non_blocking (true);
if (status == STATUS_END_OF_FILE || status == STATUS_PIPE_BROKEN)
/* EOF */
break;
if (!NT_SUCCESS (status))
{
/* Cannot continue. What should we do? */
set_errno (EIO);
nbytes = (size_t) -1;
break;
}
real_non_blocking_mode = true;
}
status = NtReadFile (get_handle (), NULL, NULL, NULL, &io, ptr,
len1, NULL, NULL);
if (real_non_blocking_mode)
set_pipe_non_blocking (false);
if (isclosed ()) /* A signal handler might have closed the fd. */
{
set_errno (EBADF);
nbytes = (size_t) -1;
}
else if (NT_SUCCESS (status) || status == STATUS_BUFFER_OVERFLOW)
{
nbytes_now = io.Information;
ptr = ((char *) ptr) + nbytes_now;
nbytes += nbytes_now;
if (select_sem && nbytes_now > 0)
release_select_sem ("raw_read");
}
else
{
/* Some errors are not really errors. Detect such cases here. */
switch (status)
{
case STATUS_END_OF_FILE:
case STATUS_PIPE_BROKEN:
/* This is really EOF. */
break;
case STATUS_PIPE_LISTENING:
case STATUS_PIPE_EMPTY:
/* Only for real_non_blocking_mode */
if (nbytes != 0)
break;
set_errno (EAGAIN);
nbytes = (size_t) -1;
break;
default: default:
__seterrno_from_nt_status (status); __seterrno_from_nt_status (status);
nbytes = (size_t) -1; nbytes = (size_t) -1;
@ -410,22 +432,10 @@ fhandler_pipe::raw_read (void *ptr, size_t& len)
|| status == STATUS_BUFFER_OVERFLOW) || status == STATUS_BUFFER_OVERFLOW)
break; break;
} }
ReleaseMutex (read_mtx); ReleaseMutex (pipe_mtx);
len = nbytes; len = nbytes;
} }
bool
fhandler_pipe::reader_closed ()
{
if (!query_hdl)
return false;
WaitForSingleObject (hdl_cnt_mtx, INFINITE);
int n_reader = get_obj_handle_count (query_hdl);
int n_writer = get_obj_handle_count (get_handle ());
ReleaseMutex (hdl_cnt_mtx);
return n_reader == n_writer;
}
ssize_t ssize_t
fhandler_pipe_fifo::raw_write (const void *ptr, size_t len) fhandler_pipe_fifo::raw_write (const void *ptr, size_t len)
{ {
@ -439,24 +449,100 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len)
if (!len) if (!len)
return 0; return 0;
if (reader_closed ()) ssize_t avail = pipe_buf_size;
bool real_non_blocking_mode = false;
if (pipe_mtx) /* pipe_mtx is NULL in the fifo case */
{ {
set_errno (EPIPE); DWORD timeout = is_nonblocking () ? 0 : INFINITE;
raise (SIGPIPE); DWORD waitret = cygwait (pipe_mtx, timeout);
return -1; switch (waitret)
{
case WAIT_OBJECT_0:
break;
case WAIT_TIMEOUT:
set_errno (EAGAIN);
return -1;
case WAIT_SIGNALED:
set_errno (EINTR);
return -1;
case WAIT_CANCELED:
pthread::static_cancel_self ();
/* NOTREACHED */
default:
/* Should not reach here. */
__seterrno ();
return -1;
}
}
if (get_device () == FH_PIPEW && is_nonblocking ())
{
fhandler_pipe *fh = (fhandler_pipe *) this;
FILE_PIPE_LOCAL_INFORMATION fpli;
status = NtQueryInformationFile (get_handle (), &io, &fpli, sizeof fpli,
FilePipeLocalInformation);
if (NT_SUCCESS (status))
{
if (fpli.WriteQuotaAvailable != 0)
avail = fpli.WriteQuotaAvailable;
else /* WriteQuotaAvailable == 0 */
{ /* Refer to the comment in select.cc: pipe_data_available(). */
/* NtSetInformationFile() in set_pipe_non_blocking(true) seems
to fail with STATUS_PIPE_BUSY if the pipe is not empty.
In this case, the pipe is really full if WriteQuotaAvailable
is zero. Otherwise, the pipe is empty. */
status = fh->set_pipe_non_blocking (true);
if (NT_SUCCESS (status))
/* Pipe should be empty because reader is waiting for data. */
/* Restore the pipe mode to blocking. */
fh->set_pipe_non_blocking (false);
else if (status == STATUS_PIPE_BUSY)
{
/* Full */
set_errno (EAGAIN);
goto err;
}
}
}
else
{
/* The pipe space is unknown. */
status = fh->set_pipe_non_blocking (true);
if (NT_SUCCESS (status))
real_non_blocking_mode = true;
else if (status == STATUS_PIPE_BUSY)
{
/* The pipe is not empty and may be full.
It is not safe to write now. */
set_errno (EAGAIN);
goto err;
}
}
if (STATUS_PIPE_IS_CLOSED (status))
{
set_errno (EPIPE);
raise (SIGPIPE);
goto err;
}
else if (!NT_SUCCESS (status))
{
/* Cannot continue. What should we do? */
set_errno (EIO);
goto err;
}
} }
if (len <= pipe_buf_size || pipe_buf_size == 0) if (len <= (size_t) avail || pipe_buf_size == 0)
chunk = len; chunk = len;
else if (is_nonblocking ()) else if (is_nonblocking ())
chunk = len = pipe_buf_size; chunk = len = pipe_buf_size;
else else
chunk = pipe_buf_size; chunk = avail;
if (!(evt = CreateEvent (NULL, false, false, NULL))) if (!(evt = CreateEvent (NULL, false, false, NULL)))
{ {
__seterrno (); __seterrno ();
return -1; goto err;
} }
/* Write in chunks, accumulating a total. If there's an error, just /* Write in chunks, accumulating a total. If there's an error, just
@ -497,27 +583,20 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len)
errno set to [EAGAIN]. */ errno set to [EAGAIN]. */
while (len1 > 0) while (len1 > 0)
{ {
status = NtWriteFile (get_handle (), evt, NULL, NULL, &io, if (is_nonblocking() && !real_non_blocking_mode && len1 > avail)
(PVOID) ptr, len1, NULL, NULL); /* Avoid being blocked in NtWriteFile() */
io.Information = 0;
else
status = NtWriteFile (get_handle (), evt, NULL, NULL, &io,
(PVOID) ptr, len1, NULL, NULL);
if (status == STATUS_PENDING) if (status == STATUS_PENDING)
{ {
do do
{ {
/* To allow constant reader_closed() checking even if the waitret = cygwait (evt, (DWORD) 0);
signal has been set up with SA_RESTART, we're handling
the signal here --> cw_sig_eintr. */
waitret = cygwait (evt, (DWORD) 0, cw_cancel | cw_sig_eintr);
/* Break out if no SA_RESTART. */ /* Break out if no SA_RESTART. */
if (waitret == WAIT_SIGNALED if (waitret == WAIT_SIGNALED)
&& !_my_tls.call_signal_handler ())
break; break;
if (reader_closed ())
{
CancelIo (get_handle ());
set_errno (EPIPE);
raise (SIGPIPE);
goto out;
}
/* Break out on completion */ /* Break out on completion */
if (waitret == WAIT_OBJECT_0) if (waitret == WAIT_OBJECT_0)
break; break;
@ -530,8 +609,7 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len)
} }
cygwait (select_sem, 10, cw_cancel); cygwait (select_sem, 10, cw_cancel);
} }
/* Loop in case of blocking write or SA_RESTART */ while (waitret == WAIT_TIMEOUT);
while (waitret == WAIT_TIMEOUT || waitret == WAIT_SIGNALED);
/* If io.Status is STATUS_CANCELLED after CancelIo, IO has /* If io.Status is STATUS_CANCELLED after CancelIo, IO has
actually been cancelled and io.Information contains the actually been cancelled and io.Information contains the
number of bytes processed so far. number of bytes processed so far.
@ -564,8 +642,7 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len)
strategy because Linux is filling the pages of a pipe buffer strategy because Linux is filling the pages of a pipe buffer
in a very implementation-defined way we can't emulate, but it in a very implementation-defined way we can't emulate, but it
resembles it closely enough to get useful results. */ resembles it closely enough to get useful results. */
ssize_t avail = pipe_data_available (-1, this, get_handle (), avail = pipe_data_available (-1, this, get_handle (), PDA_WRITE);
PDA_WRITE);
if (avail < 1) /* error or pipe closed */ if (avail < 1) /* error or pipe closed */
break; break;
if (avail > len1) /* somebody read from the pipe */ if (avail > len1) /* somebody read from the pipe */
@ -609,55 +686,43 @@ fhandler_pipe_fifo::raw_write (const void *ptr, size_t len)
if (nbytes_now == 0 || short_write_once) if (nbytes_now == 0 || short_write_once)
break; break;
} }
out:
if (real_non_blocking_mode)
((fhandler_pipe *) this)->set_pipe_non_blocking (false);
CloseHandle (evt); CloseHandle (evt);
if (pipe_mtx) /* pipe_mtx is NULL in the fifo case */
ReleaseMutex (pipe_mtx);
if (status == STATUS_THREAD_SIGNALED && nbytes == 0) if (status == STATUS_THREAD_SIGNALED && nbytes == 0)
set_errno (EINTR); set_errno (EINTR);
else if (status == STATUS_THREAD_CANCELED) else if (status == STATUS_THREAD_CANCELED)
pthread::static_cancel_self (); pthread::static_cancel_self ();
return nbytes ?: -1; return nbytes ?: -1;
err:
if (pipe_mtx)
ReleaseMutex (pipe_mtx);
return -1;
} }
void void
fhandler_pipe::set_close_on_exec (bool val) fhandler_pipe::set_close_on_exec (bool val)
{ {
fhandler_base::set_close_on_exec (val); fhandler_base::set_close_on_exec (val);
if (read_mtx) if (pipe_mtx)
set_no_inheritance (read_mtx, val); set_no_inheritance (pipe_mtx, val);
if (select_sem) if (select_sem)
set_no_inheritance (select_sem, val); set_no_inheritance (select_sem, val);
if (query_hdl)
set_no_inheritance (query_hdl, val);
set_no_inheritance (hdl_cnt_mtx, val);
} }
void void
fhandler_pipe::fixup_after_fork (HANDLE parent) fhandler_pipe::fixup_after_fork (HANDLE parent)
{ {
fork_fixup (parent, hdl_cnt_mtx, "hdl_cnt_mtx"); if (pipe_mtx)
WaitForSingleObject (hdl_cnt_mtx, INFINITE); fork_fixup (parent, pipe_mtx, "pipe_mtx");
if (read_mtx)
fork_fixup (parent, read_mtx, "read_mtx");
if (select_sem) if (select_sem)
fork_fixup (parent, select_sem, "select_sem"); fork_fixup (parent, select_sem, "select_sem");
if (query_hdl)
fork_fixup (parent, query_hdl, "query_hdl");
if (query_hdl_close_req_evt)
fork_fixup (parent, query_hdl_close_req_evt, "query_hdl_close_req_evt");
fhandler_base::fixup_after_fork (parent); fhandler_base::fixup_after_fork (parent);
ReleaseMutex (hdl_cnt_mtx);
}
void
fhandler_pipe::fixup_after_exec ()
{
/* Set read pipe itself always non-blocking for cygwin process.
Blocking/non-blocking is simulated in raw_read(). For write
pipe, follow is_nonblocking(). */
bool mode = get_device () == FH_PIPEW ? is_nonblocking () : true;
set_pipe_non_blocking (mode);
fhandler_base::fixup_after_exec ();
} }
int int
@ -667,12 +732,11 @@ fhandler_pipe::dup (fhandler_base *child, int flags)
ftp->set_popen_pid (0); ftp->set_popen_pid (0);
int res = 0; int res = 0;
WaitForSingleObject (hdl_cnt_mtx, INFINITE);
if (fhandler_base::dup (child, flags)) if (fhandler_base::dup (child, flags))
res = -1; res = -1;
else if (read_mtx && else if (pipe_mtx &&
!DuplicateHandle (GetCurrentProcess (), read_mtx, !DuplicateHandle (GetCurrentProcess (), pipe_mtx,
GetCurrentProcess (), &ftp->read_mtx, GetCurrentProcess (), &ftp->pipe_mtx,
0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS))
{ {
__seterrno (); __seterrno ();
@ -688,34 +752,6 @@ fhandler_pipe::dup (fhandler_base *child, int flags)
ftp->close (); ftp->close ();
res = -1; res = -1;
} }
else if (query_hdl &&
!DuplicateHandle (GetCurrentProcess (), query_hdl,
GetCurrentProcess (), &ftp->query_hdl,
0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS))
{
__seterrno ();
ftp->close ();
res = -1;
}
else if (!DuplicateHandle (GetCurrentProcess (), hdl_cnt_mtx,
GetCurrentProcess (), &ftp->hdl_cnt_mtx,
0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS))
{
__seterrno ();
ftp->close ();
res = -1;
}
else if (query_hdl_close_req_evt &&
!DuplicateHandle (GetCurrentProcess (), query_hdl_close_req_evt,
GetCurrentProcess (),
&ftp->query_hdl_close_req_evt,
0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS))
{
__seterrno ();
ftp->close ();
res = -1;
}
ReleaseMutex (hdl_cnt_mtx);
debug_printf ("res %d", res); debug_printf ("res %d", res);
return res; return res;
@ -729,18 +765,9 @@ fhandler_pipe::close ()
release_select_sem ("close"); release_select_sem ("close");
CloseHandle (select_sem); CloseHandle (select_sem);
} }
if (read_mtx) if (pipe_mtx)
CloseHandle (read_mtx); CloseHandle (pipe_mtx);
WaitForSingleObject (hdl_cnt_mtx, INFINITE);
if (query_hdl)
CloseHandle (query_hdl);
if (query_hdl_close_req_evt)
CloseHandle (query_hdl_close_req_evt);
int ret = fhandler_base::close (); int ret = fhandler_base::close ();
ReleaseMutex (hdl_cnt_mtx);
CloseHandle (hdl_cnt_mtx);
if (query_hdl_proc)
CloseHandle (query_hdl_proc);
return ret; return ret;
} }
@ -925,7 +952,7 @@ fhandler_pipe::create (fhandler_pipe *fhs[2], unsigned psize, int mode)
HANDLE r, w; HANDLE r, w;
SECURITY_ATTRIBUTES *sa = sec_none_cloexec (mode); SECURITY_ATTRIBUTES *sa = sec_none_cloexec (mode);
int res = -1; int res = -1;
int64_t unique_id; int64_t unique_id = 0; /* Compiler complains if not initialized... */
int ret = nt_create (sa, r, w, psize, &unique_id); int ret = nt_create (sa, r, w, psize, &unique_id);
if (ret) if (ret)
@ -941,57 +968,33 @@ fhandler_pipe::create (fhandler_pipe *fhs[2], unsigned psize, int mode)
fhs[0]->init (r, FILE_CREATE_PIPE_INSTANCE | GENERIC_READ, mode, unique_id); fhs[0]->init (r, FILE_CREATE_PIPE_INSTANCE | GENERIC_READ, mode, unique_id);
fhs[1]->init (w, FILE_CREATE_PIPE_INSTANCE | GENERIC_WRITE, mode, unique_id); fhs[1]->init (w, FILE_CREATE_PIPE_INSTANCE | GENERIC_WRITE, mode, unique_id);
/* For the read side of the pipe, add a mutex. See raw_read for the char name[MAX_PATH], *mtx_name;
usage. */ mtx_name = get_mtx_name (fhs[0]->get_handle (), "input", name);
fhs[0]->read_mtx = CreateMutexW (sa, FALSE, NULL); fhs[0]->pipe_mtx = CreateMutex (sa, FALSE, mtx_name);
if (!fhs[0]->read_mtx) if (!fhs[0]->pipe_mtx)
goto err_delete_fhs1; goto err_delete_fhs1;
mtx_name = get_mtx_name (fhs[1]->get_handle (), "output", name);
fhs[1]->pipe_mtx = CreateMutex (sa, FALSE, mtx_name);
if (!fhs[1]->pipe_mtx)
goto err_close_pipe_mtx0;
fhs[0]->select_sem = CreateSemaphore (sa, 0, INT32_MAX, NULL); fhs[0]->select_sem = CreateSemaphore (sa, 0, INT32_MAX, NULL);
if (!fhs[0]->select_sem) if (!fhs[0]->select_sem)
goto err_close_read_mtx; goto err_close_pipe_mtx1;
if (!DuplicateHandle (GetCurrentProcess (), fhs[0]->select_sem, if (!DuplicateHandle (GetCurrentProcess (), fhs[0]->select_sem,
GetCurrentProcess (), &fhs[1]->select_sem, GetCurrentProcess (), &fhs[1]->select_sem,
0, sa->bInheritHandle, DUPLICATE_SAME_ACCESS)) 0, sa->bInheritHandle, DUPLICATE_SAME_ACCESS))
goto err_close_select_sem0; goto err_close_select_sem0;
if (is_running_as_service () &&
!DuplicateHandle (GetCurrentProcess (), r,
GetCurrentProcess (), &fhs[1]->query_hdl,
FILE_READ_DATA, sa->bInheritHandle, 0))
goto err_close_select_sem1;
fhs[0]->hdl_cnt_mtx = CreateMutexW (sa, FALSE, NULL);
if (!fhs[0]->hdl_cnt_mtx)
goto err_close_query_hdl;
if (!DuplicateHandle (GetCurrentProcess (), fhs[0]->hdl_cnt_mtx,
GetCurrentProcess (), &fhs[1]->hdl_cnt_mtx,
0, sa->bInheritHandle, DUPLICATE_SAME_ACCESS))
goto err_close_hdl_cnt_mtx0;
if (fhs[1]->query_hdl)
{
fhs[1]->query_hdl_close_req_evt = CreateEvent (sa, TRUE, FALSE, NULL);
if (!fhs[1]->query_hdl_close_req_evt)
goto err_close_hdl_cnt_mtx1;
}
res = 0; res = 0;
goto out; goto out;
err_close_hdl_cnt_mtx1:
CloseHandle (fhs[1]->hdl_cnt_mtx);
err_close_hdl_cnt_mtx0:
CloseHandle (fhs[0]->hdl_cnt_mtx);
err_close_query_hdl:
if (fhs[1]->query_hdl)
CloseHandle (fhs[1]->query_hdl);
err_close_select_sem1:
CloseHandle (fhs[1]->select_sem);
err_close_select_sem0: err_close_select_sem0:
CloseHandle (fhs[0]->select_sem); CloseHandle (fhs[0]->select_sem);
err_close_read_mtx: err_close_pipe_mtx1:
CloseHandle (fhs[0]->read_mtx); CloseHandle (fhs[1]->pipe_mtx);
err_close_pipe_mtx0:
CloseHandle (fhs[0]->pipe_mtx);
err_delete_fhs1: err_delete_fhs1:
delete fhs[1]; delete fhs[1];
err_delete_fhs0: err_delete_fhs0:
@ -1038,7 +1041,6 @@ nt_create (LPSECURITY_ATTRIBUTES sa_ptr, HANDLE &r, HANDLE &w,
GetCurrentProcessId ()); GetCurrentProcessId ());
access = GENERIC_READ | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE; access = GENERIC_READ | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE;
access |= FILE_WRITE_EA; /* Add this right as a marker of cygwin read pipe */
ULONG pipe_type = pipe_byte ? FILE_PIPE_BYTE_STREAM_TYPE ULONG pipe_type = pipe_byte ? FILE_PIPE_BYTE_STREAM_TYPE
: FILE_PIPE_MESSAGE_TYPE; : FILE_PIPE_MESSAGE_TYPE;
@ -1174,22 +1176,6 @@ fhandler_pipe::ioctl (unsigned int cmd, void *p)
return 0; return 0;
} }
int
fhandler_pipe::fcntl (int cmd, intptr_t arg)
{
if (cmd != F_SETFL)
return fhandler_base::fcntl (cmd, arg);
const bool was_nonblocking = is_nonblocking ();
int res = fhandler_base::fcntl (cmd, arg);
const bool now_nonblocking = is_nonblocking ();
/* Do not set blocking mode for read pipe to allow signal handling
even with FILE_SYNCHRONOUS_IO_NONALERT. */
if (now_nonblocking != was_nonblocking && get_device () != FH_PIPER)
set_pipe_non_blocking (now_nonblocking);
return res;
}
int int
fhandler_pipe::fstat (struct stat *buf) fhandler_pipe::fstat (struct stat *buf)
{ {
@ -1210,190 +1196,3 @@ fhandler_pipe::fstatvfs (struct statvfs *sfs)
set_errno (EBADF); set_errno (EBADF);
return -1; return -1;
} }
HANDLE
fhandler_pipe::temporary_query_hdl ()
{
if (get_dev () != FH_PIPEW)
return NULL;
ULONG len;
NTSTATUS status;
tmp_pathbuf tp;
OBJECT_NAME_INFORMATION *ntfn = (OBJECT_NAME_INFORMATION *) tp.w_get ();
UNICODE_STRING *name = pc.get_nt_native_path (NULL);
/* Try process handle opened and pipe handle value cached first
in order to reduce overhead. */
if (query_hdl_proc && query_hdl_value)
{
HANDLE h;
if (!DuplicateHandle (query_hdl_proc, query_hdl_value,
GetCurrentProcess (), &h, FILE_READ_DATA, 0, 0))
goto cache_err;
/* Check name */
status = NtQueryObject (h, ObjectNameInformation, ntfn, 65536, &len);
if (!NT_SUCCESS (status) || !ntfn->Name.Buffer)
goto hdl_err;
if (RtlEqualUnicodeString (name, &ntfn->Name, FALSE))
return h;
hdl_err:
CloseHandle (h);
cache_err:
CloseHandle (query_hdl_proc);
query_hdl_proc = NULL;
query_hdl_value = NULL;
}
if (name->Length == 0 || name->Buffer == NULL)
return NULL; /* Non cygwin pipe? */
return get_query_hdl_per_process (ntfn); /* Since Win8 */
}
HANDLE
fhandler_pipe::get_query_hdl_per_process (OBJECT_NAME_INFORMATION *ntfn)
{
winpids pids ((DWORD) 0);
/* In most cases, it is faster to check the processes in reverse order. */
for (LONG i = (LONG) pids.npids - 1; i >= 0; i--)
{
NTSTATUS status;
ULONG len;
/* Non-cygwin app may call ReadFile() for empty pipe, which makes
NtQueryObject() for ObjectNameInformation block. Therefore, do
not try to get query_hdl for non-cygwin apps. */
_pinfo *p = pids[i];
if (!p || ISSTATE (p, PID_NOTCYGWIN))
continue;
HANDLE proc = OpenProcess (PROCESS_DUP_HANDLE
| PROCESS_QUERY_INFORMATION,
0, p->dwProcessId);
if (!proc)
continue;
/* Retrieve process handles */
DWORD n_handle = 256;
PPROCESS_HANDLE_SNAPSHOT_INFORMATION phi;
do
{
DWORD nbytes = 2 * sizeof (ULONG_PTR) +
n_handle * sizeof (PROCESS_HANDLE_TABLE_ENTRY_INFO);
phi = (PPROCESS_HANDLE_SNAPSHOT_INFORMATION)
HeapAlloc (GetProcessHeap (), 0, nbytes);
if (!phi)
goto close_proc;
/* NtQueryInformationProcess can return STATUS_SUCCESS with
invalid handle data for certain processes. See
https://github.com/processhacker/processhacker/blob/05f5e9fa477dcaa1709d9518170d18e1b3b8330d/phlib/native.c#L5754.
We need to ensure that NumberOfHandles is zero in this
case to avoid a crash in the for loop below. */
phi->NumberOfHandles = 0;
status = NtQueryInformationProcess (proc, ProcessHandleInformation,
phi, nbytes, &len);
if (NT_SUCCESS (status))
break;
HeapFree (GetProcessHeap (), 0, phi);
n_handle *= 2;
}
while (n_handle < (1L<<20) && status == STATUS_INFO_LENGTH_MISMATCH);
if (!NT_SUCCESS (status))
goto close_proc;
/* Sanity check in case Microsoft changes
NtQueryInformationProcess and the initialization of
NumberOfHandles above is no longer sufficient. */
assert (phi->NumberOfHandles <= n_handle);
for (ULONG j = 0; j < phi->NumberOfHandles; j++)
{
/* Check for the peculiarity of cygwin read pipe */
const ULONG access = FILE_READ_DATA | FILE_READ_EA
| FILE_WRITE_EA /* marker */
| FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES
| READ_CONTROL | SYNCHRONIZE;
if (phi->Handles[j].GrantedAccess != access)
continue;
/* Retrieve handle */
HANDLE h = (HANDLE)(intptr_t) phi->Handles[j].HandleValue;
BOOL res = DuplicateHandle (proc, h, GetCurrentProcess (), &h,
FILE_READ_DATA, 0, 0);
if (!res)
continue;
/* Check object name */
status = NtQueryObject (h, ObjectNameInformation,
ntfn, 65536, &len);
if (!NT_SUCCESS (status) || !ntfn->Name.Buffer)
goto close_handle;
if (RtlEqualUnicodeString (pc.get_nt_native_path (),
&ntfn->Name, FALSE))
{
query_hdl_proc = proc;
query_hdl_value = (HANDLE)(intptr_t) phi->Handles[j].HandleValue;
HeapFree (GetProcessHeap (), 0, phi);
return h;
}
close_handle:
CloseHandle (h);
}
HeapFree (GetProcessHeap (), 0, phi);
close_proc:
CloseHandle (proc);
}
return NULL;
}
void
fhandler_pipe::spawn_worker (int fileno_stdin, int fileno_stdout,
int fileno_stderr)
{
bool need_send_noncygchld_sig = false;
/* spawn_worker() is called from spawn.cc only when non-cygwin app
is started. Set pipe mode blocking for the non-cygwin process. */
int fd;
cygheap_fdenum cfd (false);
while ((fd = cfd.next ()) >= 0)
if (cfd->get_dev () == FH_PIPEW
&& (fd == fileno_stdout || fd == fileno_stderr))
{
fhandler_pipe *pipe = (fhandler_pipe *)(fhandler_base *) cfd;
pipe->set_pipe_non_blocking (false);
/* Setup for query_ndl stuff. Read the comment below. */
if (pipe->request_close_query_hdl ())
need_send_noncygchld_sig = true;
}
else if (cfd->get_dev () == FH_PIPER && fd == fileno_stdin)
{
fhandler_pipe *pipe = (fhandler_pipe *)(fhandler_base *) cfd;
pipe->set_pipe_non_blocking (false);
}
/* If multiple writers including non-cygwin app exist, the non-cygwin
app cannot detect pipe closure on the read side when the pipe is
created by system account or the pipe creator is running as service.
This is because query_hdl which is held in write side also is a read
end of the pipe, so the pipe is still alive for the non-cygwin app
even after the reader is closed.
To avoid this problem, let all processes in the same process
group close query_hdl using internal signal __SIGNONCYGCHLD when
non-cygwin app is started. */
if (need_send_noncygchld_sig)
{
tty_min dummy_tty;
dummy_tty.ntty = (fh_devices) myself->ctty;
dummy_tty.pgid = myself->pgid;
tty_min *t = cygwin_shared->tty.get_cttyp ();
if (!t) /* If tty is not allocated, use dummy_tty instead. */
t = &dummy_tty;
/* Emit __SIGNONCYGCHLD to let all processes in the
process group close query_hdl. */
t->kill_pgrp (__SIGNONCYGCHLD);
}
}

View File

@ -1197,6 +1197,7 @@ class fhandler_pipe_fifo: public fhandler_base
{ {
protected: protected:
size_t pipe_buf_size; size_t pipe_buf_size;
HANDLE pipe_mtx; /* Used only in the pipe case */
virtual void release_select_sem (const char *) {}; virtual void release_select_sem (const char *) {};
public: public:
@ -1209,15 +1210,8 @@ class fhandler_pipe_fifo: public fhandler_base
class fhandler_pipe: public fhandler_pipe_fifo class fhandler_pipe: public fhandler_pipe_fifo
{ {
private: private:
HANDLE read_mtx;
pid_t popen_pid; pid_t popen_pid;
HANDLE query_hdl;
HANDLE hdl_cnt_mtx;
HANDLE query_hdl_proc;
HANDLE query_hdl_value;
HANDLE query_hdl_close_req_evt;
void release_select_sem (const char *); void release_select_sem (const char *);
HANDLE get_query_hdl_per_process (OBJECT_NAME_INFORMATION *);
public: public:
fhandler_pipe (); fhandler_pipe ();
@ -1234,13 +1228,11 @@ public:
int open (int flags, mode_t mode = 0); int open (int flags, mode_t mode = 0);
bool open_setup (int flags); bool open_setup (int flags);
void fixup_after_fork (HANDLE); void fixup_after_fork (HANDLE);
void fixup_after_exec ();
int dup (fhandler_base *child, int); int dup (fhandler_base *child, int);
void set_close_on_exec (bool val); void set_close_on_exec (bool val);
int close (); int close ();
void raw_read (void *ptr, size_t& len); void raw_read (void *ptr, size_t& len);
int ioctl (unsigned int cmd, void *); int ioctl (unsigned int cmd, void *);
int fcntl (int cmd, intptr_t);
int fstat (struct stat *buf); int fstat (struct stat *buf);
int fstatvfs (struct statvfs *buf); int fstatvfs (struct statvfs *buf);
int fadvise (off_t, off_t, int); int fadvise (off_t, off_t, int);
@ -1265,39 +1257,7 @@ public:
fh->copy_from (this); fh->copy_from (this);
return fh; return fh;
} }
void set_pipe_non_blocking (bool nonblocking); NTSTATUS set_pipe_non_blocking (bool nonblocking);
HANDLE get_query_handle () const { return query_hdl; }
void close_query_handle ()
{
if (query_hdl)
{
CloseHandle (query_hdl);
query_hdl = NULL;
}
if (query_hdl_close_req_evt)
{
CloseHandle (query_hdl_close_req_evt);
query_hdl_close_req_evt = NULL;
}
}
bool reader_closed ();
HANDLE temporary_query_hdl ();
bool need_close_query_hdl ()
{
return query_hdl_close_req_evt ?
IsEventSignalled (query_hdl_close_req_evt) : false;
}
bool request_close_query_hdl ()
{
if (query_hdl_close_req_evt)
{
SetEvent (query_hdl_close_req_evt);
return true;
}
return false;
}
static void spawn_worker (int fileno_stdin, int fileno_stdout,
int fileno_stderr);
}; };
#define CYGWIN_FIFO_PIPE_NAME_LEN 47 #define CYGWIN_FIFO_PIPE_NAME_LEN 47

View File

@ -24,7 +24,6 @@ enum
__SIGSETPGRP = -(_NSIG + 9), __SIGSETPGRP = -(_NSIG + 9),
__SIGTHREADEXIT = -(_NSIG + 10), __SIGTHREADEXIT = -(_NSIG + 10),
__SIGPENDINGALL = -(_NSIG + 11), __SIGPENDINGALL = -(_NSIG + 11),
__SIGNONCYGCHLD = -(_NSIG + 12),
}; };
#endif #endif

View File

@ -34,3 +34,6 @@ What changed:
- Expose //tsclient (Microsoft Terminal Services) shares as well as - Expose //tsclient (Microsoft Terminal Services) shares as well as
//wsl$ (Plan 9 Network Provider) shares, i. e., WSL installation //wsl$ (Plan 9 Network Provider) shares, i. e., WSL installation
root dirs. root dirs.
- Redesign pipe handling to minimize toggling blocking mode.
The query_hdl stuff is no longer needed in new implementation.

View File

@ -639,35 +639,29 @@ pipe_data_available (int fd, fhandler_base *fh, HANDLE h, int flags)
on the writer side assumes that no space is available in the read on the writer side assumes that no space is available in the read
side inbound buffer. side inbound buffer.
Consequentially, the only reliable information is available on the Consequentially, there are two possibilities when WriteQuotaAvailable
read side, so fetch info from the read side via the pipe-specific is 0. One is that the buffer is really full. The other is that the
query handle. Use fpli.WriteQuotaAvailable as storage for the actual reader is currently trying to read the pipe and it is pending.
interesting value, which is the InboundQuote on the write side, In the latter case, the fact that the reader cannot read the data
decremented by the number of bytes of data in that buffer. */ immediately means that the pipe is empty. In the former case,
/* Note: Do not use NtQueryInformationFile() for query_hdl because NtSetInformationFile() in set_pipe_non_blocking(true) will fail
NtQueryInformationFile() seems to interfere with reading pipes with STATUS_PIPE_BUSY, while it succeeds in the latter case.
in non-cygwin apps. Instead, use PeekNamedPipe() here. */ Therefore, we can distinguish these cases by calling set_pipe_non_
/* Note 2: we return the number of available bytes. Select for writing blocking(true). If it returns success, the pipe is empty, so we
returns writable *only* if at least PIPE_BUF bytes are left in the return the pipe buffer size. Otherwise, we return 0. */
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) if (fh->get_device () == FH_PIPEW && fpli.WriteQuotaAvailable == 0)
{ {
HANDLE query_hdl = ((fhandler_pipe *) fh)->get_query_handle (); NTSTATUS status =
if (!query_hdl) ((fhandler_pipe *) fh)->set_pipe_non_blocking (true);
query_hdl = ((fhandler_pipe *) fh)->temporary_query_hdl (); if (status == STATUS_PIPE_BUSY)
if (!query_hdl) /* We cannot know actual write pipe space. */ return 0; /* Full */
else if (!NT_SUCCESS (status))
/* We cannot know actual write pipe space. */
return (flags & PDA_SELECT) ? PIPE_BUF : 1; return (flags & PDA_SELECT) ? PIPE_BUF : 1;
DWORD nbytes_in_pipe; /* Restore pipe mode to blocking mode */
BOOL res = ((fhandler_pipe *) fh)->set_pipe_non_blocking (false);
PeekNamedPipe (query_hdl, NULL, 0, NULL, &nbytes_in_pipe, NULL); /* Empty */
if (!((fhandler_pipe *) fh)->get_query_handle ()) fpli.WriteQuotaAvailable = fpli.InboundQuota;
CloseHandle (query_hdl); /* Close temporary query_hdl */
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) if (fpli.WriteQuotaAvailable > 0)
{ {

View File

@ -1474,16 +1474,6 @@ wait_sig (VOID *)
clearwait = true; clearwait = true;
} }
break; break;
case __SIGNONCYGCHLD:
cygheap_fdenum cfd (false);
while (cfd.next () >= 0)
if (cfd->get_dev () == FH_PIPEW)
{
fhandler_pipe *pipe = (fhandler_pipe *)(fhandler_base *) cfd;
if (pipe->need_close_query_hdl ())
pipe->close_query_handle ();
}
break;
} }
if (clearwait && !have_execed) if (clearwait && !have_execed)
proc_subproc (PROC_CLEARWAIT, 0); proc_subproc (PROC_CLEARWAIT, 0);

View File

@ -579,10 +579,6 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv,
int fileno_stdout = in__stdout < 0 ? 1 : in__stdout; int fileno_stdout = in__stdout < 0 ? 1 : in__stdout;
int fileno_stderr = 2; int fileno_stderr = 2;
if (!iscygwin ())
fhandler_pipe::spawn_worker (fileno_stdin, fileno_stdout,
fileno_stderr);
bool no_pcon = mode != _P_OVERLAY && mode != _P_WAIT; bool no_pcon = mode != _P_OVERLAY && mode != _P_WAIT;
term_spawn_worker.setup (iscygwin (), handle (fileno_stdin, false), term_spawn_worker.setup (iscygwin (), handle (fileno_stdin, false),
runpath, no_pcon, reset_sendsig, envblock); runpath, no_pcon, reset_sendsig, envblock);