From b334b22f821b8ed5205ee663c7e14de3f622fe10 Mon Sep 17 00:00:00 2001 From: cgf Date: Fri, 3 Sep 2004 01:32:02 +0000 Subject: [PATCH] Christopher Faylor * autoload.cc (NtQueryInformationFile): Return nonzero on error. * ntdll.h (FILE_PIPE_LOCAL_INFORMATION): Add. (NtQueryInformationFile): Fix types for last two arguments. * pipe.cc: Include stdlib.h, limits.h, and ntdll.h. (create_selectable_pipe): New function to create a pipe that can be used with NtQueryInformationFile for select. (fhandler_pipe::create): Call create_selectable_pipe instead of CreatePipe. (pipe): Use DEFAULT_PIPEBUFSIZE as argument to create_pipe. * select.cc: Include limits.h and ntdll.h. (peek_pipe): Add select_printf output. Call NtQueryInformationFile to implement select for write on pipes. (fhandler_pipe::select_read): Reorder field assignments to be consistent with fhandler_pipe::select_write. (fhandler_pipe::select_write): Initialize startup, verify, cleanup, and write_ready fields for select_record. (fhandler_pipe::select_except): Tweak indentation to be consistent with fhandler_pipe::select_write. --- winsup/cygwin/ChangeLog | 22 ++++++++ winsup/cygwin/autoload.cc | 2 +- winsup/cygwin/ntdll.h | 17 +++++- winsup/cygwin/pipe.cc | 138 ++++++++++++++++++++++++++++++++++++++++++++-- winsup/cygwin/select.cc | 92 ++++++++++++++++++++++++++----- 5 files changed, 250 insertions(+), 21 deletions(-) diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index 178ffc68f5..af0c5f11df 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,25 @@ +2004-09-02 Bob Byrnes + Christopher Faylor + + * autoload.cc (NtQueryInformationFile): Return nonzero on error. + * ntdll.h (FILE_PIPE_LOCAL_INFORMATION): Add. + (NtQueryInformationFile): Fix types for last two arguments. + * pipe.cc: Include stdlib.h, limits.h, and ntdll.h. + (create_selectable_pipe): New function to create a pipe that can be + used with NtQueryInformationFile for select. + (fhandler_pipe::create): Call create_selectable_pipe instead of + CreatePipe. + (pipe): Use DEFAULT_PIPEBUFSIZE as argument to create_pipe. + * select.cc: Include limits.h and ntdll.h. + (peek_pipe): Add select_printf output. Call NtQueryInformationFile to + implement select for write on pipes. + (fhandler_pipe::select_read): Reorder field assignments to be + consistent with fhandler_pipe::select_write. + (fhandler_pipe::select_write): Initialize startup, verify, cleanup, and + write_ready fields for select_record. + (fhandler_pipe::select_except): Tweak indentation to be consistent with + fhandler_pipe::select_write. + 2004-08-30 Pierre Humblet * fork.cc (fork_parent): Return the cygpid directly derived from the diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc index 7b1a5699dc..e7de842259 100644 --- a/winsup/cygwin/autoload.cc +++ b/winsup/cygwin/autoload.cc @@ -379,7 +379,7 @@ LoadDLLfuncEx (NtCreateToken, 52, ntdll, 1) LoadDLLfuncEx (NtMapViewOfSection, 40, ntdll, 1) LoadDLLfuncEx (NtOpenFile, 24, ntdll, 1) LoadDLLfuncEx (NtOpenSection, 12, ntdll, 1) -LoadDLLfuncEx (NtQueryInformationFile, 20, ntdll, 1) +LoadDLLfuncEx2 (NtQueryInformationFile, 20, ntdll, 1, 1) LoadDLLfuncEx (NtQueryInformationProcess, 20, ntdll, 1) LoadDLLfuncEx2 (NtQueryObject, 20, ntdll, 1, 1) LoadDLLfuncEx (NtQuerySystemInformation, 16, ntdll, 1) diff --git a/winsup/cygwin/ntdll.h b/winsup/cygwin/ntdll.h index 8e829f5edc..23ce468cd4 100644 --- a/winsup/cygwin/ntdll.h +++ b/winsup/cygwin/ntdll.h @@ -358,6 +358,20 @@ typedef struct _FILE_NAME_INFORMATION WCHAR FileName[MAX_PATH + 100]; } FILE_NAME_INFORMATION; +typedef struct _FILE_PIPE_LOCAL_INFORMATION +{ + ULONG NamedPipeType; + ULONG NamedPipeConfiguration; + ULONG MaximumInstances; + ULONG CurrentInstances; + ULONG InboundQuota; + ULONG ReadDataAvailable; + ULONG OutboundQuota; + ULONG WriteQuotaAvailable; + ULONG NamedPipeState; + ULONG NamedPipeEnd; +} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION; + typedef struct _FILE_COMPRESSION_INFORMATION { LARGE_INTEGER CompressedSize; @@ -369,6 +383,7 @@ typedef struct _FILE_COMPRESSION_INFORMATION typedef enum _FILE_INFORMATION_CLASS { + FilePipeLocalInformation = 24, FileCompressionInformation = 28 } FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; @@ -404,7 +419,7 @@ extern "C" PIO_STATUS_BLOCK, ULONG, ULONG); NTSTATUS NTAPI NtOpenSection (PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES); NTSTATUS NTAPI NtQueryInformationFile (HANDLE, IO_STATUS_BLOCK *, VOID *, - DWORD, DWORD); + ULONG, FILE_INFORMATION_CLASS); NTSTATUS NTAPI NtQueryInformationProcess (HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); NTSTATUS NTAPI NtQueryObject (HANDLE, OBJECT_INFORMATION_CLASS, VOID *, diff --git a/winsup/cygwin/pipe.cc b/winsup/cygwin/pipe.cc index 19fdba01fe..2a0095f3cf 100644 --- a/winsup/cygwin/pipe.cc +++ b/winsup/cygwin/pipe.cc @@ -12,7 +12,9 @@ details. */ #include "winsup.h" #include +#include #include +#include #include "cygerrno.h" #include "security.h" #include "path.h" @@ -22,6 +24,7 @@ details. */ #include "thread.h" #include "pinfo.h" #include "cygthread.h" +#include "ntdll.h" static unsigned pipecount; static const NO_COPY char pipeid_fmt[] = "stupid_pipe.%u.%u"; @@ -211,15 +214,139 @@ leave: return res; } +/* Create a pipe, and return handles to the read and write ends, + just like CreatePipe, but ensure that the write end permits + FILE_READ_ATTRIBUTES access, on later versions of win32 where + this is supported. This access is needed by NtQueryInformationFile, + which is used to implement select and nonblocking writes. + Note that the return value is either NO_ERROR or GetLastError, + unlike CreatePipe, which returns a bool for success or failure. */ +static int +create_selectable_pipe (PHANDLE read_pipe_ptr, + PHANDLE write_pipe_ptr, + LPSECURITY_ATTRIBUTES sa_ptr, + DWORD psize) +{ + /* Default to error. */ + *read_pipe_ptr = *write_pipe_ptr = INVALID_HANDLE_VALUE; + + HANDLE read_pipe = INVALID_HANDLE_VALUE, write_pipe = INVALID_HANDLE_VALUE; + + /* Ensure that there is enough pipe buffer space for atomic writes. */ + if (psize < PIPE_BUF) + psize = PIPE_BUF; + + char pipename[CYG_MAX_PATH]; + + /* Retry CreateNamedPipe as long as the pipe name is in use. + Retrying will probably never be necessary, but we want + to be as robust as possible. */ + while (1) + { + static volatile LONG pipe_unique_id; + + __small_sprintf (pipename, "\\\\.\\pipe\\cygwin-%d-%ld", myself->pid, + InterlockedIncrement ((LONG *) &pipe_unique_id)); + + debug_printf ("CreateNamedPipe: name %s, size %lu", pipename, psize); + + /* Use CreateNamedPipe instead of CreatePipe, because the latter + returns a write handle that does not permit FILE_READ_ATTRIBUTES + access, on versions of win32 earlier than WinXP SP2. + CreatePipe also stupidly creates a full duplex pipe, which is + a waste, since only a single direction is actually used. + It's important to only allow a single instance, to ensure that + the pipe was not created earlier by some other process, even if + the pid has been reused. We avoid FILE_FLAG_FIRST_PIPE_INSTANCE + because that is only available for Win2k SP2 and WinXP. */ + read_pipe = CreateNamedPipe (pipename, + PIPE_ACCESS_INBOUND, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, /* max instances */ + psize, /* output buffer size */ + psize, /* input buffer size */ + NMPWAIT_USE_DEFAULT_WAIT, + sa_ptr); + + if (read_pipe != INVALID_HANDLE_VALUE) + { + debug_printf ("pipe read handle %p", read_pipe); + break; + } + + DWORD err = GetLastError (); + switch (err) + { + case ERROR_PIPE_BUSY: + /* The pipe is already open with compatible parameters. + Pick a new name and retry. */ + debug_printf ("pipe busy, retrying"); + continue; + case ERROR_ACCESS_DENIED: + /* The pipe is already open with incompatible parameters. + Pick a new name and retry. */ + debug_printf ("pipe access denied, retrying"); + continue; + case ERROR_CALL_NOT_IMPLEMENTED: + /* We are on an older Win9x platform without named pipes. + Return an anonymous pipe as the best approximation. */ + debug_printf ("CreateNamedPipe not implemented, resorting to " + "CreatePipe size %lu", psize); + if (CreatePipe (read_pipe_ptr, write_pipe_ptr, sa_ptr, psize)) + { + debug_printf ("pipe read handle %p", *read_pipe_ptr); + debug_printf ("pipe write handle %p", *write_pipe_ptr); + return NO_ERROR; + } + err = GetLastError (); + debug_printf ("CreatePipe failed, %E"); + return err; + default: + debug_printf ("CreateNamedPipe failed, %E"); + return err; + } + /* NOTREACHED */ + } + + debug_printf ("CreateFile: name %s", pipename); + + /* Open the named pipe for writing. + Be sure to permit FILE_READ_ATTRIBUTES access. */ + write_pipe = CreateFile (pipename, + GENERIC_WRITE | FILE_READ_ATTRIBUTES, + 0, /* share mode */ + sa_ptr, + OPEN_EXISTING, + 0, /* flags and attributes */ + 0); /* handle to template file */ + + if (write_pipe == INVALID_HANDLE_VALUE) + { + /* Failure. */ + DWORD err = GetLastError (); + debug_printf ("CreateFile failed, %E"); + CloseHandle (read_pipe); + return err; + } + + debug_printf ("pipe write handle %p", write_pipe); + + /* Success. */ + *read_pipe_ptr = read_pipe; + *write_pipe_ptr = write_pipe; + return NO_ERROR; +} + int fhandler_pipe::create (fhandler_pipe *fhs[2], unsigned psize, int mode, bool fifo) { HANDLE r, w; SECURITY_ATTRIBUTES *sa = (mode & O_NOINHERIT) ? &sec_none_nih : &sec_none; int res = -1; + int ret; - if (!CreatePipe (&r, &w, sa, psize)) - __seterrno (); + if ((ret = create_selectable_pipe (&r, &w, sa, psize)) != NO_ERROR) + __seterrno_from_win_error (ret); else { fhs[0] = (fhandler_pipe *) build_fh_dev (*piper_dev); @@ -282,13 +409,16 @@ fhandler_pipe::ioctl (unsigned int cmd, void *p) return 0; } +#define DEFAULT_PIPEBUFSIZE (4 * PIPE_BUF) + extern "C" int pipe (int filedes[2]) { extern DWORD binmode; fhandler_pipe *fhs[2]; - int res = fhandler_pipe::create (fhs, 16384, (!binmode || binmode == O_BINARY) - ? O_BINARY : O_TEXT); + int res = fhandler_pipe::create (fhs, DEFAULT_PIPEBUFSIZE, + (!binmode || binmode == O_BINARY) + ? O_BINARY : O_TEXT); if (res == 0) { cygheap_fdnew fdin; diff --git a/winsup/cygwin/select.cc b/winsup/cygwin/select.cc index b244469283..e2d11a4089 100644 --- a/winsup/cygwin/select.cc +++ b/winsup/cygwin/select.cc @@ -28,7 +28,7 @@ details. */ #include #include #include -#include +#include #define USE_SYS_TYPES_FD_SET #include #include "select.h" @@ -42,6 +42,7 @@ details. */ #include "perthread.h" #include "tty.h" #include "cygthread.h" +#include "ntdll.h" /* * All these defines below should be in sys/types.h @@ -419,7 +420,7 @@ peek_pipe (select_record *s, bool from_select) { if (s->read_ready) { - select_printf ("already ready"); + select_printf ("%s, already ready for read", fh->get_name ()); gotone = 1; goto out; } @@ -451,7 +452,8 @@ peek_pipe (select_record *s, bool from_select) } if (fh->get_device () == FH_PIPEW) - /* nothing */; + select_printf ("%s, select for read/except on write end of pipe", + fh->get_name ()); else if (!PeekNamedPipe (h, NULL, 0, NULL, (DWORD *) &n, NULL)) { select_printf ("%s, PeekNamedPipe failed, %E", fh->get_name ()); @@ -489,21 +491,82 @@ peek_pipe (select_record *s, bool from_select) } if (n > 0 && s->read_selected) { - select_printf ("%s, ready for read", fh->get_name ()); + select_printf ("%s, ready for read: avail %d", fh->get_name (), n); gotone += s->read_ready = true; } if (!gotone && s->fh->hit_eof ()) { select_printf ("%s, saw EOF", fh->get_name ()); if (s->except_selected) - gotone = s->except_ready = true; + gotone += s->except_ready = true; if (s->read_selected) gotone += s->read_ready = true; - select_printf ("saw eof on '%s'", fh->get_name ()); } out: - return gotone || s->write_ready; + if (s->write_selected) + { + if (s->write_ready) + { + select_printf ("%s, already ready for write", fh->get_name ()); + gotone++; + } + /* Do we need to do anything about SIGTTOU here? */ + else if (fh->get_device () == FH_PIPER) + select_printf ("%s, select for write on read end of pipe", + fh->get_name ()); + else + { + /* We don't worry about the guard mutex, because that only applies + when from_select is false, and peek_pipe is never called that + way for writes. */ + + IO_STATUS_BLOCK iosb = {0}; + FILE_PIPE_LOCAL_INFORMATION fpli = {0}; + + if (NtQueryInformationFile (h, + &iosb, + &fpli, + sizeof (fpli), + FilePipeLocalInformation)) + { + /* If NtQueryInformationFile fails, optimistically assume the + pipe is writable. This could happen on Win9x, because + NtQueryInformationFile is not available, or if we somehow + inherit a pipe that doesn't permit FILE_READ_ATTRIBUTES + access on the write end. */ + select_printf ("%s, NtQueryInformationFile failed", + fh->get_name ()); + gotone += s->write_ready = true; + } + /* Ensure that enough space is available for atomic writes, + as required by POSIX. Subsequent writes with size > PIPE_BUF + can still block, but most (all?) UNIX variants seem to work + this way (e.g., BSD, Linux, Solaris). */ + else if (fpli.WriteQuotaAvailable >= PIPE_BUF) + { + select_printf ("%s, ready for write: size %lu, avail %lu", + fh->get_name (), + fpli.OutboundQuota, + fpli.WriteQuotaAvailable); + gotone += s->write_ready = true; + } + /* If we somehow inherit a tiny pipe (size < PIPE_BUF), then consider + the pipe writable only if it is completely empty, to minimize the + probability that a subsequent write will block. */ + else if (fpli.OutboundQuota < PIPE_BUF && + fpli.WriteQuotaAvailable == fpli.OutboundQuota) + { + select_printf ("%s, tiny pipe: size %lu, avail %lu", + fh->get_name (), + fpli.OutboundQuota, + fpli.WriteQuotaAvailable); + gotone += s->write_ready = true; + } + } + } + + return gotone; } static int start_thread_pipe (select_record *me, select_stuff *stuff); @@ -603,9 +666,9 @@ fhandler_pipe::select_read (select_record *s) s->startup = start_thread_pipe; s->peek = peek_pipe; s->verify = verify_ok; + s->cleanup = pipe_cleanup; s->read_selected = true; s->read_ready = false; - s->cleanup = pipe_cleanup; return s; } @@ -613,14 +676,13 @@ select_record * fhandler_pipe::select_write (select_record *s) { if (!s) - { - s = new select_record; - s->startup = no_startup; - s->verify = no_verify; - } + s = new select_record; + s->startup = start_thread_pipe; s->peek = peek_pipe; + s->verify = verify_ok; + s->cleanup = pipe_cleanup; s->write_selected = true; - s->write_ready = true; + s->write_ready = false; return s; } @@ -628,7 +690,7 @@ select_record * fhandler_pipe::select_except (select_record *s) { if (!s) - s = new select_record; + s = new select_record; s->startup = start_thread_pipe; s->peek = peek_pipe; s->verify = verify_ok; -- 2.11.0