diff options
Diffstat (limited to 'winsup/cygwin/fhandler/fifo.cc')
-rw-r--r-- | winsup/cygwin/fhandler/fifo.cc | 1862 |
1 files changed, 1862 insertions, 0 deletions
diff --git a/winsup/cygwin/fhandler/fifo.cc b/winsup/cygwin/fhandler/fifo.cc new file mode 100644 index 000000000..1d3e42908 --- /dev/null +++ b/winsup/cygwin/fhandler/fifo.cc @@ -0,0 +1,1862 @@ +/* fhandler_fifo.cc - See fhandler.h for a description of the fhandler classes. + + This file is part of Cygwin. + + This software is a copyrighted work licensed under the terms of the + Cygwin license. Please consult the file "CYGWIN_LICENSE" for + details. */ + +#include "winsup.h" +#include <w32api/winioctl.h> +#include "miscfuncs.h" + +#include "cygerrno.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "sigproc.h" +#include "cygtls.h" +#include "shared_info.h" +#include "ntdll.h" +#include "cygwait.h" +#include <sys/param.h> + +/* + Overview: + + FIFOs are implemented via Windows named pipes. The server end of + the pipe corresponds to an fhandler_fifo open for reading (a.k.a, + a "reader"), and the client end corresponds to an fhandler_fifo + open for writing (a.k.a, a "writer"). + + The server can have multiple instances. The reader (assuming for + the moment that there is only one) creates a pipe instance for + each writer that opens. The reader maintains a list of + "fifo_client_handler" structures, one for each writer. A + fifo_client_handler contains the handle for the pipe server + instance and information about the state of the connection with + the writer. Access to the list is controlled by a + "fifo_client_lock". + + The reader runs a "fifo_reader_thread" that creates new pipe + instances as needed and listens for client connections. + + The connection state of a fifo_client_handler has one of the + following values, in which order is important: + + fc_unknown + fc_error + fc_disconnected + fc_closing + fc_listening + fc_connected + fc_input_avail + + It can be changed in the following places: + + - It is set to fc_listening when the pipe instance is created. + + - It is set to fc_connected when the fifo_reader_thread detects + a connection. + + - It is set to a value reported by the O/S when + query_and_set_state is called. This can happen in + select.cc:peek_fifo and a couple other places. + + - It is set to fc_disconnected by raw_read when an attempt to + read yields STATUS_PIPE_BROKEN. + + - It is set to fc_error in various places when unexpected + things happen. + + State changes are always guarded by fifo_client_lock. + + If there are multiple readers open, only one of them, called the + "owner", maintains the fifo_client_handler list. The owner is + therefore the only reader that can read at any given time. If a + different reader wants to read, it has to take ownership and + duplicate the fifo_client_handler list. + + A reader that is not an owner also runs a fifo_reader_thread, + which is mostly idle. The thread wakes up if that reader might + need to take ownership. + + There is a block of named shared memory, accessible to all + fhandlers for a given FIFO. It keeps track of the number of open + readers and writers; it contains information needed for the owner + change process; and it contains some locks to prevent races and + deadlocks between the various threads. + + The shared memory is created by the first reader to open the + FIFO. It is opened by subsequent readers and by all writers. It + is destroyed by Windows when the last handle to it is closed. + + If a handle to it somehow remains open after all processes + holding file descriptors to the FIFO have closed, the shared + memory can persist and be reused with stale data by the next + process that opens the FIFO. So far I've seen this happen only + as a result of a bug in the code, but there are some debug_printf + statements in fhandler_fifo::open to help detect this if it + happens again. + + At this writing, I know of only one application (Midnight + Commander when running under tcsh) that *explicitly* opens two + readers of a FIFO. But many applications will have multiple + readers open via dup/fork/exec. +*/ + + +/* This is only to be used for writers. When reading, +STATUS_PIPE_EMPTY simply means there's no data to be read. */ +#define STATUS_PIPE_IS_CLOSED(status) \ + ({ NTSTATUS _s = (status); \ + _s == STATUS_PIPE_CLOSING \ + || _s == STATUS_PIPE_BROKEN \ + || _s == STATUS_PIPE_EMPTY; }) + +#define STATUS_PIPE_NO_INSTANCE_AVAILABLE(status) \ + ({ NTSTATUS _s = (status); \ + _s == STATUS_INSTANCE_NOT_AVAILABLE \ + || _s == STATUS_PIPE_NOT_AVAILABLE \ + || _s == STATUS_PIPE_BUSY; }) + +/* Number of pages reserved for shared_fc_handler. */ +#define SH_FC_HANDLER_PAGES 100 + +static NO_COPY fifo_reader_id_t null_fr_id = { .winpid = 0, .fh = NULL }; + +fhandler_fifo::fhandler_fifo (): + fhandler_pipe_fifo (), + read_ready (NULL), write_ready (NULL), writer_opening (NULL), + owner_needed_evt (NULL), owner_found_evt (NULL), update_needed_evt (NULL), + cancel_evt (NULL), thr_sync_evt (NULL), pipe_name_buf (NULL), + fc_handler (NULL), shandlers (0), nhandlers (0), + reader (false), writer (false), duplexer (false), + me (null_fr_id), shmem_handle (NULL), shmem (NULL), + shared_fc_hdl (NULL), shared_fc_handler (NULL) +{ + need_fork_fixup (true); +} + +PUNICODE_STRING +fhandler_fifo::get_pipe_name () +{ + if (!pipe_name_buf) + { + pipe_name.Length = CYGWIN_FIFO_PIPE_NAME_LEN * sizeof (WCHAR); + pipe_name.MaximumLength = pipe_name.Length + sizeof (WCHAR); + pipe_name_buf = (PWCHAR) cmalloc_abort (HEAP_STR, + pipe_name.MaximumLength); + pipe_name.Buffer = pipe_name_buf; + __small_swprintf (pipe_name_buf, L"%S-fifo.%08x.%016X", + &cygheap->installation_key, get_dev (), get_ino ()); + } + return &pipe_name; +} + +inline PSECURITY_ATTRIBUTES +sec_user_cloexec (bool cloexec, PSECURITY_ATTRIBUTES sa, PSID sid) +{ + return cloexec ? sec_user_nih (sa, sid) : sec_user (sa, sid); +} + +static HANDLE +create_event () +{ + NTSTATUS status; + OBJECT_ATTRIBUTES attr; + HANDLE evt = NULL; + + InitializeObjectAttributes (&attr, NULL, 0, NULL, NULL); + status = NtCreateEvent (&evt, EVENT_ALL_ACCESS, &attr, + NotificationEvent, FALSE); + if (!NT_SUCCESS (status)) + __seterrno_from_nt_status (status); + return evt; +} + + +static void +set_pipe_non_blocking (HANDLE ph, bool nonblocking) +{ + NTSTATUS status; + IO_STATUS_BLOCK io; + FILE_PIPE_INFORMATION fpi; + + fpi.ReadMode = FILE_PIPE_MESSAGE_MODE; + fpi.CompletionMode = nonblocking ? FILE_PIPE_COMPLETE_OPERATION + : FILE_PIPE_QUEUE_OPERATION; + status = NtSetInformationFile (ph, &io, &fpi, sizeof fpi, + FilePipeInformation); + if (!NT_SUCCESS (status)) + debug_printf ("NtSetInformationFile(FilePipeInformation): %y", status); +} + +/* Called when a FIFO is first opened for reading and again each time + a new client handler is needed. Each pipe instance is created in + blocking mode so that we can easily wait for a connection. After + it is connected, it is put in nonblocking mode. */ +HANDLE +fhandler_fifo::create_pipe_instance () +{ + NTSTATUS status; + HANDLE npfsh; + HANDLE ph = NULL; + ACCESS_MASK access; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + ULONG hattr; + ULONG sharing; + ULONG nonblocking = FILE_PIPE_QUEUE_OPERATION; + ULONG max_instances = -1; + LARGE_INTEGER timeout; + + status = npfs_handle (npfsh); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return NULL; + } + access = GENERIC_READ | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES + | SYNCHRONIZE; + sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; + hattr = (openflags & O_CLOEXEC ? 0 : OBJ_INHERIT) | OBJ_CASE_INSENSITIVE; + InitializeObjectAttributes (&attr, get_pipe_name (), + hattr, npfsh, NULL); + timeout.QuadPart = -500000; + status = NtCreateNamedPipeFile (&ph, access, &attr, &io, sharing, + FILE_OPEN_IF, 0, + FILE_PIPE_MESSAGE_TYPE + | FILE_PIPE_REJECT_REMOTE_CLIENTS, + FILE_PIPE_MESSAGE_MODE, + nonblocking, max_instances, + DEFAULT_PIPEBUFSIZE, DEFAULT_PIPEBUFSIZE, + &timeout); + if (!NT_SUCCESS (status)) + __seterrno_from_nt_status (status); + return ph; +} + +/* Connect to a pipe instance. */ +NTSTATUS +fhandler_fifo::open_pipe (HANDLE& ph) +{ + NTSTATUS status; + HANDLE npfsh; + ACCESS_MASK access; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + ULONG sharing; + + status = npfs_handle (npfsh); + if (!NT_SUCCESS (status)) + return status; + access = GENERIC_WRITE | FILE_READ_ATTRIBUTES | SYNCHRONIZE; + InitializeObjectAttributes (&attr, get_pipe_name (), + openflags & O_CLOEXEC ? 0 : OBJ_INHERIT, + npfsh, NULL); + sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; + return NtOpenFile (&ph, access, &attr, &io, sharing, 0); +} + +/* Wait up to 100ms for a pipe instance to be available, then connect. */ +NTSTATUS +fhandler_fifo::wait_open_pipe (HANDLE& ph) +{ + HANDLE npfsh; + HANDLE evt; + NTSTATUS status; + IO_STATUS_BLOCK io; + ULONG pwbuf_size; + PFILE_PIPE_WAIT_FOR_BUFFER pwbuf; + LONGLONG stamp; + LONGLONG orig_timeout = -100 * NS100PERSEC / MSPERSEC; /* 100ms */ + + status = npfs_handle (npfsh); + if (!NT_SUCCESS (status)) + return status; + if (!(evt = create_event ())) + api_fatal ("Can't create event, %E"); + pwbuf_size + = offsetof (FILE_PIPE_WAIT_FOR_BUFFER, Name) + get_pipe_name ()->Length; + pwbuf = (PFILE_PIPE_WAIT_FOR_BUFFER) alloca (pwbuf_size); + pwbuf->Timeout.QuadPart = orig_timeout; + pwbuf->NameLength = get_pipe_name ()->Length; + pwbuf->TimeoutSpecified = TRUE; + memcpy (pwbuf->Name, get_pipe_name ()->Buffer, get_pipe_name ()->Length); + stamp = get_clock (CLOCK_MONOTONIC)->n100secs (); + bool retry; + do + { + retry = false; + status = NtFsControlFile (npfsh, evt, NULL, NULL, &io, FSCTL_PIPE_WAIT, + pwbuf, pwbuf_size, NULL, 0); + if (status == STATUS_PENDING) + { + if (WaitForSingleObject (evt, INFINITE) == WAIT_OBJECT_0) + status = io.Status; + else + api_fatal ("WFSO failed, %E"); + } + if (NT_SUCCESS (status)) + status = open_pipe (ph); + if (STATUS_PIPE_NO_INSTANCE_AVAILABLE (status)) + { + /* Another writer has grabbed the pipe instance. Adjust + the timeout and keep waiting if there's time left. */ + pwbuf->Timeout.QuadPart = orig_timeout + + get_clock (CLOCK_MONOTONIC)->n100secs () - stamp; + if (pwbuf->Timeout.QuadPart < 0) + retry = true; + else + status = STATUS_IO_TIMEOUT; + } + } + while (retry); + NtClose (evt); + return status; +} + +/* Always called with fifo_client_lock in place. */ +int +fhandler_fifo::add_client_handler (bool new_pipe_instance) +{ + fifo_client_handler fc; + + if (nhandlers >= shandlers) + { + void *temp = realloc (fc_handler, + (shandlers += 64) * sizeof (fc_handler[0])); + if (!temp) + { + shandlers -= 64; + set_errno (ENOMEM); + return -1; + } + fc_handler = (fifo_client_handler *) temp; + } + if (new_pipe_instance) + { + HANDLE ph = create_pipe_instance (); + if (!ph) + return -1; + fc.h = ph; + fc.set_state (fc_listening); + } + fc_handler[nhandlers++] = fc; + return 0; +} + +/* Always called with fifo_client_lock in place. Delete a + client_handler by swapping it with the last one in the list. */ +void +fhandler_fifo::delete_client_handler (int i) +{ + fc_handler[i].close (); + if (i < --nhandlers) + fc_handler[i] = fc_handler[nhandlers]; +} + +/* Delete handlers that we will never read from. Always called with + fifo_client_lock in place. */ +void +fhandler_fifo::cleanup_handlers () +{ + /* Work from the top down to try to avoid copying. */ + for (int i = nhandlers - 1; i >= 0; --i) + if (fc_handler[i].get_state () < fc_connected) + delete_client_handler (i); +} + +/* Always called with fifo_client_lock in place. */ +void +fhandler_fifo::record_connection (fifo_client_handler& fc, bool set, + fifo_client_connect_state s) +{ + if (set) + fc.set_state (s); + set_pipe_non_blocking (fc.h, true); +} + +/* Called from fifo_reader_thread_func with owner_lock in place. */ +int +fhandler_fifo::update_my_handlers () +{ + int ret = 0; + + close_all_handlers (); + fifo_reader_id_t prev = get_prev_owner (); + if (!prev) + { + debug_printf ("No previous owner to copy handles from"); + return 0; + } + HANDLE prev_proc; + if (prev.winpid == me.winpid) + prev_proc = GetCurrentProcess (); + else + prev_proc = OpenProcess (PROCESS_DUP_HANDLE, false, prev.winpid); + if (!prev_proc) + api_fatal ("Can't open process of previous owner, %E"); + + fifo_client_lock (); + for (int i = 0; i < get_shared_nhandlers (); i++) + { + if (add_client_handler (false) < 0) + api_fatal ("Can't add client handler, %E"); + fifo_client_handler &fc = fc_handler[nhandlers - 1]; + if (!DuplicateHandle (prev_proc, shared_fc_handler[i].h, + GetCurrentProcess (), &fc.h, 0, + !close_on_exec (), DUPLICATE_SAME_ACCESS)) + { + debug_printf ("Can't duplicate handle of previous owner, %E"); + __seterrno (); + fc.set_state (fc_error); + fc.last_read = false; + ret = -1; + } + else + { + fc.set_state (shared_fc_handler[i].get_state ()); + fc.last_read = shared_fc_handler[i].last_read; + } + } + fifo_client_unlock (); + NtClose (prev_proc); + set_prev_owner (null_fr_id); + return ret; +} + +/* Always called with fifo_client_lock and owner_lock in place. */ +int +fhandler_fifo::update_shared_handlers () +{ + cleanup_handlers (); + if (nhandlers > get_shared_shandlers ()) + { + if (remap_shared_fc_handler (nhandlers * sizeof (fc_handler[0])) < 0) + return -1; + } + set_shared_nhandlers (nhandlers); + memcpy (shared_fc_handler, fc_handler, nhandlers * sizeof (fc_handler[0])); + shared_fc_handler_updated (true); + set_prev_owner (me); + return 0; +} + +static DWORD +fifo_reader_thread (LPVOID param) +{ + fhandler_fifo *fh = (fhandler_fifo *) param; + return fh->fifo_reader_thread_func (); +} + +DWORD +fhandler_fifo::fifo_reader_thread_func () +{ + HANDLE conn_evt; + + if (!(conn_evt = CreateEvent (NULL, false, false, NULL))) + api_fatal ("Can't create connection event, %E"); + + while (1) + { + fifo_reader_id_t cur_owner, pending_owner; + bool idle = false, take_ownership = false; + + owner_lock (); + cur_owner = get_owner (); + pending_owner = get_pending_owner (); + + if (pending_owner) + { + if (pending_owner == me) + take_ownership = true; + else if (cur_owner != me) + idle = true; + else + { + /* I'm the owner but someone else wants to be. Have I + already seen and reacted to update_needed_evt? */ + if (WaitForSingleObject (update_needed_evt, 0) == WAIT_OBJECT_0) + { + /* No, I haven't. */ + fifo_client_lock (); + if (update_shared_handlers () < 0) + api_fatal ("Can't update shared handlers, %E"); + fifo_client_unlock (); + } + owner_unlock (); + /* Yield to pending owner. */ + Sleep (1); + continue; + } + } + else if (!cur_owner) + take_ownership = true; + else if (cur_owner != me) + idle = true; + else + /* I'm the owner and there's no pending owner. */ + goto owner_listen; + if (idle) + { + owner_unlock (); + HANDLE w[2] = { owner_needed_evt, cancel_evt }; + switch (WaitForMultipleObjects (2, w, false, INFINITE)) + { + case WAIT_OBJECT_0: + continue; + case WAIT_OBJECT_0 + 1: + goto canceled; + default: + api_fatal ("WFMO failed, %E"); + } + } + else if (take_ownership) + { + if (!shared_fc_handler_updated ()) + { + owner_unlock (); + if (IsEventSignalled (cancel_evt)) + goto canceled; + continue; + } + else + { + set_owner (me); + set_pending_owner (null_fr_id); + if (update_my_handlers () < 0) + debug_printf ("error updating my handlers, %E"); + owner_found (); + /* Fall through to owner_listen. */ + } + } + +owner_listen: + fifo_client_lock (); + cleanup_handlers (); + if (add_client_handler () < 0) + api_fatal ("Can't add a client handler, %E"); + + /* Listen for a writer to connect to the new client handler. */ + fifo_client_handler& fc = fc_handler[nhandlers - 1]; + fifo_client_unlock (); + shared_fc_handler_updated (false); + owner_unlock (); + NTSTATUS status; + IO_STATUS_BLOCK io; + bool cancel = false; + bool update = false; + + status = NtFsControlFile (fc.h, conn_evt, NULL, NULL, &io, + FSCTL_PIPE_LISTEN, NULL, 0, NULL, 0); + if (status == STATUS_PENDING) + { + HANDLE w[3] = { conn_evt, update_needed_evt, cancel_evt }; + switch (WaitForMultipleObjects (3, w, false, INFINITE)) + { + case WAIT_OBJECT_0: + status = io.Status; + debug_printf ("NtFsControlFile STATUS_PENDING, then %y", + status); + break; + case WAIT_OBJECT_0 + 1: + status = STATUS_WAIT_1; + update = true; + break; + case WAIT_OBJECT_0 + 2: + status = STATUS_THREAD_IS_TERMINATING; + cancel = true; + update = true; + break; + default: + api_fatal ("WFMO failed, %E"); + } + } + else + debug_printf ("NtFsControlFile status %y, no STATUS_PENDING", + status); + HANDLE ph = NULL; + NTSTATUS status1; + + fifo_client_lock (); + if (fc.get_state () != fc_listening) + /* select.cc:peek_fifo has already recorded a connection. */ + ; + else + { + switch (status) + { + case STATUS_SUCCESS: + case STATUS_PIPE_CONNECTED: + record_connection (fc); + break; + case STATUS_PIPE_CLOSING: + debug_printf ("NtFsControlFile got STATUS_PIPE_CLOSING..."); + /* Maybe a writer already connected, wrote, and closed. + Just query the O/S. */ + fc.query_and_set_state (); + debug_printf ("...O/S reports state %d", fc.get_state ()); + record_connection (fc, false); + break; + case STATUS_THREAD_IS_TERMINATING: + case STATUS_WAIT_1: + /* Try to connect a bogus client. Otherwise fc is still + listening, and the next connection might not get recorded. */ + status1 = open_pipe (ph); + WaitForSingleObject (conn_evt, INFINITE); + if (NT_SUCCESS (status1)) + /* Bogus cilent connected. */ + delete_client_handler (nhandlers - 1); + else + /* Did a real client connect? */ + switch (io.Status) + { + case STATUS_SUCCESS: + case STATUS_PIPE_CONNECTED: + record_connection (fc); + break; + case STATUS_PIPE_CLOSING: + debug_printf ("got STATUS_PIPE_CLOSING when trying to connect bogus client..."); + fc.query_and_set_state (); + debug_printf ("...O/S reports state %d", fc.get_state ()); + record_connection (fc, false); + break; + default: + debug_printf ("NtFsControlFile status %y after failing to connect bogus client or real client", io.Status); + fc.set_state (fc_error); + break; + } + break; + default: + debug_printf ("NtFsControlFile got unexpected status %y", status); + fc.set_state (fc_error); + break; + } + } + if (ph) + NtClose (ph); + if (update) + { + owner_lock (); + if (get_owner () == me && update_shared_handlers () < 0) + api_fatal ("Can't update shared handlers, %E"); + owner_unlock (); + } + fifo_client_unlock (); + if (cancel) + goto canceled; + } +canceled: + if (conn_evt) + NtClose (conn_evt); + /* automatically return the cygthread to the cygthread pool */ + _my_tls._ctinfo->auto_release (); + return 0; +} + +/* Return -1 on error and 0 or 1 on success. If ONLY_OPEN is true, we + expect the shared memory to exist, and we only try to open it. In + this case, we return 0 on success. + + Otherwise, we create the shared memory if it doesn't exist, and we + return 1 if it already existed and we successfully open it. */ +int +fhandler_fifo::create_shmem (bool only_open) +{ + HANDLE sect; + OBJECT_ATTRIBUTES attr; + NTSTATUS status; + LARGE_INTEGER size = { .QuadPart = sizeof (fifo_shmem_t) }; + SIZE_T viewsize = sizeof (fifo_shmem_t); + PVOID addr = NULL; + UNICODE_STRING uname; + WCHAR shmem_name[MAX_PATH]; + bool already_exists = false; + + __small_swprintf (shmem_name, L"fifo-shmem.%08x.%016X", get_dev (), + get_ino ()); + RtlInitUnicodeString (&uname, shmem_name); + InitializeObjectAttributes (&attr, &uname, OBJ_INHERIT, + get_shared_parent_dir (), NULL); + if (!only_open) + { + status = NtCreateSection (§, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY + | SECTION_MAP_READ | SECTION_MAP_WRITE, + &attr, &size, PAGE_READWRITE, SEC_COMMIT, NULL); + if (status == STATUS_OBJECT_NAME_COLLISION) + already_exists = true; + } + if (only_open || already_exists) + status = NtOpenSection (§, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY + | SECTION_MAP_READ | SECTION_MAP_WRITE, &attr); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + status = NtMapViewOfSection (sect, NtCurrentProcess (), &addr, 0, viewsize, + NULL, &viewsize, ViewShare, 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + NtClose (sect); + __seterrno_from_nt_status (status); + return -1; + } + shmem_handle = sect; + shmem = (fifo_shmem_t *) addr; + return already_exists ? 1 : 0; +} + +/* shmem_handle must be valid when this is called. */ +int +fhandler_fifo::reopen_shmem () +{ + NTSTATUS status; + SIZE_T viewsize = sizeof (fifo_shmem_t); + PVOID addr = NULL; + + status = NtMapViewOfSection (shmem_handle, NtCurrentProcess (), &addr, + 0, viewsize, NULL, &viewsize, ViewShare, + 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + shmem = (fifo_shmem_t *) addr; + return 0; +} + +/* On first creation, map and commit one page of memory. */ +int +fhandler_fifo::create_shared_fc_handler () +{ + HANDLE sect; + OBJECT_ATTRIBUTES attr; + NTSTATUS status; + LARGE_INTEGER size + = { .QuadPart = (LONGLONG) (SH_FC_HANDLER_PAGES * wincap.page_size ()) }; + SIZE_T viewsize = get_shared_fc_handler_committed () ?: wincap.page_size (); + PVOID addr = NULL; + UNICODE_STRING uname; + WCHAR shared_fc_name[MAX_PATH]; + + __small_swprintf (shared_fc_name, L"fifo-shared-fc.%08x.%016X", get_dev (), + get_ino ()); + RtlInitUnicodeString (&uname, shared_fc_name); + InitializeObjectAttributes (&attr, &uname, OBJ_INHERIT, + get_shared_parent_dir (), NULL); + status = NtCreateSection (§, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY + | SECTION_MAP_READ | SECTION_MAP_WRITE, &attr, + &size, PAGE_READWRITE, SEC_RESERVE, NULL); + if (status == STATUS_OBJECT_NAME_COLLISION) + status = NtOpenSection (§, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY + | SECTION_MAP_READ | SECTION_MAP_WRITE, &attr); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + status = NtMapViewOfSection (sect, NtCurrentProcess (), &addr, 0, viewsize, + NULL, &viewsize, ViewShare, 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + NtClose (sect); + __seterrno_from_nt_status (status); + return -1; + } + shared_fc_hdl = sect; + shared_fc_handler = (fifo_client_handler *) addr; + if (!get_shared_fc_handler_committed ()) + set_shared_fc_handler_committed (viewsize); + set_shared_shandlers (viewsize / sizeof (fifo_client_handler)); + return 0; +} + +/* shared_fc_hdl must be valid when this is called. */ +int +fhandler_fifo::reopen_shared_fc_handler () +{ + NTSTATUS status; + SIZE_T viewsize = get_shared_fc_handler_committed (); + PVOID addr = NULL; + + status = NtMapViewOfSection (shared_fc_hdl, NtCurrentProcess (), + &addr, 0, viewsize, NULL, &viewsize, + ViewShare, 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + shared_fc_handler = (fifo_client_handler *) addr; + return 0; +} + +int +fhandler_fifo::remap_shared_fc_handler (size_t nbytes) +{ + NTSTATUS status; + SIZE_T viewsize = roundup2 (nbytes, wincap.page_size ()); + PVOID addr = NULL; + + if (viewsize > SH_FC_HANDLER_PAGES * wincap.page_size ()) + { + set_errno (ENOMEM); + return -1; + } + + NtUnmapViewOfSection (NtCurrentProcess (), shared_fc_handler); + status = NtMapViewOfSection (shared_fc_hdl, NtCurrentProcess (), + &addr, 0, viewsize, NULL, &viewsize, + ViewShare, 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + shared_fc_handler = (fifo_client_handler *) addr; + set_shared_fc_handler_committed (viewsize); + set_shared_shandlers (viewsize / sizeof (fc_handler[0])); + return 0; +} + +int +fhandler_fifo::open (int flags, mode_t) +{ + int saved_errno = 0, shmem_res = 0; + + if (flags & O_PATH) + return open_fs (flags); + + /* Determine what we're doing with this fhandler: reading, writing, both */ + switch (flags & O_ACCMODE) + { + case O_RDONLY: + reader = true; + break; + case O_WRONLY: + writer = true; + break; + case O_RDWR: + reader = writer = duplexer = true; + break; + default: + set_errno (EINVAL); + goto err; + } + + debug_only_printf ("reader %d, writer %d, duplexer %d", reader, writer, duplexer); + set_flags (flags); + if (reader && !duplexer) + nohandle (true); + + /* Create control events for this named pipe */ + char char_sa_buf[1024]; + LPSECURITY_ATTRIBUTES sa_buf; + sa_buf = sec_user_cloexec (flags & O_CLOEXEC, (PSECURITY_ATTRIBUTES) char_sa_buf, + cygheap->user.sid()); + + char npbuf[MAX_PATH]; + __small_sprintf (npbuf, "r-event.%08x.%016X", get_dev (), get_ino ()); + if (!(read_ready = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err; + } + npbuf[0] = 'w'; + if (!(write_ready = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_read_ready; + } + npbuf[0] = 'o'; + if (!(writer_opening = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_write_ready; + } + + /* If we're reading, create the shared memory and the shared + fc_handler memory, create some events, start the + fifo_reader_thread, signal read_ready, and wait for a writer. */ + if (reader) + { + /* Create/open shared memory. */ + if ((shmem_res = create_shmem ()) < 0) + goto err_close_writer_opening; + else if (shmem_res == 0) + debug_printf ("shmem created"); + else + debug_printf ("shmem existed; ok if we're not the first reader"); + if (create_shared_fc_handler () < 0) + goto err_close_shmem; + npbuf[0] = 'n'; + if (!(owner_needed_evt = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_shared_fc_handler; + } + npbuf[0] = 'f'; + if (!(owner_found_evt = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_owner_needed_evt; + } + npbuf[0] = 'u'; + if (!(update_needed_evt = CreateEvent (sa_buf, false, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_owner_found_evt; + } + if (!(cancel_evt = create_event ())) + goto err_close_update_needed_evt; + if (!(thr_sync_evt = create_event ())) + goto err_close_cancel_evt; + + me.winpid = GetCurrentProcessId (); + me.fh = this; + nreaders_lock (); + if (inc_nreaders () == 1) + { + /* Reinitialize _sh_fc_handler_updated, which starts as 0. */ + shared_fc_handler_updated (true); + set_owner (me); + } + new cygthread (fifo_reader_thread, this, "fifo_reader", thr_sync_evt); + SetEvent (read_ready); + nreaders_unlock (); + + /* If we're a duplexer, we need a handle for writing. */ + if (duplexer) + { + HANDLE ph = NULL; + NTSTATUS status; + + nwriters_lock (); + inc_nwriters (); + SetEvent (write_ready); + nwriters_unlock (); + + while (1) + { + status = open_pipe (ph); + if (NT_SUCCESS (status)) + { + set_handle (ph); + set_pipe_non_blocking (ph, flags & O_NONBLOCK); + break; + } + else if (status == STATUS_OBJECT_NAME_NOT_FOUND) + { + /* The pipe hasn't been created yet. */ + yield (); + continue; + } + else + { + __seterrno_from_nt_status (status); + nohandle (true); + goto err_close_reader; + } + } + } + /* Not a duplexer; wait for a writer to connect if we're blocking. */ + else if (!wait (write_ready)) + goto err_close_reader; + goto success; + } + + /* If we're writing, wait for read_ready, connect to the pipe, open + the shared memory, and signal write_ready. */ + if (writer) + { + NTSTATUS status; + + /* Don't let a reader see EOF at this point. */ + SetEvent (writer_opening); + while (1) + { + if (!wait (read_ready)) + { + ResetEvent (writer_opening); + goto err_close_writer_opening; + } + status = open_pipe (get_handle ()); + if (NT_SUCCESS (status)) + goto writer_shmem; + else if (status == STATUS_OBJECT_NAME_NOT_FOUND) + { + /* The pipe hasn't been created yet or there's no longer + a reader open. */ + yield (); + continue; + } + else if (STATUS_PIPE_NO_INSTANCE_AVAILABLE (status)) + break; + else + { + debug_printf ("create of writer failed"); + __seterrno_from_nt_status (status); + ResetEvent (writer_opening); + goto err_close_writer_opening; + } + } + + /* We should get here only if the system is heavily loaded + and/or many writers are trying to connect simultaneously */ + while (1) + { + if (!wait (read_ready)) + { + ResetEvent (writer_opening); + goto err_close_writer_opening; + } + status = wait_open_pipe (get_handle ()); + if (NT_SUCCESS (status)) + goto writer_shmem; + else if (status == STATUS_IO_TIMEOUT) + continue; + else + { + debug_printf ("create of writer failed"); + __seterrno_from_nt_status (status); + ResetEvent (writer_opening); + goto err_close_writer_opening; + } + } + } +writer_shmem: + if (create_shmem (true) < 0) + goto err_close_writer_opening; +/* writer_success: */ + set_pipe_non_blocking (get_handle (), flags & O_NONBLOCK); + nwriters_lock (); + inc_nwriters (); + SetEvent (write_ready); + ResetEvent (writer_opening); + nwriters_unlock (); +success: + if (!select_sem) + { + __small_sprintf (npbuf, "semaphore.%08x.%016X", get_dev (), get_ino ()); + select_sem = CreateSemaphore (sa_buf, 0, INT32_MAX, npbuf); + } + return 1; +err_close_reader: + saved_errno = get_errno (); + close (); + set_errno (saved_errno); + return 0; +/* err_close_thr_sync_evt: */ +/* NtClose (thr_sync_evt); */ +err_close_cancel_evt: + NtClose (cancel_evt); +err_close_update_needed_evt: + NtClose (update_needed_evt); +err_close_owner_found_evt: + NtClose (owner_found_evt); +err_close_owner_needed_evt: + NtClose (owner_needed_evt); +err_close_shared_fc_handler: + NtUnmapViewOfSection (NtCurrentProcess (), shared_fc_handler); + NtClose (shared_fc_hdl); +err_close_shmem: + NtUnmapViewOfSection (NtCurrentProcess (), shmem); + NtClose (shmem_handle); +err_close_writer_opening: + NtClose (writer_opening); +err_close_write_ready: + NtClose (write_ready); +err_close_read_ready: + NtClose (read_ready); +err: + if (get_handle ()) + NtClose (get_handle ()); + return 0; +} + +off_t +fhandler_fifo::lseek (off_t offset, int whence) +{ + debug_printf ("(%D, %d)", offset, whence); + set_errno (ESPIPE); + return -1; +} + +bool +fhandler_fifo::wait (HANDLE h) +{ +#ifdef DEBUGGING + const char *what; + if (h == read_ready) + what = "reader"; + else + what = "writer"; +#endif + /* Set the wait to zero for non-blocking I/O-related events. */ + DWORD wait = ((h == read_ready || h == write_ready) + && get_flags () & O_NONBLOCK) ? 0 : INFINITE; + + debug_only_printf ("waiting for %s", what); + /* Wait for the event. Set errno, as appropriate if something goes wrong. */ + switch (cygwait (h, wait)) + { + case WAIT_OBJECT_0: + debug_only_printf ("successfully waited for %s", what); + return true; + case WAIT_SIGNALED: + debug_only_printf ("interrupted by signal while waiting for %s", what); + set_errno (EINTR); + return false; + case WAIT_CANCELED: + debug_only_printf ("cancellable interruption while waiting for %s", what); + pthread::static_cancel_self (); /* never returns */ + break; + case WAIT_TIMEOUT: + if (h == write_ready) + { + debug_only_printf ("wait timed out waiting for write but will still open reader since non-blocking mode"); + return true; + } + else + { + set_errno (ENXIO); + return false; + } + break; + default: + debug_only_printf ("unknown error while waiting for %s", what); + __seterrno (); + return false; + } +} + +/* Called from raw_read and select.cc:peek_fifo. */ +int +fhandler_fifo::take_ownership (DWORD timeout) +{ + int ret = 0; + + owner_lock (); + if (get_owner () == me) + { + owner_unlock (); + return 0; + } + set_pending_owner (me); + /* Wake up my fifo_reader_thread. */ + owner_needed (); + if (get_owner ()) + /* Wake up the owner and request an update of the shared fc_handlers. */ + SetEvent (update_needed_evt); + owner_unlock (); + /* The reader threads should now do the transfer. */ + switch (WaitForSingleObject (owner_found_evt, timeout)) + { + case WAIT_OBJECT_0: + owner_lock (); + if (get_owner () != me) + { + debug_printf ("owner_found_evt signaled, but I'm not the owner"); + ret = -1; + } + owner_unlock (); + break; + case WAIT_TIMEOUT: + debug_printf ("timed out"); + ret = -1; + break; + default: + debug_printf ("WFSO failed, %E"); + ret = -1; + break; + } + return ret; +} + +void +fhandler_fifo::release_select_sem (const char *from) +{ + LONG n_release; + if (reader) /* Number of select() call. */ + n_release = get_obj_handle_count (select_sem) + - get_obj_handle_count (read_ready); + else /* Number of select() and reader */ + n_release = get_obj_handle_count (select_sem) + - get_obj_handle_count (get_handle ()); + debug_printf("%s(%s) release %d", from, + reader ? "reader" : "writer", n_release); + if (n_release) + ReleaseSemaphore (select_sem, n_release, NULL); +} + +/* Read from a non-blocking pipe and wait for completion. */ +static NTSTATUS +nt_read (HANDLE h, HANDLE evt, PIO_STATUS_BLOCK pio, void *in_ptr, size_t& len) +{ + NTSTATUS status; + + ResetEvent (evt); + status = NtReadFile (h, evt, NULL, NULL, pio, in_ptr, len, NULL, NULL); + if (status == STATUS_PENDING) + { + /* Very short-lived */ + status = NtWaitForSingleObject (evt, FALSE, NULL); + if (NT_SUCCESS (status)) + status = pio->Status; + } + return status; +} + +void +fhandler_fifo::raw_read (void *in_ptr, size_t& len) +{ + HANDLE evt; + + if (!len) + return; + + if (!(evt = CreateEvent (NULL, false, false, NULL))) + { + __seterrno (); + len = (size_t) -1; + return; + } + + while (1) + { + int nconnected = 0; + + /* No one else can take ownership while we hold the reading_lock. */ + reading_lock (); + if (take_ownership (10) < 0) + goto maybe_retry; + + fifo_client_lock (); + /* Poll the connected clients for input. Make three passes. + + On the first pass, just try to read from the client from + which we last read successfully. This should minimize + interleaving of writes from different clients. + + On the second pass, just try to read from the clients in the + state fc_input_avail. This should be more efficient if + select has been called and detected input available. + + On the third pass, try to read from all connected clients. */ + + /* First pass. */ + int j; + for (j = 0; j < nhandlers; j++) + if (fc_handler[j].last_read) + break; + if (j < nhandlers && fc_handler[j].get_state () < fc_connected) + { + fc_handler[j].last_read = false; + j = nhandlers; + } + if (j < nhandlers) + { + NTSTATUS status; + IO_STATUS_BLOCK io; + + status = nt_read (fc_handler[j].h, evt, &io, in_ptr, len); + switch (status) + { + case STATUS_SUCCESS: + case STATUS_BUFFER_OVERFLOW: + if (io.Information > 0) + { + len = io.Information; + goto unlock_out; + } + break; + case STATUS_PIPE_EMPTY: + /* Update state in case it's fc_input_avail. */ + fc_handler[j].set_state (fc_connected); + break; + case STATUS_PIPE_BROKEN: + fc_handler[j].set_state (fc_disconnected); + break; + default: + debug_printf ("nt_read status %y", status); + fc_handler[j].set_state (fc_error); + break; + } + } + + /* Second pass. */ + for (int i = 0; i < nhandlers; i++) + if (fc_handler[i].get_state () == fc_input_avail) + { + NTSTATUS status; + IO_STATUS_BLOCK io; + + status = nt_read (fc_handler[i].h, evt, &io, in_ptr, len); + switch (status) + { + case STATUS_SUCCESS: + case STATUS_BUFFER_OVERFLOW: + if (io.Information > 0) + { + len = io.Information; + if (j < nhandlers) + fc_handler[j].last_read = false; + fc_handler[i].last_read = true; + goto unlock_out; + } + break; + case STATUS_PIPE_EMPTY: + /* No input available after all. */ + fc_handler[i].set_state (fc_connected); + break; + case STATUS_PIPE_BROKEN: + fc_handler[i].set_state (fc_disconnected); + break; + default: + debug_printf ("nt_read status %y", status); + fc_handler[i].set_state (fc_error); + break; + } + } + + /* Third pass. */ + for (int i = 0; i < nhandlers; i++) + if (fc_handler[i].get_state () >= fc_connected) + { + NTSTATUS status; + IO_STATUS_BLOCK io; + + nconnected++; + status = nt_read (fc_handler[i].h, evt, &io, in_ptr, len); + switch (status) + { + case STATUS_SUCCESS: + case STATUS_BUFFER_OVERFLOW: + if (io.Information > 0) + { + len = io.Information; + if (j < nhandlers) + fc_handler[j].last_read = false; + fc_handler[i].last_read = true; + goto unlock_out; + } + break; + case STATUS_PIPE_EMPTY: + break; + case STATUS_PIPE_BROKEN: + fc_handler[i].set_state (fc_disconnected); + nconnected--; + break; + default: + debug_printf ("nt_read status %y", status); + fc_handler[i].set_state (fc_error); + nconnected--; + break; + } + } + if (!nconnected && hit_eof ()) + { + len = 0; + goto unlock_out; + } + fifo_client_unlock (); +maybe_retry: + reading_unlock (); + if (is_nonblocking ()) + { + set_errno (EAGAIN); + len = (size_t) -1; + goto out; + } + else + { + /* Allow interruption and don't hog the CPU. */ + DWORD waitret = cygwait (select_sem, 1, cw_cancel | cw_sig_eintr); + if (waitret == WAIT_CANCELED) + pthread::static_cancel_self (); + else if (waitret == WAIT_SIGNALED) + { + if (_my_tls.call_signal_handler ()) + continue; + else + { + set_errno (EINTR); + len = (size_t) -1; + goto out; + } + } + } + /* We might have been closed by a signal handler or another thread. */ + if (isclosed ()) + { + set_errno (EBADF); + len = (size_t) -1; + goto out; + } + } +unlock_out: + fifo_client_unlock (); + reading_unlock (); +out: + if (select_sem) + release_select_sem ("raw_read"); + CloseHandle (evt); +} + +int +fhandler_fifo::fstat (struct stat *buf) +{ + if (reader || writer || duplexer) + { + /* fhandler_fifo::open has been called, and O_PATH is not set. + We don't want to call fhandler_base::fstat. In the writer + and duplexer cases we have a handle, but it's a pipe handle + rather than a file handle, so it's not suitable for stat. In + the reader case we don't have a handle, but + fhandler_base::fstat would call fhandler_base::open, which + would modify the flags and status_flags. */ + fhandler_disk_file fh (pc); + fh.get_device () = FH_FS; + int res = fh.fstat (buf); + buf->st_dev = buf->st_rdev = dev (); + buf->st_mode = dev ().mode (); + buf->st_size = 0; + return res; + } + return fhandler_base::fstat (buf); +} + +int +fhandler_fifo::fstatvfs (struct statvfs *sfs) +{ + if (get_flags () & O_PATH) + /* We already have a handle. */ + { + HANDLE h = get_handle (); + if (h) + return fstatvfs_by_handle (h, sfs); + } + + fhandler_disk_file fh (pc); + fh.get_device () = FH_FS; + return fh.fstatvfs (sfs); +} + +void +fhandler_fifo::close_all_handlers () +{ + fifo_client_lock (); + for (int i = 0; i < nhandlers; i++) + fc_handler[i].close (); + nhandlers = 0; + fifo_client_unlock (); +} + +/* Return previous state. */ +fifo_client_connect_state +fifo_client_handler::query_and_set_state () +{ + IO_STATUS_BLOCK io; + FILE_PIPE_LOCAL_INFORMATION fpli; + NTSTATUS status; + fifo_client_connect_state prev_state = get_state (); + + if (!h) + { + set_state (fc_unknown); + goto out; + } + + status = NtQueryInformationFile (h, &io, &fpli, + sizeof (fpli), FilePipeLocalInformation); + if (!NT_SUCCESS (status)) + { + debug_printf ("NtQueryInformationFile status %y", status); + set_state (fc_error); + } + else if (fpli.ReadDataAvailable > 0) + set_state (fc_input_avail); + else + switch (fpli.NamedPipeState) + { + case FILE_PIPE_DISCONNECTED_STATE: + set_state (fc_disconnected); + break; + case FILE_PIPE_LISTENING_STATE: + set_state (fc_listening); + break; + case FILE_PIPE_CONNECTED_STATE: + set_state (fc_connected); + break; + case FILE_PIPE_CLOSING_STATE: + set_state (fc_closing); + break; + default: + set_state (fc_error); + break; + } +out: + return prev_state; +} + +void +fhandler_fifo::cancel_reader_thread () +{ + if (cancel_evt) + SetEvent (cancel_evt); + if (thr_sync_evt) + WaitForSingleObject (thr_sync_evt, INFINITE); +} + +int +fhandler_fifo::close () +{ + if (select_sem) + { + release_select_sem ("close"); + NtClose (select_sem); + } + if (writer) + { + nwriters_lock (); + if (dec_nwriters () == 0) + ResetEvent (write_ready); + nwriters_unlock (); + } + if (reader) + { + /* If we're the owner, we can't close our fc_handlers if a new + owner might need to duplicate them. */ + bool close_fc_ok = false; + + cancel_reader_thread (); + nreaders_lock (); + if (dec_nreaders () == 0) + { + close_fc_ok = true; + ResetEvent (read_ready); + ResetEvent (owner_needed_evt); + ResetEvent (owner_found_evt); + set_owner (null_fr_id); + set_prev_owner (null_fr_id); + set_pending_owner (null_fr_id); + set_shared_nhandlers (0); + } + else + { + owner_lock (); + if (get_owner () != me) + close_fc_ok = true; + else + { + set_owner (null_fr_id); + set_prev_owner (me); + if (!get_pending_owner ()) + owner_needed (); + } + owner_unlock (); + } + nreaders_unlock (); + while (!close_fc_ok) + { + if (WaitForSingleObject (owner_found_evt, 1) == WAIT_OBJECT_0) + close_fc_ok = true; + else + { + nreaders_lock (); + if (!nreaders ()) + { + close_fc_ok = true; + nreaders_unlock (); + } + else + { + nreaders_unlock (); + owner_lock (); + if (get_owner () || get_prev_owner () != me) + close_fc_ok = true; + owner_unlock (); + } + } + } + close_all_handlers (); + if (fc_handler) + free (fc_handler); + if (owner_needed_evt) + NtClose (owner_needed_evt); + if (owner_found_evt) + NtClose (owner_found_evt); + if (update_needed_evt) + NtClose (update_needed_evt); + if (cancel_evt) + NtClose (cancel_evt); + if (thr_sync_evt) + NtClose (thr_sync_evt); + if (shared_fc_handler) + NtUnmapViewOfSection (NtCurrentProcess (), shared_fc_handler); + if (shared_fc_hdl) + NtClose (shared_fc_hdl); + } + if (shmem) + NtUnmapViewOfSection (NtCurrentProcess (), shmem); + if (shmem_handle) + NtClose (shmem_handle); + if (read_ready) + NtClose (read_ready); + if (write_ready) + NtClose (write_ready); + if (writer_opening) + NtClose (writer_opening); + if (nohandle ()) + return 0; + else + return fhandler_base::close (); +} + +/* If we have a write handle (i.e., we're a duplexer or a writer), + keep the nonblocking state of the windows pipe in sync with our + nonblocking state. */ +int +fhandler_fifo::fcntl (int cmd, intptr_t arg) +{ + if (cmd != F_SETFL || nohandle () || (get_flags () & O_PATH)) + 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 (); + if (now_nonblocking != was_nonblocking) + set_pipe_non_blocking (get_handle (), now_nonblocking); + return res; +} + +int +fhandler_fifo::dup (fhandler_base *child, int flags) +{ + fhandler_fifo *fhf = NULL; + + if (get_flags () & O_PATH) + return fhandler_base::dup (child, flags); + + if (fhandler_base::dup (child, flags)) + goto err; + + fhf = (fhandler_fifo *) child; + if (!DuplicateHandle (GetCurrentProcess (), read_ready, + GetCurrentProcess (), &fhf->read_ready, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err; + } + if (!DuplicateHandle (GetCurrentProcess (), write_ready, + GetCurrentProcess (), &fhf->write_ready, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_read_ready; + } + if (!DuplicateHandle (GetCurrentProcess (), writer_opening, + GetCurrentProcess (), &fhf->writer_opening, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_write_ready; + } + if (!DuplicateHandle (GetCurrentProcess (), shmem_handle, + GetCurrentProcess (), &fhf->shmem_handle, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_writer_opening; + } + if (fhf->reopen_shmem () < 0) + goto err_close_shmem_handle; + if (select_sem && + !DuplicateHandle (GetCurrentProcess (), select_sem, + GetCurrentProcess (), &fhf->select_sem, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_shmem; + } + if (reader) + { + /* Make sure the child starts unlocked. */ + fhf->fifo_client_unlock (); + + /* Clear fc_handler list; the child never starts as owner. */ + fhf->nhandlers = fhf->shandlers = 0; + fhf->fc_handler = NULL; + + if (!DuplicateHandle (GetCurrentProcess (), shared_fc_hdl, + GetCurrentProcess (), &fhf->shared_fc_hdl, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_select_sem; + } + if (fhf->reopen_shared_fc_handler () < 0) + goto err_close_shared_fc_hdl; + if (!DuplicateHandle (GetCurrentProcess (), owner_needed_evt, + GetCurrentProcess (), &fhf->owner_needed_evt, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_shared_fc_handler; + } + if (!DuplicateHandle (GetCurrentProcess (), owner_found_evt, + GetCurrentProcess (), &fhf->owner_found_evt, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_owner_needed_evt; + } + if (!DuplicateHandle (GetCurrentProcess (), update_needed_evt, + GetCurrentProcess (), &fhf->update_needed_evt, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_owner_found_evt; + } + if (!(fhf->cancel_evt = create_event ())) + goto err_close_update_needed_evt; + if (!(fhf->thr_sync_evt = create_event ())) + goto err_close_cancel_evt; + inc_nreaders (); + fhf->me.fh = fhf; + new cygthread (fifo_reader_thread, fhf, "fifo_reader", fhf->thr_sync_evt); + } + if (writer) + inc_nwriters (); + return 0; +err_close_cancel_evt: + NtClose (fhf->cancel_evt); +err_close_update_needed_evt: + NtClose (fhf->update_needed_evt); +err_close_owner_found_evt: + NtClose (fhf->owner_found_evt); +err_close_owner_needed_evt: + NtClose (fhf->owner_needed_evt); +err_close_shared_fc_handler: + NtUnmapViewOfSection (GetCurrentProcess (), fhf->shared_fc_handler); +err_close_shared_fc_hdl: + NtClose (fhf->shared_fc_hdl); +err_close_select_sem: + NtClose (fhf->select_sem); +err_close_shmem: + NtUnmapViewOfSection (GetCurrentProcess (), fhf->shmem); +err_close_shmem_handle: + NtClose (fhf->shmem_handle); +err_close_writer_opening: + NtClose (fhf->writer_opening); +err_close_write_ready: + NtClose (fhf->write_ready); +err_close_read_ready: + NtClose (fhf->read_ready); +err: + return -1; +} + +void +fhandler_fifo::fixup_after_fork (HANDLE parent) +{ + fhandler_base::fixup_after_fork (parent); + fork_fixup (parent, read_ready, "read_ready"); + fork_fixup (parent, write_ready, "write_ready"); + fork_fixup (parent, writer_opening, "writer_opening"); + fork_fixup (parent, shmem_handle, "shmem_handle"); + if (reopen_shmem () < 0) + api_fatal ("Can't reopen shared memory during fork, %E"); + if (select_sem) + fork_fixup (parent, select_sem, "select_sem"); + if (reader) + { + /* Make sure the child starts unlocked. */ + fifo_client_unlock (); + + fork_fixup (parent, shared_fc_hdl, "shared_fc_hdl"); + if (reopen_shared_fc_handler () < 0) + api_fatal ("Can't reopen shared fc_handler memory during fork, %E"); + fork_fixup (parent, owner_needed_evt, "owner_needed_evt"); + fork_fixup (parent, owner_found_evt, "owner_found_evt"); + fork_fixup (parent, update_needed_evt, "update_needed_evt"); + if (close_on_exec ()) + /* Prevent a later attempt to close the non-inherited + pipe-instance handles copied from the parent. */ + nhandlers = 0; + if (!(cancel_evt = create_event ())) + api_fatal ("Can't create reader thread cancel event during fork, %E"); + if (!(thr_sync_evt = create_event ())) + api_fatal ("Can't create reader thread sync event during fork, %E"); + inc_nreaders (); + me.winpid = GetCurrentProcessId (); + new cygthread (fifo_reader_thread, this, "fifo_reader", thr_sync_evt); + } + if (writer) + inc_nwriters (); +} + +void +fhandler_fifo::fixup_after_exec () +{ + fhandler_base::fixup_after_exec (); + if (close_on_exec ()) + return; + if (reopen_shmem () < 0) + api_fatal ("Can't reopen shared memory during exec, %E"); + if (reader) + { + /* Make sure the child starts unlocked. */ + fifo_client_unlock (); + + if (reopen_shared_fc_handler () < 0) + api_fatal ("Can't reopen shared fc_handler memory during exec, %E"); + fc_handler = NULL; + nhandlers = shandlers = 0; + if (!(cancel_evt = create_event ())) + api_fatal ("Can't create reader thread cancel event during exec, %E"); + if (!(thr_sync_evt = create_event ())) + api_fatal ("Can't create reader thread sync event during exec, %E"); + /* At this moment we're a new reader. The count will be + decremented when the parent closes. */ + inc_nreaders (); + me.winpid = GetCurrentProcessId (); + new cygthread (fifo_reader_thread, this, "fifo_reader", thr_sync_evt); + } + if (writer) + inc_nwriters (); +} + +void +fhandler_fifo::set_close_on_exec (bool val) +{ + fhandler_base::set_close_on_exec (val); + set_no_inheritance (read_ready, val); + set_no_inheritance (write_ready, val); + set_no_inheritance (writer_opening, val); + if (reader) + { + set_no_inheritance (owner_needed_evt, val); + set_no_inheritance (owner_found_evt, val); + set_no_inheritance (update_needed_evt, val); + fifo_client_lock (); + for (int i = 0; i < nhandlers; i++) + set_no_inheritance (fc_handler[i].h, val); + fifo_client_unlock (); + } + if (select_sem) + set_no_inheritance (select_sem, val); +} |