Welcome to mirror list, hosted at ThFree Co, Russian Federation.

cygwin.com/git/newlib-cygwin.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'winsup/cygwin/fhandler')
-rw-r--r--winsup/cygwin/fhandler/base.cc1854
-rw-r--r--winsup/cygwin/fhandler/clipboard.cc359
-rw-r--r--winsup/cygwin/fhandler/console.cc4333
-rw-r--r--winsup/cygwin/fhandler/cygdrive.cc158
-rw-r--r--winsup/cygwin/fhandler/dev.cc275
-rw-r--r--winsup/cygwin/fhandler/dev_fd.cc53
-rw-r--r--winsup/cygwin/fhandler/disk_file.cc2673
-rw-r--r--winsup/cygwin/fhandler/dsp.cc1443
-rw-r--r--winsup/cygwin/fhandler/fifo.cc1862
-rw-r--r--winsup/cygwin/fhandler/floppy.cc766
-rw-r--r--winsup/cygwin/fhandler/mqueue.cc1009
-rw-r--r--winsup/cygwin/fhandler/netdrive.cc366
-rw-r--r--winsup/cygwin/fhandler/nodevice.cc28
-rw-r--r--winsup/cygwin/fhandler/null.cc35
-rw-r--r--winsup/cygwin/fhandler/pipe.cc1385
-rw-r--r--winsup/cygwin/fhandler/proc.cc2059
-rw-r--r--winsup/cygwin/fhandler/process.cc1558
-rw-r--r--winsup/cygwin/fhandler/process_fd.cc163
-rw-r--r--winsup/cygwin/fhandler/procnet.cc262
-rw-r--r--winsup/cygwin/fhandler/procsys.cc486
-rw-r--r--winsup/cygwin/fhandler/procsysvipc.cc389
-rw-r--r--winsup/cygwin/fhandler/random.cc138
-rw-r--r--winsup/cygwin/fhandler/raw.cc208
-rw-r--r--winsup/cygwin/fhandler/registry.cc1115
-rw-r--r--winsup/cygwin/fhandler/serial.cc1138
-rw-r--r--winsup/cygwin/fhandler/signalfd.cc160
-rw-r--r--winsup/cygwin/fhandler/socket.cc311
-rw-r--r--winsup/cygwin/fhandler/socket_inet.cc2404
-rw-r--r--winsup/cygwin/fhandler/socket_local.cc1691
-rw-r--r--winsup/cygwin/fhandler/socket_unix.cc2406
-rw-r--r--winsup/cygwin/fhandler/tape.cc1530
-rw-r--r--winsup/cygwin/fhandler/termios.cc720
-rw-r--r--winsup/cygwin/fhandler/timerfd.cc338
-rw-r--r--winsup/cygwin/fhandler/tty.cc4272
-rw-r--r--winsup/cygwin/fhandler/virtual.cc275
-rw-r--r--winsup/cygwin/fhandler/windows.cc161
-rw-r--r--winsup/cygwin/fhandler/zero.cc37
37 files changed, 38420 insertions, 0 deletions
diff --git a/winsup/cygwin/fhandler/base.cc b/winsup/cygwin/fhandler/base.cc
new file mode 100644
index 000000000..b2738cf20
--- /dev/null
+++ b/winsup/cygwin/fhandler/base.cc
@@ -0,0 +1,1854 @@
+/* base.cc. Base functions, inherited by all fhandlers.
+
+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 <unistd.h>
+#include <stdlib.h>
+#include <sys/uio.h>
+#include <cygwin/acl.h>
+#include <sys/param.h>
+#include "cygerrno.h"
+#include "perprocess.h"
+#include "security.h"
+#include "cygwin/version.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "pinfo.h"
+#include <assert.h>
+#include <winioctl.h>
+#include "ntdll.h"
+#include "cygtls.h"
+#include "sigproc.h"
+#include "shared_info.h"
+#include <asm/socket.h>
+#include "cygwait.h"
+
+static const int CHUNK_SIZE = 1024; /* Used for crlf conversions */
+
+struct __cygwin_perfile *perfile_table;
+
+int
+fhandler_base::puts_readahead (const char *s, size_t len)
+{
+ int success = 1;
+ while ((len == (size_t) -1 ? *s : len--)
+ && (success = put_readahead (*s++) > 0))
+ continue;
+ return success;
+}
+
+int
+fhandler_base::put_readahead (char value)
+{
+ char *newrabuf;
+ if (raixput () < rabuflen ())
+ /* Nothing to do */;
+ else if ((newrabuf = (char *) realloc (rabuf (), rabuflen () += 32)))
+ rabuf () = newrabuf;
+ else
+ return 0;
+
+ rabuf ()[raixput ()++] = value;
+ ralen ()++;
+ return 1;
+}
+
+int
+fhandler_base::get_readahead ()
+{
+ int chret = -1;
+ if (raixget () < ralen ())
+ chret = ((unsigned char) rabuf ()[raixget ()++]) & 0xff;
+ /* FIXME - not thread safe */
+ if (raixget () >= ralen ())
+ raixget () = raixput () = ralen () = 0;
+ return chret;
+}
+
+int
+fhandler_base::peek_readahead (int queryput)
+{
+ int chret = -1;
+ if (!queryput && raixget () < ralen ())
+ chret = ((unsigned char) rabuf ()[raixget ()]) & 0xff;
+ else if (queryput && raixput () > 0)
+ chret = ((unsigned char) rabuf ()[raixput () - 1]) & 0xff;
+ return chret;
+}
+
+void
+fhandler_base::set_readahead_valid (int val, int ch)
+{
+ if (!val)
+ ralen () = raixget () = raixput () = 0;
+ if (ch != -1)
+ put_readahead (ch);
+}
+
+int
+fhandler_base::get_readahead_into_buffer (char *buf, size_t buflen)
+{
+ int ch;
+ int copied_chars = 0;
+
+ while (buflen)
+ if ((ch = get_readahead ()) < 0)
+ break;
+ else
+ {
+ buf[copied_chars++] = (unsigned char)(ch & 0xff);
+ buflen--;
+ }
+
+ return copied_chars;
+}
+
+/* Record the file name. and name hash */
+void
+fhandler_base::set_name (path_conv &in_pc)
+{
+ pc << in_pc;
+}
+
+char *fhandler_base::get_proc_fd_name (char *buf)
+{
+ IO_STATUS_BLOCK io;
+ FILE_STANDARD_INFORMATION fsi;
+
+ /* If the file had been opened with O_TMPFILE, don't expose the filename. */
+ if ((get_flags () & O_TMPFILE)
+ || (get_device () == FH_FS
+ && NT_SUCCESS (NtQueryInformationFile (get_handle (), &io,
+ &fsi, sizeof fsi,
+ FileStandardInformation))
+ && fsi.DeletePending))
+ {
+ stpcpy (stpcpy (buf, get_name ()), " (deleted)");
+ return buf;
+ }
+ if (get_name ())
+ return strcpy (buf, get_name ());
+ if (dev ().name ())
+ return strcpy (buf, dev ().name ());
+ return strcpy (buf, "");
+}
+
+/* Detect if we are sitting at EOF for conditions where Windows
+ returns an error but UNIX doesn't. */
+int
+is_at_eof (HANDLE h)
+{
+ IO_STATUS_BLOCK io;
+ FILE_POSITION_INFORMATION fpi;
+ FILE_STANDARD_INFORMATION fsi;
+
+ if (NT_SUCCESS (NtQueryInformationFile (h, &io, &fsi, sizeof fsi,
+ FileStandardInformation))
+ && NT_SUCCESS (NtQueryInformationFile (h, &io, &fpi, sizeof fpi,
+ FilePositionInformation))
+ && fsi.EndOfFile.QuadPart == fpi.CurrentByteOffset.QuadPart)
+ return 1;
+ return 0;
+}
+
+void
+fhandler_base::set_flags (int flags, int supplied_bin)
+{
+ int bin;
+ int fmode;
+ debug_printf ("flags %y, supplied_bin %y", flags, supplied_bin);
+ if ((bin = flags & (O_BINARY | O_TEXT)))
+ debug_printf ("O_TEXT/O_BINARY set in flags %y", bin);
+ else if (rbinset () && wbinset ())
+ bin = rbinary () ? O_BINARY : O_TEXT; // FIXME: Not quite right
+ else if ((fmode = get_default_fmode (flags)) & O_BINARY)
+ bin = O_BINARY;
+ else if (fmode & O_TEXT)
+ bin = O_TEXT;
+ else if (supplied_bin)
+ bin = supplied_bin;
+ else
+ bin = wbinary () || rbinary () ? O_BINARY : O_TEXT;
+
+ openflags = flags | bin;
+
+ bin &= O_BINARY;
+ rbinary (bin ? true : false);
+ wbinary (bin ? true : false);
+ syscall_printf ("filemode set to %s", bin ? "binary" : "text");
+}
+
+/* Normal file i/o handlers. */
+
+/* Cover function to ReadFile to achieve (as much as possible) Posix style
+ semantics and use of errno. */
+void
+fhandler_base::raw_read (void *ptr, size_t& len)
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ int try_noreserve = 1;
+
+retry:
+ status = NtReadFile (get_handle (), NULL, NULL, NULL, &io, ptr, len,
+ NULL, NULL);
+ if (NT_SUCCESS (status))
+ len = io.Information;
+ 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. */
+ len = 0;
+ break;
+ case STATUS_MORE_ENTRIES:
+ case STATUS_BUFFER_OVERFLOW:
+ /* `io.Information' is supposedly valid. */
+ len = io.Information;
+ break;
+ case STATUS_ACCESS_VIOLATION:
+ if (is_at_eof (get_handle ()))
+ {
+ len = 0;
+ break;
+ }
+ if (try_noreserve)
+ {
+ try_noreserve = 0;
+ switch (mmap_is_attached_or_noreserve (ptr, len))
+ {
+ case MMAP_NORESERVE_COMMITED:
+ goto retry;
+ case MMAP_RAISE_SIGBUS:
+ raise(SIGBUS);
+ case MMAP_NONE:
+ break;
+ }
+ }
+ fallthrough;
+ case STATUS_INVALID_DEVICE_REQUEST:
+ case STATUS_INVALID_PARAMETER:
+ case STATUS_INVALID_HANDLE:
+ if (pc.isdir ())
+ {
+ set_errno (EISDIR);
+ len = (size_t) -1;
+ break;
+ }
+ fallthrough;
+ default:
+ __seterrno_from_nt_status (status);
+ len = (size_t) -1;
+ break;
+ }
+ }
+}
+
+/* Cover function to WriteFile to provide Posix interface and semantics
+ (as much as possible). */
+ssize_t
+fhandler_base::raw_write (const void *ptr, size_t len)
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ static _RDATA LARGE_INTEGER off_current =
+ { QuadPart:FILE_USE_FILE_POINTER_POSITION };
+ static _RDATA LARGE_INTEGER off_append =
+ { QuadPart:FILE_WRITE_TO_END_OF_FILE };
+
+ status = NtWriteFile (get_output_handle (), NULL, NULL, NULL, &io,
+ (PVOID) ptr, len,
+ (get_flags () & O_APPEND) ? &off_append : &off_current,
+ NULL);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ if (get_errno () == EPIPE)
+ raise (SIGPIPE);
+ return -1;
+ }
+ return io.Information;
+}
+
+int
+fhandler_base::get_default_fmode (int flags)
+{
+ int fmode = __fmode;
+ if (perfile_table)
+ {
+ size_t nlen = strlen (get_name ());
+ unsigned accflags = (flags & O_ACCMODE);
+ for (__cygwin_perfile *pf = perfile_table; pf->name; pf++)
+ if (!*pf->name && (pf->flags & O_ACCMODE) == accflags)
+ {
+ fmode = pf->flags & ~O_ACCMODE;
+ break;
+ }
+ else
+ {
+ size_t pflen = strlen (pf->name);
+ const char *stem = get_name () + nlen - pflen;
+ if (pflen > nlen || (stem != get_name () && !isdirsep (stem[-1])))
+ continue;
+ else if ((pf->flags & O_ACCMODE) == accflags
+ && pathmatch (stem, pf->name, !!pc.objcaseinsensitive ()))
+ {
+ fmode = pf->flags & ~O_ACCMODE;
+ break;
+ }
+ }
+ }
+ return fmode;
+}
+
+bool
+fhandler_base::device_access_denied (int flags)
+{
+ int mode = 0;
+
+ if (flags & O_PATH)
+ return false;
+
+ if (flags & O_RDWR)
+ mode |= R_OK | W_OK;
+ if (flags & (O_WRONLY | O_APPEND))
+ mode |= W_OK;
+ if (!mode)
+ mode |= R_OK;
+
+ return fhaccess (mode, true);
+}
+
+int
+fhandler_base::fhaccess (int flags, bool effective)
+{
+ int res = -1;
+ if (error ())
+ {
+ set_errno (error ());
+ goto done;
+ }
+
+ if (!exists ())
+ {
+ set_errno (ENOENT);
+ goto done;
+ }
+
+ if (!(flags & (R_OK | W_OK | X_OK)))
+ return 0;
+
+ if (is_fs_special ())
+ /* short circuit */;
+ else if (has_attribute (FILE_ATTRIBUTE_READONLY) && (flags & W_OK)
+ && !pc.isdir ())
+ goto eaccess_done;
+ else if (has_acls ())
+ {
+ res = check_file_access (pc, flags, effective);
+ goto done;
+ }
+ else if (get_device () == FH_REGISTRY && open (O_RDONLY, 0) && get_handle ())
+ {
+ res = check_registry_access (get_handle (), flags, effective);
+ close ();
+ return res;
+ }
+
+ struct stat st;
+ if (fstat (&st))
+ goto done;
+
+ if (flags & R_OK)
+ {
+ if (st.st_uid == (effective ? myself->uid : cygheap->user.real_uid))
+ {
+ if (!(st.st_mode & S_IRUSR))
+ goto eaccess_done;
+ }
+ else if (st.st_gid == (effective ? myself->gid : cygheap->user.real_gid))
+ {
+ if (!(st.st_mode & S_IRGRP))
+ goto eaccess_done;
+ }
+ else if (!(st.st_mode & S_IROTH))
+ goto eaccess_done;
+ }
+
+ if (flags & W_OK)
+ {
+ if (st.st_uid == (effective ? myself->uid : cygheap->user.real_uid))
+ {
+ if (!(st.st_mode & S_IWUSR))
+ goto eaccess_done;
+ }
+ else if (st.st_gid == (effective ? myself->gid : cygheap->user.real_gid))
+ {
+ if (!(st.st_mode & S_IWGRP))
+ goto eaccess_done;
+ }
+ else if (!(st.st_mode & S_IWOTH))
+ goto eaccess_done;
+ }
+
+ if (flags & X_OK)
+ {
+ if (st.st_uid == (effective ? myself->uid : cygheap->user.real_uid))
+ {
+ if (!(st.st_mode & S_IXUSR))
+ goto eaccess_done;
+ }
+ else if (st.st_gid == (effective ? myself->gid : cygheap->user.real_gid))
+ {
+ if (!(st.st_mode & S_IXGRP))
+ goto eaccess_done;
+ }
+ else if (!(st.st_mode & S_IXOTH))
+ goto eaccess_done;
+ }
+
+ res = 0;
+ goto done;
+
+eaccess_done:
+ set_errno (EACCES);
+done:
+ if (!res && (flags & W_OK) && get_device () == FH_FS
+ && (pc.fs_flags () & FILE_READ_ONLY_VOLUME))
+ {
+ set_errno (EROFS);
+ res = -1;
+ }
+ debug_printf ("returning %d", res);
+ return res;
+}
+
+int
+fhandler_base::open_with_arch (int flags, mode_t mode)
+{
+ int res;
+ if (!(res = (archetype && archetype->io_handle)
+ || open (flags, mode & 07777)))
+ {
+ if (archetype && archetype->usecount == 0)
+ cygheap->fdtab.delete_archetype (archetype);
+ }
+ else if (archetype)
+ {
+ if (!archetype->get_handle ())
+ {
+ archetype->copy_from (this);
+ archetype_usecount (1);
+ archetype->archetype = NULL;
+ usecount = 0;
+ }
+ else
+ {
+ char *name;
+ /* Preserve any name (like /dev/tty) derived from build_fh_pc. */
+ if (!get_name ())
+ name = NULL;
+ else
+ {
+ name = (char *) alloca (strlen (get_name ()) + 1);
+ strcpy (name, get_name ());
+ }
+ fhandler_base *arch = archetype;
+ copy_from (archetype);
+ if (name)
+ set_name (name);
+ archetype = arch;
+ archetype_usecount (1);
+ usecount = 0;
+ }
+ if (!open_setup (flags))
+ api_fatal ("open_setup failed, %E");
+ }
+
+ close_on_exec (flags & O_CLOEXEC);
+ /* A unique ID is necessary to recognize fhandler entries which are
+ duplicated by dup(2) or fork(2). This is used in BSD flock calls
+ to identify the descriptor. Skip nohandle fhandlers since advisory
+ locking is unusable for those anyway. */
+ if (!nohandle ())
+ set_unique_id ();
+ return res;
+}
+
+/* Open a fake handle to \\Device\\Null. This is a helper function for
+ fhandlers which just need some handle to keep track of BSD flock locks. */
+int
+fhandler_base::open_null (int flags)
+{
+ int res = 0;
+ HANDLE fh;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ NTSTATUS status;
+
+ InitializeObjectAttributes (&attr, &ro_u_null, OBJ_CASE_INSENSITIVE |
+ ((flags & O_CLOEXEC) ? 0 : OBJ_INHERIT),
+ NULL, NULL);
+ status = NtCreateFile (&fh, GENERIC_READ | SYNCHRONIZE, &attr, &io, NULL, 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN,
+ FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ goto done;
+ }
+ set_handle (fh);
+ set_flags (flags, pc.binmode ());
+ res = 1;
+ set_open_status ();
+done:
+ debug_printf ("%y = NtCreateFile (%p, ... %S ...)", status, fh, &ro_u_null);
+ syscall_printf ("%d = fhandler_base::open_null (%y)", res, flags);
+ return res;
+}
+
+/* Open system call handler function. */
+int
+fhandler_base::open (int flags, mode_t mode)
+{
+ int res = 0;
+ HANDLE fh;
+ ULONG file_attributes = 0;
+ ULONG shared = (get_major () == DEV_TAPE_MAJOR ? 0 : FILE_SHARE_VALID_FLAGS);
+ ULONG create_disposition;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ NTSTATUS status;
+ PFILE_FULL_EA_INFORMATION p = NULL;
+ ULONG plen = 0;
+
+ syscall_printf ("(%S, %y)%s", pc.get_nt_native_path (), flags,
+ get_handle () ? " by handle" : "");
+
+ if (flags & O_PATH)
+ query_open (query_read_attributes);
+
+ /* Allow to reopen from handle. This is utilized by
+ open ("/proc/PID/fd/DESCRIPTOR", ...); */
+ if (get_handle ())
+ {
+ pc.init_reopen_attr (attr, get_handle ());
+ if (!(flags & O_CLOEXEC))
+ attr.Attributes |= OBJ_INHERIT;
+ if (pc.has_buggy_reopen ())
+ debug_printf ("Reopen by handle requested but FS doesn't support it");
+ }
+ else
+ pc.get_object_attr (attr, *sec_none_cloexec (flags));
+
+ options = FILE_OPEN_FOR_BACKUP_INTENT;
+ switch (query_open ())
+ {
+ case query_read_control:
+ access = READ_CONTROL;
+ break;
+ case query_read_attributes:
+ access = READ_CONTROL | FILE_READ_ATTRIBUTES;
+ break;
+ case query_write_control:
+ access = READ_CONTROL | WRITE_OWNER | WRITE_DAC | FILE_WRITE_ATTRIBUTES;
+ break;
+ case query_write_dac:
+ access = READ_CONTROL | WRITE_DAC | FILE_WRITE_ATTRIBUTES;
+ break;
+ case query_write_attributes:
+ access = READ_CONTROL | FILE_WRITE_ATTRIBUTES;
+ break;
+ default:
+ switch (flags & O_ACCMODE)
+ {
+ case O_RDONLY:
+ access = GENERIC_READ;
+ break;
+ case O_WRONLY:
+ access = GENERIC_WRITE | READ_CONTROL | FILE_READ_ATTRIBUTES;
+ break;
+ default:
+ access = GENERIC_READ | GENERIC_WRITE;
+ break;
+ }
+ if (flags & O_SYNC)
+ options |= FILE_WRITE_THROUGH;
+ if (flags & O_DIRECT)
+ options |= FILE_NO_INTERMEDIATE_BUFFERING;
+ if (get_major () != DEV_SERIAL_MAJOR && get_major () != DEV_TAPE_MAJOR)
+ {
+ options |= FILE_SYNCHRONOUS_IO_NONALERT;
+ access |= SYNCHRONIZE;
+ }
+ break;
+ }
+
+ /* Don't use the FILE_OVERWRITE{_IF} flags here. See below for an
+ explanation, why that's not such a good idea. */
+ if (((flags & O_EXCL) && (flags & O_CREAT)) || (flags & O_TMPFILE))
+ create_disposition = FILE_CREATE;
+ else
+ create_disposition = (flags & O_CREAT) ? FILE_OPEN_IF : FILE_OPEN;
+
+ if (get_device () == FH_FS
+#ifdef __WITH_AF_UNIX
+ || get_device () == FH_UNIX
+#endif
+ )
+ {
+ /* Add the reparse point flag to known reparse points, otherwise we
+ open the target, not the reparse point. This would break lstat. */
+ if (pc.is_known_reparse_point ())
+ options |= FILE_OPEN_REPARSE_POINT;
+ }
+
+ if (get_device () == FH_FS)
+ {
+ /* O_TMPFILE files are created with delete-on-close semantics, as well
+ as with FILE_ATTRIBUTE_TEMPORARY. The latter speeds up file access,
+ because the OS tries to keep the file in memory as much as possible.
+ In conjunction with FILE_DELETE_ON_CLOSE, ideally the OS never has
+ to write to the disk at all.
+ Note that O_TMPFILE_FILE_ATTRS also sets the DOS HIDDEN attribute
+ to help telling Cygwin O_TMPFILE files apart from other files
+ accidentally setting FILE_ATTRIBUTE_TEMPORARY. */
+ if (flags & O_TMPFILE)
+ {
+ access |= DELETE;
+ file_attributes |= O_TMPFILE_FILE_ATTRS;
+ options |= FILE_DELETE_ON_CLOSE;
+ }
+
+ if (pc.fs_is_nfs ())
+ {
+ /* Make sure we can read EAs of files on an NFS share. Also make
+ sure that we're going to act on the file itself, even if it's a
+ a symlink. */
+ access |= FILE_READ_EA;
+ if (query_open ())
+ {
+ if (query_open () >= query_write_control)
+ access |= FILE_WRITE_EA;
+ plen = sizeof nfs_aol_ffei;
+ p = (PFILE_FULL_EA_INFORMATION) &nfs_aol_ffei;
+ }
+ }
+
+ if (flags & (O_CREAT | O_TMPFILE))
+ {
+ file_attributes |= FILE_ATTRIBUTE_NORMAL;
+
+ if (pc.fs_is_nfs ())
+ {
+ /* When creating a file on an NFS share, we have to set the
+ file mode by writing a NFS fattr3 structure with the
+ correct mode bits set. */
+ access |= FILE_WRITE_EA;
+ plen = sizeof (FILE_FULL_EA_INFORMATION) + sizeof (NFS_V3_ATTR)
+ + sizeof (fattr3);
+ p = (PFILE_FULL_EA_INFORMATION) alloca (plen);
+ p->NextEntryOffset = 0;
+ p->Flags = 0;
+ p->EaNameLength = sizeof (NFS_V3_ATTR) - 1;
+ p->EaValueLength = sizeof (fattr3);
+ strcpy (p->EaName, NFS_V3_ATTR);
+ fattr3 *nfs_attr = (fattr3 *) (p->EaName
+ + p->EaNameLength + 1);
+ memset (nfs_attr, 0, sizeof (fattr3));
+ nfs_attr->type = NF3REG;
+ nfs_attr->mode = (mode & 07777) & ~cygheap->umask;
+ }
+ else if (!has_acls ()
+ && !(mode & ~cygheap->umask & (S_IWUSR | S_IWGRP | S_IWOTH)))
+ /* If mode has no write bits set, and ACLs are not used, we set
+ the DOS R/O attribute. */
+ file_attributes |= FILE_ATTRIBUTE_READONLY;
+ /* Never set the WRITE_DAC flag here. Calls to fstat may return
+ wrong st_ctime information after calls to fchmod, fchown, etc
+ because Windows only guarantees the update of metadata when
+ the handle is closed or flushed. However, flushing the file
+ on every fstat to enforce POSIXy stat behaviour is excessivly
+ slow, compared to an extra open/close to change the file's
+ security descriptor. */
+ }
+ }
+
+ status = NtCreateFile (&fh, access, &attr, &io, NULL, file_attributes, shared,
+ create_disposition, options, p, plen);
+ /* Pre-W10, we can't reopen a file by handle with delete disposition
+ set, so we have to lie our ass off. */
+ if (get_handle () && status == STATUS_DELETE_PENDING)
+ {
+ BOOL ret = DuplicateHandle (GetCurrentProcess (), get_handle (),
+ GetCurrentProcess (), &fh,
+ access, !(flags & O_CLOEXEC), 0);
+ if (!ret)
+ ret = DuplicateHandle (GetCurrentProcess (), get_handle (),
+ GetCurrentProcess (), &fh,
+ 0, !(flags & O_CLOEXEC),
+ DUPLICATE_SAME_ACCESS);
+ if (!ret)
+ debug_printf ("DuplicateHandle after STATUS_DELETE_PENDING, %E");
+ else
+ status = STATUS_SUCCESS;
+ }
+ if (!NT_SUCCESS (status))
+ {
+ /* Trying to create a directory should return EISDIR, not ENOENT. */
+ PUNICODE_STRING upath = pc.get_nt_native_path ();
+ if (status == STATUS_OBJECT_NAME_INVALID && (flags & O_CREAT)
+ && upath->Buffer[upath->Length / sizeof (WCHAR) - 1] == '\\')
+ set_errno (EISDIR);
+ else
+ __seterrno_from_nt_status (status);
+ if (!nohandle ())
+ goto done;
+ }
+
+ if (io.Information == FILE_CREATED)
+ {
+ /* Correct file attributes are needed for later use in, e.g. fchmod. */
+ FILE_BASIC_INFORMATION fbi;
+
+ if (!NT_SUCCESS (NtQueryInformationFile (fh, &io, &fbi, sizeof fbi,
+ FileBasicInformation)))
+ fbi.FileAttributes = file_attributes | FILE_ATTRIBUTE_ARCHIVE;
+ pc.file_attributes (fbi.FileAttributes);
+
+ /* Always create files using a NULL SD. Create correct permission bits
+ afterwards, maintaining the owner and group information just like
+ chmod. This is done for two reasons.
+
+ On Windows filesystems we need to create the file with default
+ permissions to allow inheriting ACEs. When providing an explicit DACL
+ in calls to [Nt]CreateFile, the created file will not inherit default
+ permissions from the parent object. This breaks not only Windows
+ inheritance, but also POSIX ACL inheritance.
+
+ Another reason to do this are remote shares. Files on a remote share
+ are created as the user used for authentication. In a domain that's
+ usually the user you're logged in as. Outside of a domain you're
+ authenticating using a local user account on the sharing machine.
+ If the SIDs of the client machine are used, that's entirely unexpected
+ behaviour. Doing it like we do here creates the expected SD in a
+ domain as well as on standalone servers. This is the result of a
+ discussion on the samba-technical list, starting at
+ http://lists.samba.org/archive/samba-technical/2008-July/060247.html */
+ if (has_acls ())
+ set_created_file_access (fh, pc, mode);
+ }
+
+ /* If you O_TRUNC a file on Linux, the data is truncated, but the EAs are
+ preserved. If you open a file on Windows with FILE_OVERWRITE{_IF} or
+ FILE_SUPERSEDE, all streams are truncated, including the EAs. So we don't
+ use the FILE_OVERWRITE{_IF} flags, but instead just open the file and set
+ the size of the data stream explicitely to 0. Apart from being more Linux
+ compatible, this implementation has the pleasant side-effect to be more
+ than 5% faster than using FILE_OVERWRITE{_IF} (tested on W7 32 bit). */
+ if ((flags & O_TRUNC)
+ && (flags & O_ACCMODE) != O_RDONLY
+ && io.Information != FILE_CREATED
+ && get_device () == FH_FS)
+ {
+ FILE_END_OF_FILE_INFORMATION feofi = { EndOfFile:{ QuadPart:0 } };
+ status = NtSetInformationFile (fh, &io, &feofi, sizeof feofi,
+ FileEndOfFileInformation);
+ /* In theory, truncating the file should never fail, since the opened
+ handle has FILE_WRITE_DATA permissions, which is all you need to
+ be allowed to truncate a file. Better safe than sorry. */
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ NtClose (fh);
+ goto done;
+ }
+ }
+
+ set_handle (fh);
+ set_flags (flags, pc.binmode ());
+
+ res = 1;
+ set_open_status ();
+done:
+ debug_printf ("%y = NtCreateFile "
+ "(%p, %y, %S, io, NULL, %y, %y, %y, %y, NULL, 0)",
+ status, fh, access, pc.get_nt_native_path (), file_attributes,
+ shared, create_disposition, options);
+
+ syscall_printf ("%d = fhandler_base::open(%S, %y)",
+ res, pc.get_nt_native_path (), flags);
+ return res;
+}
+
+fhandler_base *
+fhandler_base::fd_reopen (int, mode_t)
+{
+ /* This is implemented in fhandler_process only. */
+ return NULL;
+}
+
+bool
+fhandler_base::open_setup (int)
+{
+ return true;
+}
+
+/* states:
+ open buffer in binary mode? Just do the read.
+
+ open buffer in text mode? Scan buffer for control zs and handle
+ the first one found. Then scan buffer, converting every \r\n into
+ an \n. If last char is an \r, look ahead one more char, if \n then
+ modify \r, if not, remember char.
+*/
+void
+fhandler_base::read (void *in_ptr, size_t& len)
+{
+ char *ptr = (char *) in_ptr;
+ ssize_t copied_chars = get_readahead_into_buffer (ptr, len);
+
+ if (copied_chars || !len)
+ {
+ len = (size_t) copied_chars;
+ goto out;
+ }
+
+ raw_read (ptr, len);
+
+ if (rbinary () || (ssize_t) len <= 0)
+ goto out;
+
+ /* Scan buffer and turn \r\n into \n */
+ char *src, *dst, *end;
+ src = (char *) ptr;
+ dst = (char *) ptr;
+ end = src + len - 1;
+
+ /* Read up to the last but one char - the last char needs special handling */
+ while (src < end)
+ {
+ if (*src == '\r' && src[1] == '\n')
+ src++;
+ *dst++ = *src++;
+ }
+
+ /* If not beyond end and last char is a '\r' then read one more
+ to see if we should translate this one too */
+ if (src > end)
+ /* nothing */;
+ else if (*src != '\r')
+ *dst++ = *src;
+ else
+ {
+ char c1;
+ size_t c1len = 1;
+ raw_read (&c1, c1len);
+ if (c1len <= 0)
+ /* nothing */;
+ else if (c1 == '\n')
+ *dst++ = '\n';
+ else
+ {
+ set_readahead_valid (1, c1);
+ *dst++ = *src;
+ }
+ }
+
+ len = dst - (char *) ptr;
+
+out:
+ debug_printf ("returning %d, %s mode", len, rbinary () ? "binary" : "text");
+}
+
+ssize_t
+fhandler_base::write (const void *ptr, size_t len)
+{
+ ssize_t res;
+
+ if (did_lseek ())
+ {
+ IO_STATUS_BLOCK io;
+ FILE_POSITION_INFORMATION fpi;
+ FILE_STANDARD_INFORMATION fsi;
+
+ did_lseek (false); /* don't do it again */
+
+ if (!(get_flags () & O_APPEND)
+ && !has_attribute (FILE_ATTRIBUTE_SPARSE_FILE)
+ && NT_SUCCESS (NtQueryInformationFile (get_output_handle (),
+ &io, &fsi, sizeof fsi,
+ FileStandardInformation))
+ && NT_SUCCESS (NtQueryInformationFile (get_output_handle (),
+ &io, &fpi, sizeof fpi,
+ FilePositionInformation))
+ && fpi.CurrentByteOffset.QuadPart
+ >= fsi.EndOfFile.QuadPart + (128 * 1024))
+ {
+ /* If the file system supports sparse files and the application
+ is writing after a long seek beyond EOF, convert the file to
+ a sparse file. */
+ NTSTATUS status;
+ status = NtFsControlFile (get_output_handle (), NULL, NULL, NULL,
+ &io, FSCTL_SET_SPARSE, NULL, 0, NULL, 0);
+ if (NT_SUCCESS (status))
+ pc.file_attributes (pc.file_attributes ()
+ | FILE_ATTRIBUTE_SPARSE_FILE);
+ debug_printf ("%y = NtFsControlFile(%S, FSCTL_SET_SPARSE)",
+ status, pc.get_nt_native_path ());
+ }
+ }
+
+ if (wbinary ())
+ res = raw_write (ptr, len);
+ else
+ {
+ debug_printf ("text write");
+ /* This is the Microsoft/DJGPP way. Still not ideal, but it's
+ compatible.
+ Modified slightly by CGF 2000-10-07 */
+
+ int left_in_data = len;
+ char *data = (char *)ptr;
+ res = 0;
+
+ while (left_in_data > 0)
+ {
+ char buf[CHUNK_SIZE + 1], *buf_ptr = buf;
+ int left_in_buf = CHUNK_SIZE;
+
+ while (left_in_buf > 0 && left_in_data > 0)
+ {
+ char ch = *data++;
+ if (ch == '\n')
+ {
+ *buf_ptr++ = '\r';
+ left_in_buf--;
+ }
+ *buf_ptr++ = ch;
+ left_in_buf--;
+ left_in_data--;
+ if (left_in_data > 0 && ch == '\r' && *data == '\n')
+ {
+ *buf_ptr++ = *data++;
+ left_in_buf--;
+ left_in_data--;
+ }
+ }
+
+ /* We've got a buffer-full, or we're out of data. Write it out */
+ ssize_t nbytes;
+ ptrdiff_t want = buf_ptr - buf;
+ if ((nbytes = raw_write (buf, (size_t) want)) == want)
+ {
+ /* Keep track of how much written not counting additional \r's */
+ res = data - (char *)ptr;
+ continue;
+ }
+
+ if (nbytes == -1)
+ res = -1; /* Error */
+ else
+ res += nbytes; /* Partial write. Return total bytes written. */
+ break; /* All done */
+ }
+ }
+
+ return res;
+}
+
+ssize_t
+fhandler_base::readv (const struct iovec *const iov, const int iovcnt,
+ ssize_t tot)
+{
+ assert (iov);
+ assert (iovcnt >= 1);
+
+ size_t len = tot;
+ if (iovcnt == 1)
+ {
+ len = iov->iov_len;
+ read (iov->iov_base, len);
+ return len;
+ }
+
+ if (tot == -1) // i.e. if not pre-calculated by the caller.
+ {
+ len = 0;
+ const struct iovec *iovptr = iov + iovcnt;
+ do
+ {
+ iovptr -= 1;
+ len += iovptr->iov_len;
+ }
+ while (iovptr != iov);
+ }
+
+ if (!len)
+ return 0;
+
+ char *buf = (char *) malloc (len);
+
+ if (!buf)
+ {
+ set_errno (ENOMEM);
+ return -1;
+ }
+
+ read (buf, len);
+ ssize_t nbytes = (ssize_t) len;
+
+ const struct iovec *iovptr = iov;
+
+ char *p = buf;
+ while (nbytes > 0)
+ {
+ const int frag = MIN (nbytes, (ssize_t) iovptr->iov_len);
+ memcpy (iovptr->iov_base, p, frag);
+ p += frag;
+ iovptr += 1;
+ nbytes -= frag;
+ }
+
+ free (buf);
+ return len;
+}
+
+ssize_t
+fhandler_base::writev (const struct iovec *const iov, const int iovcnt,
+ ssize_t tot)
+{
+ assert (iov);
+ assert (iovcnt >= 1);
+
+ if (iovcnt == 1)
+ return write (iov->iov_base, iov->iov_len);
+
+ if (tot == -1) // i.e. if not pre-calculated by the caller.
+ {
+ tot = 0;
+ const struct iovec *iovptr = iov + iovcnt;
+ do
+ {
+ iovptr -= 1;
+ tot += iovptr->iov_len;
+ }
+ while (iovptr != iov);
+ }
+
+ assert (tot >= 0);
+
+ if (tot == 0)
+ return 0;
+
+ char *const buf = (char *) malloc (tot);
+
+ if (!buf)
+ {
+ set_errno (ENOMEM);
+ return -1;
+ }
+
+ char *bufptr = buf;
+ const struct iovec *iovptr = iov;
+ int nbytes = tot;
+
+ while (nbytes != 0)
+ {
+ const int frag = MIN (nbytes, (ssize_t) iovptr->iov_len);
+ memcpy (bufptr, iovptr->iov_base, frag);
+ bufptr += frag;
+ iovptr += 1;
+ nbytes -= frag;
+ }
+ ssize_t ret = write (buf, tot);
+ free (buf);
+ return ret;
+}
+
+off_t
+fhandler_base::lseek (off_t offset, int whence)
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ FILE_POSITION_INFORMATION fpi;
+ FILE_STANDARD_INFORMATION fsi;
+
+ /* Seeks on text files is tough, we rewind and read till we get to the
+ right place. */
+
+ if (whence != SEEK_CUR || offset != 0)
+ {
+ if (whence == SEEK_CUR)
+ offset -= ralen () - raixget ();
+ set_readahead_valid (0);
+ }
+
+ switch (whence)
+ {
+ case SEEK_SET:
+ fpi.CurrentByteOffset.QuadPart = offset;
+ break;
+ case SEEK_CUR:
+ status = NtQueryInformationFile (get_handle (), &io, &fpi, sizeof fpi,
+ FilePositionInformation);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ fpi.CurrentByteOffset.QuadPart += offset;
+ break;
+ default: /* SEEK_END */
+ status = NtQueryInformationFile (get_handle (), &io, &fsi, sizeof fsi,
+ FileStandardInformation);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ fpi.CurrentByteOffset.QuadPart = fsi.EndOfFile.QuadPart + offset;
+ break;
+ }
+
+ debug_printf ("setting file pointer to %U", fpi.CurrentByteOffset.QuadPart);
+ status = NtSetInformationFile (get_handle (), &io, &fpi, sizeof fpi,
+ FilePositionInformation);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ off_t res = fpi.CurrentByteOffset.QuadPart;
+
+ /* When next we write(), we will check to see if *this* seek went beyond
+ the end of the file and if so, potentially sparsify the file. */
+ if (pc.support_sparse ())
+ did_lseek (true);
+
+ /* If this was a SEEK_CUR with offset 0, we still might have
+ readahead that we have to take into account when calculating
+ the actual position for the application. */
+ if (whence == SEEK_CUR)
+ res -= ralen () - raixget ();
+
+ return res;
+}
+
+ssize_t
+fhandler_base::pread (void *, size_t, off_t, void *)
+{
+ set_errno (ESPIPE);
+ return -1;
+}
+
+ssize_t
+fhandler_base::pwrite (void *, size_t, off_t, void *)
+{
+ set_errno (ESPIPE);
+ return -1;
+}
+
+int
+fhandler_base::close_with_arch ()
+{
+ int res;
+ fhandler_base *fh;
+ if (usecount)
+ {
+ /* This was the archetype itself. */
+ if (--usecount)
+ {
+ debug_printf ("not closing passed in archetype %p, usecount %d", archetype, usecount);
+ return 0;
+ }
+ debug_printf ("closing passed in archetype %p, usecount %d", archetype, usecount);
+ /* Set archetype temporarily so that it will eventually be deleted. */
+ archetype = fh = this;
+ }
+ else if (!archetype)
+ fh = this;
+ else if (archetype_usecount (-1) == 0)
+ {
+ debug_printf ("closing archetype");
+ fh = archetype;
+ }
+ else
+ {
+ debug_printf ("not closing archetype");
+ return 0;
+ }
+
+ cleanup ();
+ res = fh->close ();
+ if (archetype)
+ {
+ cygheap->fdtab.delete_archetype (archetype);
+ archetype = NULL;
+ }
+ return res;
+}
+
+void
+fhandler_base::cleanup ()
+{
+ /* Delete all POSIX locks on the file. Delete all flock locks on the
+ file if this is the last reference to this file. */
+ if (unique_id)
+ del_my_locks (on_close);
+}
+
+int
+fhandler_base::close ()
+{
+ int res = -1;
+
+ syscall_printf ("closing '%s' handle %p", get_name (), get_handle ());
+ if (nohandle () || CloseHandle (get_handle ()))
+ res = 0;
+ else
+ {
+ paranoid_printf ("CloseHandle failed, %E");
+ __seterrno ();
+ }
+ return res;
+}
+
+int
+fhandler_base::ioctl (unsigned int cmd, void *buf)
+{
+ int res;
+
+ switch (cmd)
+ {
+ case FIONBIO:
+ set_nonblocking (*(int *) buf);
+ res = 0;
+ break;
+ case FIONREAD:
+ case TIOCSCTTY:
+ set_errno (ENOTTY);
+ res = -1;
+ break;
+ default:
+ set_errno (EINVAL);
+ res = -1;
+ break;
+ }
+
+ syscall_printf ("%d = ioctl(%x, %p)", res, cmd, buf);
+ return res;
+}
+
+int
+fhandler_base::fstat (struct stat *buf)
+{
+ if (is_fs_special ())
+ return fstat_fs (buf);
+
+ switch (get_device ())
+ {
+ case FH_PIPE:
+ buf->st_mode = S_IFIFO | S_IRUSR | S_IWUSR;
+ break;
+ case FH_PIPEW:
+ buf->st_mode = S_IFIFO | S_IWUSR;
+ break;
+ case FH_PIPER:
+ buf->st_mode = S_IFIFO | S_IRUSR;
+ break;
+ default:
+ buf->st_mode = S_IFCHR | STD_RBITS | STD_WBITS | S_IWGRP | S_IWOTH;
+ break;
+ }
+
+ buf->st_uid = geteuid ();
+ buf->st_gid = getegid ();
+ buf->st_nlink = 1;
+ buf->st_blksize = PREFERRED_IO_BLKSIZE;
+
+ buf->st_ctim.tv_sec = 1164931200L; /* Arbitrary value: 2006-12-01 */
+ buf->st_ctim.tv_nsec = 0L;
+ buf->st_birthtim = buf->st_ctim;
+ buf->st_mtim.tv_sec = time (NULL); /* Arbitrary value: current time,
+ like Linux */
+ buf->st_mtim.tv_nsec = 0L;
+ buf->st_atim = buf->st_mtim;
+
+ return 0;
+}
+
+int
+fhandler_base::fstatvfs (struct statvfs *sfs)
+{
+ /* If we hit this base implementation, it's some device in /dev.
+ Just call statvfs on /dev for simplicity. */
+ path_conv pc ("/dev", PC_KEEP_HANDLE);
+ fhandler_disk_file fh (pc);
+ return fh.fstatvfs (sfs);
+}
+
+int
+fhandler_base::init (HANDLE f, DWORD a, mode_t bin)
+{
+ set_handle (f);
+ access = a;
+ a &= GENERIC_READ | GENERIC_WRITE;
+ int flags = 0;
+ if (a == GENERIC_READ)
+ flags = O_RDONLY;
+ else if (a == GENERIC_WRITE)
+ flags = O_WRONLY;
+ else if (a == (GENERIC_READ | GENERIC_WRITE))
+ flags = O_RDWR;
+ set_flags (flags | bin);
+ set_open_status ();
+ debug_printf ("created new fhandler_base for handle %p, bin %d", f, rbinary ());
+ return 1;
+}
+
+int
+fhandler_base::dup (fhandler_base *child, int flags)
+{
+ debug_printf ("in fhandler_base dup");
+
+ HANDLE nh;
+ if (!nohandle () && !archetype)
+ {
+ if (!DuplicateHandle (GetCurrentProcess (), get_handle (),
+ GetCurrentProcess (), &nh,
+ 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS))
+ {
+ debug_printf ("dup(%s) failed, handle %p, %E",
+ get_name (), get_handle ());
+ __seterrno ();
+ return -1;
+ }
+
+ VerifyHandle (nh);
+ child->set_handle (nh);
+ }
+ return 0;
+}
+
+int fhandler_base::fcntl (int cmd, intptr_t arg)
+{
+ int res;
+
+ switch (cmd)
+ {
+ case F_GETFD:
+ res = close_on_exec () ? FD_CLOEXEC : 0;
+ break;
+ case F_SETFD:
+ set_close_on_exec ((arg & FD_CLOEXEC) ? 1 : 0);
+ res = 0;
+ break;
+ case F_GETFL:
+ res = get_flags ();
+ debug_printf ("GETFL: %y", res);
+ break;
+ case F_SETFL:
+ {
+ /* Only O_APPEND, O_ASYNC and O_NONBLOCK are allowed.
+ Each other flag will be ignored.
+ Since O_ASYNC isn't defined in fcntl.h it's currently
+ ignored as well. */
+ const int allowed_flags = O_APPEND | O_NONBLOCK;
+ int new_flags = arg & allowed_flags;
+ set_flags ((get_flags () & ~allowed_flags) | new_flags);
+ }
+ res = 0;
+ break;
+ case F_GETLK:
+ case F_SETLK:
+ case F_SETLKW:
+ {
+ struct flock *fl = (struct flock *) arg;
+ fl->l_type &= F_RDLCK | F_WRLCK | F_UNLCK;
+ res = mandatory_locking () ? mand_lock (cmd, fl) : lock (cmd, fl);
+ }
+ break;
+ default:
+ set_errno (EINVAL);
+ res = -1;
+ break;
+ }
+ return res;
+}
+
+/* Base terminal handlers. These just return errors. */
+
+int
+fhandler_base::tcflush (int)
+{
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_base::tcsendbreak (int)
+{
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_base::tcdrain ()
+{
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_base::tcflow (int)
+{
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_base::tcsetattr (int, const struct termios *)
+{
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_base::tcgetattr (struct termios *)
+{
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_base::tcsetpgrp (const pid_t)
+{
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_base::tcgetpgrp ()
+{
+ set_errno (ENOTTY);
+ return -1;
+}
+
+pid_t
+fhandler_base::tcgetsid ()
+{
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_base::ptsname_r (char *, size_t)
+{
+ set_errno (ENOTTY);
+ return ENOTTY;
+}
+
+/* Normal I/O constructor */
+fhandler_base::fhandler_base () :
+ status (),
+ open_status (),
+ access (0),
+ io_handle (NULL),
+ ino (0),
+ _refcnt (0),
+ openflags (0),
+ unique_id (0),
+ select_sem (NULL),
+ archetype (NULL),
+ usecount (0)
+{
+ ra.rabuf = NULL;
+ ra.ralen = 0;
+ ra.raixget = 0;
+ ra.raixput = 0;
+ ra.rabuflen = 0;
+ isclosed (false);
+}
+
+/* Normal I/O destructor */
+fhandler_base::~fhandler_base ()
+{
+ if (ra.rabuf)
+ free (ra.rabuf);
+}
+
+void
+fhandler_base::set_no_inheritance (HANDLE &h, bool not_inheriting)
+{
+ if (!SetHandleInformation (h, HANDLE_FLAG_INHERIT,
+ not_inheriting ? 0 : HANDLE_FLAG_INHERIT))
+ debug_printf ("SetHandleInformation failed, %E");
+#ifdef DEBUGGING_AND_FDS_PROTECTED
+ if (h)
+ setclexec (oh, h, not_inheriting);
+#endif
+}
+
+bool
+fhandler_base::fork_fixup (HANDLE parent, HANDLE &h, const char *name)
+{
+ HANDLE oh = h;
+ bool res = false;
+ if (!close_on_exec ())
+ debug_printf ("handle %p already opened", h);
+ else if (!DuplicateHandle (parent, h, GetCurrentProcess (), &h,
+ 0, !close_on_exec (), DUPLICATE_SAME_ACCESS))
+ system_printf ("%s - %E, handle %s<%p>", get_name (), name, h);
+ else
+ {
+ if (oh != h)
+ VerifyHandle (h);
+ res = true;
+ }
+ return res;
+}
+
+void
+fhandler_base::set_close_on_exec (bool val)
+{
+ if (!nohandle ())
+ set_no_inheritance (io_handle, val);
+ close_on_exec (val);
+ debug_printf ("set close_on_exec for %s to %d", get_name (), val);
+}
+
+void
+fhandler_base::fixup_after_fork (HANDLE parent)
+{
+ debug_printf ("inheriting '%s' from parent", get_name ());
+ if (!nohandle ())
+ fork_fixup (parent, io_handle, "io_handle");
+ /* POSIX locks are not inherited across fork. */
+ if (unique_id)
+ del_my_locks (after_fork);
+}
+
+void
+fhandler_base::fixup_after_exec ()
+{
+ debug_printf ("here for '%s'", get_name ());
+ if (unique_id && close_on_exec ())
+ del_my_locks (after_exec);
+ mandatory_locking (false);
+}
+
+bool
+fhandler_base::is_nonblocking ()
+{
+ return (openflags & O_NONBLOCK) != 0;
+}
+
+void
+fhandler_base::set_nonblocking (int yes)
+{
+ int current = openflags & O_NONBLOCK;
+ int new_flags = yes ? (!current ? O_NONBLOCK : current) : 0;
+ openflags = (openflags & ~O_NONBLOCK) | new_flags;
+}
+
+int
+fhandler_base::mkdir (mode_t)
+{
+ if (exists ())
+ set_errno (EEXIST);
+ else
+ set_errno (EROFS);
+ return -1;
+}
+
+int
+fhandler_base::rmdir ()
+{
+ if (!exists ())
+ set_errno (ENOENT);
+ else if (!pc.isdir ())
+ set_errno (ENOTDIR);
+ else
+ set_errno (EROFS);
+ return -1;
+}
+
+DIR *
+fhandler_base::opendir (int fd)
+{
+ set_errno (ENOTDIR);
+ return NULL;
+}
+
+int
+fhandler_base::readdir (DIR *, dirent *)
+{
+ return ENOTDIR;
+}
+
+long
+fhandler_base::telldir (DIR *)
+{
+ set_errno (ENOTDIR);
+ return -1;
+}
+
+void
+fhandler_base::seekdir (DIR *, long)
+{
+ set_errno (ENOTDIR);
+}
+
+void
+fhandler_base::rewinddir (DIR *)
+{
+ set_errno (ENOTDIR);
+}
+
+int
+fhandler_base::closedir (DIR *)
+{
+ set_errno (ENOTDIR);
+ return -1;
+}
+
+int
+fhandler_base::fchmod (mode_t mode)
+{
+ if (pc.is_fs_special ())
+ return chmod_device (pc, mode);
+ /* By default, just succeeds. */
+ return 0;
+}
+
+int
+fhandler_base::fchown (uid_t uid, gid_t gid)
+{
+ if (pc.is_fs_special ())
+ return ((fhandler_disk_file *) this)->fhandler_disk_file::fchown (uid, gid);
+ /* By default, just succeeds. */
+ return 0;
+}
+
+int
+fhandler_base::facl (int cmd, int nentries, aclent_t *aclbufp)
+{
+ int res = -1;
+ switch (cmd)
+ {
+ case SETACL:
+ /* By default, just succeeds. */
+ res = 0;
+ break;
+ case GETACL:
+ if (!aclbufp)
+ set_errno(EFAULT);
+ else if (nentries < MIN_ACL_ENTRIES)
+ set_errno (ENOSPC);
+ else
+ {
+ aclbufp[0].a_type = USER_OBJ;
+ aclbufp[0].a_id = myself->uid;
+ aclbufp[0].a_perm = (S_IRUSR | S_IWUSR) >> 6;
+ aclbufp[1].a_type = GROUP_OBJ;
+ aclbufp[1].a_id = myself->gid;
+ aclbufp[1].a_perm = (S_IRGRP | S_IWGRP) >> 3;
+ aclbufp[2].a_type = OTHER_OBJ;
+ aclbufp[2].a_id = ILLEGAL_GID;
+ aclbufp[2].a_perm = S_IROTH | S_IWOTH;
+ res = MIN_ACL_ENTRIES;
+ }
+ break;
+ case GETACLCNT:
+ res = MIN_ACL_ENTRIES;
+ break;
+ default:
+ set_errno (EINVAL);
+ break;
+ }
+ return res;
+}
+
+ssize_t
+fhandler_base::fgetxattr (const char *name, void *value, size_t size)
+{
+ set_errno (ENOTSUP);
+ return -1;
+}
+
+int
+fhandler_base::fsetxattr (const char *name, const void *value, size_t size,
+ int flags)
+{
+ set_errno (ENOTSUP);
+ return -1;
+}
+
+int
+fhandler_base::fadvise (off_t offset, off_t length, int advice)
+{
+ set_errno (EINVAL);
+ return -1;
+}
+
+int
+fhandler_base::ftruncate (off_t length, bool allow_truncate)
+{
+ return EINVAL;
+}
+
+int
+fhandler_base::link (const char *newpath)
+{
+ set_errno (EPERM);
+ return -1;
+}
+
+int
+fhandler_base::utimens (const struct timespec *tvp)
+{
+ if (is_fs_special ())
+ return utimens_fs (tvp);
+
+ set_errno (EINVAL);
+ return -1;
+}
+
+int
+fhandler_base::fsync ()
+{
+ if (!get_handle () || nohandle () || pc.isspecial ())
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (pc.isdir ()) /* Just succeed. */
+ return 0;
+ if (FlushFileBuffers (get_handle ()))
+ return 0;
+
+ /* Ignore ERROR_INVALID_FUNCTION because FlushFileBuffers() always fails
+ with this code on raw devices which are unbuffered by default. */
+ DWORD errcode = GetLastError();
+ if (errcode == ERROR_INVALID_FUNCTION)
+ return 0;
+
+ __seterrno_from_win_error (errcode);
+ return -1;
+}
+
+int
+fhandler_base::fpathconf (int v)
+{
+ int ret;
+
+ switch (v)
+ {
+ case _PC_LINK_MAX:
+ return pc.fs_is_ntfs () || pc.fs_is_samba () || pc.fs_is_nfs ()
+ ? LINK_MAX : 1;
+ case _PC_MAX_CANON:
+ if (is_tty ())
+ return MAX_CANON;
+ set_errno (EINVAL);
+ break;
+ case _PC_MAX_INPUT:
+ if (is_tty ())
+ return MAX_INPUT;
+ set_errno (EINVAL);
+ break;
+ case _PC_NAME_MAX:
+ /* NAME_MAX is without trailing \0 */
+ if (!pc.isdir ())
+ return NAME_MAX;
+ ret = NT_MAX_PATH - strlen (get_name ()) - 2;
+ return ret < 0 ? 0 : ret > NAME_MAX ? NAME_MAX : ret;
+ case _PC_PATH_MAX:
+ /* PATH_MAX is with trailing \0 */
+ if (!pc.isdir ())
+ return PATH_MAX;
+ ret = NT_MAX_PATH - strlen (get_name ()) - 1;
+ return ret < 0 ? 0 : ret > PATH_MAX ? PATH_MAX : ret;
+ case _PC_PIPE_BUF:
+ if (pc.isdir ()
+ || get_device () == FH_FIFO || get_device () == FH_PIPE
+ || get_device () == FH_PIPER || get_device () == FH_PIPEW)
+ return PIPE_BUF;
+ set_errno (EINVAL);
+ break;
+ case _PC_CHOWN_RESTRICTED:
+ return 1;
+ case _PC_NO_TRUNC:
+ return 1;
+ case _PC_VDISABLE:
+ if (is_tty ())
+ return _POSIX_VDISABLE;
+ set_errno (EINVAL);
+ break;
+ case _PC_ASYNC_IO:
+ return 1;
+ case _PC_PRIO_IO:
+ break;
+ case _PC_SYNC_IO:
+ return 1;
+ case _PC_FILESIZEBITS:
+ return FILESIZEBITS;
+ case _PC_2_SYMLINKS:
+ return 1;
+ case _PC_SYMLINK_MAX:
+ return SYMLINK_MAX;
+ case _PC_POSIX_PERMISSIONS:
+ case _PC_POSIX_SECURITY:
+ if (get_device () == FH_FS)
+ return pc.has_acls () || pc.fs_is_nfs ();
+ set_errno (EINVAL);
+ break;
+ case _PC_CASE_INSENSITIVE:
+ return !!pc.objcaseinsensitive ();
+ default:
+ set_errno (EINVAL);
+ break;
+ }
+ return -1;
+}
+
+NTSTATUS
+fhandler_base::npfs_handle (HANDLE &nph)
+{
+ static NO_COPY SRWLOCK npfs_lock;
+ static NO_COPY HANDLE npfs_dirh;
+
+ NTSTATUS status = STATUS_SUCCESS;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+
+ /* Lockless after first call. */
+ if (npfs_dirh)
+ {
+ nph = npfs_dirh;
+ return STATUS_SUCCESS;
+ }
+ AcquireSRWLockExclusive (&npfs_lock);
+ if (!npfs_dirh)
+ {
+ InitializeObjectAttributes (&attr, &ro_u_npfs, 0, NULL, NULL);
+ status = NtOpenFile (&npfs_dirh, FILE_READ_ATTRIBUTES | SYNCHRONIZE,
+ &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ 0);
+ }
+ ReleaseSRWLockExclusive (&npfs_lock);
+ if (NT_SUCCESS (status))
+ nph = npfs_dirh;
+ return status;
+}
diff --git a/winsup/cygwin/fhandler/clipboard.cc b/winsup/cygwin/fhandler/clipboard.cc
new file mode 100644
index 000000000..fe3545bf5
--- /dev/null
+++ b/winsup/cygwin/fhandler/clipboard.cc
@@ -0,0 +1,359 @@
+/* fhandler_dev_clipboard: code to access /dev/clipboard
+
+ Written by Charles Wilson (cwilson@ece.gatech.edu)
+
+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 <wchar.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "sync.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "child_info.h"
+#include <sys/clipboard.h>
+#include <unistd.h>
+
+/* Opening clipboard immediately after CloseClipboard()
+ sometimes fails. Therefore use retry-loop. */
+static inline bool
+open_clipboard ()
+{
+ const int max_retry = 10;
+ for (int i = 0; i < max_retry; i++)
+ {
+ if (OpenClipboard (NULL))
+ return true;
+ Sleep (1);
+ }
+ return false;
+}
+
+static inline bool
+close_clipboard ()
+{
+ return CloseClipboard ();
+}
+
+/*
+ * Robert Collins:
+ * FIXME: should we use GetClipboardSequenceNumber to tell if the clipboard has
+ * changed? How does /dev/clipboard operate under (say) linux?
+ */
+
+fhandler_dev_clipboard::fhandler_dev_clipboard ()
+ : fhandler_base (), pos (0), membuffer (NULL), msize (0)
+{
+ /* FIXME: check for errors and loop until we can open the clipboard */
+ open_clipboard ();
+ cygnativeformat = RegisterClipboardFormatW (CYGWIN_NATIVE);
+ close_clipboard ();
+}
+
+/*
+ * Special clipboard dup to duplicate input and output
+ * handles.
+ */
+
+int
+fhandler_dev_clipboard::dup (fhandler_base * child, int flags)
+{
+ fhandler_dev_clipboard *fhc = (fhandler_dev_clipboard *) child;
+ fhc->pos = fhc->msize = 0;
+ fhc->membuffer = NULL;
+ return fhandler_base::dup (child, flags);
+}
+
+int
+fhandler_dev_clipboard::set_clipboard (const void *buf, size_t len)
+{
+ HGLOBAL hmem;
+ /* Native CYGWIN format */
+ if (open_clipboard ())
+ {
+ cygcb_t *clipbuf;
+
+ hmem = GlobalAlloc (GMEM_MOVEABLE, sizeof (cygcb_t) + len);
+ if (!hmem)
+ {
+ __seterrno ();
+ close_clipboard ();
+ return -1;
+ }
+ clipbuf = (cygcb_t *) GlobalLock (hmem);
+
+ clock_gettime (CLOCK_REALTIME, &clipbuf->ts);
+ clipbuf->cb_size = len;
+ memcpy (clipbuf->cb_data, buf, len); // append user-supplied data
+
+ GlobalUnlock (hmem);
+ EmptyClipboard ();
+ HANDLE ret = SetClipboardData (cygnativeformat, hmem);
+ close_clipboard ();
+ /* According to MSDN, hmem must not be free'd after transferring the
+ data to the clipboard via SetClipboardData. */
+ /* GlobalFree (hmem); */
+ if (!ret)
+ {
+ __seterrno ();
+ return -1;
+ }
+ }
+
+ /* CF_TEXT/CF_OEMTEXT for copying to wordpad and the like */
+ len = sys_mbstowcs (NULL, 0, (const char *) buf, len);
+ if (!len)
+ {
+ set_errno (EILSEQ);
+ return -1;
+ }
+ if (open_clipboard ())
+ {
+ PWCHAR clipbuf;
+
+ hmem = GlobalAlloc (GMEM_MOVEABLE, (len + 1) * sizeof (WCHAR));
+ if (!hmem)
+ {
+ __seterrno ();
+ close_clipboard ();
+ return -1;
+ }
+ clipbuf = (PWCHAR) GlobalLock (hmem);
+ sys_mbstowcs (clipbuf, len + 1, (const char *) buf);
+ GlobalUnlock (hmem);
+ HANDLE ret = SetClipboardData (CF_UNICODETEXT, hmem);
+ close_clipboard ();
+ /* According to MSDN, hmem must not be free'd after transferring the
+ data to the clipboard via SetClipboardData. */
+ /* GlobalFree (hmem); */
+ if (!ret)
+ {
+ __seterrno ();
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* FIXME: arbitrary seeking is not handled */
+ssize_t
+fhandler_dev_clipboard::write (const void *buf, size_t len)
+{
+ /* write to our membuffer */
+ size_t cursize = msize;
+ void *tempbuffer = realloc (membuffer, cursize + len);
+ if (!tempbuffer)
+ {
+ debug_printf ("Couldn't realloc() clipboard buffer for write");
+ return -1;
+ }
+ membuffer = tempbuffer;
+ msize = cursize + len;
+ memcpy ((unsigned char *) membuffer + cursize, buf, len);
+
+ /* now pass to windows */
+ if (set_clipboard (membuffer, msize))
+ {
+ /* FIXME: membuffer is now out of sync with pos, but msize
+ is used above */
+ return -1;
+ }
+
+ pos = msize;
+ return len;
+}
+
+int
+fhandler_dev_clipboard::fstat (struct stat *buf)
+{
+ buf->st_mode = S_IFCHR | STD_RBITS | STD_WBITS | S_IWGRP | S_IWOTH;
+ buf->st_uid = geteuid ();
+ buf->st_gid = getegid ();
+ buf->st_nlink = 1;
+ buf->st_blksize = PREFERRED_IO_BLKSIZE;
+
+ buf->st_ctim.tv_sec = 1164931200L; /* Arbitrary value: 2006-12-01 */
+ buf->st_ctim.tv_nsec = 0L;
+ buf->st_birthtim = buf->st_atim = buf->st_mtim = buf->st_ctim;
+
+ if (open_clipboard ())
+ {
+ UINT formatlist[1] = { cygnativeformat };
+ int format;
+ HGLOBAL hglb;
+ cygcb_t *clipbuf;
+
+ if ((format = GetPriorityClipboardFormat (formatlist, 1)) > 0
+ && (hglb = GetClipboardData (format))
+ && (clipbuf = (cygcb_t *) GlobalLock (hglb)))
+ {
+ buf->st_atim = buf->st_mtim = clipbuf->ts;
+ buf->st_size = clipbuf->cb_size;
+ GlobalUnlock (hglb);
+ }
+ close_clipboard ();
+ }
+
+ return 0;
+}
+
+void
+fhandler_dev_clipboard::read (void *ptr, size_t& len)
+{
+ HGLOBAL hglb;
+ size_t ret = 0;
+ UINT formatlist[2];
+ UINT format;
+ LPVOID cb_data;
+ int rach;
+
+ if (!open_clipboard ())
+ {
+ len = 0;
+ return;
+ }
+ formatlist[0] = cygnativeformat;
+ formatlist[1] = CF_UNICODETEXT;
+ if ((format = GetPriorityClipboardFormat (formatlist, 2)) <= 0
+ || !(hglb = GetClipboardData (format))
+ || !(cb_data = GlobalLock (hglb)))
+ {
+ close_clipboard ();
+ len = 0;
+ return;
+ }
+ if (format == cygnativeformat)
+ {
+ cygcb_t *clipbuf = (cygcb_t *) cb_data;
+
+ if (pos < (off_t) clipbuf->cb_size)
+ {
+ ret = (len > (clipbuf->cb_size - pos)) ? clipbuf->cb_size - pos : len;
+ memcpy (ptr, clipbuf->cb_data + pos, ret);
+ pos += ret;
+ }
+ }
+ else if ((rach = get_readahead ()) >= 0)
+ {
+ /* Deliver from read-ahead buffer. */
+ char * out_ptr = (char *) ptr;
+ * out_ptr++ = rach;
+ ret = 1;
+ while (ret < len && (rach = get_readahead ()) >= 0)
+ {
+ * out_ptr++ = rach;
+ ret++;
+ }
+ }
+ else
+ {
+ wchar_t *buf = (wchar_t *) cb_data;
+
+ size_t glen = GlobalSize (hglb) / sizeof (WCHAR) - 1;
+ if (pos < (off_t) glen)
+ {
+ /* If caller's buffer is too small to hold at least one
+ max-size character, redirect algorithm to local
+ read-ahead buffer, finally fill class read-ahead buffer
+ with result and feed caller from there. */
+ char *conv_ptr = (char *) ptr;
+ size_t conv_len = len;
+#define cprabuf_len MB_LEN_MAX /* max MB_CUR_MAX of all encodings */
+ char cprabuf [cprabuf_len];
+ if (len < cprabuf_len)
+ {
+ conv_ptr = cprabuf;
+ conv_len = cprabuf_len;
+ }
+
+ /* Comparing apples and oranges here, but the below loop could become
+ extremly slow otherwise. We rather return a few bytes less than
+ possible instead of being even more slow than usual... */
+ if (glen > pos + conv_len)
+ glen = pos + conv_len;
+ /* This loop is necessary because the number of bytes returned by
+ sys_wcstombs does not indicate the number of wide chars used for
+ it, so we could potentially drop wide chars. */
+ while ((ret = sys_wcstombs (NULL, 0, buf + pos, glen - pos))
+ != (size_t) -1
+ && (ret > conv_len
+ /* Skip separated high surrogate: */
+ || ((buf [glen - 1] & 0xFC00) == 0xD800
+ && glen - pos > 1)))
+ --glen;
+ if (ret == (size_t) -1)
+ ret = 0;
+ else
+ {
+ ret = sys_wcstombs ((char *) conv_ptr, (size_t) -1,
+ buf + pos, glen - pos);
+ pos = glen;
+ /* If using read-ahead buffer, copy to class read-ahead buffer
+ and deliver first byte. */
+ if (conv_ptr == cprabuf)
+ {
+ puts_readahead (cprabuf, ret);
+ char *out_ptr = (char *) ptr;
+ ret = 0;
+ while (ret < len && (rach = get_readahead ()) >= 0)
+ {
+ * out_ptr++ = rach;
+ ret++;
+ }
+ }
+ }
+ }
+ }
+ GlobalUnlock (hglb);
+ close_clipboard ();
+ len = ret;
+}
+
+off_t
+fhandler_dev_clipboard::lseek (off_t offset, int whence)
+{
+ /* On reads we check this at read time, not seek time.
+ * On writes we use this to decide how to write - empty and write, or open, copy, empty
+ * and write
+ */
+ pos = offset;
+ /* treat seek like rewind */
+ if (membuffer)
+ {
+ free (membuffer);
+ membuffer = NULL;
+ }
+ msize = 0;
+ return 0;
+}
+
+int
+fhandler_dev_clipboard::close ()
+{
+ if (!have_execed)
+ {
+ pos = msize = 0;
+ if (membuffer)
+ {
+ free (membuffer);
+ membuffer = NULL;
+ }
+ }
+ return fhandler_base::close ();
+}
+
+void
+fhandler_dev_clipboard::fixup_after_exec ()
+{
+ if (!close_on_exec ())
+ {
+ pos = msize = 0;
+ membuffer = NULL;
+ }
+}
diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc
new file mode 100644
index 000000000..a4a367005
--- /dev/null
+++ b/winsup/cygwin/fhandler/console.cc
@@ -0,0 +1,4333 @@
+/* fhandler_console.cc
+
+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 "miscfuncs.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include <ctype.h>
+#include <sys/param.h>
+#include <sys/cygwin.h>
+#include <cygwin/kd.h>
+#include <unistd.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "sigproc.h"
+#include "pinfo.h"
+#include "shared_info.h"
+#include "cygtls.h"
+#include "tls_pbuf.h"
+#include "registry.h"
+#include <asm/socket.h>
+#include "sync.h"
+#include "child_info.h"
+#include "cygwait.h"
+#include "winf.h"
+
+/* Don't make this bigger than NT_MAX_PATH as long as the temporary buffer
+ is allocated using tmp_pathbuf!!! */
+#define CONVERT_LIMIT NT_MAX_PATH
+
+#define ALT_PRESSED (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)
+#define CTRL_PRESSED (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)
+
+#define con (shared_console_info->con)
+#define srTop (con.b.srWindow.Top + con.scroll_region.Top)
+#define srBottom ((con.scroll_region.Bottom < 0) ? \
+ con.b.srWindow.Bottom : \
+ con.b.srWindow.Top + con.scroll_region.Bottom)
+#define con_is_legacy (shared_console_info && con.is_legacy)
+
+#define CONS_THREAD_SYNC "cygcons.thread_sync"
+static bool NO_COPY master_thread_started = false;
+
+const unsigned fhandler_console::MAX_WRITE_CHARS = 16384;
+
+fhandler_console::console_state NO_COPY *fhandler_console::shared_console_info;
+
+bool NO_COPY fhandler_console::invisible_console;
+
+/* con_ra is shared in the same process.
+ Only one console can exist in a process, therefore, static is suitable. */
+static struct fhandler_base::rabuf_t con_ra;
+
+/* Write pending buffer for ESC sequence handling
+ in xterm compatible mode */
+static wchar_t last_char;
+
+/* simple helper class to accumulate output in a buffer
+ and send that to the console on request: */
+static class write_pending_buffer
+{
+private:
+ static const size_t WPBUF_LEN = 256u;
+ char buf[WPBUF_LEN];
+ size_t ixput;
+ HANDLE output_handle;
+public:
+ void init (HANDLE &handle)
+ {
+ output_handle = handle;
+ empty ();
+ }
+ inline void put (char x)
+ {
+ if (ixput == WPBUF_LEN)
+ send ();
+ buf[ixput++] = x;
+ }
+ inline void empty () { ixput = 0u; }
+ inline void send ()
+ {
+ if (!output_handle)
+ {
+ empty ();
+ return;
+ }
+ mbtowc_p f_mbtowc =
+ (__MBTOWC == __ascii_mbtowc) ? __utf8_mbtowc : __MBTOWC;
+ wchar_t bufw[WPBUF_LEN];
+ DWORD len = 0;
+ mbstate_t ps;
+ memset (&ps, 0, sizeof (ps));
+ char *p = buf;
+ while (ixput)
+ {
+ int bytes = f_mbtowc (_REENT, bufw + len, p, ixput, &ps);
+ if (bytes < 0)
+ {
+ if ((size_t) ps.__count < ixput)
+ { /* Discard one byte and retry. */
+ p++;
+ ixput--;
+ memset (&ps, 0, sizeof (ps));
+ continue;
+ }
+ /* Halfway through the multibyte char. */
+ memmove (buf, p, ixput);
+ break;
+ }
+ else
+ {
+ len++;
+ p += bytes;
+ ixput -= bytes;
+ }
+ }
+ acquire_attach_mutex (mutex_timeout);
+ WriteConsoleW (output_handle, bufw, len, NULL, 0);
+ release_attach_mutex ();
+ }
+} wpbuf;
+
+static void
+beep ()
+{
+ const WCHAR ding[] = L"\\media\\ding.wav";
+ reg_key r (HKEY_CURRENT_USER, KEY_ALL_ACCESS, L"AppEvents", L"Schemes",
+ L"Apps", L".Default", L".Default", L".Current", NULL);
+ if (r.created ())
+ {
+ tmp_pathbuf tp;
+
+ PWCHAR ding_path = tp.w_get ();
+ wcpcpy (wcpcpy (ding_path, windows_directory), ding);
+ r.set_string (L"", ding_path);
+ }
+ MessageBeep (MB_OK);
+}
+
+fhandler_console::console_state *
+fhandler_console::open_shared_console (HWND hw, HANDLE& h, bool& create)
+{
+ wchar_t namebuf[(sizeof "XXXXXXXXXXXXXXXXXX-consNNNNNNNNNN")];
+ __small_swprintf (namebuf, L"%S-cons%p", &cygheap->installation_key, hw);
+
+ shared_locations m = create ? SH_SHARED_CONSOLE : SH_JUSTOPEN;
+ console_state *res = (console_state *)
+ open_shared (namebuf, 0, h, sizeof (*shared_console_info), &m);
+ create = m != SH_JUSTOPEN;
+ return res;
+}
+
+class console_unit
+{
+ int n;
+ unsigned long bitmask;
+ HWND me;
+
+public:
+ operator int () const {return n;}
+ console_unit (HWND);
+ friend BOOL CALLBACK enum_windows (HWND, LPARAM);
+};
+
+BOOL CALLBACK
+enum_windows (HWND hw, LPARAM lp)
+{
+ console_unit *this1 = (console_unit *) lp;
+ if (hw == this1->me)
+ return TRUE;
+ HANDLE h = NULL;
+ fhandler_console::console_state *cs;
+ if ((cs = fhandler_console::open_shared_console (hw, h)))
+ {
+ this1->bitmask ^= 1 << cs->tty_min_state.getntty ();
+ UnmapViewOfFile ((void *) cs);
+ CloseHandle (h);
+ }
+ return TRUE;
+}
+
+console_unit::console_unit (HWND me0):
+ bitmask (0xffffffff), me (me0)
+{
+ EnumWindows (enum_windows, (LPARAM) this);
+ n = (_minor_t) ffs (bitmask) - 1;
+ if (n < 0)
+ api_fatal ("console device allocation failure - too many consoles in use, max consoles is 32");
+}
+
+static DWORD
+cons_master_thread (VOID *arg)
+{
+ fhandler_console *fh = (fhandler_console *) arg;
+ tty *ttyp = (tty *) fh->tc ();
+ fhandler_console::handle_set_t handle_set;
+ fh->get_duplicated_handle_set (&handle_set);
+ HANDLE thread_sync_event;
+ DuplicateHandle (GetCurrentProcess (), fh->thread_sync_event,
+ GetCurrentProcess (), &thread_sync_event,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ SetEvent (thread_sync_event);
+ master_thread_started = true;
+ /* Do not touch class members after here because the class instance
+ may have been destroyed. */
+ fhandler_console::cons_master_thread (&handle_set, ttyp);
+ fhandler_console::close_handle_set (&handle_set);
+ SetEvent (thread_sync_event);
+ CloseHandle (thread_sync_event);
+ return 0;
+}
+
+/* Compare two INPUT_RECORD sequences */
+static inline bool
+inrec_eq (const INPUT_RECORD *a, const INPUT_RECORD *b, DWORD n)
+{
+ for (DWORD i = 0; i < n; i++)
+ {
+ if (a[i].EventType != b[i].EventType)
+ return false;
+ else if (a[i].EventType == KEY_EVENT)
+ { /* wVirtualKeyCode, wVirtualScanCode and dwControlKeyState
+ of the readback key event may be different from that of
+ written event. Therefore they are ignored. */
+ const KEY_EVENT_RECORD *ak = &a[i].Event.KeyEvent;
+ const KEY_EVENT_RECORD *bk = &b[i].Event.KeyEvent;
+ if (ak->bKeyDown != bk->bKeyDown
+ || ak->uChar.UnicodeChar != bk->uChar.UnicodeChar
+ || ak->wRepeatCount != bk->wRepeatCount)
+ return false;
+ }
+ else if (a[i].EventType == MOUSE_EVENT)
+ {
+ const MOUSE_EVENT_RECORD *am = &a[i].Event.MouseEvent;
+ const MOUSE_EVENT_RECORD *bm = &b[i].Event.MouseEvent;
+ if (am->dwMousePosition.X != bm->dwMousePosition.X
+ || am->dwMousePosition.Y != bm->dwMousePosition.Y
+ || am->dwButtonState != bm->dwButtonState
+ || am->dwControlKeyState != bm->dwControlKeyState
+ || am->dwEventFlags != bm->dwEventFlags)
+ return false;
+ }
+ else if (a[i].EventType == WINDOW_BUFFER_SIZE_EVENT)
+ {
+ const WINDOW_BUFFER_SIZE_RECORD
+ *aw = &a[i].Event.WindowBufferSizeEvent;
+ const WINDOW_BUFFER_SIZE_RECORD
+ *bw = &b[i].Event.WindowBufferSizeEvent;
+ if (aw->dwSize.X != bw->dwSize.X
+ || aw->dwSize.Y != bw->dwSize.Y)
+ return false;
+ }
+ else if (a[i].EventType == MENU_EVENT)
+ {
+ const MENU_EVENT_RECORD *am = &a[i].Event.MenuEvent;
+ const MENU_EVENT_RECORD *bm = &b[i].Event.MenuEvent;
+ if (am->dwCommandId != bm->dwCommandId)
+ return false;
+ }
+ else if (a[i].EventType == FOCUS_EVENT)
+ {
+ const FOCUS_EVENT_RECORD *af = &a[i].Event.FocusEvent;
+ const FOCUS_EVENT_RECORD *bf = &b[i].Event.FocusEvent;
+ if (af->bSetFocus != bf->bSetFocus)
+ return false;
+ }
+ }
+ return true;
+}
+
+/* This thread processes signals derived from input messages.
+ Without this thread, those signals can be handled only when
+ the process calls read() or select(). This thread reads input
+ records, processes signals and removes corresponding record.
+ The other input records are kept back for read() or select(). */
+void
+fhandler_console::cons_master_thread (handle_set_t *p, tty *ttyp)
+{
+ const int additional_space = 128; /* Possible max number of incoming events
+ during the process. Additional space
+ should be left for writeback fix. */
+ DWORD inrec_size = INREC_SIZE + additional_space;
+ INPUT_RECORD *input_rec =
+ (INPUT_RECORD *) malloc (inrec_size * sizeof (INPUT_RECORD));
+ INPUT_RECORD *input_tmp =
+ (INPUT_RECORD *) malloc (inrec_size * sizeof (INPUT_RECORD));
+
+ if (!input_rec || !input_tmp)
+ { /* Cannot continue */
+ free (input_rec);
+ free (input_tmp);
+ return;
+ }
+
+ DWORD inrec_size1 =
+ wincap.cons_need_small_input_record_buf () ? INREC_SIZE : inrec_size;
+
+ struct m
+ {
+ inline static size_t bytes (size_t n)
+ {
+ return sizeof (INPUT_RECORD) * n;
+ }
+ };
+ termios &ti = ttyp->ti;
+ while (con.owner == myself->pid)
+ {
+ DWORD total_read, n, i;
+
+ if (con.disable_master_thread)
+ {
+ cygwait (40);
+ continue;
+ }
+
+ acquire_attach_mutex (mutex_timeout);
+ GetNumberOfConsoleInputEvents (p->input_handle, &total_read);
+ release_attach_mutex ();
+ if (total_read > INREC_SIZE)
+ {
+ cygwait (40);
+ acquire_attach_mutex (mutex_timeout);
+ GetNumberOfConsoleInputEvents (p->input_handle, &n);
+ release_attach_mutex ();
+ if (n < total_read)
+ {
+ /* read() seems to be called. Process special keys
+ in process_input_message (). */
+ con.master_thread_suspended = true;
+ continue;
+ }
+ total_read = n;
+ }
+ con.master_thread_suspended = false;
+ if (total_read + additional_space > inrec_size)
+ {
+ DWORD new_inrec_size = total_read + additional_space;
+ INPUT_RECORD *new_input_rec = (INPUT_RECORD *)
+ realloc (input_rec, m::bytes (new_inrec_size));
+ if (new_input_rec)
+ input_rec = new_input_rec;
+ INPUT_RECORD *new_input_tmp = (INPUT_RECORD *)
+ realloc (input_tmp, m::bytes (new_inrec_size));
+ if (new_input_tmp)
+ input_tmp = new_input_tmp;
+ if (new_input_rec && new_input_tmp)
+ {
+ inrec_size = new_inrec_size;
+ if (!wincap.cons_need_small_input_record_buf ())
+ inrec_size1 = inrec_size;
+ }
+ }
+
+ WaitForSingleObject (p->input_mutex, mutex_timeout);
+ total_read = 0;
+ switch (cygwait (p->input_handle, (DWORD) 0))
+ {
+ case WAIT_OBJECT_0:
+ acquire_attach_mutex (mutex_timeout);
+ total_read = 0;
+ while (cygwait (p->input_handle, (DWORD) 0) == WAIT_OBJECT_0
+ && total_read < inrec_size)
+ {
+ DWORD len;
+ ReadConsoleInputW (p->input_handle, input_rec + total_read,
+ min (inrec_size - total_read, inrec_size1),
+ &len);
+ total_read += len;
+ }
+ release_attach_mutex ();
+ break;
+ case WAIT_TIMEOUT:
+ con.num_processed = 0;
+ case WAIT_SIGNALED:
+ case WAIT_CANCELED:
+ break;
+ default: /* Error */
+ ReleaseMutex (p->input_mutex);
+ return;
+ }
+ /* If ENABLE_VIRTUAL_TERMINAL_INPUT is not set, changing
+ window height does not generate WINDOW_BUFFER_SIZE_EVENT.
+ Therefore, check windows size every time here. */
+ if (!wincap.has_con_24bit_colors () || con_is_legacy)
+ {
+ SHORT y = con.dwWinSize.Y;
+ SHORT x = con.dwWinSize.X;
+ con.fillin (p->output_handle);
+ if (y != con.dwWinSize.Y || x != con.dwWinSize.X)
+ {
+ con.scroll_region.Top = 0;
+ con.scroll_region.Bottom = -1;
+ ttyp->kill_pgrp (SIGWINCH);
+ }
+ }
+ for (i = con.num_processed; i < total_read; i++)
+ {
+ wchar_t wc;
+ char c;
+ bool processed = false;
+ switch (input_rec[i].EventType)
+ {
+ case KEY_EVENT:
+ if (!input_rec[i].Event.KeyEvent.bKeyDown)
+ continue;
+ wc = input_rec[i].Event.KeyEvent.uChar.UnicodeChar;
+ if (!wc || (wint_t) wc >= 0x80)
+ continue;
+ c = (char) wc;
+ switch (process_sigs (c, ttyp, NULL))
+ {
+ case signalled:
+ case not_signalled_but_done:
+ case done_with_debugger:
+ processed = true;
+ ttyp->output_stopped = false;
+ if (ti.c_lflag & NOFLSH)
+ goto remove_record;
+ con.num_processed = 0;
+ goto skip_writeback;
+ default: /* not signalled */
+ break;
+ }
+ processed = process_stop_start (c, ttyp);
+ break;
+ case WINDOW_BUFFER_SIZE_EVENT:
+ SHORT y = con.dwWinSize.Y;
+ SHORT x = con.dwWinSize.X;
+ con.fillin (p->output_handle);
+ if (y != con.dwWinSize.Y || x != con.dwWinSize.X)
+ {
+ con.scroll_region.Top = 0;
+ con.scroll_region.Bottom = -1;
+ if (wincap.has_con_24bit_colors () && !con_is_legacy
+ && wincap.has_con_broken_tabs ())
+ fix_tab_position (p->output_handle);
+ ttyp->kill_pgrp (SIGWINCH);
+ }
+ processed = true;
+ break;
+ }
+remove_record:
+ if (processed)
+ { /* Remove corresponding record. */
+ if (total_read > i + 1)
+ memmove (input_rec + i, input_rec + i + 1,
+ m::bytes (total_read - i - 1));
+ total_read--;
+ i--;
+ }
+ }
+ con.num_processed = total_read;
+ if (total_read)
+ {
+ do
+ {
+ /* Writeback input records other than interrupt. */
+ acquire_attach_mutex (mutex_timeout);
+ n = 0;
+ while (n < total_read)
+ {
+ DWORD len;
+ WriteConsoleInputW (p->input_handle, input_rec + n,
+ min (total_read - n, inrec_size1), &len);
+ n += len;
+ }
+ release_attach_mutex ();
+
+ acquire_attach_mutex (mutex_timeout);
+ GetNumberOfConsoleInputEvents (p->input_handle, &n);
+ release_attach_mutex ();
+ if (n + additional_space > inrec_size)
+ {
+ DWORD new_inrec_size = n + additional_space;
+ INPUT_RECORD *new_input_rec = (INPUT_RECORD *)
+ realloc (input_rec, m::bytes (new_inrec_size));
+ if (new_input_rec)
+ input_rec = new_input_rec;
+ INPUT_RECORD *new_input_tmp = (INPUT_RECORD *)
+ realloc (input_tmp, m::bytes (new_inrec_size));
+ if (new_input_tmp)
+ input_tmp = new_input_tmp;
+ if (new_input_rec && new_input_tmp)
+ {
+ inrec_size = new_inrec_size;
+ if (!wincap.cons_need_small_input_record_buf ())
+ inrec_size1 = inrec_size;
+ }
+ }
+
+ /* Check if writeback was successfull. */
+ acquire_attach_mutex (mutex_timeout);
+ PeekConsoleInputW (p->input_handle, input_tmp, inrec_size1, &n);
+ release_attach_mutex ();
+ if (n < min (total_read, inrec_size1))
+ break; /* Someone has read input without acquiring
+ input_mutex. ConEmu cygwin-connector? */
+ if (inrec_eq (input_rec, input_tmp,
+ min (total_read, inrec_size1)))
+ break; /* OK */
+ /* Try to fix */
+ acquire_attach_mutex (mutex_timeout);
+ n = 0;
+ while (cygwait (p->input_handle, (DWORD) 0) == WAIT_OBJECT_0
+ && n < inrec_size)
+ {
+ DWORD len;
+ ReadConsoleInputW (p->input_handle, input_tmp + n,
+ min (inrec_size - n, inrec_size1), &len);
+ n += len;
+ }
+ release_attach_mutex ();
+ bool fixed = false;
+ for (DWORD ofs = n - total_read; ofs > 0; ofs--)
+ {
+ if (inrec_eq (input_rec, input_tmp + ofs, total_read))
+ {
+ memcpy (input_rec + total_read, input_tmp,
+ m::bytes (ofs));
+ memcpy (input_rec + total_read + ofs,
+ input_tmp + total_read + ofs,
+ m::bytes (n - ofs - total_read));
+ fixed = true;
+ break;
+ }
+ }
+ if (!fixed)
+ {
+ for (DWORD i = 0, j = 0; j < n; j++)
+ if (i == total_read
+ || !inrec_eq (input_rec + i, input_tmp + j, 1))
+ {
+ if (total_read + j - i >= n)
+ { /* Something is wrong. Giving up. */
+ acquire_attach_mutex (mutex_timeout);
+ DWORD l = 0;
+ while (l < n)
+ {
+ DWORD len;
+ WriteConsoleInputW (p->input_handle,
+ input_tmp + l,
+ min (n - l, inrec_size1),
+ &len);
+ l += len;
+ }
+ release_attach_mutex ();
+ goto skip_writeback;
+ }
+ input_rec[total_read + j - i] = input_tmp[j];
+ }
+ else
+ i++;
+ }
+ total_read = n;
+ }
+ while (true);
+ }
+skip_writeback:
+ ReleaseMutex (p->input_mutex);
+ cygwait (40);
+ }
+ free (input_rec);
+ free (input_tmp);
+}
+
+bool
+fhandler_console::set_unit ()
+{
+ bool created;
+ fh_devices devset;
+ lock_ttys here;
+ HWND me;
+ fh_devices this_unit = dev ();
+ bool generic_console = this_unit == FH_CONIN || this_unit == FH_CONOUT;
+ if (shared_console_info)
+ {
+ fh_devices shared_unit =
+ (fh_devices) shared_console_info->tty_min_state.getntty ();
+ devset = (shared_unit == this_unit || this_unit == FH_CONSOLE
+ || generic_console
+ || this_unit == FH_TTY) ?
+ shared_unit : FH_ERROR;
+ created = false;
+ }
+ else if ((!generic_console &&
+ (myself->ctty != -1 && !iscons_dev (myself->ctty)))
+ || !(me = GetConsoleWindow ()))
+ devset = FH_ERROR;
+ else
+ {
+ created = true;
+ shared_console_info =
+ open_shared_console (me, cygheap->console_h, created);
+ ProtectHandleINH (cygheap->console_h);
+ if (created)
+ shared_console_info->
+ tty_min_state.setntty (DEV_CONS_MAJOR, console_unit (me));
+ devset = (fh_devices) shared_console_info->tty_min_state.getntty ();
+ if (created)
+ con.owner = myself->pid;
+ }
+ if (!created && shared_console_info)
+ {
+ while (con.owner > MAX_PID)
+ Sleep (1);
+ pinfo p (con.owner);
+ if (!p)
+ con.owner = myself->pid;
+ }
+
+ dev ().parse (devset);
+ if (devset != FH_ERROR)
+ pc.file_attributes (FILE_ATTRIBUTE_NORMAL);
+ else
+ {
+ set_handle (NULL);
+ set_output_handle (NULL);
+ created = false;
+ }
+ return created;
+}
+
+/* Allocate and initialize the shared record for the current console. */
+void
+fhandler_console::setup ()
+{
+ if (set_unit ())
+ {
+ con.scroll_region.Bottom = -1;
+ con.dwLastCursorPosition.X = -1;
+ con.dwLastCursorPosition.Y = -1;
+ con.dwLastMousePosition.X = -1;
+ con.dwLastMousePosition.Y = -1;
+ con.savex = con.savey = -1;
+ con.screen_alternated = false;
+ con.dwLastButtonState = 0; /* none pressed */
+ con.last_button_code = 3; /* released */
+ con.underline_color = FOREGROUND_GREEN | FOREGROUND_BLUE;
+ con.dim_color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
+ con.meta_mask = LEFT_ALT_PRESSED;
+ /* Set the mask that determines if an input keystroke is modified by
+ META. We set this based on the keyboard layout language loaded
+ for the current thread. The left <ALT> key always generates
+ META, but the right <ALT> key only generates META if we are using
+ an English keyboard because many "international" keyboards
+ replace common shell symbols ('[', '{', etc.) with accented
+ language-specific characters (umlaut, accent grave, etc.). On
+ these keyboards right <ALT> (called AltGr) is used to produce the
+ shell symbols and should not be interpreted as META. */
+ if (PRIMARYLANGID (LOWORD (GetKeyboardLayout (0))) == LANG_ENGLISH)
+ con.meta_mask |= RIGHT_ALT_PRESSED;
+ con.set_default_attr ();
+ con.backspace_keycode = CERASE;
+ con.cons_rapoi = NULL;
+ shared_console_info->tty_min_state.is_console = true;
+ con.cursor_key_app_mode = false;
+ con.disable_master_thread = true;
+ con.master_thread_suspended = false;
+ con.num_processed = 0;
+ }
+}
+
+char *&
+fhandler_console::rabuf ()
+{
+ return con_ra.rabuf;
+}
+
+size_t &
+fhandler_console::ralen ()
+{
+ return con_ra.ralen;
+}
+
+size_t &
+fhandler_console::raixget ()
+{
+ return con_ra.raixget;
+}
+
+size_t &
+fhandler_console::raixput ()
+{
+ return con_ra.raixput;
+}
+
+size_t &
+fhandler_console::rabuflen ()
+{
+ return con_ra.rabuflen;
+}
+
+/* The function set_{in,out}put_mode() should be static so that they
+ can be called even after the fhandler_console instance is deleted. */
+void
+fhandler_console::set_input_mode (tty::cons_mode m, const termios *t,
+ const handle_set_t *p)
+{
+ DWORD oflags;
+ WaitForSingleObject (p->input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ GetConsoleMode (p->input_handle, &oflags);
+ DWORD flags = oflags
+ & (ENABLE_EXTENDED_FLAGS | ENABLE_INSERT_MODE | ENABLE_QUICK_EDIT_MODE);
+ switch (m)
+ {
+ case tty::restore:
+ flags |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
+ break;
+ case tty::cygwin:
+ flags |= ENABLE_WINDOW_INPUT;
+ if (con.master_thread_suspended)
+ flags |= ENABLE_PROCESSED_INPUT;
+ if (wincap.has_con_24bit_colors () && !con_is_legacy)
+ flags |= ENABLE_VIRTUAL_TERMINAL_INPUT;
+ else
+ flags |= ENABLE_MOUSE_INPUT;
+ break;
+ case tty::native:
+ if (t->c_lflag & ECHO)
+ flags |= ENABLE_ECHO_INPUT;
+ if (t->c_lflag & ICANON)
+ flags |= ENABLE_LINE_INPUT;
+ if (flags & ENABLE_ECHO_INPUT && !(flags & ENABLE_LINE_INPUT))
+ /* This is illegal, so turn off the echo here, and fake it
+ when we read the characters */
+ flags &= ~ENABLE_ECHO_INPUT;
+ if (t->c_lflag & ISIG)
+ flags |= ENABLE_PROCESSED_INPUT;
+ break;
+ }
+ SetConsoleMode (p->input_handle, flags);
+ if (!(oflags & ENABLE_VIRTUAL_TERMINAL_INPUT)
+ && (flags & ENABLE_VIRTUAL_TERMINAL_INPUT)
+ && con.cursor_key_app_mode)
+ { /* Restore DECCKM */
+ set_output_mode (tty::cygwin, t, p);
+ WriteConsoleW (p->output_handle, L"\033[?1h", 5, NULL, 0);
+ }
+ release_attach_mutex ();
+ ReleaseMutex (p->input_mutex);
+}
+
+void
+fhandler_console::set_output_mode (tty::cons_mode m, const termios *t,
+ const handle_set_t *p)
+{
+ DWORD flags = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT;
+ if (con.orig_virtual_terminal_processing_mode)
+ flags |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ WaitForSingleObject (p->output_mutex, mutex_timeout);
+ switch (m)
+ {
+ case tty::restore:
+ break;
+ case tty::cygwin:
+ if (wincap.has_con_24bit_colors () && !con_is_legacy)
+ flags |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ fallthrough;
+ case tty::native:
+ if (wincap.has_con_24bit_colors () && !con_is_legacy
+ && (!(t->c_oflag & OPOST) || !(t->c_oflag & ONLCR)))
+ flags |= DISABLE_NEWLINE_AUTO_RETURN;
+ break;
+ }
+ acquire_attach_mutex (mutex_timeout);
+ SetConsoleMode (p->output_handle, flags);
+ release_attach_mutex ();
+ ReleaseMutex (p->output_mutex);
+}
+
+void
+fhandler_console::setup_for_non_cygwin_app ()
+{
+ /* Setting-up console mode for non-cygwin app. */
+ /* If conmode is set to tty::native for non-cygwin apps
+ in background, tty settings of the shell is reflected
+ to the console mode of the app. So, use tty::restore
+ for background process instead. */
+ tty::cons_mode conmode =
+ (get_ttyp ()->getpgid ()== myself->pgid) ? tty::native : tty::restore;
+ set_input_mode (conmode, &tc ()->ti, get_handle_set ());
+ set_output_mode (conmode, &tc ()->ti, get_handle_set ());
+ set_disable_master_thread (true, this);
+}
+
+void
+fhandler_console::cleanup_for_non_cygwin_app (handle_set_t *p)
+{
+ termios dummy = {0, };
+ termios *ti =
+ shared_console_info ? &(shared_console_info->tty_min_state.ti) : &dummy;
+ /* Cleaning-up console mode for non-cygwin app. */
+ /* conmode can be tty::restore when non-cygwin app is
+ exec'ed from login shell. */
+ tty::cons_mode conmode =
+ (con.owner == myself->pid) ? tty::restore : tty::cygwin;
+ set_output_mode (conmode, ti, p);
+ set_input_mode (conmode, ti, p);
+ set_disable_master_thread (con.owner == myself->pid);
+}
+
+/* Return the tty structure associated with a given tty number. If the
+ tty number is < 0, just return a dummy record. */
+tty_min *
+tty_list::get_cttyp ()
+{
+ dev_t n = myself->ctty;
+ if (iscons_dev (n))
+ return fhandler_console::shared_console_info ?
+ &fhandler_console::shared_console_info->tty_min_state : NULL;
+ else if (istty_slave_dev (n))
+ return &ttys[device::minor (n)];
+ else
+ return NULL;
+}
+
+void
+fhandler_console::setup_io_mutex (void)
+{
+ char buf[MAX_PATH];
+ DWORD res;
+
+ res = WAIT_FAILED;
+ if (!input_mutex || WAIT_FAILED == (res = acquire_input_mutex (0)))
+ {
+ shared_name (buf, "cygcons.input.mutex", get_minor ());
+ input_mutex = OpenMutex (MAXIMUM_ALLOWED, TRUE, buf);
+ if (!input_mutex)
+ input_mutex = CreateMutex (&sec_none, FALSE, buf);
+ if (!input_mutex)
+ {
+ __seterrno ();
+ return;
+ }
+ }
+ if (res == WAIT_OBJECT_0)
+ release_input_mutex ();
+
+ res = WAIT_FAILED;
+ if (!output_mutex || WAIT_FAILED == (res = acquire_output_mutex (0)))
+ {
+ shared_name (buf, "cygcons.output.mutex", get_minor ());
+ output_mutex = OpenMutex (MAXIMUM_ALLOWED, TRUE, buf);
+ if (!output_mutex)
+ output_mutex = CreateMutex (&sec_none, FALSE, buf);
+ if (!output_mutex)
+ {
+ __seterrno ();
+ return;
+ }
+ }
+ if (res == WAIT_OBJECT_0)
+ release_output_mutex ();
+}
+
+inline DWORD
+dev_console::con_to_str (char *d, int dlen, WCHAR w)
+{
+ return sys_wcstombs (d, dlen, &w, 1);
+}
+
+inline UINT
+dev_console::get_console_cp ()
+{
+ /* The alternate charset is always 437, just as in the Linux console. */
+ return alternate_charset_active ? 437 : 0;
+}
+
+inline DWORD
+dev_console::str_to_con (mbtowc_p f_mbtowc, PWCHAR d, const char *s, DWORD sz)
+{
+ return _sys_mbstowcs (f_mbtowc, d, CONVERT_LIMIT, s, sz);
+}
+
+bool
+fhandler_console::set_raw_win32_keyboard_mode (bool new_mode)
+{
+ bool old_mode = con.raw_win32_keyboard_mode;
+ con.raw_win32_keyboard_mode = new_mode;
+ syscall_printf ("raw keyboard mode %sabled",
+ con.raw_win32_keyboard_mode ? "en" : "dis");
+ return old_mode;
+};
+
+void
+fhandler_console::set_cursor_maybe ()
+{
+ con.fillin (get_output_handle ());
+ /* Nothing to do for xterm compatible mode. */
+ if (wincap.has_con_24bit_colors () && !con_is_legacy)
+ return;
+ if (con.dwLastCursorPosition.X != con.b.dwCursorPosition.X ||
+ con.dwLastCursorPosition.Y != con.b.dwCursorPosition.Y)
+ {
+ acquire_attach_mutex (mutex_timeout);
+ SetConsoleCursorPosition (get_output_handle (), con.b.dwCursorPosition);
+ release_attach_mutex ();
+ con.dwLastCursorPosition = con.b.dwCursorPosition;
+ }
+}
+
+/* Workaround for a bug of windows xterm compatible mode. */
+/* The horizontal tab positions are broken after resize. */
+void
+fhandler_console::fix_tab_position (HANDLE h)
+{
+ /* Re-setting ENABLE_VIRTUAL_TERMINAL_PROCESSING
+ fixes the tab position. */
+ DWORD mode;
+ acquire_attach_mutex (mutex_timeout);
+ GetConsoleMode (h, &mode);
+ SetConsoleMode (h, mode & ~ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+ SetConsoleMode (h, mode);
+ release_attach_mutex ();
+}
+
+bool
+fhandler_console::send_winch_maybe ()
+{
+ SHORT y = con.dwWinSize.Y;
+ SHORT x = con.dwWinSize.X;
+ con.fillin (get_output_handle ());
+
+ if (y != con.dwWinSize.Y || x != con.dwWinSize.X)
+ {
+ con.scroll_region.Top = 0;
+ con.scroll_region.Bottom = -1;
+ if (wincap.has_con_24bit_colors () && !con_is_legacy
+ && wincap.has_con_broken_tabs ())
+ fix_tab_position (get_output_handle ());
+ get_ttyp ()->kill_pgrp (SIGWINCH);
+ return true;
+ }
+ return false;
+}
+
+/* Check whether a mouse event is to be reported as an escape sequence */
+bool
+fhandler_console::mouse_aware (MOUSE_EVENT_RECORD& mouse_event)
+{
+ if (!con.use_mouse)
+ return 0;
+
+ /* Adjust mouse position by window scroll buffer offset
+ and remember adjusted position in state for use by read() */
+ CONSOLE_SCREEN_BUFFER_INFO now;
+ acquire_attach_mutex (mutex_timeout);
+ BOOL r = GetConsoleScreenBufferInfo (get_output_handle (), &now);
+ release_attach_mutex ();
+ if (!r)
+ /* Cannot adjust position by window scroll buffer offset */
+ return 0;
+
+ con.dwMousePosition.X = mouse_event.dwMousePosition.X - now.srWindow.Left;
+ con.dwMousePosition.Y = mouse_event.dwMousePosition.Y - now.srWindow.Top;
+
+ return ((mouse_event.dwEventFlags == 0
+ || mouse_event.dwEventFlags == DOUBLE_CLICK)
+ && mouse_event.dwButtonState != con.dwLastButtonState)
+ || mouse_event.dwEventFlags == MOUSE_WHEELED
+ || (mouse_event.dwEventFlags == MOUSE_MOVED
+ && (con.dwMousePosition.X != con.dwLastMousePosition.X
+ || con.dwMousePosition.Y != con.dwLastMousePosition.Y)
+ && ((con.use_mouse >= 2 && mouse_event.dwButtonState)
+ || con.use_mouse >= 3));
+}
+
+
+bg_check_types
+fhandler_console::bg_check (int sig, bool dontsignal)
+{
+ /* Setting-up console mode for cygwin app. This is necessary if the
+ cygwin app and other non-cygwin apps are started simultaneously
+ in the same process group. */
+ if (sig == SIGTTIN)
+ {
+ set_input_mode (tty::cygwin, &tc ()->ti, get_handle_set ());
+ set_disable_master_thread (false, this);
+ }
+ if (sig == SIGTTOU)
+ set_output_mode (tty::cygwin, &tc ()->ti, get_handle_set ());
+
+ return fhandler_termios::bg_check (sig, dontsignal);
+}
+
+void
+fhandler_console::read (void *pv, size_t& buflen)
+{
+ termios_printf ("read(%p,%d)", pv, buflen);
+
+ push_process_state process_state (PID_TTYIN);
+
+ int copied_chars = 0;
+
+ DWORD timeout = is_nonblocking () ? 0 : INFINITE;
+
+ while (!input_ready && !get_cons_readahead_valid ())
+ {
+ int bgres;
+ if ((bgres = bg_check (SIGTTIN)) <= bg_eof)
+ {
+ buflen = bgres;
+ return;
+ }
+
+ set_cursor_maybe (); /* to make cursor appear on the screen immediately */
+wait_retry:
+ switch (cygwait (get_handle (), timeout))
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_SIGNALED:
+ goto sig_exit;
+ case WAIT_CANCELED:
+ process_state.pop ();
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ case WAIT_TIMEOUT:
+ set_sig_errno (EAGAIN);
+ buflen = (size_t) -1;
+ return;
+ default:
+ if (GetLastError () == ERROR_INVALID_HANDLE)
+ { /* Confirm the handle is still valid */
+ DWORD mode;
+ acquire_attach_mutex (mutex_timeout);
+ BOOL res = GetConsoleMode (get_handle (), &mode);
+ release_attach_mutex ();
+ if (res)
+ goto wait_retry;
+ }
+ goto err;
+ }
+
+#define buf ((char *) pv)
+
+ int ret;
+ acquire_input_mutex (mutex_timeout);
+ ret = process_input_message ();
+ switch (ret)
+ {
+ case input_error:
+ release_input_mutex ();
+ goto err;
+ case input_processing:
+ release_input_mutex ();
+ continue;
+ case input_ok: /* input ready */
+ break;
+ case input_signalled: /* signalled */
+ case input_winch:
+ release_input_mutex ();
+ if (global_sigs[get_ttyp ()->last_sig].sa_flags & SA_RESTART)
+ continue;
+ goto sig_exit;
+ default:
+ /* Should not come here */
+ release_input_mutex ();
+ goto err;
+ }
+ }
+
+ /* Check console read-ahead buffer filled from terminal requests */
+ while (con.cons_rapoi && *con.cons_rapoi && buflen)
+ {
+ buf[copied_chars++] = *con.cons_rapoi++;
+ buflen --;
+ }
+
+ copied_chars +=
+ get_readahead_into_buffer (buf + copied_chars, buflen);
+
+ if (!con_ra.ralen)
+ input_ready = false;
+ release_input_mutex ();
+
+#undef buf
+
+ buflen = copied_chars;
+ return;
+
+err:
+ __seterrno ();
+ buflen = (size_t) -1;
+ return;
+
+sig_exit:
+ set_sig_errno (EINTR);
+ buflen = (size_t) -1;
+}
+
+fhandler_console::input_states
+fhandler_console::process_input_message (void)
+{
+ char tmp[60];
+
+ if (!shared_console_info)
+ return input_error;
+
+ termios *ti = &(get_ttyp ()->ti);
+
+ fhandler_console::input_states stat = input_processing;
+ DWORD total_read, i;
+ INPUT_RECORD input_rec[INREC_SIZE];
+
+ acquire_attach_mutex (mutex_timeout);
+ BOOL r =
+ PeekConsoleInputW (get_handle (), input_rec, INREC_SIZE, &total_read);
+ release_attach_mutex ();
+ if (!r)
+ {
+ termios_printf ("PeekConsoleInput failed, %E");
+ return input_error;
+ }
+
+ for (i = 0; i < total_read; i ++)
+ {
+ DWORD nread = 1;
+ const char *toadd = NULL;
+
+ const WCHAR &unicode_char =
+ input_rec[i].Event.KeyEvent.uChar.UnicodeChar;
+ const DWORD &ctrl_key_state =
+ input_rec[i].Event.KeyEvent.dwControlKeyState;
+
+ /* check the event that occurred */
+ switch (input_rec[i].EventType)
+ {
+ case KEY_EVENT:
+
+ con.nModifiers = 0;
+
+#ifdef DEBUGGING
+ /* allow manual switching to/from raw mode via ctrl-alt-scrolllock */
+ if (input_rec[i].Event.KeyEvent.bKeyDown
+ && input_rec[i].Event.KeyEvent.wVirtualKeyCode == VK_SCROLL
+ && (ctrl_key_state & (LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED))
+ == (LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED))
+ {
+ set_raw_win32_keyboard_mode (!con.raw_win32_keyboard_mode);
+ continue;
+ }
+#endif
+
+ if (con.raw_win32_keyboard_mode)
+ {
+ __small_sprintf (tmp, "\033{%u;%u;%u;%u;%u;%luK",
+ input_rec[i].Event.KeyEvent.bKeyDown,
+ input_rec[i].Event.KeyEvent.wRepeatCount,
+ input_rec[i].Event.KeyEvent.wVirtualKeyCode,
+ input_rec[i].Event.KeyEvent.wVirtualScanCode,
+ input_rec[i].Event.KeyEvent.uChar.UnicodeChar,
+ input_rec[i].Event.KeyEvent.dwControlKeyState);
+ toadd = tmp;
+ nread = strlen (toadd);
+ break;
+ }
+
+ /* Ignore key up events, except for Alt+Numpad events. */
+ if (!input_rec[i].Event.KeyEvent.bKeyDown &&
+ !is_alt_numpad_event (&input_rec[i]))
+ continue;
+ /* Ignore Alt+Numpad keys. They are eventually handled below after
+ releasing the Alt key. */
+ if (input_rec[i].Event.KeyEvent.bKeyDown
+ && is_alt_numpad_key (&input_rec[i]))
+ continue;
+
+ if (ctrl_key_state & SHIFT_PRESSED)
+ con.nModifiers |= 1;
+ if (ctrl_key_state & RIGHT_ALT_PRESSED)
+ con.nModifiers |= 2;
+ if (ctrl_key_state & CTRL_PRESSED)
+ con.nModifiers |= 4;
+ if (ctrl_key_state & LEFT_ALT_PRESSED)
+ con.nModifiers |= 8;
+
+ /* Allow Backspace to emit ^? and escape sequences. */
+ if (input_rec[i].Event.KeyEvent.wVirtualKeyCode == VK_BACK)
+ {
+ char c = con.backspace_keycode;
+ nread = 0;
+ if (ctrl_key_state & ALT_PRESSED)
+ {
+ if (con.metabit)
+ c |= 0x80;
+ else
+ tmp[nread++] = '\e';
+ }
+ tmp[nread++] = c;
+ tmp[nread] = 0;
+ toadd = tmp;
+ }
+ /* Allow Ctrl-Space to emit ^@ */
+ else if (input_rec[i].Event.KeyEvent.wVirtualKeyCode
+ == ((wincap.has_con_24bit_colors () && !con_is_legacy) ?
+ '2' : VK_SPACE)
+ && (ctrl_key_state & CTRL_PRESSED)
+ && !(ctrl_key_state & ALT_PRESSED))
+ toadd = "";
+ else if (unicode_char == 0
+ /* arrow/function keys */
+ || (input_rec[i].Event.KeyEvent.dwControlKeyState
+ & ENHANCED_KEY))
+ {
+ toadd = get_nonascii_key (input_rec[i], tmp);
+ if (!toadd)
+ {
+ con.nModifiers = 0;
+ continue;
+ }
+ nread = strlen (toadd);
+ }
+ else
+ {
+ WCHAR second = unicode_char >= 0xd800 && unicode_char <= 0xdbff
+ && i + 1 < total_read ?
+ input_rec[i + 1].Event.KeyEvent.uChar.UnicodeChar : 0;
+
+ if (second < 0xdc00 || second > 0xdfff)
+ {
+ nread = con.con_to_str (tmp + 1, 59, unicode_char);
+ }
+ else
+ {
+ /* handle surrogate pairs */
+ WCHAR pair[2] = { unicode_char, second };
+ nread = sys_wcstombs (tmp + 1, 59, pair, 2);
+ i++;
+ }
+
+ /* Determine if the keystroke is modified by META. The tricky
+ part is to distinguish whether the right Alt key should be
+ recognized as Alt, or as AltGr. */
+ bool meta =
+ /* Alt but not AltGr (= left ctrl + right alt)? */
+ (ctrl_key_state & ALT_PRESSED) != 0
+ && ((ctrl_key_state & CTRL_PRESSED) == 0
+ /* but also allow Alt-AltGr: */
+ || (ctrl_key_state & ALT_PRESSED) == ALT_PRESSED
+ || (unicode_char <= 0x1f || unicode_char == 0x7f));
+ if (!meta)
+ {
+ /* Determine if the character is in the current multibyte
+ charset. The test is easy. If the multibyte sequence
+ is > 1 and the first byte is ASCII CAN, the character
+ has been translated into the ASCII CAN + UTF-8 replacement
+ sequence. If so, just ignore the keypress.
+ FIXME: Is there a better solution? */
+ if (nread > 1 && tmp[1] == 0x18)
+ beep ();
+ else
+ toadd = tmp + 1;
+ }
+ else if (con.metabit)
+ {
+ tmp[1] |= 0x80;
+ toadd = tmp + 1;
+ }
+ else
+ {
+ tmp[0] = '\033';
+ tmp[1] = cyg_tolower (tmp[1]);
+ toadd = tmp;
+ nread++;
+ con.nModifiers &= ~4;
+ }
+ }
+ break;
+
+ case MOUSE_EVENT:
+ send_winch_maybe ();
+ {
+ MOUSE_EVENT_RECORD& mouse_event = input_rec[i].Event.MouseEvent;
+ /* As a unique guard for mouse report generation,
+ call mouse_aware() which is common with select(), so the result
+ of select() and the actual read() will be consistent on the
+ issue of whether input (i.e. a mouse escape sequence) will
+ be available or not */
+ if (mouse_aware (mouse_event))
+ {
+ /* Note: Reported mouse position was already retrieved by
+ mouse_aware() and adjusted by window scroll buffer offset */
+
+ /* Treat the double-click event like a regular button press */
+ if (mouse_event.dwEventFlags == DOUBLE_CLICK)
+ {
+ syscall_printf ("mouse: double-click -> click");
+ mouse_event.dwEventFlags = 0;
+ }
+
+ /* This code assumes Windows never reports multiple button
+ events at the same time. */
+ int b = 0;
+ char sz[32];
+ char mode6_term = 'M';
+
+ if (mouse_event.dwEventFlags == MOUSE_WHEELED)
+ {
+ if (mouse_event.dwButtonState & 0xFF800000)
+ {
+ b = 0x41;
+ strcpy (sz, "wheel down");
+ }
+ else
+ {
+ b = 0x40;
+ strcpy (sz, "wheel up");
+ }
+ }
+ else
+ {
+ /* Ignore unimportant mouse buttons */
+ mouse_event.dwButtonState &= 0x7;
+
+ if (mouse_event.dwEventFlags == MOUSE_MOVED)
+ {
+ b = con.last_button_code;
+ }
+ else if (mouse_event.dwButtonState < con.dwLastButtonState
+ && !con.ext_mouse_mode6)
+ {
+ b = 3;
+ strcpy (sz, "btn up");
+ }
+ else if ((mouse_event.dwButtonState & 1)
+ != (con.dwLastButtonState & 1))
+ {
+ b = 0;
+ strcpy (sz, "btn1 down");
+ }
+ else if ((mouse_event.dwButtonState & 2)
+ != (con.dwLastButtonState & 2))
+ {
+ b = 2;
+ strcpy (sz, "btn2 down");
+ }
+ else if ((mouse_event.dwButtonState & 4)
+ != (con.dwLastButtonState & 4))
+ {
+ b = 1;
+ strcpy (sz, "btn3 down");
+ }
+
+ if (con.ext_mouse_mode6 /* distinguish release */
+ && mouse_event.dwButtonState < con.dwLastButtonState)
+ mode6_term = 'm';
+
+ con.last_button_code = b;
+
+ if (mouse_event.dwEventFlags == MOUSE_MOVED)
+ {
+ b += 32;
+ strcpy (sz, "move");
+ }
+ else
+ {
+ /* Remember the modified button state */
+ con.dwLastButtonState = mouse_event.dwButtonState;
+ }
+ }
+
+ /* Remember mouse position */
+ con.dwLastMousePosition.X = con.dwMousePosition.X;
+ con.dwLastMousePosition.Y = con.dwMousePosition.Y;
+
+ /* Remember the modifiers */
+ con.nModifiers = 0;
+ if (mouse_event.dwControlKeyState & SHIFT_PRESSED)
+ con.nModifiers |= 0x4;
+ if (mouse_event.dwControlKeyState & ALT_PRESSED)
+ con.nModifiers |= 0x8;
+ if (mouse_event.dwControlKeyState & CTRL_PRESSED)
+ con.nModifiers |= 0x10;
+
+ /* Indicate the modifiers */
+ b |= con.nModifiers;
+
+ /* We can now create the code. */
+ if (con.ext_mouse_mode6)
+ {
+ __small_sprintf (tmp, "\033[<%d;%d;%d%c", b,
+ con.dwMousePosition.X + 1,
+ con.dwMousePosition.Y + 1,
+ mode6_term);
+ nread = strlen (tmp);
+ }
+ else if (con.ext_mouse_mode15)
+ {
+ __small_sprintf (tmp, "\033[%d;%d;%dM", b + 32,
+ con.dwMousePosition.X + 1,
+ con.dwMousePosition.Y + 1);
+ nread = strlen (tmp);
+ }
+ else if (con.ext_mouse_mode5)
+ {
+ unsigned int xcode = con.dwMousePosition.X + ' ' + 1;
+ unsigned int ycode = con.dwMousePosition.Y + ' ' + 1;
+
+ __small_sprintf (tmp, "\033[M%c", b + ' ');
+ nread = 4;
+ /* the neat nested encoding function of mintty
+ does not compile in g++, so let's unfold it: */
+ if (xcode < 0x80)
+ tmp [nread++] = xcode;
+ else if (xcode < 0x800)
+ {
+ tmp [nread++] = 0xC0 + (xcode >> 6);
+ tmp [nread++] = 0x80 + (xcode & 0x3F);
+ }
+ else
+ tmp [nread++] = 0;
+ if (ycode < 0x80)
+ tmp [nread++] = ycode;
+ else if (ycode < 0x800)
+ {
+ tmp [nread++] = 0xC0 + (ycode >> 6);
+ tmp [nread++] = 0x80 + (ycode & 0x3F);
+ }
+ else
+ tmp [nread++] = 0;
+ }
+ else
+ {
+ unsigned int xcode = con.dwMousePosition.X + ' ' + 1;
+ unsigned int ycode = con.dwMousePosition.Y + ' ' + 1;
+ if (xcode >= 256)
+ xcode = 0;
+ if (ycode >= 256)
+ ycode = 0;
+ __small_sprintf (tmp, "\033[M%c%c%c", b + ' ',
+ xcode, ycode);
+ nread = 6; /* tmp may contain NUL bytes */
+ }
+ syscall_printf ("mouse: %s at (%d,%d)", sz,
+ con.dwMousePosition.X,
+ con.dwMousePosition.Y);
+
+ toadd = tmp;
+ }
+ }
+ break;
+
+ case FOCUS_EVENT:
+ if (con.use_focus)
+ {
+ if (input_rec[i].Event.FocusEvent.bSetFocus)
+ __small_sprintf (tmp, "\033[I");
+ else
+ __small_sprintf (tmp, "\033[O");
+
+ toadd = tmp;
+ nread = 3;
+ }
+ break;
+
+ case WINDOW_BUFFER_SIZE_EVENT:
+ if (send_winch_maybe ())
+ {
+ stat = input_winch;
+ goto out;
+ }
+ /* fall through */
+ default:
+ continue;
+ }
+
+ if (toadd)
+ {
+ ssize_t ret;
+ line_edit_status res = line_edit (toadd, nread, *ti, &ret);
+ if (res == line_edit_signalled)
+ {
+ stat = input_signalled;
+ goto out;
+ }
+ else if (res == line_edit_input_done)
+ {
+ input_ready = true;
+ stat = input_ok;
+ if (ti->c_lflag & ICANON)
+ goto out;
+ }
+ }
+ }
+out:
+ /* Discard processed recored. */
+ DWORD discard_len = min (total_read, i + 1);
+ /* If input is signalled, do not discard input here because
+ tcflush() is already called from line_edit(). */
+ if (stat == input_signalled && !(ti->c_lflag & NOFLSH))
+ discard_len = 0;
+ if (discard_len)
+ {
+ DWORD discarded;
+ acquire_attach_mutex (mutex_timeout);
+ ReadConsoleInputW (get_handle (), input_rec, discard_len, &discarded);
+ release_attach_mutex ();
+ con.num_processed -= min (con.num_processed, discarded);
+ }
+ return stat;
+}
+
+bool
+dev_console::fillin (HANDLE h)
+{
+ acquire_attach_mutex (mutex_timeout);
+ bool ret = GetConsoleScreenBufferInfo (h, &b);
+ release_attach_mutex ();
+
+ if (ret)
+ {
+ dwWinSize.Y = 1 + b.srWindow.Bottom - b.srWindow.Top;
+ dwWinSize.X = 1 + b.srWindow.Right - b.srWindow.Left;
+ if (b.dwCursorPosition.Y > dwEnd.Y
+ || (b.dwCursorPosition.Y >= dwEnd.Y
+ && b.dwCursorPosition.X > dwEnd.X))
+ dwEnd = b.dwCursorPosition;
+ }
+ else
+ {
+ memset (&b, 0, sizeof (b));
+ dwWinSize.Y = 25;
+ dwWinSize.X = 80;
+ b.srWindow.Bottom = 24;
+ b.srWindow.Right = 79;
+ }
+
+ return ret;
+}
+
+void
+dev_console::scroll_buffer (HANDLE h, int x1, int y1, int x2, int y2,
+ int xn, int yn)
+{
+/* Scroll the screen context.
+ x1, y1 - ul corner
+ x2, y2 - dr corner
+ xn, yn - new ul corner
+ Negative values represents current screen dimensions
+*/
+ SMALL_RECT sr1, sr2;
+ CHAR_INFO fill;
+ COORD dest;
+ fill.Char.UnicodeChar = L' ';
+ fill.Attributes = current_win32_attr;
+
+ fillin (h);
+ sr1.Left = x1 >= 0 ? x1 : dwWinSize.X - 1;
+ sr1.Top = y1 >= 0 ? y1 : b.srWindow.Bottom;
+ sr1.Right = x2 >= 0 ? x2 : dwWinSize.X - 1;
+ sr1.Bottom = y2 >= 0 ? y2 : b.srWindow.Bottom;
+ sr2.Top = b.srWindow.Top + scroll_region.Top;
+ sr2.Left = 0;
+ sr2.Bottom = (scroll_region.Bottom < 0) ?
+ b.srWindow.Bottom : b.srWindow.Top + scroll_region.Bottom;
+ sr2.Right = dwWinSize.X - 1;
+ if (sr1.Bottom > sr2.Bottom && sr1.Top <= sr2.Bottom)
+ sr1.Bottom = sr2.Bottom;
+ dest.X = xn >= 0 ? xn : dwWinSize.X - 1;
+ dest.Y = yn >= 0 ? yn : b.srWindow.Bottom;
+ acquire_attach_mutex (mutex_timeout);
+ ScrollConsoleScreenBufferW (h, &sr1, &sr2, dest, &fill);
+ release_attach_mutex ();
+}
+
+inline void
+fhandler_console::scroll_buffer (int x1, int y1, int x2, int y2,
+ int xn, int yn)
+{
+ con.scroll_buffer (get_output_handle (), x1, y1, x2, y2, xn, yn);
+}
+
+inline void
+fhandler_console::scroll_buffer_screen (int x1, int y1, int x2, int y2,
+ int xn, int yn)
+{
+ if (y1 >= 0)
+ y1 += con.b.srWindow.Top;
+ if (y2 >= 0)
+ y2 += con.b.srWindow.Top;
+ if (yn >= 0)
+ yn += con.b.srWindow.Top;
+ con.scroll_buffer (get_output_handle (), x1, y1, x2, y2, xn, yn);
+}
+
+int
+fhandler_console::dup (fhandler_base *child, int flags)
+{
+ /* See comments in fhandler_pty_slave::dup */
+ if (myself->ctty != -2)
+ myself->set_ctty (this, flags);
+ return 0;
+}
+
+static void hook_conemu_cygwin_connector();
+
+int
+fhandler_console::open (int flags, mode_t)
+{
+ HANDLE h;
+
+ if (dev () == FH_ERROR)
+ {
+ set_errno (EPERM); /* constructor found an error */
+ return 0;
+ }
+
+ tcinit (false);
+
+ set_handle (NULL);
+ set_output_handle (NULL);
+
+ /* Open the input handle as handle_ */
+ h = CreateFileW (L"CONIN$", GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, &sec_none,
+ OPEN_EXISTING, 0, 0);
+
+ if (h == INVALID_HANDLE_VALUE)
+ {
+ __seterrno ();
+ return 0;
+ }
+ set_handle (h);
+ handle_set.input_handle = h;
+
+ h = CreateFileW (L"CONOUT$", GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, &sec_none,
+ OPEN_EXISTING, 0, 0);
+
+ if (h == INVALID_HANDLE_VALUE)
+ {
+ __seterrno ();
+ return 0;
+ }
+ set_output_handle (h);
+ handle_set.output_handle = h;
+ wpbuf.init (get_output_handle ());
+
+ setup_io_mutex ();
+ handle_set.input_mutex = input_mutex;
+ handle_set.output_mutex = output_mutex;
+
+ if (con.fillin (get_output_handle ()))
+ {
+ con.current_win32_attr = con.b.wAttributes;
+ if (!con.default_color)
+ con.default_color = con.b.wAttributes;
+ con.set_default_attr ();
+ }
+
+ set_open_status ();
+
+ if (myself->pid == con.owner && wincap.has_con_24bit_colors ())
+ {
+ bool is_legacy = false;
+ DWORD dwMode;
+ /* Check xterm compatible mode in output */
+ acquire_attach_mutex (mutex_timeout);
+ GetConsoleMode (get_output_handle (), &dwMode);
+ con.orig_virtual_terminal_processing_mode =
+ !!(dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+ if (!SetConsoleMode (get_output_handle (),
+ dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
+ is_legacy = true;
+ SetConsoleMode (get_output_handle (), dwMode);
+ /* Check xterm compatible mode in input */
+ GetConsoleMode (get_handle (), &dwMode);
+ if (!SetConsoleMode (get_handle (),
+ dwMode | ENABLE_VIRTUAL_TERMINAL_INPUT))
+ is_legacy = true;
+ SetConsoleMode (get_handle (), dwMode);
+ release_attach_mutex ();
+ con.is_legacy = is_legacy;
+ extern int sawTERM;
+ if (con_is_legacy && !sawTERM)
+ setenv ("TERM", "cygwin", 1);
+ }
+
+ debug_printf ("opened conin$ %p, conout$ %p", get_handle (),
+ get_output_handle ());
+
+ if (myself->pid == con.owner)
+ {
+ if (GetModuleHandle ("ConEmuHk64.dll"))
+ hook_conemu_cygwin_connector ();
+ char name[MAX_PATH];
+ shared_name (name, CONS_THREAD_SYNC, get_minor ());
+ thread_sync_event = CreateEvent(NULL, FALSE, FALSE, name);
+ new cygthread (::cons_master_thread, this, "consm");
+ WaitForSingleObject (thread_sync_event, INFINITE);
+ CloseHandle (thread_sync_event);
+ }
+ return 1;
+}
+
+bool
+fhandler_console::open_setup (int flags)
+{
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ if (myself->set_ctty (this, flags) && !myself->cygstarted)
+ init_console_handler (true);
+ return fhandler_base::open_setup (flags);
+}
+
+void
+fhandler_console::post_open_setup (int fd)
+{
+ /* Setting-up console mode for cygwin app started from non-cygwin app. */
+ if (fd == 0)
+ {
+ set_input_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set);
+ set_disable_master_thread (false, this);
+ }
+ else if (fd == 1 || fd == 2)
+ set_output_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set);
+
+ fhandler_base::post_open_setup (fd);
+}
+
+int
+fhandler_console::close ()
+{
+ debug_printf ("closing: %p, %p", get_handle (), get_output_handle ());
+
+ acquire_output_mutex (mutex_timeout);
+
+ if (shared_console_info)
+ {
+ /* Restore console mode if this is the last closure. */
+ OBJECT_BASIC_INFORMATION obi;
+ NTSTATUS status;
+ status = NtQueryObject (get_handle (), ObjectBasicInformation,
+ &obi, sizeof obi, NULL);
+ if ((NT_SUCCESS (status) && obi.HandleCount == 1)
+ || myself->pid == con.owner)
+ {
+ /* Cleaning-up console mode for cygwin apps. */
+ set_output_mode (tty::restore, &get_ttyp ()->ti, &handle_set);
+ set_input_mode (tty::restore, &get_ttyp ()->ti, &handle_set);
+ set_disable_master_thread (true, this);
+ }
+ }
+
+ release_output_mutex ();
+
+ if (shared_console_info && con.owner == myself->pid
+ && master_thread_started)
+ {
+ char name[MAX_PATH];
+ shared_name (name, CONS_THREAD_SYNC, get_minor ());
+ thread_sync_event = OpenEvent (MAXIMUM_ALLOWED, FALSE, name);
+ con.owner = MAX_PID + 1;
+ WaitForSingleObject (thread_sync_event, INFINITE);
+ CloseHandle (thread_sync_event);
+ con.owner = 0;
+ }
+
+ CloseHandle (input_mutex);
+ input_mutex = NULL;
+ CloseHandle (output_mutex);
+ output_mutex = NULL;
+
+ CloseHandle (get_handle ());
+ CloseHandle (get_output_handle ());
+
+ if (con_ra.rabuf)
+ free (con_ra.rabuf);
+
+ if (!have_execed && !invisible_console)
+ free_console ();
+ return 0;
+}
+
+int
+fhandler_console::ioctl (unsigned int cmd, void *arg)
+{
+ int res = fhandler_termios::ioctl (cmd, arg);
+ if (res <= 0)
+ return res;
+ acquire_output_mutex (mutex_timeout);
+ switch (cmd)
+ {
+ case TIOCGWINSZ:
+ int st;
+
+ st = con.fillin (get_output_handle ());
+ if (st)
+ {
+ /* *not* the buffer size, the actual screen size... */
+ /* based on Left Top Right Bottom of srWindow */
+ ((struct winsize *) arg)->ws_row = con.dwWinSize.Y;
+ ((struct winsize *) arg)->ws_col = con.dwWinSize.X;
+ syscall_printf ("WINSZ: (row=%d,col=%d)",
+ ((struct winsize *) arg)->ws_row,
+ ((struct winsize *) arg)->ws_col);
+ release_output_mutex ();
+ return 0;
+ }
+ else
+ {
+ syscall_printf ("WINSZ failed");
+ __seterrno ();
+ release_output_mutex ();
+ return -1;
+ }
+ release_output_mutex ();
+ return 0;
+ case TIOCSWINSZ:
+ bg_check (SIGTTOU);
+ release_output_mutex ();
+ return 0;
+ case KDGKBMETA:
+ *(int *) arg = (con.metabit) ? K_METABIT : K_ESCPREFIX;
+ release_output_mutex ();
+ return 0;
+ case KDSKBMETA:
+ if ((intptr_t) arg == K_METABIT)
+ con.metabit = TRUE;
+ else if ((intptr_t) arg == K_ESCPREFIX)
+ con.metabit = FALSE;
+ else
+ {
+ set_errno (EINVAL);
+ release_output_mutex ();
+ return -1;
+ }
+ release_output_mutex ();
+ return 0;
+ case TIOCLINUX:
+ if (*(unsigned char *) arg == 6)
+ {
+ *(unsigned char *) arg = (unsigned char) con.nModifiers;
+ release_output_mutex ();
+ return 0;
+ }
+ set_errno (EINVAL);
+ release_output_mutex ();
+ return -1;
+ case FIONREAD:
+ {
+ DWORD n;
+ int ret = 0;
+ INPUT_RECORD inp[INREC_SIZE];
+ acquire_attach_mutex (mutex_timeout);
+ BOOL r = PeekConsoleInputW (get_handle (), inp, INREC_SIZE, &n);
+ release_attach_mutex ();
+ if (!r)
+ {
+ set_errno (EINVAL);
+ release_output_mutex ();
+ return -1;
+ }
+ bool saw_eol = false;
+ for (DWORD i=0; i<n; i++)
+ if (inp[i].EventType == KEY_EVENT &&
+ inp[i].Event.KeyEvent.bKeyDown &&
+ inp[i].Event.KeyEvent.uChar.UnicodeChar)
+ {
+ WCHAR wc = inp[i].Event.KeyEvent.uChar.UnicodeChar;
+ char mbs[8];
+ int len = con.con_to_str (mbs, sizeof (mbs), wc);
+ if ((get_ttyp ()->ti.c_lflag & ICANON) &&
+ len == 1 && CCEQ (get_ttyp ()->ti.c_cc[VEOF], mbs[0]))
+ {
+ saw_eol = true;
+ break;
+ }
+ ret += len;
+ const char eols[] = {
+ '\n',
+ '\r',
+ (char) get_ttyp ()->ti.c_cc[VEOL],
+ (char) get_ttyp ()->ti.c_cc[VEOL2]
+ };
+ if ((get_ttyp ()->ti.c_lflag & ICANON) &&
+ len == 1 && memchr (eols, mbs[0], sizeof (eols)))
+ {
+ saw_eol = true;
+ break;
+ }
+ }
+ if ((get_ttyp ()->ti.c_lflag & ICANON) && !saw_eol)
+ *(int *) arg = 0;
+ else
+ *(int *) arg = ret;
+ release_output_mutex ();
+ return 0;
+ }
+ break;
+ }
+
+ release_output_mutex ();
+ return fhandler_base::ioctl (cmd, arg);
+}
+
+int
+fhandler_console::tcflush (int queue)
+{
+ int res = 0;
+ if (queue == TCIFLUSH
+ || queue == TCIOFLUSH)
+ {
+ acquire_attach_mutex (mutex_timeout);
+ BOOL r = FlushConsoleInputBuffer (get_handle ());
+ release_attach_mutex ();
+ if (!r)
+ {
+ __seterrno ();
+ res = -1;
+ }
+ con.num_processed = 0;
+ }
+ return res;
+}
+
+int
+fhandler_console::tcsetattr (int a, struct termios const *t)
+{
+ get_ttyp ()->ti = *t;
+ return 0;
+}
+
+int
+fhandler_console::tcgetattr (struct termios *t)
+{
+ *t = get_ttyp ()->ti;
+ t->c_cflag |= CS8;
+ return 0;
+}
+
+fhandler_console::fhandler_console (fh_devices unit) :
+ fhandler_termios (), input_ready (false), thread_sync_event (NULL),
+ input_mutex (NULL), output_mutex (NULL)
+{
+ if (unit > 0)
+ dev ().parse (unit);
+ setup ();
+ trunc_buf.len = 0;
+ _tc = &(shared_console_info->tty_min_state);
+}
+
+void
+dev_console::set_color (HANDLE h)
+{
+ WORD win_fg = fg;
+ WORD win_bg = bg;
+ if (reverse)
+ {
+ WORD save_fg = win_fg;
+ win_fg = (win_bg & BACKGROUND_RED ? FOREGROUND_RED : 0) |
+ (win_bg & BACKGROUND_GREEN ? FOREGROUND_GREEN : 0) |
+ (win_bg & BACKGROUND_BLUE ? FOREGROUND_BLUE : 0) |
+ (win_bg & BACKGROUND_INTENSITY ? FOREGROUND_INTENSITY : 0);
+ win_bg = (save_fg & FOREGROUND_RED ? BACKGROUND_RED : 0) |
+ (save_fg & FOREGROUND_GREEN ? BACKGROUND_GREEN : 0) |
+ (save_fg & FOREGROUND_BLUE ? BACKGROUND_BLUE : 0) |
+ (save_fg & FOREGROUND_INTENSITY ? BACKGROUND_INTENSITY : 0);
+ }
+
+ /* apply attributes */
+ if (underline)
+ win_fg = underline_color;
+ /* emulate blink with bright background */
+ if (blink)
+ win_bg |= BACKGROUND_INTENSITY;
+ if (intensity == INTENSITY_INVISIBLE)
+ win_fg = win_bg;
+ else if (intensity != INTENSITY_BOLD)
+ /* nothing to do */;
+ /* apply foreground intensity only in non-reverse mode! */
+ else if (reverse)
+ win_bg |= BACKGROUND_INTENSITY;
+ else
+ win_fg |= FOREGROUND_INTENSITY;
+
+ current_win32_attr = win_fg | win_bg;
+ if (h)
+ {
+ acquire_attach_mutex (mutex_timeout);
+ SetConsoleTextAttribute (h, current_win32_attr);
+ release_attach_mutex ();
+ }
+}
+
+#define FOREGROUND_ATTR_MASK (FOREGROUND_RED | FOREGROUND_GREEN | \
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY)
+#define BACKGROUND_ATTR_MASK (BACKGROUND_RED | BACKGROUND_GREEN | \
+ BACKGROUND_BLUE | BACKGROUND_INTENSITY)
+void
+dev_console::set_default_attr ()
+{
+ blink = underline = reverse = false;
+ intensity = INTENSITY_NORMAL;
+ fg = default_color & FOREGROUND_ATTR_MASK;
+ bg = default_color & BACKGROUND_ATTR_MASK;
+ set_color (NULL);
+}
+
+int
+dev_console::set_cl_x (cltype x)
+{
+ if (x == cl_disp_beg || x == cl_buf_beg)
+ return 0;
+ if (x == cl_disp_end)
+ return dwWinSize.X - 1;
+ if (x == cl_buf_end)
+ return b.dwSize.X - 1;
+ return b.dwCursorPosition.X;
+}
+
+int
+dev_console::set_cl_y (cltype y)
+{
+ if (y == cl_buf_beg)
+ return 0;
+ if (y == cl_disp_beg)
+ return b.srWindow.Top;
+ if (y == cl_disp_end)
+ return b.srWindow.Bottom;
+ if (y == cl_buf_end)
+ return b.dwSize.Y - 1;
+ return b.dwCursorPosition.Y;
+}
+
+bool
+dev_console::scroll_window (HANDLE h, int x1, int y1, int x2, int y2)
+{
+ if (save_buf || x1 != 0 || x2 != dwWinSize.X - 1 || y1 != b.srWindow.Top
+ || y2 != b.srWindow.Bottom || b.dwSize.Y <= dwWinSize.Y)
+ return false;
+
+ SMALL_RECT sr;
+ int toscroll = dwEnd.Y - b.srWindow.Top + 1;
+ sr.Left = sr.Right = dwEnd.X = 0;
+
+ acquire_attach_mutex (mutex_timeout);
+ if (b.srWindow.Bottom + toscroll >= b.dwSize.Y)
+ {
+ /* So we're at the end of the buffer and scrolling the console window
+ would move us beyond the buffer. What we do here is to scroll the
+ console buffer upward by just as much so that the current last line
+ becomes the last line just prior to the first window line. That
+ keeps the end of the console buffer intact, as desired. */
+ SMALL_RECT br;
+ COORD dest;
+ CHAR_INFO fill;
+
+ br.Left = 0;
+ br.Top = (b.srWindow.Bottom - b.srWindow.Top) + 1
+ - (b.dwSize.Y - dwEnd.Y - 1);
+ br.Right = b.dwSize.X - 1;
+ br.Bottom = b.dwSize.Y - 1;
+ dest.X = dest.Y = 0;
+ fill.Char.UnicodeChar = L' ';
+ fill.Attributes = current_win32_attr;
+ ScrollConsoleScreenBufferW (h, &br, NULL, dest, &fill);
+ /* Since we're moving the console buffer under the console window
+ we only have to move the console window if the user scrolled the
+ window upwards. The number of lines is the distance to the
+ buffer bottom. */
+ toscroll = b.dwSize.Y - b.srWindow.Bottom - 1;
+ /* Fix dwEnd to reflect the new cursor line. Take the above scrolling
+ into account and subtract 1 to account for the increment below. */
+ dwEnd.Y = b.dwCursorPosition.Y + toscroll - 1;
+ }
+ if (toscroll)
+ {
+ /* FIXME: For some reason SetConsoleWindowInfo does not correctly
+ set the scrollbars. Calling SetConsoleCursorPosition here is
+ just a workaround which doesn't cover all cases. In some scenarios
+ the scrollbars are still off by one console window size. */
+
+ /* The reminder of the console buffer is big enough to simply move
+ the console window. We have to set the cursor first, otherwise
+ the scroll bars will not be corrected. */
+ SetConsoleCursorPosition (h, dwEnd);
+ /* If the user scolled manually, setting the cursor position might scroll
+ the console window so that the cursor is not at the top. Correct
+ the action by moving the window down again so the cursor is one line
+ above the new window position. */
+ GetConsoleScreenBufferInfo (h, &b);
+ if (b.dwCursorPosition.Y >= b.srWindow.Top)
+ toscroll = b.dwCursorPosition.Y - b.srWindow.Top + 1;
+ /* Move the window accordingly. */
+ sr.Top = sr.Bottom = toscroll;
+ SetConsoleWindowInfo (h, FALSE, &sr);
+ }
+ /* Eventually set cursor to new end position at the top of the window. */
+ dwEnd.Y++;
+ SetConsoleCursorPosition (h, dwEnd);
+ release_attach_mutex ();
+ /* Fix up console buffer info. */
+ fillin (h);
+ return true;
+}
+
+/*
+ * Clear the screen context from x1/y1 to x2/y2 cell.
+ * Negative values represents current screen dimensions
+ */
+void
+fhandler_console::clear_screen (cltype xc1, cltype yc1, cltype xc2, cltype yc2)
+{
+ HANDLE h = get_output_handle ();
+ SHORT oldEndY = con.dwEnd.Y;
+
+ con.fillin (h);
+
+ int x1 = con.set_cl_x (xc1);
+ int y1 = con.set_cl_y (yc1);
+ int x2 = con.set_cl_x (xc2);
+ int y2 = con.set_cl_y (yc2);
+
+ /* Make correction for the following situation: The console buffer
+ is only partially used and the user scrolled down into the as yet
+ unused area so far that the cursor is outside the window buffer. */
+ if (oldEndY < con.dwEnd.Y && oldEndY < con.b.srWindow.Top)
+ {
+ con.dwEnd.Y = con.b.dwCursorPosition.Y = oldEndY;
+ y1 = con.b.srWindow.Top;
+ }
+
+ /* Detect special case - scroll the screen if we have a buffer in order to
+ preserve the buffer. */
+ if (!con.scroll_window (h, x1, y1, x2, y2))
+ con.clear_screen (h, x1, y1, x2, y2);
+}
+
+void
+dev_console::clear_screen (HANDLE h, int x1, int y1, int x2, int y2)
+{
+ COORD tlc;
+ DWORD done;
+ int num;
+
+ num = abs (y1 - y2) * b.dwSize.X + abs (x1 - x2) + 1;
+
+ if ((y2 * b.dwSize.X + x2) > (y1 * b.dwSize.X + x1))
+ {
+ tlc.X = x1;
+ tlc.Y = y1;
+ }
+ else
+ {
+ tlc.X = x2;
+ tlc.Y = y2;
+ }
+ acquire_attach_mutex (mutex_timeout);
+ FillConsoleOutputCharacterW (h, L' ', num, tlc, &done);
+ FillConsoleOutputAttribute (h, current_win32_attr, num, tlc, &done);
+ release_attach_mutex ();
+}
+
+void
+fhandler_console::cursor_set (bool rel_to_top, int x, int y)
+{
+ COORD pos;
+
+ con.fillin (get_output_handle ());
+#if 0
+ /* Setting y to the current b.srWindow.Bottom here is the reason that the window
+ isn't scrolled back to the current cursor position like it's done in
+ any other terminal. Rather, the curser is forced to the bottom of the
+ currently scrolled region. This breaks the console buffer content if
+ output is generated while the user had the window scrolled back. This
+ behaviour is very old, it has no matching ChangeLog entry.
+ Just disable for now but keep the code in for future reference. */
+ if (y > con.b.srWindow.Bottom)
+ y = con.b.srWindow.Bottom;
+ else
+#endif
+ if (y < 0)
+ y = 0;
+ else if (rel_to_top)
+ y += con.b.srWindow.Top;
+
+ if (x > con.dwWinSize.X)
+ x = con.dwWinSize.X - 1;
+ else if (x < 0)
+ x = 0;
+
+ pos.X = x;
+ pos.Y = y;
+ acquire_attach_mutex (mutex_timeout);
+ SetConsoleCursorPosition (get_output_handle (), pos);
+ release_attach_mutex ();
+}
+
+void
+fhandler_console::cursor_rel (int x, int y)
+{
+ con.fillin (get_output_handle ());
+ x += con.b.dwCursorPosition.X;
+ y += con.b.dwCursorPosition.Y;
+ cursor_set (false, x, y);
+}
+
+void
+fhandler_console::cursor_get (int *x, int *y)
+{
+ con.fillin (get_output_handle ());
+ *y = con.b.dwCursorPosition.Y;
+ *x = con.b.dwCursorPosition.X;
+}
+
+/* VT100 line drawing graphics mode maps `abcdefghijklmnopqrstuvwxyz{|}~ to
+ graphical characters */
+static const wchar_t __vt100_conv[31] = {
+ 0x25C6, /* Black Diamond */
+ 0x2592, /* Medium Shade */
+ 0x2409, /* Symbol for Horizontal Tabulation */
+ 0x240C, /* Symbol for Form Feed */
+ 0x240D, /* Symbol for Carriage Return */
+ 0x240A, /* Symbol for Line Feed */
+ 0x00B0, /* Degree Sign */
+ 0x00B1, /* Plus-Minus Sign */
+ 0x2424, /* Symbol for Newline */
+ 0x240B, /* Symbol for Vertical Tabulation */
+ 0x2518, /* Box Drawings Light Up And Left */
+ 0x2510, /* Box Drawings Light Down And Left */
+ 0x250C, /* Box Drawings Light Down And Right */
+ 0x2514, /* Box Drawings Light Up And Right */
+ 0x253C, /* Box Drawings Light Vertical And Horizontal */
+ 0x23BA, /* Horizontal Scan Line-1 */
+ 0x23BB, /* Horizontal Scan Line-3 */
+ 0x2500, /* Box Drawings Light Horizontal */
+ 0x23BC, /* Horizontal Scan Line-7 */
+ 0x23BD, /* Horizontal Scan Line-9 */
+ 0x251C, /* Box Drawings Light Vertical And Right */
+ 0x2524, /* Box Drawings Light Vertical And Left */
+ 0x2534, /* Box Drawings Light Up And Horizontal */
+ 0x252C, /* Box Drawings Light Down And Horizontal */
+ 0x2502, /* Box Drawings Light Vertical */
+ 0x2264, /* Less-Than Or Equal To */
+ 0x2265, /* Greater-Than Or Equal To */
+ 0x03C0, /* Greek Small Letter Pi */
+ 0x2260, /* Not Equal To */
+ 0x00A3, /* Pound Sign */
+ 0x00B7, /* Middle Dot */
+};
+
+inline bool
+fhandler_console::write_console (PWCHAR buf, DWORD len, DWORD& done)
+{
+ if (con.iso_2022_G1
+ ? con.vt100_graphics_mode_G1
+ : con.vt100_graphics_mode_G0)
+ for (DWORD i = 0; i < len; i ++)
+ if (buf[i] >= (unsigned char) '`' && buf[i] <= (unsigned char) '~')
+ buf[i] = __vt100_conv[buf[i] - (unsigned char) '`'];
+
+ if (len > 0)
+ last_char = buf[len-1];
+
+ while (len > 0)
+ {
+ DWORD nbytes = len > MAX_WRITE_CHARS ? MAX_WRITE_CHARS : len;
+ acquire_attach_mutex (mutex_timeout);
+ BOOL r = WriteConsoleW (get_output_handle (), buf, nbytes, &done, 0);
+ release_attach_mutex ();
+ if (!r)
+ {
+ __seterrno ();
+ return false;
+ }
+ len -= done;
+ buf += done;
+ }
+ return true;
+}
+
+/* The following three functions were adapted (i.e., mildly modified) from
+ http://stackoverflow.com/questions/14699043/replacement-to-systemcolor */
+
+/* Split a rectangular region into two smaller rectangles based on the
+ largest dimension. */
+static void
+region_split (PCHAR_INFO& buf, COORD& bufsiz, SMALL_RECT& region,
+ PCHAR_INFO& buf_b, COORD& bufsiz_b, SMALL_RECT& region_b)
+{
+ region_b = region;
+ bufsiz_b = bufsiz;
+
+ SHORT half = (1 + region.Bottom - region.Top) / 2;
+ region_b.Top += half;
+ region.Bottom = (bufsiz.Y = region_b.Top) - 1;
+ buf_b = buf + (half * (1 + region.Right));
+ bufsiz_b.Y = region_b.Bottom - region_b.Top;
+}
+
+/* Utility function to figure out the distance between two points. */
+static SHORT
+delta (SHORT first, SHORT second)
+{
+ return (second >= first) ? (second - first + 1) : 0;
+}
+
+/* Subdivide the ReadConsoleInput operation into smaller and smaller chunks as
+ needed until it succeeds in reading the entire screen buffer. */
+static BOOL
+ReadConsoleOutputWrapper (HANDLE h, PCHAR_INFO buf, COORD bufsiz,
+ SMALL_RECT region)
+{
+ COORD coord = {};
+ SHORT width = delta (region.Left, region.Right);
+ SHORT height = delta (region.Top, region.Bottom);
+
+ if ((width == 0) || (height == 0))
+ return TRUE;
+
+ acquire_attach_mutex (mutex_timeout);
+ BOOL success = ReadConsoleOutputW (h, buf, bufsiz, coord, &region);
+ release_attach_mutex ();
+ if (success)
+ /* it worked */;
+ else if (GetLastError () == ERROR_NOT_ENOUGH_MEMORY && (width * height) > 1)
+ {
+ PCHAR_INFO buf_b;
+ COORD bufsiz_b;
+ SMALL_RECT region_b;
+ region_split (buf, bufsiz, region, buf_b, bufsiz_b, region_b);
+ success = ReadConsoleOutputWrapper (h, buf, bufsiz, region)
+ && ReadConsoleOutputWrapper (h, buf_b, bufsiz_b, region_b);
+ }
+ return success;
+}
+
+void
+dev_console::save_restore (HANDLE h, char c)
+{
+ if (c == 'h') /* save */
+ {
+ fillin (h);
+ save_bufsize.X = b.dwSize.X;
+ if ((save_bufsize.Y = dwEnd.Y + 1) > b.dwSize.Y)
+ save_bufsize.X = b.dwSize.Y;
+
+ if (save_buf)
+ cfree (save_buf);
+ size_t screen_size = sizeof (CHAR_INFO) * save_bufsize.X * save_bufsize.Y;
+ save_buf = (PCHAR_INFO) cmalloc_abort (HEAP_1_BUF, screen_size);
+
+ save_cursor = b.dwCursorPosition; /* Remember where we were. */
+ save_top = b.srWindow.Top;
+
+ SMALL_RECT now = {}; /* Read the whole buffer */
+ now.Bottom = save_bufsize.Y - 1;
+ now.Right = save_bufsize.X - 1;
+ if (!ReadConsoleOutputWrapper (h, save_buf, save_bufsize, now))
+ debug_printf ("ReadConsoleOutputWrapper(h, ...) failed during save, %E");
+
+ /* Position at top of buffer */
+ COORD cob = {};
+ acquire_attach_mutex (mutex_timeout);
+ if (!SetConsoleCursorPosition (h, cob))
+ debug_printf ("SetConsoleCursorInfo(%p, ...) failed during save, %E", h);
+ release_attach_mutex ();
+
+ /* Clear entire buffer */
+ clear_screen (h, 0, 0, now.Right, now.Bottom);
+ b.dwCursorPosition.X = b.dwCursorPosition.Y = dwEnd.X = dwEnd.Y = 0;
+ }
+ else if (save_buf)
+ {
+ COORD cob = {};
+ SMALL_RECT now = {};
+ now.Bottom = save_bufsize.Y - 1;
+ now.Right = save_bufsize.X - 1;
+ /* Restore whole buffer */
+ clear_screen (h, 0, 0, b.dwSize.X - 1, b.dwSize.Y - 1);
+ acquire_attach_mutex (mutex_timeout);
+ BOOL res = WriteConsoleOutputW (h, save_buf, save_bufsize, cob, &now);
+ release_attach_mutex ();
+ if (!res)
+ debug_printf ("WriteConsoleOutputW failed, %E");
+
+ cfree (save_buf);
+ save_buf = NULL;
+
+ cob.X = 0;
+ cob.Y = save_top;
+ /* CGF: NOOP? Doesn't seem to position screen as expected */
+ /* Temporarily position at top of screen */
+ acquire_attach_mutex (mutex_timeout);
+ if (!SetConsoleCursorPosition (h, cob))
+ debug_printf ("SetConsoleCursorInfo(%p, cob) failed during restore, %E", h);
+ /* Position where we were previously */
+ if (!SetConsoleCursorPosition (h, save_cursor))
+ debug_printf ("SetConsoleCursorInfo(%p, save_cursor) failed during restore, %E", h);
+ release_attach_mutex ();
+ /* Get back correct version of buffer information */
+ dwEnd.X = dwEnd.Y = 0;
+ fillin (h);
+ }
+}
+
+#define BAK 1
+#define ESC 2
+#define NOR 0
+#define IGN 4
+#if 1
+#define ERR 5
+#else
+#define ERR NOR
+#endif
+#define DWN 6
+#define BEL 7
+#define TAB 8 /* We should't let the console deal with these */
+#define CR 13
+#define LF 10
+#define SO 14
+#define SI 15
+
+static const char base_chars[256] =
+{
+/*00 01 02 03 04 05 06 07 */ IGN, ERR, ERR, NOR, NOR, NOR, NOR, BEL,
+/*08 09 0A 0B 0C 0D 0E 0F */ BAK, TAB, DWN, ERR, ERR, CR, SO, SI,
+/*10 11 12 13 14 15 16 17 */ NOR, NOR, ERR, ERR, ERR, ERR, ERR, ERR,
+/*18 19 1A 1B 1C 1D 1E 1F */ NOR, NOR, ERR, ESC, ERR, ERR, ERR, ERR,
+/* ! " # $ % & ' */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*( ) * + , - . / */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*0 1 2 3 4 5 6 7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*8 9 : ; < = > ? */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*@ A B C D E F G */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*H I J K L M N O */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*P Q R S T U V W */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*X Y Z [ \ ] ^ _ */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*` a b c d e f g */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*h i j k l m n o */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*p q r s t u v w */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*x y z { | } ~ 7F */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*80 81 82 83 84 85 86 87 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*88 89 8A 8B 8C 8D 8E 8F */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*90 91 92 93 94 95 96 97 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*98 99 9A 9B 9C 9D 9E 9F */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*A0 A1 A2 A3 A4 A5 A6 A7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*A8 A9 AA AB AC AD AE AF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*B0 B1 B2 B3 B4 B5 B6 B7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*B8 B9 BA BB BC BD BE BF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*C0 C1 C2 C3 C4 C5 C6 C7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*C8 C9 CA CB CC CD CE CF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*D0 D1 D2 D3 D4 D5 D6 D7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*D8 D9 DA DB DC DD DE DF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*E0 E1 E2 E3 E4 E5 E6 E7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*E8 E9 EA EB EC ED EE EF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*F0 F1 F2 F3 F4 F5 F6 F7 */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR,
+/*F8 F9 FA FB FC FD FE FF */ NOR, NOR, NOR, NOR, NOR, NOR, NOR, NOR };
+
+static const char table256[256] =
+{
+ 0, 4, 2, 6, 1, 5, 3, 7, 8,12,10,14, 9,13,11,15,
+ 0, 1, 1, 1, 9, 9, 2, 3, 3, 3, 3, 9, 2, 3, 3, 3,
+ 3,11, 2, 3, 3, 3,11,11,10, 3, 3,11,11,11,10,10,
+ 11,11,11,11, 4, 5, 5, 5, 5, 9, 6, 8, 8, 8, 8, 9,
+ 6, 8, 8, 8, 8, 7, 6, 8, 8, 8, 7, 7, 6, 8, 8, 7,
+ 7,11,10,10, 7, 7,11,11, 4, 5, 5, 5, 5,13, 6, 8,
+ 8, 8, 8, 7, 6, 8, 8, 8, 7, 7, 6, 8, 8, 7, 7, 7,
+ 6, 8, 7, 7, 7, 7,14, 7, 7, 7, 7, 7, 4, 5, 5, 5,
+ 13,13, 6, 8, 8, 8, 7, 7, 6, 8, 8, 7, 7, 7, 6, 8,
+ 7, 7, 7, 7,14, 7, 7, 7, 7, 7,14, 7, 7, 7, 7,15,
+ 12, 5, 5,13,13,13, 6, 8, 8, 7, 7,13, 6, 8, 7, 7,
+ 7, 7,14, 7, 7, 7, 7, 7,14, 7, 7, 7, 7,15,14,14,
+ 7, 7,15,15,12,12,13,13,13,13,12,12, 7, 7,13,13,
+ 14, 7, 7, 7, 7, 7,14, 7, 7, 7, 7,15,14,14, 7, 7,
+ 15,15,14,14, 7,15,15,15, 0, 0, 0, 0, 0, 0, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7,15,15
+};
+
+void
+fhandler_console::char_command (char c)
+{
+ int x, y, n;
+ char buf[40];
+ wchar_t bufw[40];
+ int r, g, b;
+
+ if (wincap.has_con_24bit_colors () && !con_is_legacy)
+ {
+ /* For xterm compatible mode */
+ switch (c)
+ {
+#if 0 /* These sequences, which are supported by real xterm, are
+ not supported by xterm compatible mode. Therefore they
+ were implemented once. However, these are not declared
+ in terminfo of xterm-256color, therefore, do not appear
+ to be necessary. */
+ case '`': /* HPA */
+ if (con.args[0] == 0)
+ con.args[0] = 1;
+ cursor_get (&x, &y);
+ cursor_set (false, con.args[0]-1, y);
+ break;
+ case 'a': /* HPR */
+ if (con.args[0] == 0)
+ con.args[0] = 1;
+ cursor_rel (con.args[0], 0);
+ break;
+ case 'e': /* VPR */
+ if (con.args[0] == 0)
+ con.args[0] = 1;
+ cursor_rel (0, con.args[0]);
+ break;
+#endif
+ case 'b': /* REP */
+ wpbuf.put (c);
+ if (wincap.has_con_esc_rep ())
+ /* Just send the sequence */
+ wpbuf.send ();
+ else if (last_char && last_char != L'\n')
+ {
+ acquire_attach_mutex (mutex_timeout);
+ for (int i = 0; i < con.args[0]; i++)
+ WriteConsoleW (get_output_handle (), &last_char, 1, 0, 0);
+ release_attach_mutex ();
+ }
+ break;
+ case 'r': /* DECSTBM */
+ con.scroll_region.Top = con.args[0] ? con.args[0] - 1 : 0;
+ con.scroll_region.Bottom = con.args[1] ? con.args[1] - 1 : -1;
+ wpbuf.put (c);
+ /* Just send the sequence */
+ wpbuf.send ();
+ break;
+ case 'L': /* IL */
+ if (wincap.has_con_broken_il_dl ())
+ {
+ /* Use "CSI Ps T" instead */
+ cursor_get (&x, &y);
+ if (y < srTop || y > srBottom)
+ break;
+ if (y == con.b.srWindow.Bottom)
+ {
+ acquire_attach_mutex (mutex_timeout);
+ WriteConsoleW (get_output_handle (), L"\033[2K", 4, 0, 0);
+ release_attach_mutex ();
+ break;
+ }
+ acquire_attach_mutex (mutex_timeout);
+ if (y == con.b.srWindow.Top
+ && srBottom == con.b.srWindow.Bottom)
+ {
+ /* Erase scroll down area */
+ n = con.args[0] ? : 1;
+ __small_swprintf (bufw, L"\033[%d;1H\033[J\033[%d;%dH",
+ srBottom - (n-1) - con.b.srWindow.Top + 1,
+ y + 1 - con.b.srWindow.Top, x + 1);
+ WriteConsoleW (get_output_handle (),
+ bufw, wcslen (bufw), 0, 0);
+ }
+ __small_swprintf (bufw, L"\033[%d;%dr",
+ y + 1 - con.b.srWindow.Top,
+ srBottom + 1 - con.b.srWindow.Top);
+ WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
+ wpbuf.put ('T');
+ wpbuf.send ();
+ __small_swprintf (bufw, L"\033[%d;%dr",
+ srTop + 1 - con.b.srWindow.Top,
+ srBottom + 1 - con.b.srWindow.Top);
+ WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
+ __small_swprintf (bufw, L"\033[%d;%dH",
+ y + 1 - con.b.srWindow.Top, x + 1);
+ WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
+ release_attach_mutex ();
+ }
+ else
+ {
+ wpbuf.put (c);
+ /* Just send the sequence */
+ wpbuf.send ();
+ }
+ break;
+ case 'M': /* DL */
+ if (wincap.has_con_broken_il_dl ())
+ {
+ /* Use "CSI Ps S" instead */
+ cursor_get (&x, &y);
+ if (y < srTop || y > srBottom)
+ break;
+ if (y == con.b.srWindow.Bottom)
+ {
+ acquire_attach_mutex (mutex_timeout);
+ WriteConsoleW (get_output_handle (), L"\033[2K", 4, 0, 0);
+ release_attach_mutex ();
+ break;
+ }
+ __small_swprintf (bufw, L"\033[%d;%dr",
+ y + 1 - con.b.srWindow.Top,
+ srBottom + 1 - con.b.srWindow.Top);
+ acquire_attach_mutex (mutex_timeout);
+ WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
+ wpbuf.put ('S');
+ wpbuf.send ();
+ __small_swprintf (bufw, L"\033[%d;%dr",
+ srTop + 1 - con.b.srWindow.Top,
+ srBottom + 1 - con.b.srWindow.Top);
+ WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
+ __small_swprintf (bufw, L"\033[%d;%dH",
+ y + 1 - con.b.srWindow.Top, x + 1);
+ WriteConsoleW (get_output_handle (), bufw, wcslen (bufw), 0, 0);
+ release_attach_mutex ();
+ }
+ else
+ {
+ wpbuf.put (c);
+ /* Just send the sequence */
+ wpbuf.send ();
+ }
+ break;
+ case 'J': /* ED */
+ wpbuf.put (c);
+ if (con.args[0] == 3 && con.savey >= 0)
+ {
+ con.fillin (get_output_handle ());
+ con.savey -= con.b.srWindow.Top;
+ }
+ if (con.args[0] == 3 && wincap.has_con_broken_csi3j ())
+ { /* Workaround for broken CSI3J in Win10 1809 */
+ CONSOLE_SCREEN_BUFFER_INFO sbi;
+ acquire_attach_mutex (mutex_timeout);
+ GetConsoleScreenBufferInfo (get_output_handle (), &sbi);
+ SMALL_RECT r = {0, sbi.srWindow.Top,
+ (SHORT) (sbi.dwSize.X - 1), (SHORT) (sbi.dwSize.Y - 1)};
+ CHAR_INFO f = {' ', sbi.wAttributes};
+ COORD d = {0, 0};
+ ScrollConsoleScreenBufferA (get_output_handle (),
+ &r, NULL, d, &f);
+ SetConsoleCursorPosition (get_output_handle (), d);
+ d = sbi.dwCursorPosition;
+ d.Y -= sbi.srWindow.Top;
+ SetConsoleCursorPosition (get_output_handle (), d);
+ release_attach_mutex ();
+ }
+ else
+ /* Just send the sequence */
+ wpbuf.send ();
+ break;
+ case 'h': /* DECSET */
+ case 'l': /* DECRST */
+ wpbuf.put (c);
+ /* Just send the sequence */
+ wpbuf.send ();
+ if (con.saw_question_mark)
+ {
+ bool need_fix_tab_position = false;
+ for (int i = 0; i < con.nargs; i++)
+ {
+ if (con.args[i] == 1049)
+ {
+ con.screen_alternated = (c == 'h');
+ need_fix_tab_position = wincap.has_con_broken_tabs ();
+ }
+ if (con.args[i] == 1) /* DECCKM */
+ con.cursor_key_app_mode = (c == 'h');
+ }
+ /* Call fix_tab_position() if screen has been alternated. */
+ if (need_fix_tab_position)
+ fix_tab_position (get_output_handle ());
+ }
+ break;
+ case 'p':
+ if (con.saw_exclamation_mark) /* DECSTR Soft reset */
+ {
+ con.scroll_region.Top = 0;
+ con.scroll_region.Bottom = -1;
+ con.savex = con.savey = -1;
+ con.cursor_key_app_mode = false;
+ }
+ wpbuf.put (c);
+ /* Just send the sequence */
+ wpbuf.send ();
+ break;
+ case 'm':
+ if (con.saw_greater_than_sign)
+ break; /* Ignore unsupported CSI > Pm m */
+ /* Text attribute settings */
+ wpbuf.put (c);
+ /* Just send the sequence */
+ wpbuf.send ();
+ break;
+ default:
+ /* Other escape sequences */
+ wpbuf.put (c);
+ /* Just send the sequence */
+ wpbuf.send ();
+ break;
+ }
+ return;
+ }
+
+ /* For legacy cygwin treminal */
+ switch (c)
+ {
+ case 'm': /* Set Graphics Rendition */
+ for (int i = 0; i < con.nargs; i++)
+ switch (con.args[i])
+ {
+ case 0: /* normal color */
+ con.set_default_attr ();
+ break;
+ case 1: /* bold */
+ con.intensity = INTENSITY_BOLD;
+ break;
+ case 2: /* dim */
+ con.intensity = INTENSITY_DIM;
+ break;
+ case 4: /* underlined */
+ con.underline = 1;
+ break;
+ case 5: /* blink mode */
+ con.blink = true;
+ break;
+ case 7: /* reverse */
+ con.reverse = true;
+ break;
+ case 8: /* invisible */
+ con.intensity = INTENSITY_INVISIBLE;
+ break;
+ case 10: /* end alternate charset */
+ con.alternate_charset_active = false;
+ break;
+ case 11: /* start alternate charset */
+ con.alternate_charset_active = true;
+ break;
+ case 22:
+ case 28:
+ con.intensity = INTENSITY_NORMAL;
+ break;
+ case 24:
+ con.underline = false;
+ break;
+ case 25:
+ con.blink = false;
+ break;
+ case 27:
+ con.reverse = false;
+ break;
+ case 30: /* BLACK foreground */
+ con.fg = 0;
+ break;
+ case 31: /* RED foreground */
+ con.fg = FOREGROUND_RED;
+ break;
+ case 32: /* GREEN foreground */
+ con.fg = FOREGROUND_GREEN;
+ break;
+ case 33: /* YELLOW foreground */
+ con.fg = FOREGROUND_RED | FOREGROUND_GREEN;
+ break;
+ case 34: /* BLUE foreground */
+ con.fg = FOREGROUND_BLUE;
+ break;
+ case 35: /* MAGENTA foreground */
+ con.fg = FOREGROUND_RED | FOREGROUND_BLUE;
+ break;
+ case 36: /* CYAN foreground */
+ con.fg = FOREGROUND_BLUE | FOREGROUND_GREEN;
+ break;
+ case 37: /* WHITE foreg */
+ con.fg = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
+ break;
+ case 38:
+ if (con.nargs < i + 2)
+ /* Sequence error (abort) */
+ break;
+ switch (con.args[i + 1])
+ {
+ case 2:
+ if (con.nargs < i + 5)
+ /* Sequence error (abort) */
+ break;
+ r = con.args[i + 2];
+ g = con.args[i + 3];
+ b = con.args[i + 4];
+ r = r < (95 + 1) / 2 ? 0 : r > 255 ? 5 : (r - 55 + 20) / 40;
+ g = g < (95 + 1) / 2 ? 0 : g > 255 ? 5 : (g - 55 + 20) / 40;
+ b = b < (95 + 1) / 2 ? 0 : b > 255 ? 5 : (b - 55 + 20) / 40;
+ con.fg = table256[16 + r*36 + g*6 + b];
+ i += 4;
+ break;
+ case 5:
+ if (con.nargs < i + 3)
+ /* Sequence error (abort) */
+ break;
+ {
+ int idx = con.args[i + 2];
+ if (idx < 0)
+ idx = 0;
+ if (idx > 255)
+ idx = 255;
+ con.fg = table256[idx];
+ i += 2;
+ }
+ break;
+ }
+ break;
+ case 39:
+ con.fg = con.default_color & FOREGROUND_ATTR_MASK;
+ break;
+ case 40: /* BLACK background */
+ con.bg = 0;
+ break;
+ case 41: /* RED background */
+ con.bg = BACKGROUND_RED;
+ break;
+ case 42: /* GREEN background */
+ con.bg = BACKGROUND_GREEN;
+ break;
+ case 43: /* YELLOW background */
+ con.bg = BACKGROUND_RED | BACKGROUND_GREEN;
+ break;
+ case 44: /* BLUE background */
+ con.bg = BACKGROUND_BLUE;
+ break;
+ case 45: /* MAGENTA background */
+ con.bg = BACKGROUND_RED | BACKGROUND_BLUE;
+ break;
+ case 46: /* CYAN background */
+ con.bg = BACKGROUND_BLUE | BACKGROUND_GREEN;
+ break;
+ case 47: /* WHITE background */
+ con.bg = BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED;
+ break;
+ case 48:
+ if (con.nargs < i + 2)
+ /* Sequence error (abort) */
+ break;
+ switch (con.args[i + 1])
+ {
+ case 2:
+ if (con.nargs < i + 5)
+ /* Sequence error (abort) */
+ break;
+ r = con.args[i + 2];
+ g = con.args[i + 3];
+ b = con.args[i + 4];
+ r = r < (95 + 1) / 2 ? 0 : r > 255 ? 5 : (r - 55 + 20) / 40;
+ g = g < (95 + 1) / 2 ? 0 : g > 255 ? 5 : (g - 55 + 20) / 40;
+ b = b < (95 + 1) / 2 ? 0 : b > 255 ? 5 : (b - 55 + 20) / 40;
+ con.bg = table256[16 + r*36 + g*6 + b] << 4;
+ i += 4;
+ break;
+ case 5:
+ if (con.nargs < i + 3)
+ /* Sequence error (abort) */
+ break;
+ {
+ int idx = con.args[i + 2];
+ if (idx < 0)
+ idx = 0;
+ if (idx > 255)
+ idx = 255;
+ con.bg = table256[idx] << 4;
+ i += 2;
+ }
+ break;
+ }
+ break;
+ case 49:
+ con.bg = con.default_color & BACKGROUND_ATTR_MASK;
+ break;
+ }
+ con.set_color (get_output_handle ());
+ break;
+ case 'q': /* Set cursor style (DECSCUSR) */
+ if (con.saw_space)
+ {
+ CONSOLE_CURSOR_INFO console_cursor_info;
+ acquire_attach_mutex (mutex_timeout);
+ GetConsoleCursorInfo (get_output_handle (), &console_cursor_info);
+ switch (con.args[0])
+ {
+ case 0: /* blinking block */
+ case 1: /* blinking block (default) */
+ case 2: /* steady block */
+ console_cursor_info.dwSize = 100;
+ SetConsoleCursorInfo (get_output_handle (),
+ &console_cursor_info);
+ break;
+ case 3: /* blinking underline */
+ case 4: /* steady underline */
+ console_cursor_info.dwSize = 10; /* or Windows default 25? */
+ SetConsoleCursorInfo (get_output_handle (),
+ &console_cursor_info);
+ break;
+ default: /* use value as percentage */
+ console_cursor_info.dwSize = con.args[0];
+ SetConsoleCursorInfo (get_output_handle (),
+ &console_cursor_info);
+ break;
+ }
+ release_attach_mutex ();
+ }
+ break;
+ case 'h':
+ case 'l':
+ if (!con.saw_question_mark)
+ {
+ switch (con.args[0])
+ {
+ case 4: /* Insert mode */
+ con.insert_mode = (c == 'h') ? true : false;
+ syscall_printf ("insert mode %sabled",
+ con.insert_mode ? "en" : "dis");
+ break;
+ }
+ break;
+ }
+ switch (con.args[0])
+ {
+ case 25: /* Show/Hide Cursor (DECTCEM) */
+ {
+ CONSOLE_CURSOR_INFO console_cursor_info;
+ acquire_attach_mutex (mutex_timeout);
+ GetConsoleCursorInfo (get_output_handle (), & console_cursor_info);
+ if (c == 'h')
+ console_cursor_info.bVisible = TRUE;
+ else
+ console_cursor_info.bVisible = FALSE;
+ SetConsoleCursorInfo (get_output_handle (), & console_cursor_info);
+ release_attach_mutex ();
+ break;
+ }
+ case 47: /* Save/Restore screen */
+ con.save_restore (get_output_handle (), c);
+ break;
+
+ case 67: /* DECBKM ("DEC Backarrow Key Mode") */
+ con.backspace_keycode = (c == 'h' ? CTRL('H') : CERASE);
+ break;
+
+ case 1000: /* Mouse tracking */
+ con.use_mouse = (c == 'h') ? 1 : 0;
+ break;
+
+ case 1002: /* Mouse button event tracking */
+ con.use_mouse = (c == 'h') ? 2 : 0;
+ break;
+
+ case 1003: /* Mouse any event tracking */
+ con.use_mouse = (c == 'h') ? 3 : 0;
+ break;
+
+ case 1004: /* Focus in/out event reporting */
+ con.use_focus = (c == 'h') ? true : false;
+ break;
+
+ case 1005: /* Extended mouse mode */
+ con.ext_mouse_mode5 = c == 'h';
+ break;
+
+ case 1006: /* SGR extended mouse mode */
+ con.ext_mouse_mode6 = c == 'h';
+ break;
+
+ case 1015: /* Urxvt extended mouse mode */
+ con.ext_mouse_mode15 = c == 'h';
+ break;
+
+ case 2000: /* Raw keyboard mode */
+ set_raw_win32_keyboard_mode ((c == 'h') ? true : false);
+ break;
+
+ default: /* Ignore */
+ syscall_printf ("unknown h/l command: %d", con.args[0]);
+ break;
+ }
+ break;
+ case 'J':
+ switch (con.args[0])
+ {
+ case 0: /* Clear to end of screen */
+ clear_screen (cl_curr_pos, cl_curr_pos, cl_disp_end, cl_disp_end);
+ break;
+ case 1: /* Clear from beginning of screen to cursor */
+ clear_screen (cl_disp_beg, cl_disp_beg, cl_curr_pos, cl_curr_pos);
+ break;
+ case 2: /* Clear screen */
+ cursor_get (&x, &y);
+ clear_screen (cl_disp_beg, cl_disp_beg, cl_disp_end, cl_disp_end);
+ cursor_set (false, x, y);
+ break;
+ default:
+ goto bad_escape;
+ }
+ break;
+
+ case 'A':
+ cursor_rel (0, -(con.args[0] ?: 1));
+ break;
+ case 'B':
+ cursor_rel (0, con.args[0] ?: 1);
+ break;
+ case 'C':
+ cursor_rel (con.args[0] ?: 1, 0);
+ break;
+ case 'D':
+ cursor_rel (-(con.args[0] ?: 1),0);
+ break;
+ case 'K':
+ switch (con.args[0])
+ {
+ case 0: /* Clear to end of line */
+ clear_screen (cl_curr_pos, cl_curr_pos, cl_disp_end, cl_curr_pos);
+ break;
+ case 2: /* Clear line */
+ clear_screen (cl_disp_beg, cl_curr_pos, cl_disp_end, cl_curr_pos);
+ break;
+ case 1: /* Clear from bol to cursor */
+ clear_screen (cl_disp_beg, cl_curr_pos, cl_curr_pos, cl_curr_pos);
+ break;
+ default:
+ goto bad_escape;
+ }
+ break;
+ case 'H':
+ case 'f':
+ cursor_set (true, (con.args[1] ?: 1) - 1,
+ (con.args[0] ?: 1) - 1);
+ break;
+ case 'G': /* hpa - position cursor at column n - 1 */
+ cursor_get (&x, &y);
+ cursor_set (false, (con.args[0] ? con.args[0] - 1 : 0), y);
+ break;
+ case 'd': /* vpa - position cursor at line n */
+ cursor_get (&x, &y);
+ cursor_set (true, x, (con.args[0] ? con.args[0] - 1 : 0));
+ break;
+ case 's': /* Save cursor position */
+ cursor_get (&con.savex, &con.savey);
+ con.savey -= con.b.srWindow.Top;
+ break;
+ case 'u': /* Restore cursor position */
+ cursor_set (true, con.savex, con.savey);
+ break;
+ case 'I': /* TAB */
+ cursor_get (&x, &y);
+ cursor_set (false, 8 * (x / 8 + 1), y);
+ break;
+ case 'L': /* AL - insert blank lines */
+ n = con.args[0] ?: 1;
+ cursor_get (&x, &y);
+ scroll_buffer (0, y, -1, -1, 0, y + n);
+ break;
+ case 'M': /* DL - delete lines */
+ n = con.args[0] ?: 1;
+ cursor_get (&x, &y);
+ scroll_buffer (0, y + n, -1, -1, 0, y);
+ break;
+ case '@': /* IC - insert chars */
+ n = con.args[0] ?: 1;
+ cursor_get (&x, &y);
+ scroll_buffer (x, y, -1, y, x + n, y);
+ break;
+ case 'P': /* DC - delete chars */
+ n = con.args[0] ?: 1;
+ cursor_get (&x, &y);
+ scroll_buffer (x + n, y, -1, y, x, y);
+ break;
+ case 'S': /* SF - Scroll forward */
+ n = con.args[0] ?: 1;
+ scroll_buffer_screen (0, n, -1, -1, 0, 0);
+ break;
+ case 'T': /* SR - Scroll down */
+ con.fillin (get_output_handle ());
+ n = con.b.srWindow.Top + con.args[0] ?: 1;
+ scroll_buffer_screen (0, 0, -1, -1, 0, n);
+ break;
+ case 'X': /* ec - erase chars */
+ n = con.args[0] ?: 1;
+ cursor_get (&x, &y);
+ scroll_buffer (x + n, y, -1, y, x, y);
+ scroll_buffer (x, y, -1, y, x + n, y);
+ break;
+ case 'Z': /* Back tab */
+ cursor_get (&x, &y);
+ cursor_set (false, ((8 * (x / 8 + 1)) - 8), y);
+ break;
+ case 'b': /* Repeat char #1 #2 times */
+ if (con.insert_mode)
+ {
+ cursor_get (&x, &y);
+ scroll_buffer (x, y, -1, y, x + con.args[1], y);
+ }
+ while (con.args[1]--)
+ WriteFile (get_output_handle (), &con.args[0], 1, (DWORD *) &x, 0);
+ break;
+ case 'c': /* u9 - Terminal enquire string */
+ if (con.saw_greater_than_sign)
+ /* Generate Secondary Device Attribute report, using 67 = ASCII 'C'
+ to indicate Cygwin (convention used by Rxvt, Urxvt, Screen, Mintty),
+ and cygwin version for terminal version. */
+ __small_sprintf (buf, "\033[>67;%d%02d;0c",
+ CYGWIN_VERSION_DLL_MAJOR, CYGWIN_VERSION_DLL_MINOR);
+ else
+ strcpy (buf, "\033[?6c");
+ /* The generated report needs to be injected for read-ahead into the
+ fhandler_console object associated with standard input.
+ So puts_readahead does not work.
+ Use a common console read-ahead buffer instead. */
+ acquire_input_mutex (mutex_timeout);
+ con.cons_rapoi = NULL;
+ strcpy (con.cons_rabuf, buf);
+ con.cons_rapoi = con.cons_rabuf;
+ release_input_mutex ();
+ /* Wake up read() or select() by sending a message
+ which has no effect */
+ PostMessageW (GetConsoleWindow (), WM_SETFOCUS, 0, 0);
+ break;
+ case 'n':
+ switch (con.args[0])
+ {
+ case 6: /* u7 - Cursor position request */
+ cursor_get (&x, &y);
+ y -= con.b.srWindow.Top;
+ /* x -= con.b.srWindow.Left; // not available yet */
+ __small_sprintf (buf, "\033[%d;%dR", y + 1, x + 1);
+ acquire_input_mutex (mutex_timeout);
+ con.cons_rapoi = NULL;
+ strcpy (con.cons_rabuf, buf);
+ con.cons_rapoi = con.cons_rabuf;
+ release_input_mutex ();
+ /* Wake up read() or select() by sending a message
+ which has no effect */
+ PostMessageW (GetConsoleWindow (), WM_SETFOCUS, 0, 0);
+ break;
+ default:
+ goto bad_escape;
+ }
+ break;
+ case 'r': /* Set Scroll region */
+ con.scroll_region.Top = con.args[0] ? con.args[0] - 1 : 0;
+ con.scroll_region.Bottom = con.args[1] ? con.args[1] - 1 : -1;
+ cursor_set (true, 0, 0);
+ break;
+ case 'g': /* TAB set/clear */
+ break;
+ default:
+bad_escape:
+ break;
+ }
+}
+
+#define NUM_REPLACEMENT_CHARS 3
+
+static const wchar_t replacement_char[NUM_REPLACEMENT_CHARS] =
+{
+ 0xfffd, /* REPLACEMENT CHARACTER */
+ 0x25a1, /* WHITE SQUARE */
+ 0x2592 /* MEDIUM SHADE */
+};
+/* nFont member is always 0 so we have to use the facename. */
+static WCHAR cons_facename[LF_FACESIZE];
+static WCHAR rp_char;
+static NO_COPY HDC cdc;
+
+static int CALLBACK
+enum_proc (const LOGFONTW *lf, const TEXTMETRICW *tm,
+ DWORD FontType, LPARAM lParam)
+{
+ int *done = (int *) lParam;
+ *done = 1;
+ return 0;
+}
+
+static void
+check_font (HANDLE hdl)
+{
+ CONSOLE_FONT_INFOEX cfi;
+ LOGFONTW lf;
+
+ cfi.cbSize = sizeof cfi;
+ acquire_attach_mutex (mutex_timeout);
+ BOOL r = GetCurrentConsoleFontEx (hdl, 0, &cfi);
+ release_attach_mutex ();
+ if (!r)
+ return;
+ /* Switched font? */
+ if (wcscmp (cons_facename, cfi.FaceName) == 0)
+ return;
+ if (!cdc && !(cdc = GetDC (GetConsoleWindow ())))
+ return;
+ /* Some FaceNames like DejaVu Sans Mono are sometimes returned with stray
+ trailing chars. Fix it. */
+ lf.lfCharSet = DEFAULT_CHARSET;
+ lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
+ wchar_t *cp = wcpcpy (lf.lfFaceName, cfi.FaceName) - 1;
+ int done = 0;
+ do
+ {
+ EnumFontFamiliesExW (cdc, &lf, enum_proc, (LPARAM) &done, 0);
+ if (!done)
+ *cp-- = L'\0';
+ }
+ while (!done && cp >= lf.lfFaceName);
+ /* What, really? No recognizable font? */
+ if (!done)
+ {
+ rp_char = L'?';
+ return;
+ }
+ /* Yes. Check for the best replacement char. */
+ HFONT f = CreateFontW (0, 0, 0, 0,
+ cfi.FontWeight, FALSE, FALSE, FALSE,
+ DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
+ CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
+ FIXED_PITCH | FF_DONTCARE, lf.lfFaceName);
+ if (!f)
+ return;
+
+ HFONT old_f = (HFONT) SelectObject(cdc, f);
+ if (old_f)
+ {
+ WORD glyph_idx[NUM_REPLACEMENT_CHARS];
+
+ if (GetGlyphIndicesW (cdc, replacement_char,
+ NUM_REPLACEMENT_CHARS, glyph_idx,
+ GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR)
+ {
+ int i;
+
+ for (i = 0; i < NUM_REPLACEMENT_CHARS; ++i)
+ if (glyph_idx[i] != 0xffff)
+ break;
+ if (i == NUM_REPLACEMENT_CHARS)
+ i = 0;
+ rp_char = replacement_char[i];
+ /* Note that we copy the original name returned by
+ GetCurrentConsoleFontEx, even if it was broken.
+ This allows an early return, rather than to store
+ the fixed name and then having to enum font families
+ all over again. */
+ wcscpy (cons_facename, cfi.FaceName);
+ }
+ SelectObject (cdc, old_f);
+ }
+ DeleteObject (f);
+}
+
+/* This gets called when we found an invalid input character.
+ Print one of the above Unicode chars as replacement char. */
+inline void
+fhandler_console::write_replacement_char ()
+{
+ check_font (get_output_handle ());
+
+ DWORD done;
+ acquire_attach_mutex (mutex_timeout);
+ WriteConsoleW (get_output_handle (), &rp_char, 1, &done, 0);
+ release_attach_mutex ();
+}
+
+const unsigned char *
+fhandler_console::write_normal (const unsigned char *src,
+ const unsigned char *end)
+{
+ /* Scan forward to see what a char which needs special treatment */
+ DWORD done;
+ DWORD buf_len;
+ const unsigned char *found = src;
+ int ret;
+ mbstate_t ps;
+ mbtowc_p f_mbtowc;
+
+ /* The alternate charset is always 437, just as in the Linux console. */
+ f_mbtowc = con.get_console_cp () ? __cp_mbtowc (437) : __MBTOWC;
+ if (f_mbtowc == __ascii_mbtowc)
+ f_mbtowc = __utf8_mbtowc;
+
+ /* First check if we have cached lead bytes of a former try to write
+ a truncated multibyte sequence. If so, process it. */
+ if (trunc_buf.len)
+ {
+ const unsigned char *nfound;
+ int cp_len = MIN (end - src, 4 - trunc_buf.len);
+ memcpy (trunc_buf.buf + trunc_buf.len, src, cp_len);
+ memset (&ps, 0, sizeof ps);
+ switch (ret = f_mbtowc (_REENT, NULL, (const char *) trunc_buf.buf,
+ trunc_buf.len + cp_len, &ps))
+ {
+ case -2:
+ /* Still truncated multibyte sequence? Keep in trunc_buf. */
+ trunc_buf.len += cp_len;
+ return end;
+ case -1:
+ /* Give up, print replacement chars for trunc_buf... */
+ for (int i = 0; i < trunc_buf.len; ++i)
+ write_replacement_char ();
+ /* ... mark trunc_buf as unused... */
+ trunc_buf.len = 0;
+ /* ... and proceed. */
+ nfound = NULL;
+ break;
+ case 0:
+ nfound = trunc_buf.buf + 1;
+ break;
+ default:
+ nfound = trunc_buf.buf + ret;
+ break;
+ }
+ /* Valid multibyte sequence? Process. */
+ if (nfound)
+ {
+ buf_len = con.str_to_con (f_mbtowc, write_buf,
+ (const char *) trunc_buf.buf,
+ nfound - trunc_buf.buf);
+ if (!write_console (write_buf, buf_len, done))
+ {
+ debug_printf ("multibyte sequence write failed, handle %p",
+ get_output_handle ());
+ return 0;
+ }
+ found = src + (nfound - trunc_buf.buf - trunc_buf.len);
+ trunc_buf.len = 0;
+ return found;
+ }
+ }
+
+ /* Loop over src buffer as long as we have just simple characters. Stop
+ as soon as we reach the conversion limit, or if we encounter a control
+ character or a truncated or invalid mutibyte sequence. */
+ /* If system has 24 bit color capability, just write all control
+ sequences to console since xterm compatible mode is enabled. */
+ memset (&ps, 0, sizeof ps);
+ while (found < end
+ && found - src < CONVERT_LIMIT
+ && base_chars[*found] != IGN
+ && base_chars[*found] != ESC
+ && ((wincap.has_con_24bit_colors () && !con_is_legacy)
+ || base_chars[*found] == NOR))
+ {
+ switch (ret = f_mbtowc (_REENT, NULL, (const char *) found,
+ end - found, &ps))
+ {
+ case -2: /* Truncated multibyte sequence. Store for next write. */
+ trunc_buf.len = end - found;
+ memcpy (trunc_buf.buf, found, trunc_buf.len);
+ goto do_print;
+ case -1: /* Invalid multibyte sequence. Handled below. */
+ goto do_print;
+ case 0:
+ found++;
+ break;
+ default:
+ found += ret;
+ break;
+ }
+ }
+
+do_print:
+
+ /* Print all the base characters out */
+ if (found != src)
+ {
+ DWORD len = found - src;
+ buf_len = con.str_to_con (f_mbtowc, write_buf, (const char *) src, len);
+ if (!buf_len)
+ {
+ debug_printf ("conversion error, handle %p",
+ get_output_handle ());
+ __seterrno ();
+ return 0;
+ }
+
+ if (con.insert_mode)
+ {
+ int x, y;
+ cursor_get (&x, &y);
+ scroll_buffer (x, y, -1, y, x + buf_len, y);
+ }
+
+ if (!write_console (write_buf, buf_len, done))
+ {
+ debug_printf ("write failed, handle %p", get_output_handle ());
+ return 0;
+ }
+ /* Stop here if we reached the conversion limit. */
+ if (len >= CONVERT_LIMIT)
+ return found + trunc_buf.len;
+ }
+ /* If there's still something in the src buffer, but it's not a truncated
+ multibyte sequence, then we stumbled over a control character or an
+ invalid multibyte sequence. Print it. */
+ if (found < end && trunc_buf.len == 0)
+ {
+ int x, y;
+ switch (base_chars[*found])
+ {
+ case SO: /* Shift Out: Invoke G1 character set (ISO 2022) */
+ con.iso_2022_G1 = true;
+ break;
+ case SI: /* Shift In: Invoke G0 character set (ISO 2022) */
+ con.iso_2022_G1 = false;
+ break;
+ case BEL:
+ beep ();
+ break;
+ case ESC:
+ con.state = gotesc;
+ wpbuf.put (*found);
+ break;
+ case DWN:
+ cursor_get (&x, &y);
+ if (y >= srBottom)
+ {
+ if (y >= con.b.srWindow.Bottom && !con.scroll_region.Top)
+ {
+ acquire_attach_mutex (mutex_timeout);
+ WriteConsoleW (get_output_handle (), L"\n", 1, &done, 0);
+ release_attach_mutex ();
+ }
+ else
+ {
+ scroll_buffer (0, srTop + 1, -1, srBottom, 0, srTop);
+ y--;
+ }
+ }
+ cursor_set (false,
+ ((get_ttyp ()->ti.c_oflag & ONLCR) ? 0 : x), y + 1);
+ break;
+ case BAK:
+ cursor_rel (-1, 0);
+ break;
+ case IGN:
+ /* Up to release 3.1.3 we called cursor_rel (1, 0); to move the cursor
+ one step to the right. However, that neither matches the terminfo
+ for the cygwin terminal, nor the one for the xterm terminal. */
+ break;
+ case CR:
+ cursor_get (&x, &y);
+ cursor_set (false, 0, y);
+ break;
+ case ERR:
+ /* Don't print chars marked as ERR chars, except for a ASCII CAN
+ sequence which is printed as singlebyte chars from the UTF
+ Basic Latin and Latin 1 Supplement plains. */
+ if (*found == 0x18)
+ {
+ write_replacement_char ();
+ if (found + 1 < end)
+ {
+ ret = __utf8_mbtowc (_REENT, NULL, (const char *) found + 1,
+ end - found - 1, &ps);
+ if (ret != -1)
+ {
+ acquire_attach_mutex (mutex_timeout);
+ while (ret-- > 0)
+ {
+ WCHAR w = *(found + 1);
+ WriteConsoleW (get_output_handle (), &w, 1, &done, 0);
+ found++;
+ }
+ release_attach_mutex ();
+ }
+ }
+ }
+ break;
+ case TAB:
+ cursor_get (&x, &y);
+ cursor_set (false, 8 * (x / 8 + 1), y);
+ break;
+ case NOR:
+ write_replacement_char ();
+ break;
+ }
+ found++;
+ }
+ return found + trunc_buf.len;
+}
+
+ssize_t
+fhandler_console::write (const void *vsrc, size_t len)
+{
+ bg_check_types bg = bg_check (SIGTTOU);
+ if (bg <= bg_eof)
+ return (ssize_t) bg;
+
+ if (get_ttyp ()->ti.c_lflag & FLUSHO)
+ return len; /* Discard write data */
+
+ if (get_ttyp ()->output_stopped && is_nonblocking ())
+ {
+ set_errno (EAGAIN);
+ return -1;
+ }
+ while (get_ttyp ()->output_stopped)
+ cygwait (10);
+
+ push_process_state process_state (PID_TTYOU);
+
+ acquire_output_mutex (mutex_timeout);
+
+ /* Run and check for ansi sequences */
+ unsigned const char *src = (unsigned char *) vsrc;
+ unsigned const char *end = src + len;
+ /* This might look a bit far fetched, but using the TLS path buffer allows
+ to allocate a big buffer without using the stack too much. Doing it here
+ in write instead of in write_normal should be faster, too. */
+ tmp_pathbuf tp;
+ write_buf = tp.w_get ();
+
+ debug_printf ("%p, %ld", vsrc, len);
+
+ while (src < end)
+ {
+ paranoid_printf ("char %0c state is %d", *src, con.state);
+ switch (con.state)
+ {
+ case normal:
+ src = write_normal (src, end);
+ if (!src) /* write_normal failed */
+ {
+ release_output_mutex ();
+ return -1;
+ }
+ break;
+ case gotesc:
+ if (*src == '[') /* CSI Control Sequence Introducer */
+ {
+ wpbuf.put (*src);
+ con.state = gotsquare;
+ memset (con.args, 0, sizeof con.args);
+ con.nargs = 0;
+ con.saw_question_mark = false;
+ con.saw_greater_than_sign = false;
+ con.saw_space = false;
+ con.saw_exclamation_mark = false;
+ }
+ else if (*src == '8') /* DECRC Restore cursor position */
+ {
+ if (con.screen_alternated)
+ {
+ /* For xterm mode only */
+ /* Just send the sequence */
+ wpbuf.put (*src);
+ wpbuf.send ();
+ }
+ else if (con.savex >= 0 && con.savey >= 0)
+ cursor_set (false, con.savex, con.savey);
+ con.state = normal;
+ wpbuf.empty();
+ }
+ else if (*src == '7') /* DECSC Save cursor position */
+ {
+ if (con.screen_alternated)
+ {
+ /* For xterm mode only */
+ /* Just send the sequence */
+ wpbuf.put (*src);
+ wpbuf.send ();
+ }
+ else
+ cursor_get (&con.savex, &con.savey);
+ con.state = normal;
+ wpbuf.empty();
+ }
+ else if (wincap.has_con_24bit_colors () && !con_is_legacy
+ && wincap.has_con_broken_il_dl () && *src == 'M')
+ { /* Reverse Index (scroll down) */
+ int x, y;
+ cursor_get (&x, &y);
+ if (y == srTop)
+ {
+ if (y == con.b.srWindow.Top
+ && srBottom == con.b.srWindow.Bottom)
+ {
+ /* Erase scroll down area */
+ wchar_t buf[] = L"\033[32768;1H\033[J\033[32768;32768";
+ __small_swprintf (buf, L"\033[%d;1H\033[J\033[%d;%dH",
+ srBottom - con.b.srWindow.Top + 1,
+ y + 1 - con.b.srWindow.Top, x + 1);
+ acquire_attach_mutex (mutex_timeout);
+ WriteConsoleW (get_output_handle (),
+ buf, wcslen (buf), 0, 0);
+ release_attach_mutex ();
+ }
+ /* Substitute "CSI Ps T" */
+ wpbuf.put ('[');
+ wpbuf.put ('T');
+ }
+ else
+ wpbuf.put (*src);
+ wpbuf.send ();
+ con.state = normal;
+ wpbuf.empty();
+ }
+ else if (*src == ']') /* OSC Operating System Command */
+ {
+ wpbuf.put (*src);
+ con.rarg = 0;
+ con.my_title_buf[0] = '\0';
+ con.state = gotrsquare;
+ }
+ else if (wincap.has_con_24bit_colors () && !con_is_legacy)
+ {
+ if (*src == 'c') /* RIS Full reset */
+ {
+ con.scroll_region.Top = 0;
+ con.scroll_region.Bottom = -1;
+ con.savex = con.savey = -1;
+ con.cursor_key_app_mode = false;
+ }
+ /* ESC sequences below (e.g. OSC, etc) are left to xterm
+ emulation in xterm compatible mode, therefore, are not
+ handled and just sent them. */
+ wpbuf.put (*src);
+ /* Just send the sequence */
+ wpbuf.send ();
+ con.state = normal;
+ wpbuf.empty();
+ }
+ else if (*src == '(') /* Designate G0 character set */
+ {
+ wpbuf.put (*src);
+ con.state = gotparen;
+ }
+ else if (*src == ')') /* Designate G1 character set */
+ {
+ wpbuf.put (*src);
+ con.state = gotrparen;
+ }
+ else if (*src == 'M') /* Reverse Index (scroll down) */
+ {
+ con.fillin (get_output_handle ());
+ scroll_buffer_screen (0, 0, -1, -1, 0, 1);
+ con.state = normal;
+ wpbuf.empty();
+ }
+ else if (*src == 'c') /* RIS Full Reset */
+ {
+ con.set_default_attr ();
+ con.vt100_graphics_mode_G0 = false;
+ con.vt100_graphics_mode_G1 = false;
+ con.iso_2022_G1 = false;
+ cursor_set (false, 0, 0);
+ clear_screen (cl_buf_beg, cl_buf_beg, cl_buf_end, cl_buf_end);
+ con.state = normal;
+ wpbuf.empty();
+ }
+ else if (*src == 'R') /* ? */
+ {
+ con.state = normal;
+ wpbuf.empty();
+ }
+ else
+ {
+ con.state = normal;
+ wpbuf.empty();
+ }
+ src++;
+ break;
+ case gotarg1:
+ if (isdigit (*src))
+ {
+ if (con.nargs < MAXARGS)
+ con.args[con.nargs] = con.args[con.nargs] * 10 + *src - '0';
+ wpbuf.put (*src);
+ src++;
+ }
+ else if (*src == ';')
+ {
+ wpbuf.put (*src);
+ src++;
+ if (con.nargs < MAXARGS)
+ con.nargs++;
+ }
+ else if (*src == ' ')
+ {
+ wpbuf.put (*src);
+ src++;
+ con.saw_space = true;
+ con.state = gotcommand;
+ }
+ else
+ con.state = gotcommand;
+ break;
+ case gotcommand:
+ if (con.nargs < MAXARGS)
+ con.nargs++;
+ char_command (*src++);
+ con.state = normal;
+ wpbuf.empty();
+ break;
+ case gotrsquare:
+ if (isdigit (*src))
+ con.rarg = con.rarg * 10 + (*src - '0');
+ else if (*src == ';')
+ {
+ if (con.rarg == 0 || con.rarg == 2)
+ con.state = gettitle;
+ else if ((con.rarg >= 4 && con.rarg <= 6)
+ || (con.rarg >=10 && con.rarg <= 19)
+ || (con.rarg >=104 && con.rarg <= 106)
+ || (con.rarg >=110 && con.rarg <= 119))
+ con.state = eatpalette;
+ else
+ con.state = eattitle;
+ }
+ else if (*src == '\033')
+ con.state = endpalette;
+ else if (*src == '\007')
+ {
+ wpbuf.put (*src);
+ if (wincap.has_con_24bit_colors () && !con_is_legacy)
+ wpbuf.send ();
+ wpbuf.empty ();
+ con.state = normal;
+ src++;
+ break;
+ }
+ wpbuf.put (*src);
+ src++;
+ break;
+ case eattitle:
+ case gettitle:
+ {
+ wpbuf.put (*src);
+ int n = strlen (con.my_title_buf);
+ if (*src < ' ')
+ {
+ if (wincap.has_con_24bit_colors () && !con_is_legacy)
+ wpbuf.send ();
+ else if (*src == '\007' && con.state == gettitle)
+ set_console_title (con.my_title_buf);
+ con.state = normal;
+ wpbuf.empty();
+ }
+ else if (n < TITLESIZE)
+ {
+ con.my_title_buf[n++] = *src;
+ con.my_title_buf[n] = '\0';
+ }
+ src++;
+ break;
+ }
+ case eatpalette:
+ wpbuf.put (*src);
+ if (*src == '?')
+ con.saw_question_mark = true;
+ else if (*src == '\033')
+ con.state = endpalette;
+ else if (*src == '\a')
+ {
+ /* Send OSC Ps; Pt BEL other than OSC Ps; ? BEL */
+ if (wincap.has_con_24bit_colors () && !con_is_legacy
+ && !con.saw_question_mark)
+ wpbuf.send ();
+ con.state = normal;
+ wpbuf.empty();
+ }
+ src++;
+ break;
+ case endpalette:
+ wpbuf.put (*src);
+ if (*src == '\\')
+ {
+ /* Send OSC Ps; Pt ST other than OSC Ps; ? ST */
+ if (wincap.has_con_24bit_colors () && !con_is_legacy
+ && !con.saw_question_mark)
+ wpbuf.send ();
+ con.state = normal;
+ }
+ else
+ /* Sequence error (abort) */
+ con.state = normal;
+ wpbuf.empty();
+ src++;
+ break;
+ case gotsquare:
+ if (*src == ';')
+ {
+ con.state = gotarg1;
+ wpbuf.put (*src);
+ if (con.nargs < MAXARGS)
+ con.nargs++;
+ src++;
+ }
+ else if (isalpha (*src))
+ con.state = gotcommand;
+ else if (*src != '@' && !isalpha (*src) && !isdigit (*src))
+ {
+ if (*src == '?')
+ con.saw_question_mark = true;
+ else if (*src == '>')
+ con.saw_greater_than_sign = true;
+ else if (*src == '!')
+ con.saw_exclamation_mark = true;
+ wpbuf.put (*src);
+ /* ignore any extra chars between [ and first arg or command */
+ src++;
+ }
+ else
+ con.state = gotarg1;
+ break;
+ case gotparen: /* Designate G0 Character Set (ISO 2022) */
+ if (*src == '0')
+ con.vt100_graphics_mode_G0 = true;
+ else
+ con.vt100_graphics_mode_G0 = false;
+ con.state = normal;
+ wpbuf.empty();
+ src++;
+ break;
+ case gotrparen: /* Designate G1 Character Set (ISO 2022) */
+ if (*src == '0')
+ con.vt100_graphics_mode_G1 = true;
+ else
+ con.vt100_graphics_mode_G1 = false;
+ con.state = normal;
+ wpbuf.empty();
+ src++;
+ break;
+ }
+ }
+ release_output_mutex ();
+
+ syscall_printf ("%ld = fhandler_console::write(...)", len);
+
+ return len;
+}
+
+void
+fhandler_console::doecho (const void *str, DWORD len)
+{
+ bool stopped = get_ttyp ()->output_stopped;
+ get_ttyp ()->output_stopped = false;
+ write (str, len);
+ get_ttyp ()->output_stopped = stopped;
+}
+
+static const struct {
+ int vk;
+ const char *val[4];
+} keytable[] = {
+ /* NORMAL */ /* SHIFT */ /* CTRL */ /* CTRL-SHIFT */
+ /* Unmodified and Alt-modified keypad keys comply with linux console
+ SHIFT, CTRL, CTRL-SHIFT modifiers comply with xterm modifier usage */
+ {VK_NUMPAD5, {"\033[G", "\033[1;2G", "\033[1;5G", "\033[1;6G"}},
+ {VK_CLEAR, {"\033[G", "\033[1;2G", "\033[1;5G", "\033[1;6G"}},
+ {VK_LEFT, {"\033[D", "\033[1;2D", "\033[1;5D", "\033[1;6D"}},
+ {VK_RIGHT, {"\033[C", "\033[1;2C", "\033[1;5C", "\033[1;6C"}},
+ {VK_UP, {"\033[A", "\033[1;2A", "\033[1;5A", "\033[1;6A"}},
+ {VK_DOWN, {"\033[B", "\033[1;2B", "\033[1;5B", "\033[1;6B"}},
+ {VK_PRIOR, {"\033[5~", "\033[5;2~", "\033[5;5~", "\033[5;6~"}},
+ {VK_NEXT, {"\033[6~", "\033[6;2~", "\033[6;5~", "\033[6;6~"}},
+ {VK_HOME, {"\033[1~", "\033[1;2~", "\033[1;5~", "\033[1;6~"}},
+ {VK_END, {"\033[4~", "\033[4;2~", "\033[4;5~", "\033[4;6~"}},
+ {VK_INSERT, {"\033[2~", "\033[2;2~", "\033[2;5~", "\033[2;6~"}},
+ {VK_DELETE, {"\033[3~", "\033[3;2~", "\033[3;5~", "\033[3;6~"}},
+ /* F1...F12, SHIFT-F1...SHIFT-F10 comply with linux console
+ F6...F12, and all modified F-keys comply with rxvt (compatible extension) */
+ {VK_F1, {"\033[[A", "\033[23~", "\033[11^", "\033[23^"}},
+ {VK_F2, {"\033[[B", "\033[24~", "\033[12^", "\033[24^"}},
+ {VK_F3, {"\033[[C", "\033[25~", "\033[13^", "\033[25^"}},
+ {VK_F4, {"\033[[D", "\033[26~", "\033[14^", "\033[26^"}},
+ {VK_F5, {"\033[[E", "\033[28~", "\033[15^", "\033[28^"}},
+ {VK_F6, {"\033[17~", "\033[29~", "\033[17^", "\033[29^"}},
+ {VK_F7, {"\033[18~", "\033[31~", "\033[18^", "\033[31^"}},
+ {VK_F8, {"\033[19~", "\033[32~", "\033[19^", "\033[32^"}},
+ {VK_F9, {"\033[20~", "\033[33~", "\033[20^", "\033[33^"}},
+ {VK_F10, {"\033[21~", "\033[34~", "\033[21^", "\033[34^"}},
+ {VK_F11, {"\033[23~", "\033[23$", "\033[23^", "\033[23@"}},
+ {VK_F12, {"\033[24~", "\033[24$", "\033[24^", "\033[24@"}},
+ /* CTRL-6 complies with Windows cmd console but should be fixed */
+ {'6', {NULL, NULL, "\036", NULL}},
+ /* Table end marker */
+ {0}
+};
+
+const char *
+fhandler_console::get_nonascii_key (INPUT_RECORD& input_rec, char *tmp)
+{
+#define NORMAL 0
+#define SHIFT 1
+#define CONTROL 2
+/*#define CONTROLSHIFT 3*/
+
+ int modifier_index = NORMAL;
+ if (input_rec.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED)
+ modifier_index = SHIFT;
+ if (input_rec.Event.KeyEvent.dwControlKeyState & CTRL_PRESSED)
+ modifier_index += CONTROL;
+
+ for (int i = 0; keytable[i].vk; i++)
+ if (input_rec.Event.KeyEvent.wVirtualKeyCode == keytable[i].vk)
+ {
+ if ((input_rec.Event.KeyEvent.dwControlKeyState & ALT_PRESSED)
+ && keytable[i].val[modifier_index] != NULL)
+ { /* Generic ESC prefixing if Alt is pressed */
+ tmp[0] = '\033';
+ strcpy (tmp + 1, keytable[i].val[modifier_index]);
+ return tmp;
+ }
+ else
+ return keytable[i].val[modifier_index];
+ }
+
+ if (input_rec.Event.KeyEvent.uChar.AsciiChar)
+ {
+ tmp[0] = input_rec.Event.KeyEvent.uChar.AsciiChar;
+ tmp[1] = '\0';
+ return tmp;
+ }
+ return NULL;
+}
+
+int
+fhandler_console::init (HANDLE h, DWORD a, mode_t bin)
+{
+ // this->fhandler_termios::init (h, mode, bin);
+ /* Ensure both input and output console handles are open */
+ int flags = 0;
+
+ a &= GENERIC_READ | GENERIC_WRITE;
+ if (a == GENERIC_READ)
+ flags = O_RDONLY;
+ if (a == GENERIC_WRITE)
+ flags = O_WRONLY;
+ if (a == (GENERIC_READ | GENERIC_WRITE))
+ flags = O_RDWR;
+ open_with_arch (flags | O_BINARY | (h ? 0 : O_NOCTTY));
+
+ return !tcsetattr (0, &get_ttyp ()->ti);
+}
+
+int
+fhandler_console::igncr_enabled ()
+{
+ return get_ttyp ()->ti.c_iflag & IGNCR;
+}
+
+void
+fhandler_console::set_close_on_exec (bool val)
+{
+ close_on_exec (val);
+}
+
+void
+set_console_title (char *title)
+{
+ wchar_t buf[TITLESIZE + 1];
+ sys_mbstowcs (buf, TITLESIZE + 1, title);
+ lock_ttys here (15000);
+ acquire_attach_mutex (mutex_timeout);
+ SetConsoleTitleW (buf);
+ release_attach_mutex ();
+ debug_printf ("title '%W'", buf);
+}
+
+static bool NO_COPY gdb_inferior_noncygwin = false;
+
+void
+fhandler_console::set_console_mode_to_native ()
+{
+ /* Setting-up console mode for non-cygwin app started by GDB. This is
+ called from hooked CreateProcess() and ContinueDebugEvent(). */
+ cygheap_fdenum cfd (false);
+ while (cfd.next () >= 0)
+ if (cfd->get_major () == DEV_CONS_MAJOR)
+ {
+ fhandler_console *cons = (fhandler_console *) (fhandler_base *) cfd;
+ if (cons->get_device () == cons->tc ()->getntty ())
+ {
+ termios *cons_ti = &cons->tc ()->ti;
+ set_input_mode (tty::native, cons_ti, cons->get_handle_set ());
+ set_output_mode (tty::native, cons_ti, cons->get_handle_set ());
+ set_disable_master_thread (true, cons);
+ break;
+ }
+ }
+}
+
+#define DEF_HOOK(name) static __typeof__ (name) *name##_Orig
+/* CreateProcess() is hooked for GDB etc. */
+DEF_HOOK (CreateProcessA);
+DEF_HOOK (CreateProcessW);
+DEF_HOOK (ContinueDebugEvent);
+DEF_HOOK (GetProcAddress); /* Hooked for ConEmu cygwin connector */
+
+static BOOL
+CreateProcessA_Hooked
+ (LPCSTR n, LPSTR c, LPSECURITY_ATTRIBUTES pa, LPSECURITY_ATTRIBUTES ta,
+ BOOL inh, DWORD f, LPVOID e, LPCSTR d,
+ LPSTARTUPINFOA si, LPPROCESS_INFORMATION pi)
+{
+ if (f & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS))
+ mutex_timeout = 0; /* to avoid deadlock in GDB */
+ gdb_inferior_noncygwin = !fhandler_termios::path_iscygexec_a (n, c);
+ if (gdb_inferior_noncygwin)
+ fhandler_console::set_console_mode_to_native ();
+ init_console_handler (false);
+ return CreateProcessA_Orig (n, c, pa, ta, inh, f, e, d, si, pi);
+}
+
+static BOOL
+CreateProcessW_Hooked
+ (LPCWSTR n, LPWSTR c, LPSECURITY_ATTRIBUTES pa, LPSECURITY_ATTRIBUTES ta,
+ BOOL inh, DWORD f, LPVOID e, LPCWSTR d,
+ LPSTARTUPINFOW si, LPPROCESS_INFORMATION pi)
+{
+ if (f & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS))
+ mutex_timeout = 0; /* to avoid deadlock in GDB */
+ gdb_inferior_noncygwin = !fhandler_termios::path_iscygexec_w (n, c);
+ if (gdb_inferior_noncygwin)
+ fhandler_console::set_console_mode_to_native ();
+ init_console_handler (false);
+ return CreateProcessW_Orig (n, c, pa, ta, inh, f, e, d, si, pi);
+}
+
+static BOOL
+ContinueDebugEvent_Hooked
+ (DWORD p, DWORD t, DWORD s)
+{
+ if (gdb_inferior_noncygwin)
+ fhandler_console::set_console_mode_to_native ();
+ init_console_handler (false);
+ return ContinueDebugEvent_Orig (p, t, s);
+}
+
+/* Hooked for ConEmu cygwin connector */
+static FARPROC
+GetProcAddress_Hooked (HMODULE h, LPCSTR n)
+{
+ if (strcmp(n, "RequestTermConnector") == 0)
+ fhandler_console::set_disable_master_thread (true);
+ return GetProcAddress_Orig (h, n);
+}
+
+void
+fhandler_console::fixup_after_fork_exec (bool execing)
+{
+ set_unit ();
+ setup_io_mutex ();
+ wpbuf.init (get_output_handle ());
+
+ if (!execing)
+ return;
+
+#define DO_HOOK(module, name) \
+ if (!name##_Orig) \
+ { \
+ void *api = hook_api (module, #name, (void *) name##_Hooked); \
+ name##_Orig = (__typeof__ (name) *) api; \
+ /*if (api) system_printf (#name " hooked.");*/ \
+ }
+ /* CreateProcess() is hooked for GDB etc. */
+ DO_HOOK (NULL, CreateProcessA);
+ DO_HOOK (NULL, CreateProcessW);
+ DO_HOOK (NULL, ContinueDebugEvent);
+}
+
+static void
+hook_conemu_cygwin_connector()
+{
+ DO_HOOK (NULL, GetProcAddress);
+}
+
+/* Ugly workaround to create invisible console required since Windows 7.
+
+ First try to just attach to any console which may have started this
+ app. If that works use this as our "invisible console".
+
+ This will fail if not started from the command prompt. In that case, start
+ a dummy console application in a hidden state so that we can use its console
+ as our invisible console. This probably works everywhere but process
+ creation is slow and to be avoided if possible so the window station method
+ is vastly preferred.
+
+ FIXME: This is not completely thread-safe since it creates two inheritable
+ handles which are known only to this function. If another thread starts
+ a process the new process will inherit these handles. However, since this
+ function is currently only called at startup and during exec, it shouldn't
+ be a big deal. */
+bool
+fhandler_console::create_invisible_console_workaround (bool force)
+{
+ /* If force is set, avoid to reattach to existing console. */
+ if (force || !AttachConsole (-1))
+ {
+ bool taskbar;
+ DWORD err = force ? 0 : GetLastError ();
+ path_conv helper ("/bin/cygwin-console-helper.exe");
+ HANDLE hello = NULL;
+ HANDLE goodbye = NULL;
+ /* If err == ERROR_PROC_FOUND then this method won't work. But that's
+ ok. The window station method should work ok when AttachConsole doesn't
+ work.
+
+ If the helper doesn't exist or we can't create event handles then we
+ can't use this method. */
+ if (err == ERROR_PROC_NOT_FOUND || !helper.exists ()
+ || !(hello = CreateEvent (&sec_none, true, false, NULL))
+ || !(goodbye = CreateEvent (&sec_none, true, false, NULL)))
+ {
+ AllocConsole (); /* This is just sanity check code. We should
+ never actually hit here unless we're running
+ in an environment which lacks the helper
+ app. */
+ taskbar = true;
+ }
+ else
+ {
+ STARTUPINFOW si = {};
+ PROCESS_INFORMATION pi;
+ size_t len = helper.get_wide_win32_path_len ();
+ WCHAR cmd[len + 1];
+ WCHAR args[len + 1 + (2 * sizeof (" 0xffffffffffffffff")) + 1];
+ WCHAR title[] = L"invisible cygwin console";
+
+ /* Create a new hidden process. Use the two event handles as
+ argv[1] and argv[2]. */
+
+ helper.get_wide_win32_path (cmd);
+ __small_swprintf (args, L"\"%W\" %p %p", cmd, hello, goodbye);
+
+ si.cb = sizeof (si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+ si.lpTitle = title;
+
+ BOOL x = CreateProcessW (cmd, args,
+ &sec_none_nih, &sec_none_nih, true,
+ CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
+ if (x)
+ {
+ CloseHandle (pi.hProcess); /* Don't need */
+ CloseHandle (pi.hThread); /* these. */
+ }
+ taskbar = false;
+ /* Wait for subprocess to indicate that it is live. This may not
+ actually be needed but it's hard to say since it is possible that
+ there will be no console for a brief time after the process
+ returns and there is no easy way to determine if/when this happens
+ in Windows. So play it safe. */
+ if (!x || (WaitForSingleObject (hello, 10000) != WAIT_OBJECT_0)
+ || !AttachConsole (pi.dwProcessId))
+ AllocConsole (); /* Oh well. Watch the flash. */
+ }
+
+ if (!taskbar)
+ /* Setting the owner of the console window to HWND_MESSAGE seems to
+ hide it from the taskbar. Don't know if this method is faster than
+ calling ShowWindowAsync but it should guarantee no taskbar presence
+ for the hidden console. */
+ SetParent (GetConsoleWindow (), HWND_MESSAGE);
+ if (hello)
+ CloseHandle (hello);
+ if (goodbye)
+ {
+ SetEvent (goodbye); /* Tell helper process it's ok to exit. */
+ CloseHandle (goodbye);
+ }
+ }
+ return invisible_console = true;
+}
+
+void
+fhandler_console::free_console ()
+{
+ BOOL res = FreeConsole ();
+ debug_printf ("freed console, res %d", res);
+ init_console_handler (false);
+}
+
+bool
+fhandler_console::need_invisible (bool force)
+{
+ BOOL b = false;
+ /* If force is set, forcibly create a new invisible console
+ even if a console device already exists. */
+ if (exists () && !force)
+ invisible_console = false;
+ else
+ {
+ HWINSTA h;
+ /* The intent here is to allocate an "invisible" console if we have no
+ controlling tty or to reuse the existing console if we already have
+ a tty. So, first get the old window station. If there is no controlling
+ terminal, create a new window station and then set it as the current
+ window station. The subsequent AllocConsole will then be allocated
+ invisibly. But, after doing that we have to restore any existing windows
+ station or, strangely, characters will not be displayed in any windows
+ drawn on the current screen. We only do this if we have changed to
+ a new window station and if we had an existing windows station previously.
+ We also close the previously opened window station even though AllocConsole
+ is now "using" it. This doesn't seem to cause any problems.
+
+ Things to watch out for if you make changes in this code:
+
+ - Flashing, black consoles showing up when you start, e.g., ssh in
+ an xterm.
+ - Non-displaying of characters in rxvt or xemacs if you start a
+ process using setsid: bash -lc "setsid rxvt". */
+
+ h = GetProcessWindowStation ();
+
+ USEROBJECTFLAGS oi;
+ DWORD len;
+ if (!h
+ || !GetUserObjectInformationW (h, UOI_FLAGS, &oi, sizeof (oi), &len)
+ || !(oi.dwFlags & WSF_VISIBLE))
+ {
+ b = true;
+ debug_printf ("window station is not visible");
+ AllocConsole ();
+ invisible_console = true;
+ }
+ b = create_invisible_console_workaround (force);
+ }
+
+ debug_printf ("invisible_console %d", invisible_console);
+ return b;
+}
+
+DWORD
+fhandler_console::__acquire_input_mutex (const char *fn, int ln, DWORD ms)
+{
+#ifdef DEBUGGING
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): trying to get input_mutex", ln);
+#endif
+ DWORD res = WaitForSingleObject (input_mutex, ms);
+ if (res != WAIT_OBJECT_0)
+ strace.prntf (_STRACE_TERMIOS, fn,
+ "(%d): Failed to acquire input_mutex %08x",
+ ln, GetLastError ());
+#ifdef DEBUGGING
+ else
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): got input_mutex", ln);
+#endif
+ return res;
+}
+
+void
+fhandler_console::__release_input_mutex (const char *fn, int ln)
+{
+ ReleaseMutex (input_mutex);
+#ifdef DEBUGGING
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): release input_mutex", ln);
+#endif
+}
+
+DWORD
+fhandler_console::__acquire_output_mutex (const char *fn, int ln, DWORD ms)
+{
+#ifdef DEBUGGING
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): trying to get output_mutex", ln);
+#endif
+ DWORD res = WaitForSingleObject (output_mutex, ms);
+ if (res != WAIT_OBJECT_0)
+ strace.prntf (_STRACE_TERMIOS, fn,
+ "(%d): Failed to acquire output_mutex %08x",
+ ln, GetLastError ());
+#ifdef DEBUGGING
+ else
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): got output_mutex", ln);
+#endif
+ return res;
+}
+
+void
+fhandler_console::__release_output_mutex (const char *fn, int ln)
+{
+ ReleaseMutex (output_mutex);
+#ifdef DEBUGGING
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): release output_mutex", ln);
+#endif
+}
+
+void
+fhandler_console::get_duplicated_handle_set (handle_set_t *p)
+{
+ DuplicateHandle (GetCurrentProcess (), get_handle (),
+ GetCurrentProcess (), &p->input_handle,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), get_output_handle (),
+ GetCurrentProcess (), &p->output_handle,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), input_mutex,
+ GetCurrentProcess (), &p->input_mutex,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), output_mutex,
+ GetCurrentProcess (), &p->output_mutex,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+}
+
+/* The function close_handle_set() should be static so that they can
+ be called even after the fhandler_console instance is deleted. */
+void
+fhandler_console::close_handle_set (handle_set_t *p)
+{
+ CloseHandle (p->input_handle);
+ p->input_handle = NULL;
+ CloseHandle (p->output_handle);
+ p->output_handle = NULL;
+ CloseHandle (p->input_mutex);
+ p->input_mutex = NULL;
+ CloseHandle (p->output_mutex);
+ p->output_mutex = NULL;
+}
+
+bool
+fhandler_console::need_console_handler ()
+{
+ return con.disable_master_thread || con.master_thread_suspended;
+}
+
+void
+fhandler_console::set_disable_master_thread (bool x, fhandler_console *cons)
+{
+ if (con.disable_master_thread == x)
+ return;
+ if (cons == NULL)
+ {
+ if (cygheap->ctty && cygheap->ctty->get_major () == DEV_CONS_MAJOR)
+ cons = (fhandler_console *) cygheap->ctty;
+ else
+ return;
+ }
+ cons->acquire_input_mutex (mutex_timeout);
+ con.disable_master_thread = x;
+ cons->release_input_mutex ();
+}
diff --git a/winsup/cygwin/fhandler/cygdrive.cc b/winsup/cygwin/fhandler/cygdrive.cc
new file mode 100644
index 000000000..1ac1d5d4f
--- /dev/null
+++ b/winsup/cygwin/fhandler/cygdrive.cc
@@ -0,0 +1,158 @@
+/* fhandler_cygdrive.cc
+
+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 <lm.h>
+#include <sys/statvfs.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "shared_info.h"
+
+#define _LIBC
+#include <dirent.h>
+
+fhandler_cygdrive::fhandler_cygdrive () :
+ fhandler_disk_file ()
+{
+}
+
+int
+fhandler_cygdrive::open (int flags, mode_t mode)
+{
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ return 0;
+ }
+ if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ return 0;
+ }
+ /* Open a fake handle to \\Device\\Null */
+ return open_null (flags);
+}
+
+int
+fhandler_cygdrive::fstat (struct stat *buf)
+{
+ fhandler_base::fstat (buf);
+ buf->st_ino = 2;
+ buf->st_mode = S_IFDIR | STD_RBITS | STD_XBITS;
+ buf->st_nlink = 1;
+ return 0;
+}
+
+int
+fhandler_cygdrive::fstatvfs (struct statvfs *sfs)
+{
+ /* Virtual file system. Just return an empty buffer with a few values
+ set to something useful. Just as on Linux. */
+ memset (sfs, 0, sizeof (*sfs));
+ sfs->f_bsize = sfs->f_frsize = 4096;
+ sfs->f_flag = ST_RDONLY;
+ sfs->f_namemax = NAME_MAX;
+ return 0;
+}
+
+#define MAX_DRIVE_BUF_LEN (sizeof ("x:\\") * 26 + 2)
+
+struct __DIR_drives
+{
+ char *pdrive;
+ char pbuf[MAX_DRIVE_BUF_LEN];
+};
+
+#define d_drives(d) ((__DIR_drives *) (d)->__d_internal)
+
+DIR *
+fhandler_cygdrive::opendir (int fd)
+{
+ DIR *dir;
+
+ dir = fhandler_disk_file::opendir (fd);
+ if (dir)
+ {
+ dir->__d_internal = (uintptr_t) new __DIR_drives;
+ GetLogicalDriveStrings (MAX_DRIVE_BUF_LEN, d_drives(dir)->pbuf);
+ d_drives(dir)->pdrive = d_drives(dir)->pbuf;
+ }
+
+ return dir;
+}
+
+int
+fhandler_cygdrive::readdir (DIR *dir, dirent *de)
+{
+ WCHAR drive[] = L"X:";
+
+ while (true)
+ {
+ if (!d_drives(dir)->pdrive || !*d_drives(dir)->pdrive)
+ {
+ if (!(dir->__flags & dirent_saw_dot))
+ {
+ de->d_name[0] = '.';
+ de->d_name[1] = '\0';
+ de->d_ino = 2;
+ }
+ return ENMFILE;
+ }
+ disk_type dt = get_disk_type ((drive[0] = *d_drives(dir)->pdrive, drive));
+ if (dt == DT_SHARE_SMB)
+ {
+ /* Calling NetUseGetInfo on SMB drives allows to fetch the
+ current state of the drive without trying to open a file
+ descriptor on the share (GetFileAttributes). This avoids
+ waiting for SMB timeouts. Of course, there's a downside:
+ If a drive becomes availabe again, it can take a couple of
+ minutes to recognize it. As long as this didn't happen,
+ the drive will not show up in the cygdrive dir. */
+ PUSE_INFO_1 pui1;
+ DWORD status;
+
+ if (NetUseGetInfo (NULL, drive, 1, (PBYTE *) &pui1) == NERR_Success)
+ {
+ status = pui1->ui1_status;
+ NetApiBufferFree (pui1);
+ if (status == USE_OK)
+ break;
+ }
+ }
+ else if (dt != DT_FLOPPY
+ && GetFileAttributes (d_drives(dir)->pdrive) != INVALID_FILE_ATTRIBUTES)
+ break;
+ d_drives(dir)->pdrive = strchr (d_drives(dir)->pdrive, '\0') + 1;
+ }
+ *de->d_name = cyg_tolower (*d_drives(dir)->pdrive);
+ de->d_name[1] = '\0';
+ user_shared->warned_msdos = true;
+ de->d_ino = readdir_get_ino (d_drives(dir)->pdrive, false);
+ dir->__d_position++;
+ d_drives(dir)->pdrive = strchr (d_drives(dir)->pdrive, '\0') + 1;
+ syscall_printf ("%p = readdir (%p) (%s)", &de, dir, de->d_name);
+ return 0;
+}
+
+void
+fhandler_cygdrive::rewinddir (DIR *dir)
+{
+ d_drives(dir)->pdrive = d_drives(dir)->pbuf;
+ dir->__d_position = 0;
+}
+
+int
+fhandler_cygdrive::closedir (DIR *dir)
+{
+ delete d_drives(dir);
+ return 0;
+}
diff --git a/winsup/cygwin/fhandler/dev.cc b/winsup/cygwin/fhandler/dev.cc
new file mode 100644
index 000000000..c6bda5654
--- /dev/null
+++ b/winsup/cygwin/fhandler/dev.cc
@@ -0,0 +1,275 @@
+/* fhandler_dev.cc, Implement /dev.
+
+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 <stdlib.h>
+#include <sys/statvfs.h>
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "devices.h"
+
+#define _LIBC
+#include <dirent.h>
+
+#define dev_prefix_len (sizeof ("/dev"))
+#define dev_storage_scan_start (dev_storage + 1)
+#define dev_storage_size (dev_storage_end - dev_storage_scan_start)
+
+static int
+device_cmp (const void *a, const void *b)
+{
+ return strcmp (((const device *) a)->name (),
+ ((const device *) b)->name () + dev_prefix_len);
+}
+
+fhandler_dev::fhandler_dev () :
+ fhandler_disk_file (), devidx (NULL), dir_exists (true)
+{
+}
+
+int
+fhandler_dev::open (int flags, mode_t mode)
+{
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ return 0;
+ }
+ if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ return 0;
+ }
+ /* Filter O_CREAT flag to avoid creating a file called /dev accidentally. */
+ int ret = fhandler_disk_file::open (flags & ~O_CREAT, mode);
+ if (!ret)
+ {
+ /* Open a fake handle to \\Device\\Null */
+ ret = open_null (flags);
+ dir_exists = false;
+ }
+ return ret;
+}
+
+int
+fhandler_dev::close ()
+{
+ return fhandler_disk_file::close ();
+}
+
+int
+fhandler_dev::fstat (struct stat *st)
+{
+ /* If /dev really exists on disk, return correct disk information. */
+ if (pc.fs_got_fs ())
+ return fhandler_disk_file::fstat (st);
+ /* Otherwise fake virtual filesystem. */
+ fhandler_base::fstat (st);
+ st->st_ino = 2;
+ st->st_mode = S_IFDIR | STD_RBITS | STD_XBITS;
+ st->st_nlink = 1;
+ return 0;
+}
+
+int
+fhandler_dev::fstatvfs (struct statvfs *sfs)
+{
+ int ret = -1, opened = 0;
+ HANDLE fh = get_handle ();
+
+ if (!fh)
+ {
+ if (!open (O_RDONLY, 0))
+ return -1;
+ opened = 1;
+ }
+ if (pc.fs_got_fs ())
+ ret = fhandler_disk_file::fstatvfs (sfs);
+ else
+ {
+ /* Virtual file system. Just return an empty buffer with a few values
+ set to something useful similar to Linux. */
+ memset (sfs, 0, sizeof (*sfs));
+ sfs->f_bsize = sfs->f_frsize = 4096;
+ sfs->f_flag = ST_RDONLY;
+ sfs->f_namemax = NAME_MAX;
+ ret = 0;
+ }
+ if (opened)
+ close ();
+ return ret;
+}
+
+int
+fhandler_dev::rmdir ()
+{
+ set_errno (ENOTEMPTY);
+ return -1;
+}
+
+DIR *
+fhandler_dev::opendir (int fd)
+{
+ DIR *dir = fhandler_disk_file::opendir (fd);
+ if (dir)
+ dir_exists = true;
+ else if ((dir = (DIR *) malloc (sizeof (DIR))) == NULL)
+ set_errno (ENOMEM);
+ else if ((dir->__d_dirent =
+ (struct dirent *) malloc (sizeof (struct dirent))) == NULL)
+ {
+ set_errno (ENOMEM);
+ goto free_dir;
+ }
+ else
+ {
+ cygheap_fdnew cfd;
+ if (cfd < 0 && fd < 0)
+ goto free_dirent;
+
+ dir->__d_dirname = NULL;
+ dir->__d_dirent->__d_version = __DIRENT_VERSION;
+ dir->__d_cookie = __DIRENT_COOKIE;
+ dir->__handle = INVALID_HANDLE_VALUE;
+ dir->__d_position = 0;
+ dir->__flags = 0;
+ dir->__d_internal = 0;
+
+ if (fd >= 0)
+ dir->__d_fd = fd;
+ else if (!open (O_RDONLY, 0))
+ goto free_dirent;
+ else
+ {
+ cfd = this;
+ dir->__d_fd = cfd;
+ }
+ set_close_on_exec (true);
+ dir->__fh = this;
+ dir_exists = false;
+ drive = part = 0;
+ }
+
+ devidx = dir_exists ? NULL : dev_storage_scan_start;
+
+ syscall_printf ("%p = opendir (%s)", dir, get_name ());
+ return dir;
+
+free_dirent:
+ free (dir->__d_dirent);
+free_dir:
+ free (dir);
+ return NULL;
+}
+
+static const WCHAR *hd_pattern = L"\\Device\\Harddisk%u\\Partition%u";
+
+int
+fhandler_dev::readdir (DIR *dir, dirent *de)
+{
+ int ret;
+ const _device *curdev;
+ device dev;
+
+ if (!devidx)
+ {
+ while ((ret = fhandler_disk_file::readdir (dir, de)) == 0)
+ {
+ /* Avoid to print devices for which users have created files under
+ /dev already, for instance by using the old script from Igor
+ Peshansky. */
+ dev.name (de->d_name);
+ if (!bsearch (&dev, dev_storage_scan_start, dev_storage_size,
+ sizeof dev, device_cmp))
+ break;
+ }
+ if (ret != ENMFILE)
+ goto out;
+ devidx = dev_storage_scan_start;
+ }
+
+ /* Now start processing our internal dev table. */
+ ret = ENMFILE;
+ while ((curdev = devidx++) < dev_storage_end)
+ {
+ /* If exists returns < 0 it means that the device can be used by a
+ program but its use is deprecated and, so, it is not returned
+ by readdir((). */
+ device *cdev = (device *) curdev;
+ if (cdev->exists () <= 0)
+ continue;
+ ++dir->__d_position;
+ strcpy (de->d_name, cdev->name () + dev_prefix_len);
+ if (cdev->get_major () == DEV_TTY_MAJOR
+ && (cdev->is_device (FH_CONIN)
+ || cdev->is_device (FH_CONOUT)
+ || cdev->is_device (FH_CONSOLE)))
+ {
+ /* Make sure conin, conout, and console have the same inode number
+ as the current consX. */
+ de->d_ino = myself->ctty;
+ }
+ else
+ de->d_ino = cdev->get_device ();
+ de->d_type = cdev->type ();
+ ret = 0;
+ break;
+ }
+ /* Last but not least, scan for existing disks/partitions. */
+ if (ret)
+ {
+ UNICODE_STRING upath;
+ WCHAR buf[(sizeof *hd_pattern + 32) / sizeof (wchar_t)];
+ OBJECT_ATTRIBUTES attr;
+ FILE_BASIC_INFORMATION fbi;
+ NTSTATUS status;
+
+ InitializeObjectAttributes (&attr, &upath, 0, NULL, NULL);
+ while (drive < 128)
+ {
+ while (part < 64)
+ {
+ USHORT len = __small_swprintf (buf, hd_pattern, drive, part);
+ RtlInitCountedUnicodeString (&upath, buf, len * sizeof (WCHAR));
+ status = NtQueryAttributesFile (&attr, &fbi);
+ debug_printf ("%S %y", &upath, status);
+ if (status != STATUS_OBJECT_NAME_NOT_FOUND
+ && status != STATUS_OBJECT_PATH_NOT_FOUND)
+ {
+ device dev (drive, part);
+ strcpy (de->d_name, dev.name () + 5);
+ de->d_ino = dev.get_device ();
+ de->d_type = DT_BLK;
+ ++part;
+ ret = 0;
+ goto out;
+ }
+ if (part == 0)
+ break;
+ ++part;
+ }
+ part = 0;
+ ++drive;
+ }
+ }
+
+out:
+ debug_printf ("returning %d", ret);
+ return ret;
+}
+
+void
+fhandler_dev::rewinddir (DIR *dir)
+{
+ devidx = dir_exists ? NULL : dev_storage_scan_start;
+ dir->__d_position = 0;
+ if (dir_exists)
+ fhandler_disk_file::rewinddir (dir);
+}
diff --git a/winsup/cygwin/fhandler/dev_fd.cc b/winsup/cygwin/fhandler/dev_fd.cc
new file mode 100644
index 000000000..96ebd85ea
--- /dev/null
+++ b/winsup/cygwin/fhandler/dev_fd.cc
@@ -0,0 +1,53 @@
+/* fhandler_process_fd.cc: fhandler for the /dev/{fd,std{in,out,err}} symlinks
+
+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 "path.h"
+#include "fhandler.h"
+
+fhandler_dev_fd::fhandler_dev_fd ():
+ fhandler_virtual ()
+{
+}
+
+virtual_ftype_t
+fhandler_dev_fd::exists ()
+{
+ return virt_symlink;
+}
+
+int
+fhandler_dev_fd::fstat (struct stat *buf)
+{
+ const char *path = get_name ();
+ debug_printf ("fstat (%s)", path);
+
+ fhandler_base::fstat (buf);
+
+ buf->st_mode = S_IFLNK | STD_RBITS | S_IWUSR | S_IWGRP | S_IWOTH | STD_XBITS;
+ buf->st_ino = get_ino ();
+
+ return 0;
+}
+
+bool
+fhandler_dev_fd::fill_filebuf ()
+{
+ const char *path = get_name ();
+ debug_printf ("fill_filebuf (%s)", path);
+
+ const char *native = get_native_name ();
+ if (!native)
+ {
+ return false;
+ }
+
+ free(filebuf);
+ filebuf = cstrdup (native);
+ return true;
+}
diff --git a/winsup/cygwin/fhandler/disk_file.cc b/winsup/cygwin/fhandler/disk_file.cc
new file mode 100644
index 000000000..62c18e5e4
--- /dev/null
+++ b/winsup/cygwin/fhandler/disk_file.cc
@@ -0,0 +1,2673 @@
+/* fhandler_disk_file.cc
+
+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 <winioctl.h>
+#include <lm.h>
+#include <stdlib.h>
+#include <cygwin/acl.h>
+#include <sys/statvfs.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "shared_info.h"
+#include "pinfo.h"
+#include "ntdll.h"
+#include "tls_pbuf.h"
+#include "devices.h"
+#include "ldap.h"
+#include <aio.h>
+#include <cygwin/fs.h>
+
+#define _LIBC
+#include <dirent.h>
+
+enum __DIR_mount_type {
+ __DIR_mount_none = 0,
+ __DIR_mount_target,
+ __DIR_mount_virt_target
+};
+
+class __DIR_mounts
+{
+ int count;
+ const char *parent_dir;
+ size_t parent_dir_len;
+ UNICODE_STRING mounts[MAX_MOUNTS];
+ bool found[MAX_MOUNTS + 3];
+ UNICODE_STRING cygdrive;
+
+#define __DIR_PROC (MAX_MOUNTS)
+#define __DIR_CYGDRIVE (MAX_MOUNTS+1)
+#define __DIR_DEV (MAX_MOUNTS+2)
+
+public:
+ __DIR_mounts (const char *posix_path)
+ : parent_dir (posix_path)
+ {
+ parent_dir_len = strlen (parent_dir);
+ count = mount_table->get_mounts_here (parent_dir, parent_dir_len, mounts,
+ &cygdrive);
+ rewind ();
+ }
+ ~__DIR_mounts ()
+ {
+ mount_table->free_mounts_here (mounts, count, &cygdrive);
+ }
+ /* For an entry within this dir, check if a mount point exists. */
+ bool check_mount (PUNICODE_STRING fname)
+ {
+ if (parent_dir_len == 1) /* root dir */
+ {
+ if (RtlEqualUnicodeString (fname, &ro_u_proc, FALSE))
+ {
+ found[__DIR_PROC] = true;
+ return true;
+ }
+ if (RtlEqualUnicodeString (fname, &ro_u_dev, FALSE))
+ {
+ found[__DIR_DEV] = true;
+ return true;
+ }
+ if (fname->Length / sizeof (WCHAR) == mount_table->cygdrive_len - 2
+ && RtlEqualUnicodeString (fname, &cygdrive, FALSE))
+ {
+ found[__DIR_CYGDRIVE] = true;
+ return true;
+ }
+ }
+ for (int i = 0; i < count; ++i)
+ if (RtlEqualUnicodeString (fname, &mounts[i], FALSE))
+ {
+ found[i] = true;
+ return true;
+ }
+ return false;
+ }
+ /* On each call, add another mount point within this directory, which is
+ not backed by a real subdir. */
+ __DIR_mount_type check_missing_mount (PUNICODE_STRING retname = NULL)
+ {
+ for (int i = 0; i < count; ++i)
+ if (!found[i])
+ {
+ found[i] = true;
+ if (retname)
+ *retname = mounts[i];
+ return __DIR_mount_target;
+ }
+ if (parent_dir_len == 1) /* root dir */
+ {
+ if (!found[__DIR_PROC])
+ {
+ found[__DIR_PROC] = true;
+ if (retname)
+ *retname = ro_u_proc;
+ return __DIR_mount_virt_target;
+ }
+ if (!found[__DIR_DEV])
+ {
+ found[__DIR_DEV] = true;
+ if (retname)
+ *retname = ro_u_dev;
+ return __DIR_mount_virt_target;
+ }
+ if (!found[__DIR_CYGDRIVE])
+ {
+ found[__DIR_CYGDRIVE] = true;
+ if (cygdrive.Length > 0)
+ {
+ if (retname)
+ *retname = cygdrive;
+ return __DIR_mount_virt_target;
+ }
+ }
+ }
+ return __DIR_mount_none;
+ }
+ void rewind () { memset (found, 0, sizeof found); }
+};
+
+inline bool
+path_conv::isgood_inode (ino_t ino) const
+{
+ /* If the FS doesn't support nonambiguous inode numbers anyway, bail out
+ immediately. */
+ if (!hasgood_inode ())
+ return false;
+ /* If the inode numbers are 64 bit numbers or if it's a local FS, they
+ are to be trusted. */
+ if (ino > UINT32_MAX || !isremote ())
+ return true;
+ /* The inode numbers returned from a remote NT4 NTFS are ephemeral
+ 32 bit numbers. */
+ if (fs_is_ntfs ())
+ return false;
+ /* Starting with version 3.5.4, Samba returns the real inode numbers, if
+ the file is on the same device as the root of the share (Samba function
+ get_FileIndex). 32 bit inode numbers returned by older versions (likely
+ < 3.0) are ephemeral. */
+ if (fs_is_samba () && fs.samba_version () < 0x03050400)
+ return false;
+ /* Otherwise, trust the inode numbers unless proved otherwise. */
+ return true;
+}
+
+/* Check reparse point to determine if it should be treated as a
+ posix symlink or as a normal file/directory. Logic is explained
+ in detail in check_reparse_point_target in path.cc. */
+static inline bool
+readdir_check_reparse_point (POBJECT_ATTRIBUTES attr, bool remote)
+{
+ NTSTATUS status;
+ HANDLE reph;
+ IO_STATUS_BLOCK io;
+ tmp_pathbuf tp;
+ UNICODE_STRING symbuf;
+ bool ret = false;
+
+ status = NtOpenFile (&reph, READ_CONTROL, attr, &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
+ if (NT_SUCCESS (status))
+ {
+ PREPARSE_DATA_BUFFER rp = (PREPARSE_DATA_BUFFER) tp.c_get ();
+ ret = (check_reparse_point_target (reph, remote, rp, &symbuf) > 0);
+ NtClose (reph);
+ }
+ return ret;
+}
+
+inline ino_t
+path_conv::get_ino_by_handle (HANDLE hdl)
+{
+ IO_STATUS_BLOCK io;
+ FILE_INTERNAL_INFORMATION fii;
+
+ if (NT_SUCCESS (NtQueryInformationFile (hdl, &io, &fii, sizeof fii,
+ FileInternalInformation))
+ && isgood_inode (fii.IndexNumber.QuadPart))
+ return fii.IndexNumber.QuadPart;
+ return 0;
+}
+
+/* For files on NFS shares, we request an EA of type NfsV3Attributes.
+ This returns the content of a struct fattr3 as defined in RFC 1813.
+ The content is the NFS equivalent of struct stat. so there's not much
+ to do here except for copying. */
+int
+fhandler_base::fstat_by_nfs_ea (struct stat *buf)
+{
+ fattr3 *nfs_attr = pc.nfsattr ();
+ PWCHAR domain;
+ cyg_ldap cldap;
+ bool ldap_open = false;
+
+ if (get_handle ())
+ {
+ /* NFS stumbles over its own caching. If you write to the file,
+ a subsequent fstat does not return the actual size of the file,
+ but the size at the time the handle has been opened. Unless
+ access through another handle invalidates the caching within the
+ NFS client. */
+ if (get_access () & GENERIC_WRITE)
+ FlushFileBuffers (get_handle ());
+ pc.get_finfo (get_handle ());
+ }
+ buf->st_dev = nfs_attr->fsid;
+ buf->st_ino = nfs_attr->fileid;
+ buf->st_mode = (nfs_attr->mode & 0xfff)
+ | nfs_type_mapping[nfs_attr->type & 7];
+ buf->st_nlink = nfs_attr->nlink;
+ if (cygheap->pg.nss_pwd_db ())
+ {
+ /* Try to map UNIX uid/gid to Cygwin uid/gid. If there's no mapping in
+ the cache, try to fetch it from the configured RFC 2307 domain (see
+ last comment in cygheap_domain_info::init() for more information) and
+ add it to the mapping cache. */
+ buf->st_uid = cygheap->ugid_cache.get_uid (nfs_attr->uid);
+ if (buf->st_uid == ILLEGAL_UID)
+ {
+ uid_t map_uid = ILLEGAL_UID;
+
+ domain = cygheap->dom.get_rfc2307_domain ();
+ if ((ldap_open = (cldap.open (domain) == NO_ERROR)))
+ map_uid = cldap.remap_uid (nfs_attr->uid);
+ if (map_uid == ILLEGAL_UID)
+ map_uid = MAP_UNIX_TO_CYGWIN_ID (nfs_attr->uid);
+ cygheap->ugid_cache.add_uid (nfs_attr->uid, map_uid);
+ buf->st_uid = map_uid;
+ }
+ }
+ else /* fake files being owned by current user. */
+ buf->st_uid = myself->uid;
+ if (cygheap->pg.nss_grp_db ())
+ {
+ /* See above */
+ buf->st_gid = cygheap->ugid_cache.get_gid (nfs_attr->gid);
+ if (buf->st_gid == ILLEGAL_GID)
+ {
+ gid_t map_gid = ILLEGAL_GID;
+
+ domain = cygheap->dom.get_rfc2307_domain ();
+ if ((ldap_open || cldap.open (domain) == NO_ERROR))
+ map_gid = cldap.remap_gid (nfs_attr->gid);
+ if (map_gid == ILLEGAL_GID)
+ map_gid = MAP_UNIX_TO_CYGWIN_ID (nfs_attr->gid);
+ cygheap->ugid_cache.add_gid (nfs_attr->gid, map_gid);
+ buf->st_gid = map_gid;
+ }
+ }
+ else /* fake files being owned by current group. */
+ buf->st_gid = myself->gid;
+ buf->st_rdev = makedev (nfs_attr->rdev.specdata1,
+ nfs_attr->rdev.specdata2);
+ buf->st_size = nfs_attr->size;
+ buf->st_blksize = PREFERRED_IO_BLKSIZE;
+ buf->st_blocks = (nfs_attr->used + S_BLKSIZE - 1) / S_BLKSIZE;
+ buf->st_atim.tv_sec = nfs_attr->atime.tv_sec;
+ buf->st_atim.tv_nsec = nfs_attr->atime.tv_nsec;
+ buf->st_mtim.tv_sec = nfs_attr->mtime.tv_sec;
+ buf->st_mtim.tv_nsec = nfs_attr->mtime.tv_nsec;
+ buf->st_ctim.tv_sec = nfs_attr->ctime.tv_sec;
+ buf->st_ctim.tv_nsec = nfs_attr->ctime.tv_nsec;
+ return 0;
+}
+
+int
+fhandler_base::fstat_by_handle (struct stat *buf)
+{
+ HANDLE h = get_stat_handle ();
+ NTSTATUS status = 0;
+
+ /* If the file has been opened for other purposes than stat, we can't rely
+ on the information stored in pc.fai. So we overwrite them here. */
+ if (get_handle ())
+ {
+ status = pc.get_finfo (h);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("%y = NtQueryInformationFile(%S, FileAllInformation)",
+ status, pc.get_nt_native_path ());
+ return -1;
+ }
+ }
+ if (pc.isgood_inode (pc.fai ()->InternalInformation.IndexNumber.QuadPart))
+ ino = pc.fai ()->InternalInformation.IndexNumber.QuadPart;
+ return fstat_helper (buf);
+}
+
+int
+fhandler_base::fstat_by_name (struct stat *buf)
+{
+ NTSTATUS status;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ UNICODE_STRING dirname;
+ UNICODE_STRING basename;
+ HANDLE dir;
+ struct {
+ FILE_ID_BOTH_DIR_INFORMATION fdi;
+ WCHAR buf[NAME_MAX + 1];
+ } fdi_buf;
+
+ if (!ino && pc.hasgood_inode () && !pc.has_buggy_fileid_dirinfo ())
+ {
+ RtlSplitUnicodePath (pc.get_nt_native_path (), &dirname, &basename);
+ InitializeObjectAttributes (&attr, &dirname, pc.objcaseinsensitive (),
+ NULL, NULL);
+ status = NtOpenFile (&dir, SYNCHRONIZE | FILE_LIST_DIRECTORY,
+ &attr, &io, FILE_SHARE_VALID_FLAGS,
+ FILE_SYNCHRONOUS_IO_NONALERT
+ | FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_DIRECTORY_FILE);
+ if (!NT_SUCCESS (status))
+ debug_printf ("%y = NtOpenFile(%S)", status,
+ pc.get_nt_native_path ());
+ else
+ {
+ status = NtQueryDirectoryFile (dir, NULL, NULL, NULL, &io,
+ &fdi_buf.fdi, sizeof fdi_buf,
+ FileIdBothDirectoryInformation,
+ TRUE, &basename, TRUE);
+ NtClose (dir);
+ if (!NT_SUCCESS (status))
+ debug_printf ("%y = NtQueryDirectoryFile(%S)", status,
+ pc.get_nt_native_path ());
+ else
+ ino = fdi_buf.fdi.FileId.QuadPart;
+ }
+ }
+ return fstat_helper (buf);
+}
+
+int
+fhandler_base::fstat_fs (struct stat *buf)
+{
+ int res = -1;
+ int oret;
+ int open_flags = O_RDONLY | O_BINARY;
+
+ if (get_stat_handle ())
+ {
+ if (!nohandle () && (!is_fs_special () || get_flags () & O_PATH))
+ res = pc.fs_is_nfs () ? fstat_by_nfs_ea (buf) : fstat_by_handle (buf);
+ if (res)
+ res = fstat_by_name (buf);
+ return res;
+ }
+ /* First try to open with generic read access. This allows to read the file
+ in fstat_helper (when checking for executability) without having to
+ re-open it. Opening a file can take a lot of time on network drives
+ so we try to avoid that. */
+ oret = open_fs (open_flags, 0);
+ if (!oret)
+ {
+ query_open (query_read_attributes);
+ oret = open_fs (open_flags, 0);
+ }
+ if (oret)
+ {
+ /* We now have a valid handle, regardless of the "nohandle" state.
+ Since fhandler_base::close only calls CloseHandle if !nohandle,
+ we have to set it to false before calling close and restore
+ the state afterwards. */
+ res = pc.fs_is_nfs () ? fstat_by_nfs_ea (buf) : fstat_by_handle (buf);
+ bool no_handle = nohandle ();
+ nohandle (false);
+ close_fs ();
+ nohandle (no_handle);
+ set_handle (NULL);
+ }
+ if (res)
+ res = fstat_by_name (buf);
+
+ return res;
+}
+
+int
+fhandler_base::fstat_helper (struct stat *buf)
+{
+ IO_STATUS_BLOCK st;
+ FILE_COMPRESSION_INFORMATION fci;
+ HANDLE h = get_stat_handle ();
+ PFILE_ALL_INFORMATION pfai = pc.fai ();
+ ULONG attributes = pc.file_attributes ();
+
+ to_timestruc_t (&pfai->BasicInformation.LastAccessTime, &buf->st_atim);
+ to_timestruc_t (&pfai->BasicInformation.LastWriteTime, &buf->st_mtim);
+ /* If the ChangeTime is 0, the underlying FS doesn't support this timestamp
+ (FAT for instance). If so, it's faked using LastWriteTime. */
+ to_timestruc_t (pfai->BasicInformation.ChangeTime.QuadPart
+ ? &pfai->BasicInformation.ChangeTime
+ : &pfai->BasicInformation.LastWriteTime,
+ &buf->st_ctim);
+ to_timestruc_t (&pfai->BasicInformation.CreationTime, &buf->st_birthtim);
+ buf->st_dev = get_dev ();
+ /* CV 2011-01-13: Observations on the Cygwin mailing list point to an
+ interesting behaviour in some Windows versions. Apparently the size of
+ a directory is computed at the time the directory is first scanned. This
+ can result in two subsequent NtQueryInformationFile calls to return size
+ 0 in the first call and size > 0 in the second call. This in turn can
+ affect applications like newer tar.
+ FIXME: Is the allocation size affected as well? */
+ buf->st_size = pc.isdir ()
+ ? 0
+ : (off_t) pfai->StandardInformation.EndOfFile.QuadPart;
+ /* The number of links to a directory includes the number of subdirectories
+ in the directory, since all those subdirectories point to it. However,
+ this is painfully slow, so we do without it. */
+ buf->st_nlink = pc.fai()->StandardInformation.NumberOfLinks;
+
+ /* Enforce namehash as inode number on untrusted file systems. */
+ buf->st_ino = ino ?: get_ino ();
+
+ buf->st_blksize = PREFERRED_IO_BLKSIZE;
+
+ if (buf->st_size == 0
+ && pfai->StandardInformation.AllocationSize.QuadPart == 0LL)
+ /* File is empty and no blocks are preallocated. */
+ buf->st_blocks = 0;
+ else if (pfai->StandardInformation.AllocationSize.QuadPart > 0LL)
+ /* A successful NtQueryInformationFile returns the allocation size
+ correctly for compressed and sparse files as well.
+ Allocation size 0 is ignored here because (at least) Windows 10
+ 1607 always returns 0 for CompactOS compressed files. */
+ buf->st_blocks = (pfai->StandardInformation.AllocationSize.QuadPart
+ + S_BLKSIZE - 1) / S_BLKSIZE;
+ else if ((pfai->StandardInformation.AllocationSize.QuadPart == 0LL
+ || ::has_attribute (attributes, FILE_ATTRIBUTE_COMPRESSED
+ | FILE_ATTRIBUTE_SPARSE_FILE))
+ && h && !is_fs_special ()
+ && !NtQueryInformationFile (h, &st, (PVOID) &fci, sizeof fci,
+ FileCompressionInformation))
+ /* Otherwise we request the actual amount of bytes allocated for
+ compressed, sparsed and CompactOS files. */
+ buf->st_blocks = (fci.CompressedFileSize.QuadPart + S_BLKSIZE - 1)
+ / S_BLKSIZE;
+ else
+ /* Otherwise compute no. of blocks from file size. */
+ buf->st_blocks = (buf->st_size + S_BLKSIZE - 1) / S_BLKSIZE;
+
+ buf->st_mode = 0;
+ /* Using a side effect: get_file_attributes checks for directory.
+ This is used, to set S_ISVTX, if needed. */
+ if (pc.isdir ())
+ buf->st_mode = S_IFDIR;
+ else if (pc.issymlink ())
+ {
+ buf->st_size = pc.get_symlink_length ();
+ /* symlinks are everything for everyone! */
+ buf->st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
+ get_file_attribute (h, pc, NULL,
+ &buf->st_uid, &buf->st_gid);
+ goto done;
+ }
+ else if (pc.issocket ())
+ buf->st_mode = S_IFSOCK;
+
+ if (!get_file_attribute (h, pc, &buf->st_mode, &buf->st_uid, &buf->st_gid))
+ {
+ /* If read-only attribute is set, modify ntsec return value */
+ if (::has_attribute (attributes, FILE_ATTRIBUTE_READONLY)
+ && !pc.isdir () && !pc.issymlink () && !pc.is_fs_special ())
+ buf->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+
+ if (buf->st_mode & S_IFMT)
+ /* nothing */;
+ else if (!is_fs_special ())
+ buf->st_mode |= S_IFREG;
+ else
+ {
+ buf->st_dev = buf->st_rdev = dev ();
+ buf->st_mode = dev ().mode ();
+ buf->st_size = 0;
+ }
+ }
+ else
+ {
+ buf->st_mode |= STD_RBITS;
+
+ if (!::has_attribute (attributes, FILE_ATTRIBUTE_READONLY))
+ buf->st_mode |= STD_WBITS;
+ /* | S_IWGRP | S_IWOTH; we don't give write to group etc */
+
+ if (pc.isdir ())
+ buf->st_mode |= S_IFDIR | STD_WBITS | STD_XBITS;
+ else if (buf->st_mode & S_IFMT)
+ /* nothing */;
+ else if (is_fs_special ())
+ {
+ buf->st_dev = buf->st_rdev = dev ();
+ buf->st_mode = dev ().mode ();
+ buf->st_size = 0;
+ }
+ else
+ {
+ buf->st_mode |= S_IFREG;
+ /* Check suffix for executable file. */
+ if (pc.exec_state () != is_executable)
+ {
+ PUNICODE_STRING path = pc.get_nt_native_path ();
+
+ if (RtlEqualUnicodePathSuffix (path, &ro_u_exe, TRUE)
+ || RtlEqualUnicodePathSuffix (path, &ro_u_lnk, TRUE)
+ || RtlEqualUnicodePathSuffix (path, &ro_u_com, TRUE))
+ pc.set_exec ();
+ }
+ /* No known suffix, check file header. This catches binaries and
+ shebang scripts. */
+ if (pc.exec_state () == dont_know_if_executable)
+ {
+ OBJECT_ATTRIBUTES attr;
+ NTSTATUS status = 0;
+ IO_STATUS_BLOCK io;
+
+ /* We have to re-open the file. Either the file is not opened
+ for reading, or the read will change the file position of the
+ original handle. */
+ status = NtOpenFile (&h, SYNCHRONIZE | FILE_READ_DATA,
+ pc.init_reopen_attr (attr, h), &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_SYNCHRONOUS_IO_NONALERT);
+ if (!NT_SUCCESS (status))
+ debug_printf ("%y = NtOpenFile(%S)", status,
+ pc.get_nt_native_path ());
+ else
+ {
+ LARGE_INTEGER off = { QuadPart:0LL };
+ char magic[3];
+
+ status = NtReadFile (h, NULL, NULL, NULL,
+ &io, magic, 3, &off, NULL);
+ if (!NT_SUCCESS (status))
+ debug_printf ("%y = NtReadFile(%S)", status,
+ pc.get_nt_native_path ());
+ else if (has_exec_chars (magic, io.Information))
+ {
+ /* Heureka, it's an executable */
+ pc.set_exec ();
+ buf->st_mode |= STD_XBITS;
+ }
+ NtClose (h);
+ }
+ }
+ }
+ if (pc.exec_state () == is_executable)
+ buf->st_mode |= STD_XBITS;
+
+ /* This fakes the permissions of all files to match the current umask. */
+ buf->st_mode &= ~(cygheap->umask);
+ /* If the FS supports ACLs, we're here because we couldn't even open
+ the file for READ_CONTROL access. Chances are high that the file's
+ security descriptor has no ACE for "Everyone", so we should not fake
+ any access for "others". */
+ if (has_acls ())
+ buf->st_mode &= ~(S_IROTH | S_IWOTH | S_IXOTH);
+ }
+
+ done:
+ syscall_printf ("0 = fstat (%S, %p) st_size=%D, st_mode=0%o, st_ino=%D"
+ "st_atim=%lx.%lx st_ctim=%lx.%lx "
+ "st_mtim=%lx.%lx st_birthtim=%lx.%lx",
+ pc.get_nt_native_path (), buf,
+ buf->st_size, buf->st_mode, buf->st_ino,
+ buf->st_atim.tv_sec, buf->st_atim.tv_nsec,
+ buf->st_ctim.tv_sec, buf->st_ctim.tv_nsec,
+ buf->st_mtim.tv_sec, buf->st_mtim.tv_nsec,
+ buf->st_birthtim.tv_sec, buf->st_birthtim.tv_nsec);
+ return 0;
+}
+
+int
+fhandler_disk_file::fstat (struct stat *buf)
+{
+ return fstat_fs (buf);
+}
+
+int
+fhandler_disk_file::fstatvfs (struct statvfs *sfs)
+{
+ int ret = -1, opened = 0;
+ IO_STATUS_BLOCK io;
+ /* We must not use the stat handle here, even if it exists. The handle
+ has been opened with FILE_OPEN_REPARSE_POINT, thus, in case of a volume
+ mount point, it points to the FS of the mount point, rather than to the
+ mounted FS. */
+ HANDLE fh = get_handle ();
+
+ if (!fh)
+ {
+ OBJECT_ATTRIBUTES attr;
+ opened = NT_SUCCESS (NtOpenFile (&fh, READ_CONTROL,
+ pc.get_object_attr (attr, sec_none_nih),
+ &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT));
+ if (!opened)
+ {
+ /* Can't open file. Try again with parent dir. */
+ UNICODE_STRING dirname;
+ RtlSplitUnicodePath (pc.get_nt_native_path (), &dirname, NULL);
+ attr.ObjectName = &dirname;
+ opened = NT_SUCCESS (NtOpenFile (&fh, READ_CONTROL, &attr, &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT));
+ if (!opened)
+ goto out;
+ }
+ }
+
+ ret = fstatvfs_by_handle (fh, sfs);
+out:
+ if (opened)
+ NtClose (fh);
+ syscall_printf ("%d = fstatvfs(%s, %p)", ret, get_name (), sfs);
+ return ret;
+}
+
+int
+fhandler_base::fstatvfs_by_handle (HANDLE fh, struct statvfs *sfs)
+{
+ int ret = -1;
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ FILE_FS_FULL_SIZE_INFORMATION full_fsi;
+
+ sfs->f_files = ULONG_MAX;
+ sfs->f_ffree = ULONG_MAX;
+ sfs->f_favail = ULONG_MAX;
+ sfs->f_fsid = pc.fs_serial_number ();
+ sfs->f_flag = pc.fs_flags ();
+ sfs->f_namemax = pc.fs_name_len ();
+ /* Get allocation related information. */
+ status = NtQueryVolumeInformationFile (fh, &io, &full_fsi, sizeof full_fsi,
+ FileFsFullSizeInformation);
+ if (NT_SUCCESS (status))
+ {
+ sfs->f_bsize = full_fsi.BytesPerSector * full_fsi.SectorsPerAllocationUnit;
+ sfs->f_frsize = sfs->f_bsize;
+ sfs->f_blocks = (fsblkcnt_t) full_fsi.TotalAllocationUnits.QuadPart;
+ sfs->f_bfree = (fsblkcnt_t)
+ full_fsi.ActualAvailableAllocationUnits.QuadPart;
+ sfs->f_bavail = (fsblkcnt_t)
+ full_fsi.CallerAvailableAllocationUnits.QuadPart;
+ if (sfs->f_bfree > sfs->f_bavail)
+ {
+ /* Quotas active. We can't trust TotalAllocationUnits. */
+ NTFS_VOLUME_DATA_BUFFER nvdb;
+
+ status = NtFsControlFile (fh, NULL, NULL, NULL, &io,
+ FSCTL_GET_NTFS_VOLUME_DATA,
+ NULL, 0, &nvdb, sizeof nvdb);
+ if (!NT_SUCCESS (status))
+ debug_printf ("%y = NtFsControlFile(%S, FSCTL_GET_NTFS_VOLUME_DATA)",
+ status, pc.get_nt_native_path ());
+ else
+ sfs->f_blocks = (fsblkcnt_t) nvdb.TotalClusters.QuadPart;
+ }
+ ret = 0;
+ }
+ else if (status == STATUS_INVALID_PARAMETER /* Netapp */
+ || status == STATUS_INVALID_INFO_CLASS)
+ {
+ FILE_FS_SIZE_INFORMATION fsi;
+ status = NtQueryVolumeInformationFile (fh, &io, &fsi, sizeof fsi,
+ FileFsSizeInformation);
+ if (NT_SUCCESS (status))
+ {
+ sfs->f_bsize = fsi.BytesPerSector * fsi.SectorsPerAllocationUnit;
+ sfs->f_frsize = sfs->f_bsize;
+ sfs->f_blocks = (fsblkcnt_t) fsi.TotalAllocationUnits.QuadPart;
+ sfs->f_bfree = sfs->f_bavail =
+ (fsblkcnt_t) fsi.AvailableAllocationUnits.QuadPart;
+ ret = 0;
+ }
+ else
+ debug_printf ("%y = NtQueryVolumeInformationFile"
+ "(%S, FileFsSizeInformation)",
+ status, pc.get_nt_native_path ());
+ }
+ else
+ debug_printf ("%y = NtQueryVolumeInformationFile"
+ "(%S, FileFsFullSizeInformation)",
+ status, pc.get_nt_native_path ());
+ return ret;
+}
+
+int
+fhandler_disk_file::fchmod (mode_t mode)
+{
+ int ret = -1;
+ int oret = 0;
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+
+ if (pc.is_fs_special ())
+ return chmod_device (pc, mode);
+
+ if (!get_handle ())
+ {
+ query_open (query_write_dac);
+ if (!(oret = open (O_BINARY, 0)))
+ {
+ /* Need WRITE_DAC to write ACLs. */
+ if (pc.has_acls ())
+ return -1;
+ /* Otherwise FILE_WRITE_ATTRIBUTES is sufficient. */
+ query_open (query_write_attributes);
+ if (!(oret = open (O_BINARY, 0)))
+ return -1;
+ }
+ }
+
+ if (pc.fs_is_nfs ())
+ {
+ /* chmod on NFS shares works by writing an EA of type NfsV3Attributes.
+ Only type and mode have to be set. Apparently type isn't checked
+ for consistency, so it's sufficent to set it to NF3REG all the time. */
+ struct {
+ FILE_FULL_EA_INFORMATION ffei;
+ char buf[sizeof (NFS_V3_ATTR) + sizeof (fattr3)];
+ } ffei_buf;
+ ffei_buf.ffei.NextEntryOffset = 0;
+ ffei_buf.ffei.Flags = 0;
+ ffei_buf.ffei.EaNameLength = sizeof (NFS_V3_ATTR) - 1;
+ ffei_buf.ffei.EaValueLength = sizeof (fattr3);
+ strcpy (ffei_buf.ffei.EaName, NFS_V3_ATTR);
+ fattr3 *nfs_attr = (fattr3 *) (ffei_buf.ffei.EaName
+ + ffei_buf.ffei.EaNameLength + 1);
+ memset (nfs_attr, 0, sizeof (fattr3));
+ nfs_attr->type = NF3REG;
+ nfs_attr->mode = mode;
+ status = NtSetEaFile (get_handle (), &io,
+ &ffei_buf.ffei, sizeof ffei_buf);
+ if (!NT_SUCCESS (status))
+ __seterrno_from_nt_status (status);
+ else
+ ret = 0;
+ goto out;
+ }
+
+ if (pc.has_acls ())
+ {
+ security_descriptor sd, sd_ret;
+ uid_t uid;
+ gid_t gid;
+ tmp_pathbuf tp;
+ aclent_t *aclp;
+ bool standard_acl = false;
+ int nentries, idx;
+
+ if (!get_file_sd (get_handle (), pc, sd, false))
+ {
+ aclp = (aclent_t *) tp.c_get ();
+ if ((nentries = get_posix_access (sd, NULL, &uid, &gid,
+ aclp, MAX_ACL_ENTRIES,
+ &standard_acl)) >= 0)
+ {
+ /* Overwrite ACL permissions as required by POSIX 1003.1e
+ draft 17. */
+ aclp[0].a_perm = (mode >> 6) & S_IRWXO;
+
+ /* POSIXly correct: If CLASS_OBJ is present, chmod only modifies
+ CLASS_OBJ, not GROUP_OBJ.
+
+ Deliberate deviation from POSIX 1003.1e: If the ACL is a
+ "standard" ACL, that is, it only contains POSIX permissions
+ as well as entries for the Administrators group and SYSTEM,
+ then it's kind of a POSIX-only ACL in a twisted, Windowsy
+ way. If so, we change GROUP_OBJ and CLASS_OBJ perms. */
+ if (standard_acl
+ && (idx = searchace (aclp, nentries, GROUP_OBJ)) >= 0)
+ aclp[idx].a_perm = (mode >> 3) & S_IRWXO;
+ if (nentries > MIN_ACL_ENTRIES
+ && (idx = searchace (aclp, nentries, CLASS_OBJ)) >= 0)
+ aclp[idx].a_perm = (mode >> 3) & S_IRWXO;
+
+ if ((idx = searchace (aclp, nentries, OTHER_OBJ)) >= 0)
+ aclp[idx].a_perm = mode & S_IRWXO;
+ if (pc.isdir ())
+ mode |= S_IFDIR;
+ if (set_posix_access (mode, uid, gid, aclp, nentries, sd_ret,
+ pc.fs_is_samba ()))
+ ret = set_file_sd (get_handle (), pc, sd_ret, false);
+ }
+ }
+ }
+
+ /* If the mode has any write bits set, the DOS R/O flag is in the way. */
+ if (mode & (S_IWUSR | S_IWGRP | S_IWOTH))
+ pc &= (DWORD) ~FILE_ATTRIBUTE_READONLY;
+ else if (!pc.has_acls ()) /* Never set DOS R/O if security is used. */
+ pc |= (DWORD) FILE_ATTRIBUTE_READONLY;
+ if (S_ISSOCK (mode))
+ pc |= (DWORD) FILE_ATTRIBUTE_SYSTEM;
+
+ status = NtSetAttributesFile (get_handle (), pc.file_attributes ());
+ /* MVFS needs a good amount of kicking to be convinced that it has to write
+ back metadata changes and to invalidate the cached metadata information
+ stored for the given handle. This method to open a second handle to
+ the file and write the same metadata information twice has been found
+ experimentally: http://cygwin.com/ml/cygwin/2009-07/msg00533.html */
+ if (pc.fs_is_mvfs () && NT_SUCCESS (status) && !oret)
+ {
+ OBJECT_ATTRIBUTES attr;
+ HANDLE fh;
+
+ if (NT_SUCCESS (NtOpenFile (&fh, FILE_WRITE_ATTRIBUTES,
+ pc.init_reopen_attr (attr, get_handle ()),
+ &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT)))
+ {
+ NtSetAttributesFile (fh, pc.file_attributes ());
+ NtClose (fh);
+ }
+ }
+ /* Correct NTFS security attributes have higher priority */
+ if (!pc.has_acls ())
+ {
+ if (!NT_SUCCESS (status))
+ __seterrno_from_nt_status (status);
+ else
+ ret = 0;
+ }
+
+out:
+ if (oret)
+ close_fs ();
+
+ return ret;
+}
+
+int
+fhandler_disk_file::fchown (uid_t uid, gid_t gid)
+{
+ int oret = 0;
+ int ret = -1;
+ security_descriptor sd, sd_ret;
+ mode_t attr = pc.isdir () ? S_IFDIR : 0;
+ uid_t old_uid;
+ gid_t old_gid;
+ tmp_pathbuf tp;
+ aclent_t *aclp;
+ int nentries;
+
+ if (!pc.has_acls ())
+ {
+ /* fake - if not supported, pretend we're like win95
+ where it just works */
+ /* FIXME: Could be supported on NFS when user->uid mapping is in place. */
+ return 0;
+ }
+
+ if (!get_handle ())
+ {
+ query_open (query_write_control);
+ if (!(oret = fhandler_disk_file::open (O_BINARY, 0)))
+ return -1;
+ }
+
+ if (get_file_sd (get_handle (), pc, sd, false))
+ goto out;
+
+ aclp = (aclent_t *) tp.c_get ();
+ if ((nentries = get_posix_access (sd, &attr, &old_uid, &old_gid,
+ aclp, MAX_ACL_ENTRIES)) < 0)
+ goto out;
+
+ /* According to POSIX, chown can be a no-op if uid is (uid_t)-1 and
+ gid is (gid_t)-1. Otherwise, even if uid and gid are unchanged,
+ we must ensure that ctime is updated. */
+ if (uid == ILLEGAL_UID && gid == ILLEGAL_GID)
+ {
+ ret = 0;
+ goto out;
+ }
+ if (uid == ILLEGAL_UID)
+ uid = old_uid;
+ else if (gid == ILLEGAL_GID)
+ gid = old_gid;
+
+ /* Windows ACLs can contain permissions for one group, while being owned by
+ another user/group. The permission bits returned above are pretty much
+ useless then. Creating a new ACL with these useless permissions results
+ in a potentially broken symlink. So what we do here is to set the
+ underlying permissions of symlinks to a sensible value which allows the
+ world to read the symlink and only the new owner to change it. */
+ if (pc.issymlink ())
+ for (int idx = 0; idx < nentries; ++idx)
+ {
+ aclp[idx].a_perm |= S_IROTH;
+ if (aclp[idx].a_type & USER_OBJ)
+ aclp[idx].a_perm |= S_IWOTH;
+ }
+
+ if (set_posix_access (attr, uid, gid, aclp, nentries, sd_ret,
+ pc.fs_is_samba ()))
+ ret = set_file_sd (get_handle (), pc, sd_ret, true);
+
+ /* If you're running a Samba server with no winbind, the uid<->SID mapping
+ is disfunctional. Even trying to chown to your own account fails since
+ the account used on the server is the UNIX account which gets used for
+ the standard user mapping. This is a default mechanism which doesn't
+ know your real Windows SID. There are two possible error codes in
+ different Samba releases for this situation, one of them unfortunately
+ the not very significant STATUS_ACCESS_DENIED. Instead of relying on
+ the error codes, we're using the below very simple heuristic.
+ If set_file_sd failed, and the original user account was either already
+ unknown, or one of the standard UNIX accounts, we're faking success. */
+ if (ret == -1 && pc.fs_is_samba ())
+ {
+ PSID sid;
+
+ if (uid == old_uid
+ || ((sid = sidfromuid (old_uid, NULL)) != NO_SID
+ && RtlEqualPrefixSid (sid,
+ well_known_samba_unix_user_fake_sid)))
+ {
+ debug_printf ("Faking chown worked on standalone Samba");
+ ret = 0;
+ }
+ }
+
+out:
+ if (oret)
+ close_fs ();
+
+ return ret;
+}
+
+int
+fhandler_disk_file::facl (int cmd, int nentries, aclent_t *aclbufp)
+{
+ int res = -1;
+ int oret = 0;
+
+ if (!pc.has_acls ())
+ {
+cant_access_acl:
+ switch (cmd)
+ {
+
+ case SETACL:
+ /* Open for writing required to be able to set ctime
+ (even though setting the ACL is just pretended). */
+ if (!get_handle ())
+ oret = open (O_WRONLY | O_BINARY, 0);
+ res = 0;
+ break;
+ case GETACL:
+ if (!aclbufp)
+ set_errno (EFAULT);
+ else if (nentries < MIN_ACL_ENTRIES)
+ set_errno (ENOSPC);
+ else
+ {
+ struct stat st;
+ if (!fstat (&st))
+ {
+ aclbufp[0].a_type = USER_OBJ;
+ aclbufp[0].a_id = st.st_uid;
+ aclbufp[0].a_perm = (st.st_mode & S_IRWXU) >> 6;
+ aclbufp[1].a_type = GROUP_OBJ;
+ aclbufp[1].a_id = st.st_gid;
+ aclbufp[1].a_perm = (st.st_mode & S_IRWXG) >> 3;
+ aclbufp[2].a_type = OTHER_OBJ;
+ aclbufp[2].a_id = ILLEGAL_GID;
+ aclbufp[2].a_perm = st.st_mode & S_IRWXO;
+ res = MIN_ACL_ENTRIES;
+ }
+ }
+ break;
+ case GETACLCNT:
+ res = MIN_ACL_ENTRIES;
+ break;
+ default:
+ set_errno (EINVAL);
+ break;
+ }
+ }
+ else
+ {
+ if ((cmd == SETACL && !get_handle ())
+ || (cmd != SETACL && !get_stat_handle ()))
+ {
+ query_open (cmd == SETACL ? query_write_dac : query_read_control);
+ if (!(oret = open (O_BINARY, 0)))
+ {
+ if (cmd == GETACL || cmd == GETACLCNT)
+ goto cant_access_acl;
+ return -1;
+ }
+ }
+ switch (cmd)
+ {
+ case SETACL:
+ if (!aclsort (nentries, 0, aclbufp))
+ {
+ bool rw = false;
+ res = setacl (get_handle (), pc, nentries, aclbufp, rw);
+ if (rw)
+ {
+ IO_STATUS_BLOCK io;
+ FILE_BASIC_INFORMATION fbi;
+ fbi.CreationTime.QuadPart
+ = fbi.LastAccessTime.QuadPart
+ = fbi.LastWriteTime.QuadPart
+ = fbi.ChangeTime.QuadPart = 0LL;
+ fbi.FileAttributes = (pc.file_attributes ()
+ & ~FILE_ATTRIBUTE_READONLY)
+ ?: FILE_ATTRIBUTE_NORMAL;
+ NtSetInformationFile (get_handle (), &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ }
+ }
+ break;
+ case GETACL:
+ if (!aclbufp)
+ set_errno(EFAULT);
+ else {
+ res = getacl (get_stat_handle (), pc, nentries, aclbufp);
+ /* For this ENOSYS case, see security.cc:get_file_attribute(). */
+ if (res == -1 && get_errno () == ENOSYS)
+ goto cant_access_acl;
+ }
+ break;
+ case GETACLCNT:
+ res = getacl (get_stat_handle (), pc, 0, NULL);
+ /* Ditto. */
+ if (res == -1 && get_errno () == ENOSYS)
+ goto cant_access_acl;
+ break;
+ default:
+ set_errno (EINVAL);
+ break;
+ }
+ }
+
+ if (oret)
+ close_fs ();
+
+ return res;
+}
+
+ssize_t
+fhandler_disk_file::fgetxattr (const char *name, void *value, size_t size)
+{
+ if (pc.is_fs_special ())
+ {
+ set_errno (ENOTSUP);
+ return -1;
+ }
+ return read_ea (get_handle (), pc, name, (char *) value, size);
+}
+
+int
+fhandler_disk_file::fsetxattr (const char *name, const void *value, size_t size,
+ int flags)
+{
+ if (pc.is_fs_special ())
+ {
+ set_errno (ENOTSUP);
+ return -1;
+ }
+ return write_ea (get_handle (), pc, name, (const char *) value, size, flags);
+}
+
+int
+fhandler_disk_file::fadvise (off_t offset, off_t length, int advice)
+{
+ if (advice < POSIX_FADV_NORMAL || advice > POSIX_FADV_NOREUSE)
+ return EINVAL;
+
+ /* Windows only supports advice flags for the whole file. We're using
+ a simplified test here so that we don't have to ask for the actual
+ file size. Length == 0 means all bytes starting at offset anyway.
+ So we only actually follow the advice, if it's given for offset == 0. */
+ if (offset != 0)
+ return 0;
+
+ /* We only support normal and sequential mode for now. Everything which
+ is not POSIX_FADV_SEQUENTIAL is treated like POSIX_FADV_NORMAL. */
+ if (advice != POSIX_FADV_SEQUENTIAL)
+ advice = POSIX_FADV_NORMAL;
+
+ IO_STATUS_BLOCK io;
+ FILE_MODE_INFORMATION fmi;
+ NTSTATUS status = NtQueryInformationFile (get_handle (), &io,
+ &fmi, sizeof fmi,
+ FileModeInformation);
+ if (NT_SUCCESS (status))
+ {
+ fmi.Mode &= ~FILE_SEQUENTIAL_ONLY;
+ if (advice == POSIX_FADV_SEQUENTIAL)
+ fmi.Mode |= FILE_SEQUENTIAL_ONLY;
+ status = NtSetInformationFile (get_handle (), &io, &fmi, sizeof fmi,
+ FileModeInformation);
+ if (NT_SUCCESS (status))
+ return 0;
+ __seterrno_from_nt_status (status);
+ }
+
+ return geterrno_from_nt_status (status);
+}
+
+int
+fhandler_disk_file::ftruncate (off_t length, bool allow_truncate)
+{
+ int res = 0;
+
+ if (length < 0 || !get_handle ())
+ res = EINVAL;
+ else if (pc.isdir ())
+ res = EISDIR;
+ else if (!(get_access () & GENERIC_WRITE))
+ res = EBADF;
+ else
+ {
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ FILE_STANDARD_INFORMATION fsi;
+ FILE_END_OF_FILE_INFORMATION feofi;
+
+ status = NtQueryInformationFile (get_handle (), &io, &fsi, sizeof fsi,
+ FileStandardInformation);
+ if (!NT_SUCCESS (status))
+ return geterrno_from_nt_status (status);
+
+ /* If called through posix_fallocate, silently succeed if length
+ is less than the file's actual length. */
+ if (!allow_truncate && length < fsi.EndOfFile.QuadPart)
+ return 0;
+
+ feofi.EndOfFile.QuadPart = length;
+ /* Create sparse files only when called through ftruncate, not when
+ called through posix_fallocate. */
+ if (allow_truncate && pc.support_sparse ()
+ && !has_attribute (FILE_ATTRIBUTE_SPARSE_FILE)
+ && length >= fsi.EndOfFile.QuadPart + (128 * 1024))
+ {
+ status = NtFsControlFile (get_handle (), NULL, NULL, NULL, &io,
+ FSCTL_SET_SPARSE, NULL, 0, NULL, 0);
+ if (NT_SUCCESS (status))
+ pc.file_attributes (pc.file_attributes ()
+ | FILE_ATTRIBUTE_SPARSE_FILE);
+ syscall_printf ("%y = NtFsControlFile(%S, FSCTL_SET_SPARSE)",
+ status, pc.get_nt_native_path ());
+ }
+ status = NtSetInformationFile (get_handle (), &io,
+ &feofi, sizeof feofi,
+ FileEndOfFileInformation);
+ if (!NT_SUCCESS (status))
+ res = geterrno_from_nt_status (status);
+ }
+ return res;
+}
+
+int
+fhandler_disk_file::link (const char *newpath)
+{
+ size_t nlen = strlen (newpath);
+ path_conv newpc (newpath, PC_SYM_NOFOLLOW | PC_POSIX | PC_NULLEMPTY, stat_suffixes);
+ if (newpc.error)
+ {
+ set_errno (newpc.error);
+ return -1;
+ }
+
+ if (newpc.exists ())
+ {
+ syscall_printf ("file '%S' exists?", newpc.get_nt_native_path ());
+ set_errno (EEXIST);
+ return -1;
+ }
+
+ if (isdirsep (newpath[nlen - 1]) || has_dot_last_component (newpath, false))
+ {
+ set_errno (ENOENT);
+ return -1;
+ }
+
+ char new_buf[nlen + 5];
+ if (!newpc.error)
+ {
+ /* If the original file is a lnk special file,
+ and if the original file has a .lnk suffix, add one to the hardlink
+ as well. */
+ if (pc.is_lnk_special ()
+ && RtlEqualUnicodePathSuffix (pc.get_nt_native_path (),
+ &ro_u_lnk, TRUE))
+ {
+ /* Shortcut hack. */
+ stpcpy (stpcpy (new_buf, newpath), ".lnk");
+ newpath = new_buf;
+ newpc.check (newpath, PC_SYM_NOFOLLOW);
+ }
+ else if (!pc.isdir ()
+ && pc.is_binary ()
+ && RtlEqualUnicodePathSuffix (pc.get_nt_native_path (),
+ &ro_u_exe, TRUE)
+ && !RtlEqualUnicodePathSuffix (newpc.get_nt_native_path (),
+ &ro_u_exe, TRUE))
+ {
+ /* Executable hack. */
+ stpcpy (stpcpy (new_buf, newpath), ".exe");
+ newpath = new_buf;
+ newpc.check (newpath, PC_SYM_NOFOLLOW);
+ }
+ }
+
+ /* We only need READ_CONTROL access so the handle returned in pc is
+ sufficient. And if the file couldn't be opened with READ_CONTROL
+ access in path_conv, we won't be able to do it here anyway. */
+ HANDLE fh = get_stat_handle ();
+ if (!fh)
+ {
+ set_errno (EACCES);
+ return -1;
+ }
+ PUNICODE_STRING tgt = newpc.get_nt_native_path ();
+ ULONG size = sizeof (FILE_LINK_INFORMATION) + tgt->Length;
+ PFILE_LINK_INFORMATION pfli = (PFILE_LINK_INFORMATION) alloca (size);
+ pfli->ReplaceIfExists = FALSE;
+ pfli->RootDirectory = NULL;
+ memcpy (pfli->FileName, tgt->Buffer, pfli->FileNameLength = tgt->Length);
+
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ status = NtSetInformationFile (fh, &io, pfli, size, FileLinkInformation);
+ if (!NT_SUCCESS (status))
+ {
+ if (status == STATUS_INVALID_DEVICE_REQUEST
+ || status == STATUS_NOT_SUPPORTED)
+ /* FS doesn't support hard links. Linux returns EPERM. */
+ set_errno (EPERM);
+ else
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ else if ((pc.file_attributes () & O_TMPFILE_FILE_ATTRS)
+ == O_TMPFILE_FILE_ATTRS)
+ {
+ /* An O_TMPFILE file has FILE_ATTRIBUTE_TEMPORARY and
+ FILE_ATTRIBUTE_HIDDEN set. After a successful hardlink the file is
+ not temporary anymore in the usual sense. So we remove these
+ attributes here.
+
+ Note that we don't create a reopen attribute for the original
+ link but rather a normal attribute for the just created link.
+ The reason is a curious behaviour of Windows: If we remove the
+ O_TMPFILE attributes on the original link, the new link will not
+ show up in file system listings (not even native ones from , e.g.,
+ `cmd /c dir'), long after the original link has been closed and
+ removed. The file and its metadata will be kept in memory only
+ as long as Windows sees fit. By opening the new link, we request
+ the attribute changes on the new link, so after closing it Windows
+ will have an increased interest to write back the metadata. */
+ OBJECT_ATTRIBUTES attr;
+ status = NtOpenFile (&fh, FILE_WRITE_ATTRIBUTES,
+ newpc.get_object_attr (attr, sec_none_nih), &io,
+ FILE_SHARE_VALID_FLAGS, FILE_OPEN_FOR_BACKUP_INTENT);
+ if (!NT_SUCCESS (status))
+ debug_printf ("Opening for removing TEMPORARY attrib failed, "
+ "status = %y", status);
+ else
+ {
+ FILE_BASIC_INFORMATION fbi;
+
+ fbi.CreationTime.QuadPart = fbi.LastAccessTime.QuadPart
+ = fbi.LastWriteTime.QuadPart = fbi.ChangeTime.QuadPart = 0LL;
+ fbi.FileAttributes = (pc.file_attributes () & ~O_TMPFILE_FILE_ATTRS)
+ ?: FILE_ATTRIBUTE_NORMAL;
+ status = NtSetInformationFile (fh, &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ if (!NT_SUCCESS (status))
+ debug_printf ("Removing the TEMPORARY attrib failed, status = %y",
+ status);
+ NtClose (fh);
+ }
+ }
+ return 0;
+}
+
+int
+fhandler_disk_file::utimens (const struct timespec *tvp)
+{
+ return utimens_fs (tvp);
+}
+
+int
+fhandler_base::utimens_fs (const struct timespec *tvp)
+{
+ struct timespec timeofday;
+ struct timespec tmp[2];
+ bool closeit = false;
+
+ if (!get_handle ())
+ {
+ query_open (query_write_attributes);
+ if (!open_fs (O_BINARY, 0))
+ {
+ /* It's documented in MSDN that FILE_WRITE_ATTRIBUTES is sufficient
+ to change the timestamps. Unfortunately it's not sufficient for a
+ remote HPFS which requires GENERIC_WRITE, so we just retry to open
+ for writing, though this fails for R/O files of course. */
+ query_open (no_query);
+ if (!open_fs (O_WRONLY | O_BINARY, 0))
+ {
+ syscall_printf ("Opening file failed");
+ return -1;
+ }
+ }
+ closeit = true;
+ }
+
+ clock_gettime (CLOCK_REALTIME, &timeofday);
+ if (!tvp)
+ tmp[1] = tmp[0] = timeofday;
+ else
+ {
+ if ((tvp[0].tv_nsec < UTIME_NOW || tvp[0].tv_nsec >= NSPERSEC)
+ || (tvp[1].tv_nsec < UTIME_NOW || tvp[1].tv_nsec >= NSPERSEC))
+ {
+ if (closeit)
+ close_fs ();
+ set_errno (EINVAL);
+ return -1;
+ }
+ tmp[0] = (tvp[0].tv_nsec == UTIME_NOW) ? timeofday : tvp[0];
+ tmp[1] = (tvp[1].tv_nsec == UTIME_NOW) ? timeofday : tvp[1];
+ }
+ debug_printf ("incoming lastaccess %ly %ly", tmp[0].tv_sec, tmp[0].tv_nsec);
+
+ IO_STATUS_BLOCK io;
+ FILE_BASIC_INFORMATION fbi;
+
+ fbi.CreationTime.QuadPart = 0LL;
+ /* UTIME_OMIT is handled in timespec_to_filetime by setting FILETIME to 0. */
+ timespec_to_filetime (&tmp[0], &fbi.LastAccessTime);
+ timespec_to_filetime (&tmp[1], &fbi.LastWriteTime);
+ fbi.ChangeTime.QuadPart = 0LL;
+ fbi.FileAttributes = 0;
+ NTSTATUS status = NtSetInformationFile (get_handle (), &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ /* For this special case for MVFS see the comment in
+ fhandler_disk_file::fchmod. */
+ if (pc.fs_is_mvfs () && NT_SUCCESS (status) && !closeit)
+ {
+ OBJECT_ATTRIBUTES attr;
+ HANDLE fh;
+
+ if (NT_SUCCESS (NtOpenFile (&fh, FILE_WRITE_ATTRIBUTES,
+ pc.init_reopen_attr (attr, get_handle ()),
+ &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT)))
+ {
+ NtSetInformationFile (fh, &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ NtClose (fh);
+ }
+ }
+ if (closeit)
+ close_fs ();
+ /* Opening a directory on a 9x share from a NT machine works(!), but
+ then NtSetInformationFile fails with STATUS_NOT_SUPPORTED. Oh well... */
+ if (!NT_SUCCESS (status) && status != STATUS_NOT_SUPPORTED)
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ return 0;
+}
+
+fhandler_disk_file::fhandler_disk_file () :
+ fhandler_base (), prw_handle (NULL)
+{
+}
+
+fhandler_disk_file::fhandler_disk_file (path_conv &pc) :
+ fhandler_base (), prw_handle (NULL)
+{
+ set_name (pc);
+}
+
+int
+fhandler_disk_file::open (int flags, mode_t mode)
+{
+ return open_fs (flags, mode);
+}
+
+int
+fhandler_disk_file::close ()
+{
+ /* Close extra pread/pwrite handle, if it exists. */
+ if (prw_handle)
+ NtClose (prw_handle);
+ return fhandler_base::close ();
+}
+
+int
+fhandler_disk_file::fcntl (int cmd, intptr_t arg)
+{
+ int res;
+
+ switch (cmd)
+ {
+ case F_LCK_MANDATORY: /* Mandatory locking only works on files. */
+ mandatory_locking (!!arg);
+ need_fork_fixup (true);
+ res = 0;
+ break;
+ default:
+ res = fhandler_base::fcntl (cmd, arg);
+ break;
+ }
+ return res;
+}
+
+int
+fhandler_disk_file::dup (fhandler_base *child, int flags)
+{
+ fhandler_disk_file *fhc = (fhandler_disk_file *) child;
+
+ int ret = fhandler_base::dup (child, flags);
+ if (!ret && prw_handle
+ && !DuplicateHandle (GetCurrentProcess (), prw_handle,
+ GetCurrentProcess (), &fhc->prw_handle,
+ 0, FALSE, DUPLICATE_SAME_ACCESS))
+ fhc->prw_handle = NULL;
+ return ret;
+}
+
+void
+fhandler_disk_file::fixup_after_fork (HANDLE parent)
+{
+ prw_handle = NULL;
+ mandatory_locking (false);
+ fhandler_base::fixup_after_fork (parent);
+}
+
+int
+fhandler_base::open_fs (int flags, mode_t mode)
+{
+ /* Unfortunately NT allows to open directories for writing, but that's
+ disallowed according to SUSv3. */
+ if (pc.isdir () && (flags & O_ACCMODE) != O_RDONLY)
+ {
+ set_errno (EISDIR);
+ return 0;
+ }
+
+ bool new_file = !exists ();
+
+ int res = fhandler_base::open (flags, mode);
+ if (res)
+ {
+ /* The file info in pc is wrong at this point for newly created files.
+ Refresh it before fetching any file info. */
+ if (new_file)
+ pc.get_finfo (get_handle ());
+
+ if (pc.isgood_inode (pc.get_ino ()))
+ ino = pc.get_ino ();
+ }
+
+ syscall_printf ("%d = fhandler_disk_file::open(%S, %y)", res,
+ pc.get_nt_native_path (), flags);
+ return res;
+}
+
+/* POSIX demands that pread/pwrite don't change the current file position.
+ While NtReadFile/NtWriteFile support atomic seek-and-io, both change the
+ file pointer if the file handle has been opened for synchonous I/O.
+ Using this handle for pread/pwrite would break atomicity, because the
+ read/write operation would have to be followed by a seek back to the old
+ file position. What we do is to open another handle to the file on the
+ first call to either pread or pwrite. This is used for any subsequent
+ pread/pwrite. Thus the current file position of the "normal" file
+ handle is not touched.
+
+ FIXME:
+
+ Note that this is just a hack. The problem with this approach is that
+ a change to the file permissions might disallow to open the file with
+ the required permissions to read or write. This appears to be a border case,
+ but that's exactly what git does. It creates the file for reading and
+ writing and after writing it, it chmods the file to read-only. Then it
+ calls pread on the file to examine the content. This works, but if git
+ would use the original handle to pwrite to the file, it would be broken
+ with our approach.
+
+ One way to implement this is to open the pread/pwrite handle right at
+ file open time. We would simply maintain two handles, which wouldn't
+ be much of a problem given how we do that for other fhandler types as
+ well.
+
+ However, ultimately fhandler_disk_file should become a derived class of
+ fhandler_base_overlapped. Each raw_read or raw_write would fetch the
+ actual file position, read/write from there, and then set the file
+ position again. Fortunately, while the file position is not maintained
+ by the I/O manager, it can be fetched and set to a new value by all
+ processes holding a handle to that file object. Pread and pwrite differ
+ from raw_read and raw_write just by not touching the current file pos.
+ Actually they could be merged with raw_read/raw_write if we add a position
+ parameter to the latter. */
+
+int
+fhandler_disk_file::prw_open (bool write, void *aio)
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ OBJECT_ATTRIBUTES attr;
+ ULONG options = get_options ();
+
+ /* If async i/o is intended, turn off the default synchronous operation */
+ if (aio)
+ options &= ~FILE_SYNCHRONOUS_IO_NONALERT;
+
+ /* First try to open with the original access mask */
+ ACCESS_MASK access = get_access ();
+ status = NtOpenFile (&prw_handle, access,
+ pc.init_reopen_attr (attr, get_handle ()), &io,
+ FILE_SHARE_VALID_FLAGS, options);
+ if (status == STATUS_ACCESS_DENIED)
+ {
+ /* If we get an access denied, chmod has been called. Try again
+ with just the required rights to perform the called function. */
+ access &= write ? ~GENERIC_READ : ~GENERIC_WRITE;
+ status = NtOpenFile (&prw_handle, access, &attr, &io,
+ FILE_SHARE_VALID_FLAGS, options);
+ }
+ debug_printf ("%y = NtOpenFile (%p, %y, %S, io, %y, %y)",
+ status, prw_handle, access, pc.get_nt_native_path (),
+ FILE_SHARE_VALID_FLAGS, options);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+
+ /* record prw_handle's asyncness for subsequent pread/pwrite operations */
+ prw_handle_isasync = !!aio;
+ return 0;
+}
+
+ssize_t
+fhandler_disk_file::pread (void *buf, size_t count, off_t offset, void *aio)
+{
+ struct aiocb *aiocb = (struct aiocb *) aio;
+ ssize_t res;
+
+ if ((get_flags () & O_ACCMODE) == O_WRONLY)
+ {
+ set_errno (EBADF);
+ return -1;
+ }
+
+ /* In binary mode, we can use an atomic NtReadFile call.
+ Windows mandatory locking semantics disallow to use another HANDLE. */
+ if (rbinary () && !mandatory_locking ())
+ {
+ extern int is_at_eof (HANDLE h);
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ LARGE_INTEGER off = { QuadPart:offset };
+ HANDLE evt = aio ? (HANDLE) aiocb->aio_wincb.event : NULL;
+ PIO_STATUS_BLOCK pio = aio ? (PIO_STATUS_BLOCK) &aiocb->aio_wincb : &io;
+
+ /* If existing prw_handle asyncness doesn't match this call's, re-open */
+ if (prw_handle && (prw_handle_isasync != !!aio))
+ NtClose (prw_handle), prw_handle = NULL;
+
+ if (!prw_handle && prw_open (false, aio))
+ goto non_atomic;
+ status = NtReadFile (prw_handle, evt, NULL, NULL, pio, buf, count,
+ &off, NULL);
+ if (status == STATUS_END_OF_FILE)
+ res = 0;
+ else if (!NT_SUCCESS (status))
+ {
+ if (pc.isdir ())
+ {
+ set_errno (EISDIR);
+ return -1;
+ }
+ if (status == (NTSTATUS) STATUS_ACCESS_VIOLATION)
+ {
+ if (is_at_eof (prw_handle))
+ {
+ res = 0;
+ goto out;
+ }
+ switch (mmap_is_attached_or_noreserve (buf, count))
+ {
+ case MMAP_NORESERVE_COMMITED:
+ status = NtReadFile (prw_handle, evt, NULL, NULL, pio,
+ buf, count, &off, NULL);
+ if (NT_SUCCESS (status))
+ {
+ res = aio ? (ssize_t) aiocb->aio_wincb.info
+ : io.Information;
+ goto out;
+ }
+ break;
+ case MMAP_RAISE_SIGBUS:
+ raise (SIGBUS);
+ default:
+ break;
+ }
+ }
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ else
+ {
+ res = aio ? (ssize_t) aiocb->aio_wincb.info : io.Information;
+ goto out;
+ }
+ }
+ else
+ {
+non_atomic:
+ /* Text mode stays slow and non-atomic. */
+ off_t curpos = lseek (0, SEEK_CUR);
+ if (curpos < 0 || lseek (offset, SEEK_SET) < 0)
+ res = -1;
+ else
+ {
+ size_t tmp_count = count;
+ read (buf, tmp_count);
+ if (lseek (curpos, SEEK_SET) >= 0)
+ res = (ssize_t) tmp_count;
+ else
+ res = -1;
+ }
+
+ /* If this was a disallowed async request, simulate its conclusion */
+ if (aio)
+ {
+ aiocb->aio_rbytes = res;
+ aiocb->aio_errno = res == -1 ? get_errno () : 0;
+ SetEvent ((HANDLE) aiocb->aio_wincb.event);
+ }
+ }
+out:
+ debug_printf ("%d = pread(%p, %ld, %D, %p)\n", res, buf, count, offset, aio);
+ return res;
+}
+
+ssize_t
+fhandler_disk_file::pwrite (void *buf, size_t count, off_t offset, void *aio)
+{
+ struct aiocb *aiocb = (struct aiocb *) aio;
+ ssize_t res;
+
+ if ((get_flags () & O_ACCMODE) == O_RDONLY)
+ {
+ set_errno (EBADF);
+ return -1;
+ }
+
+ /* In binary mode, we can use an atomic NtWriteFile call.
+ Windows mandatory locking semantics disallow to use another HANDLE. */
+ if (wbinary () && !mandatory_locking ())
+ {
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ LARGE_INTEGER off = { QuadPart:offset };
+ HANDLE evt = aio ? (HANDLE) aiocb->aio_wincb.event : NULL;
+ PIO_STATUS_BLOCK pio = aio ? (PIO_STATUS_BLOCK) &aiocb->aio_wincb : &io;
+
+ /* If existing prw_handle asyncness doesn't match this call's, re-open */
+ if (prw_handle && (prw_handle_isasync != !!aio))
+ NtClose (prw_handle), prw_handle = NULL;
+
+ if (!prw_handle && prw_open (true, aio))
+ goto non_atomic;
+ status = NtWriteFile (prw_handle, evt, NULL, NULL, pio, buf, count,
+ &off, NULL);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ res = aio ? (ssize_t) aiocb->aio_wincb.info : io.Information;
+ goto out;
+ }
+ else
+ {
+non_atomic:
+ /* Text mode stays slow and non-atomic. */
+ off_t curpos = lseek (0, SEEK_CUR);
+ if (curpos < 0 || lseek (offset, SEEK_SET) < 0)
+ res = curpos;
+ else
+ {
+ res = (ssize_t) write (buf, count);
+ if (lseek (curpos, SEEK_SET) < 0)
+ res = -1;
+ }
+
+ /* If this was a disallowed async request, simulate its conclusion */
+ if (aio)
+ {
+ aiocb->aio_rbytes = res;
+ aiocb->aio_errno = res == -1 ? get_errno () : 0;
+ SetEvent ((HANDLE) aiocb->aio_wincb.event);
+ }
+ }
+out:
+ debug_printf ("%d = pwrite(%p, %ld, %D, %p)\n", res, buf, count, offset, aio);
+ return res;
+}
+
+int
+fhandler_disk_file::mkdir (mode_t mode)
+{
+ int res = -1;
+ SECURITY_ATTRIBUTES sa = sec_none_nih;
+ NTSTATUS status;
+ HANDLE dir;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ PFILE_FULL_EA_INFORMATION p = NULL;
+ ULONG plen = 0;
+ ULONG access = FILE_LIST_DIRECTORY | SYNCHRONIZE;
+
+ if (pc.fs_is_nfs ())
+ {
+ /* When creating a dir on an NFS share, we have to set the
+ file mode by writing a NFS fattr3 structure with the
+ correct mode bits set. */
+ plen = sizeof (FILE_FULL_EA_INFORMATION) + sizeof (NFS_V3_ATTR)
+ + sizeof (fattr3);
+ p = (PFILE_FULL_EA_INFORMATION) alloca (plen);
+ p->NextEntryOffset = 0;
+ p->Flags = 0;
+ p->EaNameLength = sizeof (NFS_V3_ATTR) - 1;
+ p->EaValueLength = sizeof (fattr3);
+ strcpy (p->EaName, NFS_V3_ATTR);
+ fattr3 *nfs_attr = (fattr3 *) (p->EaName + p->EaNameLength + 1);
+ memset (nfs_attr, 0, sizeof (fattr3));
+ nfs_attr->type = NF3DIR;
+ nfs_attr->mode = (mode & 07777) & ~cygheap->umask;
+ }
+ else if (has_acls () && !isremote ())
+ /* If the filesystem supports ACLs, we will overwrite the DACL after the
+ call to NtCreateFile. This requires a handle with READ_CONTROL and
+ WRITE_DAC access, otherwise get_file_sd and set_file_sd both have to
+ open the file again.
+ FIXME: On remote NTFS shares open sometimes fails because even the
+ creator of the file doesn't have the right to change the DACL.
+ I don't know what setting that is or how to recognize such a share,
+ so for now we don't request WRITE_DAC on remote drives. */
+ access |= READ_CONTROL | WRITE_DAC;
+ /* Setting case sensitivity requires FILE_WRITE_ATTRIBUTES. */
+ if (wincap.has_case_sensitive_dirs ()
+ && !pc.isremote () && pc.fs_is_ntfs ())
+ access |= FILE_WRITE_ATTRIBUTES;
+ status = NtCreateFile (&dir, access, pc.get_object_attr (attr, sa), &io, NULL,
+ FILE_ATTRIBUTE_DIRECTORY, FILE_SHARE_VALID_FLAGS,
+ FILE_CREATE,
+ FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
+ | FILE_OPEN_FOR_BACKUP_INTENT,
+ p, plen);
+ if (NT_SUCCESS (status))
+ {
+ /* Set the "directory attribute" so that pc.isdir() returns correct
+ value in subsequent function calls. */
+ pc.file_attributes (FILE_ATTRIBUTE_DIRECTORY);
+ if (has_acls ())
+ set_created_file_access (dir, pc, mode & 07777);
+#if 0
+ /* FIXME: This default behaviour badly breaks interoperability.
+ Inspecting the content of case-sensitive directories
+ on remote machines results in lots of errors like
+ disappearing diretories and files, file not found, etc. */
+
+ /* Starting with Windows 10 1803, try to create all dirs below the
+ installation root as case-sensitive. If STATUS_NOT_SUPPORTED
+ is returned, WSL isn't installed (unfortunately a requirement
+ for this functionality. */
+ if (wincap.has_case_sensitive_dirs ()
+ && !pc.isremote () && pc.fs_is_ntfs ())
+ {
+ PUNICODE_STRING new_dir = pc.get_nt_native_path ();
+ PUNICODE_STRING root_dir = &cygheap->installation_root;
+
+ if (RtlEqualUnicodePathPrefix (new_dir, root_dir, TRUE)
+ && new_dir->Buffer[root_dir->Length / sizeof (WCHAR)] == L'\\')
+ {
+ FILE_CASE_SENSITIVE_INFORMATION fcsi;
+
+ fcsi.Flags = FILE_CS_FLAG_CASE_SENSITIVE_DIR;
+ status = NtSetInformationFile (dir, &io, &fcsi, sizeof fcsi,
+ FileCaseSensitiveInformation);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("Setting dir case sensitivity, status %y",
+ status);
+ if (status == STATUS_NOT_SUPPORTED)
+ {
+ debug_printf ("Dir case sensitivity requires WSL");
+ wincap.disable_case_sensitive_dirs ();
+ }
+ }
+ }
+ }
+#endif
+ NtClose (dir);
+ res = 0;
+ }
+ else
+ __seterrno_from_nt_status (status);
+
+ return res;
+}
+
+int
+fhandler_disk_file::rmdir ()
+{
+ extern NTSTATUS unlink_nt (path_conv &pc);
+
+ if (!pc.isdir ())
+ {
+ set_errno (ENOTDIR);
+ return -1;
+ }
+ if (!pc.exists ())
+ {
+ set_errno (ENOENT);
+ return -1;
+ }
+
+ NTSTATUS status = unlink_nt (pc);
+
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ return 0;
+}
+
+/* This is the minimal number of entries which fit into the readdir cache.
+ The number of bytes allocated by the cache is determined by this number,
+ To tune caching, just tweak this number. To get a feeling for the size,
+ the size of the readdir cache is DIR_NUM_ENTRIES * 624 + 4 bytes. */
+
+#define DIR_NUM_ENTRIES 100 /* Cache size 62404 bytes */
+
+#define DIR_BUF_SIZE (DIR_NUM_ENTRIES \
+ * (sizeof (FILE_ID_BOTH_DIR_INFORMATION) \
+ + (NAME_MAX + 1) * sizeof (WCHAR)))
+
+struct __DIR_cache
+{
+ char __cache[DIR_BUF_SIZE];
+ ULONG __pos;
+};
+
+#define d_cachepos(d) (((__DIR_cache *) (d)->__d_dirname)->__pos)
+#define d_cache(d) (((__DIR_cache *) (d)->__d_dirname)->__cache)
+
+#define d_mounts(d) ((__DIR_mounts *) (d)->__d_internal)
+
+DIR *
+fhandler_disk_file::opendir (int fd)
+{
+ DIR *dir;
+ DIR *res = NULL;
+
+ if (!pc.isdir ())
+ set_errno (ENOTDIR);
+ else if ((dir = (DIR *) malloc (sizeof (DIR))) == NULL)
+ set_errno (ENOMEM);
+ else if ((dir->__d_dirname = (char *) malloc ( sizeof (struct __DIR_cache)))
+ == NULL)
+ {
+ set_errno (ENOMEM);
+ goto free_dir;
+ }
+ else if ((dir->__d_dirent =
+ (struct dirent *) malloc (sizeof (struct dirent))) == NULL)
+ {
+ set_errno (ENOMEM);
+ goto free_dirname;
+ }
+ else
+ {
+ cygheap_fdnew cfd;
+ if (cfd < 0 && fd < 0)
+ goto free_dirent;
+
+ dir->__d_dirent->__d_version = __DIRENT_VERSION;
+ dir->__d_cookie = __DIRENT_COOKIE;
+ dir->__handle = INVALID_HANDLE_VALUE;
+ dir->__d_position = 0;
+ dir->__flags = (get_name ()[0] == '/' && get_name ()[1] == '\0')
+ ? dirent_isroot : 0;
+ dir->__d_internal = 0;
+
+ if (pc.iscygdrive ())
+ {
+ if (fd < 0 && !open (O_RDONLY, 0))
+ goto free_mounts;
+ }
+ else
+ {
+ dir->__d_internal = (uintptr_t) new __DIR_mounts (get_name ());
+ d_cachepos (dir) = 0;
+ if (fd < 0)
+ {
+ /* opendir() case. Initialize with given directory name and
+ NULL directory handle. */
+ OBJECT_ATTRIBUTES attr;
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ /* Tools like ls(1) call dirfd() to fetch the directory
+ descriptor for calls to facl or fstat. The tight access mask
+ used so far is not sufficient to reuse the handle for these
+ calls, instead the facl/fstat calls find the handle to be
+ unusable and have to re-open the file for reading attributes
+ and control data. So, what we do here is to try to open the
+ directory with more relaxed access mask which enables to use
+ the handle for the aforementioned purpose. This should work
+ in almost all cases. Only if it doesn't work due to
+ permission problems, we drop the additional access bits and
+ try again. */
+ ACCESS_MASK fstat_mask = READ_CONTROL | FILE_READ_ATTRIBUTES;
+
+ do
+ {
+ status = NtOpenFile (&get_handle (),
+ SYNCHRONIZE | FILE_LIST_DIRECTORY
+ | fstat_mask,
+ pc.get_object_attr (attr, sec_none_nih),
+ &io, FILE_SHARE_VALID_FLAGS,
+ FILE_SYNCHRONOUS_IO_NONALERT
+ | FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_DIRECTORY_FILE);
+ if (!NT_SUCCESS (status))
+ {
+ if (status == STATUS_ACCESS_DENIED && fstat_mask)
+ fstat_mask = 0;
+ else
+ {
+ __seterrno_from_nt_status (status);
+ goto free_mounts;
+ }
+ }
+ }
+ while (!NT_SUCCESS (status));
+ }
+
+ /* FileIdBothDirectoryInformation was unsupported on XP when
+ accessing UDF. It's not clear if the call isn't also unsupported
+ on other OS/FS combinations. Instead of testing for yet another
+ error code, use FileIdBothDirectoryInformation only on FSes
+ supporting persistent ACLs.
+
+ NFS clients hide dangling symlinks from directory queries,
+ unless you use the FileNamesInformation info class.
+ FileIdBothDirectoryInformation works fine, but only if the NFS
+ share is mounted to a drive letter. TODO: We don't test that
+ here for now, but it might be worth to test if there's a speed
+ gain in using FileIdBothDirectoryInformation, because it doesn't
+ require to open the file to read the inode number. */
+ if (pc.hasgood_inode ())
+ {
+ dir->__flags |= dirent_set_d_ino;
+ if (pc.fs_is_nfs ())
+ dir->__flags |= dirent_nfs_d_ino;
+ else if (!pc.has_buggy_fileid_dirinfo ())
+ dir->__flags |= dirent_get_d_ino;
+ }
+ }
+ if (fd >= 0)
+ dir->__d_fd = fd;
+ else
+ {
+ /* Filling cfd with `this' (aka storing this in the file
+ descriptor table should only happen after it's clear that
+ opendir doesn't fail, otherwise we end up cfree'ing the
+ fhandler twice, once in opendir() in dir.cc, the second
+ time on exit. Nasty, nasty... */
+ cfd = this;
+ dir->__d_fd = cfd;
+ }
+ set_close_on_exec (true);
+ dir->__fh = this;
+ res = dir;
+ }
+
+ syscall_printf ("%p = opendir (%s)", res, get_name ());
+ return res;
+
+free_mounts:
+ delete d_mounts (dir);
+free_dirent:
+ free (dir->__d_dirent);
+free_dirname:
+ free (dir->__d_dirname);
+free_dir:
+ free (dir);
+ return res;
+}
+
+ino_t
+readdir_get_ino (const char *path, bool dot_dot)
+{
+ char *fname;
+ struct stat st;
+ HANDLE hdl;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ ino_t ino = 0;
+
+ if (dot_dot)
+ {
+ fname = (char *) alloca (strlen (path) + 4);
+ char *c = stpcpy (fname, path);
+ if (c[-1] != '/')
+ *c++ = '/';
+ strcpy (c, "..");
+ path = fname;
+ }
+ path_conv pc (path, PC_SYM_NOFOLLOW | PC_POSIX | PC_KEEP_HANDLE);
+ if (pc.isspecial ())
+ {
+ if (!stat_worker (pc, &st))
+ ino = st.st_ino;
+ }
+ else if (!pc.hasgood_inode ())
+ ino = hash_path_name (0, pc.get_nt_native_path ());
+ else if ((hdl = pc.handle ()) != NULL
+ || NT_SUCCESS (NtOpenFile (&hdl, READ_CONTROL,
+ pc.get_object_attr (attr, sec_none_nih),
+ &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT
+ | (pc.is_known_reparse_point ()
+ ? FILE_OPEN_REPARSE_POINT : 0)))
+ )
+ {
+ ino = pc.get_ino_by_handle (hdl);
+ if (!ino)
+ ino = hash_path_name (0, pc.get_nt_native_path ());
+ }
+ return ino;
+}
+
+int
+fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err,
+ DWORD attr, PUNICODE_STRING fname)
+{
+ if (w32_err)
+ {
+ switch (d_mounts (dir)->check_missing_mount (fname))
+ {
+ case __DIR_mount_none:
+ fname->Length = 0;
+ return geterrno_from_win_error (w32_err);
+ case __DIR_mount_virt_target:
+ de->d_type = DT_DIR;
+ break;
+ default:
+ break;
+ }
+ attr = 0;
+ dir->__flags &= ~dirent_set_d_ino;
+ }
+
+ /* Set d_type if type can be determined from file attributes. For .lnk
+ symlinks, d_type will be reset below. Reparse points can be NTFS
+ symlinks, even if they have the FILE_ATTRIBUTE_DIRECTORY flag set. */
+ if (attr && !(attr & ~FILE_ATTRIBUTE_VALID_FLAGS))
+ {
+ if (attr & FILE_ATTRIBUTE_DIRECTORY)
+ de->d_type = DT_DIR;
+ /* FILE_ATTRIBUTE_SYSTEM might denote system-bit type symlinks. */
+ else if (!(attr & FILE_ATTRIBUTE_SYSTEM))
+ de->d_type = DT_REG;
+ }
+
+ /* Check for reparse points that can be treated as posix symlinks.
+ Mountpoints and unknown or unhandled reparse points will be treated
+ as normal file/directory/unknown. In all cases, returning the INO of
+ the reparse point (not of the target) matches behavior of posix systems.
+ */
+ if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
+ {
+ OBJECT_ATTRIBUTES oattr;
+
+ InitializeObjectAttributes (&oattr, fname, pc.objcaseinsensitive (),
+ get_handle (), NULL);
+ if (readdir_check_reparse_point (&oattr, isremote ()))
+ de->d_type = DT_LNK;
+ }
+
+ /* Check for Windows shortcut. If it's a Cygwin or U/WIN symlink, drop the
+ .lnk suffix and set d_type accordingly. */
+ if ((attr & (FILE_ATTRIBUTE_DIRECTORY
+ | FILE_ATTRIBUTE_REPARSE_POINT
+ | FILE_ATTRIBUTE_READONLY)) == FILE_ATTRIBUTE_READONLY
+ && fname->Length > 4 * sizeof (WCHAR))
+ {
+ UNICODE_STRING uname;
+
+ RtlInitCountedUnicodeString (&uname,
+ fname->Buffer
+ + fname->Length / sizeof (WCHAR) - 4,
+ 4 * sizeof (WCHAR));
+ if (RtlEqualUnicodeString (&uname, &ro_u_lnk, TRUE))
+ {
+ tmp_pathbuf tp;
+ char *file = tp.c_get ();
+ char *p = stpcpy (file, pc.get_posix ());
+ if (p[-1] != '/')
+ *p++ = '/';
+ sys_wcstombs (p, NT_MAX_PATH - (p - file),
+ fname->Buffer, fname->Length / sizeof (WCHAR));
+ path_conv fpath (file, PC_SYM_NOFOLLOW);
+ if (fpath.issymlink ())
+ {
+ fname->Length -= 4 * sizeof (WCHAR);
+ de->d_type = DT_LNK;
+ }
+ else if (fpath.isfifo ())
+ {
+ fname->Length -= 4 * sizeof (WCHAR);
+ de->d_type = DT_FIFO;
+ }
+ else if (fpath.is_fs_special ())
+ {
+ fname->Length -= 4 * sizeof (WCHAR);
+ de->d_type = S_ISCHR (fpath.dev.mode ()) ? DT_CHR : DT_BLK;
+ }
+ }
+ }
+
+ sys_wcstombs (de->d_name, NAME_MAX + 1, fname->Buffer,
+ fname->Length / sizeof (WCHAR));
+
+ /* Don't try to optimize relative to dir->__d_position. On several
+ filesystems it's no safe bet that "." and ".." entries always
+ come first. */
+ if (de->d_name[0] == '.')
+ {
+ if (de->d_name[1] == '\0')
+ dir->__flags |= dirent_saw_dot;
+ else if (de->d_name[1] == '.' && de->d_name[2] == '\0')
+ dir->__flags |= dirent_saw_dot_dot;
+ }
+ return 0;
+}
+
+int
+fhandler_disk_file::readdir (DIR *dir, dirent *de)
+{
+ int res = 0;
+ NTSTATUS status = STATUS_SUCCESS;
+ PFILE_ID_BOTH_DIR_INFORMATION buf = NULL;
+ PWCHAR FileName;
+ ULONG FileNameLength;
+ ULONG FileAttributes;
+ IO_STATUS_BLOCK io;
+ UNICODE_STRING fname;
+
+ /* d_cachepos always refers to the next cache entry to use. If it's 0
+ we must reload the cache. */
+ FileAttributes = 0;
+ if (d_cachepos (dir) == 0)
+ {
+ if ((dir->__flags & dirent_get_d_ino))
+ {
+ status = NtQueryDirectoryFile (get_handle (), NULL, NULL, NULL, &io,
+ d_cache (dir), DIR_BUF_SIZE,
+ FileIdBothDirectoryInformation,
+ FALSE, NULL, dir->__d_position == 0);
+ /* FileIdBothDirectoryInformation isn't supported for remote drives
+ on NT4 and 2K systems. There are also hacked versions of
+ Samba 3.0.x out there (Debian-based it seems), which return
+ STATUS_NOT_SUPPORTED rather than handling this info class.
+ We just fall back to using a standard directory query in
+ this case and note this case using the dirent_get_d_ino flag. */
+ if (!NT_SUCCESS (status) && status != STATUS_NO_MORE_FILES
+ && (status == STATUS_INVALID_LEVEL
+ || status == STATUS_NOT_SUPPORTED
+ || status == STATUS_INVALID_PARAMETER
+ || status == STATUS_INVALID_NETWORK_RESPONSE
+ || status == STATUS_INVALID_INFO_CLASS))
+ dir->__flags &= ~dirent_get_d_ino;
+ /* Something weird happens on Samba up to version 3.0.21c, which is
+ fixed in 3.0.22. FileIdBothDirectoryInformation seems to work
+ nicely, but only up to the 128th entry in the directory. After
+ reaching this entry, the next call to NtQueryDirectoryFile
+ (FileIdBothDirectoryInformation) returns STATUS_INVALID_LEVEL.
+ Why should we care, we can just switch to
+ FileBothDirectoryInformation, isn't it? Nope! The next call to
+ NtQueryDirectoryFile(FileBothDirectoryInformation) actually
+ returns STATUS_NO_MORE_FILES, regardless how many files are left
+ unread in the directory. This does not happen when using
+ FileBothDirectoryInformation right from the start, but since
+ we can't decide whether the server we're talking with has this
+ bug or not, we end up serving Samba shares always in the slow
+ mode using FileBothDirectoryInformation. So, what we do here is
+ to implement the solution suggested by Andrew Tridgell, we just
+ reread all entries up to dir->d_position using
+ FileBothDirectoryInformation.
+ However, We do *not* mark this server as broken and fall back to
+ using FileBothDirectoryInformation further on. This would slow
+ down every access to such a server, even for directories under
+ 128 entries. Also, bigger dirs only suffer from one additional
+ call per full directory scan, which shouldn't be too big a hit.
+ This can easily be changed if necessary. */
+ if (status == STATUS_INVALID_LEVEL && dir->__d_position)
+ {
+ d_cachepos (dir) = 0;
+ for (int cnt = 0; cnt < dir->__d_position; ++cnt)
+ {
+ if (d_cachepos (dir) == 0)
+ {
+ status = NtQueryDirectoryFile (get_handle (), NULL, NULL,
+ NULL, &io, d_cache (dir),
+ DIR_BUF_SIZE,
+ FileBothDirectoryInformation,
+ FALSE, NULL, cnt == 0);
+ if (!NT_SUCCESS (status))
+ goto go_ahead;
+ }
+ buf = (PFILE_ID_BOTH_DIR_INFORMATION) (d_cache (dir)
+ + d_cachepos (dir));
+ if (buf->NextEntryOffset == 0)
+ d_cachepos (dir) = 0;
+ else
+ d_cachepos (dir) += buf->NextEntryOffset;
+ }
+ goto go_ahead;
+ }
+ }
+ if (!(dir->__flags & dirent_get_d_ino))
+ status = NtQueryDirectoryFile (get_handle (), NULL, NULL, NULL, &io,
+ d_cache (dir), DIR_BUF_SIZE,
+ (dir->__flags & dirent_nfs_d_ino)
+ ? FileNamesInformation
+ : FileBothDirectoryInformation,
+ FALSE, NULL, dir->__d_position == 0);
+ }
+
+go_ahead:
+
+ if (status == STATUS_NO_MORE_FILES)
+ /*nothing*/;
+ else if (!NT_SUCCESS (status))
+ debug_printf ("NtQueryDirectoryFile failed, status %y, win32 error %u",
+ status, RtlNtStatusToDosError (status));
+ else
+ {
+ buf = (PFILE_ID_BOTH_DIR_INFORMATION) (d_cache (dir) + d_cachepos (dir));
+ if (buf->NextEntryOffset == 0)
+ d_cachepos (dir) = 0;
+ else
+ d_cachepos (dir) += buf->NextEntryOffset;
+ if ((dir->__flags & dirent_get_d_ino))
+ {
+ FileName = buf->FileName;
+ FileNameLength = buf->FileNameLength;
+ FileAttributes = buf->FileAttributes;
+ if ((dir->__flags & dirent_set_d_ino))
+ de->d_ino = buf->FileId.QuadPart;
+ }
+ else if ((dir->__flags & dirent_nfs_d_ino))
+ {
+ FileName = ((PFILE_NAMES_INFORMATION) buf)->FileName;
+ FileNameLength = ((PFILE_NAMES_INFORMATION) buf)->FileNameLength;
+ }
+ else
+ {
+ FileName = ((PFILE_BOTH_DIR_INFORMATION) buf)->FileName;
+ FileNameLength =
+ ((PFILE_BOTH_DIR_INFORMATION) buf)->FileNameLength;
+ FileAttributes =
+ ((PFILE_BOTH_DIR_INFORMATION) buf)->FileAttributes;
+ }
+ RtlInitCountedUnicodeString (&fname, FileName, FileNameLength);
+ d_mounts (dir)->check_mount (&fname);
+ if (de->d_ino == 0 && (dir->__flags & dirent_set_d_ino))
+ {
+ /* Don't try to optimize relative to dir->__d_position. On several
+ filesystems it's no safe bet that "." and ".." entries always
+ come first. */
+ if (FileNameLength == sizeof (WCHAR) && FileName[0] == '.')
+ de->d_ino = pc.get_ino_by_handle (get_handle ());
+ else if (FileNameLength == 2 * sizeof (WCHAR)
+ && FileName[0] == L'.' && FileName[1] == L'.')
+ {
+ if (!(dir->__flags & dirent_isroot))
+ de->d_ino = readdir_get_ino (get_name (), true);
+ else
+ de->d_ino = pc.get_ino_by_handle (get_handle ());
+ }
+ else
+ {
+ OBJECT_ATTRIBUTES attr;
+ HANDLE hdl;
+ NTSTATUS f_status;
+
+ InitializeObjectAttributes (&attr, &fname,
+ pc.objcaseinsensitive (),
+ get_handle (), NULL);
+ /* FILE_OPEN_REPARSE_POINT on NFS is a no-op, so the normal
+ NtOpenFile here returns the inode number of the symlink target,
+ rather than the inode number of the symlink itself.
+
+ Worse, trying to open a symlink without setting the special
+ "ActOnSymlink" EA triggers a bug in Windows 7 which results
+ in a timeout of up to 20 seconds, followed by two exceptions
+ in the NT kernel.
+
+ Since both results are far from desirable, we open symlinks
+ on NFS so that we get the right inode and a happy W7.
+ And, since some filesystems choke on the EAs, we don't
+ use them unconditionally. */
+ f_status = (dir->__flags & dirent_nfs_d_ino)
+ ? NtCreateFile (&hdl, READ_CONTROL, &attr, &io,
+ NULL, 0, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT,
+ &nfs_aol_ffei, sizeof nfs_aol_ffei)
+ : NtOpenFile (&hdl, READ_CONTROL, &attr, &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_OPEN_REPARSE_POINT);
+ if (NT_SUCCESS (f_status))
+ {
+ /* We call NtQueryInformationFile here, rather than
+ pc.get_ino_by_handle(), otherwise we can't short-circuit
+ dirent_set_d_ino correctly. */
+ FILE_INTERNAL_INFORMATION fii;
+ f_status = NtQueryInformationFile (hdl, &io, &fii, sizeof fii,
+ FileInternalInformation);
+ NtClose (hdl);
+ if (NT_SUCCESS (f_status))
+ {
+ if (pc.isgood_inode (fii.IndexNumber.QuadPart))
+ de->d_ino = fii.IndexNumber.QuadPart;
+ else
+ /* Untrusted file system. Don't try to fetch inode
+ number again. */
+ dir->__flags &= ~dirent_set_d_ino;
+ }
+ }
+ }
+ }
+ }
+
+ if (!(res = readdir_helper (dir, de, RtlNtStatusToDosError (status),
+ FileAttributes, &fname)))
+ dir->__d_position++;
+ else if (!(dir->__flags & dirent_saw_dot))
+ {
+ strcpy (de->d_name , ".");
+ de->d_ino = pc.get_ino_by_handle (get_handle ());
+ de->d_type = DT_DIR;
+ dir->__d_position++;
+ dir->__flags |= dirent_saw_dot;
+ res = 0;
+ }
+ else if (!(dir->__flags & dirent_saw_dot_dot))
+ {
+ strcpy (de->d_name , "..");
+ if (!(dir->__flags & dirent_isroot))
+ de->d_ino = readdir_get_ino (get_name (), true);
+ else
+ de->d_ino = pc.get_ino_by_handle (get_handle ());
+ de->d_type = DT_DIR;
+ dir->__d_position++;
+ dir->__flags |= dirent_saw_dot_dot;
+ res = 0;
+ }
+
+ syscall_printf ("%d = readdir(%p, %p) (L\"%lS\" > \"%ls\") (attr %y > type %d)",
+ res, dir, &de, res ? NULL : &fname, res ? "***" : de->d_name,
+ FileAttributes, de->d_type);
+ return res;
+}
+
+long
+fhandler_disk_file::telldir (DIR *dir)
+{
+ return dir->__d_position;
+}
+
+void
+fhandler_disk_file::seekdir (DIR *dir, long loc)
+{
+ rewinddir (dir);
+ while (loc > dir->__d_position)
+ if (!::readdir (dir))
+ break;
+}
+
+void
+fhandler_disk_file::rewinddir (DIR *dir)
+{
+ d_cachepos (dir) = 0;
+ dir->__d_position = 0;
+ d_mounts (dir)->rewind ();
+}
+
+int
+fhandler_disk_file::closedir (DIR *dir)
+{
+ int res = 0;
+
+ delete d_mounts (dir);
+ syscall_printf ("%d = closedir(%p, %s)", res, dir, get_name ());
+ return res;
+}
+
+uint64_t
+fhandler_disk_file::fs_ioc_getflags ()
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ FILE_BASIC_INFORMATION fbi;
+ FILE_CASE_SENSITIVE_INFORMATION fcsi;
+ uint64_t flags = 0;
+
+ status = NtQueryInformationFile (get_handle (), &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ if (NT_SUCCESS (status))
+ {
+ flags = (uint64_t) fbi.FileAttributes & FS_FL_USER_VISIBLE;
+ pc.file_attributes (fbi.FileAttributes);
+ }
+ else
+ flags = (uint64_t) pc.file_attributes () & FS_FL_USER_VISIBLE;
+ if (pc.isdir () && wincap.has_case_sensitive_dirs ()
+ && !pc.isremote () && pc.fs_is_ntfs ())
+ {
+ fcsi.Flags = 0;
+ status = NtQueryInformationFile (get_handle (), &io,
+ &fcsi, sizeof fcsi,
+ FileCaseSensitiveInformation);
+ if (NT_SUCCESS (status)
+ && (fcsi.Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR))
+ flags |= FS_CASESENS_FL;
+ }
+ return flags;
+}
+
+/* Settable DOS attributes */
+#define FS_FL_SETATTRIBS (FS_READONLY_FL \
+ | FS_HIDDEN_FL \
+ | FS_SYSTEM_FL \
+ | FS_ARCHIVE_FL \
+ | FS_TEMP_FL \
+ | FS_NOTINDEXED_FL)
+
+int
+fhandler_disk_file::fs_ioc_setflags (uint64_t flags)
+{
+ int ret = -1;
+ uint64_t old_flags;
+ HANDLE fh;
+ NTSTATUS status;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ FILE_BASIC_INFORMATION fbi;
+ FILE_SET_SPARSE_BUFFER fssb;
+ USHORT comp;
+ FILE_CASE_SENSITIVE_INFORMATION fcsi;
+
+ if ((get_access () & (GENERIC_WRITE | FILE_WRITE_ATTRIBUTES)) != 0)
+ fh = get_handle ();
+ else
+ {
+ status = NtOpenFile (&fh, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
+ pc.init_reopen_attr (attr, get_handle ()), &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT);
+ if (!NT_SUCCESS (status))
+ {
+ fh = get_handle ();
+ __seterrno_from_nt_status (status);
+ goto out;
+ }
+ }
+ old_flags = fs_ioc_getflags ();
+ if ((old_flags & FS_FL_SETATTRIBS) != (flags & FS_FL_SETATTRIBS))
+ {
+ fbi.CreationTime.QuadPart
+ = fbi.LastAccessTime.QuadPart
+ = fbi.LastWriteTime.QuadPart
+ = fbi.ChangeTime.QuadPart = 0LL;
+ fbi.FileAttributes = (ULONG) old_flags;
+ fbi.FileAttributes &= ~FS_FL_SETATTRIBS;
+ fbi.FileAttributes |= (flags & FS_FL_SETATTRIBS);
+ if (fbi.FileAttributes == 0)
+ fbi.FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ status = NtSetInformationFile (fh, &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ goto out;
+ }
+ }
+ if (!pc.isdir() && (flags & FS_SPARSE_FL) != (old_flags & FS_SPARSE_FL))
+ {
+ fssb.SetSparse = (flags & FS_SPARSE_FL) ? TRUE : FALSE;
+ status = NtFsControlFile (fh, NULL, NULL, NULL, &io,
+ FSCTL_SET_SPARSE, &fssb, sizeof fssb, NULL, 0);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ goto out;
+ }
+ }
+ if (pc.isdir () && (flags & FS_CASESENS_FL) != (old_flags & FS_CASESENS_FL))
+ {
+ if (wincap.has_case_sensitive_dirs ()
+ && !pc.isremote () && pc.fs_is_ntfs ())
+ {
+ fcsi.Flags = (flags & FS_CASESENS_FL)
+ ? FILE_CS_FLAG_CASE_SENSITIVE_DIR : 0;
+ status = NtSetInformationFile (fh, &io, &fcsi, sizeof fcsi,
+ FileCaseSensitiveInformation);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ goto out;
+ }
+ }
+ else
+ {
+ set_errno (ENOTSUP);
+ goto out;
+ }
+ }
+ if ((flags & FS_COMPRESSED_FL) != (old_flags & FS_COMPRESSED_FL))
+ {
+ if (fh != get_handle ())
+ NtClose (fh);
+ fh = NULL;
+ if ((get_access () & (GENERIC_WRITE | GENERIC_READ))
+ != (GENERIC_WRITE | GENERIC_READ))
+ {
+ status = NtOpenFile (&fh, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
+ pc.init_reopen_attr (attr, get_handle ()), &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_SYNCHRONOUS_IO_NONALERT
+ | FILE_OPEN_FOR_BACKUP_INTENT);
+ if (!NT_SUCCESS (status))
+ {
+ fh = get_handle ();
+ __seterrno_from_nt_status (status);
+ goto out;
+ }
+ }
+ comp = (flags & FS_COMPRESSED_FL)
+ ? COMPRESSION_FORMAT_DEFAULT : COMPRESSION_FORMAT_NONE;
+ status = NtFsControlFile (fh, NULL, NULL, NULL, &io,
+ FSCTL_SET_COMPRESSION, &comp, sizeof comp,
+ NULL, 0);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ goto out;
+ }
+ }
+ if (!pc.isdir() && (flags & FS_ENCRYPT_FL) != (old_flags & FS_ENCRYPT_FL))
+ {
+ tmp_pathbuf tp;
+ PWCHAR path = tp.w_get ();
+ BOOL cret;
+
+ /* EncryptFileW/DecryptFileW needs exclusive access. */
+ if (fh != get_handle ())
+ NtClose (fh);
+ NtClose (get_handle ());
+ set_handle (NULL);
+
+ pc.get_wide_win32_path (path);
+ cret = (flags & FS_ENCRYPT_FL)
+ ? EncryptFileW (path) : DecryptFileW (path, 0);
+ status = NtOpenFile (&fh, get_access (),
+ pc.get_object_attr (attr, sec_none_nih), &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_SYNCHRONOUS_IO_NONALERT
+ | FILE_OPEN_FOR_BACKUP_INTENT);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ set_handle (fh);
+ if (!cret)
+ {
+ __seterrno ();
+ goto out;
+ }
+ }
+ ret = 0;
+out:
+ status = NtQueryInformationFile (fh, &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ if (NT_SUCCESS (status))
+ pc.file_attributes (fbi.FileAttributes);
+ if (fh != get_handle ())
+ NtClose (fh);
+ return ret;
+}
+
+int
+fhandler_disk_file::ioctl (unsigned int cmd, void *p)
+{
+ int ret = -1;
+ uint64_t flags = 0;
+
+ switch (cmd)
+ {
+ case FS_IOC_GETFLAGS:
+ __try
+ {
+ uint64_t *fp = (uint64_t *) p;
+ *fp = fs_ioc_getflags ();
+ ret = 0;
+ }
+ __except (EFAULT) {}
+ __endtry
+ break;
+ case FS_IOC_SETFLAGS:
+ __try
+ {
+ flags = *(__uint64_t *) p;
+ }
+ __except (EFAULT)
+ {
+ break;
+ }
+ __endtry
+ if (flags & ~FS_FL_USER_MODIFIABLE)
+ {
+ set_errno (EINVAL);
+ break;
+ }
+ ret = fs_ioc_setflags (flags);
+ break;
+ default:
+ ret = fhandler_base::ioctl (cmd, p);
+ break;
+ }
+ syscall_printf ("%d = ioctl_file(%x, %p)", ret, cmd, p);
+ return ret;
+}
diff --git a/winsup/cygwin/fhandler/dsp.cc b/winsup/cygwin/fhandler/dsp.cc
new file mode 100644
index 000000000..c37bedea5
--- /dev/null
+++ b/winsup/cygwin/fhandler/dsp.cc
@@ -0,0 +1,1443 @@
+/* fhandler_dev_dsp: code to emulate OSS sound model /dev/dsp
+
+ Written by Andy Younger (andy@snoogie.demon.co.uk)
+ Extended by Gerd Spalink (Gerd.Spalink@t-online.de)
+ to support recording from the audio input
+
+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 <sys/soundcard.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "sigproc.h"
+#include "cygwait.h"
+
+/*------------------------------------------------------------------------
+ Simple encapsulation of the win32 audio device.
+
+ Implementation Notes
+ 1. Audio structures are malloced just before the first read or
+ write to /dev/dsp. The actual buffer size is determined at that time,
+ such that one buffer holds about 125ms of audio data.
+ At the time of this writing, 12 buffers are allocated,
+ so that up to 1.5 seconds can be buffered within Win32.
+ The buffer size can be queried with the ioctl SNDCTL_DSP_GETBLKSIZE,
+ but for this implementation only returns meaningful results if
+ sampling rate, number of channels and number of bits per sample
+ are not changed afterwards.
+ The audio structures are freed when the device is reset or closed,
+ and they are not passed to exec'ed processes.
+ The dev_ member is cleared after a fork. This forces the child
+ to reopen the audio device._
+
+ 2. Every open call creates a new instance of the handler. After a
+ successful open, every subsequent open from the same process
+ to the device fails with EBUSY.
+ The structures are shared between duped handles, but not with
+ children. They only inherit the settings from the parent.
+ */
+
+class fhandler_dev_dsp::Audio
+{ // This class contains functionality common to Audio_in and Audio_out
+ public:
+ Audio (fhandler_dev_dsp *my_fh);
+ ~Audio ();
+
+ class queue;
+
+ bool isvalid ();
+ void setconvert (int format);
+ void convert_none (unsigned char *buffer, int size_bytes) { }
+ void convert_U8_S8 (unsigned char *buffer, int size_bytes);
+ void convert_S16LE_U16LE (unsigned char *buffer, int size_bytes);
+ void convert_S16LE_U16BE (unsigned char *buffer, int size_bytes);
+ void convert_S16LE_S16BE (unsigned char *buffer, int size_bytes);
+ void fillFormat (WAVEFORMATEX * format,
+ int rate, int bits, int channels);
+ static unsigned blockSize (int rate, int bits, int channels);
+ void (fhandler_dev_dsp::Audio::*convert_)
+ (unsigned char *buffer, int size_bytes);
+
+ enum { MAX_BLOCKS = 12 };
+ int bufferIndex_; // offset into pHdr_->lpData
+ WAVEHDR *pHdr_; // data to be filled by write
+ WAVEHDR wavehdr_[MAX_BLOCKS];
+ char *bigwavebuffer_; // audio samples only
+ // Member variables below must be locked
+ queue *Qisr2app_; // blocks passed from wave callback
+
+ fhandler_dev_dsp *fh;
+};
+
+class fhandler_dev_dsp::Audio::queue
+{ // non-blocking fixed size queues for buffer management
+ public:
+ queue (int depth = 4);
+ ~queue ();
+
+ bool send (WAVEHDR *); // queue an item, returns true if successful
+ bool recv (WAVEHDR **); // retrieve an item, returns true if successful
+ void reset ();
+ int query (); // return number of items queued
+ inline void lock () { EnterCriticalSection (&lock_); }
+ inline void unlock () { LeaveCriticalSection (&lock_); }
+ inline void dellock () { debug_printf ("Deleting Critical Section"); DeleteCriticalSection (&lock_); }
+ bool isvalid () { return storage_; }
+ private:
+ CRITICAL_SECTION lock_;
+ int head_;
+ int tail_;
+ int depth_;
+ WAVEHDR **storage_;
+};
+
+static void CALLBACK waveOut_callback (HWAVEOUT hWave, UINT msg,
+ DWORD_PTR instance, DWORD_PTR param1,
+ DWORD_PTR param2);
+
+class fhandler_dev_dsp::Audio_out: public Audio
+{
+ public:
+ Audio_out (fhandler_dev_dsp *my_fh) : Audio (my_fh) {}
+
+ void fork_fixup (HANDLE parent);
+ bool query (int rate, int bits, int channels);
+ bool start ();
+ void stop (bool immediately = false);
+ int write (const char *pSampleData, int nBytes);
+ void buf_info (audio_buf_info *p, int rate, int bits, int channels);
+ static void default_buf_info (audio_buf_info *p, int rate, int bits, int channels);
+ void callback_sampledone (WAVEHDR *pHdr);
+ bool parsewav (const char *&pData, int &nBytes,
+ int rate, int bits, int channels);
+
+ private:
+ void init (unsigned blockSize);
+ void waitforallsent ();
+ bool waitforspace ();
+ bool sendcurrent ();
+
+ enum { MAX_BLOCKS = 12 };
+ HWAVEOUT dev_; // The wave device
+ /* Private copies of audiofreq_, audiobits_, audiochannels_,
+ possibly set from wave file */
+ int freq_;
+ int bits_;
+ int channels_;
+};
+
+static void CALLBACK waveIn_callback (HWAVEIN hWave, UINT msg,
+ DWORD_PTR instance, DWORD_PTR param1,
+ DWORD_PTR param2);
+
+class fhandler_dev_dsp::Audio_in: public Audio
+{
+public:
+ Audio_in (fhandler_dev_dsp *my_fh) : Audio (my_fh) {}
+
+ void fork_fixup (HANDLE parent);
+ bool query (int rate, int bits, int channels);
+ bool start (int rate, int bits, int channels);
+ void stop ();
+ bool read (char *pSampleData, int &nBytes);
+ void buf_info (audio_buf_info *p, int rate, int bits, int channels);
+ static void default_buf_info (audio_buf_info *p, int rate, int bits, int channels);
+ void callback_blockfull (WAVEHDR *pHdr);
+
+private:
+ bool init (unsigned blockSize);
+ bool queueblock (WAVEHDR *pHdr);
+ bool waitfordata (); // blocks until we have a good pHdr_ unless O_NONBLOCK
+
+ HWAVEIN dev_;
+};
+
+/* --------------------------------------------------------------------
+ Implementation */
+
+// Simple fixed length FIFO queue implementation for audio buffer management
+fhandler_dev_dsp::Audio::queue::queue (int depth)
+{
+ // allow space for one extra object in the queue
+ // so we can distinguish full and empty status
+ depth_ = depth;
+ storage_ = new WAVEHDR *[depth_ + 1];
+}
+
+fhandler_dev_dsp::Audio::queue::~queue ()
+{
+ delete[] storage_;
+}
+
+void
+fhandler_dev_dsp::Audio::queue::reset ()
+ {
+ /* When starting, after reset and after fork */
+ head_ = tail_ = 0;
+ debug_printf ("InitializeCriticalSection");
+ memset (&lock_, 0, sizeof (lock_));
+ InitializeCriticalSection (&lock_);
+ }
+
+bool
+fhandler_dev_dsp::Audio::queue::send (WAVEHDR *x)
+{
+ bool res = false;
+ lock ();
+ if (query () == depth_)
+ system_printf ("Queue overflow");
+ else
+ {
+ storage_[tail_] = x;
+ if (++tail_ > depth_)
+ tail_ = 0;
+ res = true;
+ }
+ unlock ();
+ return res;
+}
+
+bool
+fhandler_dev_dsp::Audio::queue::recv (WAVEHDR **x)
+{
+ bool res = false;
+ lock ();
+ if (query () != 0)
+ {
+ *x = storage_[head_];
+ if (++head_ > depth_)
+ head_ = 0;
+ res = true;
+ }
+ unlock ();
+ return res;
+}
+
+int
+fhandler_dev_dsp::Audio::queue::query ()
+{
+ int n = tail_ - head_;
+ if (n < 0)
+ n += depth_ + 1;
+ return n;
+}
+
+// Audio class implements functionality need for both read and write
+fhandler_dev_dsp::Audio::Audio (fhandler_dev_dsp *my_fh)
+{
+ bigwavebuffer_ = NULL;
+ Qisr2app_ = new queue (MAX_BLOCKS);
+ convert_ = &fhandler_dev_dsp::Audio::convert_none;
+ fh = my_fh;
+}
+
+fhandler_dev_dsp::Audio::~Audio ()
+{
+ debug_printf("");
+ delete Qisr2app_;
+ delete[] bigwavebuffer_;
+}
+
+inline bool
+fhandler_dev_dsp::Audio::isvalid ()
+{
+ return bigwavebuffer_ && Qisr2app_ && Qisr2app_->isvalid ();
+}
+
+void
+fhandler_dev_dsp::Audio::setconvert (int format)
+{
+ switch (format)
+ {
+ case AFMT_S8:
+ convert_ = &fhandler_dev_dsp::Audio::convert_U8_S8;
+ debug_printf ("U8_S8");
+ break;
+ case AFMT_U16_LE:
+ convert_ = &fhandler_dev_dsp::Audio::convert_S16LE_U16LE;
+ debug_printf ("S16LE_U16LE");
+ break;
+ case AFMT_U16_BE:
+ convert_ = &fhandler_dev_dsp::Audio::convert_S16LE_U16BE;
+ debug_printf ("S16LE_U16BE");
+ break;
+ case AFMT_S16_BE:
+ convert_ = &fhandler_dev_dsp::Audio::convert_S16LE_S16BE;
+ debug_printf ("S16LE_S16BE");
+ break;
+ default:
+ convert_ = &fhandler_dev_dsp::Audio::convert_none;
+ debug_printf ("none");
+ }
+}
+
+void
+fhandler_dev_dsp::Audio::convert_U8_S8 (unsigned char *buffer,
+ int size_bytes)
+{
+ while (size_bytes-- > 0)
+ {
+ *buffer ^= (unsigned char)0x80;
+ buffer++;
+ }
+}
+
+void
+fhandler_dev_dsp::Audio::convert_S16LE_U16BE (unsigned char *buffer,
+ int size_bytes)
+{
+ int size_samples = size_bytes / 2;
+ unsigned char hi, lo;
+ while (size_samples-- > 0)
+ {
+ hi = buffer[0];
+ lo = buffer[1];
+ *buffer++ = lo;
+ *buffer++ = hi ^ (unsigned char)0x80;
+ }
+}
+
+void
+fhandler_dev_dsp::Audio::convert_S16LE_U16LE (unsigned char *buffer,
+ int size_bytes)
+{
+ int size_samples = size_bytes / 2;
+ while (size_samples-- > 0)
+ {
+ buffer++;
+ *buffer ^= (unsigned char)0x80;
+ buffer++;
+ }
+}
+
+void
+fhandler_dev_dsp::Audio::convert_S16LE_S16BE (unsigned char *buffer,
+ int size_bytes)
+{
+ int size_samples = size_bytes / 2;
+ unsigned char hi, lo;
+ while (size_samples-- > 0)
+ {
+ hi = buffer[0];
+ lo = buffer[1];
+ *buffer++ = lo;
+ *buffer++ = hi;
+ }
+}
+
+void
+fhandler_dev_dsp::Audio::fillFormat (WAVEFORMATEX * format,
+ int rate, int bits, int channels)
+{
+ memset (format, 0, sizeof (*format));
+ format->wFormatTag = WAVE_FORMAT_PCM;
+ format->wBitsPerSample = bits;
+ format->nChannels = channels;
+ format->nSamplesPerSec = rate;
+ format->nAvgBytesPerSec = format->nSamplesPerSec * format->nChannels
+ * (bits / 8);
+ format->nBlockAlign = format->nChannels * (bits / 8);
+}
+
+// calculate a good block size
+unsigned
+fhandler_dev_dsp::Audio::blockSize (int rate, int bits, int channels)
+{
+ unsigned blockSize;
+ blockSize = ((bits / 8) * channels * rate) / 8; // approx 125ms per block
+ // round up to multiple of 64
+ blockSize += 0x3f;
+ blockSize &= ~0x3f;
+ return blockSize;
+}
+
+//=======================================================================
+void
+fhandler_dev_dsp::Audio_out::fork_fixup (HANDLE parent)
+{
+ /* Null dev_.
+ It will be necessary to reset the queue, open the device
+ and create a lock when writing */
+ debug_printf ("parent=%p", parent);
+ dev_ = NULL;
+}
+
+
+bool
+fhandler_dev_dsp::Audio_out::query (int rate, int bits, int channels)
+{
+ WAVEFORMATEX format;
+ MMRESULT rc;
+
+ fillFormat (&format, rate, bits, channels);
+ rc = waveOutOpen (NULL, WAVE_MAPPER, &format, 0L, 0L, WAVE_FORMAT_QUERY);
+ debug_printf ("%u = waveOutOpen(freq=%d bits=%d channels=%d)", rc, rate, bits, channels);
+ return (rc == MMSYSERR_NOERROR);
+}
+
+bool
+fhandler_dev_dsp::Audio_out::start ()
+{
+ WAVEFORMATEX format;
+ MMRESULT rc;
+ unsigned bSize = blockSize (freq_, bits_, channels_);
+
+ if (dev_)
+ return true;
+
+ /* In case of fork bigwavebuffer may already exist */
+ if (!bigwavebuffer_)
+ bigwavebuffer_ = new char[MAX_BLOCKS * bSize];
+
+ if (!isvalid ())
+ return false;
+
+ fillFormat (&format, freq_, bits_, channels_);
+ rc = waveOutOpen (&dev_, WAVE_MAPPER, &format, (DWORD_PTR) waveOut_callback,
+ (DWORD_PTR) this, CALLBACK_FUNCTION);
+ if (rc == MMSYSERR_NOERROR)
+ init (bSize);
+
+ debug_printf ("%u = waveOutOpen(freq=%d bits=%d channels=%d)", rc, freq_, bits_, channels_);
+
+ return (rc == MMSYSERR_NOERROR);
+}
+
+void
+fhandler_dev_dsp::Audio_out::stop (bool immediately)
+{
+ MMRESULT rc;
+ WAVEHDR *pHdr;
+
+ debug_printf ("dev_=%p", dev_);
+ if (dev_)
+ {
+ if (!immediately)
+ {
+ sendcurrent (); // force out last block whatever size..
+ waitforallsent (); // block till finished..
+ }
+
+ rc = waveOutReset (dev_);
+ debug_printf ("%u = waveOutReset()", rc);
+ while (Qisr2app_->recv (&pHdr))
+ {
+ rc = waveOutUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
+ debug_printf ("%u = waveOutUnprepareHeader(%p)", rc, pHdr);
+ }
+
+ no_thread_exit_protect for_now (true);
+ rc = waveOutClose (dev_);
+ debug_printf ("%u = waveOutClose()", rc);
+
+ Qisr2app_->dellock ();
+ }
+}
+
+void
+fhandler_dev_dsp::Audio_out::init (unsigned blockSize)
+{
+ int i;
+
+ // internally queue all of our buffer for later use by write
+ Qisr2app_->reset ();
+ for (i = 0; i < MAX_BLOCKS; i++)
+ {
+ wavehdr_[i].lpData = &bigwavebuffer_[i * blockSize];
+ wavehdr_[i].dwUser = (int) blockSize;
+ wavehdr_[i].dwFlags = 0;
+ if (!Qisr2app_->send (&wavehdr_[i]))
+ {
+ system_printf ("Internal Error i=%d", i);
+ break; // should not happen
+ }
+ }
+ pHdr_ = NULL;
+}
+
+int
+fhandler_dev_dsp::Audio_out::write (const char *pSampleData, int nBytes)
+{
+ int bytes_to_write = nBytes;
+ while (bytes_to_write != 0)
+ { // Block if all blocks used until at least one is free
+ if (!waitforspace ())
+ {
+ if (bytes_to_write != nBytes)
+ break;
+ return -1;
+ }
+
+ int sizeleft = (int)pHdr_->dwUser - bufferIndex_;
+ if (bytes_to_write < sizeleft)
+ { // all data fits into the current block, with some space left
+ memcpy (&pHdr_->lpData[bufferIndex_], pSampleData, bytes_to_write);
+ bufferIndex_ += bytes_to_write;
+ bytes_to_write = 0;
+ break;
+ }
+ else
+ { // data will fill up the current block
+ memcpy (&pHdr_->lpData[bufferIndex_], pSampleData, sizeleft);
+ bufferIndex_ += sizeleft;
+ sendcurrent ();
+ pSampleData += sizeleft;
+ bytes_to_write -= sizeleft;
+ }
+ }
+ return nBytes - bytes_to_write;
+}
+
+void
+fhandler_dev_dsp::Audio_out::buf_info (audio_buf_info *p,
+ int rate, int bits, int channels)
+{
+ if (dev_)
+ {
+ /* If the device is running we use the internal values,
+ possibly set from the wave file. */
+ p->fragstotal = MAX_BLOCKS;
+ p->fragsize = blockSize (freq_, bits_, channels_);
+ p->fragments = Qisr2app_->query ();
+ if (pHdr_ != NULL)
+ p->bytes = (int)pHdr_->dwUser - bufferIndex_
+ + p->fragsize * p->fragments;
+ else
+ p->bytes = p->fragsize * p->fragments;
+ }
+ else
+ {
+ default_buf_info(p, rate, bits, channels);
+ }
+}
+
+void fhandler_dev_dsp::Audio_out::default_buf_info (audio_buf_info *p,
+ int rate, int bits, int channels)
+{
+ p->fragstotal = MAX_BLOCKS;
+ p->fragsize = blockSize (rate, bits, channels);
+ p->fragments = MAX_BLOCKS;
+ p->bytes = p->fragsize * p->fragments;
+}
+
+/* This is called on an interupt so use locking.. Note Qisr2app_
+ is used so we should wrap all references to it in locks. */
+inline void
+fhandler_dev_dsp::Audio_out::callback_sampledone (WAVEHDR *pHdr)
+{
+ Qisr2app_->send (pHdr);
+}
+
+bool
+fhandler_dev_dsp::Audio_out::waitforspace ()
+{
+ WAVEHDR *pHdr;
+ MMRESULT rc = WAVERR_STILLPLAYING;
+
+ if (pHdr_ != NULL)
+ return true;
+ while (!Qisr2app_->recv (&pHdr))
+ {
+ if (fh->is_nonblocking ())
+ {
+ set_errno (EAGAIN);
+ return false;
+ }
+ debug_printf ("100ms");
+ switch (cygwait (100))
+ {
+ case WAIT_SIGNALED:
+ if (!_my_tls.call_signal_handler ())
+ {
+ set_errno (EINTR);
+ return false;
+ }
+ break;
+ case WAIT_CANCELED:
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ default:
+ break;
+ }
+ }
+ if (pHdr->dwFlags)
+ {
+ /* Errors are ignored here. They will probbaly cause a failure
+ in the subsequent PrepareHeader */
+ rc = waveOutUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
+ debug_printf ("%u = waveOutUnprepareHeader(%p)", rc, pHdr);
+ }
+ pHdr_ = pHdr;
+ bufferIndex_ = 0;
+ return true;
+}
+
+void
+fhandler_dev_dsp::Audio_out::waitforallsent ()
+{
+ while (Qisr2app_->query () != MAX_BLOCKS)
+ {
+ debug_printf ("%d blocks in Qisr2app", Qisr2app_->query ());
+ Sleep (100);
+ }
+}
+
+// send the block described by pHdr_ and bufferIndex_ to wave device
+bool
+fhandler_dev_dsp::Audio_out::sendcurrent ()
+{
+ WAVEHDR *pHdr = pHdr_;
+ MMRESULT rc;
+ debug_printf ("pHdr=%p bytes=%d", pHdr, bufferIndex_);
+
+ if (pHdr_ == NULL)
+ return false;
+ pHdr_ = NULL;
+
+ // Sample buffer conversion
+ (this->*convert_) ((unsigned char *)pHdr->lpData, bufferIndex_);
+
+ // Send internal buffer out to the soundcard
+ pHdr->dwBufferLength = bufferIndex_;
+ rc = waveOutPrepareHeader (dev_, pHdr, sizeof (WAVEHDR));
+ debug_printf ("%u = waveOutPrepareHeader(%p)", rc, pHdr);
+ if (rc == MMSYSERR_NOERROR)
+ {
+ rc = waveOutWrite (dev_, pHdr, sizeof (WAVEHDR));
+ debug_printf ("%u = waveOutWrite(%p)", rc, pHdr);
+ }
+ if (rc == MMSYSERR_NOERROR)
+ return true;
+
+ /* FIXME: Should we return an error instead ?*/
+ pHdr->dwFlags = 0; /* avoid calling UnprepareHeader again */
+ Qisr2app_->send (pHdr);
+ return false;
+}
+
+//------------------------------------------------------------------------
+// Call back routine
+static void CALLBACK
+waveOut_callback (HWAVEOUT hWave, UINT msg, DWORD_PTR instance,
+ DWORD_PTR param1, DWORD_PTR param2)
+{
+ if (msg == WOM_DONE)
+ {
+ fhandler_dev_dsp::Audio_out *ptr =
+ (fhandler_dev_dsp::Audio_out *) instance;
+ ptr->callback_sampledone ((WAVEHDR *) param1);
+ }
+}
+
+//------------------------------------------------------------------------
+// wav file detection..
+#pragma pack(1)
+struct wavchunk
+{
+ char id[4];
+ unsigned int len;
+};
+struct wavformat
+{
+ unsigned short wFormatTag;
+ unsigned short wChannels;
+ unsigned int dwSamplesPerSec;
+ unsigned int dwAvgBytesPerSec;
+ unsigned short wBlockAlign;
+ unsigned short wBitsPerSample;
+};
+#pragma pack()
+
+bool
+fhandler_dev_dsp::Audio_out::parsewav (const char * &pData, int &nBytes,
+ int dev_freq, int dev_bits, int dev_channels)
+{
+ int len;
+ const char *end = pData + nBytes;
+ const char *pDat;
+ int skip = 0;
+
+ /* Start with default values from the device handler */
+ freq_ = dev_freq;
+ bits_ = dev_bits;
+ channels_ = dev_channels;
+ setconvert (bits_ == 8 ? AFMT_U8 : AFMT_S16_LE);
+
+ // Check alignment first: A lot of the code below depends on it
+ if (((uintptr_t)pData & 0x3) != 0)
+ return false;
+ if (!(pData[0] == 'R' && pData[1] == 'I'
+ && pData[2] == 'F' && pData[3] == 'F'))
+ return false;
+ if (!(pData[8] == 'W' && pData[9] == 'A'
+ && pData[10] == 'V' && pData[11] == 'E'))
+ return false;
+
+ len = *(int *) &pData[4];
+ len -= 12;
+ pDat = pData + 12;
+ skip = 12;
+ while ((len > 0) && (pDat + sizeof (wavchunk) < end))
+ { /* We recognize two kinds of wavchunk:
+ "fmt " for the PCM parameters (only PCM supported here)
+ "data" for the start of PCM data */
+ wavchunk * pChunk = (wavchunk *) pDat;
+ int blklen = pChunk-> len;
+ if (pChunk->id[0] == 'f' && pChunk->id[1] == 'm'
+ && pChunk->id[2] == 't' && pChunk->id[3] == ' ')
+ {
+ wavformat *format = (wavformat *) (pChunk + 1);
+ if ((char *) (format + 1) >= end)
+ return false;
+ // We have found the parameter chunk
+ if (format->wFormatTag == 0x0001)
+ { // Micr*s*ft PCM; check if parameters work with our device
+ if (query (format->dwSamplesPerSec, format->wBitsPerSample,
+ format->wChannels))
+ { // return the parameters we found
+ freq_ = format->dwSamplesPerSec;
+ bits_ = format->wBitsPerSample;
+ channels_ = format->wChannels;
+ }
+ }
+ }
+ else
+ {
+ if (pChunk->id[0] == 'd' && pChunk->id[1] == 'a'
+ && pChunk->id[2] == 't' && pChunk->id[3] == 'a')
+ { // throw away all the header & not output it to the soundcard.
+ skip += sizeof (wavchunk);
+ debug_printf ("Discard %d bytes wave header", skip);
+ pData += skip;
+ nBytes -= skip;
+ setconvert (bits_ == 8 ? AFMT_U8 : AFMT_S16_LE);
+ return true;
+ }
+ }
+ pDat += blklen + sizeof (wavchunk);
+ skip += blklen + sizeof (wavchunk);
+ len -= blklen + sizeof (wavchunk);
+ }
+ return false;
+}
+
+/* ========================================================================
+ Buffering concept for Audio_in:
+ On the first read, we queue all blocks of our bigwavebuffer
+ for reception and start the wave-in device.
+ We manage queues of pointers to WAVEHDR
+ When a block has been filled, the callback puts the corresponding
+ WAVEHDR pointer into a queue.
+ The function read() blocks (polled, sigh) until at least one good buffer
+ has arrived, then the data is copied into the buffer provided to read().
+ After a buffer has been fully used by read(), it is queued again
+ to the wave-in device immediately.
+ The function read() iterates until all data requested has been
+ received, there is no way to interrupt it */
+
+void
+fhandler_dev_dsp::Audio_in::fork_fixup (HANDLE parent)
+{
+ /* Null dev_.
+ It will be necessary to reset the queue, open the device
+ and create a lock when reading */
+ debug_printf ("parent=%p", parent);
+ dev_ = NULL;
+}
+
+bool
+fhandler_dev_dsp::Audio_in::query (int rate, int bits, int channels)
+{
+ WAVEFORMATEX format;
+ MMRESULT rc;
+
+ fillFormat (&format, rate, bits, channels);
+ rc = waveInOpen (NULL, WAVE_MAPPER, &format, 0L, 0L, WAVE_FORMAT_QUERY);
+ debug_printf ("%u = waveInOpen(freq=%d bits=%d channels=%d)", rc, rate, bits, channels);
+ return (rc == MMSYSERR_NOERROR);
+}
+
+bool
+fhandler_dev_dsp::Audio_in::start (int rate, int bits, int channels)
+{
+ WAVEFORMATEX format;
+ MMRESULT rc;
+ unsigned bSize = blockSize (rate, bits, channels);
+
+ if (dev_)
+ return true;
+
+ /* In case of fork bigwavebuffer may already exist */
+ if (!bigwavebuffer_)
+ bigwavebuffer_ = new char[MAX_BLOCKS * bSize];
+
+ if (!isvalid ())
+ return false;
+
+ fillFormat (&format, rate, bits, channels);
+ rc = waveInOpen (&dev_, WAVE_MAPPER, &format, (DWORD_PTR) waveIn_callback,
+ (DWORD_PTR) this, CALLBACK_FUNCTION);
+ debug_printf ("%u = waveInOpen(rate=%d bits=%d channels=%d)", rc, rate, bits, channels);
+
+ if (rc == MMSYSERR_NOERROR)
+ {
+ if (!init (bSize))
+ return false;
+ }
+ return (rc == MMSYSERR_NOERROR);
+}
+
+void
+fhandler_dev_dsp::Audio_in::stop ()
+{
+ MMRESULT rc;
+ WAVEHDR *pHdr;
+
+ debug_printf ("dev_=%p", dev_);
+ if (dev_)
+ {
+ /* Note that waveInReset calls our callback for all incomplete buffers.
+ Since all the win32 wave functions appear to use a common lock,
+ we must not call into the wave API from the callback.
+ Otherwise we end up in a deadlock. */
+ rc = waveInReset (dev_);
+ debug_printf ("%u = waveInReset()", rc);
+
+ while (Qisr2app_->recv (&pHdr))
+ {
+ rc = waveInUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
+ debug_printf ("%u = waveInUnprepareHeader(%p)", rc, pHdr);
+ }
+
+ no_thread_exit_protect for_now (true);
+ rc = waveInClose (dev_);
+ debug_printf ("%u = waveInClose()", rc);
+
+ Qisr2app_->dellock ();
+ }
+}
+
+bool
+fhandler_dev_dsp::Audio_in::queueblock (WAVEHDR *pHdr)
+{
+ MMRESULT rc;
+ rc = waveInPrepareHeader (dev_, pHdr, sizeof (WAVEHDR));
+ debug_printf ("%u = waveInPrepareHeader(%p)", rc, pHdr);
+ if (rc == MMSYSERR_NOERROR)
+ {
+ rc = waveInAddBuffer (dev_, pHdr, sizeof (WAVEHDR));
+ debug_printf ("%u = waveInAddBuffer(%p)", rc, pHdr);
+ }
+ if (rc == MMSYSERR_NOERROR)
+ return true;
+
+ /* FIXME: Should the calling function return an error instead ?*/
+ pHdr->dwFlags = 0; /* avoid calling UnprepareHeader again */
+ pHdr->dwBytesRecorded = 0; /* no data will have been read */
+ Qisr2app_->send (pHdr);
+ return false;
+}
+
+bool
+fhandler_dev_dsp::Audio_in::init (unsigned blockSize)
+{
+ MMRESULT rc;
+ int i;
+
+ // try to queue all of our buffer for reception
+ Qisr2app_->reset ();
+ for (i = 0; i < MAX_BLOCKS; i++)
+ {
+ wavehdr_[i].lpData = &bigwavebuffer_[i * blockSize];
+ wavehdr_[i].dwBufferLength = blockSize;
+ wavehdr_[i].dwFlags = 0;
+ if (!queueblock (&wavehdr_[i]))
+ break;
+ }
+ pHdr_ = NULL;
+ rc = waveInStart (dev_);
+ debug_printf ("%u = waveInStart(), queued=%d", rc, i);
+ return (rc == MMSYSERR_NOERROR);
+}
+
+bool
+fhandler_dev_dsp::Audio_in::read (char *pSampleData, int &nBytes)
+{
+ int bytes_to_read = nBytes;
+ nBytes = 0;
+ debug_printf ("pSampleData=%p nBytes=%d", pSampleData, bytes_to_read);
+ while (bytes_to_read != 0)
+ { // Block till next sound has been read
+ if (!waitfordata ())
+ {
+ if (nBytes)
+ return true;
+ nBytes = -1;
+ return false;
+ }
+
+ // Handle gathering our blocks into smaller or larger buffer
+ int sizeleft = pHdr_->dwBytesRecorded - bufferIndex_;
+ if (bytes_to_read < sizeleft)
+ { // The current buffer holds more data than requested
+ memcpy (pSampleData, &pHdr_->lpData[bufferIndex_], bytes_to_read);
+ (this->*convert_) ((unsigned char *)pSampleData, bytes_to_read);
+ nBytes += bytes_to_read;
+ bufferIndex_ += bytes_to_read;
+ debug_printf ("got %d", bytes_to_read);
+ break; // done; use remaining data in next call to read
+ }
+ else
+ { // not enough or exact amount in the current buffer
+ if (sizeleft)
+ { // use up what we have
+ memcpy (pSampleData, &pHdr_->lpData[bufferIndex_], sizeleft);
+ (this->*convert_) ((unsigned char *)pSampleData, sizeleft);
+ nBytes += sizeleft;
+ bytes_to_read -= sizeleft;
+ pSampleData += sizeleft;
+ debug_printf ("got %d", sizeleft);
+ }
+ queueblock (pHdr_); // re-queue this block to ISR
+ pHdr_ = NULL; // need to wait for a new block
+ // if more samples are needed, we need a new block now
+ }
+ }
+ debug_printf ("end nBytes=%d", nBytes);
+ return true;
+}
+
+bool
+fhandler_dev_dsp::Audio_in::waitfordata ()
+{
+ WAVEHDR *pHdr;
+ MMRESULT rc;
+
+ if (pHdr_ != NULL)
+ return true;
+ while (!Qisr2app_->recv (&pHdr))
+ {
+ if (fh->is_nonblocking ())
+ {
+ set_errno (EAGAIN);
+ return false;
+ }
+ debug_printf ("100ms");
+ switch (cygwait (100))
+ {
+ case WAIT_SIGNALED:
+ if (!_my_tls.call_signal_handler ())
+ {
+ set_errno (EINTR);
+ return false;
+ }
+ break;
+ case WAIT_CANCELED:
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ default:
+ break;
+ }
+ }
+ if (pHdr->dwFlags) /* Zero if queued following error in queueblock */
+ {
+ /* Errors are ignored here. They will probbaly cause a failure
+ in the subsequent PrepareHeader */
+ rc = waveInUnprepareHeader (dev_, pHdr, sizeof (WAVEHDR));
+ debug_printf ("%u = waveInUnprepareHeader(%p)", rc, pHdr);
+ }
+ pHdr_ = pHdr;
+ bufferIndex_ = 0;
+ return true;
+}
+
+void fhandler_dev_dsp::Audio_in::default_buf_info (audio_buf_info *p,
+ int rate, int bits, int channels)
+{
+ p->fragstotal = MAX_BLOCKS;
+ p->fragsize = blockSize (rate, bits, channels);
+ p->fragments = 0;
+ p->bytes = 0;
+}
+
+void
+fhandler_dev_dsp::Audio_in::buf_info (audio_buf_info *p,
+ int rate, int bits, int channels)
+{
+ if (dev_)
+ {
+ p->fragstotal = MAX_BLOCKS;
+ p->fragsize = blockSize (rate, bits, channels);
+ p->fragments = Qisr2app_->query ();
+ if (pHdr_ != NULL)
+ p->bytes = pHdr_->dwBytesRecorded - bufferIndex_
+ + p->fragsize * p->fragments;
+ else
+ p->bytes = p->fragsize * p->fragments;
+ }
+ else
+ {
+ default_buf_info(p, rate, bits, channels);
+ }
+}
+
+inline void
+fhandler_dev_dsp::Audio_in::callback_blockfull (WAVEHDR *pHdr)
+{
+ Qisr2app_->send (pHdr);
+}
+
+static void CALLBACK
+waveIn_callback (HWAVEIN hWave, UINT msg, DWORD_PTR instance, DWORD_PTR param1,
+ DWORD_PTR param2)
+{
+ if (msg == WIM_DATA)
+ {
+ fhandler_dev_dsp::Audio_in *ptr =
+ (fhandler_dev_dsp::Audio_in *) instance;
+ ptr->callback_blockfull ((WAVEHDR *) param1);
+ }
+}
+
+
+/* ------------------------------------------------------------------------
+ /dev/dsp handler
+ ------------------------------------------------------------------------ */
+fhandler_dev_dsp::fhandler_dev_dsp ():
+ fhandler_base ()
+{
+ audio_in_ = NULL;
+ audio_out_ = NULL;
+ dev ().parse (FH_OSS_DSP);
+}
+
+ssize_t
+fhandler_dev_dsp::write (const void *ptr, size_t len)
+{
+ return base ()->_write (ptr, len);
+}
+
+void
+fhandler_dev_dsp::read (void *ptr, size_t& len)
+{
+ return base ()->_read (ptr, len);
+}
+
+int
+fhandler_dev_dsp::ioctl (unsigned int cmd, void *buf)
+{
+ return base ()->_ioctl (cmd, buf);
+}
+
+void
+fhandler_dev_dsp::fixup_after_fork (HANDLE parent)
+{
+ base ()->_fixup_after_fork (parent);
+}
+
+void
+fhandler_dev_dsp::fixup_after_exec ()
+{
+ base ()->_fixup_after_exec ();
+}
+
+
+int
+fhandler_dev_dsp::open (int flags, mode_t)
+{
+ int ret = 0, err = 0;
+ UINT num_in = 0, num_out = 0;
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ // Work out initial sample format & frequency, /dev/dsp defaults
+ audioformat_ = AFMT_U8;
+ audiofreq_ = 8000;
+ audiobits_ = 8;
+ audiochannels_ = 1;
+ switch (flags & O_ACCMODE)
+ {
+ case O_RDWR:
+ if ((num_in = waveInGetNumDevs ()) == 0)
+ err = ENXIO;
+ fallthrough;
+ case O_WRONLY:
+ if ((num_out = waveOutGetNumDevs ()) == 0)
+ err = ENXIO;
+ break;
+ case O_RDONLY:
+ if ((num_in = waveInGetNumDevs ()) == 0)
+ err = ENXIO;
+ break;
+ default:
+ err = EINVAL;
+ }
+
+ if (err)
+ set_errno (err);
+ else
+ ret = open_null (flags);
+
+ debug_printf ("ACCMODE=%y audio_in=%d audio_out=%d, err=%d, ret=%d",
+ flags & O_ACCMODE, num_in, num_out, err, ret);
+ return ret;
+}
+
+#define IS_WRITE() ((get_flags() & O_ACCMODE) != O_RDONLY)
+#define IS_READ() ((get_flags() & O_ACCMODE) != O_WRONLY)
+
+ssize_t
+fhandler_dev_dsp::_write (const void *ptr, size_t len)
+{
+ debug_printf ("ptr=%p len=%ld", ptr, len);
+ int len_s = len;
+ const char *ptr_s = static_cast <const char *> (ptr);
+
+ if (audio_out_)
+ /* nothing to do */;
+ else if (IS_WRITE ())
+ {
+ debug_printf ("Allocating");
+ if (!(audio_out_ = new Audio_out (this)))
+ return -1;
+
+ /* check for wave file & get parameters & skip header if possible. */
+
+ if (audio_out_->parsewav (ptr_s, len_s,
+ audiofreq_, audiobits_, audiochannels_))
+ debug_printf ("=> ptr_s=%p len_s=%d", ptr_s, len_s);
+ }
+ else
+ {
+ set_errno (EBADF); // device was opened for read?
+ return -1;
+ }
+
+ /* Open audio device properly with callbacks.
+ Private parameters were set in call to parsewav.
+ This is a no-op when there are successive writes in the same process */
+ if (!audio_out_->start ())
+ {
+ set_errno (EIO);
+ return -1;
+ }
+
+ int written = audio_out_->write (ptr_s, len_s);
+ if (written < 0)
+ {
+ if (len - len_s > 0)
+ return len - len_s;
+ return -1;
+ }
+ return len - len_s + written;
+}
+
+void
+fhandler_dev_dsp::_read (void *ptr, size_t& len)
+{
+ debug_printf ("ptr=%p len=%ld", ptr, len);
+
+ if (audio_in_)
+ /* nothing to do */;
+ else if (IS_READ ())
+ {
+ debug_printf ("Allocating");
+ if (!(audio_in_ = new Audio_in (this)))
+ {
+ len = (size_t)-1;
+ return;
+ }
+ audio_in_->setconvert (audioformat_);
+ }
+ else
+ {
+ len = (size_t)-1;
+ set_errno (EBADF); // device was opened for write?
+ return;
+ }
+
+ /* Open audio device properly with callbacks.
+ This is a noop when there are successive reads in the same process */
+ if (!audio_in_->start (audiofreq_, audiobits_, audiochannels_))
+ {
+ len = (size_t)-1;
+ set_errno (EIO);
+ return;
+ }
+
+ audio_in_->read ((char *)ptr, (int&)len);
+}
+
+void
+fhandler_dev_dsp::close_audio_in ()
+{
+ if (audio_in_)
+ {
+ audio_in_->stop ();
+ delete audio_in_;
+ audio_in_ = NULL;
+ }
+}
+
+void
+fhandler_dev_dsp::close_audio_out (bool immediately)
+{
+ if (audio_out_)
+ {
+ audio_out_->stop (immediately);
+ delete audio_out_;
+ audio_out_ = NULL;
+ }
+}
+
+int
+fhandler_dev_dsp::close ()
+{
+ debug_printf ("audio_in=%p audio_out=%p", audio_in_, audio_out_);
+ close_audio_in ();
+ close_audio_out ();
+ return fhandler_base::close ();
+}
+
+int
+fhandler_dev_dsp::_ioctl (unsigned int cmd, void *buf)
+{
+ debug_printf ("audio_in=%p audio_out=%p", audio_in_, audio_out_);
+ int *intbuf = (int *) buf;
+ switch (cmd)
+ {
+#define CASE(a) case a : debug_printf ("/dev/dsp: ioctl %s", #a);
+
+ CASE (SNDCTL_DSP_RESET)
+ close_audio_in ();
+ close_audio_out (true);
+ return 0;
+ break;
+
+ CASE (SNDCTL_DSP_GETBLKSIZE)
+ /* This is valid even if audio_X is NULL */
+ if (IS_WRITE ())
+ {
+ *intbuf = audio_out_->blockSize (audiofreq_,
+ audiobits_,
+ audiochannels_);
+ }
+ else
+ { // I am very sure that IS_READ is valid
+ *intbuf = audio_in_->blockSize (audiofreq_,
+ audiobits_,
+ audiochannels_);
+ }
+ return 0;
+
+ CASE (SNDCTL_DSP_SETFMT)
+ {
+ int nBits;
+ switch (*intbuf)
+ {
+ case AFMT_QUERY:
+ *intbuf = audioformat_;
+ return 0;
+ break;
+ case AFMT_U16_BE:
+ case AFMT_U16_LE:
+ case AFMT_S16_BE:
+ case AFMT_S16_LE:
+ nBits = 16;
+ break;
+ case AFMT_U8:
+ case AFMT_S8:
+ nBits = 8;
+ break;
+ default:
+ nBits = 0;
+ }
+ if (nBits && IS_WRITE ())
+ {
+ close_audio_out ();
+ if (audio_out_->query (audiofreq_, nBits, audiochannels_))
+ {
+ audiobits_ = nBits;
+ audioformat_ = *intbuf;
+ }
+ else
+ {
+ *intbuf = audiobits_;
+ return -1;
+ }
+ }
+ if (nBits && IS_READ ())
+ {
+ close_audio_in ();
+ if (audio_in_->query (audiofreq_, nBits, audiochannels_))
+ {
+ audiobits_ = nBits;
+ audioformat_ = *intbuf;
+ }
+ else
+ {
+ *intbuf = audiobits_;
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ CASE (SNDCTL_DSP_SPEED)
+ if (IS_WRITE ())
+ {
+ close_audio_out ();
+ if (audio_out_->query (*intbuf, audiobits_, audiochannels_))
+ audiofreq_ = *intbuf;
+ else
+ {
+ *intbuf = audiofreq_;
+ return -1;
+ }
+ }
+ if (IS_READ ())
+ {
+ close_audio_in ();
+ if (audio_in_->query (*intbuf, audiobits_, audiochannels_))
+ audiofreq_ = *intbuf;
+ else
+ {
+ *intbuf = audiofreq_;
+ return -1;
+ }
+ }
+ return 0;
+
+ CASE (SNDCTL_DSP_STEREO)
+ {
+ int nChannels = *intbuf + 1;
+ int res = _ioctl (SNDCTL_DSP_CHANNELS, &nChannels);
+ *intbuf = nChannels - 1;
+ return res;
+ }
+
+ CASE (SNDCTL_DSP_CHANNELS)
+ {
+ int nChannels = *intbuf;
+
+ if (IS_WRITE ())
+ {
+ close_audio_out ();
+ if (audio_out_->query (audiofreq_, audiobits_, nChannels))
+ audiochannels_ = nChannels;
+ else
+ {
+ *intbuf = audiochannels_;
+ return -1;
+ }
+ }
+ if (IS_READ ())
+ {
+ close_audio_in ();
+ if (audio_in_->query (audiofreq_, audiobits_, nChannels))
+ audiochannels_ = nChannels;
+ else
+ {
+ *intbuf = audiochannels_;
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ CASE (SNDCTL_DSP_GETOSPACE)
+ {
+ if (!IS_WRITE ())
+ {
+ set_errno(EBADF);
+ return -1;
+ }
+ audio_buf_info *p = (audio_buf_info *) buf;
+ if (audio_out_) {
+ audio_out_->buf_info (p, audiofreq_, audiobits_, audiochannels_);
+ } else {
+ Audio_out::default_buf_info(p, audiofreq_, audiobits_, audiochannels_);
+ }
+ debug_printf ("buf=%p frags=%d fragsize=%d bytes=%d",
+ buf, p->fragments, p->fragsize, p->bytes);
+ return 0;
+ }
+
+ CASE (SNDCTL_DSP_GETISPACE)
+ {
+ if (!IS_READ ())
+ {
+ set_errno(EBADF);
+ return -1;
+ }
+ audio_buf_info *p = (audio_buf_info *) buf;
+ if (audio_in_) {
+ audio_in_->buf_info (p, audiofreq_, audiobits_, audiochannels_);
+ } else {
+ Audio_in::default_buf_info(p, audiofreq_, audiobits_, audiochannels_);
+ }
+ debug_printf ("buf=%p frags=%d fragsize=%d bytes=%d",
+ buf, p->fragments, p->fragsize, p->bytes);
+ return 0;
+ }
+
+ CASE (SNDCTL_DSP_SETFRAGMENT)
+ // Fake!! esound & mikmod require this on non PowerPC platforms.
+ //
+ return 0;
+
+ CASE (SNDCTL_DSP_GETFMTS)
+ *intbuf = AFMT_S16_LE | AFMT_U8; // only native formats returned here
+ return 0;
+
+ CASE (SNDCTL_DSP_GETCAPS)
+ *intbuf = DSP_CAP_BATCH | DSP_CAP_DUPLEX;
+ return 0;
+
+ CASE (SNDCTL_DSP_POST)
+ CASE (SNDCTL_DSP_SYNC)
+ // Stop audio out device
+ close_audio_out ();
+ // Stop audio in device
+ close_audio_in ();
+ return 0;
+
+ default:
+ return fhandler_base::ioctl (cmd, buf);
+ break;
+
+#undef CASE
+ }
+}
+
+void
+fhandler_dev_dsp::_fixup_after_fork (HANDLE parent)
+{ // called from new child process
+ debug_printf ("audio_in=%p audio_out=%p",
+ audio_in_, audio_out_);
+
+ fhandler_base::fixup_after_fork (parent);
+ if (audio_in_)
+ audio_in_->fork_fixup (parent);
+ if (audio_out_)
+ audio_out_->fork_fixup (parent);
+}
+
+void
+fhandler_dev_dsp::_fixup_after_exec ()
+{
+ debug_printf ("audio_in=%p audio_out=%p, close_on_exec %d",
+ audio_in_, audio_out_, close_on_exec ());
+ if (!close_on_exec ())
+ {
+ audio_in_ = NULL;
+ audio_out_ = NULL;
+ }
+}
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 (&sect, 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 (&sect, 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 (&sect, 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 (&sect, 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);
+}
diff --git a/winsup/cygwin/fhandler/floppy.cc b/winsup/cygwin/fhandler/floppy.cc
new file mode 100644
index 000000000..e883ab697
--- /dev/null
+++ b/winsup/cygwin/fhandler/floppy.cc
@@ -0,0 +1,766 @@
+/* fhandler_floppy.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 <alloca.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <winioctl.h>
+#include <cygwin/rdevio.h>
+#include <cygwin/hdreg.h>
+#include <cygwin/fs.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "ntdll.h"
+
+#define IS_EOM(err) ((err) == ERROR_INVALID_PARAMETER \
+ || (err) == ERROR_SEEK \
+ || (err) == ERROR_SECTOR_NOT_FOUND)
+
+#define bytes_per_sector devbufalign
+
+/**********************************************************************/
+/* fhandler_dev_floppy */
+
+fhandler_dev_floppy::fhandler_dev_floppy ()
+ : fhandler_dev_raw (), status ()
+{
+}
+
+int
+fhandler_dev_floppy::get_drive_info (struct hd_geometry *geo)
+{
+ char dbuf[256];
+ char pbuf[256];
+
+ DISK_GEOMETRY_EX *dix = NULL;
+ DISK_GEOMETRY *di = NULL;
+ PARTITION_INFORMATION_EX *pix = NULL;
+ PARTITION_INFORMATION *pi = NULL;
+ DWORD bytes_read = 0;
+
+ /* Always try using the new EX ioctls first. If not available, fall back
+ to trying the non-EX ioctls which are unfortunately not implemented in
+ the floppy driver. */
+ if (get_major () != DEV_FLOPPY_MAJOR)
+ {
+ if (!DeviceIoControl (get_handle (),
+ IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, 0,
+ dbuf, 256, &bytes_read, NULL))
+ __seterrno ();
+ else
+ {
+ dix = (DISK_GEOMETRY_EX *) dbuf;
+ di = &dix->Geometry;
+ }
+ }
+ if (!di)
+ {
+ if (!DeviceIoControl (get_handle (),
+ IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0,
+ dbuf, 256, &bytes_read, NULL))
+ {
+ __seterrno ();
+ return -1;
+ }
+ di = (DISK_GEOMETRY *) dbuf;
+ }
+ if (dix) /* Don't try IOCTL_DISK_GET_PARTITION_INFO_EX if
+ IOCTL_DISK_GET_DRIVE_GEOMETRY_EX didn't work.
+ Probably a floppy.*/
+ {
+ if (!DeviceIoControl (get_handle (),
+ IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0,
+ pbuf, 256, &bytes_read, NULL))
+ __seterrno ();
+ else
+ pix = (PARTITION_INFORMATION_EX *) pbuf;
+ }
+ if (!pix && get_major () != DEV_FLOPPY_MAJOR)
+ {
+ /* It's unlikely that this code path will be used at all. Either the
+ _EX call already worked, or it's a floppy. But it doesn't hurt to
+ keep the code in. */
+ if (!DeviceIoControl (get_handle (),
+ IOCTL_DISK_GET_PARTITION_INFO, NULL, 0,
+ pbuf, 256, &bytes_read, NULL))
+ __seterrno ();
+ else
+ pi = (PARTITION_INFORMATION *) pbuf;
+ }
+ debug_printf ("disk geometry: (%D cyl)*(%u trk)*(%u sec)*(%u bps)",
+ di->Cylinders.QuadPart,
+ di->TracksPerCylinder,
+ di->SectorsPerTrack,
+ di->BytesPerSector);
+ bytes_per_sector = di->BytesPerSector;
+ if (pix)
+ {
+ debug_printf ("partition info: offset %D length %D",
+ pix->StartingOffset.QuadPart,
+ pix->PartitionLength.QuadPart);
+ drive_size = pix->PartitionLength.QuadPart;
+ }
+ else if (pi)
+ {
+ debug_printf ("partition info: offset %D length %D",
+ pi->StartingOffset.QuadPart,
+ pi->PartitionLength.QuadPart);
+ drive_size = pi->PartitionLength.QuadPart;
+ }
+ else /* Floppy drive. */
+ drive_size = di->Cylinders.QuadPart * di->TracksPerCylinder
+ * di->SectorsPerTrack * di->BytesPerSector;
+ if (geo)
+ {
+ geo->heads = di->TracksPerCylinder;
+ geo->sectors = di->SectorsPerTrack;
+ geo->cylinders = di->Cylinders.LowPart;
+ if (pix)
+ geo->start = pix->StartingOffset.QuadPart >> 9ULL;
+ else if (pi)
+ geo->start = pi->StartingOffset.QuadPart >> 9ULL;
+ else
+ geo->start = 0;
+ }
+ debug_printf ("drive size: %D", drive_size);
+
+ return 0;
+}
+
+/* Wrapper functions for ReadFile and WriteFile to simplify error handling. */
+BOOL
+fhandler_dev_floppy::read_file (void *buf, DWORD to_read, DWORD *read, int *err)
+{
+ BOOL ret;
+
+ *err = 0;
+ if (!(ret = ReadFile (get_handle (), buf, to_read, read, 0)))
+ *err = GetLastError ();
+ syscall_printf ("%d (err %d) = ReadFile (%p, %p, to_read %u, read %u, 0)",
+ ret, *err, get_handle (), buf, to_read, *read);
+ return ret;
+}
+
+/* See comment in write_file below. */
+BOOL
+fhandler_dev_floppy::lock_partition (DWORD to_write)
+{
+ DWORD bytes_read;
+
+ /* The simple case. We have only a single partition open anyway.
+ Try to lock the partition so that a subsequent write succeeds.
+ If there's some file handle open on one of the affected partitions,
+ this fails, but that's how it works...
+ The high partition major numbers don't have a partition 0. */
+ if (get_major () == DEV_FLOPPY_MAJOR
+ || get_major () >= DEV_SD_HIGHPART_START || get_minor () % 16 != 0)
+ {
+ if (!DeviceIoControl (get_handle (), FSCTL_LOCK_VOLUME,
+ NULL, 0, NULL, 0, &bytes_read, NULL))
+ {
+ debug_printf ("DeviceIoControl (FSCTL_LOCK_VOLUME) failed, %E");
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /* The tricky case. We're writing to the entire disk. What this code
+ basically does is to find out if the current write operation affects
+ one or more partitions on the disk. If so, it tries to lock all these
+ partitions and stores the handles for a subsequent close(). */
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ FILE_POSITION_INFORMATION fpi;
+ /* Allocate space for 4 times the maximum partition count we can handle.
+ The reason is that for *every* single logical drive in an extended
+ partition on an MBR drive, 3 filler entries with partition number set
+ to 0 are added into the partition table returned by
+ IOCTL_DISK_GET_DRIVE_LAYOUT_EX. The first of them reproduces the data
+ of the next partition entry, if any, except for the partiton number.
+ Then two entries with everything set to 0 follow. Well, the
+ documentation states that for MBR drives the number of partition entries
+ in the PARTITION_INFORMATION_EX array is always a multiple of 4, but,
+ nevertheless, how crappy is that layout? */
+ const DWORD size = sizeof (DRIVE_LAYOUT_INFORMATION_EX)
+ + 4 * MAX_PARTITIONS * sizeof (PARTITION_INFORMATION_EX);
+ PDRIVE_LAYOUT_INFORMATION_EX pdlix = (PDRIVE_LAYOUT_INFORMATION_EX)
+ alloca (size);
+ BOOL found = FALSE;
+
+ /* Fetch current file pointer position on disk. */
+ status = NtQueryInformationFile (get_handle (), &io, &fpi, sizeof fpi,
+ FilePositionInformation);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtQueryInformationFile(FilePositionInformation): %y",
+ status);
+ return FALSE;
+ }
+ /* Fetch drive layout to get start and end positions of partitions on disk. */
+ if (!DeviceIoControl (get_handle (), IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0,
+ pdlix, size, &bytes_read, NULL))
+ {
+ debug_printf ("DeviceIoControl(IOCTL_DISK_GET_DRIVE_LAYOUT_EX): %E");
+ return FALSE;
+ }
+ /* Scan through partition info to find the partition(s) into which we're
+ currently trying to write. */
+ PARTITION_INFORMATION_EX *ppie = pdlix->PartitionEntry;
+ for (DWORD i = 0; i < pdlix->PartitionCount; ++i, ++ppie)
+ {
+ /* A partition number of 0 denotes an extended partition or one of the
+ aforementioned filler entries. Just skip. */
+ if (ppie->PartitionNumber == 0)
+ continue;
+ /* Check if our writing range affects this partition. */
+ if (fpi.CurrentByteOffset.QuadPart < ppie->StartingOffset.QuadPart
+ + ppie->PartitionLength.QuadPart
+ && ppie->StartingOffset.QuadPart < fpi.CurrentByteOffset.QuadPart
+ + to_write)
+ {
+ /* Yes. Now check if we can handle it. We can only handle
+ up to MAX_PARTITIONS partitions. The partition numbering is
+ one-based, so we decrement the partition number by 1 when using
+ as index into the partition array. */
+ DWORD &part_no = ppie->PartitionNumber;
+ if (part_no >= MAX_PARTITIONS)
+ return FALSE;
+ found = TRUE;
+ debug_printf ("%u %D->%D : %D->%D", part_no,
+ ppie->StartingOffset.QuadPart,
+ ppie->StartingOffset.QuadPart
+ + ppie->PartitionLength.QuadPart,
+ fpi.CurrentByteOffset.QuadPart,
+ fpi.CurrentByteOffset.QuadPart + to_write);
+ /* Do we already have partitions? If not, create it. */
+ if (!partitions)
+ {
+ partitions = (part_t *) ccalloc_abort (HEAP_FHANDLER, 1,
+ sizeof (part_t));
+ partitions->refcnt = 1;
+ }
+ /* Next, check if the partition is already open. If so, skip it. */
+ if (partitions->hdl[part_no - 1])
+ continue;
+ /* Now open the partition and lock it. */
+ WCHAR part[MAX_PATH], *p;
+ NTSTATUS status;
+ UNICODE_STRING upart;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+
+ sys_mbstowcs (part, MAX_PATH, get_win32_name ());
+ p = wcschr (part, L'\0') - 1;
+ __small_swprintf (p, L"%d", part_no);
+ RtlInitUnicodeString (&upart, part);
+ InitializeObjectAttributes (&attr, &upart,
+ OBJ_CASE_INSENSITIVE
+ | ((get_flags () & O_CLOEXEC)
+ ? 0 : OBJ_INHERIT),
+ NULL, NULL);
+ status = NtOpenFile (&partitions->hdl[part_no - 1],
+ GENERIC_READ | GENERIC_WRITE, &attr,
+ &io, FILE_SHARE_READ | FILE_SHARE_WRITE, 0);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtCreateFile(%W): %y", part, status);
+ return FALSE;
+ }
+ if (!DeviceIoControl (partitions->hdl[part_no - 1], FSCTL_LOCK_VOLUME,
+ NULL, 0, NULL, 0, &bytes_read, NULL))
+ {
+ debug_printf ("DeviceIoControl (%W, FSCTL_LOCK_VOLUME) "
+ "failed, %E", part);
+ return FALSE;
+ }
+ }
+ }
+ /* If we didn't find a single matching partition, the "Access denied"
+ had another reason, so return FALSE in that case. */
+ return found;
+}
+
+BOOL
+fhandler_dev_floppy::write_file (const void *buf, DWORD to_write,
+ DWORD *written, int *err)
+{
+ BOOL ret;
+
+ *err = 0;
+ if (!(ret = WriteFile (get_handle (), buf, to_write, written, 0)))
+ *err = GetLastError ();
+ /* When writing to a disk or partition an "Access denied" error may
+ occur due to the raw disk write restriction.
+ See http://support.microsoft.com/kb/942448 for details.
+ What we do here is to lock the affected partition(s) and retry. */
+ if (*err == ERROR_ACCESS_DENIED
+ && get_major () != DEV_CDROM_MAJOR
+ && (get_flags () & O_ACCMODE) != O_RDONLY
+ && lock_partition (to_write))
+ {
+ *err = 0;
+ if (!(ret = WriteFile (get_handle (), buf, to_write, written, 0)))
+ *err = GetLastError ();
+ }
+ syscall_printf ("%d (err %d) = WriteFile (%p, %p, write %u, written %u, 0)",
+ ret, *err, get_handle (), buf, to_write, *written);
+ return ret;
+}
+
+int
+fhandler_dev_floppy::open (int flags, mode_t)
+{
+ int ret = fhandler_dev_raw::open (flags);
+
+ if (ret)
+ {
+ DWORD bytes_read;
+
+ if (get_drive_info (NULL))
+ {
+ close ();
+ return 0;
+ }
+ if (!(flags & O_DIRECT))
+ {
+ /* Create sector-aligned buffer. As default buffer size, we're using
+ some big, sector-aligned value. Since direct blockdev IO is
+ usually non-buffered and non-cached, the performance without
+ buffering is worse than access to a file system on same device.
+ Whoever uses O_DIRECT has my condolences. */
+ devbufsiz = MAX (16 * bytes_per_sector, 65536);
+ devbufalloc = new char [devbufsiz + devbufalign];
+ devbuf = (char *) roundup2 ((uintptr_t) devbufalloc,
+ (uintptr_t) devbufalign);
+ }
+
+ /* If we're not trying to access a floppy disk, make sure we're actually
+ allowed to read *all* of the device or volume. This is actually
+ documented in the MSDN CreateFile man page. */
+ if (get_major () != DEV_FLOPPY_MAJOR
+ && !DeviceIoControl (get_handle (), FSCTL_ALLOW_EXTENDED_DASD_IO,
+ NULL, 0, NULL, 0, &bytes_read, NULL))
+ debug_printf ("DeviceIoControl (FSCTL_ALLOW_EXTENDED_DASD_IO) "
+ "failed, %E");
+ }
+
+ return ret;
+}
+
+int
+fhandler_dev_floppy::close ()
+{
+ int ret = fhandler_dev_raw::close ();
+
+ if (partitions && InterlockedDecrement (&partitions->refcnt) == 0)
+ {
+ for (int i = 0; i < MAX_PARTITIONS; ++i)
+ if (partitions->hdl[i])
+ NtClose (partitions->hdl[i]);
+ cfree (partitions);
+ }
+ return ret;
+}
+
+int
+fhandler_dev_floppy::dup (fhandler_base *child, int flags)
+{
+ int ret = fhandler_dev_raw::dup (child, flags);
+
+ if (!ret && partitions)
+ InterlockedIncrement (&partitions->refcnt);
+ return ret;
+}
+
+inline off_t
+fhandler_dev_floppy::get_current_position ()
+{
+ IO_STATUS_BLOCK io;
+ FILE_POSITION_INFORMATION fpi = { { QuadPart : 0LL } };
+
+ NtQueryInformationFile (get_handle (), &io, &fpi, sizeof fpi,
+ FilePositionInformation);
+ return fpi.CurrentByteOffset.QuadPart;
+}
+
+void
+fhandler_dev_floppy::raw_read (void *ptr, size_t& ulen)
+{
+ DWORD bytes_read = 0;
+ DWORD read2;
+ DWORD bytes_to_read;
+ int ret;
+ size_t len = ulen;
+ char *tgt;
+ char *p = (char *) ptr;
+
+ /* Checking a previous end of media */
+ if (eom_detected () && !lastblk_to_read ())
+ {
+ set_errno (ENOSPC);
+ goto err;
+ }
+
+ if (devbuf)
+ {
+ while (len > 0)
+ {
+ if (devbufstart < devbufend)
+ {
+ bytes_to_read = MIN (len, devbufend - devbufstart);
+ debug_printf ("read %u bytes from buffer (rest %u)",
+ bytes_to_read,
+ devbufend - devbufstart - bytes_to_read);
+ memcpy (p, devbuf + devbufstart, bytes_to_read);
+ len -= bytes_to_read;
+ p += bytes_to_read;
+ bytes_read += bytes_to_read;
+ devbufstart += bytes_to_read;
+
+ if (lastblk_to_read ())
+ {
+ lastblk_to_read (false);
+ break;
+ }
+ }
+ if (len > 0)
+ {
+ if (len >= devbufsiz)
+ {
+ bytes_to_read = (len / bytes_per_sector) * bytes_per_sector;
+ tgt = p;
+ }
+ else
+ {
+ tgt = devbuf;
+ bytes_to_read = devbufsiz;
+ }
+ off_t current_position = get_current_position ();
+ if (current_position + bytes_to_read >= drive_size)
+ bytes_to_read = drive_size - current_position;
+ if (!bytes_to_read)
+ break;
+
+ debug_printf ("read %u bytes from pos %U %s", bytes_to_read,
+ current_position,
+ len < devbufsiz ? "into buffer" : "directly");
+ if (!read_file (tgt, bytes_to_read, &read2, &ret))
+ {
+ if (!IS_EOM (ret))
+ {
+ __seterrno ();
+ goto err;
+ }
+
+ eom_detected (true);
+
+ if (!read2)
+ {
+ if (!bytes_read)
+ {
+ debug_printf ("return -1, set errno to ENOSPC");
+ set_errno (ENOSPC);
+ goto err;
+ }
+ break;
+ }
+ lastblk_to_read (true);
+ }
+ if (!read2)
+ break;
+ if (tgt == devbuf)
+ {
+ devbufstart = 0;
+ devbufend = read2;
+ }
+ else
+ {
+ len -= read2;
+ p += read2;
+ bytes_read += read2;
+ }
+ }
+ }
+ }
+ else
+ {
+ off_t current_position = get_current_position ();
+ bytes_to_read = len;
+ if (current_position + bytes_to_read >= drive_size)
+ bytes_to_read = drive_size - current_position;
+ debug_printf ("read %u bytes from pos %U directly", bytes_to_read,
+ current_position);
+ if (bytes_to_read && !read_file (p, bytes_to_read, &bytes_read, &ret))
+ {
+ if (!IS_EOM (ret))
+ {
+ __seterrno ();
+ goto err;
+ }
+ if (bytes_read)
+ eom_detected (true);
+ else
+ {
+ debug_printf ("return -1, set errno to ENOSPC");
+ set_errno (ENOSPC);
+ goto err;
+ }
+ }
+ }
+
+ ulen = (size_t) bytes_read;
+ return;
+
+err:
+ ulen = (size_t) -1;
+}
+
+ssize_t
+fhandler_dev_floppy::raw_write (const void *ptr, size_t len)
+{
+ DWORD bytes_written = 0;
+ char *p = (char *) ptr;
+ int ret;
+
+ /* Checking a previous end of media */
+ if (eom_detected ())
+ {
+ set_errno (ENOSPC);
+ return -1;
+ }
+
+ if (!len)
+ return 0;
+
+ if (devbuf)
+ {
+ DWORD cplen, written;
+
+ /* First check if we have an active read buffer. If so, try to fit in
+ the start of the input buffer and write out the entire result.
+ This also covers the situation after lseek since lseek fills the read
+ buffer in case we seek to an address which is not sector aligned. */
+ if (devbufend && devbufstart < devbufend)
+ {
+ off_t current_pos = get_current_position ();
+ cplen = MIN (len, devbufend - devbufstart);
+ memcpy (devbuf + devbufstart, p, cplen);
+ LARGE_INTEGER off = { QuadPart:current_pos - devbufend };
+ if (!SetFilePointerEx (get_handle (), off, NULL, FILE_BEGIN))
+ {
+ devbufstart = devbufend = 0;
+ __seterrno ();
+ return -1;
+ }
+ if (!write_file (devbuf, devbufend, &written, &ret))
+ {
+ devbufstart = devbufend = 0;
+ goto err;
+ }
+ /* Align pointers, lengths, etc. */
+ cplen = MIN (cplen, written);
+ devbufstart += cplen;
+ if (devbufstart >= devbufend)
+ devbufstart = devbufend = 0;
+ p += cplen;
+ len -= cplen;
+ bytes_written += cplen;
+ }
+ /* As long as there's still something left in the input buffer ... */
+ while (len)
+ {
+ /* Compute the length to write. The problem is that the underlying
+ driver may require sector aligned read/write. So we copy the data
+ over to devbuf, which is guaranteed to be sector aligned. */
+ cplen = MIN (len, devbufsiz);
+ if (cplen >= bytes_per_sector)
+ /* If the remaining len is >= sector size, write out the maximum
+ possible multiple of the sector size which fits into devbuf. */
+ cplen = rounddown (cplen, bytes_per_sector);
+ else
+ {
+ /* If len < sector size, read in the next sector, seek back,
+ and just copy the new data over the old one before writing. */
+ LARGE_INTEGER off = { QuadPart:get_current_position () };
+ if (!read_file (devbuf, bytes_per_sector, &written, &ret))
+ goto err;
+ if (!SetFilePointerEx (get_handle (), off, NULL, FILE_BEGIN))
+ {
+ __seterrno ();
+ return -1;
+ }
+ }
+ memcpy (devbuf, p, cplen);
+ if (!write_file (devbuf, MAX (cplen, bytes_per_sector), &written,
+ &ret))
+ {
+ bytes_written += MIN (cplen, written);
+ goto err;
+ }
+ cplen = MIN (cplen, written);
+ p += cplen;
+ len -= cplen;
+ bytes_written += cplen;
+ /* If we overwrote, revalidate devbuf. It still contains the
+ content from the above read/modify/write. Revalidating makes
+ sure lseek reports the correct position. */
+ if (cplen < bytes_per_sector)
+ {
+ devbufstart = cplen;
+ devbufend = bytes_per_sector;
+ }
+ }
+ return (ssize_t) bytes_written;
+ }
+
+ /* In O_DIRECT case, just write. */
+ if (write_file (p, len, &bytes_written, &ret))
+ return (ssize_t) bytes_written;
+
+err:
+ if (IS_EOM (ret))
+ {
+ eom_detected (true);
+ if (!bytes_written)
+ set_errno (ENOSPC);
+ }
+ else if (!bytes_written)
+ __seterrno ();
+ /* Cast is required, otherwise the error return value is (DWORD)-1. */
+ return (ssize_t) bytes_written ?: -1;
+}
+
+off_t
+fhandler_dev_floppy::lseek (off_t offset, int whence)
+{
+ char buf[bytes_per_sector];
+ off_t current_pos = (off_t) -1;
+ LARGE_INTEGER sector_aligned_offset;
+ size_t bytes_left;
+
+ if (whence == SEEK_END)
+ {
+ offset += drive_size;
+ whence = SEEK_SET;
+ }
+ else if (whence == SEEK_CUR)
+ {
+ current_pos = get_current_position ();
+ off_t exact_pos = current_pos - (devbufend - devbufstart);
+ /* Shortcut when used to get current position. */
+ if (offset == 0)
+ return exact_pos;
+ offset += exact_pos;
+ whence = SEEK_SET;
+ }
+
+ if (whence != SEEK_SET || offset < 0 || offset > drive_size)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ /* If new position is in buffered range, adjust buffer and return */
+ if (devbufstart < devbufend)
+ {
+ if (current_pos == (off_t) -1)
+ current_pos = get_current_position ();
+ if (current_pos - devbufend <= offset && offset <= current_pos)
+ {
+ devbufstart = devbufend - (current_pos - offset);
+ return offset;
+ }
+ }
+
+ sector_aligned_offset.QuadPart = rounddown (offset, bytes_per_sector);
+ bytes_left = offset - sector_aligned_offset.QuadPart;
+
+ /* Invalidate buffer. */
+ devbufstart = devbufend = 0;
+
+ if (!SetFilePointerEx (get_handle (), sector_aligned_offset, NULL,
+ FILE_BEGIN))
+ {
+ __seterrno ();
+ return -1;
+ }
+
+ eom_detected (false);
+
+ if (bytes_left)
+ {
+ raw_read (buf, bytes_left);
+ if (bytes_left == (size_t) -1)
+ return -1;
+ }
+
+ return sector_aligned_offset.QuadPart + bytes_left;
+}
+
+int
+fhandler_dev_floppy::ioctl (unsigned int cmd, void *buf)
+{
+ int ret = 0;
+ DWORD bytes_read;
+
+ switch (cmd)
+ {
+ case HDIO_GETGEO:
+ debug_printf ("HDIO_GETGEO");
+ ret = get_drive_info ((struct hd_geometry *) buf);
+ break;
+ case BLKGETSIZE:
+ case BLKGETSIZE64:
+ debug_printf ("BLKGETSIZE");
+ if (cmd == BLKGETSIZE)
+ *(long *)buf = drive_size >> 9UL;
+ else
+ *(off_t *)buf = drive_size;
+ break;
+ case BLKRRPART:
+ debug_printf ("BLKRRPART");
+ if (!DeviceIoControl (get_handle (), IOCTL_DISK_UPDATE_PROPERTIES,
+ NULL, 0, NULL, 0, &bytes_read, NULL))
+ {
+ __seterrno ();
+ ret = -1;
+ }
+ else
+ get_drive_info (NULL);
+ break;
+ case BLKSSZGET:
+ debug_printf ("BLKSSZGET");
+ *(int *)buf = (int) bytes_per_sector;
+ break;
+ case BLKIOMIN:
+ debug_printf ("BLKIOMIN");
+ *(int *)buf = (int) bytes_per_sector;
+ break;
+ case BLKIOOPT:
+ debug_printf ("BLKIOOPT");
+ *(int *)buf = (int) bytes_per_sector;
+ break;
+ case BLKPBSZGET:
+ debug_printf ("BLKPBSZGET");
+ *(int *)buf = (int) bytes_per_sector;
+ break;
+ case BLKALIGNOFF:
+ debug_printf ("BLKALIGNOFF");
+ *(int *)buf = 0;
+ break;
+ default:
+ ret = fhandler_dev_raw::ioctl (cmd, buf);
+ break;
+ }
+ return ret;
+}
+
diff --git a/winsup/cygwin/fhandler/mqueue.cc b/winsup/cygwin/fhandler/mqueue.cc
new file mode 100644
index 000000000..6b94bca85
--- /dev/null
+++ b/winsup/cygwin/fhandler/mqueue.cc
@@ -0,0 +1,1009 @@
+/* fhandler_mqueue.cc: fhandler for POSIX message queue
+
+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 "shared_info.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "clock.h"
+#include <stdio.h>
+#include <mqueue.h>
+#include <sys/param.h>
+
+#define MSGSIZE(i) roundup((i), sizeof(long))
+
+#define FILESIZE 80
+
+struct mq_attr defattr = { 0, 10, 8192, 0 }; /* Linux defaults. */
+
+fhandler_mqueue::fhandler_mqueue () :
+ fhandler_disk_file ()
+{
+ filebuf = (char *) ccalloc_abort (HEAP_BUF, 1, FILESIZE);
+}
+
+fhandler_mqueue::~fhandler_mqueue ()
+{
+ cfree (filebuf);
+}
+
+bool
+fhandler_mqueue::valid_path ()
+{
+ const char *posix_basename = get_name () + MQ_LEN;
+ size_t len = strlen (posix_basename);
+ if (len > 0 && len <= NAME_MAX && !strpbrk (posix_basename, "/\\"))
+ return true;
+ return false;
+}
+
+int
+fhandler_mqueue::open (int flags, mode_t mode)
+{
+ if (!valid_path ())
+ {
+ set_errno (EINVAL);
+ return 0;
+ }
+ /* FIXME: reopen by handle semantics missing yet */
+ flags &= ~(O_NOCTTY | O_PATH | O_BINARY | O_TEXT);
+ return mq_open (flags, mode, NULL);
+}
+
+int
+fhandler_mqueue::mq_open (int oflags, mode_t mode, struct mq_attr *attr)
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ PUNICODE_STRING mqstream;
+ OBJECT_ATTRIBUTES oa;
+ struct mq_info *mqinfo = NULL;
+ bool created = false;
+
+ if ((oflags & ~(O_ACCMODE | O_CLOEXEC | O_CREAT | O_EXCL | O_NONBLOCK))
+ || (oflags & O_ACCMODE) == O_ACCMODE)
+ {
+ set_errno (EINVAL);
+ return 0;
+ }
+
+ /* attach a stream suffix to the NT filename, thus creating a stream. */
+ mqstream = pc.get_nt_native_path (&ro_u_mq_suffix);
+ pc.get_object_attr (oa, sec_none_nih);
+
+again:
+ if (oflags & O_CREAT)
+ {
+ /* Create and disallow sharing */
+ status = NtCreateFile (&get_handle (),
+ GENERIC_READ | GENERIC_WRITE | DELETE
+ | SYNCHRONIZE, &oa, &io, NULL,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_DELETE,
+ FILE_CREATE,
+ FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_SYNCHRONOUS_IO_NONALERT,
+ NULL, 0);
+ if (!NT_SUCCESS (status))
+ {
+ if (status == STATUS_OBJECT_NAME_COLLISION && (oflags & O_EXCL) == 0)
+ goto exists;
+ __seterrno_from_nt_status (status);
+ return 0;
+ }
+ if (pc.has_acls ())
+ set_created_file_access (get_handle (), pc, mode);
+ created = true;
+ goto out;
+ }
+exists:
+ /* Open the file, and loop while detecting a sharing violation. */
+ while (true)
+ {
+ status = NtOpenFile (&get_handle (),
+ GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
+ &oa, &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_SYNCHRONOUS_IO_NONALERT);
+ if (NT_SUCCESS (status))
+ break;
+ if (status == STATUS_OBJECT_NAME_NOT_FOUND && (oflags & O_CREAT))
+ goto again;
+ if (status != STATUS_SHARING_VIOLATION)
+ {
+ __seterrno_from_nt_status (status);
+ return 0;
+ }
+ Sleep (100L);
+ }
+out:
+ /* We need the filename without STREAM_SUFFIX later on */
+ mqstream->Length -= ro_u_mq_suffix.Length;
+ mqstream->Buffer[mqstream->Length / sizeof (WCHAR)] = L'\0';
+
+ if (created)
+ {
+ if (attr == NULL)
+ attr = &defattr;
+ /* Check minimum and maximum values. The max values are pretty much
+ arbitrary, taken from the linux mq_overview man page, up to Linux
+ 3.4. These max values make sure that the internal mq_fattr
+ structure can use 32 bit types. */
+ if (attr->mq_maxmsg <= 0 || attr->mq_maxmsg > 32768
+ || attr->mq_msgsize <= 0 || attr->mq_msgsize > 1048576)
+ set_errno (EINVAL);
+ else
+ mqinfo = mqinfo_create (attr, mode, oflags & O_NONBLOCK);
+ }
+ else
+ mqinfo = mqinfo_open (oflags & O_NONBLOCK);
+ mq_open_finish (mqinfo != NULL, created);
+ /* Set fhandler open flags */
+ if (mqinfo)
+ {
+ set_access (GENERIC_READ | SYNCHRONIZE);
+ close_on_exec (true);
+ set_flags (oflags | O_CLOEXEC, O_BINARY);
+ set_open_status ();
+ }
+ return mqinfo ? 1 : 0;
+}
+
+struct mq_info *
+fhandler_mqueue::_mqinfo (SIZE_T filesize, mode_t mode, int flags,
+ bool just_open)
+{
+ WCHAR buf[NAME_MAX + sizeof ("mqueue/XXX")];
+ UNICODE_STRING uname;
+ OBJECT_ATTRIBUTES oa;
+ NTSTATUS status;
+ LARGE_INTEGER fsiz = { QuadPart: (LONGLONG) filesize };
+ PVOID mptr = NULL;
+
+ /* Set sectsize prior to using filesize in NtMapViewOfSection. It will
+ get pagesize aligned, which breaks the next NtMapViewOfSection in fork. */
+ mqinfo ()->mqi_sectsize = filesize;
+ mqinfo ()->mqi_mode = mode;
+ set_nonblocking (flags & O_NONBLOCK);
+
+ __small_swprintf (buf, L"mqueue/mtx%s", get_name ());
+ RtlInitUnicodeString (&uname, buf);
+ InitializeObjectAttributes (&oa, &uname, OBJ_OPENIF | OBJ_CASE_INSENSITIVE,
+ get_shared_parent_dir (),
+ everyone_sd (CYG_MUTANT_ACCESS));
+ status = NtCreateMutant (&mqinfo ()->mqi_lock, CYG_MUTANT_ACCESS, &oa,
+ FALSE);
+ if (!NT_SUCCESS (status))
+ goto err;
+
+ wcsncpy (buf + 7, L"snd", 3);
+ /* same length, no RtlInitUnicodeString required */
+ InitializeObjectAttributes (&oa, &uname, OBJ_OPENIF | OBJ_CASE_INSENSITIVE,
+ get_shared_parent_dir (),
+ everyone_sd (CYG_EVENT_ACCESS));
+ status = NtCreateEvent (&mqinfo ()->mqi_waitsend, CYG_EVENT_ACCESS, &oa,
+ NotificationEvent, FALSE);
+ if (!NT_SUCCESS (status))
+ goto err;
+ wcsncpy (buf + 7, L"rcv", 3);
+ /* same length, same attributes, no more init required */
+ status = NtCreateEvent (&mqinfo ()->mqi_waitrecv, CYG_EVENT_ACCESS, &oa,
+ NotificationEvent, FALSE);
+ if (!NT_SUCCESS (status))
+ goto err;
+
+ InitializeObjectAttributes (&oa, NULL, 0, NULL, NULL);
+ status = NtCreateSection (&mqinfo ()->mqi_sect, SECTION_ALL_ACCESS, &oa,
+ &fsiz, PAGE_READWRITE, SEC_COMMIT, get_handle ());
+ if (!NT_SUCCESS (status))
+ goto err;
+
+ status = NtMapViewOfSection (mqinfo ()->mqi_sect, NtCurrentProcess (),
+ &mptr, 0, filesize, NULL, &filesize,
+ ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
+ if (!NT_SUCCESS (status))
+ goto err;
+
+ mqinfo ()->mqi_hdr = (struct mq_hdr *) mptr;
+
+ /* Special problem on Cygwin. /dev/mqueue is just a simple dir,
+ so there's a chance normal files are created in there. */
+ if (just_open && mqinfo ()->mqi_hdr->mqh_magic != MQI_MAGIC)
+ {
+ status = STATUS_ACCESS_DENIED;
+ goto err;
+ }
+
+ mqinfo ()->mqi_magic = MQI_MAGIC;
+ return mqinfo ();
+
+err:
+ if (mqinfo ()->mqi_sect)
+ NtClose (mqinfo ()->mqi_sect);
+ if (mqinfo ()->mqi_waitrecv)
+ NtClose (mqinfo ()->mqi_waitrecv);
+ if (mqinfo ()->mqi_waitsend)
+ NtClose (mqinfo ()->mqi_waitsend);
+ if (mqinfo ()->mqi_lock)
+ NtClose (mqinfo ()->mqi_lock);
+ __seterrno_from_nt_status (status);
+ return NULL;
+}
+
+struct mq_info *
+fhandler_mqueue::mqinfo_open (int flags)
+{
+ FILE_STANDARD_INFORMATION fsi;
+ IO_STATUS_BLOCK io;
+ NTSTATUS status;
+ mode_t mode;
+
+ fsi.EndOfFile.QuadPart = 0;
+ status = NtQueryInformationFile (get_handle (), &io, &fsi, sizeof fsi,
+ FileStandardInformation);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return NULL;
+ }
+ if (get_file_attribute (get_handle (), pc, &mode, NULL, NULL))
+ mode = STD_RBITS | STD_WBITS;
+
+ return _mqinfo (fsi.EndOfFile.QuadPart, mode, flags, true);
+}
+
+struct mq_info *
+fhandler_mqueue::mqinfo_create (struct mq_attr *attr, mode_t mode, int flags)
+{
+ long msgsize;
+ off_t filesize = 0;
+ FILE_END_OF_FILE_INFORMATION feofi;
+ IO_STATUS_BLOCK io;
+ NTSTATUS status;
+ struct mq_info *mqinfo = NULL;
+
+ msgsize = MSGSIZE (attr->mq_msgsize);
+ filesize = sizeof (struct mq_hdr)
+ + (attr->mq_maxmsg * (sizeof (struct msg_hdr) + msgsize));
+ feofi.EndOfFile.QuadPart = filesize;
+ status = NtSetInformationFile (get_handle (), &io, &feofi, sizeof feofi,
+ FileEndOfFileInformation);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return NULL;
+ }
+
+ mqinfo = _mqinfo (filesize, mode, flags, false);
+
+ if (mqinfo)
+ {
+ /* Initialize header at beginning of file */
+ /* Create free list with all messages on it */
+ int8_t *mptr;
+ struct mq_hdr *mqhdr;
+ struct msg_hdr *msghdr;
+
+ mptr = (int8_t *) mqinfo->mqi_hdr;
+ mqhdr = mqinfo->mqi_hdr;
+ mqhdr->mqh_attr.mq_flags = 0;
+ mqhdr->mqh_attr.mq_maxmsg = attr->mq_maxmsg;
+ mqhdr->mqh_attr.mq_msgsize = attr->mq_msgsize;
+ mqhdr->mqh_attr.mq_curmsgs = 0;
+ mqhdr->mqh_nwait = 0;
+ mqhdr->mqh_pid = 0;
+ mqhdr->mqh_head = 0;
+ mqhdr->mqh_magic = MQI_MAGIC;
+ long index = sizeof (struct mq_hdr);
+ mqhdr->mqh_free = index;
+ for (int i = 0; i < attr->mq_maxmsg - 1; i++)
+ {
+ msghdr = (struct msg_hdr *) &mptr[index];
+ index += sizeof (struct msg_hdr) + msgsize;
+ msghdr->msg_next = index;
+ }
+ msghdr = (struct msg_hdr *) &mptr[index];
+ msghdr->msg_next = 0; /* end of free list */
+ }
+
+ return mqinfo;
+}
+
+void
+fhandler_mqueue::mq_open_finish (bool success, bool created)
+{
+ NTSTATUS status;
+ HANDLE def_stream;
+ OBJECT_ATTRIBUTES oa;
+ IO_STATUS_BLOCK io;
+
+ if (get_handle ())
+ {
+ /* If we have an open queue stream handle, close it and set it to NULL */
+ HANDLE queue_stream = get_handle ();
+ set_handle (NULL);
+ if (success)
+ {
+ /* In case of success, open the default stream for reading. This
+ can be used to implement various IO functions without exposing
+ the actual message queue. */
+ pc.get_object_attr (oa, sec_none_nih);
+ status = NtOpenFile (&def_stream, GENERIC_READ | SYNCHRONIZE,
+ &oa, &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_SYNCHRONOUS_IO_NONALERT);
+ if (NT_SUCCESS (status))
+ set_handle (def_stream);
+ else /* Note that we don't treat this as an error! */
+ {
+ debug_printf ("Opening default stream failed: status %y", status);
+ nohandle (true);
+ }
+ }
+ else if (created)
+ {
+ /* In case of error at creation time, delete the file */
+ FILE_DISPOSITION_INFORMATION disp = { TRUE };
+
+ NtSetInformationFile (queue_stream, &io, &disp, sizeof disp,
+ FileDispositionInformation);
+ /* We also have to set the delete disposition on the default stream,
+ otherwise only the queue stream will get deleted */
+ pc.get_object_attr (oa, sec_none_nih);
+ status = NtOpenFile (&def_stream, DELETE, &oa, &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT);
+ if (NT_SUCCESS (status))
+ {
+ NtSetInformationFile (def_stream, &io, &disp, sizeof disp,
+ FileDispositionInformation);
+ NtClose (def_stream);
+ }
+ }
+ NtClose (queue_stream);
+ }
+}
+
+char *
+fhandler_mqueue::get_proc_fd_name (char *buf)
+{
+ return strcpy (buf, strrchr (get_name (), '/'));
+}
+
+int
+fhandler_mqueue::fcntl (int cmd, intptr_t arg)
+{
+ int res;
+
+ switch (cmd)
+ {
+ case F_GETFD:
+ res = close_on_exec () ? FD_CLOEXEC : 0;
+ break;
+ case F_GETFL:
+ res = get_flags ();
+ debug_printf ("GETFL: %y", res);
+ break;
+ default:
+ set_errno (EINVAL);
+ res = -1;
+ break;
+ }
+ return res;
+}
+
+/* Do what fhandler_virtual does for read/lseek */
+bool
+fhandler_mqueue::fill_filebuf ()
+{
+ unsigned long qsize = 0;
+ int notify = 0;
+ int signo = 0;
+ int notify_pid = 0;
+
+ if (mutex_lock (mqinfo ()->mqi_lock, true) == 0)
+ {
+ struct mq_hdr *mqhdr = mqinfo ()->mqi_hdr;
+ int8_t *mptr = (int8_t *) mqhdr;
+ struct msg_hdr *msghdr;
+ for (long index = mqhdr->mqh_head; index; index = msghdr->msg_next)
+ {
+ msghdr = (struct msg_hdr *) &mptr[index];
+ qsize += msghdr->msg_len;
+ }
+ if (mqhdr->mqh_pid)
+ {
+ notify = mqhdr->mqh_event.sigev_notify;
+ if (notify == SIGEV_SIGNAL)
+ signo = mqhdr->mqh_event.sigev_signo;
+ notify_pid = mqhdr->mqh_pid;
+ }
+ mutex_unlock (mqinfo ()->mqi_lock);
+ }
+ /* QSIZE: bytes of all current msgs
+ NOTIFY: sigev_notify if there's a notifier
+ SIGNO: signal number if NOTIFY && sigev_notify == SIGEV_SIGNAL
+ NOTIFY_PID: if NOTIFY pid */
+ snprintf (filebuf, FILESIZE,
+ "QSIZE:%-10lu NOTIFY:%-5d SIGNO:%-5d NOTIFY_PID:%-6d\n",
+ qsize, notify, signo, notify_pid);
+ filesize = strlen (filebuf);
+ return true;
+}
+
+void
+fhandler_mqueue::read (void *in_ptr, size_t& len)
+{
+ if (len == 0)
+ return;
+ if (!filebuf[0] && !fill_filebuf ())
+ {
+ len = (size_t) -1;
+ return;
+ }
+ if ((ssize_t) len > filesize - position)
+ len = (size_t) (filesize - position);
+ if ((ssize_t) len < 0)
+ len = 0;
+ else
+ memcpy (in_ptr, filebuf + position, len);
+ position += len;
+}
+
+off_t
+fhandler_mqueue::lseek (off_t offset, int whence)
+{
+ if (!fill_filebuf ())
+ return (off_t) -1;
+ switch (whence)
+ {
+ case SEEK_SET:
+ position = offset;
+ break;
+ case SEEK_CUR:
+ position += offset;
+ break;
+ case SEEK_END:
+ position = filesize + offset;
+ break;
+ default:
+ set_errno (EINVAL);
+ return (off_t) -1;
+ }
+ return position;
+}
+
+
+int
+fhandler_mqueue::fstat (struct stat *buf)
+{
+ int ret = fhandler_disk_file::fstat (buf);
+ if (!ret)
+ {
+ buf->st_size = FILESIZE;
+ buf->st_dev = FH_MQUEUE;
+ }
+ return ret;
+}
+
+int
+fhandler_mqueue::_dup (HANDLE parent, fhandler_mqueue *fhc)
+{
+ __try
+ {
+ PVOID mptr = NULL;
+ SIZE_T filesize = mqinfo ()->mqi_sectsize;
+ NTSTATUS status;
+
+ if (!DuplicateHandle (parent, mqinfo ()->mqi_sect,
+ GetCurrentProcess (), &fhc->mqinfo ()->mqi_sect,
+ 0, FALSE, DUPLICATE_SAME_ACCESS))
+ __leave;
+ status = NtMapViewOfSection (mqinfo ()->mqi_sect, NtCurrentProcess (),
+ &mptr, 0, filesize, NULL, &filesize,
+ ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
+ if (!NT_SUCCESS (status))
+ api_fatal ("Mapping message queue failed in fork, status 0x%x\n",
+ status);
+
+ fhc->mqinfo ()->mqi_hdr = (struct mq_hdr *) mptr;
+ if (!DuplicateHandle (parent, mqinfo ()->mqi_waitsend,
+ GetCurrentProcess (), &fhc->mqinfo ()->mqi_waitsend,
+ 0, FALSE, DUPLICATE_SAME_ACCESS))
+ __leave;
+ if (!DuplicateHandle (parent, mqinfo ()->mqi_waitrecv,
+ GetCurrentProcess (), &fhc->mqinfo ()->mqi_waitrecv,
+ 0, FALSE, DUPLICATE_SAME_ACCESS))
+ __leave;
+ if (!DuplicateHandle (parent, mqinfo ()->mqi_lock,
+ GetCurrentProcess (), &fhc->mqinfo ()->mqi_lock,
+ 0, FALSE, DUPLICATE_SAME_ACCESS))
+ __leave;
+ return 0;
+ }
+ __except (EFAULT) {}
+ __endtry
+ return -1;
+}
+
+int
+fhandler_mqueue::dup (fhandler_base *child, int flags)
+{
+ fhandler_mqueue *fhc = (fhandler_mqueue *) child;
+
+ int ret = fhandler_disk_file::dup (child, flags);
+ if (!ret)
+ {
+ memcpy (fhc->filebuf, filebuf, FILESIZE);
+ ret = _dup (GetCurrentProcess (), fhc);
+ }
+ return ret;
+}
+
+void
+fhandler_mqueue::fixup_after_fork (HANDLE parent)
+{
+ if (_dup (parent, this))
+ api_fatal ("Creating IPC object failed in fork, %E");
+}
+
+int
+fhandler_mqueue::ioctl (unsigned int cmd, void *buf)
+{
+ return fhandler_base::ioctl (cmd, buf);
+}
+
+int
+fhandler_mqueue::close ()
+{
+ __try
+ {
+ mqinfo ()->mqi_magic = 0; /* just in case */
+ NtUnmapViewOfSection (NtCurrentProcess (), mqinfo ()->mqi_hdr);
+ NtClose (mqinfo ()->mqi_sect);
+ NtClose (mqinfo ()->mqi_waitsend);
+ NtClose (mqinfo ()->mqi_waitrecv);
+ NtClose (mqinfo ()->mqi_lock);
+ }
+ __except (0) {}
+ __endtry
+ return 0;
+}
+
+int
+fhandler_mqueue::mutex_lock (HANDLE mtx, bool eintr)
+{
+ switch (cygwait (mtx, cw_infinite, cw_cancel | cw_cancel_self
+ | (eintr ? cw_sig_eintr : cw_sig_restart)))
+ {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED_0:
+ return 0;
+ case WAIT_SIGNALED:
+ set_errno (EINTR);
+ return 1;
+ default:
+ break;
+ }
+ return geterrno_from_win_error ();
+}
+
+int
+fhandler_mqueue::mutex_unlock (HANDLE mtx)
+{
+ return ReleaseMutex (mtx) ? 0 : geterrno_from_win_error ();
+}
+
+int
+fhandler_mqueue::cond_timedwait (HANDLE evt, HANDLE mtx,
+ const struct timespec *abstime)
+{
+ HANDLE w4[4] = { evt, };
+ DWORD cnt = 2;
+ DWORD timer_idx = 0;
+ int ret = 0;
+
+ wait_signal_arrived here (w4[1]);
+ if ((w4[cnt] = pthread::get_cancel_event ()) != NULL)
+ ++cnt;
+ if (abstime)
+ {
+ if (!valid_timespec (*abstime))
+ return EINVAL;
+
+ /* If a timeout is set, we create a waitable timer to wait for.
+ This is the easiest way to handle the absolute timeout value, given
+ that NtSetTimer also takes absolute times and given the double
+ dependency on evt *and* mtx, which requires to call WFMO twice. */
+ NTSTATUS status;
+ LARGE_INTEGER duetime;
+
+ timer_idx = cnt++;
+ status = NtCreateTimer (&w4[timer_idx], TIMER_ALL_ACCESS, NULL,
+ NotificationTimer);
+ if (!NT_SUCCESS (status))
+ return geterrno_from_nt_status (status);
+ timespec_to_filetime (abstime, &duetime);
+ status = NtSetTimer (w4[timer_idx], &duetime, NULL, NULL, FALSE, 0, NULL);
+ if (!NT_SUCCESS (status))
+ {
+ NtClose (w4[timer_idx]);
+ return geterrno_from_nt_status (status);
+ }
+ }
+ ResetEvent (evt);
+ if ((ret = mutex_unlock (mtx)) != 0)
+ return ret;
+ /* Everything's set up, so now wait for the event to be signalled. */
+restart1:
+ switch (WaitForMultipleObjects (cnt, w4, FALSE, INFINITE))
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_OBJECT_0 + 1:
+ if (_my_tls.call_signal_handler ())
+ goto restart1;
+ ret = EINTR;
+ break;
+ case WAIT_OBJECT_0 + 2:
+ if (timer_idx != 2)
+ pthread::static_cancel_self ();
+ fallthrough;
+ case WAIT_OBJECT_0 + 3:
+ ret = ETIMEDOUT;
+ break;
+ default:
+ ret = geterrno_from_win_error ();
+ break;
+ }
+ if (ret == 0)
+ {
+ /* At this point we need to lock the mutex. The wait is practically
+ the same as before, just that we now wait on the mutex instead of the
+ event. */
+ restart2:
+ w4[0] = mtx;
+ switch (WaitForMultipleObjects (cnt, w4, FALSE, INFINITE))
+ {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED_0:
+ break;
+ case WAIT_OBJECT_0 + 1:
+ if (_my_tls.call_signal_handler ())
+ goto restart2;
+ ret = EINTR;
+ break;
+ case WAIT_OBJECT_0 + 2:
+ if (timer_idx != 2)
+ pthread_testcancel ();
+ fallthrough;
+ case WAIT_OBJECT_0 + 3:
+ ret = ETIMEDOUT;
+ break;
+ default:
+ ret = geterrno_from_win_error ();
+ break;
+ }
+ }
+ if (timer_idx)
+ {
+ if (ret != ETIMEDOUT)
+ NtCancelTimer (w4[timer_idx], NULL);
+ NtClose (w4[timer_idx]);
+ }
+ return ret;
+}
+
+void
+fhandler_mqueue::cond_signal (HANDLE evt)
+{
+ SetEvent (evt);
+}
+
+int
+fhandler_mqueue::mq_getattr (struct mq_attr *mqstat)
+{
+ int n;
+ struct mq_hdr *mqhdr;
+ struct mq_fattr *attr;
+
+ __try
+ {
+ mqhdr = mqinfo ()->mqi_hdr;
+ attr = &mqhdr->mqh_attr;
+ if ((n = mutex_lock (mqinfo ()->mqi_lock, false)) != 0)
+ {
+ errno = n;
+ __leave;
+ }
+ mqstat->mq_flags = is_nonblocking () ? O_NONBLOCK : 0; /* per-open */
+ mqstat->mq_maxmsg = attr->mq_maxmsg; /* remaining three per-queue */
+ mqstat->mq_msgsize = attr->mq_msgsize;
+ mqstat->mq_curmsgs = attr->mq_curmsgs;
+
+ mutex_unlock (mqinfo ()->mqi_lock);
+ return 0;
+ }
+ __except (EBADF) {}
+ __endtry
+ return -1;
+}
+
+int
+fhandler_mqueue::mq_setattr (const struct mq_attr *mqstat,
+ struct mq_attr *omqstat)
+{
+ int n;
+ struct mq_hdr *mqhdr;
+ struct mq_fattr *attr;
+
+ __try
+ {
+ mqhdr = mqinfo ()->mqi_hdr;
+ attr = &mqhdr->mqh_attr;
+ if ((n = mutex_lock (mqinfo ()->mqi_lock, false)) != 0)
+ {
+ errno = n;
+ __leave;
+ }
+
+ if (omqstat != NULL)
+ {
+ omqstat->mq_flags = is_nonblocking () ? O_NONBLOCK : 0;
+ omqstat->mq_maxmsg = attr->mq_maxmsg;
+ omqstat->mq_msgsize = attr->mq_msgsize;
+ omqstat->mq_curmsgs = attr->mq_curmsgs; /* and current status */
+ }
+
+ set_nonblocking (mqstat->mq_flags & O_NONBLOCK);
+
+ mutex_unlock (mqinfo ()->mqi_lock);
+ return 0;
+ }
+ __except (EBADF) {}
+ __endtry
+ return -1;
+}
+
+int
+fhandler_mqueue::mq_notify (const struct sigevent *notification)
+{
+ int n;
+ pid_t pid;
+ struct mq_hdr *mqhdr;
+
+ __try
+ {
+ mqhdr = mqinfo ()->mqi_hdr;
+ if ((n = mutex_lock (mqinfo ()->mqi_lock, false)) != 0)
+ {
+ errno = n;
+ __leave;
+ }
+
+ pid = myself->pid;
+ if (!notification)
+ {
+ if (mqhdr->mqh_pid == pid)
+ mqhdr->mqh_pid = 0; /* unregister calling process */
+ }
+ else
+ {
+ if (mqhdr->mqh_pid != 0)
+ {
+ if (kill (mqhdr->mqh_pid, 0) != -1 || errno != ESRCH)
+ {
+ set_errno (EBUSY);
+ mutex_unlock (mqinfo ()->mqi_lock);
+ __leave;
+ }
+ }
+ mqhdr->mqh_pid = pid;
+ mqhdr->mqh_event = *notification;
+ }
+ mutex_unlock (mqinfo ()->mqi_lock);
+ return 0;
+ }
+ __except (EBADF) {}
+ __endtry
+ return -1;
+}
+
+int
+fhandler_mqueue::mq_timedsend (const char *ptr, size_t len, unsigned int prio,
+ const struct timespec *abstime)
+{
+ int n;
+ long index, freeindex;
+ int8_t *mptr;
+ struct sigevent *sigev;
+ struct mq_hdr *mqhdr;
+ struct mq_fattr *attr;
+ struct msg_hdr *msghdr, *nmsghdr, *pmsghdr;
+ bool mutex_locked = false;
+ int ret = -1;
+
+ pthread_testcancel ();
+
+ __try
+ {
+ if (prio >= MQ_PRIO_MAX)
+ {
+ set_errno (EINVAL);
+ __leave;
+ }
+
+ mqhdr = mqinfo ()->mqi_hdr; /* struct pointer */
+ mptr = (int8_t *) mqhdr; /* byte pointer */
+ attr = &mqhdr->mqh_attr;
+ if ((n = mutex_lock (mqinfo ()->mqi_lock, true)) != 0)
+ {
+ errno = n;
+ __leave;
+ }
+ mutex_locked = true;
+ if (len > (size_t) attr->mq_msgsize)
+ {
+ set_errno (EMSGSIZE);
+ __leave;
+ }
+ if (attr->mq_curmsgs == 0)
+ {
+ if (mqhdr->mqh_pid != 0 && mqhdr->mqh_nwait == 0)
+ {
+ sigev = &mqhdr->mqh_event;
+ if (sigev->sigev_notify == SIGEV_SIGNAL)
+ sigqueue (mqhdr->mqh_pid, sigev->sigev_signo,
+ sigev->sigev_value);
+ mqhdr->mqh_pid = 0; /* unregister */
+ }
+ }
+ else if (attr->mq_curmsgs >= attr->mq_maxmsg)
+ {
+ /* Queue is full */
+ if (is_nonblocking ())
+ {
+ set_errno (EAGAIN);
+ __leave;
+ }
+ /* Wait for room for one message on the queue */
+ while (attr->mq_curmsgs >= attr->mq_maxmsg)
+ {
+ int ret = cond_timedwait (mqinfo ()->mqi_waitsend,
+ mqinfo ()->mqi_lock, abstime);
+ if (ret != 0)
+ {
+ set_errno (ret);
+ __leave;
+ }
+ }
+ }
+
+ /* nmsghdr will point to new message */
+ if ((freeindex = mqhdr->mqh_free) == 0)
+ api_fatal ("mq_send: curmsgs = %ld; free = 0", attr->mq_curmsgs);
+
+ nmsghdr = (struct msg_hdr *) &mptr[freeindex];
+ nmsghdr->msg_prio = prio;
+ nmsghdr->msg_len = len;
+ memcpy (nmsghdr + 1, ptr, len); /* copy message from caller */
+ mqhdr->mqh_free = nmsghdr->msg_next; /* new freelist head */
+
+ /* Find right place for message in linked list */
+ index = mqhdr->mqh_head;
+ pmsghdr = (struct msg_hdr *) &(mqhdr->mqh_head);
+ while (index)
+ {
+ msghdr = (struct msg_hdr *) &mptr[index];
+ if (prio > msghdr->msg_prio)
+ {
+ nmsghdr->msg_next = index;
+ pmsghdr->msg_next = freeindex;
+ break;
+ }
+ index = msghdr->msg_next;
+ pmsghdr = msghdr;
+ }
+ if (index == 0)
+ {
+ /* Queue was empty or new goes at end of list */
+ pmsghdr->msg_next = freeindex;
+ nmsghdr->msg_next = 0;
+ }
+ /* Wake up anyone blocked in mq_receive waiting for a message */
+ if (attr->mq_curmsgs == 0)
+ cond_signal (mqinfo ()->mqi_waitrecv);
+ attr->mq_curmsgs++;
+
+ ret = 0;
+ }
+ __except (EBADF) {}
+ __endtry
+ if (mutex_locked)
+ mutex_unlock (mqinfo ()->mqi_lock);
+ return ret;
+}
+
+ssize_t
+fhandler_mqueue::mq_timedrecv (char *ptr, size_t maxlen, unsigned int *priop,
+ const struct timespec *abstime)
+{
+ int n;
+ long index;
+ int8_t *mptr;
+ ssize_t len = -1;
+ struct mq_hdr *mqhdr;
+ struct mq_fattr *attr;
+ struct msg_hdr *msghdr;
+ bool mutex_locked = false;
+
+ pthread_testcancel ();
+
+ __try
+ {
+ mqhdr = mqinfo ()->mqi_hdr; /* struct pointer */
+ mptr = (int8_t *) mqhdr; /* byte pointer */
+ attr = &mqhdr->mqh_attr;
+ if ((n = mutex_lock (mqinfo ()->mqi_lock, true)) != 0)
+ {
+ errno = n;
+ __leave;
+ }
+ mutex_locked = true;
+ if (maxlen < (size_t) attr->mq_msgsize)
+ {
+ set_errno (EMSGSIZE);
+ __leave;
+ }
+ if (attr->mq_curmsgs == 0) /* queue is empty */
+ {
+ if (is_nonblocking ())
+ {
+ set_errno (EAGAIN);
+ __leave;
+ }
+ /* Wait for a message to be placed onto queue */
+ mqhdr->mqh_nwait++;
+ while (attr->mq_curmsgs == 0)
+ {
+ int ret = cond_timedwait (mqinfo ()->mqi_waitrecv,
+ mqinfo ()->mqi_lock, abstime);
+ if (ret != 0)
+ {
+ set_errno (ret);
+ __leave;
+ }
+ }
+ mqhdr->mqh_nwait--;
+ }
+
+ if ((index = mqhdr->mqh_head) == 0)
+ api_fatal ("mq_receive: curmsgs = %ld; head = 0", attr->mq_curmsgs);
+
+ msghdr = (struct msg_hdr *) &mptr[index];
+ mqhdr->mqh_head = msghdr->msg_next; /* new head of list */
+ len = msghdr->msg_len;
+ memcpy(ptr, msghdr + 1, len); /* copy the message itself */
+ if (priop != NULL)
+ *priop = msghdr->msg_prio;
+
+ /* Just-read message goes to front of free list */
+ msghdr->msg_next = mqhdr->mqh_free;
+ mqhdr->mqh_free = index;
+
+ /* Wake up anyone blocked in mq_send waiting for room */
+ if (attr->mq_curmsgs == attr->mq_maxmsg)
+ cond_signal (mqinfo ()->mqi_waitsend);
+ attr->mq_curmsgs--;
+ }
+ __except (EBADF) {}
+ __endtry
+ if (mutex_locked)
+ mutex_unlock (mqinfo ()->mqi_lock);
+ return len;
+}
diff --git a/winsup/cygwin/fhandler/netdrive.cc b/winsup/cygwin/fhandler/netdrive.cc
new file mode 100644
index 000000000..58ab8811b
--- /dev/null
+++ b/winsup/cygwin/fhandler/netdrive.cc
@@ -0,0 +1,366 @@
+/* fhandler_netdrive.cc: fhandler for // and //MACHINE handling
+
+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 <stdlib.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "cygthread.h"
+#include "tls_pbuf.h"
+
+#include <dirent.h>
+
+enum
+ {
+ GET_RESOURCE_OPENENUM = 1,
+ GET_RESOURCE_OPENENUMTOP = 2,
+ GET_RESOURCE_ENUM = 3
+ };
+
+struct netdriveinf
+ {
+ int what;
+ int ret;
+ PVOID in;
+ PVOID out;
+ DWORD outsize;
+ HANDLE sem;
+ };
+
+struct net_hdls
+ {
+ HANDLE net;
+ HANDLE dom;
+ };
+
+static void
+wnet_dbg_out (const char *func, DWORD ndi_ret)
+{
+ DWORD gle_ret;
+ DWORD error;
+ WCHAR errorbuf[MAX_PATH];
+ WCHAR namebuf[MAX_PATH];
+
+ if (ndi_ret != ERROR_EXTENDED_ERROR)
+ {
+ debug_printf ("%s failed: %u", func, ndi_ret);
+ return;
+ }
+ gle_ret = WNetGetLastErrorW (&error, errorbuf, MAX_PATH, namebuf, MAX_PATH);
+ if (gle_ret == NO_ERROR)
+ debug_printf ("%s failed: %u --> %u from '%W': '%W'",
+ func, ndi_ret, error, namebuf, errorbuf);
+ else
+ debug_printf ("WNetGetLastError failed: %u", gle_ret);
+}
+
+static DWORD
+thread_netdrive (void *arg)
+{
+ netdriveinf *ndi = (netdriveinf *) arg;
+ WCHAR provider[256], *dummy = NULL;
+ LPNETRESOURCEW nro;
+ DWORD cnt, size;
+ struct net_hdls *nh;
+ tmp_pathbuf tp;
+
+ ReleaseSemaphore (ndi->sem, 1, NULL);
+ switch (ndi->what)
+ {
+ case GET_RESOURCE_OPENENUMTOP:
+ nro = (LPNETRESOURCEW) tp.c_get ();
+ nh = (struct net_hdls *) ndi->out;
+ ndi->ret = WNetGetProviderNameW (WNNC_NET_LANMAN, provider,
+ (size = 256, &size));
+ if (ndi->ret != NO_ERROR)
+ {
+ wnet_dbg_out ("WNetGetProviderNameW", ndi->ret);
+ break;
+ }
+ memset (nro, 0, sizeof *nro);
+ nro->dwScope = RESOURCE_GLOBALNET;
+ nro->dwType = RESOURCETYPE_ANY;
+ nro->dwDisplayType = RESOURCEDISPLAYTYPE_GROUP;
+ nro->dwUsage = RESOURCEUSAGE_RESERVED | RESOURCEUSAGE_CONTAINER;
+ nro->lpRemoteName = provider;
+ nro->lpProvider = provider;
+ ndi->ret = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
+ RESOURCEUSAGE_ALL, nro, &nh->net);
+ if (ndi->ret != NO_ERROR)
+ {
+ wnet_dbg_out ("WNetOpenEnumW", ndi->ret);
+ break;
+ }
+ while ((ndi->ret = WNetEnumResourceW (nh->net, (cnt = 1, &cnt), nro,
+ (size = NT_MAX_PATH, &size)))
+ == NO_ERROR)
+ {
+ ndi->ret = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
+ RESOURCEUSAGE_ALL, nro, &nh->dom);
+ if (ndi->ret == NO_ERROR)
+ break;
+ }
+ break;
+ case GET_RESOURCE_OPENENUM:
+ nro = (LPNETRESOURCEW) tp.c_get ();
+ nh = (struct net_hdls *) ndi->out;
+ ndi->ret = WNetGetProviderNameW (WNNC_NET_LANMAN, provider,
+ (size = 256, &size));
+ if (ndi->ret != NO_ERROR)
+ {
+ wnet_dbg_out ("WNetGetProviderNameW", ndi->ret);
+ break;
+ }
+ ((LPNETRESOURCEW) ndi->in)->lpProvider = provider;
+ ndi->ret = WNetGetResourceInformationW ((LPNETRESOURCEW) ndi->in, nro,
+ (size = NT_MAX_PATH, &size),
+ &dummy);
+ if (ndi->ret != NO_ERROR)
+ {
+ wnet_dbg_out ("WNetGetResourceInformationW", ndi->ret);
+ break;
+ }
+ ndi->ret = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
+ RESOURCEUSAGE_ALL, nro, &nh->dom);
+ break;
+ case GET_RESOURCE_ENUM:
+ nh = (struct net_hdls *) ndi->in;
+ if (!nh->dom)
+ {
+ ndi->ret = ERROR_NO_MORE_ITEMS;
+ break;
+ }
+ nro = (LPNETRESOURCEW) tp.c_get ();
+ while ((ndi->ret = WNetEnumResourceW (nh->dom, (cnt = 1, &cnt),
+ (LPNETRESOURCEW) ndi->out,
+ &ndi->outsize)) != NO_ERROR
+ && nh->net)
+ {
+ WNetCloseEnum (nh->dom);
+ nh->dom = NULL;
+ while ((ndi->ret = WNetEnumResourceW (nh->net, (cnt = 1, &cnt), nro,
+ (size = NT_MAX_PATH, &size)))
+ == NO_ERROR)
+ {
+ ndi->ret = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
+ RESOURCEUSAGE_ALL, nro, &nh->dom);
+ if (ndi->ret == NO_ERROR)
+ break;
+ }
+ if (ndi->ret != NO_ERROR)
+ break;
+ }
+ break;
+ }
+ ReleaseSemaphore (ndi->sem, 1, NULL);
+ return 0;
+}
+
+static DWORD
+create_thread_and_wait (int what, PVOID in, PVOID out, DWORD outsize,
+ const char *name)
+{
+ netdriveinf ndi = { what, 0, in, out, outsize,
+ CreateSemaphore (&sec_none_nih, 0, 2, NULL) };
+ cygthread *thr = new cygthread (thread_netdrive, &ndi, name);
+ if (thr->detach (ndi.sem))
+ ndi.ret = ERROR_OPERATION_ABORTED;
+ CloseHandle (ndi.sem);
+ return ndi.ret;
+}
+
+virtual_ftype_t
+fhandler_netdrive::exists ()
+{
+ char *to;
+ const char *from;
+ size_t len = strlen (get_name ());
+ if (len == 2)
+ return virt_rootdir;
+
+ char namebuf[len + 1];
+ tmp_pathbuf tp;
+ PWCHAR name = tp.w_get ();
+
+ for (to = namebuf, from = get_name (); *from; to++, from++)
+ *to = (*from == '/') ? '\\' : *from;
+ *to = '\0';
+
+ struct net_hdls nh = { NULL, NULL };
+ NETRESOURCEW nr = {0};
+ nr.dwType = RESOURCETYPE_DISK;
+ sys_mbstowcs (name, NT_MAX_PATH, namebuf);
+ nr.lpRemoteName = name;
+ DWORD ret = create_thread_and_wait (GET_RESOURCE_OPENENUM,
+ &nr, &nh, 0, "WNetOpenEnum");
+ if (nh.dom)
+ WNetCloseEnum (nh.dom);
+ return ret != NO_ERROR ? virt_none : virt_directory;
+}
+
+fhandler_netdrive::fhandler_netdrive ():
+ fhandler_virtual ()
+{
+}
+
+int
+fhandler_netdrive::fstat (struct stat *buf)
+{
+ const char *path = get_name ();
+ debug_printf ("fstat (%s)", path);
+
+ fhandler_base::fstat (buf);
+
+ buf->st_mode = S_IFDIR | STD_RBITS | STD_XBITS;
+ buf->st_ino = get_ino ();
+
+ return 0;
+}
+
+int
+fhandler_netdrive::readdir (DIR *dir, dirent *de)
+{
+ NETRESOURCEW *nro;
+ DWORD ret;
+ int res;
+ tmp_pathbuf tp;
+
+ if (!dir->__d_position)
+ {
+ size_t len = strlen (get_name ());
+ PWCHAR name = NULL;
+ NETRESOURCEW nr = { 0 };
+ struct net_hdls *nh;
+
+ if (len != 2) /* // */
+ {
+ const char *from;
+ char *to;
+ char *namebuf = (char *) alloca (len + 1);
+ for (to = namebuf, from = get_name (); *from; to++, from++)
+ *to = (*from == '/') ? '\\' : *from;
+ *to = '\0';
+ name = tp.w_get ();
+ sys_mbstowcs (name, NT_MAX_PATH, namebuf);
+ }
+ nr.lpRemoteName = name;
+ nr.dwType = RESOURCETYPE_DISK;
+ nh = (struct net_hdls *) ccalloc (HEAP_FHANDLER, 1, sizeof *nh);
+ ret = create_thread_and_wait (len == 2 ? GET_RESOURCE_OPENENUMTOP
+ : GET_RESOURCE_OPENENUM,
+ &nr, nh, 0, "WNetOpenEnum");
+ if (ret != NO_ERROR)
+ {
+ dir->__handle = INVALID_HANDLE_VALUE;
+ res = geterrno_from_win_error (ret);
+ goto out;
+ }
+ dir->__handle = (HANDLE) nh;
+ }
+ nro = (LPNETRESOURCEW) tp.c_get ();
+ ret = create_thread_and_wait (GET_RESOURCE_ENUM, dir->__handle, nro,
+ NT_MAX_PATH, "WnetEnumResource");
+ if (ret != NO_ERROR)
+ res = geterrno_from_win_error (ret);
+ else
+ {
+ dir->__d_position++;
+ PWCHAR bs = wcsrchr (nro->lpRemoteName, L'\\');
+ bs = bs ? bs + 1 : nro->lpRemoteName;
+ if (strlen (get_name ()) == 2)
+ {
+ UNICODE_STRING ss, ds;
+
+ tp.u_get (&ds);
+ RtlInitUnicodeString (&ss, bs);
+ RtlDowncaseUnicodeString (&ds, &ss, FALSE);
+ sys_wcstombs (de->d_name, sizeof de->d_name,
+ ds.Buffer, ds.Length / sizeof (WCHAR));
+ de->d_ino = hash_path_name (get_ino (), de->d_name);
+ }
+ else
+ {
+ sys_wcstombs (de->d_name, sizeof de->d_name, bs);
+ char *rpath = tp.c_get ();
+ sys_wcstombs (rpath, NT_MAX_PATH, nro->lpRemoteName);
+ de->d_ino = readdir_get_ino (rpath, false);
+ /* We can't trust remote inode numbers of only 32 bit. That means,
+ remote NT4 NTFS, as well as shares of Samba version < 3.0. */
+ if (de->d_ino <= UINT32_MAX)
+ de->d_ino = hash_path_name (0, rpath);
+ }
+ de->d_type = DT_DIR;
+
+ res = 0;
+ }
+out:
+ syscall_printf ("%d = readdir(%p, %p)", res, dir, de);
+ return res;
+}
+
+void
+fhandler_netdrive::seekdir (DIR *dir, long pos)
+{
+ rewinddir (dir);
+ if (pos < 0)
+ return;
+ while (dir->__d_position < pos)
+ if (readdir (dir, dir->__d_dirent))
+ break;
+}
+
+void
+fhandler_netdrive::rewinddir (DIR *dir)
+{
+ if (dir->__handle != INVALID_HANDLE_VALUE)
+ {
+ struct net_hdls *nh = (struct net_hdls *) dir->__handle;
+ if (nh->dom)
+ WNetCloseEnum (nh->dom);
+ if (nh->net)
+ WNetCloseEnum (nh->net);
+ cfree (nh);
+ }
+ dir->__handle = INVALID_HANDLE_VALUE;
+ return fhandler_virtual::rewinddir (dir);
+}
+
+int
+fhandler_netdrive::closedir (DIR *dir)
+{
+ rewinddir (dir);
+ return fhandler_virtual::closedir (dir);
+}
+
+int
+fhandler_netdrive::open (int flags, mode_t mode)
+{
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ return 0;
+ }
+ if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ return 0;
+ }
+ /* Open a fake handle to \\Device\\Null */
+ return open_null (flags);
+}
+
+int
+fhandler_netdrive::close ()
+{
+ /* Skip fhandler_virtual::close, which is a no-op. */
+ return fhandler_base::close ();
+}
diff --git a/winsup/cygwin/fhandler/nodevice.cc b/winsup/cygwin/fhandler/nodevice.cc
new file mode 100644
index 000000000..515b1ae58
--- /dev/null
+++ b/winsup/cygwin/fhandler/nodevice.cc
@@ -0,0 +1,28 @@
+/* fhandler_nodevice.cc. "No such device" handler.
+
+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 "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+
+int
+fhandler_nodevice::open (int flags, mode_t)
+{
+ if (!pc.error)
+ set_errno (ENXIO);
+ /* Fixup EROFS error returned from path_conv if /dev is not backed by real
+ directory on disk and the file doesn't exist. */
+ else if (pc.error == EROFS && (flags & O_ACCMODE) == O_RDONLY)
+ set_errno (ENOENT);
+ return 0;
+}
+
+fhandler_nodevice::fhandler_nodevice ()
+{
+}
diff --git a/winsup/cygwin/fhandler/null.cc b/winsup/cygwin/fhandler/null.cc
new file mode 100644
index 000000000..e4dec35e1
--- /dev/null
+++ b/winsup/cygwin/fhandler/null.cc
@@ -0,0 +1,35 @@
+/* null.cc. /dev/null specifics.
+
+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 <unistd.h>
+#include <stdlib.h>
+#include <sys/uio.h>
+#include <cygwin/acl.h>
+#include <sys/param.h>
+#include "cygerrno.h"
+#include "perprocess.h"
+#include "security.h"
+#include "cygwin/version.h"
+#include "path.h"
+#include "fhandler.h"
+
+fhandler_dev_null::fhandler_dev_null () :
+ fhandler_base ()
+{
+}
+
+ssize_t
+fhandler_dev_null::write (const void *ptr, size_t len)
+{
+ /* Shortcut. This also fixes a problem with the NUL device on x86_64:
+ If you write > 4 GB in a single attempt, the bytes written returned
+ from by is numBytes & 0xffffffff. */
+ return len;
+}
+
diff --git a/winsup/cygwin/fhandler/pipe.cc b/winsup/cygwin/fhandler/pipe.cc
new file mode 100644
index 000000000..720e4efd3
--- /dev/null
+++ b/winsup/cygwin/fhandler/pipe.cc
@@ -0,0 +1,1385 @@
+/* fhandler_pipe.cc: pipes for Cygwin.
+
+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 <stdlib.h>
+#include <sys/socket.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "pinfo.h"
+#include "shared_info.h"
+#include "tls_pbuf.h"
+#include <assert.h>
+
+/* This is only to be used for writing. 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; })
+
+fhandler_pipe_fifo::fhandler_pipe_fifo ()
+ : fhandler_base (), pipe_buf_size (DEFAULT_PIPEBUFSIZE)
+{
+}
+
+
+fhandler_pipe::fhandler_pipe ()
+ : fhandler_pipe_fifo (), popen_pid (0)
+{
+ need_fork_fixup (true);
+}
+
+/* The following function is intended for fhandler_pipe objects
+ created by the second version of fhandler_pipe::create below. See
+ the comment preceding the latter.
+
+ In addition to setting the blocking mode of the pipe handle, it
+ also sets the pipe's read mode to byte_stream unconditionally. */
+void
+fhandler_pipe::set_pipe_non_blocking (bool nonblocking)
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ FILE_PIPE_INFORMATION fpi;
+
+ fpi.ReadMode = FILE_PIPE_BYTE_STREAM_MODE;
+ fpi.CompletionMode = nonblocking ? FILE_PIPE_COMPLETE_OPERATION
+ : FILE_PIPE_QUEUE_OPERATION;
+ status = NtSetInformationFile (get_handle (), &io, &fpi, sizeof fpi,
+ FilePipeInformation);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtSetInformationFile(FilePipeInformation): %y", status);
+}
+
+int
+fhandler_pipe::init (HANDLE f, DWORD a, mode_t mode, int64_t uniq_id)
+{
+ /* FIXME: Have to clean this up someday
+ FIXME: Do we have to check for both !get_win32_name() and
+ !*get_win32_name()? */
+ if ((!get_win32_name () || !*get_win32_name ()) && get_name ())
+ {
+ char *d;
+ const char *s;
+ char *hold_normalized_name = (char *) alloca (strlen (get_name ()) + 1);
+ for (s = get_name (), d = hold_normalized_name; *s; s++, d++)
+ if (*s == '/')
+ *d = '\\';
+ else
+ *d = *s;
+ *d = '\0';
+ set_name (hold_normalized_name);
+ }
+
+ bool opened_properly = a & FILE_CREATE_PIPE_INSTANCE;
+ a &= ~FILE_CREATE_PIPE_INSTANCE;
+ fhandler_base::init (f, a, mode);
+ close_on_exec (mode & O_CLOEXEC);
+ set_ino (uniq_id);
+ set_unique_id (uniq_id | !!(mode & GENERIC_WRITE));
+ if (opened_properly)
+ /* Set read pipe always nonblocking to allow signal handling
+ even with FILE_SYNCHRONOUS_IO_NONALERT. */
+ set_pipe_non_blocking (get_device () == FH_PIPER ?
+ true : is_nonblocking ());
+ return 1;
+}
+
+extern "C" int sscanf (const char *, const char *, ...);
+
+int
+fhandler_pipe::open (int flags, mode_t mode)
+{
+ HANDLE proc, nio_hdl = NULL;
+ int64_t uniq_id;
+ fhandler_pipe *fh = NULL, *fhr = NULL, *fhw = NULL;
+ size_t size;
+ int pid, rwflags = (flags & O_ACCMODE);
+ bool inh;
+ bool got_one = false;
+
+ if (sscanf (get_name (), "/proc/%d/fd/pipe:[%llu]",
+ &pid, (long long *) &uniq_id) < 2)
+ {
+ set_errno (ENOENT);
+ return 0;
+ }
+ if (pid == myself->pid)
+ {
+ cygheap_fdenum cfd (true);
+ while (cfd.next () >= 0)
+ {
+ /* Windows doesn't allow to copy a pipe HANDLE with another access
+ mode. So we check for read and write side of pipe and try to
+ find the one matching the requested access mode. */
+ if (cfd->get_unique_id () == uniq_id)
+ got_one = true;
+ else if (cfd->get_unique_id () == uniq_id + 1)
+ got_one = true;
+ else
+ continue;
+ if ((rwflags == O_RDONLY && !(cfd->get_access () & GENERIC_READ))
+ || (rwflags == O_WRONLY && !(cfd->get_access () & GENERIC_WRITE)))
+ continue;
+ copy_from (cfd);
+ set_handle (NULL);
+ pc.close_conv_handle ();
+ if (!cfd->dup (this, flags))
+ return 1;
+ return 0;
+ }
+ /* Found the pipe but access mode didn't match? EACCES.
+ Otherwise ENOENT */
+ set_errno (got_one ? EACCES : ENOENT);
+ return 0;
+ }
+
+ pinfo p (pid);
+ if (!p)
+ {
+ set_errno (ESRCH);
+ return 0;
+ }
+ if (!(proc = OpenProcess (PROCESS_DUP_HANDLE, false, p->dwProcessId)))
+ {
+ __seterrno ();
+ return 0;
+ }
+ fhr = p->pipe_fhandler (uniq_id, size);
+ if (fhr && rwflags == O_RDONLY)
+ fh = fhr;
+ else
+ {
+ fhw = p->pipe_fhandler (uniq_id + 1, size);
+ if (fhw && rwflags == O_WRONLY)
+ fh = fhw;
+ }
+ if (!fh)
+ {
+ /* Too bad, but Windows only allows the same access mode when dup'ing
+ the pipe. */
+ set_errno (fhr || fhw ? EACCES : ENOENT);
+ goto out;
+ }
+ inh = !(flags & O_CLOEXEC);
+ if (!DuplicateHandle (proc, fh->get_handle (), GetCurrentProcess (),
+ &nio_hdl, 0, inh, DUPLICATE_SAME_ACCESS))
+ {
+ __seterrno ();
+ goto out;
+ }
+ init (nio_hdl, fh->get_access (), mode & O_TEXT ?: O_BINARY,
+ fh->get_plain_ino ());
+ cfree (fh);
+ CloseHandle (proc);
+ return 1;
+out:
+ if (nio_hdl)
+ CloseHandle (nio_hdl);
+ if (fh)
+ free (fh);
+ if (proc)
+ CloseHandle (proc);
+ return 0;
+}
+
+bool
+fhandler_pipe::open_setup (int flags)
+{
+ bool read_mtx_created = false;
+
+ if (!fhandler_base::open_setup (flags))
+ goto err;
+ if (get_dev () == FH_PIPER && !read_mtx)
+ {
+ SECURITY_ATTRIBUTES *sa = sec_none_cloexec (flags);
+ read_mtx = CreateMutex (sa, FALSE, NULL);
+ if (read_mtx)
+ read_mtx_created = true;
+ else
+ {
+ debug_printf ("CreateMutex read_mtx failed: %E");
+ goto err;
+ }
+ }
+ 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;
+
+err_close_read_mtx:
+ if (read_mtx_created)
+ CloseHandle (read_mtx);
+err:
+ return false;
+}
+
+off_t
+fhandler_pipe::lseek (off_t offset, int whence)
+{
+ debug_printf ("(%D, %d)", offset, whence);
+ set_errno (ESPIPE);
+ return -1;
+}
+
+int
+fhandler_pipe::fadvise (off_t offset, off_t length, int advice)
+{
+ return ESPIPE;
+}
+
+int
+fhandler_pipe::ftruncate (off_t length, bool allow_truncate)
+{
+ return allow_truncate ? EINVAL : ESPIPE;
+}
+
+char *
+fhandler_pipe::get_proc_fd_name (char *buf)
+{
+ __small_sprintf (buf, "pipe:[%U]", get_plain_ino ());
+ return buf;
+}
+
+void
+fhandler_pipe::release_select_sem (const char *from)
+{
+ LONG n_release;
+ 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 ());
+ debug_printf("%s(%s) release %d", from,
+ get_dev () == FH_PIPER ? "PIPER" : "PIPEW", n_release);
+ if (n_release)
+ ReleaseSemaphore (select_sem, n_release, NULL);
+}
+
+void
+fhandler_pipe::raw_read (void *ptr, size_t& len)
+{
+ size_t nbytes = 0;
+ NTSTATUS status = STATUS_SUCCESS;
+ IO_STATUS_BLOCK io;
+
+ if (!len)
+ return;
+
+ DWORD timeout = is_nonblocking () ? 0 : INFINITE;
+ DWORD waitret = cygwait (read_mtx, timeout);
+ switch (waitret)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_TIMEOUT:
+ set_errno (EAGAIN);
+ len = (size_t) -1;
+ return;
+ case WAIT_SIGNALED:
+ set_errno (EINTR);
+ len = (size_t) -1;
+ return;
+ case WAIT_CANCELED:
+ pthread::static_cancel_self ();
+ /* NOTREACHED */
+ default:
+ /* Should not reach here. */
+ __seterrno ();
+ len = (size_t) -1;
+ return;
+ }
+ while (nbytes < len)
+ {
+ ULONG_PTR nbytes_now = 0;
+ ULONG len1 = (ULONG) (len - nbytes);
+
+ FILE_PIPE_LOCAL_INFORMATION fpli;
+ status = NtQueryInformationFile (get_handle (), &io,
+ &fpli, sizeof (fpli),
+ FilePipeLocalInformation);
+ if (NT_SUCCESS (status))
+ {
+ if (fpli.ReadDataAvailable == 0 && nbytes != 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:
+ case STATUS_PIPE_BROKEN:
+ /* This is really EOF. */
+ break;
+ case STATUS_PIPE_LISTENING:
+ case STATUS_PIPE_EMPTY:
+ if (nbytes != 0)
+ break;
+ if (is_nonblocking ())
+ {
+ set_errno (EAGAIN);
+ nbytes = (size_t) -1;
+ break;
+ }
+ waitret = cygwait (select_sem, 1);
+ if (waitret == WAIT_CANCELED)
+ pthread::static_cancel_self ();
+ else if (waitret == WAIT_SIGNALED)
+ {
+ set_errno (EINTR);
+ nbytes = (size_t) -1;
+ break;
+ }
+ continue;
+ default:
+ __seterrno_from_nt_status (status);
+ nbytes = (size_t) -1;
+ break;
+ }
+ }
+
+ if ((nbytes_now == 0 && !NT_SUCCESS (status))
+ || status == STATUS_BUFFER_OVERFLOW)
+ break;
+ }
+ ReleaseMutex (read_mtx);
+ 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
+fhandler_pipe_fifo::raw_write (const void *ptr, size_t len)
+{
+ size_t nbytes = 0;
+ ULONG chunk;
+ NTSTATUS status = STATUS_SUCCESS;
+ IO_STATUS_BLOCK io;
+ HANDLE evt;
+
+ if (!len)
+ return 0;
+
+ if (reader_closed ())
+ {
+ set_errno (EPIPE);
+ raise (SIGPIPE);
+ return -1;
+ }
+
+ if (len <= pipe_buf_size || pipe_buf_size == 0)
+ chunk = len;
+ else if (is_nonblocking ())
+ chunk = len = pipe_buf_size;
+ else
+ chunk = pipe_buf_size;
+
+ if (!(evt = CreateEvent (NULL, false, false, NULL)))
+ {
+ __seterrno ();
+ return -1;
+ }
+
+ /* Write in chunks, accumulating a total. If there's an error, just
+ return the accumulated total unless the first write fails, in
+ which case return -1. */
+ while (nbytes < len)
+ {
+ ULONG_PTR nbytes_now = 0;
+ size_t left = len - nbytes;
+ ULONG len1;
+ DWORD waitret = WAIT_OBJECT_0;
+
+ if (left > chunk)
+ len1 = chunk;
+ else
+ len1 = (ULONG) left;
+ /* NtWriteFile returns success with # of bytes written == 0 if writing
+ on a non-blocking pipe fails because the pipe buffer doesn't have
+ sufficient space.
+
+ POSIX requires
+ - A write request for {PIPE_BUF} or fewer bytes shall have the
+ following effect: if there is sufficient space available in the
+ pipe, write() shall transfer all the data and return the number
+ of bytes requested. Otherwise, write() shall transfer no data and
+ return -1 with errno set to [EAGAIN].
+
+ - A write request for more than {PIPE_BUF} bytes shall cause one
+ of the following:
+
+ - When at least one byte can be written, transfer what it can and
+ return the number of bytes written. When all data previously
+ written to the pipe is read, it shall transfer at least {PIPE_BUF}
+ bytes.
+
+ - When no data can be written, transfer no data, and return -1 with
+ errno set to [EAGAIN]. */
+ while (len1 > 0)
+ {
+ status = NtWriteFile (get_handle (), evt, NULL, NULL, &io,
+ (PVOID) ptr, len1, NULL, NULL);
+ if (status == STATUS_PENDING)
+ {
+ while (WAIT_TIMEOUT ==
+ (waitret = cygwait (evt, (DWORD) 0, cw_cancel | cw_sig)))
+ {
+ if (reader_closed ())
+ {
+ CancelIo (get_handle ());
+ set_errno (EPIPE);
+ raise (SIGPIPE);
+ goto out;
+ }
+ else
+ cygwait (select_sem, 10);
+ }
+ /* If io.Status is STATUS_CANCELLED after CancelIo, IO has
+ actually been cancelled and io.Information contains the
+ number of bytes processed so far.
+ Otherwise IO has been finished regulary and io.Status
+ contains valid success or error information. */
+ CancelIo (get_handle ());
+ if (waitret == WAIT_SIGNALED && io.Status != STATUS_CANCELLED)
+ waitret = WAIT_OBJECT_0;
+
+ if (waitret == WAIT_CANCELED)
+ status = STATUS_THREAD_CANCELED;
+ else if (waitret == WAIT_SIGNALED)
+ status = STATUS_THREAD_SIGNALED;
+ else
+ status = io.Status;
+ }
+ if (!is_nonblocking () || !NT_SUCCESS (status) || io.Information > 0
+ || len <= PIPE_BUF)
+ break;
+ len1 >>= 1;
+ }
+ if (isclosed ()) /* A signal handler might have closed the fd. */
+ {
+ if (waitret == WAIT_OBJECT_0)
+ set_errno (EBADF);
+ else
+ __seterrno ();
+ }
+ else if (NT_SUCCESS (status)
+ || status == STATUS_THREAD_CANCELED
+ || status == STATUS_THREAD_SIGNALED)
+ {
+ nbytes_now = io.Information;
+ ptr = ((char *) ptr) + nbytes_now;
+ nbytes += nbytes_now;
+ if (select_sem && nbytes_now > 0)
+ release_select_sem ("raw_write");
+ /* 0 bytes returned? EAGAIN. See above. */
+ if (NT_SUCCESS (status) && nbytes == 0)
+ set_errno (EAGAIN);
+ }
+ else if (STATUS_PIPE_IS_CLOSED (status))
+ {
+ set_errno (EPIPE);
+ raise (SIGPIPE);
+ }
+ else
+ __seterrno_from_nt_status (status);
+
+ if (nbytes_now == 0)
+ break;
+ }
+out:
+ CloseHandle (evt);
+ if (status == STATUS_THREAD_SIGNALED && nbytes == 0)
+ set_errno (EINTR);
+ else if (status == STATUS_THREAD_CANCELED)
+ pthread::static_cancel_self ();
+ return nbytes ?: -1;
+}
+
+void
+fhandler_pipe::set_close_on_exec (bool val)
+{
+ fhandler_base::set_close_on_exec (val);
+ if (read_mtx)
+ set_no_inheritance (read_mtx, val);
+ if (select_sem)
+ set_no_inheritance (select_sem, val);
+ if (query_hdl)
+ set_no_inheritance (query_hdl, val);
+ set_no_inheritance (hdl_cnt_mtx, val);
+}
+
+void
+fhandler_pipe::fixup_after_fork (HANDLE parent)
+{
+ fork_fixup (parent, hdl_cnt_mtx, "hdl_cnt_mtx");
+ WaitForSingleObject (hdl_cnt_mtx, INFINITE);
+ if (read_mtx)
+ fork_fixup (parent, read_mtx, "read_mtx");
+ if (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);
+ ReleaseMutex (hdl_cnt_mtx);
+}
+
+int
+fhandler_pipe::dup (fhandler_base *child, int flags)
+{
+ fhandler_pipe *ftp = (fhandler_pipe *) child;
+ ftp->set_popen_pid (0);
+
+ int res = 0;
+ WaitForSingleObject (hdl_cnt_mtx, INFINITE);
+ if (fhandler_base::dup (child, flags))
+ res = -1;
+ else if (read_mtx &&
+ !DuplicateHandle (GetCurrentProcess (), read_mtx,
+ GetCurrentProcess (), &ftp->read_mtx,
+ 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS))
+ {
+ __seterrno ();
+ ftp->close ();
+ res = -1;
+ }
+ else if (select_sem &&
+ !DuplicateHandle (GetCurrentProcess (), select_sem,
+ GetCurrentProcess (), &ftp->select_sem,
+ 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS))
+ {
+ __seterrno ();
+ ftp->close ();
+ 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);
+ return res;
+}
+
+int
+fhandler_pipe::close ()
+{
+ if (select_sem)
+ {
+ release_select_sem ("close");
+ CloseHandle (select_sem);
+ }
+ if (read_mtx)
+ CloseHandle (read_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 ();
+ ReleaseMutex (hdl_cnt_mtx);
+ CloseHandle (hdl_cnt_mtx);
+ if (query_hdl_proc)
+ CloseHandle (query_hdl_proc);
+ return ret;
+}
+
+#define PIPE_INTRO "\\\\.\\pipe\\cygwin-"
+
+/* 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 0 or GetLastError,
+ unlike CreatePipe, which returns a bool for success or failure. */
+DWORD
+fhandler_pipe::create (LPSECURITY_ATTRIBUTES sa_ptr, PHANDLE r, PHANDLE w,
+ DWORD psize, const char *name, DWORD open_mode,
+ int64_t *unique_id)
+{
+ /* Default to error. */
+ if (r)
+ *r = NULL;
+ if (w)
+ *w = NULL;
+
+ /* Ensure that there is enough pipe buffer space for atomic writes. */
+ if (!psize)
+ psize = DEFAULT_PIPEBUFSIZE;
+
+ char pipename[MAX_PATH];
+ size_t len = __small_sprintf (pipename, PIPE_INTRO "%S-",
+ &cygheap->installation_key);
+ DWORD pipe_mode = PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS;
+ if (!name)
+ pipe_mode |= pipe_byte ? PIPE_TYPE_BYTE : PIPE_TYPE_MESSAGE;
+ else
+ pipe_mode |= PIPE_TYPE_MESSAGE;
+
+ if (!name || (open_mode & PIPE_ADD_PID))
+ {
+ len += __small_sprintf (pipename + len, "%u-", GetCurrentProcessId ());
+ open_mode &= ~PIPE_ADD_PID;
+ }
+
+ if (name)
+ len += __small_sprintf (pipename + len, "%s", name);
+
+ open_mode |= PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE;
+
+ /* 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. */
+ DWORD err = 0;
+ while (r && !*r)
+ {
+ static volatile ULONG pipe_unique_id;
+ if (!name)
+ {
+ LONG id = InterlockedIncrement ((LONG *) &pipe_unique_id);
+ __small_sprintf (pipename + len, "pipe-%p", id);
+ if (unique_id)
+ *unique_id = ((int64_t) id << 32 | GetCurrentProcessId ());
+ }
+
+ debug_printf ("name %s, size %u, mode %s", pipename, psize,
+ (pipe_mode & PIPE_TYPE_MESSAGE)
+ ? "PIPE_TYPE_MESSAGE" : "PIPE_TYPE_BYTE");
+
+ /* 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.
+
+ Note that the write side of the pipe is opened as PIPE_TYPE_MESSAGE.
+ This *seems* to more closely mimic Linux pipe behavior and is
+ definitely required for pty handling since fhandler_pty_master
+ writes to the pipe in chunks, terminated by newline when CANON mode
+ is specified. */
+ *r = CreateNamedPipe (pipename, open_mode, pipe_mode, 1, psize,
+ psize, NMPWAIT_USE_DEFAULT_WAIT, sa_ptr);
+
+ if (*r != INVALID_HANDLE_VALUE)
+ {
+ debug_printf ("pipe read handle %p", *r);
+ err = 0;
+ break;
+ }
+
+ 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", !name ? ", retrying" : "");
+ if (!name)
+ *r = NULL;
+ break;
+ case ERROR_ACCESS_DENIED:
+ /* The pipe is already open with incompatible parameters.
+ Pick a new name and retry. */
+ debug_printf ("pipe access denied%s", !name ? ", retrying" : "");
+ if (!name)
+ *r = NULL;
+ break;
+ default:
+ {
+ err = GetLastError ();
+ debug_printf ("failed, %E");
+ }
+ }
+ }
+
+ if (err)
+ {
+ *r = NULL;
+ return err;
+ }
+
+ if (!w)
+ debug_printf ("pipe write handle NULL");
+ else
+ {
+ debug_printf ("CreateFile: name %s", pipename);
+
+ /* Open the named pipe for writing.
+ Be sure to permit FILE_READ_ATTRIBUTES access. */
+ DWORD access = GENERIC_WRITE | FILE_READ_ATTRIBUTES;
+ if ((open_mode & PIPE_ACCESS_DUPLEX) == PIPE_ACCESS_DUPLEX)
+ access |= GENERIC_READ | FILE_WRITE_ATTRIBUTES;
+ *w = CreateFile (pipename, access, 0, sa_ptr, OPEN_EXISTING,
+ open_mode & FILE_FLAG_OVERLAPPED, 0);
+
+ if (!*w || *w == INVALID_HANDLE_VALUE)
+ {
+ /* Failure. */
+ DWORD err = GetLastError ();
+ debug_printf ("CreateFile failed, r %p, %E", r);
+ if (r)
+ CloseHandle (*r);
+ *w = NULL;
+ return err;
+ }
+
+ debug_printf ("pipe write handle %p", *w);
+ }
+
+ /* Success. */
+ return 0;
+}
+
+inline static bool
+is_running_as_service (void)
+{
+ return check_token_membership (well_known_service_sid)
+ || cygheap->user.saved_sid () == well_known_system_sid;
+}
+
+/* The next version of fhandler_pipe::create used to call the previous
+ version. But the read handle created by the latter doesn't have
+ FILE_WRITE_ATTRIBUTES access unless the pipe is created with
+ PIPE_ACCESS_DUPLEX, and it doesn't seem possible to add that access
+ right. This causes set_pipe_non_blocking to fail.
+
+ To fix this we will define a helper function 'nt_create' that is
+ similar to the above fhandler_pipe::create but uses
+ NtCreateNamedPipeFile instead of CreateNamedPipe; this gives more
+ flexibility in setting the access rights. We will use this helper
+ function in the version of fhandler_pipe::create below, which
+ suffices for all of our uses of set_pipe_non_blocking. For
+ simplicity, nt_create will omit the 'open_mode' and 'name'
+ parameters, which aren't needed for our purposes. */
+
+static int nt_create (LPSECURITY_ATTRIBUTES, HANDLE &, HANDLE &, DWORD,
+ int64_t *);
+
+int
+fhandler_pipe::create (fhandler_pipe *fhs[2], unsigned psize, int mode)
+{
+ HANDLE r, w;
+ SECURITY_ATTRIBUTES *sa = sec_none_cloexec (mode);
+ int res = -1;
+ int64_t unique_id;
+
+ int ret = nt_create (sa, r, w, psize, &unique_id);
+ if (ret)
+ {
+ __seterrno_from_win_error (ret);
+ goto out;
+ }
+ if ((fhs[0] = (fhandler_pipe *) build_fh_dev (*piper_dev)) == NULL)
+ goto err_close_rw_handle;
+ if ((fhs[1] = (fhandler_pipe *) build_fh_dev (*pipew_dev)) == NULL)
+ goto err_delete_fhs0;
+ mode |= mode & O_TEXT ?: O_BINARY;
+ 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);
+
+ /* For the read side of the pipe, add a mutex. See raw_read for the
+ usage. */
+ fhs[0]->read_mtx = CreateMutexW (sa, FALSE, NULL);
+ if (!fhs[0]->read_mtx)
+ goto err_delete_fhs1;
+
+ fhs[0]->select_sem = CreateSemaphore (sa, 0, INT32_MAX, NULL);
+ if (!fhs[0]->select_sem)
+ goto err_close_read_mtx;
+ if (!DuplicateHandle (GetCurrentProcess (), fhs[0]->select_sem,
+ GetCurrentProcess (), &fhs[1]->select_sem,
+ 0, sa->bInheritHandle, DUPLICATE_SAME_ACCESS))
+ 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;
+ 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:
+ CloseHandle (fhs[0]->select_sem);
+err_close_read_mtx:
+ CloseHandle (fhs[0]->read_mtx);
+err_delete_fhs1:
+ delete fhs[1];
+err_delete_fhs0:
+ delete fhs[0];
+err_close_rw_handle:
+ NtClose (r);
+ NtClose (w);
+out:
+ debug_printf ("%R = pipe([%p, %p], %d, %y)",
+ res, fhs[0], fhs[1], psize, mode);
+ return res;
+}
+
+static int
+nt_create (LPSECURITY_ATTRIBUTES sa_ptr, HANDLE &r, HANDLE &w,
+ DWORD psize, int64_t *unique_id)
+{
+ NTSTATUS status;
+ HANDLE npfsh;
+ ACCESS_MASK access;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ LARGE_INTEGER timeout;
+
+ /* Default to error. */
+ r = NULL;
+ w = NULL;
+
+ status = fhandler_base::npfs_handle (npfsh);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return GetLastError ();
+ }
+
+ /* Ensure that there is enough pipe buffer space for atomic writes. */
+ if (!psize)
+ psize = DEFAULT_PIPEBUFSIZE;
+
+ UNICODE_STRING pipename;
+ WCHAR pipename_buf[MAX_PATH];
+ size_t len = __small_swprintf (pipename_buf, L"%S-%u-",
+ &cygheap->installation_key,
+ GetCurrentProcessId ());
+
+ 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
+ : FILE_PIPE_MESSAGE_TYPE;
+
+ /* Retry NtCreateNamedPipeFile as long as the pipe name is in use.
+ Retrying will probably never be necessary, but we want
+ to be as robust as possible. */
+ DWORD err = 0;
+ while (!r)
+ {
+ static volatile ULONG pipe_unique_id;
+ LONG id = InterlockedIncrement ((LONG *) &pipe_unique_id);
+ __small_swprintf (pipename_buf + len, L"pipe-nt-%p", id);
+ if (unique_id)
+ *unique_id = ((int64_t) id << 32 | GetCurrentProcessId ());
+
+ debug_printf ("name %W, size %u, mode %s", pipename_buf, psize,
+ (pipe_type & FILE_PIPE_MESSAGE_TYPE)
+ ? "PIPE_TYPE_MESSAGE" : "PIPE_TYPE_BYTE");
+
+ RtlInitUnicodeString (&pipename, pipename_buf);
+
+ InitializeObjectAttributes (&attr, &pipename,
+ sa_ptr->bInheritHandle ? OBJ_INHERIT : 0,
+ npfsh, sa_ptr->lpSecurityDescriptor);
+
+ timeout.QuadPart = -500000;
+ /* Set FILE_SYNCHRONOUS_IO_NONALERT flag so that native
+ C# programs work with cygwin pipe. */
+ status = NtCreateNamedPipeFile (&r, access, &attr, &io,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ FILE_CREATE,
+ FILE_SYNCHRONOUS_IO_NONALERT, pipe_type,
+ FILE_PIPE_BYTE_STREAM_MODE,
+ 0, 1, psize, psize, &timeout);
+
+ if (NT_SUCCESS (status))
+ {
+ debug_printf ("pipe read handle %p", r);
+ err = 0;
+ break;
+ }
+
+ switch (status)
+ {
+ case STATUS_PIPE_BUSY:
+ case STATUS_INSTANCE_NOT_AVAILABLE:
+ case STATUS_PIPE_NOT_AVAILABLE:
+ /* The pipe is already open with compatible parameters.
+ Pick a new name and retry. */
+ debug_printf ("pipe busy, retrying");
+ r = NULL;
+ break;
+ case STATUS_ACCESS_DENIED:
+ /* The pipe is already open with incompatible parameters.
+ Pick a new name and retry. */
+ debug_printf ("pipe access denied, retrying");
+ r = NULL;
+ break;
+ default:
+ {
+ __seterrno_from_nt_status (status);
+ err = GetLastError ();
+ debug_printf ("failed, %E");
+ r = NULL;
+ }
+ }
+ }
+
+ if (err)
+ {
+ r = NULL;
+ return err;
+ }
+
+ debug_printf ("NtOpenFile: name %S", &pipename);
+
+ access = GENERIC_WRITE | FILE_READ_ATTRIBUTES | SYNCHRONIZE;
+ status = NtOpenFile (&w, access, &attr, &io, 0, 0);
+ if (!NT_SUCCESS (status))
+ {
+ DWORD err = GetLastError ();
+ debug_printf ("NtOpenFile failed, r %p, %E", r);
+ if (r)
+ NtClose (r);
+ w = NULL;
+ return err;
+ }
+
+ /* Success. */
+ return 0;
+}
+
+/* Called by dtable::init_std_file_from_handle for stdio handles
+ inherited from non-Cygwin processes. */
+void
+fhandler_pipe::set_pipe_buf_size ()
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ FILE_PIPE_LOCAL_INFORMATION fpli;
+
+ status = NtQueryInformationFile (get_handle (), &io, &fpli, sizeof fpli,
+ FilePipeLocalInformation);
+ if (NT_SUCCESS (status))
+ pipe_buf_size = fpli.InboundQuota;
+}
+
+int
+fhandler_pipe::ioctl (unsigned int cmd, void *p)
+{
+ int n;
+
+ switch (cmd)
+ {
+ case FIONREAD:
+ if (get_device () == FH_PIPEW)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (!PeekNamedPipe (get_handle (), NULL, 0, NULL, (DWORD *) &n, NULL))
+ {
+ __seterrno ();
+ return -1;
+ }
+ break;
+ default:
+ return fhandler_base::ioctl (cmd, p);
+ break;
+ }
+ *(int *) p = n;
+ 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
+fhandler_pipe::fstat (struct stat *buf)
+{
+ int ret = fhandler_base::fstat (buf);
+ if (!ret)
+ {
+ buf->st_dev = FH_PIPE;
+ if (!(buf->st_ino = get_plain_ino ()))
+ sscanf (get_name (), "/proc/%*d/fd/pipe:[%llu]",
+ (long long *) &buf->st_ino);
+ }
+ return ret;
+}
+
+int
+fhandler_pipe::fstatvfs (struct statvfs *sfs)
+{
+ set_errno (EBADF);
+ 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 ();
+
+ /* 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;
+ ntfn->Name.Buffer[ntfn->Name.Length / sizeof (WCHAR)] = L'\0';
+ uint64_t key;
+ DWORD pid;
+ LONG id;
+ if (swscanf (ntfn->Name.Buffer,
+ L"\\Device\\NamedPipe\\%llx-%u-pipe-nt-0x%x",
+ &key, &pid, &id) == 3 &&
+ key == pipename_key && pid == pipename_pid && id == pipename_id)
+ return h;
+hdl_err:
+ CloseHandle (h);
+cache_err:
+ CloseHandle (query_hdl_proc);
+ query_hdl_proc = NULL;
+ query_hdl_value = NULL;
+ }
+
+ status = NtQueryObject (get_handle (), ObjectNameInformation, ntfn,
+ 65536, &len);
+ if (!NT_SUCCESS (status) || !ntfn->Name.Buffer)
+ return NULL; /* Non cygwin pipe? */
+ WCHAR name[MAX_PATH];
+ int namelen = min (ntfn->Name.Length / sizeof (WCHAR), MAX_PATH-1);
+ memcpy (name, ntfn->Name.Buffer, namelen * sizeof (WCHAR));
+ name[namelen] = L'\0';
+ if (swscanf (name, L"\\Device\\NamedPipe\\%llx-%u-pipe-nt-0x%x",
+ &pipename_key, &pipename_pid, &pipename_id) != 3)
+ return NULL; /* Non cygwin pipe? */
+
+ if (wincap.has_query_process_handle_info ())
+ return get_query_hdl_per_process (name, ntfn); /* Since Win8 */
+ else
+ return get_query_hdl_per_system (name, ntfn); /* Win7 */
+}
+
+/* This function is faster than get_query_hdl_per_system(), however,
+ only works since Windows 8 because ProcessHandleInformation is not
+ suppoted by NtQueryInformationProcess() before Windows 8. */
+HANDLE
+fhandler_pipe::get_query_hdl_per_process (WCHAR *name,
+ OBJECT_NAME_INFORMATION *ntfn)
+{
+ NTSTATUS status;
+ ULONG len;
+ DWORD n_process = 256;
+ PSYSTEM_PROCESS_INFORMATION spi;
+ do
+ { /* Enumerate processes */
+ DWORD nbytes = n_process * sizeof (SYSTEM_PROCESS_INFORMATION);
+ spi = (PSYSTEM_PROCESS_INFORMATION) HeapAlloc (GetProcessHeap (),
+ 0, nbytes);
+ if (!spi)
+ return NULL;
+ status = NtQuerySystemInformation (SystemProcessInformation,
+ spi, nbytes, &len);
+ if (NT_SUCCESS (status))
+ break;
+ HeapFree (GetProcessHeap (), 0, spi);
+ n_process *= 2;
+ }
+ while (n_process < (1L<<20) && status == STATUS_INFO_LENGTH_MISMATCH);
+ if (!NT_SUCCESS (status))
+ return NULL;
+
+ /* In most cases, it is faster to check the processes in reverse order.
+ To do this, store PIDs into an array. */
+ DWORD *proc_pids = (DWORD *) HeapAlloc (GetProcessHeap (), 0,
+ n_process * sizeof (DWORD));
+ if (!proc_pids)
+ {
+ HeapFree (GetProcessHeap (), 0, spi);
+ return NULL;
+ }
+ PSYSTEM_PROCESS_INFORMATION p = spi;
+ n_process = 0;
+ while (true)
+ {
+ proc_pids[n_process++] = (DWORD)(intptr_t) p->UniqueProcessId;
+ if (!p->NextEntryOffset)
+ break;
+ p = (PSYSTEM_PROCESS_INFORMATION) ((char *) p + p->NextEntryOffset);
+ }
+ HeapFree (GetProcessHeap (), 0, spi);
+
+ for (LONG i = (LONG) n_process - 1; i >= 0; i--)
+ {
+ HANDLE proc = OpenProcess (PROCESS_DUP_HANDLE
+ | PROCESS_QUERY_INFORMATION,
+ 0, proc_pids[i]);
+ 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;
+ ntfn->Name.Buffer[ntfn->Name.Length / sizeof (WCHAR)] = L'\0';
+ if (wcscmp (name, ntfn->Name.Buffer) == 0)
+ {
+ query_hdl_proc = proc;
+ query_hdl_value = (HANDLE)(intptr_t) phi->Handles[j].HandleValue;
+ HeapFree (GetProcessHeap (), 0, phi);
+ HeapFree (GetProcessHeap (), 0, proc_pids);
+ return h;
+ }
+close_handle:
+ CloseHandle (h);
+ }
+ HeapFree (GetProcessHeap (), 0, phi);
+close_proc:
+ CloseHandle (proc);
+ }
+ HeapFree (GetProcessHeap (), 0, proc_pids);
+ return NULL;
+}
+
+/* This function is slower than get_query_hdl_per_process(), however,
+ works even before Windows 8. */
+HANDLE
+fhandler_pipe::get_query_hdl_per_system (WCHAR *name,
+ OBJECT_NAME_INFORMATION *ntfn)
+{
+ NTSTATUS status;
+ SIZE_T n_handle = 65536;
+ PSYSTEM_HANDLE_INFORMATION shi;
+ do
+ { /* Enumerate handles */
+ SIZE_T nbytes =
+ sizeof (ULONG) + n_handle * sizeof (SYSTEM_HANDLE_TABLE_ENTRY_INFO);
+ shi = (PSYSTEM_HANDLE_INFORMATION) HeapAlloc (GetProcessHeap (),
+ 0, nbytes);
+ if (!shi)
+ return NULL;
+ status = NtQuerySystemInformation (SystemHandleInformation,
+ shi, nbytes, NULL);
+ if (NT_SUCCESS (status))
+ break;
+ HeapFree (GetProcessHeap (), 0, shi);
+ n_handle *= 2;
+ }
+ while (n_handle < (1L<<23) && status == STATUS_INFO_LENGTH_MISMATCH);
+ if (!NT_SUCCESS (status))
+ return NULL;
+
+ for (LONG i = (LONG) shi->NumberOfHandles - 1; i >= 0; i--)
+ {
+ /* 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 (shi->Handles[i].GrantedAccess != access)
+ continue;
+
+ /* Retrieve handle */
+ HANDLE proc = OpenProcess (PROCESS_DUP_HANDLE, 0,
+ shi->Handles[i].UniqueProcessId);
+ if (!proc)
+ continue;
+ HANDLE h = (HANDLE)(intptr_t) shi->Handles[i].HandleValue;
+ BOOL res = DuplicateHandle (proc, h, GetCurrentProcess (), &h,
+ FILE_READ_DATA, 0, 0);
+ if (!res)
+ goto close_proc;
+
+ /* Check object name */
+ ULONG len;
+ status = NtQueryObject (h, ObjectNameInformation, ntfn, 65536, &len);
+ if (!NT_SUCCESS (status) || !ntfn->Name.Buffer)
+ goto close_handle;
+ ntfn->Name.Buffer[ntfn->Name.Length / sizeof (WCHAR)] = L'\0';
+ if (wcscmp (name, ntfn->Name.Buffer) == 0)
+ {
+ query_hdl_proc = proc;
+ query_hdl_value = (HANDLE)(intptr_t) shi->Handles[i].HandleValue;
+ HeapFree (GetProcessHeap (), 0, shi);
+ return h;
+ }
+close_handle:
+ CloseHandle (h);
+close_proc:
+ CloseHandle (proc);
+ }
+ HeapFree (GetProcessHeap (), 0, shi);
+ return NULL;
+}
diff --git a/winsup/cygwin/fhandler/proc.cc b/winsup/cygwin/fhandler/proc.cc
new file mode 100644
index 000000000..24067f687
--- /dev/null
+++ b/winsup/cygwin/fhandler/proc.cc
@@ -0,0 +1,2059 @@
+/* fhandler_proc.cc: fhandler for /proc virtual filesystem
+
+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 "miscfuncs.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "shared_info.h"
+#include "fhandler.h"
+#include "fhandler_virtual.h"
+#include "pinfo.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "tls_pbuf.h"
+#include <sys/utsname.h>
+#include <sys/param.h>
+#include <sys/sysinfo.h>
+#include "ntdll.h"
+#include <winioctl.h>
+#include <wchar.h>
+#include <wctype.h>
+#include "cpuid.h"
+#include "mount.h"
+#include <math.h>
+
+#define _LIBC
+#include <dirent.h>
+
+static off_t format_proc_loadavg (void *, char *&);
+static off_t format_proc_meminfo (void *, char *&);
+static off_t format_proc_stat (void *, char *&);
+static off_t format_proc_version (void *, char *&);
+static off_t format_proc_uptime (void *, char *&);
+static off_t format_proc_cpuinfo (void *, char *&);
+static off_t format_proc_partitions (void *, char *&);
+static off_t format_proc_self (void *, char *&);
+static off_t format_proc_cygdrive (void *, char *&);
+static off_t format_proc_mounts (void *, char *&);
+static off_t format_proc_filesystems (void *, char *&);
+static off_t format_proc_swaps (void *, char *&);
+static off_t format_proc_devices (void *, char *&);
+static off_t format_proc_misc (void *, char *&);
+
+/* names of objects in /proc */
+static const virt_tab_t proc_tab[] = {
+ { _VN ("."), FH_PROC, virt_directory, NULL },
+ { _VN (".."), FH_PROC, virt_directory, NULL },
+ { _VN ("cpuinfo"), FH_PROC, virt_file, format_proc_cpuinfo },
+ { _VN ("cygdrive"), FH_PROC, virt_symlink, format_proc_cygdrive },
+ { _VN ("devices"), FH_PROC, virt_file, format_proc_devices },
+ { _VN ("filesystems"), FH_PROC, virt_file, format_proc_filesystems },
+ { _VN ("loadavg"), FH_PROC, virt_file, format_proc_loadavg },
+ { _VN ("meminfo"), FH_PROC, virt_file, format_proc_meminfo },
+ { _VN ("misc"), FH_PROC, virt_file, format_proc_misc },
+ { _VN ("mounts"), FH_PROC, virt_symlink, format_proc_mounts },
+ { _VN ("net"), FH_PROCNET, virt_directory, NULL },
+ { _VN ("partitions"), FH_PROC, virt_file, format_proc_partitions },
+ { _VN ("registry"), FH_REGISTRY, virt_directory, NULL },
+ { _VN ("registry32"), FH_REGISTRY, virt_directory, NULL },
+ { _VN ("registry64"), FH_REGISTRY, virt_directory, NULL },
+ { _VN ("self"), FH_PROC, virt_symlink, format_proc_self },
+ { _VN ("stat"), FH_PROC, virt_file, format_proc_stat },
+ { _VN ("swaps"), FH_PROC, virt_file, format_proc_swaps },
+ { _VN ("sys"), FH_PROCSYS, virt_directory, NULL },
+ { _VN ("sysvipc"), FH_PROCSYSVIPC, virt_directory, NULL },
+ { _VN ("uptime"), FH_PROC, virt_file, format_proc_uptime },
+ { _VN ("version"), FH_PROC, virt_file, format_proc_version },
+ { NULL, 0, FH_NADA, virt_none, NULL }
+};
+
+#define PROC_DIR_COUNT 4
+
+static const int PROC_LINK_COUNT = (sizeof (proc_tab) / sizeof (virt_tab_t)) - 1;
+
+/* name of the /proc filesystem */
+const char proc[] = "/proc";
+const size_t proc_len = sizeof (proc) - 1;
+
+/* bsearch compare function. */
+static int
+proc_tab_cmp (const void *key, const void *memb)
+{
+ int ret = strncmp (((virt_tab_t *) key)->name, ((virt_tab_t *) memb)->name,
+ ((virt_tab_t *) memb)->name_len);
+ if (!ret && ((virt_tab_t *) key)->name[((virt_tab_t *) memb)->name_len] != '\0' && ((virt_tab_t *) key)->name[((virt_tab_t *) memb)->name_len] != '/')
+ return 1;
+ return ret;
+}
+
+/* Helper function to perform a binary search of the incoming pathname
+ against the alpha-sorted virtual file table. */
+virt_tab_t *
+virt_tab_search (const char *path, bool prefix, const virt_tab_t *table,
+ size_t nelem)
+{
+ virt_tab_t key = { path, 0, FH_NADA, virt_none, NULL };
+ virt_tab_t *entry = (virt_tab_t *) bsearch (&key, table, nelem,
+ sizeof (virt_tab_t),
+ proc_tab_cmp);
+ if (entry && (path[entry->name_len] == '\0'
+ || (prefix && path[entry->name_len] == '/')))
+ return entry;
+ return NULL;
+}
+
+/* Auxillary function that returns the fhandler associated with the given
+ path. */
+fh_devices
+fhandler_proc::get_proc_fhandler (const char *path)
+{
+ debug_printf ("get_proc_fhandler(%s)", path);
+ path += proc_len;
+ /* Since this method is called from path_conv::check we can't rely on
+ it being normalised and therefore the path may have runs of slashes
+ in it. */
+ while (isdirsep (*path))
+ path++;
+
+ /* Check if this is the root of the virtual filesystem (i.e. /proc). */
+ if (*path == 0)
+ return FH_PROC;
+
+ virt_tab_t *entry = virt_tab_search (path, true, proc_tab,
+ PROC_LINK_COUNT);
+ if (entry)
+ return entry->fhandler;
+
+ char *e;
+ pid_t pid = strtoul (path, &e, 10);
+ if (*e != '/' && *e != '\0')
+ return FH_NADA;
+ pinfo p (pid);
+ /* If p->pid != pid, then pid is actually the Windows PID for an execed
+ Cygwin process, and the pinfo entry is the additional entry created
+ at exec time. We don't want to enable the user to access a process
+ entry by using the Win32 PID, though. */
+ if (p && p->pid == pid)
+ {
+ /* Check for entry in fd subdir */
+ if (!strncmp (++e, "fd/", 3) && e[3] != '\0')
+ return FH_PROCESSFD;
+ return FH_PROCESS;
+ }
+
+ bool has_subdir = false;
+ while (*path)
+ if (isdirsep (*path++))
+ {
+ has_subdir = true;
+ break;
+ }
+
+ if (has_subdir)
+ /* The user is trying to access a non-existent subdirectory of /proc. */
+ return FH_NADA;
+ else
+ /* Return FH_PROC so that we can return EROFS if the user is trying to
+ create a file. */
+ return FH_PROC;
+}
+
+virtual_ftype_t
+fhandler_proc::exists ()
+{
+ const char *path = get_name ();
+ debug_printf ("exists (%s)", path);
+ path += proc_len;
+ if (*path == 0)
+ return virt_rootdir;
+ virt_tab_t *entry = virt_tab_search (path + 1, false, proc_tab,
+ PROC_LINK_COUNT);
+ if (entry)
+ {
+ fileid = entry - proc_tab;
+ return entry->type;
+ }
+ return virt_none;
+}
+
+fhandler_proc::fhandler_proc ():
+ fhandler_virtual ()
+{
+}
+
+int
+fhandler_proc::fstat (struct stat *buf)
+{
+ const char *path = get_name ();
+ debug_printf ("fstat (%s)", path);
+
+ path += proc_len;
+ fhandler_base::fstat (buf);
+
+ buf->st_mode &= ~_IFMT & NO_W;
+
+ if (!*path)
+ {
+ winpids pids ((DWORD) 0);
+ buf->st_ino = 2;
+ buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
+ buf->st_nlink = PROC_DIR_COUNT + 2 + pids.npids;
+ return 0;
+ }
+ else
+ {
+ virt_tab_t *entry = virt_tab_search (path + 1, false, proc_tab,
+ PROC_LINK_COUNT);
+ if (entry)
+ {
+ if (entry->type == virt_directory)
+ buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
+ else if (entry->type == virt_symlink)
+ buf->st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
+ else
+ {
+ buf->st_mode &= NO_X;
+ buf->st_mode |= S_IFREG;
+ }
+ return 0;
+ }
+ }
+ set_errno (ENOENT);
+ return -1;
+}
+
+DIR *
+fhandler_proc::opendir (int fd)
+{
+ DIR *dir = fhandler_virtual::opendir (fd);
+ if (dir && !(dir->__handle = (void *) new winpids ((DWORD) 0)))
+ {
+ free (dir);
+ dir = NULL;
+ set_errno (ENOMEM);
+ }
+ return dir;
+}
+
+int
+fhandler_proc::closedir (DIR *dir)
+{
+ delete (winpids *) dir->__handle;
+ return fhandler_virtual::closedir (dir);
+}
+
+int
+fhandler_proc::readdir (DIR *dir, dirent *de)
+{
+ int res;
+ if (dir->__d_position < PROC_LINK_COUNT)
+ {
+ strcpy (de->d_name, proc_tab[dir->__d_position].name);
+ de->d_type = virt_ftype_to_dtype (proc_tab[dir->__d_position].type);
+ dir->__d_position++;
+ dir->__flags |= dirent_saw_dot | dirent_saw_dot_dot;
+ res = 0;
+ }
+ else
+ {
+ winpids &pids = *(winpids *) dir->__handle;
+ int found = 0;
+ res = ENMFILE;
+ for (unsigned i = 0; i < pids.npids; i++)
+ if (found++ == dir->__d_position - PROC_LINK_COUNT)
+ {
+ __small_sprintf (de->d_name, "%d", pids[i]->pid);
+ de->d_type = DT_DIR;
+ dir->__d_position++;
+ res = 0;
+ break;
+ }
+ }
+
+ syscall_printf ("%d = readdir(%p, %p) (%s)", res, dir, de, de->d_name);
+ return res;
+}
+
+int
+fhandler_proc::open (int flags, mode_t mode)
+{
+ int proc_file_no = -1;
+
+ int res = fhandler_virtual::open (flags, mode);
+ if (!res)
+ goto out;
+
+ nohandle (true);
+
+ const char *path;
+
+ path = get_name () + proc_len;
+
+ if (!*path)
+ {
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ res = 0;
+ goto out;
+ }
+ else if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ res = 0;
+ goto out;
+ }
+ else
+ {
+ diropen = true;
+ goto success;
+ }
+ }
+
+ proc_file_no = -1;
+ for (int i = 0; proc_tab[i].name; i++)
+ if (path_prefix_p (proc_tab[i].name, path + 1, strlen (proc_tab[i].name),
+ false))
+ {
+ proc_file_no = i;
+ if (proc_tab[i].fhandler != FH_PROC)
+ {
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ res = 0;
+ goto out;
+ }
+ else if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ res = 0;
+ goto out;
+ }
+ else
+ {
+ diropen = true;
+ goto success;
+ }
+ }
+ }
+
+ if (proc_file_no == -1)
+ {
+ if (flags & O_CREAT)
+ {
+ set_errno (EROFS);
+ res = 0;
+ goto out;
+ }
+ else
+ {
+ set_errno (ENOENT);
+ res = 0;
+ goto out;
+ }
+ }
+ if (flags & O_WRONLY)
+ {
+ set_errno (EROFS);
+ res = 0;
+ goto out;
+ }
+
+ fileid = proc_file_no;
+ if (!fill_filebuf ())
+ {
+ res = 0;
+ goto out;
+ }
+
+ if (flags & O_APPEND)
+ position = filesize;
+ else
+ position = 0;
+
+success:
+ res = 1;
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ set_open_status ();
+out:
+ syscall_printf ("%d = fhandler_proc::open(%y, 0%o)", res, flags, mode);
+ return res;
+}
+
+bool
+fhandler_proc::fill_filebuf ()
+{
+ if (fileid < PROC_LINK_COUNT && proc_tab[fileid].format_func)
+ {
+ filesize = proc_tab[fileid].format_func (NULL, filebuf);
+ if (filesize > 0)
+ return true;
+ }
+ return false;
+}
+
+extern "C" int uname_x (struct utsname *);
+
+static off_t
+format_proc_version (void *, char *&destbuf)
+{
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+ char *bufptr = buf;
+ struct utsname uts_name;
+
+ uname_x (&uts_name);
+ bufptr += __small_sprintf (bufptr, "%s version %s (%s@%s) (%s) %s\n",
+ uts_name.sysname, uts_name.release, USERNAME, HOSTNAME,
+ GCC_VERSION, uts_name.version);
+
+ destbuf = (char *) crealloc_abort (destbuf, bufptr - buf);
+ memcpy (destbuf, buf, bufptr - buf);
+ return bufptr - buf;
+}
+
+static off_t
+format_proc_loadavg (void *, char *&destbuf)
+{
+ extern int get_process_state (DWORD dwProcessId);
+ unsigned int running = 0;
+ winpids pids ((DWORD) 0);
+
+ for (unsigned i = 0; i < pids.npids; i++)
+ switch (get_process_state (i)) {
+ case 'O':
+ case 'R':
+ running++;
+ break;
+ }
+
+ double loadavg[3] = { 0.0, 0.0, 0.0 };
+ getloadavg (loadavg, 3);
+
+#define HUNDRETHS(l) (int)((l - floor(l))*100)
+
+ destbuf = (char *) crealloc_abort (destbuf, 48);
+ return __small_sprintf (destbuf, "%u.%02u %u.%02u %u.%02u %u/%u\n",
+ (int)loadavg[0], HUNDRETHS(loadavg[0]),
+ (int)loadavg[1], HUNDRETHS(loadavg[1]),
+ (int)loadavg[2], HUNDRETHS(loadavg[2]),
+ running, (unsigned int)pids.npids);
+}
+
+static off_t
+format_proc_meminfo (void *, char *&destbuf)
+{
+ unsigned long long mem_total, mem_free, swap_total, swap_free;
+ struct sysinfo info;
+
+ sysinfo (&info);
+ mem_total = (unsigned long long) info.totalram * info.mem_unit;
+ mem_free = (unsigned long long) info.freeram * info.mem_unit;
+ swap_total = (unsigned long long) info.totalswap * info.mem_unit;
+ swap_free = (unsigned long long) info.freeswap * info.mem_unit;
+
+ destbuf = (char *) crealloc_abort (destbuf, 512);
+ return sprintf (destbuf, "MemTotal: %10llu kB\n"
+ "MemFree: %10llu kB\n"
+ "HighTotal: 0 kB\n"
+ "HighFree: 0 kB\n"
+ "LowTotal: %10llu kB\n"
+ "LowFree: %10llu kB\n"
+ "SwapTotal: %10llu kB\n"
+ "SwapFree: %10llu kB\n",
+ mem_total >> 10, mem_free >> 10,
+ mem_total >> 10, mem_free >> 10,
+ swap_total >> 10, swap_free >> 10);
+}
+
+static off_t
+format_proc_uptime (void *, char *&destbuf)
+{
+ unsigned long long uptime = 0ULL, idle_time = 0ULL;
+ NTSTATUS status;
+ SYSTEM_TIMEOFDAY_INFORMATION stodi;
+ /* Sizeof SYSTEM_PERFORMANCE_INFORMATION on 64 bit systems. It
+ appears to contain some trailing additional information from
+ what I can tell after examining the content.
+ FIXME: It would be nice if this could be verified somehow. */
+ const size_t sizeof_spi = sizeof (SYSTEM_PERFORMANCE_INFORMATION) + 16;
+ PSYSTEM_PERFORMANCE_INFORMATION spi = (PSYSTEM_PERFORMANCE_INFORMATION)
+ alloca (sizeof_spi);
+
+ status = NtQuerySystemInformation (SystemTimeOfDayInformation, &stodi,
+ sizeof stodi, NULL);
+ if (NT_SUCCESS (status))
+ uptime = (stodi.CurrentTime.QuadPart - stodi.BootTime.QuadPart) / 100000ULL;
+ else
+ debug_printf ("NtQuerySystemInformation(SystemTimeOfDayInformation), "
+ "status %y", status);
+
+ if (NT_SUCCESS (NtQuerySystemInformation (SystemPerformanceInformation,
+ spi, sizeof_spi, NULL)))
+ idle_time = (spi->IdleTime.QuadPart / wincap.cpu_count ())
+ / 100000ULL;
+
+ destbuf = (char *) crealloc_abort (destbuf, 80);
+ return __small_sprintf (destbuf, "%U.%02u %U.%02u\n",
+ uptime / 100, long (uptime % 100),
+ idle_time / 100, long (idle_time % 100));
+}
+
+static off_t
+format_proc_stat (void *, char *&destbuf)
+{
+ unsigned long pages_in = 0UL, pages_out = 0UL, interrupt_count = 0UL,
+ context_switches = 0UL, swap_in = 0UL, swap_out = 0UL;
+ time_t boot_time = 0;
+ NTSTATUS status;
+ /* Sizeof SYSTEM_PERFORMANCE_INFORMATION on 64 bit systems. It
+ appears to contain some trailing additional information from
+ what I can tell after examining the content.
+ FIXME: It would be nice if this could be verified somehow. */
+ const size_t sizeof_spi = sizeof (SYSTEM_PERFORMANCE_INFORMATION) + 16;
+ PSYSTEM_PERFORMANCE_INFORMATION spi = (PSYSTEM_PERFORMANCE_INFORMATION)
+ alloca (sizeof_spi);
+ SYSTEM_TIMEOFDAY_INFORMATION stodi;
+ tmp_pathbuf tp;
+
+ char *buf = tp.c_get ();
+ char *eobuf = buf;
+
+ SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION spt[wincap.cpu_count ()];
+ status = NtQuerySystemInformation (SystemProcessorPerformanceInformation,
+ (PVOID) spt,
+ sizeof spt[0] * wincap.cpu_count (), NULL);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtQuerySystemInformation(SystemProcessorPerformanceInformation), "
+ "status %y", status);
+ else
+ {
+ unsigned long long user_time = 0ULL, kernel_time = 0ULL, idle_time = 0ULL;
+ for (unsigned long i = 0; i < wincap.cpu_count (); i++)
+ {
+ kernel_time += (spt[i].KernelTime.QuadPart - spt[i].IdleTime.QuadPart)
+ * CLOCKS_PER_SEC / NS100PERSEC;
+ user_time += spt[i].UserTime.QuadPart * CLOCKS_PER_SEC / NS100PERSEC;
+ idle_time += spt[i].IdleTime.QuadPart * CLOCKS_PER_SEC / NS100PERSEC;
+ }
+
+ eobuf += __small_sprintf (eobuf, "cpu %U %U %U %U\n",
+ user_time, 0ULL, kernel_time, idle_time);
+ user_time = 0ULL, kernel_time = 0ULL, idle_time = 0ULL;
+ for (unsigned long i = 0; i < wincap.cpu_count (); i++)
+ {
+ interrupt_count += spt[i].InterruptCount;
+ kernel_time = (spt[i].KernelTime.QuadPart - spt[i].IdleTime.QuadPart)
+ * CLOCKS_PER_SEC / NS100PERSEC;
+ user_time = spt[i].UserTime.QuadPart * CLOCKS_PER_SEC / NS100PERSEC;
+ idle_time = spt[i].IdleTime.QuadPart * CLOCKS_PER_SEC / NS100PERSEC;
+ eobuf += __small_sprintf (eobuf, "cpu%d %U %U %U %U\n", i,
+ user_time, 0ULL, kernel_time, idle_time);
+ }
+
+ status = NtQuerySystemInformation (SystemPerformanceInformation,
+ (PVOID) spi, sizeof_spi, NULL);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtQuerySystemInformation(SystemPerformanceInformation)"
+ ", status %y", status);
+ memset (spi, 0, sizeof_spi);
+ }
+ status = NtQuerySystemInformation (SystemTimeOfDayInformation,
+ (PVOID) &stodi, sizeof stodi, NULL);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtQuerySystemInformation(SystemTimeOfDayInformation), "
+ "status %y", status);
+ }
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return 0;
+ }
+
+ pages_in = spi->PagesRead;
+ pages_out = spi->PagefilePagesWritten + spi->MappedFilePagesWritten;
+ /* Note: there is no distinction made in this structure between pages read
+ from the page file and pages read from mapped files, but there is such
+ a distinction made when it comes to writing. Goodness knows why. The
+ value of swap_in, then, will obviously be wrong but its our best guess. */
+ swap_in = spi->PagesRead;
+ swap_out = spi->PagefilePagesWritten;
+ context_switches = spi->ContextSwitches;
+ boot_time = to_time_t (&stodi.BootTime);
+
+ eobuf += __small_sprintf (eobuf, "page %u %u\n"
+ "swap %u %u\n"
+ "intr %u\n"
+ "ctxt %u\n"
+ "btime %u\n",
+ pages_in, pages_out,
+ swap_in, swap_out,
+ interrupt_count,
+ context_switches,
+ boot_time);
+ destbuf = (char *) crealloc_abort (destbuf, eobuf - buf);
+ memcpy (destbuf, buf, eobuf - buf);
+ return eobuf - buf;
+}
+
+#define print(x) { bufptr = stpcpy (bufptr, (x)); }
+/* feature test unconditional print */
+#define ftuprint(msg) print (" " msg)
+/* feature test bit position (0-32) and conditional print */
+#define ftcprint(feat,bitno,msg) if ((feat) & (1 << (bitno))) { ftuprint (msg); }
+
+static inline uint32_t
+get_msb (uint32_t in)
+{
+ return 32 - __builtin_clz (in);
+}
+
+static inline uint32_t
+mask_bits (uint32_t in)
+{
+ uint32_t bits = get_msb (in) - 1;
+ if (in & (in - 1))
+ ++bits;
+ return bits;
+}
+
+static off_t
+format_proc_cpuinfo (void *, char *&destbuf)
+{
+ WCHAR cpu_key[128], *cpu_num_p;
+ DWORD orig_affinity_mask = 0;
+ GROUP_AFFINITY orig_group_affinity;
+ int cpu_number;
+ const int BUFSIZE = 256;
+ union
+ {
+ BYTE b[BUFSIZE];
+ char s[BUFSIZE];
+ WCHAR w[BUFSIZE / sizeof (WCHAR)];
+ DWORD d;
+ uint32_t m[13];
+ } in_buf;
+ tmp_pathbuf tp;
+
+ char *buf = tp.c_get ();
+ char *bufptr = buf;
+
+ //WORD num_cpu_groups = 1; /* Pre Windows 7, only one group... */
+ WORD num_cpu_per_group = __get_cpus_per_group ();
+
+ cpu_num_p = wcpcpy (cpu_key, L"\\Registry\\Machine\\HARDWARE\\DESCRIPTION"
+ "\\System\\CentralProcessor\\");
+ for (cpu_number = 0; ; cpu_number++)
+ {
+ __small_swprintf (cpu_num_p, L"%d", cpu_number);
+ if (!NT_SUCCESS (RtlCheckRegistryKey (RTL_REGISTRY_ABSOLUTE, cpu_key)))
+ break;
+ if (cpu_number)
+ print ("\n");
+
+ WORD cpu_group = cpu_number / num_cpu_per_group;
+ KAFFINITY cpu_mask = 1L << (cpu_number % num_cpu_per_group);
+ GROUP_AFFINITY affinity = {
+ .Mask = cpu_mask,
+ .Group = cpu_group,
+ };
+
+ if (!SetThreadGroupAffinity (GetCurrentThread (), &affinity,
+ &orig_group_affinity))
+ system_printf ("SetThreadGroupAffinity(%x,%d (%x/%d)) failed %E", cpu_mask, cpu_group, cpu_number, cpu_number);
+ orig_affinity_mask = 1; /* Just mark success. */
+ /* I'm not sure whether the thread changes processor immediately
+ and I'm not sure whether this function will cause the thread
+ to be rescheduled */
+ yield ();
+
+ DWORD cpu_mhz = 0;
+ union
+ {
+ LONG uc_len; /* -max size of buffer before call */
+ char uc_microcode[16]; /* at least 8 bytes */
+ } uc[4]; /* microcode values changed historically */
+
+ RTL_QUERY_REGISTRY_TABLE tab[6] =
+ {
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_NOSTRING,
+ L"~Mhz", &cpu_mhz, REG_NONE, NULL, 0 },
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_NOSTRING,
+ L"Update Revision", &uc[0], REG_NONE, NULL, 0 },
+ /* latest MSR */
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_NOSTRING,
+ L"Update Signature", &uc[1], REG_NONE, NULL, 0 },
+ /* previous MSR */
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_NOSTRING,
+ L"CurrentPatchLevel", &uc[2], REG_NONE, NULL, 0 },
+ /* earlier MSR */
+ { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_NOSTRING,
+ L"Platform Specific Field1", &uc[3], REG_NONE, NULL, 0 },
+ /* alternative */
+ { NULL, 0, NULL, NULL, 0, NULL, 0 }
+ };
+
+ for (size_t uci = 0; uci < sizeof (uc)/sizeof (*uc); ++uci)
+ {
+ memset (&uc[uci], 0, sizeof (uc[uci]));
+ uc[uci].uc_len = -(LONG)sizeof (uc[0].uc_microcode);
+ /* neg buffer size */
+ }
+
+ RtlQueryRegistryValues (RTL_REGISTRY_ABSOLUTE, cpu_key, tab,
+ NULL, NULL);
+ cpu_mhz = ((cpu_mhz - 1) / 10 + 1) * 10; /* round up to multiple of 10 */
+ DWORD bogomips = cpu_mhz * 2; /* bogomips is double cpu MHz since MMX */
+
+ unsigned long long microcode = 0; /* needs 8 bytes */
+ for (size_t uci = 0; uci < sizeof (uc)/sizeof (*uc) && !microcode; ++uci)
+ {
+ /* still neg buffer size => no data */
+ if (-(LONG)sizeof (uc[uci].uc_microcode) != uc[uci].uc_len)
+ {
+ memcpy (&microcode, uc[uci].uc_microcode, sizeof (microcode));
+
+ if (!(microcode & 0xFFFFFFFFLL)) /* some values in high bits */
+ microcode >>= 32; /* shift them down */
+ }
+ }
+
+ bufptr += __small_sprintf (bufptr, "processor\t: %d\n", cpu_number);
+ uint32_t maxf, vendor_id[4], unused;
+
+ cpuid (&maxf, &vendor_id[0], &vendor_id[2], &vendor_id[1], 0x00000000);
+ maxf &= 0xffff;
+ vendor_id[3] = 0;
+
+ /* Vendor identification. */
+ bool is_amd = false, is_intel = false;
+ if (!strcmp ((char*)vendor_id, "AuthenticAMD")
+ || !strcmp((char*)vendor_id, "HygonGenuine"))
+ is_amd = true;
+ else if (!strcmp ((char*)vendor_id, "GenuineIntel"))
+ is_intel = true;
+
+ bufptr += __small_sprintf (bufptr, "vendor_id\t: %s\n",
+ (char *)vendor_id);
+
+ uint32_t features1, features2, extra_info, cpuid_sig;
+ cpuid (&cpuid_sig, &extra_info, &features2, &features1, 0x00000001);
+ uint32_t family = (cpuid_sig & 0x00000f00) >> 8,
+ model = (cpuid_sig & 0x000000f0) >> 4,
+ stepping = cpuid_sig & 0x0000000f,
+ apic_id = (extra_info & 0xff000000) >> 24;
+ if (family == 15)
+ family += (cpuid_sig >> 20) & 0xff;
+ if (family >= 6)
+ model |= ((cpuid_sig >> 16) & 0x0f) << 4; /* ext model << 4 | model */
+
+ uint32_t maxe = 0;
+ cpuid (&maxe, &unused, &unused, &unused, 0x80000000);
+ if (maxe >= 0x80000004)
+ {
+ cpuid (&in_buf.m[0], &in_buf.m[1], &in_buf.m[2],
+ &in_buf.m[3], 0x80000002);
+ cpuid (&in_buf.m[4], &in_buf.m[5], &in_buf.m[6],
+ &in_buf.m[7], 0x80000003);
+ cpuid (&in_buf.m[8], &in_buf.m[9], &in_buf.m[10],
+ &in_buf.m[11], 0x80000004);
+ in_buf.m[12] = 0;
+ }
+ else
+ {
+ /* Could implement a lookup table here if someone needs it. */
+ strcpy (in_buf.s, "unknown");
+ }
+ int cache_size = -1,
+ clflush = 64,
+ cache_alignment = 64;
+ long (*get_cpu_cache) (int, uint32_t) = NULL;
+ uint32_t max;
+ if (features1 & (1 << 19)) /* CLFSH */
+ clflush = ((extra_info >> 8) & 0xff) << 3;
+ if (is_intel && family == 15)
+ cache_alignment = clflush * 2;
+ if (is_intel)
+ {
+ extern long get_cpu_cache_intel (int sysc, uint32_t maxf);
+ get_cpu_cache = get_cpu_cache_intel;
+ max = maxf; /* Intel uses normal cpuid levels */
+ }
+ else if (is_amd)
+ {
+ extern long get_cpu_cache_amd (int sysc, uint32_t maxe);
+ get_cpu_cache = get_cpu_cache_amd;
+ max = maxe; /* AMD uses extended cpuid levels */
+ }
+ if (get_cpu_cache)
+ {
+ long cs;
+
+ cs = get_cpu_cache (_SC_LEVEL3_CACHE_SIZE, max);
+ if (cs <= 0)
+ cs = get_cpu_cache (_SC_LEVEL2_CACHE_SIZE, max);
+ if (cs <= 0)
+ {
+ cs = get_cpu_cache (_SC_LEVEL1_ICACHE_SIZE, max);
+ if (cs > 0)
+ cache_size = cs;
+ cs = get_cpu_cache (_SC_LEVEL1_DCACHE_SIZE, max);
+ if (cs > 0)
+ cache_size += cs;
+ }
+ else
+ cache_size = cs;
+ if (cache_size > 0)
+ cache_size >>= 10;
+ }
+ bufptr += __small_sprintf (bufptr, "cpu family\t: %d\n"
+ "model\t\t: %d\n"
+ "model name\t: %s\n"
+ "stepping\t: %d\n"
+ "microcode\t: 0x%X\n"
+ "cpu MHz\t\t: %d.000\n",
+ family,
+ model,
+ in_buf.s + strspn (in_buf.s, " \t"),
+ stepping,
+ microcode,
+ cpu_mhz);
+
+ if (cache_size >= 0)
+ bufptr += __small_sprintf (bufptr, "cache size\t: %d KB\n",
+ cache_size);
+
+ /* Recognize multi-core CPUs. */
+ if (features1 & (1 << 28)) /* HTT */
+ {
+ uint32_t siblings = 0;
+ uint32_t cpu_cores = 0;
+ uint32_t phys_id = 0;
+ uint32_t core_id = 0;
+ uint32_t initial_apic_id = apic_id;
+
+ uint32_t logical_bits = 0; /* # of logical core bits in apicid. */
+ uint32_t ht_bits = 0; /* # of thread bits in apic_id. */
+
+ if (is_intel)
+ {
+ bool valid = false;
+ if (maxf >= 0x0000000b) /* topoext supported? */
+ {
+ uint32_t bits, logical, level, unused;
+
+ /* Threads */
+ cpuid (&bits, &logical, &level, &unused,
+ 0x0000000b, 0x00);
+ /* Even if topoext is supposedly supported, it can return
+ "invalid". */
+ if (bits != 0 && ((level >> 8) & 0xff) == 1)
+ {
+ valid = true;
+ ht_bits = (bits & 0x1f);
+ siblings = (logical & 0xffff);
+ cpu_cores = siblings;
+ for (uint32_t idx = 1; ; ++idx)
+ {
+ cpuid (&bits, &logical, &level, &initial_apic_id,
+ 0x0000000b, idx);
+
+ uint32_t level_type = ((level >> 8) & 0xff);
+ if (level_type == 0) /* Invalid */
+ break;
+ if (level_type == 2) /* Core */
+ {
+ logical_bits = (bits & 0x1f);
+ siblings = (logical & 0xffff);
+ cpu_cores = siblings >> ht_bits;
+ break;
+ }
+ }
+ }
+ }
+ if (!valid && maxf >= 0x00000004)
+ {
+ uint32_t apic_reserved;
+
+ cpuid (&apic_reserved, &unused, &unused, &unused,
+ 0x00000004, 0x00);
+ if (apic_reserved & 0x1f)
+ {
+ valid = true;
+ cpu_cores = ((apic_reserved >> 26) & 0x3f) + 1;
+ siblings = (extra_info >> 16) & 0xff;
+ if (siblings <= 1) /* HT could be fused out */
+ {
+ logical_bits = mask_bits (cpu_cores);
+ ht_bits = 0;
+ }
+ else
+ {
+ logical_bits = mask_bits (siblings);
+ ht_bits = mask_bits (siblings / cpu_cores);
+ }
+ }
+ }
+ if (!valid) /* single core, multi thread */
+ {
+ cpu_cores = 1;
+ siblings = (extra_info >> 16) & 0xff;
+ logical_bits = mask_bits (siblings);
+ ht_bits = logical_bits;
+ }
+ }
+ else if (is_amd)
+ {
+ if (maxe >= 0x8000001e)
+ {
+ uint32_t cus, core_info;
+
+ cpuid (&unused, &unused, &core_info, &unused, 0x80000008);
+ cpuid (&unused, &cus, &unused, &unused, 0x8000001e);
+ siblings = cpu_cores = (core_info & 0xff) + 1;
+ logical_bits = (core_info >> 12) & 0xf;
+ cus = ((cus >> 8) & 0x3) + 1;
+ ht_bits = mask_bits (cus);
+ }
+ else if (maxe >= 0x80000008)
+ {
+ uint32_t core_info;
+
+ cpuid (&unused, &unused, &core_info, &unused, 0x80000008);
+ siblings = (core_info & 0xff) + 1;
+ cpu_cores = siblings;
+ logical_bits = (core_info >> 12) & 0xf;
+ if (!logical_bits)
+ logical_bits = mask_bits (siblings);
+ ht_bits = 0;
+ }
+ else
+ {
+ siblings = (extra_info >> 16) & 0xff;
+ cpu_cores = siblings;
+ logical_bits = mask_bits (siblings);
+ ht_bits = 0;
+ }
+ }
+ phys_id = initial_apic_id >> logical_bits;
+ core_id = (initial_apic_id & ((1 << logical_bits) - 1)) >> ht_bits;
+
+ bufptr += __small_sprintf (bufptr, "physical id\t: %d\n", phys_id);
+ if (siblings > 0)
+ bufptr += __small_sprintf (bufptr, "siblings\t: %u\n", siblings);
+ bufptr += __small_sprintf (bufptr, "core id\t\t: %d\n"
+ "cpu cores\t: %d\n",
+ core_id, cpu_cores);
+ if (features1 & (1 << 9)) /* apic */
+ bufptr += __small_sprintf (bufptr, "apicid\t\t: %d\n"
+ "initial apicid\t: %d\n",
+ apic_id, initial_apic_id);
+
+ }
+
+ /* level is number of non-zero leafs exc. sub-leafs */
+ int level = maxf + 1 + (maxe & 0x7fffffff) + 1;
+
+ for (uint32_t l = maxe; 0x80000001 < l; --l)
+ {
+ uint32_t a, b, c, d;
+ cpuid (&a, &b, &c, &d, l);
+ if (!(a | b | c | d)) --level;
+ }
+
+ for (uint32_t l = maxf; 1 < l; --l)
+ {
+ uint32_t a, b, c, d;
+ cpuid (&a, &b, &c, &d, l);
+ if (!(a | b | c | d)) --level;
+ }
+
+ bufptr += __small_sprintf (bufptr, "fpu\t\t: %s\n"
+ "fpu_exception\t: %s\n"
+ "cpuid level\t: %d\n"
+ "wp\t\t: yes\n",
+ (features1 & (1 << 0)) ? "yes" : "no",
+ (features1 & (1 << 0)) ? "yes" : "no",
+ level);
+ print ("flags\t\t:");
+ /* cpuid 0x00000001 edx */
+ ftcprint (features1, 0, "fpu"); /* x87 floating point */
+ ftcprint (features1, 1, "vme"); /* VM enhancements */
+ ftcprint (features1, 2, "de"); /* debugging extensions */
+ ftcprint (features1, 3, "pse"); /* page size extensions */
+ ftcprint (features1, 4, "tsc"); /* rdtsc/p */
+ ftcprint (features1, 5, "msr"); /* rd/wrmsr */
+ ftcprint (features1, 6, "pae"); /* phy addr extensions */
+ ftcprint (features1, 7, "mce"); /* Machine check exception */
+ ftcprint (features1, 8, "cx8"); /* cmpxchg8b */
+ ftcprint (features1, 9, "apic"); /* APIC enabled */
+ ftcprint (features1, 11, "sep"); /* sysenter/sysexit */
+ ftcprint (features1, 12, "mtrr"); /* memory type range registers */
+ ftcprint (features1, 13, "pge"); /* page global extension */
+ ftcprint (features1, 14, "mca"); /* machine check architecture */
+ ftcprint (features1, 15, "cmov"); /* conditional move */
+ ftcprint (features1, 16, "pat"); /* page attribute table */
+ ftcprint (features1, 17, "pse36");/* 36 bit page size extensions */
+ ftcprint (features1, 18, "pn"); /* processor serial number */
+ ftcprint (features1, 19, "clflush"); /* clflush instruction */
+ ftcprint (features1, 21, "dts"); /* debug store */
+ ftcprint (features1, 22, "acpi"); /* ACPI via MSR */
+ ftcprint (features1, 23, "mmx"); /* multimedia extensions */
+ ftcprint (features1, 24, "fxsr"); /* fxsave/fxrstor */
+ ftcprint (features1, 25, "sse"); /* xmm sse */
+ ftcprint (features1, 26, "sse2"); /* xmm2 sse2 */
+ ftcprint (features1, 27, "ss"); /* CPU self snoop */
+ ftcprint (features1, 28, "ht"); /* hyper threading */
+ ftcprint (features1, 29, "tm"); /* acc automatic clock control */
+ ftcprint (features1, 30, "ia64"); /* IA 64 processor */
+ ftcprint (features1, 31, "pbe"); /* pending break enable */
+
+ /* AMD cpuid 0x80000001 edx */
+ if (is_amd && maxe >= 0x80000001)
+ {
+ cpuid (&unused, &unused, &unused, &features1, 0x80000001);
+
+ ftcprint (features1, 11, "syscall"); /* syscall/sysret */
+ ftcprint (features1, 19, "mp"); /* MP capable */
+ ftcprint (features1, 20, "nx"); /* no-execute protection */
+ ftcprint (features1, 22, "mmxext"); /* MMX extensions */
+ ftcprint (features1, 25, "fxsr_opt"); /* fxsave/fxrstor optims */
+ ftcprint (features1, 26, "pdpe1gb"); /* GB large pages */
+ ftcprint (features1, 27, "rdtscp"); /* rdtscp */
+ ftcprint (features1, 29, "lm"); /* long mode (x86-64 amd64) */
+ ftcprint (features1, 30, "3dnowext"); /* 3DNow extensions */
+ ftcprint (features1, 31, "3dnow"); /* 3DNow */
+ }
+
+ /* cpuid 0x80000007 edx */
+ if (maxe >= 0x80000007)
+ {
+ cpuid (&unused, &unused, &unused, &features1, 0x80000007);
+
+ ftcprint (features1, 8, "constant_tsc"); /* TSC ticks at a constant rate */
+ }
+
+/* ftcprint (features2, 9, "up"); *//* SMP kernel running on UP N/A */
+
+ /* cpuid 0x00000007 ebx */
+ if (maxf >= 0x00000007)
+ cpuid (&unused, &features1, &unused, &unused, 0x00000007, 0);
+ else
+ features1 = 0;
+ /* cpuid 0x80000007 edx */
+ if (maxe >= 0x80000007)
+ cpuid (&unused, &unused, &unused, &features2, 0x80000007);
+ else
+ features2 = 0;
+ uint32_t cr_den, cr_num, Hz;
+ /* cpuid 0x00000015 eax ebx ecx clock ratios optional Hz */
+ if (is_intel && maxf >= 0x00000015)
+ cpuid (&cr_den, &cr_num, &Hz, &unused, 0x00000015);
+ else
+ cr_den = 0;
+ /* TSC requires adjustment, nonstop, and clock ratio divider min */
+ if ((features1 & (1 << 1)) && /* TSC adjustment MSR 0x3B */
+ (features2 & (1 << 8)) && /* nonstop C states */
+ (cr_den > 1)) /* clock ratio denominator > min */
+ ftuprint ("art"); /* Always running timer (ART) */
+
+ /* Intel cpuid 0x0000000a eax Arch Perf Mon */
+ if (is_intel && maxf >= 0x0000000a)
+ {
+ cpuid (&features2, &unused, &unused, &unused, 0x0000000a);
+
+ /* rev > 0 and # counters/cpu > 1 */
+ if ((features2 & 0xff) > 0 && (((features2 >> 8) & 0xff) > 1))
+ ftuprint ("arch_perfmon"); /* Intel Arch Perf Mon */
+ }
+
+/* ftcprint (features2, 12, "pebs");*//* MSR_IA32_MISC_ENABLE 12 Precise-Event Based Sampling */
+/* ftcprint (features2, 13, "bts"); *//* MSR_IA32_MISC_ENABLE 11 Branch Trace Store */
+
+ /* AMD cpuid 0x00000001 eax */
+ if (is_amd && maxf >= 0x00000001)
+ cpuid(&features2, &unused, &unused, &unused, 0x00000001);
+ else
+ features2 = 0;
+
+/* Intel family 6 or AMD family >= x10 or ... */
+/* ... or AMD cpuid 0x00000001 eax in [0x0f58,) or [0x0f48,0x0f50) */
+ if ((is_intel && family == 6) ||
+ (is_amd && (family >= 0x10 ||
+ (features2 >= 0x0f58 ||
+ (features2 >= 0x0f48 && features2 < 0x0f50)))))
+ ftuprint ("rep_good"); /* REP microcode works well */
+
+ /* cpuid 0x80000007 edx Advanced power management */
+ if (maxe >= 0x80000007)
+ {
+ cpuid (&unused, &unused, &unused, &features2, 0x80000007);
+
+ ftcprint (features2, 12, "acc_power"); /* accum power */
+ }
+
+ ftuprint ("nopl"); /* NOPL (0F 1F) instructions */
+
+/* cpuid 0x0000000b ecx[8:15] type */
+#define BAD_TYPE 0
+#define SMT_TYPE 1
+#define CORE_TYPE 2
+ /* cpuid 0x0000000b ebx ecx */
+ if (maxf >= 0x0000000b)
+ {
+ cpuid(&unused, &features1, &features2, &unused, 0x0000000b);
+
+ /* check if SMT implemented */
+ if (features1 != 0 && (((features2 >> 8) & 0xff) == SMT_TYPE))
+ ftuprint ("xtopology"); /* CPU topology enum extensions */
+ }
+
+ /* AMD cpuid 0x80000007 edx */
+ if (is_amd && maxe >= 0x80000007)
+ {
+ cpuid (&unused, &unused, &unused, &features1, 0x80000007);
+
+ ftcprint (features1, 8, "tsc_reliable"); /* TSC constant rate */
+ ftcprint (features1, 8, "nonstop_tsc"); /* nonstop C states */
+ }
+
+ if (is_amd || is_intel)
+ ftuprint ("cpuid"); /* CPU has CPUID instruction */
+
+ if (is_amd && family > 0x16)
+ ftuprint ("extd_apicid"); /* Extended APICID (8 bits) */
+
+ /* AMD cpuid 0x8000001e ecx */
+ if (is_amd && maxe >= 0x8000001e)
+ {
+ cpuid (&unused, &unused, &features1, &unused, 0x8000001e);
+
+ if (((features1 >> 8) & 7) + 1 > 1) /* nodes/socket */
+ ftuprint ("amd_dcm"); /* AMD multi-node processor */
+ }
+
+ /* cpuid 0x00000006 ecx */
+ if (maxf >= 0x00000006)
+ {
+ cpuid (&unused, &unused, &features1, &unused, 0x00000006);
+
+ ftcprint (features1, 0, "aperfmperf"); /* P state hw coord fb */
+ }
+
+ /* cpuid 0x80000007 edx Advanced power management */
+ if (maxe >= 0x80000007)
+ {
+ cpuid (&unused, &unused, &unused, &features2, 0x80000007);
+
+ ftcprint (features2, 14, "rapl"); /* runtime avg power limit */
+ }
+
+ /* Penwell, Cloverview, ... TSC doesn't sleep on S3 */
+ if (is_intel && family == 6)
+ switch (model)
+ {
+ case 0x27: /* Atom Saltwell Mid Penwell */
+ case 0x35: /* Atom Saltwell Tablet Cloverview */
+ case 0x4a: /* Atom Silvermont Mid Merriefield */
+ case 0x75: /* Atom Airmont NP Lightning Mountain */
+ ftuprint ("nonstop_tsc_s3"); /* TSC doesn't stop in S3 state */
+ break;
+ }
+
+ /* cpuid 0x00000015 eax ebx ecx clock ratios optional Hz */
+ if (is_intel && maxf >= 0x00000015)
+ {
+ uint32_t cr_den, cr_num, Hz, kHz;
+ cpuid (&cr_den, &cr_num, &Hz, &unused, 0x00000015);
+ if (cr_num != 0 && cr_den != 0)
+ {
+ kHz = Hz / 1000;
+ /* Denverton don't report hz nor support cpuid 0x16 so set 25MHz */
+ if (kHz == 0 && model == 0x5F) /* Atom Goldmont D Denverton */
+ kHz = 25000;
+
+ /* cpuid TSC frequency is known */
+ if (kHz != 0)
+ ftuprint ("tsc_known_freq"); /* TSC has known frequency */
+#if 0 /* keep for future and doc */
+ else /* kHz == 0 */
+ /* Skylake and Kabylake don't report clock so use CPU speed and ratio */
+ if (maxf >= 0x00000016)
+ {
+ uint32_t mHz;
+ cpuid(&mHz, &unused, &unused, &unused, 0x00000016);
+ kHz = mHz * 1000 * cr_num / cr_den;
+ }
+#endif
+ }
+ }
+
+ /* cpuid 0x00000001 ecx */
+ cpuid (&unused, &unused, &features2, &unused, 0x00000001);
+
+ ftcprint (features2, 0, "pni"); /* xmm3 sse3 */
+ ftcprint (features2, 1, "pclmuldq"); /* pclmulqdq instruction */
+ ftcprint (features2, 2, "dtes64"); /* 64-bit debug store */
+ ftcprint (features2, 3, "monitor"); /* monitor/mwait support */
+ ftcprint (features2, 4, "ds_cpl"); /* CPL-qual debug store */
+ ftcprint (features2, 5, "vmx"); /* hardware virtualization */
+ ftcprint (features2, 6, "smx"); /* safer mode extensions */
+ ftcprint (features2, 7, "est"); /* enhanced speedstep */
+ ftcprint (features2, 8, "tm2"); /* thermal monitor 2 */
+ ftcprint (features2, 9, "ssse3"); /* supplemental sse3 */
+ ftcprint (features2, 10, "cid"); /* context id */
+ ftcprint (features2, 11, "sdbg"); /* silicon debug */
+ ftcprint (features2, 12, "fma"); /* fused multiply add */
+ ftcprint (features2, 13, "cx16"); /* cmpxchg16b instruction */
+ ftcprint (features2, 14, "xtpr"); /* send task priority messages */
+ ftcprint (features2, 15, "pdcm"); /* perf/debug capabilities MSR */
+ ftcprint (features2, 17, "pcid"); /* process context identifiers */
+ ftcprint (features2, 18, "dca"); /* direct cache access */
+ ftcprint (features2, 19, "sse4_1"); /* xmm 4_1 sse 4.1 */
+ ftcprint (features2, 20, "sse4_2"); /* xmm 4_2 sse 4.2 */
+ ftcprint (features2, 21, "x2apic"); /* x2 APIC */
+ ftcprint (features2, 22, "movbe"); /* movbe instruction */
+ ftcprint (features2, 23, "popcnt"); /* popcnt instruction */
+ ftcprint (features2, 24, "tsc_deadline_timer"); /* TSC deadline timer */
+ ftcprint (features2, 25, "aes"); /* AES instructions */
+ ftcprint (features2, 26, "xsave"); /* xsave/xrstor/xsetbv/xgetbv */
+/* ftcprint (features2, 27, "osxsave");*//* "" XSAVE supported in OS */
+ ftcprint (features2, 28, "avx"); /* advanced vector extensions */
+ ftcprint (features2, 29, "f16c"); /* 16 bit FP conversions */
+ ftcprint (features2, 30, "rdrand"); /* RNG rdrand instruction */
+ ftcprint (features2, 31, "hypervisor"); /* hypervisor guest */
+
+ /* cpuid 0x80000001 ecx */
+ if (maxe >= 0x80000001)
+ {
+ cpuid (&unused, &unused, &features1, &unused, 0x80000001);
+
+ ftcprint (features1, 0, "lahf_lm"); /* l/sahf long mode */
+ ftcprint (features1, 1, "cmp_legacy"); /* HT not valid */
+ if (is_amd)
+ {
+ ftcprint (features1, 2, "svm"); /* secure VM */
+ ftcprint (features1, 3, "extapic"); /* ext APIC space */
+ ftcprint (features1, 4, "cr8_legacy"); /* CR8 32 bit mode */
+ ftcprint (features1, 5, "abm"); /* adv bit manip lzcnt */
+ ftcprint (features1, 6, "sse4a"); /* sse 4a */
+ ftcprint (features1, 7, "misalignsse"); /* misaligned SSE ok */
+ ftcprint (features1, 8, "3dnowprefetch"); /* 3DNow prefetch */
+ ftcprint (features1, 9, "osvw"); /* OS vis workaround */
+ }
+ ftcprint (features1, 10, "ibs"); /* instr based sampling */
+ if (is_amd)
+ {
+ ftcprint (features1, 11, "xop"); /* sse 5 extended AVX */
+ ftcprint (features1, 12, "skinit"); /* skinit/stgi */
+ ftcprint (features1, 13, "wdt"); /* watchdog timer */
+ ftcprint (features1, 15, "lwp"); /* light weight prof */
+ ftcprint (features1, 16, "fma4"); /* 4 operand MAC */
+ ftcprint (features1, 17, "tce"); /* translat cache ext */
+ ftcprint (features1, 19, "nodeid_msr"); /* nodeid MSR */
+ ftcprint (features1, 21, "tbm"); /* trailing bit manip */
+ ftcprint (features1, 22, "topoext"); /* topology ext */
+ ftcprint (features1, 23, "perfctr_core"); /* core perf ctr ext */
+ ftcprint (features1, 24, "perfctr_nb"); /* NB perf ctr ext */
+ ftcprint (features1, 26, "bpext"); /* data brkpt ext */
+ ftcprint (features1, 27, "ptsc"); /* perf timestamp ctr */
+ ftcprint (features1, 28, "perfctr_llc"); /* ll cache perf ctr */
+ ftcprint (features1, 29, "mwaitx"); /* monitor/mwaitx ext */
+ }
+ }
+
+ /* ring 3 monitor/mwait */
+ if (is_intel && family == 6)
+ switch (model)
+ {
+ case 0x57: /* Xeon Phi Knights Landing */
+ case 0x85: /* Xeon Phi Knights Mill */
+ ftuprint ("ring3mwait"); /* Ring 3 MONITOR/MWAIT instructions */
+ break;
+ }
+
+/* ftcprint (features1, 1, "cpuid_fault");*//* MSR_PLATFORM_INFO Intel CPUID faulting */
+
+/* features scattered in various CPUID levels. */
+ /* cpuid 0x80000007 edx */
+ if (maxe >= 0x80000007)
+ {
+ cpuid (&unused, &unused, &unused, &features1, 0x80000007);
+
+ ftcprint (features1, 9, "cpb"); /* core performance boost */
+ }
+ /* cpuid 0x00000006 ecx */
+ if (maxf >= 0x00000006)
+ {
+ cpuid (&unused, &unused, &features1, &unused, 0x00000006);
+
+ ftcprint (features1, 3, "epb"); /* energy perf bias */
+ }
+ /* cpuid 0x00000007:1 ebx */
+ if (maxf >= 0x00000007)
+ {
+ cpuid (&unused, &features1, &unused, &unused, 0x00000007, 1);
+
+ ftcprint (features1, 0, "intel_ppin"); /* Prot Proc Id No */
+ }
+ /* cpuid 0x00000010 ebx */
+ if (maxf >= 0x00000010)
+ {
+ cpuid (&unused, &features1, &unused, &unused, 0x00000010);
+
+ ftcprint (features1, 1, "cat_l3"); /* cache alloc tech l3 */
+ ftcprint (features1, 2, "cat_l2"); /* cache alloc tech l2 */
+
+ /* cpuid 0x00000010:1 ecx */
+ cpuid (&unused, &unused, &features1, &unused, 0x00000010, 1);
+
+ ftcprint (features1, 2, "cdp_l3"); /* code data prior l3 */
+ }
+
+/* ftcprint (features1, 7, "invpcid_single");*//* INVPCID && CR4.PCIDE=1 */
+
+ /* cpuid 0x80000007 edx */
+ if (maxe >= 0x80000007)
+ {
+ cpuid (&unused, &unused, &unused, &features1, 0x80000007);
+
+ ftcprint (features1, 7, "hw_pstate"); /* hw P state */
+ ftcprint (features1, 11, "proc_feedback"); /* proc feedback interf */
+ }
+
+/* ftcprint (features1, 11, "pti");*//* Page Table Isolation reqd with Meltdown */
+
+ /* cpuid 0x00000010:2 ecx */
+ if (maxf >= 0x00000010)
+ {
+ cpuid (&unused, &unused, &features2, &unused, 0x00000010, 2);
+
+ ftcprint (features2, 2, "cdp_l2"); /* code data prior l2 */
+ }
+ /* cpuid 0x80000008 ebx */
+ if (maxe >= 0x80000008)
+ {
+ cpuid (&unused, &features1, &unused, &unused, 0x80000008, 0);
+
+ ftcprint (features1, 24, "ssbd"); /* spec store byp dis */
+ }
+ /* cpuid 0x00000010 ebx */
+ if (maxf >= 0x00000010)
+ {
+ cpuid (&unused, &features2, &unused, &unused, 0x00000010);
+
+ ftcprint (features2, 3, "mba"); /* memory bandwidth alloc */
+ }
+ /* cpuid 0x80000008 ebx */
+ if (maxe >= 0x80000008)
+ {
+/* cpuid (&unused, &features1, &unused, &unused, 0x80000008, 0); */
+/* from above ^ */
+ ftcprint (features1, 6, "mba"); /* memory bandwidth alloc */
+ }
+ /* cpuid 0x80000022 ebx AMD ExtPerfMonAndDbg */
+ if (maxe >= 0x80000022)
+ {
+ cpuid (&features2, &unused, &unused, &unused, 0x80000022);
+
+ ftcprint (features2, 0, "perfmon_v2"); /* Performance Monitoring Version 2 */
+ }
+ /* cpuid 0x80000008 ebx */
+ if (maxe >= 0x80000008)
+ {
+/* cpuid (&unused, &features1, &unused, &unused, 0x80000008, 0); */
+/* from above ^ */
+/* ftcprint (features1, 0, "clzero"); *//* clzero instruction */
+/* ftcprint (features1, 1, "irperf"); *//* instr retired count */
+/* ftcprint (features1, 2, "xsaveerptr");*//* save/rest FP err ptrs */
+/* ftcprint (features1, 4, "rdpru"); *//* user level rd proc reg */
+/* ftcprint (features1, 6, "mba"); *//* memory BW alloc */
+/* ftcprint (features1, 9, "wbnoinvd"); *//* wbnoinvd instruction */
+ ftcprint (features1, 14, "ibrs"); /* ind br restricted spec */
+ ftcprint (features1, 12, "ibpb"); /* ind br pred barrier */
+ ftcprint (features1, 15, "stibp"); /* 1 thread ind br pred */
+ ftcprint (features1, 16, "ibrs_enhanced"); /* IBRS_ALL enhanced IBRS always on */
+/* ftcprint (features1, 17, "stibp_always_on"); */ /* stibp always on */
+/* ftcprint (features1, 18, "ibrs_pref");*//* IBRS_PREF IBRS preferred */
+/* ftcprint (features1, 23, "amd_ppin"); *//* protected proc id no */
+/* ftcprint (features1, 24, "ssbd"); *//* spec store byp dis */
+/* ftcprint (features1, 25, "virt_ssbd");*//* vir spec store byp dis */
+/* ftcprint (features1, 26, "ssb_no"); *//* ssb fixed in hardware */
+ }
+
+ /* cpuid 0x00000021 ebx|edx|ecx == "IntelTDX " */
+ if (is_intel && maxf >= 0x00000021)
+ {
+ uint32_t tdx[3];
+
+ cpuid (&unused, &tdx[0], &tdx[2], &tdx[1], 0x00000021, 0);
+ if (!memcmp ("IntelTDX ", tdx, sizeof (tdx)))
+ ftuprint ("tdx_guest"); /* Intel Trust Domain Extensions Guest Support */
+ }
+
+ /* cpuid 0x00000007 ebx */
+ if (maxf >= 0x00000007)
+ {
+ cpuid (&unused, &features1, &unused, &unused, 0x00000007, 0);
+
+ ftcprint (features1, 0, "fsgsbase"); /* rd/wr fs/gs base */
+ ftcprint (features1, 1, "tsc_adjust"); /* TSC adjustment MSR 0x3B */
+ ftcprint (features1, 2, "sgx"); /* software guard extensions */
+ ftcprint (features1, 3, "bmi1"); /* bit manip ext group 1 */
+ ftcprint (features1, 4, "hle"); /* hardware lock elision */
+ ftcprint (features1, 5, "avx2"); /* AVX ext instructions */
+/* ftcprint (features1, 6, "fpdx"); */ /* "" FP data ptr upd on exc */
+ ftcprint (features1, 7, "smep"); /* super mode exec prot */
+ ftcprint (features1, 8, "bmi2"); /* bit manip ext group 2 */
+ ftcprint (features1, 9, "erms"); /* enh rep movsb/stosb */
+ ftcprint (features1, 10, "invpcid"); /* inv proc context id */
+ ftcprint (features1, 11, "rtm"); /* restricted txnal mem */
+ ftcprint (features1, 12, "cqm"); /* cache QoS monitoring */
+/* ftcprint (features1, 13, "fpcsdsz"); */ /* "" zero FP cs/ds */
+ ftcprint (features1, 14, "mpx"); /* mem prot ext */
+ ftcprint (features1, 15, "rdt_a"); /* rsrc dir tech alloc */
+ ftcprint (features1, 16, "avx512f"); /* vec foundation */
+ ftcprint (features1, 17, "avx512dq"); /* vec dq granular */
+ ftcprint (features1, 18, "rdseed"); /* RNG rdseed instruction */
+ ftcprint (features1, 19, "adx"); /* adcx/adox */
+ ftcprint (features1, 20, "smap"); /* sec mode access prev */
+ ftcprint (features1, 21, "avx512ifma"); /* vec int FMA */
+ ftcprint (features1, 23, "clflushopt"); /* cache line flush opt */
+ ftcprint (features1, 24, "clwb"); /* cache line write back */
+ ftcprint (features1, 25, "intel_pt"); /* intel processor trace */
+ ftcprint (features1, 26, "avx512pf"); /* vec prefetch */
+ ftcprint (features1, 27, "avx512er"); /* vec exp/recip aprx */
+ ftcprint (features1, 28, "avx512cd"); /* vec conflict detect */
+ ftcprint (features1, 29, "sha_ni"); /* SHA extensions */
+ ftcprint (features1, 30, "avx512bw"); /* vec byte/word gran */
+ ftcprint (features1, 31, "avx512vl"); /* vec vec len ext */
+ }
+
+ /* more random feature flags */
+ /* cpuid 0x0000000d:1 eax */
+ if (maxf >= 0x0000000d)
+ {
+ cpuid (&features1, &unused, &unused, &unused, 0x0000000d, 1);
+
+ ftcprint (features1, 0, "xsaveopt"); /* xsaveopt instruction */
+ ftcprint (features1, 1, "xsavec"); /* xsavec instruction */
+ ftcprint (features1, 2, "xgetbv1"); /* xgetbv ecx 1 */
+ ftcprint (features1, 3, "xsaves"); /* xsaves/xrstors */
+ }
+ /* cpuid 0x0000000f edx */
+ if (maxf >= 0x0000000f)
+ {
+ cpuid (&unused, &unused, &unused, &features1, 0x0000000f);
+
+ ftcprint (features1, 1, "cqm_llc"); /* llc QoS */
+
+ /* cpuid 0x0000000f:1 edx */
+ cpuid (&unused, &unused, &unused, &features1, 0x0000000f, 1);
+
+ ftcprint (features1, 0, "cqm_occup_llc"); /* llc occup monitor */
+ ftcprint (features1, 1, "cqm_mbm_total"); /* llc total MBM mon */
+ ftcprint (features1, 2, "cqm_mbm_local"); /* llc local MBM mon */
+ }
+
+/* ftcprint (features1, 6, "split_lock_detect");*//* MSR_TEST_CTRL split lock */
+
+ /* cpuid 0x00000007:1 eax */
+ if (maxf >= 0x00000007)
+ {
+ cpuid (&features1, &unused, &unused, &unused, 0x00000007, 1);
+
+ ftcprint (features1, 4, "avx_vnni"); /* vex enc NN vec */
+ ftcprint (features1, 5, "avx512_bf16"); /* vec bfloat16 short */
+ }
+
+ /* AMD cpuid 0x80000008 ebx */
+ if (is_amd && maxe >= 0x80000008)
+ {
+ cpuid (&unused, &features1, &unused, &unused, 0x80000008, 0);
+
+ ftcprint (features1, 0, "clzero"); /* clzero instruction */
+ ftcprint (features1, 1, "irperf"); /* instr retired count */
+ ftcprint (features1, 2, "xsaveerptr"); /* save/rest FP err ptrs */
+ ftcprint (features1, 4, "rdpru"); /* user level rd proc reg */
+/* ftcprint (features1, 6, "mba"); */ /* memory BW alloc */
+ ftcprint (features1, 9, "wbnoinvd"); /* wbnoinvd instruction */
+/* ftcprint (features1, 12, "ibpb" ); */ /* ind br pred barrier */
+/* ftcprint (features1, 14, "ibrs" ); */ /* ind br restricted spec */
+/* ftcprint (features1, 15, "stibp"); */ /* 1 thread ind br pred */
+/* ftcprint (features1, 16, "ibrs_enhanced");*//* IBRS_ALL enhanced IBRS always on */
+/* ftcprint (features1, 17, "stibp_always_on"); */ /* stibp always on */
+/* ftcprint (features1, 18, "ibrs_pref");*//* IBRS_PREF IBRS preferred */
+ ftcprint (features1, 23, "amd_ppin"); /* protected proc id no */
+/* ftcprint (features1, 24, "ssbd"); */ /* spec store byp dis */
+ ftcprint (features1, 25, "virt_ssbd"); /* vir spec store byp dis */
+/* ftcprint (features1, 26, "ssb_no"); */ /* ssb fixed in hardware */
+ ftcprint (features1, 27, "cppc"); /* collab proc perf ctl */
+ ftcprint (features1, 31, "brs"); /* branch sampling */
+ }
+
+ /* thermal & power cpuid 0x00000006 eax */
+ if (maxf >= 0x00000006)
+ {
+ cpuid (&features1, &unused, &features2, &unused, 0x00000006);
+
+ ftcprint (features1, 0, "dtherm"); /* digital thermal sensor */
+ ftcprint (features1, 1, "ida"); /* Intel dynamic acceleration */
+ ftcprint (features1, 2, "arat"); /* always running APIC timer */
+ ftcprint (features1, 4, "pln"); /* power limit notification */
+ ftcprint (features1, 6, "pts"); /* package thermal status */
+ ftcprint (features1, 7, "hwp"); /* hardware P states */
+ ftcprint (features1, 8, "hwp_notify"); /* HWP notification */
+ ftcprint (features1, 9, "hwp_act_window"); /* HWP activity window */
+ ftcprint (features1, 10, "hwp_epp"); /* HWP energy perf pref */
+ ftcprint (features1, 11, "hwp_pkg_req"); /* HWP package level req */
+ ftcprint (features1, 19, "hfi"); /* Hardware Feedback Interface */
+ }
+
+ /* AMD SVM cpuid 0x8000000a edx */
+ if (is_amd && maxe >= 0x8000000a)
+ {
+ cpuid (&unused, &unused, &unused, &features1, 0x8000000a, 0);
+
+ ftcprint (features1, 0, "npt"); /* nested paging */
+ ftcprint (features1, 1, "lbrv"); /* lbr virtualization */
+ ftcprint (features1, 2, "svm_lock"); /* SVM locking MSR */
+ ftcprint (features1, 3, "nrip_save"); /* SVM next rip save */
+ ftcprint (features1, 4, "tsc_scale"); /* TSC rate control */
+ ftcprint (features1, 5, "vmcb_clean"); /* VMCB clean bits */
+ ftcprint (features1, 6, "flushbyasid"); /* flush by ASID */
+ ftcprint (features1, 7, "decode_assists"); /* decode assists */
+ ftcprint (features1, 10, "pausefilter"); /* filt pause intrcpt */
+ ftcprint (features1, 12, "pfthreshold"); /* pause filt thresh */
+ ftcprint (features1, 13, "avic"); /* virt int control */
+ ftcprint (features1, 15, "v_vmsave_vmload"); /* virt vmsave vmload */
+ ftcprint (features1, 16, "vgif"); /* virt glb int flag */
+ ftcprint (features1, 20, "v_spec_ctrl"); /* virt spec ctrl support */
+/* ftcprint (features1, 28, "svme_addr_chk"); *//* secure vmexit addr check */
+ }
+
+ /* Intel cpuid 0x00000007 ecx */
+ if (is_intel && maxf >= 0x00000007)
+ {
+ cpuid (&unused, &unused, &features1, &unused, 0x00000007, 0);
+
+ ftcprint (features1, 1, "avx512vbmi"); /* vec bit manip */
+ ftcprint (features1, 2, "umip"); /* user mode ins prot */
+ ftcprint (features1, 3, "pku"); /* prot key userspace */
+ ftcprint (features1, 4, "ospke"); /* OS prot keys en */
+ ftcprint (features1, 5, "waitpkg"); /* umon/umwait/tpause */
+ ftcprint (features1, 6, "avx512_vbmi2"); /* vec bit manip 2 */
+ ftcprint (features1, 8, "gfni"); /* Galois field instr */
+ ftcprint (features1, 9, "vaes"); /* vector AES */
+ ftcprint (features1, 10, "vpclmulqdq"); /* nc mul dbl quad */
+ ftcprint (features1, 11, "avx512_vnni"); /* vec neural net */
+ ftcprint (features1, 12, "avx512_bitalg"); /* vpopcnt/b/w vpshuf */
+ ftcprint (features1, 13, "tme"); /* total mem encrypt */
+ ftcprint (features1, 14, "avx512_vpopcntdq"); /* vec popcnt dw/qw */
+ ftcprint (features1, 16, "la57"); /* 5 level paging */
+ ftcprint (features1, 22, "rdpid"); /* rdpid instruction */
+ ftcprint (features1, 24, "bus_lock_detect"); /* bus lock detect dbg excptn */
+ ftcprint (features1, 25, "cldemote"); /* cldemote instr */
+ ftcprint (features1, 27, "movdiri"); /* movdiri instr */
+ ftcprint (features1, 28, "movdir64b"); /* movdir64b instr */
+ ftcprint (features1, 29, "enqcmd"); /* enqcmd/s instructions*/
+ ftcprint (features1, 30, "sgx_lc"); /* sgx launch control */
+ }
+
+ /* AMD MCA cpuid 0x80000007 ebx */
+ if (is_amd && maxe >= 0x80000007)
+ {
+ cpuid (&unused, &features1, &unused, &unused, 0x80000007, 0);
+
+ ftcprint (features1, 0, "overflow_recov"); /* MCA oflow recovery */
+ ftcprint (features1, 1, "succor"); /* uncor err recovery */
+ ftcprint (features1, 3, "smca"); /* scalable MCA */
+ }
+
+ /* Intel cpuid 0x00000007 edx */
+ if (is_intel && maxf >= 0x00000007)
+ {
+ cpuid (&unused, &unused, &unused, &features1, 0x00000007, 0);
+
+ ftcprint (features1, 2, "avx512_4vnniw"); /* vec dot prod dw */
+ ftcprint (features1, 3, "avx512_4fmaps"); /* vec 4 FMA single */
+ ftcprint (features1, 4, "fsrm"); /* fast short REP MOVSB */
+ ftcprint (features1, 8, "avx512_vp2intersect"); /* vec intcpt d/q */
+ ftcprint (features1, 10, "md_clear"); /* verw clear buf */
+ ftcprint (features1, 14, "serialize"); /* SERIALIZE instruction */
+ ftcprint (features1, 16, "tsxldtrk"); /* TSX Susp Ld Addr Track */
+ ftcprint (features1, 18, "pconfig"); /* platform config */
+ ftcprint (features1, 19, "arch_lbr"); /* last branch records */
+ ftcprint (features1, 20, "ibt"); /* Indirect Branch Tracking */
+ ftcprint (features1, 22, "amx_bf16"); /* Advanced Matrix eXtensions Brain Float 16 dot product */
+ ftcprint (features1, 23, "avx512_fp16"); /* avx512 fp16 */
+ ftcprint (features1, 24, "amx_tile"); /* Advanced Matrix eXtensions Tile matrix multiply */
+ ftcprint (features1, 25, "amx_int8"); /* Advanced Matrix eXtensions Int 8 byte dot product */
+ ftcprint (features1, 28, "flush_l1d"); /* flush l1d cache */
+ ftcprint (features1, 29, "arch_capabilities"); /* arch cap MSR */
+ }
+
+ /* cpuid x8000001f eax */
+ if (is_amd && maxe >= 0x8000001f)
+ {
+ cpuid (&features2, &unused, &unused, &unused, 0x8000001f);
+
+ ftcprint (features2, 0, "sme"); /* secure memory encryption */
+ ftcprint (features2, 1, "sev"); /* AMD secure encrypted virt */
+/* ftcprint (features2, 2, "vm_page_flush");*/ /* VM page flush MSR */
+ ftcprint (features2, 3, "sev_es"); /* AMD SEV encrypted state */
+/* ftcprint (features2, 4, "sev_snp");*//* AMD SEV secure nested paging */
+/* ftcprint (features2, 5, "vmpl"); *//* VM permission levels support */
+/* ftcprint (features2, 10, "sme_coherent"); *//* SME h/w cache coherent */
+/* ftcprint (features2, 11, "sev_64b");*//* SEV 64 bit host guest only */
+/* ftcprint (features2, 12, "sev_rest_inj"); *//* SEV restricted injection */
+/* ftcprint (features2, 13, "sev_alt_inj"); *//* SEV alternate injection */
+/* ftcprint (features2, 14, "sev_es_dbg_swap");*//* SEV-ES debug state swap */
+/* ftcprint (features2, 15, "no_host_ibs"); *//* host IBS unsupported */
+/* ftcprint (features2, 16, "vte"); *//* virtual transparent encryption */
+ }
+
+ print ("\n");
+
+ bufptr += __small_sprintf (bufptr, "bogomips\t: %d.00\n",
+ bogomips);
+
+ /* cpuid 0x80000006 ebx TLB size */
+ if (maxe >= 0x80000006)
+ {
+ cpuid( &unused, &features1, &unused, &unused, 0x80000006, 0);
+ uint32_t tlbsize = ((features1 >> 16) & 0xfff) + (features1 & 0xfff);
+ if (tlbsize > 0)
+ bufptr += __small_sprintf (bufptr, "TLB size\t: %d 4K pages\n",
+ tlbsize);
+ }
+
+ bufptr += __small_sprintf (bufptr, "clflush size\t: %d\n"
+ "cache_alignment\t: %d\n",
+ clflush,
+ cache_alignment);
+
+ if (maxe >= 0x80000008) /* Address size. */
+ {
+ uint32_t addr_size, phys, virt;
+ cpuid (&addr_size, &unused, &unused, &unused, 0x80000008);
+
+ phys = addr_size & 0xff;
+ virt = (addr_size >> 8) & 0xff;
+ /* Fix an errata on Intel CPUs */
+ if (is_intel && family == 15 && model == 3 && stepping == 4)
+ phys = 36;
+ bufptr += __small_sprintf (bufptr, "address sizes\t: "
+ "%u bits physical, "
+ "%u bits virtual\n",
+ phys, virt);
+ }
+
+ /* cpuid 0x80000007 edx */
+ if (maxe >= 0x80000007) /* Advanced power management. */
+ {
+ cpuid (&unused, &unused, &unused, &features1, 0x80000007);
+
+ print ("power management:");
+ ftcprint (features1, 0, "ts"); /* temperature sensor */
+ ftcprint (features1, 1, "fid"); /* frequency id control */
+ ftcprint (features1, 2, "vid"); /* voltage id control */
+ ftcprint (features1, 3, "ttp"); /* thermal trip */
+ ftcprint (features1, 4, "tm"); /* hw thermal control */
+ ftcprint (features1, 5, "stc"); /* sw thermal control */
+ ftcprint (features1, 6, "100mhzsteps"); /* 100 MHz mult control */
+ ftcprint (features1, 7, "hwpstate"); /* hw P state control */
+/* ftcprint (features1, 8, "invariant_tsc"); */ /* TSC invariant */
+ ftcprint (features1, 9, "cpb"); /* core performance boost */
+ ftcprint (features1, 10, "eff_freq_ro"); /* ro eff freq interface */
+ ftcprint (features1, 11, "proc_feedback");/* proc feedback if */
+ ftcprint (features1, 12, "acc_power"); /* core power reporting */
+/* ftcprint (features1, 13, "connstby"); */ /* connected standby */
+/* ftcprint (features1, 14, "rapl"); */ /* running average power limit */
+ }
+
+ if (orig_affinity_mask != 0)
+ SetThreadGroupAffinity (GetCurrentThread (), &orig_group_affinity,
+ NULL);
+ print ("\n");
+ }
+
+ destbuf = (char *) crealloc_abort (destbuf, bufptr - buf);
+ memcpy (destbuf, buf, bufptr - buf);
+ return bufptr - buf;
+}
+
+static off_t
+format_proc_partitions (void *, char *&destbuf)
+{
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ NTSTATUS status;
+ HANDLE dirhdl;
+
+ /* Open \Device object directory. */
+ wchar_t wpath[MAX_PATH] = L"\\Device";
+ UNICODE_STRING upath = {14, 16, wpath};
+ InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE, NULL, NULL);
+ status = NtOpenDirectoryObject (&dirhdl, DIRECTORY_QUERY, &attr);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtOpenDirectoryObject, status %y", status);
+ __seterrno_from_nt_status (status);
+ return 0;
+ }
+
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+ char *bufptr = buf;
+ char *ioctl_buf = tp.c_get ();
+ PWCHAR mp_buf = tp.w_get ();
+ PDIRECTORY_BASIC_INFORMATION dbi_buf = (PDIRECTORY_BASIC_INFORMATION)
+ tp.w_get ();
+ WCHAR fpath[MAX_PATH];
+ WCHAR gpath[MAX_PATH];
+ DWORD len;
+
+ /* Traverse \Device directory ... */
+ BOOLEAN restart = TRUE;
+ bool got_one = false;
+ bool last_run = false;
+ ULONG context = 0;
+ while (!last_run)
+ {
+ status = NtQueryDirectoryObject (dirhdl, dbi_buf, 65536, FALSE, restart,
+ &context, NULL);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtQueryDirectoryObject, status %y", status);
+ __seterrno_from_nt_status (status);
+ break;
+ }
+ if (status != STATUS_MORE_ENTRIES)
+ last_run = true;
+ restart = FALSE;
+ for (PDIRECTORY_BASIC_INFORMATION dbi = dbi_buf;
+ dbi->ObjectName.Length > 0;
+ dbi++)
+ {
+ HANDLE devhdl;
+ PARTITION_INFORMATION_EX *pix = NULL;
+ PARTITION_INFORMATION *pi = NULL;
+ DWORD bytes_read;
+ DWORD part_cnt = 0;
+ unsigned long drive_num;
+ unsigned long long size;
+
+ /* ... and check for a "Harddisk[0-9]*" entry. */
+ if (dbi->ObjectName.Length < 9 * sizeof (WCHAR)
+ || wcsncasecmp (dbi->ObjectName.Buffer, L"Harddisk", 8) != 0
+ || !iswdigit (dbi->ObjectName.Buffer[8]))
+ continue;
+ /* Got it. Now construct the path to the entire disk, which is
+ "\\Device\\HarddiskX\\Partition0", and open the disk with
+ minimum permissions. */
+ drive_num = wcstoul (dbi->ObjectName.Buffer + 8, NULL, 10);
+ wcscpy (wpath, dbi->ObjectName.Buffer);
+ PWCHAR wpart = wpath + dbi->ObjectName.Length / sizeof (WCHAR);
+ wcpcpy (wpart, L"\\Partition0");
+ upath.Length = dbi->ObjectName.Length + 22;
+ upath.MaximumLength = upath.Length + sizeof (WCHAR);
+ InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE,
+ dirhdl, NULL);
+ status = NtOpenFile (&devhdl, READ_CONTROL, &attr, &io,
+ FILE_SHARE_VALID_FLAGS, 0);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtOpenFile(%S), status %y", &upath, status);
+ __seterrno_from_nt_status (status);
+ continue;
+ }
+ if (!got_one)
+ {
+ print ("major minor #blocks name win-mounts\n\n");
+ got_one = true;
+ }
+ /* Fetch partition info for the entire disk to get its size. */
+ if (DeviceIoControl (devhdl, IOCTL_DISK_GET_PARTITION_INFO_EX, NULL,
+ 0, ioctl_buf, NT_MAX_PATH, &bytes_read, NULL))
+ {
+ pix = (PARTITION_INFORMATION_EX *) ioctl_buf;
+ size = pix->PartitionLength.QuadPart;
+ }
+ else if (DeviceIoControl (devhdl, IOCTL_DISK_GET_PARTITION_INFO, NULL,
+ 0, ioctl_buf, NT_MAX_PATH, &bytes_read,
+ NULL))
+ {
+ pi = (PARTITION_INFORMATION *) ioctl_buf;
+ size = pi->PartitionLength.QuadPart;
+ }
+ else
+ {
+ debug_printf ("DeviceIoControl (%S, "
+ "IOCTL_DISK_GET_PARTITION_INFO{_EX}) %E", &upath);
+ size = 0;
+ }
+ device dev (drive_num, 0);
+ bufptr += __small_sprintf (bufptr, "%5d %5d %9U %s\n",
+ dev.get_major (), dev.get_minor (),
+ size >> 10, dev.name () + 5);
+ /* Fetch drive layout info to get size of all partitions on disk. */
+ if (DeviceIoControl (devhdl, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0,
+ ioctl_buf, NT_MAX_PATH, &bytes_read, NULL))
+ {
+ PDRIVE_LAYOUT_INFORMATION_EX pdlix =
+ (PDRIVE_LAYOUT_INFORMATION_EX) ioctl_buf;
+ part_cnt = pdlix->PartitionCount;
+ pix = pdlix->PartitionEntry;
+ }
+ else if (DeviceIoControl (devhdl, IOCTL_DISK_GET_DRIVE_LAYOUT, NULL,
+ 0, ioctl_buf, NT_MAX_PATH, &bytes_read,
+ NULL))
+ {
+ PDRIVE_LAYOUT_INFORMATION pdli =
+ (PDRIVE_LAYOUT_INFORMATION) ioctl_buf;
+ part_cnt = pdli->PartitionCount;
+ pi = pdli->PartitionEntry;
+ }
+ else
+ debug_printf ("DeviceIoControl(%S, "
+ "IOCTL_DISK_GET_DRIVE_LAYOUT{_EX}): %E", &upath);
+ /* Loop over partitions. */
+ if (pix || pi)
+ for (DWORD i = 0; i < part_cnt && i < 64; ++i)
+ {
+ DWORD part_num;
+
+ if (pix)
+ {
+ size = pix->PartitionLength.QuadPart;
+ part_num = pix->PartitionNumber;
+ ++pix;
+ }
+ else
+ {
+ size = pi->PartitionLength.QuadPart;
+ part_num = pi->PartitionNumber;
+ ++pi;
+ }
+ /* A partition number of 0 denotes an extended partition or a
+ filler entry as described in
+ fhandler_dev_floppy::lock_partition. Just skip. */
+ if (part_num == 0)
+ continue;
+ device dev (drive_num, part_num);
+
+ bufptr += __small_sprintf (bufptr, "%5d %5d %9U %s",
+ dev.get_major (), dev.get_minor (),
+ size >> 10, dev.name () + 5);
+ /* Check if the partition is mounted in Windows and, if so,
+ print the mount point list. */
+ __small_swprintf (fpath,
+ L"\\\\?\\GLOBALROOT\\Device\\%S\\Partition%u\\",
+ &dbi->ObjectName, part_num);
+ if (GetVolumeNameForVolumeMountPointW (fpath, gpath, MAX_PATH)
+ && GetVolumePathNamesForVolumeNameW (gpath, mp_buf,
+ NT_MAX_PATH, &len))
+ {
+ len = strlen (dev.name () + 5);
+ while (len++ < 6)
+ *bufptr++ = ' ';
+ for (PWCHAR p = mp_buf; *p; p = wcschr (p, L'\0') + 1)
+ bufptr += __small_sprintf (bufptr, " %W", p);
+ }
+
+ *bufptr++ = '\n';
+ }
+ NtClose (devhdl);
+ }
+ }
+ NtClose (dirhdl);
+
+ if (!got_one)
+ return 0;
+
+ destbuf = (char *) crealloc_abort (destbuf, bufptr - buf);
+ memcpy (destbuf, buf, bufptr - buf);
+ return bufptr - buf;
+}
+
+static off_t
+format_proc_self (void *, char *&destbuf)
+{
+ destbuf = (char *) crealloc_abort (destbuf, 16);
+ return __small_sprintf (destbuf, "%d", getpid ());
+}
+
+static off_t
+format_proc_cygdrive (void *, char *&destbuf)
+{
+ destbuf = (char *) crealloc_abort (destbuf, mount_table->cygdrive_len + 1);
+ char *dend = stpcpy (destbuf, mount_table->cygdrive);
+ if (dend > destbuf + 1) /* cygdrive != "/"? */
+ *--dend = '\0';
+ return dend - destbuf;
+}
+
+static off_t
+format_proc_mounts (void *, char *&destbuf)
+{
+ destbuf = (char *) crealloc_abort (destbuf, sizeof ("self/mounts"));
+ return __small_sprintf (destbuf, "self/mounts");
+}
+
+static off_t
+format_proc_filesystems (void *, char *&destbuf)
+{
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+ char *bufptr = buf;
+
+ /* start at 1 to skip type "none" */
+ for (int i = 1; fs_names[i].name; i++)
+ bufptr += __small_sprintf(bufptr, "%s\t%s\n",
+ fs_names[i].block_device ? "" : "nodev",
+ fs_names[i].name);
+
+ destbuf = (char *) crealloc_abort (destbuf, bufptr - buf);
+ memcpy (destbuf, buf, bufptr - buf);
+ return bufptr - buf;
+}
+
+static off_t
+format_proc_swaps (void *, char *&destbuf)
+{
+ unsigned long long total = 0ULL, used = 0ULL;
+ PSYSTEM_PAGEFILE_INFORMATION spi = NULL;
+ ULONG size = 512;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+ char *bufptr = buf;
+
+ spi = (PSYSTEM_PAGEFILE_INFORMATION) malloc (size);
+ if (spi)
+ {
+ status = NtQuerySystemInformation (SystemPagefileInformation, (PVOID) spi,
+ size, &size);
+ if (status == STATUS_INFO_LENGTH_MISMATCH)
+ {
+ free (spi);
+ spi = (PSYSTEM_PAGEFILE_INFORMATION) malloc (size);
+ if (spi)
+ status = NtQuerySystemInformation (SystemPagefileInformation,
+ (PVOID) spi, size, &size);
+ }
+ }
+
+ bufptr += __small_sprintf (bufptr,
+ "Filename\t\t\t\tType\t\tSize\t\tUsed\t\tPriority\n");
+
+ if (spi && NT_SUCCESS (status))
+ {
+ PSYSTEM_PAGEFILE_INFORMATION spp = spi;
+ char *filename = tp.c_get ();
+ do
+ {
+ total = (unsigned long long) spp->CurrentSize * wincap.page_size ();
+ used = (unsigned long long) spp->TotalUsed * wincap.page_size ();
+ cygwin_conv_path (CCP_WIN_W_TO_POSIX, spp->FileName.Buffer,
+ filename, NT_MAX_PATH);
+ /* ensure space between fields for clarity */
+ size_t tabo = strlen (filename) / 8; /* offset tabs to space name */
+ bufptr += sprintf (bufptr, "%s%s%s\t\t%llu%s\t%llu%s\t%d\n",
+ filename,
+ tabo < 5 ? "\t\t\t\t\t" + tabo : " ",
+ "file",
+ total >> 10,
+ total < 10000000000 ? "\t" : "",
+ used >> 10,
+ used < 10000000000 ? "\t" : "",
+ 0);
+ }
+ while (spp->NextEntryOffset
+ && (spp = (PSYSTEM_PAGEFILE_INFORMATION)
+ ((char *) spp + spp->NextEntryOffset)));
+ }
+
+ if (spi)
+ free (spi);
+
+ destbuf = (char *) crealloc_abort (destbuf, bufptr - buf);
+ memcpy (destbuf, buf, bufptr - buf);
+ return bufptr - buf;
+}
+
+static off_t
+format_proc_devices (void *, char *&destbuf)
+{
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+ char *bufptr = buf;
+
+ bufptr += __small_sprintf (bufptr,
+ "Character devices:\n"
+ "%3d mem\n"
+ "%3d cons\n"
+ "%3d /dev/tty\n"
+ "%3d /dev/console\n"
+ "%3d /dev/ptmx\n"
+ "%3d st\n"
+ "%3d misc\n"
+ "%3d sound\n"
+ "%3d ttyS\n"
+ "%3d tty\n"
+ "\n"
+ "Block devices:\n"
+ "%3d fd\n"
+ "%3d sd\n"
+ "%3d sr\n"
+ "%3d sd\n"
+ "%3d sd\n"
+ "%3d sd\n"
+ "%3d sd\n"
+ "%3d sd\n"
+ "%3d sd\n"
+ "%3d sd\n",
+ DEV_MEM_MAJOR, DEV_CONS_MAJOR, _major (FH_TTY),
+ _major (FH_CONSOLE), _major (FH_PTMX),
+ DEV_TAPE_MAJOR, DEV_MISC_MAJOR, DEV_SOUND_MAJOR,
+ DEV_SERIAL_MAJOR, DEV_PTYS_MAJOR, DEV_FLOPPY_MAJOR,
+ DEV_SD_MAJOR, DEV_CDROM_MAJOR, DEV_SD1_MAJOR,
+ DEV_SD2_MAJOR, DEV_SD3_MAJOR, DEV_SD4_MAJOR,
+ DEV_SD5_MAJOR, DEV_SD6_MAJOR, DEV_SD7_MAJOR);
+
+ destbuf = (char *) crealloc_abort (destbuf, bufptr - buf);
+ memcpy (destbuf, buf, bufptr - buf);
+ return bufptr - buf;
+}
+
+static off_t
+format_proc_misc (void *, char *&destbuf)
+{
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+ char *bufptr = buf;
+
+ bufptr += __small_sprintf (bufptr,
+ "%3d clipboard\n"
+ "%3d windows\n",
+ _minor (FH_CLIPBOARD), _minor (FH_WINDOWS));
+
+ destbuf = (char *) crealloc_abort (destbuf, bufptr - buf);
+ memcpy (destbuf, buf, bufptr - buf);
+ return bufptr - buf;
+}
+
+#undef print
diff --git a/winsup/cygwin/fhandler/process.cc b/winsup/cygwin/fhandler/process.cc
new file mode 100644
index 000000000..c8ca6e25a
--- /dev/null
+++ b/winsup/cygwin/fhandler/process.cc
@@ -0,0 +1,1558 @@
+/* fhandler_process.cc: fhandler for /proc/<pid> virtual filesystem
+
+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 <stdlib.h>
+#include <stdio.h>
+#include <sys/cygwin.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "fhandler_virtual.h"
+#include "pinfo.h"
+#include "shared_info.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "ntdll.h"
+#include "cygtls.h"
+#include "mount.h"
+#include "tls_pbuf.h"
+#include <sys/sysmacros.h>
+#include <sys/param.h>
+#include <ctype.h>
+
+#define _LIBC
+#include <dirent.h>
+
+static off_t format_process_maps (void *, char *&);
+static off_t format_process_stat (void *, char *&);
+static off_t format_process_status (void *, char *&);
+static off_t format_process_statm (void *, char *&);
+static off_t format_process_winexename (void *, char *&);
+static off_t format_process_winpid (void *, char *&);
+static off_t format_process_exename (void *, char *&);
+static off_t format_process_root (void *, char *&);
+static off_t format_process_cwd (void *, char *&);
+static off_t format_process_cmdline (void *, char *&);
+static off_t format_process_ppid (void *, char *&);
+static off_t format_process_uid (void *, char *&);
+static off_t format_process_pgid (void *, char *&);
+static off_t format_process_sid (void *, char *&);
+static off_t format_process_gid (void *, char *&);
+static off_t format_process_ctty (void *, char *&);
+static off_t format_process_fd (void *, char *&);
+static off_t format_process_mounts (void *, char *&);
+static off_t format_process_mountinfo (void *, char *&);
+static off_t format_process_environ (void *, char *&);
+
+static const virt_tab_t process_tab[] =
+{
+ { _VN ("."), FH_PROCESS, virt_directory, NULL },
+ { _VN (".."), FH_PROCESS, virt_directory, NULL },
+ { _VN ("cmdline"), FH_PROCESS, virt_file, format_process_cmdline },
+ { _VN ("ctty"), FH_PROCESS, virt_file, format_process_ctty },
+ { _VN ("cwd"), FH_PROCESS, virt_symlink, format_process_cwd },
+ { _VN ("environ"), FH_PROCESS, virt_file, format_process_environ },
+ { _VN ("exe"), FH_PROCESS, virt_symlink, format_process_exename },
+ { _VN ("exename"), FH_PROCESS, virt_file, format_process_exename },
+ { _VN ("fd"), FH_PROCESSFD, virt_directory, format_process_fd },
+ { _VN ("gid"), FH_PROCESS, virt_file, format_process_gid },
+ { _VN ("maps"), FH_PROCESS, virt_file, format_process_maps },
+ { _VN ("mountinfo"), FH_PROCESS, virt_file, format_process_mountinfo },
+ { _VN ("mounts"), FH_PROCESS, virt_file, format_process_mounts },
+ { _VN ("pgid"), FH_PROCESS, virt_file, format_process_pgid },
+ { _VN ("ppid"), FH_PROCESS, virt_file, format_process_ppid },
+ { _VN ("root"), FH_PROCESS, virt_symlink, format_process_root },
+ { _VN ("sid"), FH_PROCESS, virt_file, format_process_sid },
+ { _VN ("stat"), FH_PROCESS, virt_file, format_process_stat },
+ { _VN ("statm"), FH_PROCESS, virt_file, format_process_statm },
+ { _VN ("status"), FH_PROCESS, virt_file, format_process_status },
+ { _VN ("uid"), FH_PROCESS, virt_file, format_process_uid },
+ { _VN ("winexename"), FH_PROCESS, virt_file, format_process_winexename },
+ { _VN ("winpid"), FH_PROCESS, virt_file, format_process_winpid },
+ { NULL, 0, FH_NADA, virt_none, NULL }
+};
+
+static const int PROCESS_LINK_COUNT =
+ (sizeof (process_tab) / sizeof (virt_tab_t)) - 1;
+int get_process_state (DWORD dwProcessId);
+static bool get_mem_values (DWORD dwProcessId, size_t &vmsize, size_t &vmrss,
+ size_t &vmtext, size_t &vmdata, size_t &vmlib,
+ size_t &vmshare);
+
+virtual_ftype_t
+fhandler_process::exists ()
+{
+ const char *path = get_name ();
+ debug_printf ("exists (%s)", path);
+ path += proc_len + 1;
+ while (*path != 0 && !isdirsep (*path))
+ path++;
+ if (*path == 0)
+ return virt_rootdir;
+
+ virt_tab_t *entry = virt_tab_search (path + 1, true, process_tab,
+ PROCESS_LINK_COUNT);
+ if (entry)
+ {
+ if (!path[entry->name_len + 1])
+ {
+ fileid = entry - process_tab;
+ return entry->type;
+ }
+ if (entry->type == virt_directory) /* fd subdir only */
+ {
+ fileid = entry - process_tab;
+ if (fill_filebuf ())
+ return fd_type;
+ /* Check for nameless device entries. */
+ path = strrchr (path, '/');
+ if (path && *++path)
+ {
+ if (!strncmp (path, "pipe:[", 6))
+ return virt_pipe;
+ else if (!strncmp (path, "socket:[", 8))
+ return virt_socket;
+ }
+ }
+ }
+ return virt_none;
+}
+
+fhandler_process::fhandler_process ():
+ fhandler_proc ()
+{
+}
+
+int
+fhandler_process::fstat (struct stat *buf)
+{
+ const char *path = get_name ();
+ int file_type = exists ();
+ fhandler_base::fstat (buf);
+ path += proc_len + 1;
+ pid = atoi (path);
+
+ pinfo p (pid);
+ /* If p->pid != pid, then pid is actually the Windows PID for an execed
+ Cygwin process, and the pinfo entry is the additional entry created
+ at exec time. We don't want to enable the user to access a process
+ entry by using the Win32 PID, though. */
+ if (!p || p->pid != pid)
+ {
+ set_errno (ENOENT);
+ return -1;
+ }
+
+ buf->st_mode &= ~_IFMT & NO_W;
+
+ switch (file_type)
+ {
+ case virt_none:
+ set_errno (ENOENT);
+ return -1;
+ case virt_directory:
+ case virt_rootdir:
+ buf->st_ctime = buf->st_mtime = buf->st_birthtime = p->start_time;
+ buf->st_ctim.tv_nsec = buf->st_mtim.tv_nsec
+ = buf->st_birthtim.tv_nsec = 0;
+ time_as_timestruc_t (&buf->st_atim);
+ buf->st_uid = p->uid;
+ buf->st_gid = p->gid;
+ buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
+ if (file_type == virt_directory)
+ buf->st_nlink = 2;
+ else
+ buf->st_nlink = 3;
+ return 0;
+ case virt_symlink:
+ case virt_fdsymlink:
+ buf->st_uid = p->uid;
+ buf->st_gid = p->gid;
+ buf->st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
+ return 0;
+ case virt_pipe:
+ buf->st_uid = p->uid;
+ buf->st_gid = p->gid;
+ buf->st_mode = S_IFIFO | S_IRUSR | S_IWUSR;
+ return 0;
+ case virt_socket:
+ buf->st_uid = p->uid;
+ buf->st_gid = p->gid;
+ buf->st_mode = S_IFSOCK | S_IRUSR | S_IWUSR;
+ return 0;
+ case virt_file:
+ default:
+ buf->st_uid = p->uid;
+ buf->st_gid = p->gid;
+ buf->st_mode |= S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
+ return 0;
+ }
+}
+
+DIR *
+fhandler_process::opendir (int fd)
+{
+ DIR *dir = fhandler_virtual::opendir (fd);
+ if (dir && process_tab[fileid].fhandler == FH_PROCESSFD)
+ fill_filebuf ();
+ return dir;
+}
+
+int
+fhandler_process::closedir (DIR *dir)
+{
+ return fhandler_virtual::closedir (dir);
+}
+
+int
+fhandler_process::readdir (DIR *dir, dirent *de)
+{
+ int res = ENMFILE;
+ if (process_tab[fileid].fhandler == FH_PROCESSFD)
+ {
+ if ((size_t) dir->__d_position >= 2 + filesize / sizeof (int))
+ goto out;
+ }
+ else if (dir->__d_position >= PROCESS_LINK_COUNT)
+ goto out;
+ if (process_tab[fileid].fhandler == FH_PROCESSFD && dir->__d_position > 1)
+ {
+ int *p = (int *) filebuf;
+ __small_sprintf (de->d_name, "%d", p[dir->__d_position++ - 2]);
+ de->d_type = DT_LNK;
+ }
+ else
+ {
+ strcpy (de->d_name, process_tab[dir->__d_position].name);
+ de->d_type = virt_ftype_to_dtype (process_tab[dir->__d_position].type);
+ dir->__d_position++;
+ }
+ dir->__flags |= dirent_saw_dot | dirent_saw_dot_dot;
+ res = 0;
+out:
+ syscall_printf ("%d = readdir(%p, %p) (%s)", res, dir, de, de->d_name);
+ return res;
+}
+
+int
+fhandler_process::open (int flags, mode_t mode)
+{
+ int res = fhandler_virtual::open (flags, mode);
+ if (!res)
+ goto out;
+
+ nohandle (true);
+
+ const char *path;
+ path = get_name () + proc_len + 1;
+ pid = atoi (path);
+ while (*path != 0 && !isdirsep (*path))
+ path++;
+
+ if (*path == 0)
+ {
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ res = 0;
+ goto out;
+ }
+ else if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ res = 0;
+ goto out;
+ }
+ else
+ {
+ diropen = true;
+ goto success;
+ }
+ }
+
+ virt_tab_t *entry;
+ entry = virt_tab_search (path + 1, true, process_tab, PROCESS_LINK_COUNT);
+ if (!entry)
+ {
+ set_errno ((flags & O_CREAT) ? EROFS : ENOENT);
+ res = 0;
+ goto out;
+ }
+ if (entry->fhandler == FH_PROCESSFD)
+ {
+ diropen = true;
+ goto success;
+ }
+ if (flags & O_WRONLY)
+ {
+ set_errno (EROFS);
+ res = 0;
+ goto out;
+ }
+
+ fileid = entry - process_tab;
+ if (!fill_filebuf ())
+ {
+ res = 0;
+ goto out;
+ }
+
+ if (flags & O_APPEND)
+ position = filesize;
+ else
+ position = 0;
+
+success:
+ res = 1;
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ set_open_status ();
+out:
+ syscall_printf ("%d = fhandler_proc::open(%y, 0%o)", res, flags, mode);
+ return res;
+}
+
+struct process_fd_t {
+ const char *path;
+ _pinfo *p;
+ virtual_ftype_t *fd_type;
+};
+
+bool
+fhandler_process::fill_filebuf ()
+{
+ const char *path;
+ path = get_name () + proc_len + 1;
+ if (!pid)
+ pid = atoi (path);
+
+ pinfo p (pid);
+ /* If p->pid != pid, then pid is actually the Windows PID for an execed
+ Cygwin process, and the pinfo entry is the additional entry created
+ at exec time. We don't want to enable the user to access a process
+ entry by using the Win32 PID, though. */
+ if (!p || p->pid != pid)
+ {
+ set_errno (ENOENT);
+ return false;
+ }
+
+ if (process_tab[fileid].format_func)
+ {
+ if (process_tab[fileid].fhandler == FH_PROCESSFD)
+ {
+ process_fd_t fd = { path, p , &fd_type };
+ filesize = process_tab[fileid].format_func (&fd, filebuf);
+ }
+ else
+ filesize = process_tab[fileid].format_func (p, filebuf);
+ return filesize < 0 ? false : true;
+ }
+ return false;
+}
+
+static off_t
+format_process_fd (void *data, char *&destbuf)
+{
+ _pinfo *p = ((process_fd_t *) data)->p;
+ const char *path = ((process_fd_t *) data)->path;
+ size_t fs = 0;
+ /* path looks like "$PID/fd", "$PID/fd/", "$PID/fd/[0-9]*". In the latter
+ case a trailing slash and more followup chars are allowed, provided the
+ descriptor symlink points to a directory. */
+ char *fdp = strchr (path, '/') + 3;
+ /* The "fd" directory itself? */
+ if (fdp[0] =='\0' || (fdp[0] == '/' && fdp[1] == '\0'))
+ {
+ if (destbuf)
+ cfree (destbuf);
+ destbuf = p ? p->fds (fs) : NULL;
+ *((process_fd_t *) data)->fd_type = virt_symlink;
+ }
+ else
+ {
+ char *e;
+ int fd;
+
+ if (destbuf)
+ cfree (destbuf);
+ fd = strtol (++fdp, &e, 10);
+ if (fd < 0 || e == fdp || (*e != '/' && *e != '\0'))
+ {
+ set_errno (ENOENT);
+ return -1;
+ }
+ destbuf = p ? p->fd (fd, fs) : NULL;
+ if (!destbuf || !*destbuf)
+ {
+ set_errno (ENOENT);
+ return -1;
+ }
+ if (*e == '\0')
+ *((process_fd_t *) data)->fd_type = virt_fdsymlink;
+ else /* trailing path */
+ {
+ /* Does the descriptor link point to a directory? */
+ bool dir;
+ if (*destbuf != '/') /* e.g., "pipe:[" or "socket:[" */
+ dir = false;
+ else
+ {
+ path_conv pc (destbuf);
+ dir = pc.isdir ();
+ }
+ if (!dir)
+ {
+ set_errno (ENOTDIR);
+ cfree (destbuf);
+ return -1;
+ }
+ char *newbuf = (char *) cmalloc_abort (HEAP_STR, strlen (destbuf)
+ + strlen (e) + 1);
+ stpcpy (stpcpy (newbuf, destbuf), e);
+ cfree (destbuf);
+ destbuf = newbuf;
+ *((process_fd_t *) data)->fd_type = virt_fsdir;
+ }
+ }
+ return fs;
+}
+
+static off_t
+format_process_ppid (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ destbuf = (char *) crealloc_abort (destbuf, 40);
+ return __small_sprintf (destbuf, "%d\n", p->ppid);
+}
+
+static off_t
+format_process_uid (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ destbuf = (char *) crealloc_abort (destbuf, 40);
+ return __small_sprintf (destbuf, "%d\n", p->uid);
+}
+
+static off_t
+format_process_pgid (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ destbuf = (char *) crealloc_abort (destbuf, 40);
+ return __small_sprintf (destbuf, "%d\n", p->pgid);
+}
+
+static off_t
+format_process_sid (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ destbuf = (char *) crealloc_abort (destbuf, 40);
+ return __small_sprintf (destbuf, "%d\n", p->sid);
+}
+
+static off_t
+format_process_gid (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ destbuf = (char *) crealloc_abort (destbuf, 40);
+ return __small_sprintf (destbuf, "%d\n", p->gid);
+}
+
+static off_t
+format_process_ctty (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ if (p->ctty < 0)
+ {
+ destbuf = (char *) crealloc_abort (destbuf, 2);
+ return __small_sprintf (destbuf, "\n");
+ }
+ device d;
+ d.parse (p->ctty);
+ destbuf = (char *) crealloc_abort (destbuf, strlen (d.name ()) + 2);
+ return __small_sprintf (destbuf, "%s\n", d.name ());
+}
+
+static off_t
+format_process_root (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ size_t fs;
+
+ if (destbuf)
+ {
+ cfree (destbuf);
+ destbuf = NULL;
+ }
+ destbuf = p ? p->root (fs) : NULL;
+ if (!destbuf || !*destbuf)
+ {
+ destbuf = cstrdup ("<defunct>");
+ fs = strlen (destbuf) + 1;
+ }
+ return fs;
+}
+
+static off_t
+format_process_cwd (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ size_t fs;
+
+ if (destbuf)
+ {
+ cfree (destbuf);
+ destbuf = NULL;
+ }
+ destbuf = p ? p->cwd (fs) : NULL;
+ if (!destbuf || !*destbuf)
+ {
+ destbuf = cstrdup ("<defunct>");
+ fs = strlen (destbuf) + 1;
+ }
+ return fs;
+}
+
+static off_t
+format_process_cmdline (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ size_t fs;
+
+ if (destbuf)
+ {
+ cfree (destbuf);
+ destbuf = NULL;
+ }
+ destbuf = p ? p->cmdline (fs) : NULL;
+ if (destbuf && *destbuf)
+ return fs;
+ return format_process_exename (data, destbuf);
+}
+
+static off_t
+format_process_exename (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ int len;
+ tmp_pathbuf tp;
+
+ char *buf = tp.c_get ();
+ if (p->process_state & PID_EXITED)
+ stpcpy (buf, "<defunct>");
+ else
+ {
+ mount_table->conv_to_posix_path (p->progname, buf, CCP_RELATIVE);
+ len = strlen (buf);
+ if (len > 4)
+ {
+ char *s = buf + len - 4;
+ if (ascii_strcasematch (s, ".exe"))
+ *s = 0;
+ }
+ }
+ destbuf = (char *) crealloc_abort (destbuf, (len = strlen (buf)) + 1);
+ stpcpy (destbuf, buf);
+ return len;
+}
+
+static off_t
+format_process_winpid (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ destbuf = (char *) crealloc_abort (destbuf, 20);
+ return __small_sprintf (destbuf, "%d\n", p->dwProcessId);
+}
+
+static off_t
+format_process_winexename (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ size_t len = sys_wcstombs (NULL, 0, p->progname);
+ destbuf = (char *) crealloc_abort (destbuf, len + 1);
+ /* With trailing \0 for backward compat reasons. */
+ sys_wcstombs (destbuf, len + 1, p->progname);
+ return len;
+}
+
+static off_t
+format_process_environ (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ size_t fs;
+
+ if (destbuf)
+ {
+ cfree (destbuf);
+ destbuf = NULL;
+ }
+ destbuf = p ? p->environ (fs) : NULL;
+ if (!destbuf || !*destbuf)
+ {
+ destbuf = cstrdup ("<defunct>");
+ fs = strlen (destbuf) + 1;
+ }
+ return fs;
+}
+
+struct heap_info
+{
+ struct heap
+ {
+ heap *next;
+ unsigned heap_id;
+ char *base;
+ char *end;
+ unsigned long flags;
+ };
+ heap *heap_vm_chunks;
+
+ heap_info (DWORD pid)
+ : heap_vm_chunks (NULL)
+ {
+ PDEBUG_BUFFER buf;
+ NTSTATUS status;
+ PDEBUG_HEAP_ARRAY harray;
+
+ buf = RtlCreateQueryDebugBuffer (16 * 65536, FALSE);
+ if (!buf)
+ return;
+ status = RtlQueryProcessDebugInformation (pid, PDI_HEAPS | PDI_HEAP_BLOCKS,
+ buf);
+ if (NT_SUCCESS (status)
+ && (harray = (PDEBUG_HEAP_ARRAY) buf->HeapInformation) != NULL)
+ for (ULONG hcnt = 0; hcnt < harray->Count; ++hcnt)
+ {
+ PDEBUG_HEAP_BLOCK barray = (PDEBUG_HEAP_BLOCK)
+ harray->Heaps[hcnt].Blocks;
+ if (!barray)
+ continue;
+ for (ULONG bcnt = 0; bcnt < harray->Heaps[hcnt].BlockCount; ++bcnt)
+ if (barray[bcnt].Flags & 2)
+ {
+ heap *h = (heap *) malloc (sizeof (heap));
+ if (h)
+ {
+ *h = (heap) { heap_vm_chunks,
+ hcnt, (char *) barray[bcnt].Address,
+ (char *) barray[bcnt].Address
+ + barray[bcnt].Size,
+ harray->Heaps[hcnt].Flags };
+ heap_vm_chunks = h;
+ }
+ }
+ }
+ RtlDestroyQueryDebugBuffer (buf);
+ }
+
+ char *fill_if_match (char *base, ULONG type, char *dest)
+ {
+ for (heap *h = heap_vm_chunks; h; h = h->next)
+ if (base >= h->base && base < h->end)
+ {
+ char *p = dest + __small_sprintf (dest, "[win heap %ld", h->heap_id);
+ if (!(h->flags & HEAP_FLAG_NONDEFAULT))
+ p = stpcpy (p, " default");
+ if ((h->flags & HEAP_FLAG_SHAREABLE) && (type & MEM_MAPPED))
+ p = stpcpy (p, " shared");
+ if (h->flags & HEAP_FLAG_EXECUTABLE)
+ p = stpcpy (p, " exec");
+ if (h->flags & HEAP_FLAG_GROWABLE)
+ p = stpcpy (p, " grow");
+ if (h->flags & HEAP_FLAG_NOSERIALIZE)
+ p = stpcpy (p, " noserial");
+ if (h->flags == HEAP_FLAG_DEBUGGED)
+ p = stpcpy (p, " debug");
+ stpcpy (p, "]");
+ return dest;
+ }
+ return NULL;
+ }
+
+ ~heap_info ()
+ {
+ heap *n = 0;
+ for (heap *m = heap_vm_chunks; m; m = n)
+ {
+ n = m->next;
+ free (m);
+ }
+ }
+};
+
+struct thread_info
+{
+ struct region
+ {
+ region *next;
+ ULONG thread_id;
+ char *start;
+ char *end;
+ bool teb;
+ };
+ region *regions;
+
+ thread_info (DWORD pid, HANDLE process)
+ : regions (NULL)
+ {
+ NTSTATUS status;
+ PVOID buf = NULL;
+ ULONG size = 50 * (sizeof (SYSTEM_PROCESS_INFORMATION)
+ + 16 * sizeof (SYSTEM_THREADS));
+ PSYSTEM_PROCESS_INFORMATION proc;
+ PSYSTEM_THREADS thread;
+
+ do
+ {
+ buf = realloc (buf, size);
+ status = NtQuerySystemInformation (SystemProcessInformation,
+ buf, size, NULL);
+ size <<= 1;
+ }
+ while (status == STATUS_INFO_LENGTH_MISMATCH);
+ if (!NT_SUCCESS (status))
+ {
+ if (buf)
+ free (buf);
+ debug_printf ("NtQuerySystemInformation, %y", status);
+ return;
+ }
+ proc = (PSYSTEM_PROCESS_INFORMATION) buf;
+ while (true)
+ {
+ if ((DWORD) (uintptr_t) proc->UniqueProcessId == pid)
+ break;
+ if (!proc->NextEntryOffset)
+ {
+ free (buf);
+ return;
+ }
+ proc = (PSYSTEM_PROCESS_INFORMATION) ((PBYTE) proc
+ + proc->NextEntryOffset);
+ }
+ thread = proc->Threads;
+ for (ULONG i = 0; i < proc->NumberOfThreads; ++i)
+ {
+ THREAD_BASIC_INFORMATION tbi;
+ TEB teb;
+ HANDLE thread_h;
+
+ thread_h = OpenThread (THREAD_QUERY_LIMITED_INFORMATION, FALSE,
+ (ULONG) (ULONG_PTR) thread[i].ClientId.UniqueThread);
+ if (!thread_h)
+ continue;
+ status = NtQueryInformationThread (thread_h, ThreadBasicInformation,
+ &tbi, sizeof tbi, NULL);
+ CloseHandle (thread_h);
+ if (!NT_SUCCESS (status))
+ continue;
+ region *r = (region *) malloc (sizeof (region));
+ if (r)
+ {
+ *r = (region) { regions,
+ (ULONG) (ULONG_PTR) thread[i].ClientId.UniqueThread,
+ (char *) tbi.TebBaseAddress,
+ (char *) tbi.TebBaseAddress
+ + 2 * wincap.page_size (),
+ true };
+ regions = r;
+ }
+ if (!ReadProcessMemory (process, (PVOID) tbi.TebBaseAddress,
+ &teb, sizeof teb, NULL))
+ continue;
+ r = (region *) malloc (sizeof (region));
+ if (r)
+ {
+ *r = (region) { regions,
+ (ULONG) (ULONG_PTR) thread[i].ClientId.UniqueThread,
+ (char *) (teb.DeallocationStack
+ ?: teb.Tib.StackLimit),
+ (char *) teb.Tib.StackBase,
+ false };
+ regions = r;
+ }
+ }
+ free (buf);
+ }
+
+ char *fill_if_match (char *base, ULONG type, char *dest)
+ {
+ for (region *r = regions; r; r = r->next)
+ if (base >= r->start && base < r->end)
+ {
+ char *p = dest + __small_sprintf (dest, "[%s (tid %ld)",
+ r->teb ? "teb" : "stack",
+ r->thread_id);
+ if (type & MEM_MAPPED)
+ p = stpcpy (p, " shared");
+ stpcpy (p, "]");
+ return dest;
+ }
+ return NULL;
+ }
+ /* Helper to look for TEBs inside single allocated region since W10 1511. */
+ char *fill_if_match (char *start, char *dest)
+ {
+ for (region *r = regions; r; r = r->next)
+ if (r->teb && start == r->start)
+ {
+ __small_sprintf (dest, "[teb (tid %ld)]", r->thread_id);
+ return r->end;
+ }
+ return NULL;
+ }
+
+ ~thread_info ()
+ {
+ region *n = 0;
+ for (region *m = regions; m; m = n)
+ {
+ n = m->next;
+ free (m);
+ }
+ }
+};
+
+static off_t
+format_process_maps (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ HANDLE proc = OpenProcess (PROCESS_QUERY_INFORMATION
+ | PROCESS_VM_READ, FALSE, p->dwProcessId);
+ if (!proc)
+ {
+ if (!(p->process_state & PID_EXITED))
+ {
+ DWORD error = GetLastError ();
+ __seterrno_from_win_error (error);
+ debug_printf ("OpenProcess: ret %u; pid: %d", error, p->dwProcessId);
+ return -1;
+ }
+ else
+ {
+ /* Else it's a zombie process; just return an empty string */
+ destbuf = (char *) crealloc_abort (destbuf, 1);
+ destbuf[0] = '\0';
+ return 0;
+ }
+ }
+
+ NTSTATUS status;
+ PROCESS_BASIC_INFORMATION pbi;
+ PPEB peb = NULL;
+
+ memset (&pbi, 0, sizeof (pbi));
+ status = NtQueryInformationProcess (proc, ProcessBasicInformation,
+ &pbi, sizeof pbi, NULL);
+ if (NT_SUCCESS (status))
+ peb = pbi.PebBaseAddress;
+ /* myself is in the same spot in every process, so is the pointer to the
+ procinfo. But make sure the destructor doesn't try to release procinfo! */
+ pinfo proc_pinfo;
+ if (ReadProcessMemory (proc, &myself, &proc_pinfo, sizeof proc_pinfo, NULL))
+ proc_pinfo.preserve ();
+ /* The heap info on the cygheap is also in the same spot in each process
+ because the cygheap is located at the same address. */
+ user_heap_info user_heap;
+ ReadProcessMemory (proc, &cygheap->user_heap, &user_heap,
+ sizeof user_heap, NULL);
+
+ off_t len = 0;
+
+ union access
+ {
+ char flags[8];
+ off_t word;
+ } a;
+
+ struct region {
+ access a;
+ char *abase;
+ char *rbase;
+ char *rend;
+ DWORD state;
+ } cur = {{{'\0'}}, (char *)1, 0, 0};
+
+ MEMORY_BASIC_INFORMATION mb;
+ dos_drive_mappings drive_maps;
+ heap_info heaps (p->dwProcessId);
+ thread_info threads (p->dwProcessId, proc);
+ struct stat st;
+ long last_pass = 0;
+
+ tmp_pathbuf tp;
+ PMEMORY_SECTION_NAME msi = (PMEMORY_SECTION_NAME) tp.w_get ();
+ char *posix_modname = tp.c_get ();
+ size_t maxsize = 0;
+ char *peb_teb_abase = NULL;
+
+ if (destbuf)
+ {
+ cfree (destbuf);
+ destbuf = NULL;
+ }
+
+ /* Iterate over each VM region in the address space, coalescing
+ memory regions with the same permissions. Once we run out, do one
+ last_pass to trigger output of the last accumulated region. */
+ for (char *i = 0;
+ VirtualQueryEx (proc, i, &mb, sizeof(mb)) || (1 == ++last_pass);
+ i = cur.rend)
+ {
+ if (last_pass)
+ posix_modname[0] = '\0';
+ if (mb.State == MEM_FREE)
+ a.word = 0;
+ else if (mb.State == MEM_RESERVE)
+ {
+ char *p = stpcpy (a.flags, "===");
+ stpcpy (p, (mb.Type & MEM_MAPPED) ? "s" : "p");
+ }
+ else
+ {
+ static DWORD const RO = (PAGE_EXECUTE_READ | PAGE_READONLY);
+ static DWORD const RW = (PAGE_EXECUTE_READWRITE | PAGE_READWRITE
+ | PAGE_EXECUTE_WRITECOPY | PAGE_WRITECOPY);
+ static DWORD const X = (PAGE_EXECUTE | PAGE_EXECUTE_READ
+ | PAGE_EXECUTE_READWRITE
+ | PAGE_EXECUTE_WRITECOPY);
+ static DWORD const WC = (PAGE_EXECUTE_WRITECOPY | PAGE_WRITECOPY);
+ DWORD p = mb.Protect;
+ a = (access) {{
+ (p & (RO | RW)) ? 'r' : '-',
+ (p & (RW)) ? 'w' : '-',
+ (p & (X)) ? 'x' : '-',
+ (mb.Type & MEM_MAPPED) && !(p & (WC)) ? 's'
+ : (p & PAGE_GUARD) ? 'g' : 'p',
+ '\0', // zero-fill the remaining bytes
+ }};
+ }
+
+ region next = { a,
+ (char *) mb.AllocationBase,
+ (char *) mb.BaseAddress,
+ (char *) mb.BaseAddress+mb.RegionSize,
+ mb.State
+ };
+
+ /* Windows permissions are more fine-grained than the unix rwxp,
+ so we reduce clutter by manually coalescing regions sharing
+ the same allocation base and effective permissions. */
+ bool newbase = (next.abase != cur.abase);
+ if (!last_pass && !newbase && next.a.word == cur.a.word)
+ cur.rend = next.rend; /* merge with previous */
+ else
+ {
+ char *peb_teb_end = NULL;
+peb_teb_rinse_repeat:
+ /* Starting with W10 1511, PEB and TEBs don't get allocated
+ separately. Rather they are created in a single region. Examine
+ the region starting at the PEB address page-wise. */
+ if (wincap.has_new_pebteb_region ())
+ {
+ if (peb_teb_abase && !peb_teb_end && cur.abase == peb_teb_abase)
+ {
+ posix_modname[0] = '\0';
+ peb_teb_end = cur.rend;
+ if (cur.state == MEM_COMMIT)
+ cur.rend = cur.rbase + wincap.page_size ();
+ }
+ if (cur.state == MEM_COMMIT)
+ {
+ if (!peb_teb_abase && cur.rbase == (char *) peb)
+ {
+ peb_teb_abase = cur.abase;
+ peb_teb_end = cur.rend;
+ cur.rend = cur.rbase + wincap.page_size ();
+ strcpy (posix_modname, "[peb]");
+ }
+ else if (peb_teb_end)
+ {
+ char *end;
+ posix_modname[0] = '\0';
+ end = threads.fill_if_match (cur.rbase, posix_modname);
+
+ if (end)
+ cur.rend = end;
+ else
+ {
+ char *base = cur.rbase;
+ do
+ {
+ base += wincap.page_size ();
+ }
+ while (!threads.fill_if_match (base, posix_modname)
+ && base < peb_teb_end);
+ if (posix_modname[0])
+ {
+ posix_modname[0] = '\0';
+ cur.rend = base;
+ }
+ else
+ cur.rend = peb_teb_end;
+ }
+ }
+ }
+ }
+ /* output the current region if it's "interesting". */
+ if (cur.a.word)
+ {
+ size_t newlen = strlen (posix_modname) + 62;
+ if (len + newlen >= maxsize)
+ destbuf = (char *)
+ crealloc_abort (destbuf,
+ maxsize += roundup2 (newlen, 2048UL));
+ int written = __small_sprintf (destbuf + len,
+ "%08lx-%08lx %s %08lx %04x:%04x %U ",
+ cur.rbase, cur.rend, cur.a.flags,
+ cur.rbase - cur.abase,
+ st.st_dev >> 16,
+ st.st_dev & 0xffff,
+ st.st_ino);
+ while (written < 62)
+ destbuf[len + written++] = ' ';
+ len += written;
+ len += __small_sprintf (destbuf + len, "%s\n", posix_modname);
+ }
+
+ if (peb_teb_end && cur.state == MEM_COMMIT)
+ {
+ cur.rbase = cur.rend;
+ cur.rend += wincap.page_size ();
+ if (cur.rbase < peb_teb_end)
+ goto peb_teb_rinse_repeat;
+ }
+ /* start of a new region (but possibly still the same allocation). */
+ cur = next;
+ /* if a new allocation, figure out what kind it is. */
+ if (newbase && !last_pass && cur.state != MEM_FREE)
+ {
+ /* If the return length pointer is missing, NtQueryVirtualMemory
+ returns with STATUS_ACCESS_VIOLATION on Windows 2000. */
+ SIZE_T ret_len = 0;
+
+ st.st_dev = 0;
+ st.st_ino = 0;
+ if ((mb.Type & (MEM_MAPPED | MEM_IMAGE))
+ && NT_SUCCESS (status = NtQueryVirtualMemory (proc, cur.abase,
+ MemorySectionName,
+ msi, 65536, &ret_len)))
+ {
+ PWCHAR dosname =
+ drive_maps.fixup_if_match (msi->SectionFileName.Buffer);
+ if (mount_table->conv_to_posix_path (dosname,
+ posix_modname, 0))
+ sys_wcstombs (posix_modname, NT_MAX_PATH, dosname);
+ stat (posix_modname, &st);
+ }
+ else if (!threads.fill_if_match (cur.abase, mb.Type,
+ posix_modname)
+ && !heaps.fill_if_match (cur.abase, mb.Type,
+ posix_modname))
+ {
+ if (cur.abase == (char *) peb)
+ strcpy (posix_modname, "[peb]");
+ else if (cur.abase == (char *) &SharedUserData)
+ strcpy (posix_modname, "[shared-user-data]");
+ else if (cur.abase == (char *) cygwin_shared)
+ strcpy (posix_modname, "[cygwin-shared]");
+ else if (cur.abase == (char *) user_shared)
+ strcpy (posix_modname, "[cygwin-user-shared]");
+ else if (cur.abase == (char *) *proc_pinfo)
+ strcpy (posix_modname, "[procinfo]");
+ else if (cur.abase == user_heap.base)
+ strcpy (posix_modname, "[heap]");
+ else
+ posix_modname[0] = 0;
+ }
+ }
+ }
+ }
+ CloseHandle (proc);
+ return len;
+}
+
+static off_t
+format_process_stat (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ char cmd[NAME_MAX + 1];
+ int state = 'R';
+ unsigned long fault_count = 0UL,
+ vmsize = 0UL, vmrss = 0UL, vmmaxrss = 0UL;
+ uint64_t utime = 0ULL, stime = 0ULL, start_time = 0ULL;
+ int nice = 0;
+/* ctty maj is 31:16, min is 15:0; tty_nr s/b maj 15:8, min 31:20, 7:0;
+ maj is 31:16 >> 16 & fff << 8; min is 15:0 >> 8 & ff << 20 | & ff */
+ int tty_nr = (((p->ctty >> 8) & 0xff) << 20)
+ | (((p->ctty >> 16) & 0xfff) << 8)
+ | (p->ctty & 0xff);
+
+ if (p->process_state & PID_EXITED)
+ strcpy (cmd, "<defunct>");
+ else
+ {
+ PWCHAR last_slash = wcsrchr (p->progname, L'\\');
+ sys_wcstombs (cmd, NAME_MAX + 1,
+ last_slash ? last_slash + 1 : p->progname);
+ int len = strlen (cmd);
+ if (len > 4)
+ {
+ char *s = cmd + len - 4;
+ if (ascii_strcasematch (s, ".exe"))
+ *s = 0;
+ }
+ }
+ /* Note: under Windows, a process is always running - it's only threads
+ that get suspended. Therefore the default state is R (runnable). */
+ if (p->process_state & PID_EXITED)
+ state = 'Z';
+ else if (p->process_state & PID_STOPPED)
+ state = 'T';
+ else
+ state = get_process_state (p->dwProcessId);
+
+ NTSTATUS status;
+ HANDLE hProcess;
+ VM_COUNTERS vmc = { 0 };
+ KERNEL_USER_TIMES put = { 0 };
+ QUOTA_LIMITS ql = { 0 };
+ SYSTEM_TIMEOFDAY_INFORMATION stodi = { 0 };
+
+ hProcess = OpenProcess (PROCESS_QUERY_LIMITED_INFORMATION,
+ FALSE, p->dwProcessId);
+ if (hProcess == NULL)
+ {
+ if (!(p->process_state & PID_EXITED))
+ {
+ DWORD error = GetLastError ();
+ __seterrno_from_win_error (error);
+ debug_printf ("OpenProcess: ret %u; pid: %d", error, p->dwProcessId);
+ return -1;
+ }
+ /* Else it's a zombie process; just leave each structure zero'd */
+ }
+ else
+ {
+ status = NtQueryInformationProcess (hProcess, ProcessVmCounters,
+ (PVOID) &vmc, sizeof vmc, NULL);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtQueryInformationProcess(ProcessVmCounters): status %y",
+ status);
+ status = NtQueryInformationProcess (hProcess, ProcessTimes,
+ (PVOID) &put, sizeof put, NULL);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtQueryInformationProcess(ProcessTimes): status %y",
+ status);
+ status = NtQueryInformationProcess (hProcess, ProcessQuotaLimits,
+ (PVOID) &ql, sizeof ql, NULL);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtQueryInformationProcess(ProcessQuotaLimits): "
+ "status %y", status);
+ nice = winprio_to_nice (GetPriorityClass (hProcess));
+ CloseHandle (hProcess);
+ }
+ status = NtQuerySystemInformation (SystemTimeOfDayInformation,
+ (PVOID) &stodi, sizeof stodi, NULL);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtQuerySystemInformation(SystemTimeOfDayInformation): "
+ "status %y", status);
+ fault_count = vmc.PageFaultCount;
+ utime = put.UserTime.QuadPart * CLOCKS_PER_SEC / NS100PERSEC;
+ stime = put.KernelTime.QuadPart * CLOCKS_PER_SEC / NS100PERSEC;
+ if (put.CreateTime.QuadPart)
+ start_time = (put.CreateTime.QuadPart - stodi.BootTime.QuadPart)
+ * CLOCKS_PER_SEC / NS100PERSEC;
+ else
+ start_time = (p->start_time - to_time_t (&stodi.BootTime)) * CLOCKS_PER_SEC;
+ unsigned page_size = wincap.page_size ();
+ vmsize = vmc.PagefileUsage; /* bytes */
+ vmrss = vmc.WorkingSetSize / page_size; /* pages */
+ vmmaxrss = ql.MaximumWorkingSetSize; /* bytes */
+
+ destbuf = (char *) crealloc_abort (destbuf, strlen (cmd) + 320);
+ return __small_sprintf (destbuf, "%d (%s) %c "
+ "%d %d %d %d "
+ "%d %u %lu %lu %u %u "
+ "%U %U %U %U "
+ "%d %d %d %d "
+ "%U "
+ "%lu %ld %lu\n",
+ p->pid, cmd, state,
+ p->ppid, p->pgid, p->sid, tty_nr,
+ -1, 0, fault_count, fault_count, 0, 0,
+ utime, stime, utime, stime,
+ NZERO + nice, nice, 0, 0,
+ start_time,
+ vmsize, vmrss, vmmaxrss
+ );
+}
+
+static off_t
+format_process_status (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ char cmd[NAME_MAX + 1];
+ int state = 'R';
+ const char *state_str = "unknown";
+ size_t vmsize = 0, vmrss = 0, vmdata = 0, vmlib = 0, vmtext = 0, vmshare = 0;
+ sigset_t pnd = 0, blk = 0, ign = 0;
+ bool fetch_siginfo = false;
+
+ PWCHAR last_slash = wcsrchr (p->progname, L'\\');
+ sys_wcstombs (cmd, NAME_MAX + 1, last_slash ? last_slash + 1 : p->progname);
+ int len = strlen (cmd);
+ if (len > 4)
+ {
+ char *s = cmd + len - 4;
+ if (ascii_strcasematch (s, ".exe"))
+ *s = 0;
+ }
+ /* Note: under Windows, a process is always running - it's only threads
+ that get suspended. Therefore the default state is R (runnable). */
+ if (p->process_state & PID_EXITED)
+ state = 'Z';
+ else if (p->process_state & PID_STOPPED)
+ state = 'T';
+ else
+ state = get_process_state (p->dwProcessId);
+ switch (state)
+ {
+ case 'O':
+ state_str = "running";
+ fetch_siginfo = true;
+ break;
+ case 'D':
+ case 'S':
+ state_str = "sleeping";
+ fetch_siginfo = true;
+ break;
+ case 'R':
+ state_str = "runnable";
+ fetch_siginfo = true;
+ break;
+ case 'Z':
+ state_str = "zombie";
+ break;
+ case 'T':
+ state_str = "stopped";
+ break;
+ }
+ get_mem_values (p->dwProcessId, vmsize, vmrss, vmtext, vmdata,
+ vmlib, vmshare);
+ if (fetch_siginfo)
+ p->siginfo (pnd, blk, ign);
+ /* The real uid value for *this* process is stored at cygheap->user.real_uid
+ but we can't get at the real uid value for any other process, so
+ just fake it as p->uid. Similar for p->gid. */
+ size_t kb_per_page = wincap.allocation_granularity() / 1024;
+ destbuf = (char *) crealloc_abort (destbuf, strlen (cmd) + 320);
+ return __small_sprintf (destbuf, "Name:\t%s\n"
+ "State:\t%c (%s)\n"
+ "Tgid:\t%d\n"
+ "Pid:\t%d\n"
+ "PPid:\t%d\n"
+ "Uid:\t%d %d %d %d\n"
+ "Gid:\t%d %d %d %d\n"
+ "VmSize:\t%8lu kB\n"
+ "VmLck:\t%8lu kB\n"
+ "VmRSS:\t%8lu kB\n"
+ "VmData:\t%8lu kB\n"
+ "VmStk:\t%8lu kB\n"
+ "VmExe:\t%8lu kB\n"
+ "VmLib:\t%8lu kB\n"
+ "SigPnd:\t%016lx\n"
+ "SigBlk:\t%016lx\n"
+ "SigIgn:\t%016lx\n",
+ cmd,
+ state, state_str,
+ p->pgid,
+ p->pid,
+ p->ppid,
+ p->uid, p->uid, p->uid, p->uid,
+ p->gid, p->gid, p->gid, p->gid,
+ vmsize * kb_per_page, 0UL, vmrss * kb_per_page,
+ vmdata * kb_per_page, 0UL, vmtext * kb_per_page,
+ vmlib * kb_per_page,
+ pnd, blk, ign
+ );
+}
+
+static off_t
+format_process_statm (void *data, char *&destbuf)
+{
+ _pinfo *p = (_pinfo *) data;
+ size_t vmsize = 0, vmrss = 0, vmtext = 0, vmdata = 0, vmlib = 0, vmshare = 0;
+
+ if (!get_mem_values (p->dwProcessId, vmsize, vmrss, vmtext, vmdata,
+ vmlib, vmshare) && !(p->process_state & PID_EXITED))
+ return -1; /* Error out unless it's a zombie process */
+
+ destbuf = (char *) crealloc_abort (destbuf, 96);
+ return __small_sprintf (destbuf, "%lu %lu %lu %lu %lu %lu 0\n",
+ vmsize, vmrss, vmshare, vmtext, vmlib, vmdata);
+}
+
+extern "C" {
+ FILE *setmntent (const char *, const char *);
+ struct mntent *getmntent (FILE *);
+};
+
+static off_t
+format_process_mountstuff (void *data, char *&destbuf, bool mountinfo)
+{
+ _pinfo *p = (_pinfo *) data;
+ user_info *u_shared = NULL;
+ HANDLE u_hdl = NULL;
+ off_t len = 0;
+ struct mntent *mnt;
+
+ if (p->uid != myself->uid)
+ {
+ WCHAR sid_string[UNLEN + 1] = L""; /* Large enough for SID */
+
+ cygsid p_sid;
+
+ if (!p_sid.getfrompw (internal_getpwuid (p->uid)))
+ return 0;
+ p_sid.string (sid_string);
+ u_shared = (user_info *) open_shared (sid_string, USER_VERSION, u_hdl,
+ sizeof (user_info), SH_JUSTOPEN,
+ &sec_none_nih);
+ if (!u_shared)
+ return 0;
+ }
+ else
+ u_shared = user_shared;
+ mount_info *mtab = &u_shared->mountinfo;
+
+ /* Store old value of _my_tls.locals here. */
+ int iteration = _my_tls.locals.iteration;
+ unsigned available_drives = _my_tls.locals.available_drives;
+ /* This reinitializes the above values in _my_tls. */
+ setmntent (NULL, NULL);
+ /* Restore iteration immediately since it's not used below. We use the
+ local iteration variable instead*/
+ _my_tls.locals.iteration = iteration;
+
+ for (iteration = 0; (mnt = mtab->getmntent (iteration)); ++iteration)
+ {
+ /* We have no access to the drives mapped into another user session and
+ _my_tls.locals.available_drives contains the mappings of the current
+ user. So, when printing the mount table of another user, we check
+ each cygdrive entry if it's a remote drive. If so, ignore it. */
+ if (iteration >= mtab->nmounts && u_hdl)
+ {
+ WCHAR drive[3] = { (WCHAR) mnt->mnt_fsname[0], L':', L'\0' };
+ disk_type dt = get_disk_type (drive);
+
+ if (dt == DT_SHARE_SMB || dt == DT_SHARE_NFS)
+ continue;
+ }
+ destbuf = (char *) crealloc_abort (destbuf, len
+ + strlen (mnt->mnt_fsname)
+ + strlen (mnt->mnt_dir)
+ + strlen (mnt->mnt_type)
+ + strlen (mnt->mnt_opts)
+ + 30);
+ if (mountinfo)
+ {
+ path_conv pc (mnt->mnt_dir, PC_SYM_NOFOLLOW | PC_POSIX);
+ dev_t dev = pc.exists () ? pc.fs_serial_number () : -1;
+
+ len += __small_sprintf (destbuf + len,
+ "%d %d %d:%d / %s %s - %s %s %s\n",
+ iteration, iteration,
+ major (dev), minor (dev),
+ mnt->mnt_dir, mnt->mnt_opts,
+ mnt->mnt_type, mnt->mnt_fsname,
+ (pc.fs_flags () & FILE_READ_ONLY_VOLUME)
+ ? "ro" : "rw");
+ }
+ else
+ len += __small_sprintf (destbuf + len, "%s %s %s %s %d %d\n",
+ mnt->mnt_fsname, mnt->mnt_dir, mnt->mnt_type,
+ mnt->mnt_opts, mnt->mnt_freq, mnt->mnt_passno);
+ }
+
+ /* Restore available_drives */
+ _my_tls.locals.available_drives = available_drives;
+
+ if (u_hdl) /* Only not-NULL if open_shared has been called. */
+ {
+ UnmapViewOfFile (u_shared);
+ CloseHandle (u_hdl);
+ }
+ return len;
+}
+
+static off_t
+format_process_mounts (void *data, char *&destbuf)
+{
+ return format_process_mountstuff (data, destbuf, false);
+}
+
+static off_t
+format_process_mountinfo (void *data, char *&destbuf)
+{
+ return format_process_mountstuff (data, destbuf, true);
+}
+
+int
+get_process_state (DWORD dwProcessId)
+{
+ /* This isn't really heavy magic - just go through the processes' threads
+ one by one and return a value accordingly. Errors are silently ignored. */
+ NTSTATUS status;
+ PSYSTEM_PROCESS_INFORMATION p, sp;
+ ULONG n = 0x4000;
+ int state =' ';
+
+ p = (PSYSTEM_PROCESS_INFORMATION) malloc (n);
+ if (!p)
+ return state;
+ while (true)
+ {
+ status = NtQuerySystemInformation (SystemProcessInformation,
+ (PVOID) p, n, NULL);
+ if (status != STATUS_INFO_LENGTH_MISMATCH)
+ break;
+ n <<= 1;
+ PSYSTEM_PROCESS_INFORMATION new_p = (PSYSTEM_PROCESS_INFORMATION) realloc (p, n);
+ if (!new_p)
+ goto out;
+ p = new_p;
+ }
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtQuerySystemInformation: status %y, %u",
+ status, RtlNtStatusToDosError (status));
+ goto out;
+ }
+ state = 'Z';
+ sp = p;
+ for (;;)
+ {
+ if ((DWORD) (uintptr_t) sp->UniqueProcessId == dwProcessId)
+ {
+ SYSTEM_THREADS *st;
+ st = &sp->Threads[0];
+ state = 'S';
+ for (unsigned i = 0; i < sp->NumberOfThreads; i++)
+ {
+ /* FIXME: at some point we should consider generating 'O' */
+ if (st->State == StateRunning ||
+ st->State == StateReady)
+ {
+ state = 'R';
+ goto out;
+ }
+ st++;
+ }
+ break;
+ }
+ if (!sp->NextEntryOffset)
+ break;
+ sp = (PSYSTEM_PROCESS_INFORMATION) ((char *) sp + sp->NextEntryOffset);
+ }
+out:
+ free (p);
+ return state;
+}
+
+static bool
+get_mem_values (DWORD dwProcessId, size_t &vmsize, size_t &vmrss,
+ size_t &vmtext, size_t &vmdata, size_t &vmlib, size_t &vmshare)
+{
+ bool res = false;
+ NTSTATUS status;
+ HANDLE hProcess;
+ VM_COUNTERS vmc;
+ PMEMORY_WORKING_SET_LIST p;
+ SIZE_T n = 0x4000, length;
+ const size_t page_scale = wincap.allocation_granularity()
+ / wincap.page_size();
+
+ /* This appears to work despite MSDN claiming that QueryWorkingSet requires
+ PROCESS_QUERY_INFORMATION *and* PROCESS_VM_READ. Since we're trying to do
+ everything with least perms, we stick to PROCESS_QUERY_INFORMATION only
+ unless this changes in Windows for some reason. */
+ hProcess = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
+ if (hProcess == NULL)
+ {
+ __seterrno ();
+ debug_printf ("OpenProcess, %E");
+ return false;
+ }
+ p = (PMEMORY_WORKING_SET_LIST) malloc (n);
+ if (!p)
+ goto out;
+ while (true)
+ {
+ status = NtQueryVirtualMemory (hProcess, 0, MemoryWorkingSetList,
+ (PVOID) p, n,
+ (length = (SIZE_T) -1, &length));
+ if (status != STATUS_INFO_LENGTH_MISMATCH)
+ break;
+ n <<= 1;
+ PMEMORY_WORKING_SET_LIST new_p = (PMEMORY_WORKING_SET_LIST)
+ realloc (p, n);
+ if (!new_p)
+ goto out;
+ p = new_p;
+ }
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtQueryVirtualMemory: status %y", status);
+ if (status == STATUS_PROCESS_IS_TERMINATING)
+ {
+ vmsize = vmrss = vmtext = vmdata = vmlib = vmshare = 0;
+ res = true;
+ }
+ else
+ __seterrno_from_nt_status (status);
+ goto out;
+ }
+ for (unsigned long i = 0; i < p->NumberOfPages; i++)
+ {
+ ++vmrss;
+ unsigned flags = p->WorkingSetList[i] & 0x0FFF;
+ if ((flags & (WSLE_PAGE_EXECUTE | WSLE_PAGE_SHAREABLE))
+ == (WSLE_PAGE_EXECUTE | WSLE_PAGE_SHAREABLE))
+ ++vmlib;
+ else if (flags & WSLE_PAGE_SHAREABLE)
+ ++vmshare;
+ else if (flags & WSLE_PAGE_EXECUTE)
+ ++vmtext;
+ else
+ ++vmdata;
+ }
+ status = NtQueryInformationProcess (hProcess, ProcessVmCounters, (PVOID) &vmc,
+ sizeof vmc, NULL);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtQueryInformationProcess: status %y", status);
+ __seterrno_from_nt_status (status);
+ goto out;
+ }
+ vmsize = vmc.PagefileUsage / wincap.page_size ();
+ /* Return number of Cygwin pages. Page size in Cygwin is equivalent
+ to Windows allocation_granularity. */
+ vmsize = howmany (vmsize, page_scale);
+ vmrss = howmany (vmrss, page_scale);
+ vmshare = howmany (vmshare, page_scale);
+ vmtext = howmany (vmtext, page_scale);
+ vmlib = howmany (vmlib, page_scale);
+ vmdata = howmany (vmdata, page_scale);
+ res = true;
+out:
+ free (p);
+ CloseHandle (hProcess);
+ return res;
+}
diff --git a/winsup/cygwin/fhandler/process_fd.cc b/winsup/cygwin/fhandler/process_fd.cc
new file mode 100644
index 000000000..d81495103
--- /dev/null
+++ b/winsup/cygwin/fhandler/process_fd.cc
@@ -0,0 +1,163 @@
+/* fhandler_process_fd.cc: fhandler for /proc/<pid>/fd/<desc> operations
+
+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 "path.h"
+#include "fhandler.h"
+#include "fhandler_virtual.h"
+#include "pinfo.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "tls_pbuf.h"
+
+fhandler_base *
+fhandler_process_fd::fetch_fh (HANDLE &out_hdl, uint32_t flags)
+{
+ const char *path;
+ char *e;
+ int fd;
+ HANDLE proc;
+ HANDLE hdl = NULL;
+ path_conv pc;
+
+ path = get_name () + proc_len + 1;
+ pid = strtoul (path, &e, 10);
+ path = e + 4;
+ fd = strtoul (path, &e, 10);
+
+ out_hdl = NULL;
+ if (pid == myself->pid)
+ {
+ cygheap_fdget cfd (fd, true);
+ if (cfd < 0)
+ return NULL;
+ if ((flags & FFH_LINKAT)
+ && (cfd->get_flags () & (O_TMPFILE | O_EXCL)) == (O_TMPFILE | O_EXCL))
+ {
+ set_errno (ENOENT);
+ return NULL;
+ }
+ proc = GetCurrentProcess ();
+ pc << cfd->pc;
+ hdl = cfd->get_handle ();
+ }
+ else
+ {
+ pinfo p (pid);
+ if (!p)
+ {
+ set_errno (ENOENT);
+ return NULL;
+ }
+ proc = OpenProcess (PROCESS_DUP_HANDLE, false, p->dwProcessId);
+ if (!proc)
+ {
+ __seterrno ();
+ return NULL;
+ }
+ size_t size;
+ void *buf = p->file_pathconv (fd, flags, size);
+ if (size == 0)
+ {
+ set_errno (ENOENT);
+ CloseHandle (proc);
+ return NULL;
+ }
+ hdl = pc.deserialize (buf);
+ }
+ if (hdl == NULL)
+ {
+ if (proc != GetCurrentProcess ())
+ CloseHandle (proc);
+ set_errno (EACCES);
+ return NULL;
+ }
+ BOOL ret = DuplicateHandle (proc, hdl, GetCurrentProcess (), &hdl,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ if (proc != GetCurrentProcess ())
+ CloseHandle (proc);
+ if (!ret)
+ {
+ __seterrno ();
+ CloseHandle (hdl);
+ return NULL;
+ }
+ /* relative path? This happens for special types like pipes and sockets. */
+ if (*pc.get_posix () != '/')
+ {
+ tmp_pathbuf tp;
+ char *fullpath = tp.c_get ();
+
+ stpcpy (stpncpy (fullpath, get_name (), path - get_name ()),
+ pc.get_posix ());
+ pc.set_posix (fullpath);
+ }
+ fhandler_base *fh = build_fh_pc (pc);
+ if (!fh)
+ {
+ CloseHandle (hdl);
+ return NULL;
+ }
+ out_hdl = hdl;
+ return fh;
+}
+
+fhandler_base *
+fhandler_process_fd::fd_reopen (int flags, mode_t mode)
+{
+ fhandler_base *fh;
+ HANDLE hdl;
+
+ fh = fetch_fh (hdl, 0);
+ if (!fh)
+ return NULL;
+ fh->set_handle (hdl);
+ int ret = fh->open_with_arch (flags, mode);
+ CloseHandle (hdl);
+ if (!ret)
+ {
+ delete fh;
+ fh = NULL;
+ }
+ return fh;
+}
+
+int
+fhandler_process_fd::fstat (struct stat *statbuf)
+{
+ if (!pc.follow_fd_symlink ())
+ return fhandler_process::fstat (statbuf);
+
+ fhandler_base *fh;
+ HANDLE hdl;
+
+ fh = fetch_fh (hdl, 0);
+ if (!fh)
+ return -1;
+ fh->set_handle (hdl);
+ int ret = fh->fstat (statbuf);
+ CloseHandle (hdl);
+ delete fh;
+ return ret;
+}
+
+int
+fhandler_process_fd::link (const char *newpath)
+{
+ fhandler_base *fh;
+ HANDLE hdl;
+
+ fh = fetch_fh (hdl, FFH_LINKAT);
+ if (!fh)
+ return -1;
+ fh->set_handle (hdl);
+ int ret = fh->link (newpath);
+ CloseHandle (hdl);
+ delete fh;
+ return ret;
+}
diff --git a/winsup/cygwin/fhandler/procnet.cc b/winsup/cygwin/fhandler/procnet.cc
new file mode 100644
index 000000000..d512887d5
--- /dev/null
+++ b/winsup/cygwin/fhandler/procnet.cc
@@ -0,0 +1,262 @@
+/* fhandler_procnet.cc: fhandler for /proc/net virtual filesystem
+
+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. */
+
+#define __INSIDE_CYGWIN_NET__
+#define USE_SYS_TYPES_FD_SET
+#include "winsup.h"
+/* 2014-04-24: Current Mingw headers define sockaddr_in6 using u_long (8 byte)
+ because a redefinition for LP64 systems is missing. This leads to a wrong
+ definition and size of sockaddr_in6 when building with winsock headers. */
+#undef u_long
+#define u_long __ms_u_long
+#include <w32api/ws2tcpip.h>
+#include <w32api/iphlpapi.h>
+#include <asm/byteorder.h>
+#include <stdio.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "fhandler_virtual.h"
+#include "dtable.h"
+
+bool get_adapters_addresses (PIP_ADAPTER_ADDRESSES *pa0, ULONG family);
+
+static off_t format_procnet_ifinet6 (void *, char *&);
+
+static const virt_tab_t procnet_tab[] =
+{
+ { _VN ("."), FH_PROCNET, virt_directory, NULL },
+ { _VN (".."), FH_PROCNET, virt_directory, NULL },
+ { _VN ("if_inet6"), FH_PROCNET, virt_file, format_procnet_ifinet6 },
+ { NULL, 0, FH_NADA, virt_none, NULL }
+};
+
+static const int PROCNET_LINK_COUNT =
+ (sizeof (procnet_tab) / sizeof (virt_tab_t)) - 1;
+
+virtual_ftype_t
+fhandler_procnet::exists ()
+{
+ const char *path = get_name ();
+ debug_printf ("exists (%s)", path);
+ path += proc_len + 1;
+ while (*path != 0 && !isdirsep (*path))
+ path++;
+ if (*path == 0)
+ return virt_rootdir;
+
+ virt_tab_t *entry = virt_tab_search (path + 1, false, procnet_tab,
+ PROCNET_LINK_COUNT);
+ if (entry)
+ {
+ if (entry->type == virt_file && !get_adapters_addresses (NULL, AF_INET6))
+ return virt_none;
+ fileid = entry - procnet_tab;
+ return entry->type;
+ }
+ return virt_none;
+}
+
+fhandler_procnet::fhandler_procnet ():
+ fhandler_proc ()
+{
+}
+
+int
+fhandler_procnet::fstat (struct stat *buf)
+{
+ fhandler_base::fstat (buf);
+ buf->st_mode &= ~_IFMT & NO_W;
+ int file_type = exists ();
+ switch (file_type)
+ {
+ case virt_none:
+ set_errno (ENOENT);
+ return -1;
+ case virt_directory:
+ case virt_rootdir:
+ buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
+ buf->st_nlink = 2;
+ return 0;
+ case virt_file:
+ default:
+ buf->st_mode |= S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
+ return 0;
+ }
+}
+
+int
+fhandler_procnet::readdir (DIR *dir, dirent *de)
+{
+ int res = ENMFILE;
+ if (dir->__d_position >= PROCNET_LINK_COUNT)
+ goto out;
+ if (procnet_tab[dir->__d_position].type == virt_file
+ && !get_adapters_addresses (NULL, AF_INET6))
+ goto out;
+ strcpy (de->d_name, procnet_tab[dir->__d_position].name);
+ de->d_type = virt_ftype_to_dtype (procnet_tab[dir->__d_position].type);
+ dir->__d_position++;
+ dir->__flags |= dirent_saw_dot | dirent_saw_dot_dot;
+ res = 0;
+out:
+ syscall_printf ("%d = readdir(%p, %p) (%s)", res, dir, de, de->d_name);
+ return res;
+}
+
+int
+fhandler_procnet::open (int flags, mode_t mode)
+{
+ int res = fhandler_virtual::open (flags, mode);
+ if (!res)
+ goto out;
+
+ nohandle (true);
+
+ const char *path;
+ path = get_name () + proc_len + 1;
+ while (*path != 0 && !isdirsep (*path))
+ path++;
+
+ if (*path == 0)
+ {
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ res = 0;
+ goto out;
+ }
+ else if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ res = 0;
+ goto out;
+ }
+ else
+ {
+ diropen = true;
+ goto success;
+ }
+ }
+
+ virt_tab_t *entry;
+ entry = virt_tab_search (path + 1, true, procnet_tab, PROCNET_LINK_COUNT);
+ if (!entry)
+ {
+ set_errno ((flags & O_CREAT) ? EROFS : ENOENT);
+ res = 0;
+ goto out;
+ }
+ if (flags & O_WRONLY)
+ {
+ set_errno (EROFS);
+ res = 0;
+ goto out;
+ }
+
+ fileid = entry - procnet_tab;
+ if (!fill_filebuf ())
+ {
+ res = 0;
+ goto out;
+ }
+
+ if (flags & O_APPEND)
+ position = filesize;
+ else
+ position = 0;
+
+success:
+ res = 1;
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ set_open_status ();
+out:
+ syscall_printf ("%d = fhandler_proc::open(%y, 0%o)", res, flags, mode);
+ return res;
+}
+
+bool
+fhandler_procnet::fill_filebuf ()
+{
+ if (procnet_tab[fileid].format_func)
+ {
+ filesize = procnet_tab[fileid].format_func (NULL, filebuf);
+ return true;
+ }
+ return false;
+}
+
+/* Return the same scope values as Linux. */
+static unsigned int
+get_scope (struct in6_addr *addr)
+{
+ if (IN6_IS_ADDR_LOOPBACK (addr))
+ return 0x10;
+ if (IN6_IS_ADDR_LINKLOCAL (addr))
+ return 0x20;
+ if (IN6_IS_ADDR_SITELOCAL (addr))
+ return 0x40;
+ if (IN6_IS_ADDR_V4COMPAT (addr))
+ return 0x80;
+ return 0x0;
+}
+
+/* Convert DAD state into Linux compatible values. */
+static unsigned int dad_to_flags[] =
+{
+ 0x02, /* Invalid -> NODAD */
+ 0x40, /* Tentative -> TENTATIVE */
+ 0xc0, /* Duplicate -> PERMANENT | TENTATIVE */
+ 0x20, /* Deprecated -> DEPRECATED */
+ 0x80 /* Preferred -> PERMANENT */
+};
+
+static off_t
+format_procnet_ifinet6 (void *, char *&filebuf)
+{
+ PIP_ADAPTER_ADDRESSES pa0 = NULL, pap;
+ PIP_ADAPTER_UNICAST_ADDRESS pua;
+ ULONG alloclen;
+ off_t filesize = 0;
+
+ if (!get_adapters_addresses (&pa0, AF_INET6))
+ goto out;
+ alloclen = 0;
+ for (pap = pa0; pap; pap = pap->Next)
+ for (pua = pap->FirstUnicastAddress; pua; pua = pua->Next)
+ alloclen += 100;
+ if (!alloclen)
+ goto out;
+ filebuf = (char *) crealloc (filebuf, alloclen);
+ if (!filebuf)
+ goto out;
+ for (pap = pa0; pap; pap = pap->Next)
+ for (pua = pap->FirstUnicastAddress; pua; pua = pua->Next)
+ {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)
+ pua->Address.lpSockaddr;
+ for (int i = 0; i < 8; ++i)
+ /* __small_sprintf generates upper-case hex. */
+ filesize += sprintf (filebuf + filesize, "%04x",
+ ntohs (sin6->sin6_addr.s6_addr16[i]));
+ filebuf[filesize++] = ' ';
+ filesize += sprintf (filebuf + filesize,
+ "%02lx %02x %02x %02x %s\n",
+ (long) pap->Ipv6IfIndex,
+ pua->OnLinkPrefixLength,
+ get_scope (&((struct sockaddr_in6 *)
+ pua->Address.lpSockaddr)->sin6_addr),
+ dad_to_flags [pua->DadState],
+ pap->AdapterName);
+ }
+
+out:
+ if (pa0)
+ free (pa0);
+ return filesize;
+}
diff --git a/winsup/cygwin/fhandler/procsys.cc b/winsup/cygwin/fhandler/procsys.cc
new file mode 100644
index 000000000..cd1d35984
--- /dev/null
+++ b/winsup/cygwin/fhandler/procsys.cc
@@ -0,0 +1,486 @@
+/* fhandler_procsys.cc: fhandler for native NT namespace.
+
+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 <stdlib.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include <winioctl.h>
+#include "ntdll.h"
+#include "tls_pbuf.h"
+
+#include <dirent.h>
+
+/* Path of the /proc/sys filesystem */
+const char procsys[] = "/proc/sys";
+const size_t procsys_len = sizeof (procsys) - 1;
+
+#define mk_unicode_path(p) \
+ WCHAR namebuf[strlen (get_name ()) + 1]; \
+ { \
+ const char *from; \
+ PWCHAR to; \
+ for (to = namebuf, from = get_name () + procsys_len; *from; \
+ to++, from++) \
+ /* The NT device namespace is ASCII only. */ \
+ *to = (*from == '/') ? L'\\' : (WCHAR) *from; \
+ if (to == namebuf) \
+ *to++ = L'\\'; \
+ *to = L'\0'; \
+ RtlInitUnicodeString ((p), namebuf); \
+ }
+
+virtual_ftype_t
+fhandler_procsys::exists (struct stat *buf)
+{
+ UNICODE_STRING path;
+ UNICODE_STRING dir;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ NTSTATUS status;
+ HANDLE h;
+ FILE_BASIC_INFORMATION fbi;
+ bool internal = false;
+ bool desperate_parent_check = false;
+ /* Default device type is character device. */
+ virtual_ftype_t file_type = virt_chr;
+
+ if (strlen (get_name ()) == procsys_len)
+ return virt_rootdir;
+ mk_unicode_path (&path);
+
+ /* Try to open parent dir. If it works, the object is definitely
+ an object within the internal namespace. We don't need to test
+ it for being a file or dir on the filesystem anymore. If the
+ error is STATUS_OBJECT_TYPE_MISMATCH, we know that the file
+ itself is external. Otherwise we don't know. */
+ RtlSplitUnicodePath (&path, &dir, NULL);
+ /* RtlSplitUnicodePath preserves the trailing backslash in dir. Don't
+ preserve it to open the dir, unless it's the object root. */
+ if (dir.Length > sizeof (WCHAR))
+ dir.Length -= sizeof (WCHAR);
+ InitializeObjectAttributes (&attr, &dir, OBJ_CASE_INSENSITIVE, NULL, NULL);
+ status = NtOpenDirectoryObject (&h, DIRECTORY_QUERY, &attr);
+ debug_printf ("NtOpenDirectoryObject: %y", status);
+ if (NT_SUCCESS (status))
+ {
+ internal = true;
+ NtClose (h);
+ }
+
+ /* First check if the object is a symlink. */
+ InitializeObjectAttributes (&attr, &path, OBJ_CASE_INSENSITIVE, NULL, NULL);
+ status = NtOpenSymbolicLinkObject (&h, READ_CONTROL | SYMBOLIC_LINK_QUERY,
+ &attr);
+ debug_printf ("NtOpenSymbolicLinkObject: %y", status);
+ if (NT_SUCCESS (status))
+ {
+ /* If requested, check permissions. */
+ if (buf)
+ get_object_attribute (h, &buf->st_uid, &buf->st_gid, &buf->st_mode);
+ NtClose (h);
+ return virt_symlink;
+ }
+ else if (status == STATUS_ACCESS_DENIED)
+ return virt_symlink;
+ /* Then check if it's an object directory. */
+ status = NtOpenDirectoryObject (&h, READ_CONTROL | DIRECTORY_QUERY, &attr);
+ debug_printf ("NtOpenDirectoryObject: %y", status);
+ if (NT_SUCCESS (status))
+ {
+ /* If requested, check permissions. */
+ if (buf)
+ get_object_attribute (h, &buf->st_uid, &buf->st_gid, &buf->st_mode);
+ NtClose (h);
+ return virt_directory;
+ }
+ else if (status == STATUS_ACCESS_DENIED)
+ return virt_directory;
+ /* Next try to open as file/device. */
+ status = NtOpenFile (&h, READ_CONTROL | FILE_READ_ATTRIBUTES, &attr, &io,
+ FILE_SHARE_VALID_FLAGS, FILE_OPEN_FOR_BACKUP_INTENT);
+ debug_printf ("NtOpenFile: %y", status);
+ /* Name is invalid, that's nothing. */
+ if (status == STATUS_OBJECT_NAME_INVALID)
+ return virt_none;
+ /* If no media is found, or we get this dreaded sharing violation, let
+ the caller immediately try again as normal file. */
+ if (status == STATUS_NO_MEDIA_IN_DEVICE
+ || status == STATUS_SHARING_VIOLATION)
+ return virt_fsfile; /* Just try again as normal file. */
+ /* If file or path can't be found, let caller try again as normal file. */
+ if (status == STATUS_OBJECT_PATH_NOT_FOUND
+ || status == STATUS_OBJECT_NAME_NOT_FOUND)
+ return virt_fsfile;
+ /* Check for pipe errors, which make a good hint... */
+ if (status >= STATUS_PIPE_NOT_AVAILABLE && status <= STATUS_PIPE_BUSY)
+ return virt_pipe;
+ if (status == STATUS_ACCESS_DENIED && !internal)
+ {
+ /* Check if this is just some file or dir on a real FS to circumvent
+ most permission problems. Don't try that on internal objects,
+ since NtQueryAttributesFile might crash the machine if the underlying
+ driver is badly written. */
+ status = NtQueryAttributesFile (&attr, &fbi);
+ debug_printf ("NtQueryAttributesFile: %y", status);
+ if (NT_SUCCESS (status))
+ return (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ ? virt_fsdir : virt_fsfile;
+ /* Ok, so we're desperate and the file still maybe on some filesystem.
+ To check this, we now split the path until we can finally access any
+ of the parent's. Then we fall through to check the parent type. In
+ contrast to the first parent check, we now check explicitely with
+ trailing backslash. This will fail for directories in the internal
+ namespace, so we won't accidentally test those. */
+ dir = path;
+ InitializeObjectAttributes (&attr, &dir, OBJ_CASE_INSENSITIVE,
+ NULL, NULL);
+ do
+ {
+ RtlSplitUnicodePath (&dir, &dir, NULL);
+ status = NtOpenFile (&h, READ_CONTROL | FILE_READ_ATTRIBUTES,
+ &attr, &io, FILE_SHARE_VALID_FLAGS,
+ FILE_OPEN_FOR_BACKUP_INTENT);
+ debug_printf ("NtOpenDirectoryObject: %y", status);
+ if (dir.Length > sizeof (WCHAR))
+ dir.Length -= sizeof (WCHAR);
+ }
+ while (dir.Length > sizeof (WCHAR) && !NT_SUCCESS (status));
+ desperate_parent_check = true;
+ }
+ if (NT_SUCCESS (status))
+ {
+ FILE_FS_DEVICE_INFORMATION ffdi;
+
+ /* If requested, check permissions. If this is a parent handle from
+ the above desperate parent check, skip. */
+ if (buf && !desperate_parent_check)
+ get_object_attribute (h, &buf->st_uid, &buf->st_gid, &buf->st_mode);
+
+ /* Check for the device type. */
+ status = NtQueryVolumeInformationFile (h, &io, &ffdi, sizeof ffdi,
+ FileFsDeviceInformation);
+ debug_printf ("NtQueryVolumeInformationFile: %y", status);
+ /* Don't call NtQueryInformationFile unless we know it's a safe type.
+ The call is known to crash machines, if the underlying driver is
+ badly written. */
+ if (NT_SUCCESS (status))
+ {
+ if (ffdi.DeviceType == FILE_DEVICE_NAMED_PIPE)
+ file_type = internal ? virt_blk : virt_pipe;
+ else if (ffdi.DeviceType == FILE_DEVICE_DISK
+ || ffdi.DeviceType == FILE_DEVICE_CD_ROM
+ || ffdi.DeviceType == FILE_DEVICE_VIRTUAL_DISK)
+ {
+ /* Check for file attributes. If we get them, we peeked
+ into a real FS through /proc/sys. */
+ status = NtQueryInformationFile (h, &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ debug_printf ("NtQueryInformationFile: %y", status);
+ if (!NT_SUCCESS (status))
+ file_type = virt_blk;
+ else
+ file_type = (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ ? virt_fsdir : virt_fsfile;
+ }
+ }
+ NtClose (h);
+ }
+ /* That's it. Return type we found above. */
+ return file_type;
+}
+
+virtual_ftype_t
+fhandler_procsys::exists ()
+{
+ return exists (NULL);
+}
+
+fhandler_procsys::fhandler_procsys ():
+ fhandler_virtual ()
+{
+}
+
+#define UNREADABLE_SYMLINK_CONTENT "<access denied>"
+
+bool
+fhandler_procsys::fill_filebuf ()
+{
+ char *fnamep;
+ UNICODE_STRING path, target;
+ OBJECT_ATTRIBUTES attr;
+ NTSTATUS status;
+ HANDLE h;
+ tmp_pathbuf tp;
+ size_t len;
+
+ mk_unicode_path (&path);
+ if (path.Buffer[path.Length / sizeof (WCHAR) - 1] == L'\\')
+ path.Length -= sizeof (WCHAR);
+ InitializeObjectAttributes (&attr, &path, OBJ_CASE_INSENSITIVE, NULL, NULL);
+ status = NtOpenSymbolicLinkObject (&h, SYMBOLIC_LINK_QUERY, &attr);
+ if (!NT_SUCCESS (status))
+ goto unreadable;
+ RtlInitEmptyUnicodeString (&target, tp.w_get (),
+ (NT_MAX_PATH - 1) * sizeof (WCHAR));
+ status = NtQuerySymbolicLinkObject (h, &target, NULL);
+ NtClose (h);
+ if (!NT_SUCCESS (status))
+ goto unreadable;
+ len = sys_wcstombs (NULL, 0, target.Buffer, target.Length / sizeof (WCHAR));
+ filebuf = (char *) crealloc_abort (filebuf, procsys_len + len + 1);
+ sys_wcstombs (fnamep = stpcpy (filebuf, procsys), len + 1, target.Buffer,
+ target.Length / sizeof (WCHAR));
+ while ((fnamep = strchr (fnamep, '\\')))
+ *fnamep = '/';
+ return true;
+
+unreadable:
+ filebuf = (char *) crealloc_abort (filebuf,
+ sizeof (UNREADABLE_SYMLINK_CONTENT));
+ strcpy (filebuf, UNREADABLE_SYMLINK_CONTENT);
+ return false;
+}
+
+int
+fhandler_procsys::fstat (struct stat *buf)
+{
+ const char *path = get_name ();
+ debug_printf ("fstat (%s)", path);
+
+ fhandler_base::fstat (buf);
+ /* Best bet. */
+ buf->st_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
+ buf->st_uid = 544;
+ buf->st_gid = 18;
+ buf->st_dev = buf->st_rdev = dev ();
+ buf->st_ino = get_ino ();
+ switch (exists (buf))
+ {
+ case virt_directory:
+ case virt_rootdir:
+ case virt_fsdir:
+ buf->st_mode |= S_IFDIR;
+ if (buf->st_mode & S_IRUSR)
+ buf->st_mode |= S_IXUSR;
+ if (buf->st_mode & S_IRGRP)
+ buf->st_mode |= S_IXGRP;
+ if (buf->st_mode & S_IROTH)
+ buf->st_mode |= S_IXOTH;
+ break;
+ case virt_file:
+ case virt_fsfile:
+ buf->st_mode |= S_IFREG;
+ break;
+ case virt_symlink:
+ buf->st_mode |= S_IFLNK;
+ break;
+ case virt_pipe:
+ buf->st_mode |= S_IFIFO;
+ break;
+ case virt_socket:
+ buf->st_mode |= S_IFSOCK;
+ break;
+ case virt_chr:
+ buf->st_mode |= S_IFCHR;
+ break;
+ case virt_blk:
+ buf->st_mode |= S_IFBLK;
+ break;
+ default:
+ set_errno (ENOENT);
+ return -1;
+ }
+ return 0;
+}
+
+DIR *
+fhandler_procsys::opendir (int fd)
+{
+ UNICODE_STRING path;
+ OBJECT_ATTRIBUTES attr;
+ NTSTATUS status;
+ HANDLE dir_hdl;
+ DIR *dir;
+
+ mk_unicode_path (&path);
+ InitializeObjectAttributes (&attr, &path, OBJ_CASE_INSENSITIVE, NULL, NULL);
+ status = NtOpenDirectoryObject (&dir_hdl, DIRECTORY_QUERY, &attr);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return NULL;
+ }
+
+ void *dbi_buf = NULL;
+ ULONG size = 65536;
+ ULONG context = 0;
+ int iter;
+ for (iter = 0; iter <= 3; ++iter) /* Allows for a 512K buffer */
+ {
+ void *new_buf = realloc (dbi_buf, size);
+ if (!new_buf)
+ goto err;
+ dbi_buf = new_buf;
+ status = NtQueryDirectoryObject (dir_hdl, dbi_buf, size, FALSE, TRUE,
+ &context, NULL);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ goto err;
+ }
+ if (status != STATUS_MORE_ENTRIES)
+ break;
+ size <<= 1;
+ }
+ if (iter > 3)
+ {
+ __seterrno_from_nt_status (STATUS_INSUFFICIENT_RESOURCES);
+ goto err;
+ }
+ if (!(dir = fhandler_virtual::opendir (fd)))
+ goto err;
+ /* Note that dir->__handle points to the buffer, it does NOT contain an
+ actual handle! */
+ dir->__handle = dbi_buf;
+ /* dir->__d_internal contains the number of objects returned in the buffer. */
+ dir->__d_internal = context;
+ return dir;
+
+err:
+ NtClose (dir_hdl);
+ free (dbi_buf);
+ return NULL;
+}
+
+int
+fhandler_procsys::readdir (DIR *dir, dirent *de)
+{
+ PDIRECTORY_BASIC_INFORMATION dbi;
+ int res = EBADF;
+
+ if (dir->__handle != INVALID_HANDLE_VALUE)
+ {
+ dbi = ((PDIRECTORY_BASIC_INFORMATION) dir->__handle);
+ dbi += dir->__d_position;
+ if (dir->__d_position >= (__int32_t) dir->__d_internal
+ || dbi->ObjectName.Length == 0)
+ res = ENMFILE;
+ else
+ {
+ sys_wcstombs (de->d_name, NAME_MAX + 1, dbi->ObjectName.Buffer,
+ dbi->ObjectName.Length / sizeof (WCHAR));
+ de->d_ino = hash_path_name (get_ino (), de->d_name);
+ if (RtlEqualUnicodeString (&dbi->ObjectTypeName, &ro_u_natdir, FALSE))
+ de->d_type = DT_DIR;
+ else if (RtlEqualUnicodeString (&dbi->ObjectTypeName, &ro_u_natsyml,
+ FALSE))
+ de->d_type = DT_LNK;
+ else if (!RtlEqualUnicodeString (&dbi->ObjectTypeName, &ro_u_natdev,
+ FALSE))
+ de->d_type = DT_CHR;
+ else /* Can't nail down "Device" objects without further testing. */
+ de->d_type = DT_UNKNOWN;
+ ++dir->__d_position;
+ res = 0;
+ }
+ }
+ syscall_printf ("%d = readdir(%p, %p)", res, dir, de);
+ return res;
+}
+
+long
+fhandler_procsys::telldir (DIR *dir)
+{
+ return dir->__d_position;
+}
+
+void
+fhandler_procsys::seekdir (DIR *dir, long pos)
+{
+ if (pos < 0)
+ dir->__d_position = 0;
+ else if (pos > (__int32_t) dir->__d_internal)
+ dir->__d_position = (__int32_t) dir->__d_internal;
+ else
+ dir->__d_position = pos;
+}
+
+int
+fhandler_procsys::closedir (DIR *dir)
+{
+ if (dir->__handle != INVALID_HANDLE_VALUE)
+ {
+ free (dir->__handle);
+ dir->__handle = INVALID_HANDLE_VALUE;
+ }
+ return fhandler_virtual::closedir (dir);
+}
+
+void
+fhandler_procsys::read (void *ptr, size_t& len)
+{
+ fhandler_base::raw_read (ptr, len);
+}
+
+ssize_t
+fhandler_procsys::write (const void *ptr, size_t len)
+{
+ return fhandler_base::raw_write (ptr, len);
+}
+
+int
+fhandler_procsys::open (int flags, mode_t mode)
+{
+ int res = 0;
+
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ set_errno (EINVAL);
+ else
+ {
+ switch (exists (NULL))
+ {
+ case virt_directory:
+ case virt_rootdir:
+ if ((flags & O_ACCMODE) != O_RDONLY)
+ set_errno (EISDIR);
+ else
+ {
+ nohandle (true);
+ res = 1;
+ }
+ break;
+ case virt_none:
+ set_errno (ENOENT);
+ break;
+ default:
+ res = fhandler_base::open (flags, mode);
+ break;
+ }
+ }
+ syscall_printf ("%d = fhandler_procsys::open(%p, 0%o)", res, flags, mode);
+ return res;
+}
+
+int
+fhandler_procsys::close ()
+{
+ if (!nohandle ())
+ NtClose (get_handle ());
+ return fhandler_virtual::close ();
+}
+#if 0
+int
+fhandler_procsys::ioctl (unsigned int cmd, void *)
+{
+}
+#endif
diff --git a/winsup/cygwin/fhandler/procsysvipc.cc b/winsup/cygwin/fhandler/procsysvipc.cc
new file mode 100644
index 000000000..453d3b4f7
--- /dev/null
+++ b/winsup/cygwin/fhandler/procsysvipc.cc
@@ -0,0 +1,389 @@
+/* fhandler_procsysvipc.cc: fhandler for /proc/sysvipc virtual filesystem
+
+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 <stdlib.h>
+#include <stdio.h>
+#include <sys/cygwin.h>
+#include "cygerrno.h"
+#include "cygserver.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "fhandler_virtual.h"
+#include "pinfo.h"
+#include "shared_info.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "ntdll.h"
+#include "cygtls.h"
+#include "tls_pbuf.h"
+#include <sys/param.h>
+#include <ctype.h>
+
+#define _LIBC
+#include <dirent.h>
+
+#define _KERNEL
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+
+static off_t format_procsysvipc_msg (void *, char *&);
+static off_t format_procsysvipc_sem (void *, char *&);
+static off_t format_procsysvipc_shm (void *, char *&);
+
+static const virt_tab_t procsysvipc_tab[] =
+{
+ { _VN ("."), FH_PROCSYSVIPC, virt_directory, NULL },
+ { _VN (".."), FH_PROCSYSVIPC, virt_directory, NULL },
+ { _VN ("msg"), FH_PROCSYSVIPC, virt_file, format_procsysvipc_msg },
+ { _VN ("sem"), FH_PROCSYSVIPC, virt_file, format_procsysvipc_sem },
+ { _VN ("shm"), FH_PROCSYSVIPC, virt_file, format_procsysvipc_shm },
+ { NULL, 0, FH_NADA, virt_none, NULL }
+};
+
+static const int PROCSYSVIPC_LINK_COUNT =
+ (sizeof (procsysvipc_tab) / sizeof (virt_tab_t)) - 1;
+
+virtual_ftype_t
+fhandler_procsysvipc::exists ()
+{
+ const char *path = get_name ();
+ debug_printf ("exists (%s)", path);
+ path += proc_len + 1;
+ while (*path != 0 && !isdirsep (*path))
+ path++;
+ if (*path == 0)
+ return virt_rootdir;
+
+ virt_tab_t *entry = virt_tab_search (path + 1, true, procsysvipc_tab,
+ PROCSYSVIPC_LINK_COUNT);
+
+ cygserver_init ();
+
+ if (entry)
+ {
+ if (entry->type == virt_file)
+ {
+ if (cygserver_running != CYGSERVER_OK)
+ return virt_none;
+ }
+ fileid = entry - procsysvipc_tab;
+ return entry->type;
+ }
+ return virt_none;
+}
+
+fhandler_procsysvipc::fhandler_procsysvipc ():
+ fhandler_proc ()
+{
+}
+
+int
+fhandler_procsysvipc::fstat (struct stat *buf)
+{
+ fhandler_base::fstat (buf);
+ buf->st_mode &= ~_IFMT & NO_W;
+ int file_type = exists ();
+ switch (file_type)
+ {
+ case virt_none:
+ set_errno (ENOENT);
+ return -1;
+ case virt_directory:
+ case virt_rootdir:
+ buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
+ buf->st_nlink = 2;
+ return 0;
+ case virt_file:
+ default:
+ buf->st_mode |= S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
+ return 0;
+ }
+}
+
+int
+fhandler_procsysvipc::readdir (DIR *dir, dirent *de)
+{
+ int res = ENMFILE;
+ if (dir->__d_position >= PROCSYSVIPC_LINK_COUNT)
+ goto out;
+ {
+ cygserver_init ();
+ if (cygserver_running != CYGSERVER_OK)
+ goto out;
+ }
+ strcpy (de->d_name, procsysvipc_tab[dir->__d_position].name);
+ de->d_type = virt_ftype_to_dtype (procsysvipc_tab[dir->__d_position].type);
+ dir->__d_position++;
+ dir->__flags |= dirent_saw_dot | dirent_saw_dot_dot;
+ res = 0;
+out:
+ syscall_printf ("%d = readdir(%p, %p) (%s)", res, dir, de, de->d_name);
+ return res;
+}
+
+int
+fhandler_procsysvipc::open (int flags, mode_t mode)
+{
+ int res = fhandler_virtual::open (flags, mode);
+ if (!res)
+ goto out;
+
+ nohandle (true);
+
+ const char *path;
+ path = get_name () + proc_len + 1;
+ pid = atoi (path);
+ while (*path != 0 && !isdirsep (*path))
+ path++;
+
+ if (*path == 0)
+ {
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ res = 0;
+ goto out;
+ }
+ else if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ res = 0;
+ goto out;
+ }
+ else
+ {
+ diropen = true;
+ goto success;
+ }
+ }
+
+ virt_tab_t *entry;
+ entry = virt_tab_search (path + 1, true, procsysvipc_tab, PROCSYSVIPC_LINK_COUNT);
+ if (!entry)
+ {
+ set_errno ((flags & O_CREAT) ? EROFS : ENOENT);
+ res = 0;
+ goto out;
+ }
+ if (flags & O_WRONLY)
+ {
+ set_errno (EROFS);
+ res = 0;
+ goto out;
+ }
+
+ fileid = entry - procsysvipc_tab;
+ if (!fill_filebuf ())
+ {
+ res = 0;
+ goto out;
+ }
+
+ if (flags & O_APPEND)
+ position = filesize;
+ else
+ position = 0;
+
+success:
+ res = 1;
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ set_open_status ();
+out:
+ syscall_printf ("%d = fhandler_proc::open(%p, 0%o)", res, flags, mode);
+ return res;
+}
+
+bool
+fhandler_procsysvipc::fill_filebuf ()
+{
+ if (procsysvipc_tab[fileid].format_func)
+ {
+ filesize = procsysvipc_tab[fileid].format_func (NULL, filebuf);
+ return true;
+ }
+ return false;
+}
+
+#define MSG_HEADLINE " key msqid perms cbytes qnum lspid lrpid uid gid cuid cgid stime rtime ctime\n"
+
+static off_t
+format_procsysvipc_msg (void *, char *&destbuf)
+{
+ char *buf;
+ struct msginfo msginfo;
+ struct msqid_ds *xmsqids;
+
+ msgctl (0, IPC_INFO, (struct msqid_ds *) &msginfo);
+ /* Don't use tmp_pathbuf. The required buffer sizes can be up to 128K! */
+ xmsqids = (struct msqid_ds *) malloc (sizeof (struct msqid_ds)
+ * msginfo.msgmni);
+ if (!xmsqids)
+ return 0;
+ /* buf size = sizeof headline + 128 bytes per msg queue entry. */
+ buf = (char *) malloc (sizeof (MSG_HEADLINE) + msginfo.msgmni * 128);
+ if (!buf)
+ {
+ free (xmsqids);
+ return 0;
+ }
+
+ char *bufptr = stpcpy (buf, MSG_HEADLINE);
+ msgctl (msginfo.msgmni, IPC_INFO, (struct msqid_ds *) xmsqids);
+ for (int i = 0; i < msginfo.msgmni; i++)
+ {
+ if (xmsqids[i].msg_qbytes != 0)
+ {
+ bufptr += sprintf (bufptr,
+ "%10llu %10u %5o %11u %10u %5d %5d %5u %5u %5u %5u "
+ "%10ld %10ld %10ld\n",
+ xmsqids[i].msg_perm.key,
+ IXSEQ_TO_IPCID(i, xmsqids[i].msg_perm),
+ xmsqids[i].msg_perm.mode,
+ xmsqids[i].msg_cbytes,
+ xmsqids[i].msg_qnum,
+ xmsqids[i].msg_lspid,
+ xmsqids[i].msg_lrpid,
+ (unsigned) xmsqids[i].msg_perm.uid,
+ (unsigned) xmsqids[i].msg_perm.gid,
+ (unsigned) xmsqids[i].msg_perm.cuid,
+ (unsigned) xmsqids[i].msg_perm.cgid,
+ xmsqids[i].msg_stime,
+ xmsqids[i].msg_rtime,
+ xmsqids[i].msg_ctime);
+ }
+ }
+
+ off_t size = bufptr - buf;
+ destbuf = (char *) crealloc_abort (destbuf, size);
+ memcpy (destbuf, buf, size);
+ free (buf);
+ free (xmsqids);
+ return size;
+}
+
+#undef MSG_HEADLINE
+
+#define SEM_HEADLINE " key semid perms nsems uid gid cuid cgid otime ctime\n"
+
+static off_t
+format_procsysvipc_sem (void *, char *&destbuf)
+{
+ char *buf;
+ union semun semun;
+ struct seminfo seminfo;
+ struct semid_ds *xsemids;
+
+ semun.buf = (struct semid_ds *) &seminfo;
+ semctl (0, 0, IPC_INFO, semun);
+ /* Don't use tmp_pathbuf. The required buffer sizes can be up to 96K! */
+ xsemids = (struct semid_ds *) malloc (sizeof (struct semid_ds)
+ * seminfo.semmni);
+ if (!xsemids)
+ return 0;
+ /* buf size = sizeof headline + 96 bytes per semaphore entry. */
+ buf = (char *) malloc (sizeof (SEM_HEADLINE) + seminfo.semmni * 96);
+ if (!buf)
+ {
+ free (xsemids);
+ return 0;
+ }
+
+ char *bufptr = stpcpy (buf, SEM_HEADLINE);
+ semun.buf = xsemids;
+ semctl (seminfo.semmni, 0, IPC_INFO, semun);
+ for (int i = 0; i < seminfo.semmni; i++)
+ {
+ if ((xsemids[i].sem_perm.mode & SEM_ALLOC) != 0)
+ {
+ bufptr += sprintf (bufptr,
+ "%10llu %10u %5o %10d %5u %5u %5u %5u %10ld %10ld\n",
+ xsemids[i].sem_perm.key,
+ IXSEQ_TO_IPCID(i, xsemids[i].sem_perm),
+ xsemids[i].sem_perm.mode,
+ xsemids[i].sem_nsems,
+ (unsigned) xsemids[i].sem_perm.uid,
+ (unsigned) xsemids[i].sem_perm.gid,
+ (unsigned) xsemids[i].sem_perm.cuid,
+ (unsigned) xsemids[i].sem_perm.cgid,
+ xsemids[i].sem_otime,
+ xsemids[i].sem_ctime);
+ }
+ }
+
+ off_t size = bufptr - buf;
+ destbuf = (char *) crealloc_abort (destbuf, size);
+ memcpy (destbuf, buf, size);
+ free (buf);
+ free (xsemids);
+ return size;
+}
+
+#undef SEM_HEADLINE
+
+#define SHM_HEADLINE " key shmid perms size cpid lpid nattch uid gid cuid cgid atime dtime ctime\n"
+
+static off_t
+format_procsysvipc_shm (void *, char *&destbuf)
+{
+ char *buf;
+ struct shminfo shminfo;
+ struct shmid_ds *xshmids;
+
+ shmctl (0, IPC_INFO, (struct shmid_ds *) &shminfo);
+ /* Don't use tmp_pathbuf. The required buffer sizes can be up to 120K! */
+ xshmids = (struct shmid_ds *) malloc (sizeof (struct shmid_ds)
+ * shminfo.shmmni);
+ if (!xshmids)
+ return 0;
+ /* buf size = sizeof headline + 120 bytes per shmem entry. */
+ buf = (char *) malloc (sizeof (SHM_HEADLINE) + shminfo.shmmni * 120);
+ if (!buf)
+ {
+ free (xshmids);
+ return 0;
+ }
+
+ char *bufptr = stpcpy (buf, SHM_HEADLINE);
+ shmctl (shminfo.shmmni, IPC_INFO, (struct shmid_ds *) xshmids);
+ for (int i = 0; i < shminfo.shmmni; i++)
+ {
+ if (xshmids[i].shm_perm.mode & 0x0800)
+ {
+ bufptr += sprintf (bufptr,
+ "%10llu %10u %5o %10u %5d %5d %6u %5u %5u %5u %5u "
+ "%10ld %10ld %10ld\n",
+ xshmids[i].shm_perm.key,
+ IXSEQ_TO_IPCID(i, xshmids[i].shm_perm),
+ xshmids[i].shm_perm.mode,
+ xshmids[i].shm_segsz,
+ xshmids[i].shm_cpid,
+ xshmids[i].shm_lpid,
+ xshmids[i].shm_nattch,
+ (unsigned) xshmids[i].shm_perm.uid,
+ (unsigned) xshmids[i].shm_perm.gid,
+ (unsigned) xshmids[i].shm_perm.cuid,
+ (unsigned) xshmids[i].shm_perm.cgid,
+ xshmids[i].shm_atime,
+ xshmids[i].shm_dtime,
+ xshmids[i].shm_ctime);
+ }
+ }
+
+ off_t size = bufptr - buf;
+ destbuf = (char *) crealloc_abort (destbuf, size);
+ memcpy (destbuf, buf, size);
+ free (buf);
+ free (xshmids);
+ return size;
+}
+
+#undef SHM_HEADLINE
diff --git a/winsup/cygwin/fhandler/random.cc b/winsup/cygwin/fhandler/random.cc
new file mode 100644
index 000000000..495e3a94b
--- /dev/null
+++ b/winsup/cygwin/fhandler/random.cc
@@ -0,0 +1,138 @@
+/* fhandler_random.cc: code to access /dev/random and /dev/urandom
+
+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 <ntsecapi.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "sync.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "child_info.h"
+
+#define RANDOM 8
+#define URANDOM 9
+
+/* The system PRNG is reseeded after reading 128K bytes. */
+#define RESEED_INTERVAL (128 * 1024)
+
+#define PSEUDO_MULTIPLIER (6364136223846793005LL)
+#define PSEUDO_SHIFTVAL (21)
+
+int
+fhandler_dev_random::pseudo_write (const void *ptr, size_t len)
+{
+ /* Use buffer to mess up the pseudo random number generator. */
+ for (size_t i = 0; i < len; ++i)
+ pseudo = (pseudo + ((unsigned char *)ptr)[i]) * PSEUDO_MULTIPLIER + 1;
+ return len;
+}
+
+ssize_t
+fhandler_dev_random::write (const void *ptr, size_t len)
+{
+ if (!len)
+ return 0;
+ if (!ptr)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ /* Limit len to a value <= 4096 since we don't want to overact.
+ Copy to local buffer because RtlGenRandom violates const. */
+ size_t limited_len = MIN (len, 4096);
+ unsigned char buf[limited_len];
+
+ /* Mess up system entropy source. Return error if device is /dev/random. */
+ __try
+ {
+ memcpy (buf, ptr, limited_len);
+ if (!RtlGenRandom (buf, limited_len) && dev () == FH_RANDOM)
+ return -1;
+ /* Mess up the pseudo random number generator. */
+ pseudo_write (buf, limited_len);
+ }
+ __except (EFAULT)
+ {
+ len = -1;
+ }
+ __endtry
+ /* Note that we return len, not limited_len. No reason to confuse the
+ caller... */
+ return len;
+}
+
+int
+fhandler_dev_random::pseudo_read (void *ptr, size_t len)
+{
+ /* Use pseudo random number generator as fallback entropy source.
+ This multiplier was obtained from Knuth, D.E., "The Art of
+ Computer Programming," Vol 2, Seminumerical Algorithms, Third
+ Edition, Addison-Wesley, 1998, p. 106 (line 26) & p. 108 */
+ for (size_t i = 0; i < len; ++i)
+ {
+ pseudo = pseudo * PSEUDO_MULTIPLIER + 1;
+ ((unsigned char *)ptr)[i] = (pseudo >> PSEUDO_SHIFTVAL) & UCHAR_MAX;
+ }
+ return len;
+}
+
+void
+fhandler_dev_random::read (void *ptr, size_t& len)
+{
+ if (!len)
+ return;
+
+ if (!ptr)
+ {
+ set_errno (EINVAL);
+ len = (size_t) -1;
+ return;
+ }
+
+ __try
+ {
+ /* /dev/random has to provide high quality random numbers. Therefore we
+ re-seed the system PRNG for each block of 512 bytes. This results in
+ sufficiently random sequences, comparable to the Linux /dev/random. */
+ if (dev () == FH_RANDOM)
+ {
+ void *dummy = malloc (RESEED_INTERVAL);
+ if (!dummy)
+ {
+ __seterrno ();
+ len = (size_t) -1;
+ return;
+ }
+ for (size_t offset = 0; offset < len; offset += 512)
+ {
+ if (!RtlGenRandom (dummy, RESEED_INTERVAL) ||
+ !RtlGenRandom ((PBYTE) ptr + offset, len - offset))
+ {
+ len = (size_t) -1;
+ break;
+ }
+ }
+ free (dummy);
+ }
+
+ /* If device is /dev/urandom, just use system RNG as is, with our own
+ PRNG as fallback. */
+ else if (!RtlGenRandom (ptr, len))
+ len = pseudo_read (ptr, len);
+ }
+ __except (EFAULT)
+ {
+ len = (size_t) -1;
+ }
+ __endtry
+}
diff --git a/winsup/cygwin/fhandler/raw.cc b/winsup/cygwin/fhandler/raw.cc
new file mode 100644
index 000000000..44cb10dfb
--- /dev/null
+++ b/winsup/cygwin/fhandler/raw.cc
@@ -0,0 +1,208 @@
+/* fhandler_raw.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 <unistd.h>
+#include <cygwin/rdevio.h>
+#include <sys/mtio.h>
+#include <sys/param.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+
+/**********************************************************************/
+/* fhandler_dev_raw */
+
+fhandler_dev_raw::fhandler_dev_raw ()
+ : fhandler_base (),
+ status ()
+{
+ need_fork_fixup (true);
+}
+
+fhandler_dev_raw::~fhandler_dev_raw ()
+{
+ if (devbufsiz > 1L)
+ delete [] devbufalloc;
+}
+
+int
+fhandler_dev_raw::fstat (struct stat *buf)
+{
+ debug_printf ("here");
+
+ fhandler_base::fstat (buf);
+ if (!dev ().isfs ())
+ {
+ if (get_major () == DEV_TAPE_MAJOR)
+ buf->st_mode = S_IFCHR | STD_RBITS | STD_WBITS | S_IWGRP | S_IWOTH;
+ else
+ buf->st_mode = S_IFBLK | STD_RBITS | STD_WBITS | S_IWGRP | S_IWOTH;
+
+ if (get_major () == DEV_SD_HIGHPART_END && get_minor () == 9999)
+ buf->st_ino = get_ino ();
+ buf->st_uid = geteuid ();
+ buf->st_gid = getegid ();
+ buf->st_nlink = 1;
+ buf->st_blksize = PREFERRED_IO_BLKSIZE;
+ time_as_timestruc_t (&buf->st_ctim);
+ buf->st_atim = buf->st_mtim = buf->st_birthtim = buf->st_ctim;
+ }
+ return 0;
+}
+
+int
+fhandler_dev_raw::open (int flags, mode_t)
+{
+ /* Check for illegal flags. */
+ if (get_major () != DEV_TAPE_MAJOR && (flags & O_APPEND))
+ {
+ set_errno (EINVAL);
+ return 0;
+ }
+
+ /* Always open a raw device existing and binary. */
+ flags &= ~(O_CREAT | O_TRUNC);
+ flags |= O_BINARY;
+
+ /* Write-only doesn't work well with raw devices */
+ if ((flags & O_ACCMODE) == O_WRONLY)
+ flags = ((flags & ~O_WRONLY) | O_RDWR);
+
+ int res = fhandler_base::open (flags, 0);
+
+ return res;
+}
+
+int
+fhandler_dev_raw::dup (fhandler_base *child, int flags)
+{
+ int ret = fhandler_base::dup (child, flags);
+
+ if (!ret)
+ {
+ fhandler_dev_raw *fhc = (fhandler_dev_raw *) child;
+
+ if (devbufsiz > 1L)
+ {
+ /* Create sector-aligned buffer */
+ fhc->devbufalloc = new char [devbufsiz + devbufalign];
+ fhc->devbuf = (char *) roundup2 ((uintptr_t) fhc->devbufalloc,
+ (uintptr_t) devbufalign);
+ }
+ fhc->devbufstart = 0;
+ fhc->devbufend = 0;
+ fhc->lastblk_to_read (false);
+ }
+ return ret;
+}
+
+void
+fhandler_dev_raw::fixup_after_fork (HANDLE parent)
+{
+ fhandler_base::fixup_after_fork (parent);
+ devbufstart = 0;
+ devbufend = 0;
+ lastblk_to_read (false);
+}
+
+void
+fhandler_dev_raw::fixup_after_exec ()
+{
+ if (!close_on_exec ())
+ {
+ if (devbufsiz > 1L)
+ {
+ /* Create sector-aligned buffer */
+ devbufalloc = new char [devbufsiz + devbufalign];
+ devbuf = (char *) roundup2 ((uintptr_t) devbufalloc,
+ (uintptr_t) devbufalign);
+ }
+ devbufstart = 0;
+ devbufend = 0;
+ lastblk_to_read (false);
+ }
+}
+
+int
+fhandler_dev_raw::ioctl (unsigned int cmd, void *buf)
+{
+ int ret = NO_ERROR;
+
+ if (cmd == RDIOCDOP)
+ {
+ struct rdop *op = (struct rdop *) buf;
+
+ if (!op)
+ ret = ERROR_INVALID_PARAMETER;
+ else
+ switch (op->rd_op)
+ {
+ case RDSETBLK:
+ if (get_major () == DEV_TAPE_MAJOR)
+ {
+ struct mtop mop;
+
+ mop.mt_op = MTSETBLK;
+ mop.mt_count = op->rd_parm;
+ ret = ioctl (MTIOCTOP, &mop);
+ }
+ else if ((op->rd_parm <= 1 && get_major () != DEV_TAPE_MAJOR)
+ || (op->rd_parm > 1 && (op->rd_parm % devbufalign))
+ || (get_flags () & O_DIRECT))
+ /* The conditions for a valid parameter are:
+ - The new size is either 0 or 1, both indicating unbuffered
+ I/O, and the device is a tape device.
+ - Or, the new buffersize must be a multiple of the
+ required buffer alignment.
+ - In the O_DIRECT case, the whole request is invalid. */
+ ret = ERROR_INVALID_PARAMETER;
+ else if (!devbuf || op->rd_parm != devbufsiz)
+ {
+ char *buf = NULL;
+ off_t curpos = lseek (0, SEEK_CUR);
+
+ if (op->rd_parm > 1L)
+ buf = new char [op->rd_parm + devbufalign];
+
+ if (devbufsiz > 1L)
+ delete [] devbufalloc;
+
+ devbufalloc = buf;
+ devbuf = (char *) roundup2 ((uintptr_t) buf,
+ (uintptr_t) devbufalign);
+ devbufsiz = op->rd_parm ?: 1L;
+ devbufstart = devbufend = 0;
+ lseek (curpos, SEEK_SET);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ else if (cmd == RDIOCGET)
+ {
+ struct rdget *get = (struct rdget *) buf;
+
+ if (!get)
+ ret = ERROR_INVALID_PARAMETER;
+ else
+ get->bufsiz = devbufsiz;
+ }
+ else
+ return fhandler_base::ioctl (cmd, buf);
+
+ if (ret != NO_ERROR)
+ {
+ SetLastError (ret);
+ __seterrno ();
+ return -1;
+ }
+ return 0;
+}
diff --git a/winsup/cygwin/fhandler/registry.cc b/winsup/cygwin/fhandler/registry.cc
new file mode 100644
index 000000000..2830c708a
--- /dev/null
+++ b/winsup/cygwin/fhandler/registry.cc
@@ -0,0 +1,1115 @@
+/* fhandler_registry.cc: fhandler for /proc/registry virtual filesystem
+
+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. */
+
+/* FIXME: Access permissions are ignored at the moment. */
+
+#include "winsup.h"
+#include <stdlib.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "child_info.h"
+
+#define _LIBC
+#include <dirent.h>
+
+/* If this bit is set in __d_position then we are enumerating values,
+ * else sub-keys. keeping track of where we are is horribly messy
+ * the bottom 16 bits are the absolute position and the top 15 bits
+ * make up the value index if we are enuerating values.
+ */
+static const __int32_t REG_ENUM_VALUES_MASK = 0x8000000;
+static const __int32_t REG_POSITION_MASK = 0xffff;
+
+/* These key paths are used below whenever we return key information.
+ The problem is UAC virtualization when running an admin account with
+ restricted rights. In that case the subkey "Classes" in the VirtualStore
+ points to the HKEY_CLASSES_ROOT key again. If "Classes" is handled as a
+ normal subdirectory, applications recursing throught the directory
+ hirarchy will invariably run into an infinite recursion. What we do here
+ is to handle the "Classes" subkey as a symlink to HKEY_CLASSES_ROOT. This
+ avoids the infinite recursion, unless the application blindly follows
+ symlinks pointing to directories, in which case it's their own fault. */
+#define VIRT_CLASSES_KEY_PREFIX "/VirtualStore/MACHINE/SOFTWARE"
+#define VIRT_CLASSES_KEY_SUFFIX "Classes"
+#define VIRT_CLASSES_KEY VIRT_CLASSES_KEY_PREFIX "/" VIRT_CLASSES_KEY_SUFFIX
+#define VIRT_CLASSES_LINKTGT "/proc/registry/HKEY_CLASSES_ROOT"
+
+/* List of root keys in /proc/registry.
+ * Possibly we should filter out those not relevant to the flavour of Windows
+ * Cygwin is running on.
+ */
+static const char *registry_listing[] =
+{
+ ".",
+ "..",
+ "HKEY_CLASSES_ROOT",
+ "HKEY_CURRENT_CONFIG",
+ "HKEY_CURRENT_USER",
+ "HKEY_LOCAL_MACHINE",
+ "HKEY_USERS",
+ "HKEY_PERFORMANCE_DATA",
+ NULL
+};
+
+static const HKEY registry_keys[] =
+{
+ (HKEY) INVALID_HANDLE_VALUE,
+ (HKEY) INVALID_HANDLE_VALUE,
+ HKEY_CLASSES_ROOT,
+ HKEY_CURRENT_CONFIG,
+ HKEY_CURRENT_USER,
+ HKEY_LOCAL_MACHINE,
+ HKEY_USERS,
+ HKEY_PERFORMANCE_DATA
+};
+
+static const int ROOT_KEY_COUNT = sizeof (registry_keys) / sizeof (HKEY);
+
+/* Make sure to access the correct per-user HKCR and HKCU hives, even if
+ the current user is only impersonated in another user's session. */
+static HKEY
+fetch_hkey (int idx) /* idx *must* be valid */
+{
+ HKEY key;
+
+ if (registry_keys[idx] == HKEY_CLASSES_ROOT)
+ {
+ if (RegOpenUserClassesRoot (cygheap->user.issetuid ()
+ ? cygheap->user.imp_token () : hProcToken,
+ 0, KEY_READ, &key) == ERROR_SUCCESS)
+ return key;
+ }
+ else if (registry_keys[idx] == HKEY_CURRENT_USER)
+ {
+ if (RegOpenCurrentUser (KEY_READ, &key) == ERROR_SUCCESS)
+ return key;
+ }
+ else if (registry_keys[idx] == HKEY_CURRENT_CONFIG)
+ {
+ if (RegOpenKeyExW (HKEY_LOCAL_MACHINE,
+ L"System\\CurrentControlSet\\Hardware Profiles\\Current",
+ 0, KEY_READ, &key) == ERROR_SUCCESS)
+ return key;
+ }
+ /* Unfortunately there's no way to generate a valid OS registry key for
+ the other root keys. HKEY_USERS and HKEY_LOCAL_MACHINE are file
+ handles internally, HKEY_PERFORMANCE_DATA is just a bad hack and
+ no registry key at all. */
+ return registry_keys[idx];
+}
+
+/* These get added to each subdirectory in /proc/registry.
+ * If we wanted to implement writing, we could maybe add a '.writable' entry or
+ * suchlike.
+ */
+static const char *special_dot_files[] =
+{
+ ".",
+ "..",
+ NULL
+};
+
+static const int SPECIAL_DOT_FILE_COUNT =
+ (sizeof (special_dot_files) / sizeof (const char *)) - 1;
+
+/* Value names for HKEY_PERFORMANCE_DATA.
+ *
+ * CAUTION: Never call RegQueryValueEx (HKEY_PERFORMANCE_DATA, "Add", ...).
+ * It WRITES data and may destroy the perfc009.dat file. Same applies to
+ * name prefixes "Ad" and "A".
+ */
+static const char * const perf_data_files[] =
+{
+ "@",
+ "Costly",
+ "Global"
+};
+
+static const int PERF_DATA_FILE_COUNT =
+ sizeof (perf_data_files) / sizeof (perf_data_files[0]);
+
+static HKEY open_key (const char *name, REGSAM access, DWORD wow64, bool isValue);
+
+/* Return true if char must be encoded.
+ */
+static inline bool
+must_encode (wchar_t c)
+{
+ return (iswdirsep (c) || c == L':' || c == L'%');
+}
+
+/* Encode special chars in registry key or value name.
+ * Returns 0: success, -1: error.
+ */
+static int
+encode_regname (char *dst, const wchar_t *src, bool add_val)
+{
+ int di = 0;
+ if (!src[0])
+ dst[di++] = '@'; // Default value.
+ else
+ for (int si = 0; src[si]; si++)
+ {
+ wchar_t c = src[si];
+ if (must_encode (c) ||
+ (si == 0 && ((c == L'.'
+ && (!src[1] || (src[1] == L'.' && !src[2])))
+ || (c == L'@' && !src[1]))))
+ {
+ if (di + 3 >= NAME_MAX + 1)
+ return -1;
+ __small_sprintf (dst + di, "%%%02x", c);
+ di += 3;
+ }
+ else
+ di += sys_wcstombs (dst + di, NAME_MAX + 1 - di, &c, 1);
+ }
+
+ if (add_val)
+ {
+ if (di + 4 >= NAME_MAX + 1)
+ return -1;
+ memcpy (dst + di, "%val", 4);
+ di += 4;
+ }
+
+ dst[di] = 0;
+ return 0;
+}
+
+/* Decode special chars in registry key or value name.
+ * Returns 0: success, 1: "%val" detected, -1: error.
+ */
+static int
+decode_regname (wchar_t *wdst, const char *src, int len = -1)
+{
+ if (len < 0)
+ len = strlen (src);
+ char dst[len + 1];
+ int res = 0;
+
+ if (len > 4 && !memcmp (src + len - 4, "%val", 4))
+ {
+ len -= 4;
+ res = 1;
+ }
+
+ int di = 0;
+ if (len == 1 && src[0] == '@')
+ ; // Default value.
+ else
+ for (int si = 0; si < len; si++)
+ {
+ char c = src[si];
+ if (c == '%')
+ {
+ if (si + 2 >= len)
+ return -1;
+ char s[] = {src[si+1], src[si+2], '\0'};
+ char *p;
+ c = strtoul (s, &p, 16);
+ if (!(must_encode ((wchar_t) c) ||
+ (si == 0 && ((c == '.' && (len == 3 || (src[3] == '.' && len == 4))) ||
+ (c == '@' && len == 3)))))
+ return -1;
+ dst[di++] = c;
+ si += 2;
+ }
+ else
+ dst[di++] = c;
+ }
+
+ dst[di] = 0;
+ sys_mbstowcs (wdst, NAME_MAX + 1, dst);
+ return res;
+}
+
+
+/* Hash table to limit calls to key_exists ().
+ */
+class __DIR_hash
+{
+public:
+ __DIR_hash ()
+ {
+ memset (table, 0, sizeof(table));
+ }
+
+ void set (unsigned h)
+ {
+ table [(h >> 3) & (HASH_SIZE - 1)] |= (1 << (h & 0x3));
+ }
+
+ bool is_set (unsigned h) const
+ {
+ return (table [(h >> 3) & (HASH_SIZE - 1)] & (1 << (h & 0x3))) != 0;
+ }
+
+private:
+ enum { HASH_SIZE = 1024 };
+ unsigned char table[HASH_SIZE];
+};
+
+#define d_hash(d) ((__DIR_hash *) (d)->__d_internal)
+
+
+/* Return true if subkey NAME exists in key PARENT.
+ */
+static bool
+key_exists (HKEY parent, const wchar_t *name, DWORD wow64)
+{
+ HKEY hKey = (HKEY) INVALID_HANDLE_VALUE;
+ LONG error = RegOpenKeyExW (parent, name, 0, KEY_READ | wow64, &hKey);
+ if (error == ERROR_SUCCESS)
+ RegCloseKey (hKey);
+
+ return (error == ERROR_SUCCESS || error == ERROR_ACCESS_DENIED);
+}
+
+static size_t
+multi_wcstombs (char *dst, size_t len, const wchar_t *src, size_t nwc)
+{
+ size_t siz, sum = 0;
+ const wchar_t *nsrc;
+
+ while (nwc)
+ {
+ siz = sys_wcstombs (dst, len, src, nwc) + 1;
+ sum += siz;
+ if (dst)
+ {
+ dst += siz;
+ len -= siz;
+ }
+ nsrc = wcschr (src, L'\0') + 1;
+ if ((size_t) (nsrc - src) >= nwc)
+ break;
+ nwc -= nsrc - src;
+ src = nsrc;
+ if (*src == L'\0')
+ {
+ if (dst)
+ *dst++ = '\0';
+ ++sum;
+ break;
+ }
+ }
+ return sum;
+}
+
+virtual_ftype_t
+fhandler_registry::exists ()
+{
+ virtual_ftype_t file_type = virt_none;
+ int index = 0, pathlen;
+ DWORD buf_size = NAME_MAX + 1;
+ LONG error;
+ wchar_t buf[buf_size];
+ const char *file;
+ HKEY hKey = (HKEY) INVALID_HANDLE_VALUE;
+
+ const char *path = get_name ();
+ debug_printf ("exists (%s)", path);
+ path += proc_len + prefix_len + 1;
+ if (*path)
+ path++;
+ else
+ {
+ file_type = virt_rootdir;
+ goto out;
+ }
+ pathlen = strlen (path);
+ file = path + pathlen - 1;
+ if (isdirsep (*file) && pathlen > 1)
+ file--;
+ while (!isdirsep (*file))
+ file--;
+ file++;
+
+ if (file == path)
+ {
+ for (int i = 0; registry_listing[i]; i++)
+ if (path_prefix_p (registry_listing[i], path,
+ strlen (registry_listing[i]), true))
+ {
+ file_type = virt_directory;
+ break;
+ }
+ }
+ else
+ {
+ wchar_t dec_file[NAME_MAX + 1];
+
+ int val_only = decode_regname (dec_file, file);
+ if (val_only < 0)
+ goto out;
+
+ if (!val_only)
+ hKey = open_key (path, KEY_READ, wow64, false);
+ if (hKey != (HKEY) INVALID_HANDLE_VALUE)
+ {
+ if (!strcasecmp (path + strlen (path)
+ - sizeof (VIRT_CLASSES_KEY) + 1,
+ VIRT_CLASSES_KEY))
+ file_type = virt_symlink;
+ else
+ file_type = virt_directory;
+ }
+ else
+ {
+ /* Key does not exist or open failed with EACCES,
+ enumerate subkey and value names of parent key. */
+ hKey = open_key (path, KEY_READ, wow64, true);
+ if (hKey == (HKEY) INVALID_HANDLE_VALUE)
+ return virt_none;
+
+ if (hKey == HKEY_PERFORMANCE_DATA)
+ {
+ /* RegEnumValue () returns garbage for this key.
+ RegQueryValueEx () returns a PERF_DATA_BLOCK even
+ if a value does not contain any counter objects.
+ So allow access to the generic names and to
+ (blank separated) lists of counter numbers.
+ Never allow access to "Add", see above comment. */
+ for (int i = 0; i < PERF_DATA_FILE_COUNT
+ && file_type == virt_none; i++)
+ {
+ if (strcasematch (perf_data_files[i], file))
+ file_type = virt_file;
+ }
+ if (file_type == virt_none && !file[strspn (file, " 0123456789")])
+ file_type = virt_file;
+ goto out;
+ }
+
+ if (!val_only && dec_file[0])
+ {
+ while (ERROR_SUCCESS ==
+ (error = RegEnumKeyExW (hKey, index++, buf, &buf_size,
+ NULL, NULL, NULL, NULL))
+ || (error == ERROR_MORE_DATA))
+ {
+ if (!wcscasecmp (buf, dec_file))
+ {
+ file_type = virt_directory;
+ goto out;
+ }
+ buf_size = NAME_MAX + 1;
+ }
+ if (error != ERROR_NO_MORE_ITEMS)
+ {
+ seterrno_from_win_error (__FILE__, __LINE__, error);
+ goto out;
+ }
+ index = 0;
+ buf_size = NAME_MAX + 1;
+ }
+
+ while (ERROR_SUCCESS ==
+ (error = RegEnumValueW (hKey, index++, buf, &buf_size,
+ NULL, NULL, NULL, NULL))
+ || (error == ERROR_MORE_DATA))
+ {
+ if (!wcscasecmp (buf, dec_file))
+ {
+ file_type = virt_file;
+ goto out;
+ }
+ buf_size = NAME_MAX + 1;
+ }
+ if (error != ERROR_NO_MORE_ITEMS)
+ {
+ seterrno_from_win_error (__FILE__, __LINE__, error);
+ goto out;
+ }
+ }
+ }
+out:
+ if (hKey != (HKEY) INVALID_HANDLE_VALUE)
+ RegCloseKey (hKey);
+ return file_type;
+}
+
+void
+fhandler_registry::set_name (path_conv &in_pc)
+{
+ if (strncasematch (in_pc.get_posix (), "/proc/registry32", 16))
+ {
+ wow64 = KEY_WOW64_32KEY;
+ prefix_len += 2;
+ }
+ else if (strncasematch (in_pc.get_posix (), "/proc/registry64", 16))
+ prefix_len += 2;
+ fhandler_base::set_name (in_pc);
+}
+
+fhandler_registry::fhandler_registry ():
+fhandler_proc ()
+{
+ wow64 = 0;
+ prefix_len = sizeof ("registry") - 1;
+}
+
+int
+fhandler_registry::fstat (struct stat *buf)
+{
+ fhandler_base::fstat (buf);
+ buf->st_mode &= ~_IFMT & NO_W;
+ virtual_ftype_t file_type = exists ();
+ switch (file_type)
+ {
+ case virt_none:
+ set_errno (ENOENT);
+ return -1;
+ case virt_symlink:
+ buf->st_mode |= S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
+ break;
+ case virt_directory:
+ buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
+ break;
+ case virt_rootdir:
+ buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
+ buf->st_nlink = ROOT_KEY_COUNT;
+ break;
+ default:
+ case virt_file:
+ buf->st_mode |= S_IFREG;
+ buf->st_mode &= NO_X;
+ break;
+ }
+ if (file_type != virt_none && file_type != virt_rootdir)
+ {
+ HKEY hKey;
+ const char *path = get_name () + proc_len + prefix_len + 2;
+ hKey =
+ open_key (path, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE, wow64,
+ virt_ftype_isfile (file_type) ? true : false);
+
+ if (hKey == HKEY_PERFORMANCE_DATA)
+ /* RegQueryInfoKey () always returns write time 0,
+ RegQueryValueEx () does not return required buffer size. */
+ ;
+ else if (hKey != (HKEY) INVALID_HANDLE_VALUE)
+ {
+ FILETIME ftLastWriteTime;
+ DWORD subkey_count;
+ if (ERROR_SUCCESS ==
+ RegQueryInfoKeyW (hKey, NULL, NULL, NULL, &subkey_count, NULL,
+ NULL, NULL, NULL, NULL, NULL, &ftLastWriteTime))
+ {
+ to_timestruc_t ((PLARGE_INTEGER) &ftLastWriteTime, &buf->st_mtim);
+ buf->st_ctim = buf->st_birthtim = buf->st_mtim;
+ time_as_timestruc_t (&buf->st_atim);
+ if (virt_ftype_isdir (file_type))
+ buf->st_nlink = subkey_count + 2;
+ else
+ {
+ int pathlen = strlen (path);
+ const char *value_name = path + pathlen - 1;
+ if (isdirsep (*value_name) && pathlen > 1)
+ value_name--;
+ while (!isdirsep (*value_name))
+ value_name--;
+ value_name++;
+ wchar_t dec_value_name[NAME_MAX + 1];
+ DWORD dwSize = 0;
+ DWORD type;
+ if (decode_regname (dec_value_name, value_name) >= 0
+ && RegQueryValueExW (hKey, dec_value_name, NULL, &type,
+ NULL, &dwSize) == ERROR_SUCCESS
+ && (type == REG_SZ || type == REG_EXPAND_SZ
+ || type == REG_MULTI_SZ || type == REG_LINK))
+ {
+ PBYTE tmpbuf = (PBYTE) malloc (dwSize);
+ if (!tmpbuf
+ || RegQueryValueExW (hKey, dec_value_name,
+ NULL, NULL, tmpbuf, &dwSize)
+ != ERROR_SUCCESS)
+ buf->st_size = dwSize / sizeof (wchar_t);
+ else if (type == REG_MULTI_SZ)
+ buf->st_size = multi_wcstombs (NULL, 0,
+ (wchar_t *) tmpbuf,
+ dwSize / sizeof (wchar_t));
+ else
+ buf->st_size = sys_wcstombs (NULL, 0,
+ (wchar_t *) tmpbuf,
+ dwSize / sizeof (wchar_t))
+ + 1;
+ if (tmpbuf)
+ free (tmpbuf);
+ }
+ else
+ buf->st_size = dwSize;
+ }
+ uid_t uid;
+ gid_t gid;
+ if (get_reg_attribute (hKey, &buf->st_mode, &uid, &gid) == 0)
+ {
+ buf->st_uid = uid;
+ buf->st_gid = gid;
+ buf->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+ if (virt_ftype_isdir (file_type))
+ buf->st_mode |= S_IFDIR;
+ else
+ buf->st_mode &= NO_X;
+ }
+ }
+ RegCloseKey (hKey);
+ }
+ else
+ {
+ /* Here's the problem: If we can't open the key, we don't know
+ nothing at all about the key/value. It's only clear that
+ the current user has no read access. At this point it's
+ rather unlikely that the user has write or execute access
+ and it's also rather unlikely that the user is the owner.
+ Therefore it's probably most safe to assume unknown ownership
+ and no permissions for nobody. */
+ buf->st_uid = ILLEGAL_UID;
+ buf->st_gid = ILLEGAL_GID;
+ buf->st_mode &= ~0777;
+ }
+ }
+ return 0;
+}
+
+DIR *
+fhandler_registry::opendir (int fd)
+{
+ /* Skip fhandler_proc::opendir, which allocates dir->_d_handle for its
+ own devilish purposes... */
+ return fhandler_virtual::opendir (fd);
+}
+
+int
+fhandler_registry::readdir (DIR *dir, dirent *de)
+{
+ DWORD buf_size = NAME_MAX + 1;
+ wchar_t buf[buf_size];
+ const char *path = dir->__d_dirname + proc_len + 1 + prefix_len;
+ LONG error;
+ int res = ENMFILE;
+
+ dir->__flags |= dirent_saw_dot | dirent_saw_dot_dot;
+ if (*path == 0)
+ {
+ if (dir->__d_position >= ROOT_KEY_COUNT)
+ goto out;
+ strcpy (de->d_name, registry_listing[dir->__d_position++]);
+ res = 0;
+ goto out;
+ }
+ if (dir->__handle == INVALID_HANDLE_VALUE)
+ {
+ if (dir->__d_position != 0)
+ goto out;
+ dir->__handle = open_key (path + 1, KEY_READ, wow64, false);
+ if (dir->__handle == INVALID_HANDLE_VALUE)
+ goto out;
+ dir->__d_internal = (uintptr_t) new __DIR_hash ();
+ }
+ if (dir->__d_position < SPECIAL_DOT_FILE_COUNT)
+ {
+ strcpy (de->d_name, special_dot_files[dir->__d_position++]);
+ res = 0;
+ goto out;
+ }
+ if ((HKEY) dir->__handle == HKEY_PERFORMANCE_DATA)
+ {
+ /* RegEnumValue () returns garbage for this key,
+ simulate only a minimal listing of the generic names. */
+ if (dir->__d_position >= SPECIAL_DOT_FILE_COUNT + PERF_DATA_FILE_COUNT)
+ goto out;
+ strcpy (de->d_name, perf_data_files[dir->__d_position - SPECIAL_DOT_FILE_COUNT]);
+ dir->__d_position++;
+ res = 0;
+ goto out;
+ }
+
+retry:
+ if (dir->__d_position & REG_ENUM_VALUES_MASK)
+ /* For the moment, the type of key is ignored here. when write access is added,
+ * maybe add an extension for the type of each value?
+ */
+ error = RegEnumValueW ((HKEY) dir->__handle,
+ (dir->__d_position & ~REG_ENUM_VALUES_MASK) >> 16,
+ buf, &buf_size, NULL, NULL, NULL, NULL);
+ else
+ error =
+ RegEnumKeyExW ((HKEY) dir->__handle, dir->__d_position -
+ SPECIAL_DOT_FILE_COUNT, buf, &buf_size,
+ NULL, NULL, NULL, NULL);
+ if (error == ERROR_NO_MORE_ITEMS
+ && (dir->__d_position & REG_ENUM_VALUES_MASK) == 0)
+ {
+ /* If we're finished with sub-keys, start on values under this key. */
+ dir->__d_position |= REG_ENUM_VALUES_MASK;
+ buf_size = NAME_MAX + 1;
+ goto retry;
+ }
+ if (error != ERROR_SUCCESS && error != ERROR_MORE_DATA)
+ {
+ delete d_hash (dir);
+ RegCloseKey ((HKEY) dir->__handle);
+ dir->__handle = INVALID_HANDLE_VALUE;
+ if (error != ERROR_NO_MORE_ITEMS)
+ seterrno_from_win_error (__FILE__, __LINE__, error);
+ goto out;
+ }
+
+ /* We get here if `buf' contains valid data. */
+ dir->__d_position++;
+ if (dir->__d_position & REG_ENUM_VALUES_MASK)
+ dir->__d_position += 0x10000;
+
+ {
+ /* Append "%val" if value name is identical to a previous key name. */
+ unsigned h = hash_path_name (1, buf);
+ bool add_val = false;
+ if (! (dir->__d_position & REG_ENUM_VALUES_MASK))
+ d_hash (dir)->set (h);
+ else if (d_hash (dir)->is_set (h)
+ && key_exists ((HKEY) dir->__handle, buf, wow64))
+ add_val = true;
+
+ if (encode_regname (de->d_name, buf, add_val))
+ {
+ buf_size = NAME_MAX + 1;
+ goto retry;
+ }
+ }
+
+ if (dir->__d_position & REG_ENUM_VALUES_MASK)
+ de->d_type = DT_REG;
+ else if (!strcasecmp (de->d_name, "Classes")
+ && !strcasecmp (path + strlen (path)
+ - sizeof (VIRT_CLASSES_KEY_PREFIX) + 1,
+ VIRT_CLASSES_KEY_PREFIX))
+ de->d_type = DT_LNK;
+ else
+ de->d_type = DT_DIR;
+
+ res = 0;
+out:
+ syscall_printf ("%d = readdir(%p, %p)", res, dir, de);
+ return res;
+}
+
+long
+fhandler_registry::telldir (DIR * dir)
+{
+ return dir->__d_position & REG_POSITION_MASK;
+}
+
+void
+fhandler_registry::seekdir (DIR * dir, long loc)
+{
+ /* Unfortunately cannot simply set __d_position due to transition from sub-keys to
+ * values.
+ */
+ rewinddir (dir);
+ while (loc > (dir->__d_position & REG_POSITION_MASK))
+ if (readdir (dir, dir->__d_dirent))
+ break;
+}
+
+void
+fhandler_registry::rewinddir (DIR * dir)
+{
+ if (dir->__handle != INVALID_HANDLE_VALUE)
+ {
+ delete d_hash (dir);
+ RegCloseKey ((HKEY) dir->__handle);
+ dir->__handle = INVALID_HANDLE_VALUE;
+ }
+ dir->__d_position = 0;
+ dir->__flags = dirent_saw_dot | dirent_saw_dot_dot;
+}
+
+int
+fhandler_registry::closedir (DIR * dir)
+{
+ int res = 0;
+ if (dir->__handle != INVALID_HANDLE_VALUE)
+ {
+ delete d_hash (dir);
+ if (RegCloseKey ((HKEY) dir->__handle) != ERROR_SUCCESS)
+ {
+ __seterrno ();
+ res = -1;
+ }
+ }
+ syscall_printf ("%d = closedir(%p)", res, dir);
+ return 0;
+}
+
+int
+fhandler_registry::open (int flags, mode_t mode)
+{
+ int pathlen;
+ const char *file;
+ HKEY handle = (HKEY) INVALID_HANDLE_VALUE;
+
+ int res = fhandler_virtual::open (flags, mode);
+ if (!res)
+ goto out;
+
+ const char *path;
+ path = get_name () + proc_len + 1 + prefix_len;
+ if (!*path)
+ {
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ res = 0;
+ goto out;
+ }
+ else if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ res = 0;
+ goto out;
+ }
+ else
+ {
+ diropen = true;
+ /* Marking as nohandle allows to call dup. */
+ nohandle (true);
+ goto success;
+ }
+ }
+ path++;
+ pathlen = strlen (path);
+ file = path + pathlen - 1;
+ if (isdirsep (*file) && pathlen > 1)
+ file--;
+ while (!isdirsep (*file))
+ file--;
+ file++;
+
+ if (file == path)
+ {
+ for (int i = 0; registry_listing[i]; i++)
+ if (path_prefix_p (registry_listing[i], path,
+ strlen (registry_listing[i]), true))
+ {
+ if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ set_errno (EEXIST);
+ res = 0;
+ goto out;
+ }
+ else if (flags & O_WRONLY)
+ {
+ set_errno (EISDIR);
+ res = 0;
+ goto out;
+ }
+ else
+ {
+ set_handle (fetch_hkey (i));
+ /* Marking as nohandle allows to call dup on pseudo registry
+ handles. */
+ if (get_handle () >= HKEY_CLASSES_ROOT)
+ nohandle (true);
+ diropen = true;
+ goto success;
+ }
+ }
+
+ if (flags & O_CREAT)
+ {
+ set_errno (EROFS);
+ res = 0;
+ }
+ else
+ {
+ set_errno (ENOENT);
+ res = 0;
+ }
+ goto out;
+ }
+
+ if (flags & O_WRONLY)
+ {
+ set_errno (EROFS);
+ res = 0;
+ goto out;
+ }
+ else
+ {
+ wchar_t dec_file[NAME_MAX + 1];
+ int val_only = decode_regname (dec_file, file);
+ if (val_only < 0)
+ {
+ set_errno (EINVAL);
+ res = 0;
+ goto out;
+ }
+
+ if (!val_only)
+ handle = open_key (path, KEY_READ, wow64, false);
+ if (handle == (HKEY) INVALID_HANDLE_VALUE)
+ {
+ if (val_only || get_errno () != EACCES)
+ handle = open_key (path, KEY_READ, wow64, true);
+ if (handle == (HKEY) INVALID_HANDLE_VALUE)
+ {
+ res = 0;
+ goto out;
+ }
+ }
+ else
+ diropen = true;
+
+ set_handle (handle);
+ set_close_on_exec (!!(flags & O_CLOEXEC));
+ value_name = cwcsdup (dec_file);
+
+ if (!diropen && !fill_filebuf ())
+ {
+ RegCloseKey (handle);
+ res = 0;
+ goto out;
+ }
+
+ if (flags & O_APPEND)
+ position = filesize;
+ else
+ position = 0;
+ }
+
+success:
+ res = 1;
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ set_open_status ();
+out:
+ syscall_printf ("%d = fhandler_registry::open(%p, 0%o)", res, flags, mode);
+ return res;
+}
+
+int
+fhandler_registry::close ()
+{
+ int res = fhandler_virtual::close ();
+ if (res != 0)
+ return res;
+ HKEY handle = (HKEY) get_handle ();
+ if (handle != (HKEY) INVALID_HANDLE_VALUE && handle < HKEY_CLASSES_ROOT)
+ {
+ if (RegCloseKey (handle) != ERROR_SUCCESS)
+ {
+ __seterrno ();
+ res = -1;
+ }
+ }
+ if (!have_execed && value_name)
+ {
+ cfree (value_name);
+ value_name = NULL;
+ }
+ return res;
+}
+
+bool
+fhandler_registry::fill_filebuf ()
+{
+ DWORD type, size;
+ LONG error;
+ HKEY handle = (HKEY) get_handle ();
+ size_t bufalloc;
+
+ if (handle != HKEY_PERFORMANCE_DATA)
+ {
+ error = RegQueryValueExW (handle, value_name, NULL, &type, NULL, &size);
+ if (error != ERROR_SUCCESS)
+ {
+ if (error == ERROR_INVALID_HANDLE
+ && !strcasecmp (get_name () + strlen (get_name ())
+ - sizeof (VIRT_CLASSES_KEY) + 1,
+ VIRT_CLASSES_KEY))
+ {
+ filesize = sizeof (VIRT_CLASSES_LINKTGT);
+ filebuf = (char *) cmalloc_abort (HEAP_BUF, filesize);
+ strcpy (filebuf, VIRT_CLASSES_LINKTGT);
+ return true;
+ }
+ if (error != ERROR_FILE_NOT_FOUND)
+ {
+ seterrno_from_win_error (__FILE__, __LINE__, error);
+ return false;
+ }
+ goto value_not_found;
+ }
+ PBYTE tmpbuf = (PBYTE) cmalloc_abort (HEAP_BUF, size);
+ error =
+ RegQueryValueExW (handle, value_name, NULL, NULL, tmpbuf, &size);
+ if (error != ERROR_SUCCESS)
+ {
+ seterrno_from_win_error (__FILE__, __LINE__, error);
+ return true;
+ }
+ if (type == REG_SZ || type == REG_EXPAND_SZ || type == REG_LINK)
+ bufalloc = sys_wcstombs (NULL, 0, (wchar_t *) tmpbuf,
+ size / sizeof (wchar_t)) + 1;
+ else if (type == REG_MULTI_SZ)
+ bufalloc = multi_wcstombs (NULL, 0, (wchar_t *) tmpbuf,
+ size / sizeof (wchar_t));
+ else
+ bufalloc = size;
+ filebuf = (char *) cmalloc_abort (HEAP_BUF, bufalloc);
+ if (type == REG_SZ || type == REG_EXPAND_SZ || type == REG_LINK)
+ sys_wcstombs (filebuf, bufalloc, (wchar_t *) tmpbuf,
+ size / sizeof (wchar_t));
+ else if (type == REG_MULTI_SZ)
+ multi_wcstombs (filebuf, bufalloc, (wchar_t *) tmpbuf,
+ size / sizeof (wchar_t));
+ else
+ memcpy (filebuf, tmpbuf, bufalloc);
+ filesize = bufalloc;
+ }
+ else
+ {
+ bufalloc = 0;
+ do
+ {
+ bufalloc += 16 * 1024;
+ filebuf = (char *) crealloc_abort (filebuf, bufalloc);
+ size = bufalloc;
+ error = RegQueryValueExW (handle, value_name, NULL, &type,
+ (PBYTE) filebuf, &size);
+ if (error != ERROR_SUCCESS && error != ERROR_MORE_DATA)
+ {
+ seterrno_from_win_error (__FILE__, __LINE__, error);
+ return false;
+ }
+ }
+ while (error == ERROR_MORE_DATA);
+ filesize = size;
+ /* RegQueryValueEx () opens HKEY_PERFORMANCE_DATA. */
+ RegCloseKey (handle);
+ }
+ return true;
+value_not_found:
+ DWORD buf_size = NAME_MAX + 1;
+ wchar_t buf[buf_size];
+ int index = 0;
+ while (ERROR_SUCCESS ==
+ (error = RegEnumKeyExW (handle, index++, buf, &buf_size, NULL, NULL,
+ NULL, NULL)) || (error == ERROR_MORE_DATA))
+ {
+ if (!wcscasecmp (buf, value_name))
+ {
+ set_errno (EISDIR);
+ return false;
+ }
+ buf_size = NAME_MAX + 1;
+ }
+ if (error != ERROR_NO_MORE_ITEMS)
+ {
+ seterrno_from_win_error (__FILE__, __LINE__, error);
+ return false;
+ }
+ set_errno (ENOENT);
+ return false;
+}
+
+/* Auxillary member function to open registry keys. */
+static HKEY
+open_key (const char *name, REGSAM access, DWORD wow64, bool isValue)
+{
+ HKEY hKey = (HKEY) INVALID_HANDLE_VALUE;
+ HKEY hParentKey = (HKEY) INVALID_HANDLE_VALUE;
+ bool parentOpened = false;
+ wchar_t component[NAME_MAX + 1];
+
+ while (*name)
+ {
+ const char *anchor = name;
+ while (*name && !isdirsep (*name))
+ name++;
+ int val_only = decode_regname (component, anchor, name - anchor);
+ if (val_only < 0)
+ {
+ set_errno (EINVAL);
+ if (parentOpened)
+ RegCloseKey (hParentKey);
+ hKey = (HKEY) INVALID_HANDLE_VALUE;
+ break;
+ }
+ if (*name)
+ name++;
+ if (*name == 0 && isValue == true)
+ break;
+
+ if (val_only || !component[0] || hKey == HKEY_PERFORMANCE_DATA)
+ {
+ set_errno (ENOENT);
+ if (parentOpened)
+ RegCloseKey (hParentKey);
+ hKey = (HKEY) INVALID_HANDLE_VALUE;
+ break;
+ }
+
+ if (hParentKey != (HKEY) INVALID_HANDLE_VALUE)
+ {
+ REGSAM effective_access = KEY_READ;
+ if ((strchr (name, '/') == NULL && isValue == true) || *name == 0)
+ effective_access = access;
+ LONG error = RegOpenKeyExW (hParentKey, component, 0,
+ effective_access | wow64, &hKey);
+ if (error == ERROR_ACCESS_DENIED) /* Try opening with backup intent */
+ error = RegCreateKeyExW (hParentKey, component, 0, NULL,
+ REG_OPTION_BACKUP_RESTORE,
+ effective_access | wow64, NULL,
+ &hKey, NULL);
+ if (parentOpened)
+ RegCloseKey (hParentKey);
+ if (error != ERROR_SUCCESS)
+ {
+ hKey = (HKEY) INVALID_HANDLE_VALUE;
+ seterrno_from_win_error (__FILE__, __LINE__, error);
+ return hKey;
+ }
+ hParentKey = hKey;
+ parentOpened = true;
+ }
+ else
+ {
+ for (int i = 0; registry_listing[i]; i++)
+ if (strncasematch (anchor, registry_listing[i], name - anchor - 1))
+ hKey = fetch_hkey (i);
+ if (hKey == (HKEY) INVALID_HANDLE_VALUE)
+ return hKey;
+ hParentKey = hKey;
+ }
+ }
+ return hKey;
+}
+
+int
+fhandler_registry::dup (fhandler_base *child, int flags)
+{
+ debug_printf ("here");
+ fhandler_registry *fhs = (fhandler_registry *) child;
+
+ int ret = fhandler_virtual::dup (fhs, flags);
+ /* Pseudo registry handles can't be duplicated using DuplicateHandle.
+ Therefore those fhandlers are marked with the nohandle flag. This
+ allows fhandler_base::dup to succeed as usual for nohandle fhandlers.
+ Here we just have to fix up by copying the pseudo handle value. */
+ if ((HKEY) get_handle () >= HKEY_CLASSES_ROOT)
+ fhs->set_handle (get_handle ());
+ if (value_name)
+ fhs->value_name = cwcsdup (value_name);
+ return ret;
+}
diff --git a/winsup/cygwin/fhandler/serial.cc b/winsup/cygwin/fhandler/serial.cc
new file mode 100644
index 000000000..174a57a43
--- /dev/null
+++ b/winsup/cygwin/fhandler/serial.cc
@@ -0,0 +1,1138 @@
+/* fhandler_serial.cc
+
+
+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 <unistd.h>
+#include <sys/param.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "sigproc.h"
+#include "pinfo.h"
+#include <asm/socket.h>
+#include <devioctl.h>
+#include <ntddser.h>
+#include "cygwait.h"
+
+/**********************************************************************/
+/* fhandler_serial */
+
+fhandler_serial::fhandler_serial ()
+ : fhandler_base (), vmin_ (0), vtime_ (0), pgrp_ (myself->pgid)
+{
+ need_fork_fixup (true);
+}
+
+void
+fhandler_serial::raw_read (void *ptr, size_t& ulen)
+{
+ OVERLAPPED ov = { 0 };
+ DWORD io_err;
+ COMSTAT st;
+ DWORD bytes_to_read, read_bytes;
+ ssize_t tot = 0;
+
+ if (ulen > SSIZE_MAX)
+ ulen = SSIZE_MAX;
+ if (ulen == 0)
+ return;
+
+ /* If MIN > 0 in blocking mode, we have to read at least VMIN chars,
+ otherwise we're in polling mode and there's no minimum chars. */
+ ssize_t minchars = (!is_nonblocking () && vmin_) ? MIN (vmin_, ulen) : 0;
+
+ debug_printf ("ulen %ld, vmin_ %u, vtime_ %u", ulen, vmin_, vtime_);
+
+ ov.hEvent = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL);
+
+ do
+ {
+ /* First check if chars are already in the inbound queue. */
+ if (!ClearCommError (get_handle (), &io_err, &st))
+ goto err;
+ /* FIXME: In case of I/O error, do we really want to bail out or is it
+ better just trying to pull through? */
+ if (io_err)
+ {
+ termios_printf ("error detected %x", io_err);
+ SetLastError (ERROR_IO_DEVICE);
+ goto err;
+ }
+ /* ReadFile only handles up to DWORD bytes. */
+ bytes_to_read = MIN (ulen, UINT32_MAX);
+ if (is_nonblocking ())
+ {
+ /* In O_NONBLOCK mode we just care for the number of chars already
+ in the inbound queue. */
+ if (!st.cbInQue)
+ break;
+ bytes_to_read = MIN (st.cbInQue, bytes_to_read);
+ }
+ else
+ {
+ /* If the number of chars in the inbound queue is sufficent
+ (minchars defines the minimum), set bytes_to_read accordingly
+ and don't wait. */
+ if (st.cbInQue && (ssize_t) st.cbInQue >= minchars)
+ bytes_to_read = MIN (st.cbInQue, bytes_to_read);
+ }
+
+ ResetEvent (ov.hEvent);
+ if (!ReadFile (get_handle (), ptr, bytes_to_read, &read_bytes, &ov))
+ {
+ if (GetLastError () != ERROR_IO_PENDING)
+ goto err;
+ if (is_nonblocking ())
+ {
+ CancelIo (get_handle ());
+ if (!GetOverlappedResult (get_handle (), &ov, &read_bytes,
+ TRUE))
+ goto err;
+ }
+ else
+ {
+ switch (cygwait (ov.hEvent))
+ {
+ default: /* Handle an error case from cygwait basically like
+ a cancel condition and see if we got "something" */
+ CancelIo (get_handle ());
+ fallthrough;
+ case WAIT_OBJECT_0:
+ if (!GetOverlappedResult (get_handle (), &ov, &read_bytes,
+ TRUE))
+ goto err;
+ debug_printf ("got %u bytes from ReadFile", read_bytes);
+ break;
+ case WAIT_SIGNALED:
+ CancelIo (get_handle ());
+ if (!GetOverlappedResult (get_handle (), &ov, &read_bytes,
+ TRUE))
+ goto err;
+ /* Only if no bytes read, return with EINTR. */
+ if (!tot && !read_bytes)
+ {
+ tot = -1;
+ set_sig_errno (EINTR);
+ debug_printf ("signal received, set EINTR");
+ }
+ else
+ debug_printf ("signal received but ignored");
+ goto out;
+ case WAIT_CANCELED:
+ CancelIo (get_handle ());
+ GetOverlappedResult (get_handle (), &ov, &read_bytes, TRUE);
+ debug_printf ("thread canceled");
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ }
+ }
+ }
+ tot += read_bytes;
+ ptr = (void *) ((caddr_t) ptr + read_bytes);
+ ulen -= read_bytes;
+ minchars -= read_bytes;
+ debug_printf ("vtime_ %u, vmin_ %u, read_bytes %u, tot %D",
+ vtime_, vmin_, read_bytes, tot);
+ continue;
+
+ err:
+ debug_printf ("err %E");
+ if (GetLastError () != ERROR_OPERATION_ABORTED)
+ {
+ if (tot == 0)
+ {
+ tot = -1;
+ __seterrno ();
+ }
+ break;
+ }
+ }
+ /* ALL of these are required to loop:
+
+ Still room in user space buffer
+ AND still a minchars requirement (implies blocking mode)
+ AND vtime_ is not set. */
+ while (ulen > 0 && minchars > 0 && vtime_ == 0);
+
+out:
+ CloseHandle (ov.hEvent);
+ ulen = (size_t) tot;
+ if (is_nonblocking () && ulen == 0)
+ {
+ ulen = (size_t) -1;
+ set_errno (EAGAIN);
+ }
+}
+
+/* Cover function to WriteFile to provide Posix interface and semantics
+ (as much as possible). */
+ssize_t
+fhandler_serial::raw_write (const void *ptr, size_t len)
+{
+ DWORD bytes_written;
+ OVERLAPPED write_status;
+
+ memset (&write_status, 0, sizeof (write_status));
+ write_status.hEvent = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL);
+ ProtectHandle (write_status.hEvent);
+
+ for (;;)
+ {
+ if (WriteFile (get_handle (), ptr, len, &bytes_written, &write_status))
+ break;
+
+ switch (GetLastError ())
+ {
+ case ERROR_OPERATION_ABORTED:
+ DWORD ev;
+ if (!ClearCommError (get_handle (), &ev, NULL))
+ goto err;
+ if (ev)
+ termios_printf ("error detected %x", ev);
+ continue;
+ case ERROR_IO_PENDING:
+ break;
+ default:
+ goto err;
+ }
+
+ if (!is_nonblocking ())
+ {
+ switch (cygwait (write_status.hEvent))
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_SIGNALED:
+ PurgeComm (get_handle (), PURGE_TXABORT);
+ set_sig_errno (EINTR);
+ ForceCloseHandle (write_status.hEvent);
+ return -1;
+ case WAIT_CANCELED:
+ PurgeComm (get_handle (), PURGE_TXABORT);
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ default:
+ goto err;
+ }
+ }
+ if (!GetOverlappedResult (get_handle (), &write_status, &bytes_written, TRUE))
+ goto err;
+
+ break;
+ }
+
+ ForceCloseHandle (write_status.hEvent);
+
+ return bytes_written;
+
+err:
+ __seterrno ();
+ ForceCloseHandle (write_status.hEvent);
+ return -1;
+}
+
+int
+fhandler_serial::init (HANDLE f, DWORD flags, mode_t bin)
+{
+ return open (flags, bin & (O_BINARY | O_TEXT));
+}
+
+int
+fhandler_serial::open (int flags, mode_t mode)
+{
+ int res;
+ COMMTIMEOUTS to;
+
+ syscall_printf ("fhandler_serial::open (%s, %y, 0%o)",
+ get_name (), flags, mode);
+
+ if (!fhandler_base::open (flags, mode))
+ return 0;
+
+ res = 1;
+
+ SetCommMask (get_handle (), EV_RXCHAR);
+
+ memset (&to, 0, sizeof (to));
+ SetCommTimeouts (get_handle (), &to);
+
+ /* Reset serial port to known state of 9600-8-1-no flow control
+ on open for better behavior under Win 95.
+
+ FIXME: This should only be done when explicitly opening the com
+ port. It should not be reset if an fd is inherited.
+ Using __progname in this way, to determine how far along in the
+ initialization we are, is really a terrible kludge and should
+ be fixed ASAP.
+ */
+ if (reset_com && __progname)
+ {
+ DCB state;
+ GetCommState (get_handle (), &state);
+ syscall_printf ("setting initial state on %s (reset_com %d)",
+ get_name (), reset_com);
+ state.BaudRate = CBR_9600;
+ state.ByteSize = 8;
+ state.StopBits = ONESTOPBIT;
+ state.Parity = NOPARITY; /* FIXME: correct default? */
+ state.fBinary = TRUE; /* binary xfer */
+ state.EofChar = 0; /* no end-of-data in binary mode */
+ state.fNull = FALSE; /* don't discard nulls in binary mode */
+ state.fParity = FALSE; /* ignore parity errors */
+ state.fErrorChar = FALSE;
+ state.fTXContinueOnXoff = TRUE; /* separate TX and RX flow control */
+ state.fOutX = FALSE; /* disable transmission flow control */
+ state.fInX = FALSE; /* disable reception flow control */
+ state.XonChar = 0x11;
+ state.XoffChar = 0x13;
+ state.fOutxDsrFlow = FALSE; /* disable DSR flow control */
+ state.fRtsControl = RTS_CONTROL_ENABLE; /* ignore lead control except
+ DTR */
+ state.fOutxCtsFlow = FALSE; /* disable output flow control */
+ state.fDtrControl = DTR_CONTROL_ENABLE; /* assert DTR */
+ state.fDsrSensitivity = FALSE; /* don't assert DSR */
+ state.fAbortOnError = TRUE;
+ if (!SetCommState (get_handle (), &state))
+ system_printf ("couldn't set initial state for %s, %E", get_name ());
+ }
+
+ SetCommMask (get_handle (), EV_RXCHAR);
+ set_open_status ();
+ syscall_printf ("%p = fhandler_serial::open (%s, %y, 0%o)",
+ res, get_name (), flags, mode);
+ return res;
+}
+
+/* tcsendbreak: POSIX 7.2.2.1 */
+/* Break for 250-500 milliseconds if duration == 0 */
+/* Otherwise, units for duration are undefined */
+int
+fhandler_serial::tcsendbreak (int duration)
+{
+ unsigned int sleeptime = 300000;
+
+ if (duration > 0)
+ sleeptime *= duration;
+
+ if (SetCommBreak (get_handle ()) == 0)
+ return -1;
+
+ /* FIXME: need to send zero bits during duration */
+ usleep (sleeptime);
+
+ if (ClearCommBreak (get_handle ()) == 0)
+ return -1;
+
+ syscall_printf ("0 = fhandler_serial:tcsendbreak (%d)", duration);
+
+ return 0;
+}
+
+/* tcdrain: POSIX 7.2.2.1 */
+int
+fhandler_serial::tcdrain ()
+{
+ if (FlushFileBuffers (get_handle ()) == 0)
+ return -1;
+
+ return 0;
+}
+
+/* tcflow: POSIX 7.2.2.1 */
+int
+fhandler_serial::tcflow (int action)
+{
+ DWORD win32action = 0;
+ DCB dcb;
+ char xchar;
+
+ termios_printf ("action %d", action);
+
+ switch (action)
+ {
+ case TCOOFF:
+ win32action = SETXOFF;
+ break;
+ case TCOON:
+ win32action = SETXON;
+ break;
+ case TCION:
+ case TCIOFF:
+ if (GetCommState (get_handle (), &dcb) == 0)
+ return -1;
+ if (action == TCION)
+ xchar = (dcb.XonChar ? dcb.XonChar : 0x11);
+ else
+ xchar = (dcb.XoffChar ? dcb.XoffChar : 0x13);
+ if (TransmitCommChar (get_handle (), xchar) == 0)
+ return -1;
+ return 0;
+ break;
+ default:
+ return -1;
+ break;
+ }
+
+ if (EscapeCommFunction (get_handle (), win32action) == 0)
+ return -1;
+
+ return 0;
+}
+
+
+/* switch_modem_lines: set or clear RTS and/or DTR */
+int
+fhandler_serial::switch_modem_lines (int set, int clr)
+{
+ int res = 0;
+
+ if (set & TIOCM_RTS)
+ {
+ if (EscapeCommFunction (get_handle (), SETRTS))
+ rts = TIOCM_RTS;
+ else
+ {
+ __seterrno ();
+ res = -1;
+ }
+ }
+ else if (clr & TIOCM_RTS)
+ {
+ if (EscapeCommFunction (get_handle (), CLRRTS))
+ rts = 0;
+ else
+ {
+ __seterrno ();
+ res = -1;
+ }
+ }
+ if (set & TIOCM_DTR)
+ {
+ if (EscapeCommFunction (get_handle (), SETDTR))
+ rts = TIOCM_DTR;
+ else
+ {
+ __seterrno ();
+ res = -1;
+ }
+ }
+ else if (clr & TIOCM_DTR)
+ {
+ if (EscapeCommFunction (get_handle (), CLRDTR))
+ rts = 0;
+ else
+ {
+ __seterrno ();
+ res = -1;
+ }
+ }
+
+ return res;
+}
+
+/* ioctl: */
+int
+fhandler_serial::ioctl (unsigned int cmd, void *buf)
+{
+ int res = 0;
+
+# define ibuf ((int) (intptr_t) buf)
+# define ipbuf (*(int *) buf)
+
+ DWORD ev;
+ COMSTAT st;
+ if (!ClearCommError (get_handle (), &ev, &st))
+ {
+ __seterrno ();
+ res = -1;
+ }
+ else
+ switch (cmd)
+ {
+ case TCFLSH:
+ res = tcflush (ibuf);
+ break;
+ case TIOCMGET:
+ DWORD modem_lines;
+ if (!GetCommModemStatus (get_handle (), &modem_lines))
+ {
+ __seterrno ();
+ res = -1;
+ }
+ else
+ {
+ ipbuf = 0;
+ if (modem_lines & MS_CTS_ON)
+ ipbuf |= TIOCM_CTS;
+ if (modem_lines & MS_DSR_ON)
+ ipbuf |= TIOCM_DSR;
+ if (modem_lines & MS_RING_ON)
+ ipbuf |= TIOCM_RI;
+ if (modem_lines & MS_RLSD_ON)
+ ipbuf |= TIOCM_CD;
+
+ DWORD cb;
+ DWORD mcr;
+ if (!DeviceIoControl (get_handle (), IOCTL_SERIAL_GET_DTRRTS,
+ NULL, 0, &mcr, 4, &cb, 0) || cb != 4)
+ ipbuf |= rts | dtr;
+ else
+ {
+ if (mcr & 2)
+ ipbuf |= TIOCM_RTS;
+ if (mcr & 1)
+ ipbuf |= TIOCM_DTR;
+ }
+ }
+ break;
+ case TIOCMSET:
+ if (switch_modem_lines (ipbuf, ~ipbuf))
+ res = -1;
+ break;
+ case TIOCMBIS:
+ if (switch_modem_lines (ipbuf, 0))
+ res = -1;
+ break;
+ case TIOCMBIC:
+ if (switch_modem_lines (0, ipbuf))
+ res = -1;
+ break;
+ case TIOCCBRK:
+ if (ClearCommBreak (get_handle ()) == 0)
+ {
+ __seterrno ();
+ res = -1;
+ }
+ break;
+ case TIOCSBRK:
+ if (SetCommBreak (get_handle ()) == 0)
+ {
+ __seterrno ();
+ res = -1;
+ }
+ break;
+ case TIOCINQ:
+ ipbuf = st.cbInQue;
+ break;
+ case TIOCGWINSZ:
+ ((struct winsize *) buf)->ws_row = 0;
+ ((struct winsize *) buf)->ws_col = 0;
+ break;
+ case FIONREAD:
+ set_errno (ENOTSUP);
+ res = -1;
+ break;
+ default:
+ res = fhandler_base::ioctl (cmd, buf);
+ break;
+ }
+
+ termios_printf ("%d = ioctl(%x, %p)", res, cmd, buf);
+# undef ibuf
+# undef ipbuf
+ return res;
+}
+
+/* tcflush: POSIX 7.2.2.1 */
+int
+fhandler_serial::tcflush (int queue)
+{
+ DWORD flags;
+
+ switch (queue)
+ {
+ case TCOFLUSH:
+ flags = PURGE_TXABORT | PURGE_TXCLEAR;
+ break;
+ case TCIFLUSH:
+ flags = PURGE_RXABORT | PURGE_RXCLEAR;
+ break;
+ case TCIOFLUSH:
+ flags = PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR;
+ break;
+ default:
+ termios_printf ("Invalid tcflush queue %d", queue);
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ if (!PurgeComm (get_handle (), flags))
+ {
+ __seterrno ();
+ return -1;
+ }
+
+ return 0;
+}
+
+/* tcsetattr: POSIX 7.2.1.1 */
+int
+fhandler_serial::tcsetattr (int action, const struct termios *t)
+{
+ /* Possible actions:
+ TCSANOW: immediately change attributes.
+ TCSADRAIN: flush output, then change attributes.
+ TCSAFLUSH: flush output and discard input, then change attributes.
+ */
+
+ bool dropDTR = false;
+ COMMTIMEOUTS to;
+ DCB ostate, state;
+ int tmpDtr, tmpRts, res;
+ res = tmpDtr = tmpRts = 0;
+
+ termios_printf ("action %d", action);
+ if ((action == TCSADRAIN) || (action == TCSAFLUSH))
+ {
+ FlushFileBuffers (get_handle ());
+ termios_printf ("flushed file buffers");
+ }
+ if (action == TCSAFLUSH)
+ PurgeComm (get_handle (), (PURGE_RXABORT | PURGE_RXCLEAR));
+
+ /* get default/last comm state */
+ if (!GetCommState (get_handle (), &ostate))
+ return -1;
+
+ state = ostate;
+
+ /* -------------- Set baud rate ------------------ */
+ /* FIXME: WIN32 also has 14400, 56000, 128000, and 256000.
+ Unix also has 230400. */
+
+ switch (t->c_ospeed)
+ {
+ case B0:
+ /* Drop DTR - but leave DCB-resident bitrate as-is since
+ 0 is an invalid bitrate in Win32 */
+ dropDTR = true;
+ break;
+ case B110:
+ state.BaudRate = CBR_110;
+ break;
+ case B300:
+ state.BaudRate = CBR_300;
+ break;
+ case B600:
+ state.BaudRate = CBR_600;
+ break;
+ case B1200:
+ state.BaudRate = CBR_1200;
+ break;
+ case B2400:
+ state.BaudRate = CBR_2400;
+ break;
+ case B4800:
+ state.BaudRate = CBR_4800;
+ break;
+ case B9600:
+ state.BaudRate = CBR_9600;
+ break;
+ case B19200:
+ state.BaudRate = CBR_19200;
+ break;
+ case B38400:
+ state.BaudRate = CBR_38400;
+ break;
+ case B57600:
+ state.BaudRate = CBR_57600;
+ break;
+ case B115200:
+ state.BaudRate = CBR_115200;
+ break;
+ case B128000:
+ state.BaudRate = CBR_128000;
+ break;
+ case B230400:
+ state.BaudRate = 230400 /* CBR_230400 - not defined */;
+ break;
+ case B256000:
+ state.BaudRate = CBR_256000;
+ break;
+ case B460800:
+ state.BaudRate = 460800 /* CBR_460800 - not defined */;
+ break;
+ case B500000:
+ state.BaudRate = 500000 /* CBR_500000 - not defined */;
+ break;
+ case B576000:
+ state.BaudRate = 576000 /* CBR_576000 - not defined */;
+ break;
+ case B921600:
+ state.BaudRate = 921600 /* CBR_921600 - not defined */;
+ break;
+ case B1000000:
+ state.BaudRate = 1000000 /* CBR_1000000 - not defined */;
+ break;
+ case B1152000:
+ state.BaudRate = 1152000 /* CBR_1152000 - not defined */;
+ break;
+ case B1500000:
+ state.BaudRate = 1500000 /* CBR_1500000 - not defined */;
+ break;
+ case B2000000:
+ state.BaudRate = 2000000 /* CBR_2000000 - not defined */;
+ break;
+ case B2500000:
+ state.BaudRate = 2500000 /* CBR_2500000 - not defined */;
+ break;
+ case B3000000:
+ state.BaudRate = 3000000 /* CBR_3000000 - not defined */;
+ break;
+ default:
+ /* Unsupported baud rate! */
+ termios_printf ("Invalid t->c_ospeed %u", t->c_ospeed);
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ /* -------------- Set byte size ------------------ */
+
+ switch (t->c_cflag & CSIZE)
+ {
+ case CS5:
+ state.ByteSize = 5;
+ break;
+ case CS6:
+ state.ByteSize = 6;
+ break;
+ case CS7:
+ state.ByteSize = 7;
+ break;
+ case CS8:
+ state.ByteSize = 8;
+ break;
+ default:
+ /* Unsupported byte size! */
+ termios_printf ("Invalid t->c_cflag byte size %u",
+ t->c_cflag & CSIZE);
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ /* -------------- Set stop bits ------------------ */
+
+ if (t->c_cflag & CSTOPB)
+ state.StopBits = TWOSTOPBITS;
+ else
+ state.StopBits = ONESTOPBIT;
+
+ /* -------------- Set parity ------------------ */
+
+ if (t->c_cflag & PARENB)
+ {
+ if(t->c_cflag & CMSPAR)
+ state.Parity = (t->c_cflag & PARODD) ? MARKPARITY : SPACEPARITY;
+ else
+ state.Parity = (t->c_cflag & PARODD) ? ODDPARITY : EVENPARITY;
+ }
+ else
+ state.Parity = NOPARITY;
+
+ state.fBinary = TRUE; /* Binary transfer */
+ state.EofChar = 0; /* No end-of-data in binary mode */
+ state.fNull = FALSE; /* Don't discard nulls in binary mode */
+
+ /* -------------- Parity errors ------------------ */
+ /* fParity combines the function of INPCK and NOT IGNPAR */
+
+ if ((t->c_iflag & INPCK) && !(t->c_iflag & IGNPAR))
+ state.fParity = TRUE; /* detect parity errors */
+ else
+ state.fParity = FALSE; /* ignore parity errors */
+
+ /* Only present in Win32, Unix has no equivalent */
+ state.fErrorChar = FALSE;
+ state.ErrorChar = 0;
+
+ /* -------------- Set software flow control ------------------ */
+ /* Set fTXContinueOnXoff to FALSE. This prevents the triggering of a
+ premature XON when the remote device interprets a received character
+ as XON (same as IXANY on the remote side). Otherwise, a TRUE
+ value separates the TX and RX functions. */
+
+ state.fTXContinueOnXoff = TRUE; /* separate TX and RX flow control */
+
+ /* Transmission flow control */
+ if (t->c_iflag & IXON)
+ state.fOutX = TRUE; /* enable */
+ else
+ state.fOutX = FALSE; /* disable */
+
+ /* Reception flow control */
+ if (t->c_iflag & IXOFF)
+ state.fInX = TRUE; /* enable */
+ else
+ state.fInX = FALSE; /* disable */
+
+ /* XoffLim and XonLim are left at default values */
+
+ state.XonChar = (t->c_cc[VSTART] ? t->c_cc[VSTART] : 0x11);
+ state.XoffChar = (t->c_cc[VSTOP] ? t->c_cc[VSTOP] : 0x13);
+
+ /* -------------- Set hardware flow control ------------------ */
+
+ /* Disable DSR flow control */
+ state.fOutxDsrFlow = FALSE;
+
+ /* Some old flavors of Unix automatically enabled hardware flow
+ control when software flow control was not enabled. Since newer
+ Unices tend to require explicit setting of hardware flow-control,
+ this is what we do. */
+
+ /* RTS/CTS flow control */
+ if (t->c_cflag & CRTSCTS)
+ { /* enable */
+ state.fOutxCtsFlow = TRUE;
+ state.fRtsControl = RTS_CONTROL_HANDSHAKE;
+ }
+ else
+ { /* disable */
+ state.fRtsControl = RTS_CONTROL_ENABLE;
+ state.fOutxCtsFlow = FALSE;
+ tmpRts = TIOCM_RTS;
+ }
+
+ if (t->c_cflag & CRTSXOFF)
+ state.fRtsControl = RTS_CONTROL_HANDSHAKE;
+
+ /* -------------- DTR ------------------ */
+ /* Assert DTR on device open */
+
+ state.fDtrControl = DTR_CONTROL_ENABLE;
+
+ /* -------------- DSR ------------------ */
+ /* Assert DSR at the device? */
+
+ if (t->c_cflag & CLOCAL)
+ state.fDsrSensitivity = FALSE; /* no */
+ else
+ state.fDsrSensitivity = TRUE; /* yes */
+
+ /* -------------- Error handling ------------------ */
+ /* Since read/write operations terminate upon error, we
+ will use ClearCommError() to resume. */
+
+ state.fAbortOnError = TRUE;
+
+ if ((memcmp (&ostate, &state, sizeof (state)) != 0)
+ && !SetCommState (get_handle (), &state))
+ {
+ /* SetCommState() failed, usually due to invalid DCB param.
+ Keep track of this so we can set errno to EINVAL later
+ and return failure */
+ termios_printf ("SetCommState() failed, %E");
+ __seterrno ();
+ res = -1;
+ }
+
+ rbinary ((t->c_iflag & IGNCR) ? false : true);
+ wbinary ((t->c_oflag & ONLCR) ? false : true);
+
+ if (dropDTR)
+ {
+ EscapeCommFunction (get_handle (), CLRDTR);
+ tmpDtr = 0;
+ }
+ else
+ {
+ /* FIXME: Sometimes when CLRDTR is set, setting
+ state.fDtrControl = DTR_CONTROL_ENABLE will fail. This
+ is a problem since a program might want to change some
+ parameters while DTR is still down. */
+
+ EscapeCommFunction (get_handle (), SETDTR);
+ tmpDtr = TIOCM_DTR;
+ }
+
+ rts = tmpRts;
+ dtr = tmpDtr;
+
+ /* The following documentation on was taken from "Linux Serial Programming
+ HOWTO". It explains how MIN (t->c_cc[VMIN] || vmin_) and TIME
+ (t->c_cc[VTIME] || vtime_) is to be used.
+
+ In non-canonical input processing mode, input is not assembled into
+ lines and input processing (erase, kill, delete, etc.) does not
+ occur. Two parameters control the behavior of this mode: c_cc[VTIME]
+ sets the character timer, and c_cc[VMIN] sets the minimum number of
+ characters to receive before satisfying the read.
+
+ If MIN > 0 and TIME = 0, MIN sets the number of characters to receive
+ before the read is satisfied. As TIME is zero, the timer is not used.
+
+ If MIN = 0 and TIME > 0, TIME serves as a timeout value. The read will
+ be satisfied if a single character is read, or TIME is exceeded (t =
+ TIME *0.1 s). If TIME is exceeded, no character will be returned.
+
+ If MIN > 0 and TIME > 0, TIME serves as an inter-character timer. The
+ read will be satisfied if MIN characters are received, or the time
+ between two characters exceeds TIME. The timer is restarted every time
+ a character is received and only becomes active after the first
+ character has been received.
+
+ If MIN = 0 and TIME = 0, read will be satisfied immediately. The
+ number of characters currently available, or the number of characters
+ requested will be returned. According to Antonino (see contributions),
+ you could issue a fcntl(fd, F_SETFL, FNDELAY); before reading to get
+ the same result.
+ */
+
+ if (t->c_lflag & ICANON)
+ {
+ vmin_ = 0;
+ vtime_ = 0;
+ }
+ else
+ {
+ vtime_ = t->c_cc[VTIME];
+ vmin_ = t->c_cc[VMIN];
+ }
+
+ debug_printf ("vtime %u, vmin %u", vtime_, vmin_);
+
+ memset (&to, 0, sizeof (to));
+
+ if ((vmin_ > 0) && (vtime_ == 0))
+ {
+ /* Returns immediately with whatever is in buffer on a ReadFile();
+ or blocks if nothing found. We will keep calling ReadFile(); until
+ vmin_ characters are read */
+ to.ReadIntervalTimeout = to.ReadTotalTimeoutMultiplier = MAXDWORD;
+ to.ReadTotalTimeoutConstant = MAXDWORD - 1;
+ }
+ else if ((vmin_ == 0) && (vtime_ > 0))
+ {
+ /* set timeoout constant appropriately and we will only try to
+ read one character in ReadFile() */
+ to.ReadTotalTimeoutConstant = vtime_ * 100;
+ to.ReadIntervalTimeout = to.ReadTotalTimeoutMultiplier = MAXDWORD;
+ }
+ else if ((vmin_ > 0) && (vtime_ > 0))
+ {
+ /* time applies to the interval time for this case */
+ to.ReadIntervalTimeout = vtime_ * 100;
+ }
+ else if ((vmin_ == 0) && (vtime_ == 0))
+ {
+ /* returns immediately with whatever is in buffer as per
+ Time-Outs docs in Win32 SDK API docs */
+ to.ReadIntervalTimeout = MAXDWORD;
+ }
+
+ debug_printf ("ReadTotalTimeoutConstant %u, ReadIntervalTimeout %u, "
+ "ReadTotalTimeoutMultiplier %u", to.ReadTotalTimeoutConstant,
+ to.ReadIntervalTimeout, to.ReadTotalTimeoutMultiplier);
+
+ if (!SetCommTimeouts(get_handle (), &to))
+ {
+ /* SetCommTimeouts() failed. Keep track of this so we
+ can set errno to EINVAL later and return failure */
+ termios_printf ("SetCommTimeouts() failed, %E");
+ __seterrno ();
+ res = -1;
+ }
+
+ return res;
+}
+
+/* tcgetattr: POSIX 7.2.1.1 */
+int
+fhandler_serial::tcgetattr (struct termios *t)
+{
+ DCB state;
+
+ /* Get current Win32 comm state */
+ if (GetCommState (get_handle (), &state) == 0)
+ return -1;
+
+ /* for safety */
+ memset (t, 0, sizeof (*t));
+
+ t->c_cflag = 0;
+ /* -------------- Baud rate ------------------ */
+ switch (state.BaudRate)
+ {
+ case CBR_110:
+ t->c_ospeed = t->c_ispeed = B110;
+ break;
+ case CBR_300:
+ t->c_ospeed = t->c_ispeed = B300;
+ break;
+ case CBR_600:
+ t->c_ospeed = t->c_ispeed = B600;
+ break;
+ case CBR_1200:
+ t->c_ospeed = t->c_ispeed = B1200;
+ break;
+ case CBR_2400:
+ t->c_ospeed = t->c_ispeed = B2400;
+ break;
+ case CBR_4800:
+ t->c_ospeed = t->c_ispeed = B4800;
+ break;
+ case CBR_9600:
+ t->c_ospeed = t->c_ispeed = B9600;
+ break;
+ case CBR_19200:
+ t->c_ospeed = t->c_ispeed = B19200;
+ break;
+ case CBR_38400:
+ t->c_ospeed = t->c_ispeed = B38400;
+ break;
+ case CBR_57600:
+ t->c_ospeed = t->c_ispeed = B57600;
+ break;
+ case CBR_115200:
+ t->c_ospeed = t->c_ispeed = B115200;
+ break;
+ case CBR_128000:
+ t->c_ospeed = t->c_ispeed = B128000;
+ break;
+ case 230400: /* CBR_230400 - not defined */
+ t->c_ospeed = t->c_ispeed = B230400;
+ break;
+ case CBR_256000:
+ t->c_ospeed = t->c_ispeed = B256000;
+ break;
+ case 460800: /* CBR_460000 - not defined */
+ t->c_ospeed = t->c_ispeed = B460800;
+ break;
+ case 500000: /* CBR_500000 - not defined */
+ t->c_ospeed = t->c_ispeed = B500000;
+ break;
+ case 576000: /* CBR_576000 - not defined */
+ t->c_ospeed = t->c_ispeed = B576000;
+ break;
+ case 921600: /* CBR_921600 - not defined */
+ t->c_ospeed = t->c_ispeed = B921600;
+ break;
+ case 1000000: /* CBR_1000000 - not defined */
+ t->c_ospeed = t->c_ispeed = B1000000;
+ break;
+ case 1152000: /* CBR_1152000 - not defined */
+ t->c_ospeed = t->c_ispeed = B1152000;
+ break;
+ case 1500000: /* CBR_1500000 - not defined */
+ t->c_ospeed = t->c_ispeed = B1500000;
+ break;
+ case 2000000: /* CBR_2000000 - not defined */
+ t->c_ospeed = t->c_ispeed = B2000000;
+ break;
+ case 2500000: /* CBR_2500000 - not defined */
+ t->c_ospeed = t->c_ispeed = B2500000;
+ break;
+ case 3000000: /* CBR_3000000 - not defined */
+ t->c_ospeed = t->c_ispeed = B3000000;
+ break;
+ default:
+ /* Unsupported baud rate! */
+ termios_printf ("Invalid baud rate %u", state.BaudRate);
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ /* -------------- Byte size ------------------ */
+
+ switch (state.ByteSize)
+ {
+ case 5:
+ t->c_cflag |= CS5;
+ break;
+ case 6:
+ t->c_cflag |= CS6;
+ break;
+ case 7:
+ t->c_cflag |= CS7;
+ break;
+ case 8:
+ t->c_cflag |= CS8;
+ break;
+ default:
+ /* Unsupported byte size! */
+ termios_printf ("Invalid byte size %u", state.ByteSize);
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ /* -------------- Stop bits ------------------ */
+
+ if (state.StopBits == TWOSTOPBITS)
+ t->c_cflag |= CSTOPB;
+
+ /* -------------- Parity ------------------ */
+
+ if (state.Parity == ODDPARITY)
+ t->c_cflag |= (PARENB | PARODD);
+ if (state.Parity == EVENPARITY)
+ t->c_cflag |= PARENB;
+ if (state.Parity == MARKPARITY)
+ t->c_cflag |= (PARENB | PARODD | CMSPAR);
+ if (state.Parity == SPACEPARITY)
+ t->c_cflag |= (PARENB | CMSPAR);
+
+ /* -------------- Parity errors ------------------ */
+
+ /* fParity combines the function of INPCK and NOT IGNPAR */
+ if (state.fParity)
+ t->c_iflag |= INPCK;
+ else
+ t->c_iflag |= IGNPAR; /* not necessarily! */
+
+ /* -------------- Software flow control ------------------ */
+
+ /* transmission flow control */
+ if (state.fOutX)
+ t->c_iflag |= IXON;
+
+ /* reception flow control */
+ if (state.fInX)
+ t->c_iflag |= IXOFF;
+
+ t->c_cc[VSTART] = (state.XonChar ? state.XonChar : 0x11);
+ t->c_cc[VSTOP] = (state.XoffChar ? state.XoffChar : 0x13);
+
+ /* -------------- Hardware flow control ------------------ */
+ /* Some old flavors of Unix automatically enabled hardware flow
+ control when software flow control was not enabled. Since newer
+ Unices tend to require explicit setting of hardware flow-control,
+ this is what we do. */
+
+ /* Input flow-control */
+ if ((state.fRtsControl == RTS_CONTROL_HANDSHAKE) && state.fOutxCtsFlow)
+ t->c_cflag |= CRTSCTS;
+ if (state.fRtsControl == RTS_CONTROL_HANDSHAKE)
+ t->c_cflag |= CRTSXOFF;
+
+ /* -------------- CLOCAL --------------- */
+ /* DSR is only lead toggled only by CLOCAL. Check it to see if
+ CLOCAL was called. */
+ /* FIXME: If tcsetattr() hasn't been called previously, this may
+ give a false CLOCAL. */
+
+ if (!state.fDsrSensitivity)
+ t->c_cflag |= CLOCAL;
+
+ /* FIXME: need to handle IGNCR */
+#if 0
+ if (!rbinary ())
+ t->c_iflag |= IGNCR;
+#endif
+
+ if (!wbinary ())
+ t->c_oflag |= ONLCR;
+
+ t->c_cc[VTIME] = vtime_;
+ t->c_cc[VMIN] = vmin_;
+
+ debug_printf ("vmin_ %u, vtime_ %u", vmin_, vtime_);
+
+ return 0;
+}
diff --git a/winsup/cygwin/fhandler/signalfd.cc b/winsup/cygwin/fhandler/signalfd.cc
new file mode 100644
index 000000000..bdd8bc93e
--- /dev/null
+++ b/winsup/cygwin/fhandler/signalfd.cc
@@ -0,0 +1,160 @@
+/* fhandler_signalfd.cc: fhandler for signalfd
+
+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 "path.h"
+#include "fhandler.h"
+#include "pinfo.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "sigproc.h"
+#include <cygwin/signal.h>
+#include <sys/signalfd.h>
+
+fhandler_signalfd::fhandler_signalfd () :
+ fhandler_base (),
+ sigset (0)
+{
+}
+
+char *
+fhandler_signalfd::get_proc_fd_name (char *buf)
+{
+ return strcpy (buf, "anon_inode:[signalfd]");
+}
+
+int
+fhandler_signalfd::signalfd (const sigset_t *mask, int flags)
+{
+ __try
+ {
+ sigset = *mask & ~(SIGKILL | SIGSTOP);
+ }
+ __except (EINVAL)
+ {
+ return -1;
+ }
+ __endtry
+ if (flags & SFD_NONBLOCK)
+ set_nonblocking (true);
+ if (flags & SFD_CLOEXEC)
+ set_close_on_exec (true);
+ if (get_unique_id () == 0)
+ {
+ nohandle (true);
+ set_unique_id ();
+ set_ino (get_unique_id ());
+ set_flags (O_RDWR | O_BINARY);
+ }
+ return 0;
+}
+
+int
+fhandler_signalfd::fstat (struct stat *buf)
+{
+ int ret = fhandler_base::fstat (buf);
+ if (!ret)
+ {
+ buf->st_mode = S_IRUSR | S_IWUSR;
+ buf->st_dev = FH_SIGNALFD;
+ buf->st_ino = get_unique_id ();
+ }
+ return ret;
+}
+
+static inline void
+copy_siginfo_to_signalfd (struct signalfd_siginfo *sfd,
+ const siginfo_t * const si)
+{
+ sfd->ssi_signo = si->si_signo;
+ sfd->ssi_errno = si->si_errno;
+ sfd->ssi_code = si->si_code;
+ sfd->ssi_pid = si->si_pid;
+ sfd->ssi_uid = si->si_uid;
+ sfd->ssi_fd = -1;
+ sfd->ssi_tid = si->si_tid;
+ sfd->ssi_band = 0;
+ sfd->ssi_overrun = si->si_overrun;
+ sfd->ssi_trapno = 0;
+ sfd->ssi_status = si->si_status;
+ sfd->ssi_int = si->si_value.sival_int;
+ sfd->ssi_ptr = (uint64_t) si->si_value.sival_ptr;
+ sfd->ssi_utime = si->si_utime;
+ sfd->ssi_stime = si->si_stime;
+ sfd->ssi_addr = (uint64_t) si->si_addr;
+}
+
+void
+fhandler_signalfd::read (void *ptr, size_t& len)
+{
+ const LARGE_INTEGER poll = { QuadPart : 0 };
+ siginfo_t si;
+ int ret, old_errno;
+ size_t curlen = 0;
+ signalfd_siginfo *sfd_ptr = (signalfd_siginfo *) ptr;
+
+ if (len < sizeof (struct signalfd_siginfo))
+ {
+ set_errno (EINVAL);
+ len = (size_t) -1;
+ return;
+ }
+ old_errno = get_errno ();
+ do
+ {
+ /* Even when read is blocking, only one pending signal is actually
+ required to return. Subsequently use sigtimedwait to just poll
+ if some more signal is available. */
+ ret = sigwait_common (&sigset, &si, (is_nonblocking () || curlen)
+ ? (PLARGE_INTEGER) &poll : NULL);
+ if (ret == -1)
+ {
+ /* If we already read a signal so the buffer isn't empty, just
+ return success. */
+ if (curlen > 0)
+ break;
+ len = -1;
+ return;
+ }
+ __try
+ {
+ copy_siginfo_to_signalfd (sfd_ptr, &si);
+ }
+ __except (EFAULT)
+ {
+ len = (size_t) -1;
+ return;
+ }
+ __endtry
+ sfd_ptr++;
+ curlen += sizeof (*sfd_ptr);
+ }
+ while ((len - curlen >= sizeof (struct signalfd_siginfo)));
+ set_errno (old_errno);
+ len = curlen;
+ return;
+}
+
+ssize_t
+fhandler_signalfd::write (const void *, size_t)
+{
+ set_errno (EINVAL);
+ return -1;
+}
+
+/* Called from select */
+int
+fhandler_signalfd::poll ()
+{
+ sigset_t outset = sig_send (myself, __SIGPENDING, &_my_tls);
+ if (outset == SIG_BAD_MASK)
+ return -1;
+ if ((outset & sigset) != 0)
+ return 0;
+ return -1;
+}
diff --git a/winsup/cygwin/fhandler/socket.cc b/winsup/cygwin/fhandler/socket.cc
new file mode 100644
index 000000000..f7c5ff629
--- /dev/null
+++ b/winsup/cygwin/fhandler/socket.cc
@@ -0,0 +1,311 @@
+/* fhandler_socket.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 <sys/socket.h>
+#include <asm/byteorder.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/statvfs.h>
+#include <cygwin/acl.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "tls_pbuf.h"
+
+extern "C" {
+ int sscanf (const char *, const char *, ...);
+} /* End of "C" section */
+
+/**********************************************************************/
+/* fhandler_socket */
+
+fhandler_socket::fhandler_socket () :
+ fhandler_base (),
+ uid (myself->uid),
+ gid (myself->gid),
+ mode (S_IFSOCK | S_IRWXU | S_IRWXG | S_IRWXO),
+ _rcvtimeo (INFINITE),
+ _sndtimeo (INFINITE)
+{
+}
+
+fhandler_socket::~fhandler_socket ()
+{
+}
+
+char *
+fhandler_socket::get_proc_fd_name (char *buf)
+{
+ __small_sprintf (buf, "socket:[%lu]", get_plain_ino ());
+ return buf;
+}
+
+int
+fhandler_socket::getpeereid (pid_t *pid, uid_t *euid, gid_t *egid)
+{
+ set_errno (EINVAL);
+ return -1;
+}
+
+/* Definitions of old ifreq stuff used prior to Cygwin 1.7.0. */
+#define OLD_SIOCGIFFLAGS _IOW('s', 101, struct __old_ifreq)
+#define OLD_SIOCGIFADDR _IOW('s', 102, struct __old_ifreq)
+#define OLD_SIOCGIFBRDADDR _IOW('s', 103, struct __old_ifreq)
+#define OLD_SIOCGIFNETMASK _IOW('s', 104, struct __old_ifreq)
+#define OLD_SIOCGIFHWADDR _IOW('s', 105, struct __old_ifreq)
+#define OLD_SIOCGIFMETRIC _IOW('s', 106, struct __old_ifreq)
+#define OLD_SIOCGIFMTU _IOW('s', 107, struct __old_ifreq)
+#define OLD_SIOCGIFINDEX _IOW('s', 108, struct __old_ifreq)
+
+#define CONV_OLD_TO_NEW_SIO(old) (((old)&0xff00ffff)|(((long)sizeof(struct ifreq)&IOCPARM_MASK)<<16))
+
+struct __old_ifreq {
+#define __OLD_IFNAMSIZ 16
+ union {
+ char ifrn_name[__OLD_IFNAMSIZ]; /* if name, e.g. "en0" */
+ } ifr_ifrn;
+
+ union {
+ struct sockaddr ifru_addr;
+ struct sockaddr ifru_broadaddr;
+ struct sockaddr ifru_netmask;
+ struct sockaddr ifru_hwaddr;
+ short ifru_flags;
+ int ifru_metric;
+ int ifru_mtu;
+ int ifru_ifindex;
+ } ifr_ifru;
+};
+
+int
+fhandler_socket::ioctl (unsigned int cmd, void *p)
+{
+ extern int get_ifconf (struct ifconf *ifc, int what); /* net.cc */
+ int res;
+ struct ifconf ifc, *ifcp;
+ struct ifreq *ifrp;
+
+ switch (cmd)
+ {
+ case SIOCGIFCONF:
+ ifcp = (struct ifconf *) p;
+ if (!ifcp)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ ifc.ifc_len = ifcp->ifc_len;
+ ifc.ifc_buf = ifcp->ifc_buf;
+ res = get_ifconf (&ifc, cmd);
+ if (res)
+ debug_printf ("error in get_ifconf");
+ ifcp->ifc_len = ifc.ifc_len;
+ break;
+ case OLD_SIOCGIFFLAGS:
+ case OLD_SIOCGIFADDR:
+ case OLD_SIOCGIFBRDADDR:
+ case OLD_SIOCGIFNETMASK:
+ case OLD_SIOCGIFHWADDR:
+ case OLD_SIOCGIFMETRIC:
+ case OLD_SIOCGIFMTU:
+ case OLD_SIOCGIFINDEX:
+ cmd = CONV_OLD_TO_NEW_SIO (cmd);
+ fallthrough;
+ case SIOCGIFFLAGS:
+ case SIOCGIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCGIFADDR:
+ case SIOCGIFHWADDR:
+ case SIOCGIFMETRIC:
+ case SIOCGIFMTU:
+ case SIOCGIFINDEX:
+ case SIOCGIFFRNDLYNAM:
+ case SIOCGIFDSTADDR:
+ {
+ if (!p)
+ {
+ debug_printf ("ifr == NULL");
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ ifc.ifc_len = 64 * sizeof (struct ifreq);
+ ifc.ifc_buf = (caddr_t) alloca (ifc.ifc_len);
+ if (cmd == SIOCGIFFRNDLYNAM)
+ {
+ struct ifreq_frndlyname *iff = (struct ifreq_frndlyname *)
+ alloca (64 * sizeof (struct ifreq_frndlyname));
+ for (int i = 0; i < 64; ++i)
+ ifc.ifc_req[i].ifr_frndlyname = &iff[i];
+ }
+
+ res = get_ifconf (&ifc, cmd);
+ if (res)
+ {
+ debug_printf ("error in get_ifconf");
+ break;
+ }
+
+ struct ifreq *ifr = (struct ifreq *) p;
+ debug_printf (" name: %s", ifr->ifr_name);
+ for (ifrp = ifc.ifc_req;
+ (caddr_t) ifrp < ifc.ifc_buf + ifc.ifc_len;
+ ++ifrp)
+ {
+ debug_printf ("testname: %s", ifrp->ifr_name);
+ if (! strcmp (ifrp->ifr_name, ifr->ifr_name))
+ {
+ if (cmd == SIOCGIFFRNDLYNAM)
+ /* The application has to care for the space. */
+ memcpy (ifr->ifr_frndlyname, ifrp->ifr_frndlyname,
+ sizeof (struct ifreq_frndlyname));
+ else
+ memcpy (&ifr->ifr_ifru, &ifrp->ifr_ifru,
+ sizeof ifr->ifr_ifru);
+ break;
+ }
+ }
+
+ if ((caddr_t) ifrp >= ifc.ifc_buf + ifc.ifc_len)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ break;
+ }
+ default:
+ res = fhandler_base::ioctl (cmd, p);
+ break;
+ }
+ syscall_printf ("%d = ioctl_socket(%x, %p)", res, cmd, p);
+ return res;
+}
+
+int
+fhandler_socket::fcntl (int cmd, intptr_t arg)
+{
+ int res = 0;
+ int request, current;
+
+ switch (cmd)
+ {
+ case F_SETFL:
+ {
+ int new_flags = arg & O_NONBLOCK;
+ current = get_flags () & O_NONBLOCK;
+ request = new_flags ? 1 : 0;
+ if (!!current != !!new_flags && (res = ioctl (FIONBIO, &request)))
+ break;
+ set_flags ((get_flags () & ~O_NONBLOCK) | new_flags);
+ break;
+ }
+ default:
+ res = fhandler_base::fcntl (cmd, arg);
+ break;
+ }
+ return res;
+}
+
+int
+fhandler_socket::open (int flags, mode_t mode)
+{
+ set_errno (ENXIO);
+ return 0;
+}
+
+int
+fhandler_socket::fstat (struct stat *buf)
+{
+ int res;
+
+ res = fhandler_base::fstat (buf);
+ if (!res)
+ {
+ buf->st_dev = FHDEV (DEV_SOCK_MAJOR, 0);
+ if (!(buf->st_ino = get_plain_ino ()))
+ sscanf (get_name (), "/proc/%*d/fd/socket:[%lld]",
+ (long long *) &buf->st_ino);
+ buf->st_uid = uid;
+ buf->st_gid = gid;
+ buf->st_mode = mode;
+ buf->st_size = 0;
+ }
+ return res;
+}
+
+int
+fhandler_socket::fstatvfs (struct statvfs *sfs)
+{
+ memset (sfs, 0, sizeof (*sfs));
+ sfs->f_bsize = sfs->f_frsize = 4096;
+ sfs->f_namemax = NAME_MAX;
+ return 0;
+}
+
+int
+fhandler_socket::fchmod (mode_t newmode)
+{
+ mode = (newmode & ~S_IFMT) | S_IFSOCK;
+ return 0;
+}
+
+int
+fhandler_socket::fchown (uid_t newuid, gid_t newgid)
+{
+ bool perms = check_token_membership (&well_known_admins_sid);
+
+ /* Admin rulez */
+ if (!perms)
+ {
+ /* Otherwise, new uid == old uid or current uid is fine */
+ if (newuid == ILLEGAL_UID || newuid == uid || newuid == myself->uid)
+ perms = true;
+ /* Otherwise, new gid == old gid or current gid is fine */
+ else if (newgid == ILLEGAL_GID || newgid == gid || newgid == myself->gid)
+ perms = true;
+ else
+ {
+ /* Last but not least, newgid in supplementary group list is fine */
+ tmp_pathbuf tp;
+ gid_t *gids = (gid_t *) tp.w_get ();
+ int num = getgroups (65536 / sizeof (*gids), gids);
+
+ for (int idx = 0; idx < num; ++idx)
+ if (newgid == gids[idx])
+ {
+ perms = true;
+ break;
+ }
+ }
+ }
+
+ if (perms)
+ {
+ if (newuid != ILLEGAL_UID)
+ uid = newuid;
+ if (newgid != ILLEGAL_GID)
+ gid = newgid;
+ return 0;
+ }
+ set_errno (EPERM);
+ return -1;
+}
+
+int
+fhandler_socket::facl (int cmd, int nentries, aclent_t *aclbufp)
+{
+ set_errno (EOPNOTSUPP);
+ return -1;
+}
+
+int
+fhandler_socket::link (const char *newpath)
+{
+ return fhandler_base::link (newpath);
+}
diff --git a/winsup/cygwin/fhandler/socket_inet.cc b/winsup/cygwin/fhandler/socket_inet.cc
new file mode 100644
index 000000000..63cc498f1
--- /dev/null
+++ b/winsup/cygwin/fhandler/socket_inet.cc
@@ -0,0 +1,2404 @@
+/* fhandler_socket_inet.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. */
+
+#define __INSIDE_CYGWIN_NET__
+#define USE_SYS_TYPES_FD_SET
+
+#include "winsup.h"
+/* 2014-04-24: Current Mingw headers define sockaddr_in6 using u_long (8 byte)
+ because a redefinition for LP64 systems is missing. This leads to a wrong
+ definition and size of sockaddr_in6 when building with winsock headers.
+ This definition is also required to use the right u_long type in subsequent
+ function calls. */
+#undef u_long
+#define u_long __ms_u_long
+#include <w32api/ws2tcpip.h>
+#include <w32api/mswsock.h>
+#include <w32api/mstcpip.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <unistd.h>
+#include <asm/byteorder.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <sys/statvfs.h>
+#include <cygwin/acl.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "shared_info.h"
+#include "wininfo.h"
+#include "tls_pbuf.h"
+
+#define ASYNC_MASK (FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT)
+#define EVENT_MASK (FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE)
+
+#define LOCK_EVENTS \
+ if (wsock_mtx && \
+ WaitForSingleObject (wsock_mtx, INFINITE) != WAIT_FAILED) \
+ {
+
+#define UNLOCK_EVENTS \
+ ReleaseMutex (wsock_mtx); \
+ }
+
+/* Maximum number of concurrently opened sockets from all Cygwin processes
+ per session. Note that shared sockets (through dup/fork/exec) are
+ counted as one socket. */
+#define NUM_SOCKS 2048U
+
+#define LOCK_EVENTS \
+ if (wsock_mtx && \
+ WaitForSingleObject (wsock_mtx, INFINITE) != WAIT_FAILED) \
+ {
+
+#define UNLOCK_EVENTS \
+ ReleaseMutex (wsock_mtx); \
+ }
+
+static wsa_event wsa_events[NUM_SOCKS] __attribute__((section (".cygwin_dll_common"), shared));
+
+static LONG socket_serial_number __attribute__((section (".cygwin_dll_common"), shared));
+
+static HANDLE wsa_slot_mtx;
+
+static PWCHAR
+sock_shared_name (PWCHAR buf, LONG num)
+{
+ __small_swprintf (buf, L"socket.%d", num);
+ return buf;
+}
+
+static wsa_event *
+search_wsa_event_slot (LONG new_serial_number)
+{
+ WCHAR name[32], searchname[32];
+ UNICODE_STRING uname;
+ OBJECT_ATTRIBUTES attr;
+ NTSTATUS status;
+
+ if (!wsa_slot_mtx)
+ {
+ RtlInitUnicodeString (&uname, sock_shared_name (name, 0));
+ InitializeObjectAttributes (&attr, &uname, OBJ_INHERIT | OBJ_OPENIF,
+ get_session_parent_dir (),
+ everyone_sd (CYG_MUTANT_ACCESS));
+ status = NtCreateMutant (&wsa_slot_mtx, CYG_MUTANT_ACCESS, &attr, FALSE);
+ if (!NT_SUCCESS (status))
+ api_fatal ("Couldn't create/open shared socket mutex %S, %y",
+ &uname, status);
+ }
+ switch (WaitForSingleObject (wsa_slot_mtx, INFINITE))
+ {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED:
+ break;
+ default:
+ api_fatal ("WFSO failed for shared socket mutex, %E");
+ break;
+ }
+ unsigned int slot = new_serial_number % NUM_SOCKS;
+ while (wsa_events[slot].serial_number)
+ {
+ HANDLE searchmtx;
+ RtlInitUnicodeString (&uname, sock_shared_name (searchname,
+ wsa_events[slot].serial_number));
+ InitializeObjectAttributes (&attr, &uname, 0, get_session_parent_dir (),
+ NULL);
+ status = NtOpenMutant (&searchmtx, READ_CONTROL, &attr);
+ if (!NT_SUCCESS (status))
+ break;
+ /* Mutex still exists, attached socket is active, try next slot. */
+ NtClose (searchmtx);
+ slot = (slot + 1) % NUM_SOCKS;
+ if (slot == (new_serial_number % NUM_SOCKS))
+ {
+ /* Did the whole array once. Too bad. */
+ debug_printf ("No free socket slot");
+ ReleaseMutex (wsa_slot_mtx);
+ return NULL;
+ }
+ }
+ memset (&wsa_events[slot], 0, sizeof (wsa_event));
+ wsa_events[slot].serial_number = new_serial_number;
+ ReleaseMutex (wsa_slot_mtx);
+ return wsa_events + slot;
+}
+
+/* cygwin internal: map sockaddr into internet domain address */
+static int
+get_inet_addr_inet (const struct sockaddr *in, int inlen,
+ struct sockaddr_storage *out, int *outlen)
+{
+ switch (in->sa_family)
+ {
+ case AF_INET:
+ memcpy (out, in, inlen);
+ *outlen = inlen;
+ /* If the peer address given in connect or sendto is the ANY address,
+ Winsock fails with WSAEADDRNOTAVAIL, while Linux converts that into
+ a connection/send attempt to LOOPBACK. We're doing the same here. */
+ if (((struct sockaddr_in *) out)->sin_addr.s_addr == htonl (INADDR_ANY))
+ ((struct sockaddr_in *) out)->sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ return 0;
+ case AF_INET6:
+ memcpy (out, in, inlen);
+ *outlen = inlen;
+ /* See comment in AF_INET case. */
+ if (IN6_IS_ADDR_UNSPECIFIED (&((struct sockaddr_in6 *) out)->sin6_addr))
+ ((struct sockaddr_in6 *) out)->sin6_addr = in6addr_loopback;
+ return 0;
+ default:
+ set_errno (EAFNOSUPPORT);
+ return SOCKET_ERROR;
+ }
+}
+
+/* There's no DLL which exports the symbol WSARecvMsg. One has to call
+ WSAIoctl as below to fetch the function pointer. Why on earth did the
+ MS developers decide not to export a normal symbol for these extension
+ functions? */
+inline int
+get_ext_funcptr (SOCKET sock, void *funcptr)
+{
+ DWORD bret;
+ const GUID guid = WSAID_WSARECVMSG;
+ return WSAIoctl (sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
+ (void *) &guid, sizeof (GUID), funcptr, sizeof (void *),
+ &bret, NULL, NULL);
+}
+
+fhandler_socket_wsock::fhandler_socket_wsock () :
+ fhandler_socket (),
+ wsock_events (NULL),
+ wsock_mtx (NULL),
+ wsock_evt (NULL),
+ status (),
+ prot_info_ptr (NULL)
+{
+ need_fork_fixup (true);
+}
+
+fhandler_socket_wsock::~fhandler_socket_wsock ()
+{
+ if (prot_info_ptr)
+ cfree (prot_info_ptr);
+}
+
+bool
+fhandler_socket_wsock::init_events ()
+{
+ LONG new_serial_number;
+ WCHAR name[32];
+ UNICODE_STRING uname;
+ OBJECT_ATTRIBUTES attr;
+ NTSTATUS status;
+
+ do
+ {
+ new_serial_number =
+ InterlockedIncrement (&socket_serial_number);
+ if (!new_serial_number) /* 0 is reserved for global mutex */
+ InterlockedIncrement (&socket_serial_number);
+ set_ino (new_serial_number);
+ RtlInitUnicodeString (&uname, sock_shared_name (name, new_serial_number));
+ InitializeObjectAttributes (&attr, &uname, OBJ_INHERIT | OBJ_OPENIF,
+ get_session_parent_dir (),
+ everyone_sd (CYG_MUTANT_ACCESS));
+ status = NtCreateMutant (&wsock_mtx, CYG_MUTANT_ACCESS, &attr, FALSE);
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("NtCreateMutant(%S), %y", &uname, status);
+ set_errno (ENOBUFS);
+ return false;
+ }
+ if (status == STATUS_OBJECT_NAME_EXISTS)
+ NtClose (wsock_mtx);
+ }
+ while (status == STATUS_OBJECT_NAME_EXISTS);
+ if ((wsock_evt = CreateEvent (&sec_all, TRUE, FALSE, NULL))
+ == WSA_INVALID_EVENT)
+ {
+ debug_printf ("CreateEvent, %E");
+ set_errno (ENOBUFS);
+ NtClose (wsock_mtx);
+ return false;
+ }
+ if (WSAEventSelect (get_socket (), wsock_evt, EVENT_MASK) == SOCKET_ERROR)
+ {
+ debug_printf ("WSAEventSelect, %E");
+ set_winsock_errno ();
+ NtClose (wsock_evt);
+ NtClose (wsock_mtx);
+ return false;
+ }
+ if (!(wsock_events = search_wsa_event_slot (new_serial_number)))
+ {
+ set_errno (ENOBUFS);
+ NtClose (wsock_evt);
+ NtClose (wsock_mtx);
+ return false;
+ }
+ if (get_socket_type () == SOCK_DGRAM)
+ wsock_events->events = FD_WRITE;
+ return true;
+}
+
+int
+fhandler_socket_wsock::evaluate_events (const long event_mask, long &events,
+ const bool erase)
+{
+ int ret = 0;
+ long events_now = 0;
+
+ WSANETWORKEVENTS evts = { 0 };
+ if (!(WSAEnumNetworkEvents (get_socket (), wsock_evt, &evts)))
+ {
+ if (evts.lNetworkEvents)
+ {
+ LOCK_EVENTS;
+ wsock_events->events |= evts.lNetworkEvents;
+ events_now = (wsock_events->events & event_mask);
+ if (evts.lNetworkEvents & FD_CONNECT)
+ {
+ wsock_events->connect_errorcode = evts.iErrorCode[FD_CONNECT_BIT];
+
+ /* Setting the connect_state and calling the AF_LOCAL handshake
+ here allows to handle this stuff from a single point. This
+ is independent of FD_CONNECT being requested. Consider a
+ server calling connect(2) and then immediately poll(2) with
+ only polling for POLLIN (example: postfix), or select(2) just
+ asking for descriptors ready to read.
+
+ Something weird occurs in Winsock: If you fork off and call
+ recv/send on the duplicated, already connected socket, another
+ FD_CONNECT event is generated in the child process. This
+ would trigger a call to af_local_connect which obviously fail.
+ Avoid this by calling set_connect_state only if connect_state
+ is connect_pending. */
+ if (connect_state () == connect_pending)
+ {
+ if (wsock_events->connect_errorcode)
+ connect_state (connect_failed);
+ else if (af_local_connect ())
+ {
+ wsock_events->connect_errorcode = WSAGetLastError ();
+ connect_state (connect_failed);
+ }
+ else
+ connect_state (connected);
+ }
+ }
+ UNLOCK_EVENTS;
+ if ((evts.lNetworkEvents & FD_OOB) && wsock_events->owner)
+ kill (wsock_events->owner, SIGURG);
+ }
+ }
+
+ LOCK_EVENTS;
+ if ((events = events_now) != 0
+ || (events = (wsock_events->events & event_mask)) != 0)
+ {
+ if (events & FD_CONNECT)
+ {
+ int wsa_err = wsock_events->connect_errorcode;
+ if (wsa_err)
+ {
+ /* CV 2014-04-23: This is really weird. If you call connect
+ asynchronously on a socket and then select, an error like
+ "Connection refused" is set in the event and in the SO_ERROR
+ socket option. If you call connect, then dup, then select,
+ the error is set in the event, but not in the SO_ERROR socket
+ option, despite the dup'ed socket handle referring to the same
+ socket. We're trying to workaround this problem here by
+ taking the connect errorcode from the event and write it back
+ into the SO_ERROR socket option.
+
+ CV 2014-06-16: Call WSASetLastError *after* setsockopt since,
+ apparently, setsockopt sets the last WSA error code to 0 on
+ success. */
+ ::setsockopt (get_socket (), SOL_SOCKET, SO_ERROR,
+ (const char *) &wsa_err, sizeof wsa_err);
+ WSASetLastError (wsa_err);
+ ret = SOCKET_ERROR;
+ }
+ /* Since FD_CONNECT is only given once, we have to keep FD_CONNECT
+ for connection failed sockets to have consistent behaviour in
+ programs calling poll/select multiple times. Example test to
+ non-listening port: curl -v 127.0.0.1:47 */
+ if (connect_state () != connect_failed)
+ wsock_events->events &= ~FD_CONNECT;
+ wsock_events->events |= FD_WRITE;
+ wsock_events->connect_errorcode = 0;
+ }
+ if (events & FD_CLOSE)
+ {
+ if (evts.iErrorCode[FD_CLOSE_BIT])
+ {
+ WSASetLastError (evts.iErrorCode[FD_CLOSE_BIT]);
+ ret = SOCKET_ERROR;
+ }
+ /* This test makes accept/connect behave as on Linux when accept/
+ connect is called on a socket for which shutdown has been called.
+ The second half of this code is in the shutdown method. Note that
+ we only do this when called from accept/connect, not from select.
+ In this case erase == false, just as with read (MSG_PEEK). */
+ if (erase)
+ {
+ if ((event_mask & FD_ACCEPT) && saw_shutdown_read ())
+ {
+ WSASetLastError (WSAEINVAL);
+ ret = SOCKET_ERROR;
+ }
+ if (event_mask & FD_CONNECT)
+ {
+ WSASetLastError (WSAECONNRESET);
+ ret = SOCKET_ERROR;
+ }
+ }
+ }
+ if (erase)
+ wsock_events->events &= ~(events & ~(FD_WRITE | FD_CLOSE));
+ }
+ UNLOCK_EVENTS;
+
+ return ret;
+}
+
+int
+fhandler_socket_wsock::wait_for_events (const long event_mask,
+ const DWORD flags)
+{
+ if (async_io ())
+ return 0;
+
+ int ret;
+ long events = 0;
+ DWORD wfmo_timeout = 50;
+ DWORD timeout;
+
+ WSAEVENT ev[3] = { wsock_evt, NULL, NULL };
+ wait_signal_arrived here (ev[1]);
+ DWORD ev_cnt = 2;
+ if ((ev[2] = pthread::get_cancel_event ()) != NULL)
+ ++ev_cnt;
+
+ if (is_nonblocking () || (flags & MSG_DONTWAIT))
+ timeout = 0;
+ else if (event_mask & FD_READ)
+ timeout = rcvtimeo ();
+ else if (event_mask & FD_WRITE)
+ timeout = sndtimeo ();
+ else
+ timeout = INFINITE;
+
+ while (!(ret = evaluate_events (event_mask, events, !(flags & MSG_PEEK)))
+ && !events)
+ {
+ if (timeout == 0)
+ {
+ WSASetLastError (WSAEWOULDBLOCK);
+ return SOCKET_ERROR;
+ }
+
+ if (timeout < wfmo_timeout)
+ wfmo_timeout = timeout;
+ switch (WSAWaitForMultipleEvents (ev_cnt, ev, FALSE, wfmo_timeout, FALSE))
+ {
+ case WSA_WAIT_TIMEOUT:
+ case WSA_WAIT_EVENT_0:
+ if (timeout != INFINITE)
+ timeout -= wfmo_timeout;
+ break;
+
+ case WSA_WAIT_EVENT_0 + 1:
+ if (_my_tls.call_signal_handler ())
+ break;
+ WSASetLastError (WSAEINTR);
+ return SOCKET_ERROR;
+
+ case WSA_WAIT_EVENT_0 + 2:
+ pthread::static_cancel_self ();
+ break;
+
+ default:
+ /* wsock_evt can be NULL. We're generating the same errno values
+ as for sockets on which shutdown has been called. */
+ if (WSAGetLastError () != WSA_INVALID_HANDLE)
+ WSASetLastError (WSAEFAULT);
+ else
+ WSASetLastError ((event_mask & FD_CONNECT) ? WSAECONNRESET
+ : WSAEINVAL);
+ return SOCKET_ERROR;
+ }
+ }
+ return ret;
+}
+
+void
+fhandler_socket_wsock::release_events ()
+{
+ if (WaitForSingleObject (wsock_mtx, INFINITE) != WAIT_FAILED)
+ {
+ HANDLE evt = wsock_evt;
+ HANDLE mtx = wsock_mtx;
+
+ wsock_evt = wsock_mtx = NULL;
+ ReleaseMutex (mtx);
+ NtClose (evt);
+ NtClose (mtx);
+ }
+}
+
+void
+fhandler_socket_wsock::set_close_on_exec (bool val)
+{
+ set_no_inheritance (wsock_mtx, val);
+ set_no_inheritance (wsock_evt, val);
+ if (need_fixup_before ())
+ {
+ close_on_exec (val);
+ debug_printf ("set close_on_exec for %s to %d", get_name (), val);
+ }
+ else
+ fhandler_base::set_close_on_exec (val);
+}
+
+/* Called if a freshly created socket is not inheritable. In that case we
+ have to use fixup_before_fork_exec. See comment in set_socket_handle for
+ a description of the problem. */
+void
+fhandler_socket_wsock::init_fixup_before ()
+{
+ prot_info_ptr = (LPWSAPROTOCOL_INFOW)
+ cmalloc_abort (HEAP_BUF, sizeof (WSAPROTOCOL_INFOW));
+ cygheap->fdtab.inc_need_fixup_before ();
+}
+
+int
+fhandler_socket_wsock::fixup_before_fork_exec (DWORD win_pid)
+{
+ SOCKET ret = WSADuplicateSocketW (get_socket (), win_pid, prot_info_ptr);
+ if (ret)
+ set_winsock_errno ();
+ else
+ debug_printf ("WSADuplicateSocket succeeded (%x)", prot_info_ptr->dwProviderReserved);
+ return (int) ret;
+}
+
+void
+fhandler_socket_wsock::fixup_after_fork (HANDLE parent)
+{
+ fork_fixup (parent, wsock_mtx, "wsock_mtx");
+ fork_fixup (parent, wsock_evt, "wsock_evt");
+
+ if (!need_fixup_before ())
+ {
+ fhandler_base::fixup_after_fork (parent);
+ return;
+ }
+
+ SOCKET new_sock = WSASocketW (FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
+ FROM_PROTOCOL_INFO, prot_info_ptr, 0,
+ WSA_FLAG_OVERLAPPED);
+ if (new_sock == INVALID_SOCKET)
+ {
+ set_winsock_errno ();
+ set_handle ((HANDLE) INVALID_SOCKET);
+ }
+ else
+ {
+ /* Even though the original socket was not inheritable, the duplicated
+ socket is potentially inheritable again. */
+ SetHandleInformation ((HANDLE) new_sock, HANDLE_FLAG_INHERIT, 0);
+ set_handle ((HANDLE) new_sock);
+ debug_printf ("WSASocket succeeded (%p)", new_sock);
+ }
+}
+
+void
+fhandler_socket_wsock::fixup_after_exec ()
+{
+ if (need_fixup_before () && !close_on_exec ())
+ fixup_after_fork (NULL); /* No parent handle required. */
+}
+
+int
+fhandler_socket_wsock::dup (fhandler_base *child, int flags)
+{
+ debug_printf ("here");
+ fhandler_socket_wsock *fhs = (fhandler_socket_wsock *) child;
+
+ if (!DuplicateHandle (GetCurrentProcess (), wsock_mtx,
+ GetCurrentProcess (), &fhs->wsock_mtx,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ __seterrno ();
+ return -1;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), wsock_evt,
+ GetCurrentProcess (), &fhs->wsock_evt,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ __seterrno ();
+ NtClose (fhs->wsock_mtx);
+ return -1;
+ }
+ if (!need_fixup_before ())
+ {
+ int ret = fhandler_base::dup (child, flags);
+ if (ret)
+ {
+ NtClose (fhs->wsock_evt);
+ NtClose (fhs->wsock_mtx);
+ }
+ return ret;
+ }
+
+ cygheap->user.deimpersonate ();
+ fhs->init_fixup_before ();
+ fhs->set_handle (get_handle ());
+ int ret = fhs->fixup_before_fork_exec (GetCurrentProcessId ());
+ cygheap->user.reimpersonate ();
+ if (!ret)
+ {
+ fhs->fixup_after_fork (GetCurrentProcess ());
+ if (fhs->get_handle() != (HANDLE) INVALID_SOCKET)
+ return 0;
+ }
+ cygheap->fdtab.dec_need_fixup_before ();
+ NtClose (fhs->wsock_evt);
+ NtClose (fhs->wsock_mtx);
+ return -1;
+}
+
+int
+fhandler_socket_wsock::set_socket_handle (SOCKET sock, int af, int type,
+ int flags)
+{
+ DWORD hdl_flags;
+ bool lsp_fixup = false;
+ int file_flags = O_RDWR | O_BINARY;
+
+ /* Usually sockets are inheritable IFS objects. Unfortunately some virus
+ scanners or other network-oriented software replace normal sockets
+ with their own kind, which is running through a filter driver called
+ "layered service provider" (LSP) which, fortunately, are deprecated.
+
+ LSP sockets are not kernel objects. They are typically not marked as
+ inheritable, nor are they IFS handles. They are in fact not inheritable
+ to child processes, and it does not help to mark them inheritable via
+ SetHandleInformation. Subsequent socket calls in the child process fail
+ with error 10038, WSAENOTSOCK.
+
+ There's a neat way to workaround these annoying LSP sockets. WSAIoctl
+ allows to fetch the underlying base socket, which is a normal, inheritable
+ IFS handle. So we fetch the base socket, duplicate it, and close the
+ original socket. Now we have a standard IFS socket which (hopefully)
+ works as expected.
+
+ If that doesn't work for some reason, mark the sockets for duplication
+ via WSADuplicateSocket/WSASocket. This requires to start the child
+ process in SUSPENDED state so we only do this if really necessary. */
+ if (!GetHandleInformation ((HANDLE) sock, &hdl_flags)
+ || !(hdl_flags & HANDLE_FLAG_INHERIT))
+ {
+ int ret;
+ SOCKET base_sock;
+ DWORD bret;
+
+ lsp_fixup = true;
+ debug_printf ("LSP handle: %p", sock);
+ ret = WSAIoctl (sock, SIO_BASE_HANDLE, NULL, 0, (void *) &base_sock,
+ sizeof (base_sock), &bret, NULL, NULL);
+ if (ret)
+ debug_printf ("WSAIoctl: %u", WSAGetLastError ());
+ else if (base_sock != sock)
+ {
+ if (GetHandleInformation ((HANDLE) base_sock, &hdl_flags)
+ && (flags & HANDLE_FLAG_INHERIT))
+ {
+ if (!DuplicateHandle (GetCurrentProcess (), (HANDLE) base_sock,
+ GetCurrentProcess (), (PHANDLE) &base_sock,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ debug_printf ("DuplicateHandle failed, %E");
+ else
+ {
+ ::closesocket (sock);
+ sock = base_sock;
+ lsp_fixup = false;
+ }
+ }
+ }
+ }
+ set_handle ((HANDLE) sock);
+ set_addr_family (af);
+ set_socket_type (type);
+ if (!init_events ())
+ return -1;
+ if (flags & SOCK_NONBLOCK)
+ file_flags |= O_NONBLOCK;
+ if (flags & SOCK_CLOEXEC)
+ {
+ set_close_on_exec (true);
+ file_flags |= O_CLOEXEC;
+ }
+ set_flags (file_flags);
+ if (lsp_fixup)
+ init_fixup_before ();
+ set_unique_id ();
+ if (get_socket_type () == SOCK_DGRAM)
+ {
+ /* Workaround the problem that a missing listener on a UDP socket
+ in a call to sendto will result in select/WSAEnumNetworkEvents
+ reporting that the socket has pending data and a subsequent call
+ to recvfrom will return -1 with error set to WSAECONNRESET.
+
+ This problem is a regression introduced in Windows 2000.
+ Instead of fixing the problem, a new socket IOCTL code has
+ been added, see http://support.microsoft.com/kb/263823 */
+ BOOL cr = FALSE;
+ DWORD blen;
+ if (WSAIoctl (sock, SIO_UDP_CONNRESET, &cr, sizeof cr, NULL, 0,
+ &blen, NULL, NULL) == SOCKET_ERROR)
+ debug_printf ("Reset SIO_UDP_CONNRESET: WinSock error %u",
+ WSAGetLastError ());
+ }
+ rmem () = 212992;
+ wmem () = 212992;
+ return 0;
+}
+
+fhandler_socket_inet::fhandler_socket_inet () :
+ fhandler_socket_wsock (),
+ oobinline (false),
+ tcp_quickack (false),
+ tcp_fastopen (false),
+ tcp_keepidle (7200), /* WinSock default */
+ tcp_keepcnt (10), /* WinSock default */
+ tcp_keepintvl (1) /* WinSock default */
+{
+}
+
+fhandler_socket_inet::~fhandler_socket_inet ()
+{
+}
+
+int
+fhandler_socket_inet::socket (int af, int type, int protocol, int flags)
+{
+ SOCKET sock;
+ int ret;
+
+ /* This test should be covered by ::socket, but make sure we don't
+ accidentally try anything else. */
+ if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_RAW)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ sock = ::socket (af, type, protocol);
+ if (sock == INVALID_SOCKET)
+ {
+ set_winsock_errno ();
+ return -1;
+ }
+ ret = set_socket_handle (sock, af, type, flags);
+ if (ret < 0)
+ ::closesocket (sock);
+ return ret;
+}
+
+int
+fhandler_socket_inet::socketpair (int af, int type, int protocol, int flags,
+ fhandler_socket *fh_out)
+{
+ set_errno (EAFNOSUPPORT);
+ return -1;
+}
+
+int
+fhandler_socket_inet::bind (const struct sockaddr *name, int namelen)
+{
+ int res = -1;
+
+ if (!saw_reuseaddr ())
+ {
+ /* If the application didn't explicitely request SO_REUSEADDR,
+ enforce POSIX standard socket binding behaviour by setting the
+ SO_EXCLUSIVEADDRUSE socket option. See cygwin_setsockopt()
+ for a more detailed description. */
+ int on = 1;
+ int ret = ::setsockopt (get_socket (), SOL_SOCKET,
+ SO_EXCLUSIVEADDRUSE,
+ (const char *) &on, sizeof on);
+ debug_printf ("%d = setsockopt(SO_EXCLUSIVEADDRUSE), %E", ret);
+ }
+ if (::bind (get_socket (), name, namelen))
+ set_winsock_errno ();
+ else
+ res = 0;
+
+ return res;
+}
+
+int
+fhandler_socket_inet::connect (const struct sockaddr *name, int namelen)
+{
+ struct sockaddr_storage sst;
+ bool reset = (name->sa_family == AF_UNSPEC
+ && get_socket_type () == SOCK_DGRAM);
+
+ if (reset)
+ {
+ if (connect_state () == unconnected)
+ return 0;
+ /* To reset a connected DGRAM socket, call Winsock's connect
+ function with the address member of the sockaddr structure
+ filled with zeroes. */
+ memset (&sst, 0, sizeof sst);
+ sst.ss_family = get_addr_family ();
+ }
+ else if (get_inet_addr_inet (name, namelen, &sst, &namelen) == SOCKET_ERROR)
+ return SOCKET_ERROR;
+
+ /* Initialize connect state to "connect_pending". In the SOCK_STREAM
+ case, the state is ultimately set to "connected" or "connect_failed" in
+ wait_for_events when the FD_CONNECT event occurs. Note that the
+ underlying OS sockets are always non-blocking in this case and a
+ successfully initiated non-blocking Winsock connect always returns
+ WSAEWOULDBLOCK. Thus it's safe to rely on event handling. For DGRAM
+ sockets, however, connect can return immediately.
+
+ Check for either unconnected or connect_failed since in both cases it's
+ allowed to retry connecting the socket. It's also ok (albeit ugly) to
+ call connect to check if a previous non-blocking connect finished.
+
+ Set connect_state before calling connect, otherwise a race condition with
+ an already running select or poll might occur. */
+ if (connect_state () == unconnected || connect_state () == connect_failed)
+ connect_state (connect_pending);
+
+ int res = ::connect (get_socket (), (struct sockaddr *) &sst, namelen);
+ if (!res)
+ {
+ if (reset)
+ connect_state (unconnected);
+ else
+ connect_state (connected);
+ }
+ else if (!is_nonblocking ()
+ && res == SOCKET_ERROR
+ && WSAGetLastError () == WSAEWOULDBLOCK)
+ res = wait_for_events (FD_CONNECT | FD_CLOSE, 0);
+
+ if (res)
+ {
+ DWORD err = WSAGetLastError ();
+
+ /* Some applications use the ugly technique to check if a non-blocking
+ connect succeeded by calling connect again, until it returns EISCONN.
+ This circumvents the event handling and connect_state is never set.
+ Thus we check for this situation here. */
+ if (err == WSAEISCONN)
+ connect_state (connected);
+ /* Winsock returns WSAEWOULDBLOCK if the non-blocking socket cannot be
+ conected immediately. Convert to POSIX/Linux compliant EINPROGRESS. */
+ else if (is_nonblocking () && err == WSAEWOULDBLOCK)
+ WSASetLastError (WSAEINPROGRESS);
+ /* Winsock returns WSAEINVAL if the socket is already a listener.
+ Convert to POSIX/Linux compliant EISCONN. */
+ else if (err == WSAEINVAL && connect_state () == listener)
+ WSASetLastError (WSAEISCONN);
+ /* Any other error except WSAEALREADY means the connect failed. */
+ else if (connect_state () == connect_pending && err != WSAEALREADY)
+ connect_state (connect_failed);
+ set_winsock_errno ();
+ }
+
+ return res;
+}
+
+int
+fhandler_socket_inet::listen (int backlog)
+{
+ int res = ::listen (get_socket (), backlog);
+ if (res && WSAGetLastError () == WSAEINVAL)
+ {
+ /* It's perfectly valid to call listen on an unbound INET socket.
+ In this case the socket is automatically bound to an unused
+ port number, listening on all interfaces. On WinSock, listen
+ fails with WSAEINVAL when it's called on an unbound socket.
+ So we have to bind manually here to have POSIX semantics. */
+ if (get_addr_family () == AF_INET)
+ {
+ struct sockaddr_in sin;
+ sin.sin_family = AF_INET;
+ sin.sin_port = 0;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ if (!::bind (get_socket (), (struct sockaddr *) &sin, sizeof sin))
+ res = ::listen (get_socket (), backlog);
+ }
+ else if (get_addr_family () == AF_INET6)
+ {
+ struct sockaddr_in6 sin6;
+ memset (&sin6, 0, sizeof sin6);
+ sin6.sin6_family = AF_INET6;
+ if (!::bind (get_socket (), (struct sockaddr *) &sin6, sizeof sin6))
+ res = ::listen (get_socket (), backlog);
+ }
+ }
+ if (!res)
+ connect_state (listener); /* gets set to connected on accepted socket. */
+ else
+ set_winsock_errno ();
+ return res;
+}
+
+int
+fhandler_socket_inet::accept4 (struct sockaddr *peer, int *len, int flags)
+{
+ int ret = -1;
+ /* Allows NULL peer and len parameters. */
+ struct sockaddr_storage lpeer;
+ int llen = sizeof (struct sockaddr_storage);
+
+ /* Windows event handling does not check for the validity of the desired
+ flags so we have to do it here. */
+ if (connect_state () != listener)
+ {
+ WSASetLastError (WSAEINVAL);
+ set_winsock_errno ();
+ return -1;
+ }
+
+ SOCKET res = INVALID_SOCKET;
+ while (!(res = wait_for_events (FD_ACCEPT | FD_CLOSE, 0))
+ && (res = ::accept (get_socket (), (struct sockaddr *) &lpeer, &llen))
+ == INVALID_SOCKET
+ && WSAGetLastError () == WSAEWOULDBLOCK)
+ ;
+ if (res == INVALID_SOCKET)
+ set_winsock_errno ();
+ else
+ {
+ cygheap_fdnew fd;
+
+ if (fd >= 0)
+ {
+ fhandler_socket_inet *sock = (fhandler_socket_inet *)
+ build_fh_dev (dev ());
+ if (sock && sock->set_socket_handle (res, get_addr_family (),
+ get_socket_type (),
+ get_socket_flags ()) == 0)
+ {
+ sock->async_io (false); /* set_socket_handle disables async. */
+ /* No locking necessary at this point. */
+ sock->wsock_events->events = wsock_events->events | FD_WRITE;
+ sock->wsock_events->owner = wsock_events->owner;
+ sock->connect_state (connected);
+ fd = sock;
+ if (fd <= 2)
+ set_std_handle (fd);
+ ret = fd;
+ if (peer)
+ {
+ memcpy (peer, &lpeer, MIN (*len, llen));
+ *len = llen;
+ }
+ }
+ else
+ delete sock;
+ }
+ if (ret == -1)
+ ::closesocket (res);
+ }
+ return ret;
+}
+
+int
+fhandler_socket_inet::getsockname (struct sockaddr *name, int *namelen)
+{
+ int res = -1;
+
+ /* WinSock just returns WSAEFAULT if the buffer is too small. Use a
+ big enough local buffer and truncate later as necessary, per POSIX. */
+ struct sockaddr_storage sock;
+ int len = sizeof sock;
+ res = ::getsockname (get_socket (), (struct sockaddr *) &sock, &len);
+ if (!res)
+ {
+ memcpy (name, &sock, MIN (*namelen, len));
+ *namelen = len;
+ }
+ else
+ {
+ if (WSAGetLastError () == WSAEINVAL)
+ {
+ /* WinSock returns WSAEINVAL if the socket is locally
+ unbound. Per SUSv3 this is not an error condition.
+ We're faking a valid return value here by creating the
+ same content in the sockaddr structure as on Linux. */
+ memset (&sock, 0, sizeof sock);
+ sock.ss_family = get_addr_family ();
+ switch (get_addr_family ())
+ {
+ case AF_INET:
+ res = 0;
+ len = (int) sizeof (struct sockaddr_in);
+ break;
+ case AF_INET6:
+ res = 0;
+ len = (int) sizeof (struct sockaddr_in6);
+ break;
+ default:
+ WSASetLastError (WSAEOPNOTSUPP);
+ break;
+ }
+ if (!res)
+ {
+ memcpy (name, &sock, MIN (*namelen, len));
+ *namelen = len;
+ }
+ }
+ if (res)
+ set_winsock_errno ();
+ }
+ return res;
+}
+
+int
+fhandler_socket_inet::getpeername (struct sockaddr *name, int *namelen)
+{
+ /* Always use a local big enough buffer and truncate later as necessary
+ per POSIX. WinSock unfortunately only returns WSAEFAULT if the buffer
+ is too small. */
+ struct sockaddr_storage sock;
+ int len = sizeof sock;
+ int res = ::getpeername (get_socket (), (struct sockaddr *) &sock, &len);
+ if (res)
+ set_winsock_errno ();
+ else
+ {
+ memcpy (name, &sock, MIN (*namelen, len));
+ *namelen = len;
+ }
+ return res;
+}
+
+int
+fhandler_socket_wsock::shutdown (int how)
+{
+ int res = ::shutdown (get_socket (), how);
+
+ /* Linux allows to call shutdown for any socket, even if it's not connected.
+ This also disables to call accept on this socket, if shutdown has been
+ called with the SHUT_RD or SHUT_RDWR parameter. In contrast, WinSock
+ only allows to call shutdown on a connected socket. The accept function
+ is in no way affected. So, what we do here is to fake success, and to
+ change the event settings so that an FD_CLOSE event is triggered for the
+ calling Cygwin function. The evaluate_events method handles the call
+ from accept specially to generate a Linux-compatible behaviour. */
+ if (res && WSAGetLastError () != WSAENOTCONN)
+ set_winsock_errno ();
+ else
+ {
+ res = 0;
+ switch (how)
+ {
+ case SHUT_RD:
+ saw_shutdown_read (true);
+ wsock_events->events |= FD_CLOSE;
+ SetEvent (wsock_evt);
+ break;
+ case SHUT_WR:
+ saw_shutdown_write (true);
+ break;
+ case SHUT_RDWR:
+ saw_shutdown_read (true);
+ saw_shutdown_write (true);
+ wsock_events->events |= FD_CLOSE;
+ SetEvent (wsock_evt);
+ break;
+ }
+ }
+ return res;
+}
+
+int
+fhandler_socket_wsock::close ()
+{
+ int res = 0;
+
+ release_events ();
+ while ((res = ::closesocket (get_socket ())) != 0)
+ {
+ if (WSAGetLastError () != WSAEWOULDBLOCK)
+ {
+ set_winsock_errno ();
+ res = -1;
+ break;
+ }
+ if (cygwait (10) == WAIT_SIGNALED)
+ {
+ set_errno (EINTR);
+ res = -1;
+ break;
+ }
+ WSASetLastError (0);
+ }
+ return res;
+}
+
+ssize_t
+fhandler_socket_inet::recv_internal (LPWSAMSG wsamsg, bool use_recvmsg)
+{
+ ssize_t res = 0;
+ DWORD ret = 0, wret;
+ int evt_mask = (wsamsg->dwFlags & MSG_OOB) ? FD_OOB : FD_READ;
+ LPWSABUF &wsabuf = wsamsg->lpBuffers;
+ ULONG &wsacnt = wsamsg->dwBufferCount;
+ static NO_COPY LPFN_WSARECVMSG WSARecvMsg;
+ bool read_oob = false;
+
+ /* CV 2014-10-26: Do not check for the connect_state at this point. In
+ certain scenarios there's no way to check the connect state reliably.
+ Example (hexchat): Parent process creates socket, forks, child process
+ calls connect, parent process calls read. Even if the event handling
+ allows to check for FD_CONNECT in the parent, there is always yet another
+ scenario we can easily break. */
+
+ DWORD wait_flags = wsamsg->dwFlags;
+ bool waitall = !!(wait_flags & MSG_WAITALL);
+ wsamsg->dwFlags &= (MSG_OOB | MSG_PEEK | MSG_DONTROUTE);
+ if (use_recvmsg)
+ {
+ if (!WSARecvMsg
+ && get_ext_funcptr (get_socket (), &WSARecvMsg) == SOCKET_ERROR)
+ {
+ if (wsamsg->Control.len > 0)
+ {
+ set_winsock_errno ();
+ return SOCKET_ERROR;
+ }
+ use_recvmsg = false;
+ }
+ else /* Only MSG_PEEK is supported by WSARecvMsg. */
+ wsamsg->dwFlags &= MSG_PEEK;
+ }
+ if (waitall)
+ {
+ if (get_socket_type () != SOCK_STREAM)
+ {
+ WSASetLastError (WSAEOPNOTSUPP);
+ set_winsock_errno ();
+ return SOCKET_ERROR;
+ }
+ if (is_nonblocking () || (wsamsg->dwFlags & (MSG_OOB | MSG_PEEK)))
+ waitall = false;
+ }
+
+ /* recv() returns EINVAL if MSG_OOB flag is set in inline mode. */
+ if (oobinline && (wsamsg->dwFlags & MSG_OOB))
+ {
+ set_errno (EINVAL);
+ return SOCKET_ERROR;
+ }
+
+ /* Check whether OOB data is ready or not */
+ if (get_socket_type () == SOCK_STREAM)
+ if ((wsamsg->dwFlags & MSG_OOB) || oobinline)
+ {
+ u_long atmark = 0;
+ /* SIOCATMARK = _IOR('s',7,u_long) */
+ int err = ::ioctlsocket (get_socket (), _IOR('s',7,u_long), &atmark);
+ if (err)
+ {
+ set_winsock_errno ();
+ return SOCKET_ERROR;
+ }
+ /* If there is no OOB data, recv() with MSG_OOB returns EINVAL.
+ Note: The return value of SIOCATMARK in non-inline mode of
+ winsock is FALSE if OOB data exists, TRUE otherwise. */
+ if (atmark && (wsamsg->dwFlags & MSG_OOB))
+ {
+ /* No OOB data */
+ set_errno (EINVAL);
+ return SOCKET_ERROR;
+ }
+ /* Inline mode for out-of-band (OOB) data of winsock is
+ completely broken. That is, SIOCATMARK always returns
+ TRUE in inline mode. Due to this problem, application
+ cannot determine OOB data at all. Therefore the behavior
+ of a socket with SO_OOBINLINE set is simulated using
+ a socket with SO_OOBINLINE not set. In this fake inline
+ mode, the order of the OOB and non-OOB data is not
+ preserved. OOB data is read before non-OOB data sent
+ prior to the OOB data. However, this most likely is
+ not a problem in most cases. */
+ /* If there is OOB data, read OOB data using MSG_OOB in
+ fake inline mode. */
+ if (!atmark && oobinline)
+ {
+ read_oob = true;
+ evt_mask = FD_OOB;
+ }
+ }
+
+ /* Note: Don't call WSARecvFrom(MSG_PEEK) without actually having data
+ waiting in the buffers, otherwise the event handling gets messed up
+ for some reason. */
+ while (!(res = wait_for_events (evt_mask | FD_CLOSE, wait_flags))
+ || saw_shutdown_read ())
+ {
+ DWORD dwFlags = wsamsg->dwFlags | (read_oob ? MSG_OOB : 0);
+ if (use_recvmsg)
+ res = WSARecvMsg (get_socket (), wsamsg, &wret, NULL, NULL);
+ /* This is working around a really weird problem in WinSock.
+
+ Assume you create a socket, fork the process (thus duplicating
+ the socket), connect the socket in the child, then call recv
+ on the original socket handle in the parent process.
+ In this scenario, calls to WinSock's recvfrom and WSARecvFrom
+ in the parent will fail with WSAEINVAL, regardless whether both
+ address parameters, name and namelen, are NULL or point to valid
+ storage. However, calls to recv and WSARecv succeed as expected.
+ Per MSDN, WSAEINVAL in the context of recv means "The socket has not
+ been bound". It is as if the recvfrom functions test if the socket
+ is bound locally, but in the parent process, WinSock doesn't know
+ about that and fails, while the same test is omitted in the recv
+ functions.
+
+ This also covers another weird case: WinSock returns WSAEFAULT if
+ namelen is a valid pointer while name is NULL. Both parameters are
+ ignored for TCP sockets, so this only occurs when using UDP socket. */
+ else if (!wsamsg->name || get_socket_type () == SOCK_STREAM)
+ res = WSARecv (get_socket (), wsabuf, wsacnt, &wret, &dwFlags,
+ NULL, NULL);
+ else
+ res = WSARecvFrom (get_socket (), wsabuf, wsacnt, &wret,
+ &dwFlags, wsamsg->name, &wsamsg->namelen,
+ NULL, NULL);
+ if (!res)
+ {
+ ret += wret;
+ if (!waitall)
+ break;
+ while (wret && wsacnt)
+ {
+ if (wsabuf->len > wret)
+ {
+ wsabuf->len -= wret;
+ wsabuf->buf += wret;
+ wret = 0;
+ }
+ else
+ {
+ wret -= wsabuf->len;
+ ++wsabuf;
+ --wsacnt;
+ }
+ }
+ if (!wsacnt)
+ break;
+ }
+ else if (WSAGetLastError () != WSAEWOULDBLOCK)
+ break;
+ }
+
+ if (res)
+ {
+ /* According to SUSv3, errno isn't set in that case and no error
+ condition is returned. */
+ if (WSAGetLastError () == WSAEMSGSIZE)
+ ret += wret;
+ else if (!ret)
+ {
+ /* ESHUTDOWN isn't defined for recv in SUSv3. Simply EOF is returned
+ in this case. */
+ if (WSAGetLastError () == WSAESHUTDOWN)
+ ret = 0;
+ else
+ {
+ set_winsock_errno ();
+ return SOCKET_ERROR;
+ }
+ }
+ }
+
+ return ret;
+}
+
+ssize_t
+fhandler_socket_wsock::recvfrom (void *in_ptr, size_t len, int flags,
+ struct sockaddr *from, int *fromlen)
+{
+ char *ptr = (char *) in_ptr;
+
+ /* size_t is 64 bit, but the len member in WSABUF is 32 bit.
+ Split buffer if necessary. */
+ DWORD bufcnt = len / UINT32_MAX + ((!len || (len % UINT32_MAX)) ? 1 : 0);
+ WSABUF wsabuf[bufcnt];
+ WSAMSG wsamsg = { from, from && fromlen ? *fromlen : 0,
+ wsabuf, bufcnt,
+ { 0, NULL },
+ (DWORD) flags };
+ /* Don't use len as loop condition, it could be 0. */
+ for (WSABUF *wsaptr = wsabuf; bufcnt--; ++wsaptr)
+ {
+ wsaptr->len = MIN (len, UINT32_MAX);
+ wsaptr->buf = ptr;
+ len -= wsaptr->len;
+ ptr += wsaptr->len;
+ }
+ ssize_t ret = recv_internal (&wsamsg, false);
+ if (fromlen)
+ *fromlen = wsamsg.namelen;
+ return ret;
+}
+
+ssize_t
+fhandler_socket_wsock::recvmsg (struct msghdr *msg, int flags)
+{
+ /* Disappointing but true: Even if WSARecvMsg is supported, it's only
+ supported for datagram and raw sockets. */
+ bool use_recvmsg = true;
+ if (get_socket_type () == SOCK_STREAM || get_addr_family () == AF_LOCAL)
+ {
+ use_recvmsg = false;
+ msg->msg_controllen = 0;
+ }
+
+ WSABUF wsabuf[msg->msg_iovlen];
+ WSABUF *wsaptr = wsabuf + msg->msg_iovlen;
+ const struct iovec *iovptr = msg->msg_iov + msg->msg_iovlen;
+ while (--wsaptr >= wsabuf)
+ {
+ wsaptr->len = (--iovptr)->iov_len;
+ wsaptr->buf = (char *) iovptr->iov_base;
+ }
+ WSAMSG wsamsg = { (struct sockaddr *) msg->msg_name, msg->msg_namelen,
+ wsabuf, (DWORD) msg->msg_iovlen,
+ { (DWORD) msg->msg_controllen, (char *) msg->msg_control },
+ (DWORD) flags };
+ ssize_t ret = recv_internal (&wsamsg, use_recvmsg);
+ if (ret >= 0)
+ {
+ msg->msg_namelen = wsamsg.namelen;
+ msg->msg_controllen = wsamsg.Control.len;
+ msg->msg_flags = wsamsg.dwFlags;
+ /* if a UDP_GRO packet is present, convert gso_size from Windows DWORD
+ to Linux-compatible uint16_t. We don't have to change the
+ msg_control block layout for that, assuming applications do as they
+ have been told and only use CMSG_FIRSTHDR/CMSG_NXTHDR/CMSG_DATA to
+ access control messages. The cmsghdr alignment saves our ass here! */
+ if (msg->msg_controllen && get_socket_type () == SOCK_DGRAM
+ && (get_addr_family () == AF_INET || get_addr_family () == AF_INET6))
+ {
+ struct cmsghdr *cmsg;
+
+ for (cmsg = CMSG_FIRSTHDR (msg);
+ cmsg;
+ cmsg = CMSG_NXTHDR (msg, cmsg))
+ {
+ if (cmsg->cmsg_level == SOL_UDP
+ && cmsg->cmsg_type == UDP_GRO)
+ {
+ PDWORD gso_size_win = (PDWORD) CMSG_DATA(cmsg);
+ uint16_t *gso_size_cyg = (uint16_t *) CMSG_DATA(cmsg);
+ uint16_t gso_size = (uint16_t) *gso_size_win;
+ *gso_size_cyg = gso_size;
+ break;
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+void
+fhandler_socket_wsock::read (void *in_ptr, size_t& len)
+{
+ char *ptr = (char *) in_ptr;
+
+ /* size_t is 64 bit, but the len member in WSABUF is 32 bit.
+ Split buffer if necessary. */
+ DWORD bufcnt = len / UINT32_MAX + ((!len || (len % UINT32_MAX)) ? 1 : 0);
+ WSABUF wsabuf[bufcnt];
+ WSAMSG wsamsg = { NULL, 0, wsabuf, bufcnt, { 0, NULL }, 0 };
+ /* Don't use len as loop condition, it could be 0. */
+ for (WSABUF *wsaptr = wsabuf; bufcnt--; ++wsaptr)
+ {
+ wsaptr->len = MIN (len, UINT32_MAX);
+ wsaptr->buf = ptr;
+ len -= wsaptr->len;
+ ptr += wsaptr->len;
+ }
+ len = recv_internal (&wsamsg, false);
+}
+
+ssize_t
+fhandler_socket_wsock::readv (const struct iovec *const iov, const int iovcnt,
+ ssize_t tot)
+{
+ WSABUF wsabuf[iovcnt];
+ WSABUF *wsaptr = wsabuf + iovcnt;
+ const struct iovec *iovptr = iov + iovcnt;
+ while (--wsaptr >= wsabuf)
+ {
+ wsaptr->len = (--iovptr)->iov_len;
+ wsaptr->buf = (char *) iovptr->iov_base;
+ }
+ WSAMSG wsamsg = { NULL, 0, wsabuf, (DWORD) iovcnt, { 0, NULL}, 0 };
+ return recv_internal (&wsamsg, false);
+}
+
+ssize_t
+fhandler_socket_wsock::send_internal (struct _WSAMSG *wsamsg, int flags)
+{
+ ssize_t res = 0;
+ DWORD ret = 0, sum = 0;
+ WSABUF out_buf[wsamsg->dwBufferCount];
+ bool use_sendmsg = false;
+ DWORD wait_flags = flags & MSG_DONTWAIT;
+ bool nosignal = !!(flags & MSG_NOSIGNAL);
+
+ /* MSG_EOR not supported by any protocol */
+ if (flags & MSG_EOR)
+ {
+ set_errno (EOPNOTSUPP);
+ return SOCKET_ERROR;
+ }
+
+ flags &= (MSG_OOB | MSG_DONTROUTE);
+ if (wsamsg->Control.len > 0)
+ use_sendmsg = true;
+ /* Workaround for MSDN KB 823764: Split a message into chunks <= SO_SNDBUF.
+ in_idx is the index of the current lpBuffers from the input wsamsg buffer.
+ in_off is used to keep track of the next byte to write from a wsamsg
+ buffer which only gets partially written. */
+ for (DWORD in_idx = 0, in_off = 0;
+ in_idx < wsamsg->dwBufferCount;
+ in_off >= wsamsg->lpBuffers[in_idx].len && (++in_idx, (in_off = 0)))
+ {
+ /* Split a message into the least number of pieces to minimize the
+ number of WsaSendTo calls. Don't split datagram messages (bad idea).
+ out_idx is the index of the next buffer in the out_buf WSABUF,
+ also the number of buffers given to WSASendTo.
+ out_len is the number of bytes in the buffers given to WSASendTo.
+ Don't split datagram messages (very bad idea). */
+ DWORD out_idx = 0;
+ DWORD out_len = 0;
+ if (get_socket_type () == SOCK_STREAM)
+ {
+ do
+ {
+ out_buf[out_idx].buf = wsamsg->lpBuffers[in_idx].buf + in_off;
+ out_buf[out_idx].len = wsamsg->lpBuffers[in_idx].len - in_off;
+ out_len += out_buf[out_idx].len;
+ out_idx++;
+ }
+ while (out_len < (unsigned) wmem ()
+ && (in_off = 0, ++in_idx < wsamsg->dwBufferCount));
+ /* Tweak len of the last out_buf buffer so the entire number of bytes
+ is (less than or) equal to wmem (). Fix out_len as well since it's
+ used in a subsequent test expression. */
+ if (out_len > (unsigned) wmem ())
+ {
+ out_buf[out_idx - 1].len -= out_len - (unsigned) wmem ();
+ out_len = (unsigned) wmem ();
+ }
+ /* Add the bytes written from the current last buffer to in_off,
+ so in_off points to the next byte to be written from that buffer,
+ or beyond which lets the outper loop skip to the next buffer. */
+ in_off += out_buf[out_idx - 1].len;
+ }
+
+ do
+ {
+ if (use_sendmsg)
+ res = WSASendMsg (get_socket (), wsamsg, flags, &ret, NULL, NULL);
+ else if (get_socket_type () == SOCK_STREAM)
+ res = WSASendTo (get_socket (), out_buf, out_idx, &ret, flags,
+ wsamsg->name, wsamsg->namelen, NULL, NULL);
+ else
+ res = WSASendTo (get_socket (), wsamsg->lpBuffers,
+ wsamsg->dwBufferCount, &ret, flags,
+ wsamsg->name, wsamsg->namelen, NULL, NULL);
+ if (res && (WSAGetLastError () == WSAEWOULDBLOCK))
+ {
+ LOCK_EVENTS;
+ wsock_events->events &= ~FD_WRITE;
+ UNLOCK_EVENTS;
+ }
+ }
+ while (res && (WSAGetLastError () == WSAEWOULDBLOCK)
+ && !(res = wait_for_events (FD_WRITE | FD_CLOSE, wait_flags)));
+
+ if (!res)
+ {
+ sum += ret;
+ /* For streams, return to application if the number of bytes written
+ is less than the number of bytes we intended to write in a single
+ call to WSASendTo. Otherwise we would have to add code to
+ backtrack in the input buffers, which is questionable. There was
+ probably a good reason we couldn't write more. */
+ if (get_socket_type () != SOCK_STREAM || ret < out_len)
+ break;
+ }
+ else if (is_nonblocking () || WSAGetLastError() != WSAEWOULDBLOCK)
+ break;
+ }
+
+ if (sum)
+ res = sum;
+ else if (res == SOCKET_ERROR)
+ {
+ set_winsock_errno ();
+
+ /* Special handling for EPIPE and SIGPIPE.
+
+ EPIPE is generated if the local end has been shut down on a connection
+ oriented socket. In this case the process will also receive a SIGPIPE
+ unless MSG_NOSIGNAL is set. */
+ if ((get_errno () == ECONNABORTED || get_errno () == ESHUTDOWN)
+ && get_socket_type () == SOCK_STREAM)
+ {
+ set_errno (EPIPE);
+ if (!nosignal)
+ raise (SIGPIPE);
+ }
+ }
+
+ return res;
+}
+
+ssize_t
+fhandler_socket_inet::sendto (const void *in_ptr, size_t len, int flags,
+ const struct sockaddr *to, int tolen)
+{
+ char *ptr = (char *) in_ptr;
+ struct sockaddr_storage sst;
+
+ if (to && get_inet_addr_inet (to, tolen, &sst, &tolen) == SOCKET_ERROR)
+ return SOCKET_ERROR;
+
+ /* size_t is 64 bit, but the len member in WSABUF is 32 bit.
+ Split buffer if necessary. */
+ DWORD bufcnt = len / UINT32_MAX + ((!len || (len % UINT32_MAX)) ? 1 : 0);
+ WSABUF wsabuf[bufcnt];
+ WSAMSG wsamsg = { to ? (struct sockaddr *) &sst : NULL, tolen,
+ wsabuf, bufcnt,
+ { 0, NULL },
+ 0 };
+ /* Don't use len as loop condition, it could be 0. */
+ for (WSABUF *wsaptr = wsabuf; bufcnt--; ++wsaptr)
+ {
+ wsaptr->len = MIN (len, UINT32_MAX);
+ wsaptr->buf = ptr;
+ len -= wsaptr->len;
+ ptr += wsaptr->len;
+ }
+ return send_internal (&wsamsg, flags);
+}
+
+ssize_t
+fhandler_socket_inet::sendmsg (const struct msghdr *in_msg, int flags)
+{
+ struct sockaddr_storage sst;
+ int len = 0;
+ DWORD old_gso_size = MAXDWORD;
+ ssize_t ret;
+
+ /* Copy incoming msghdr into a local copy. We only access this from
+ here on. Thus, make sure not to manipulate user space data. */
+ struct msghdr local_msg = *in_msg;
+ struct msghdr *msg = &local_msg;
+
+ if (msg->msg_name
+ && get_inet_addr_inet ((struct sockaddr *) msg->msg_name,
+ msg->msg_namelen, &sst, &len) == SOCKET_ERROR)
+ return SOCKET_ERROR;
+
+ /* Check for our optmem_max value */
+ if (msg->msg_controllen > NT_MAX_PATH)
+ {
+ set_errno (ENOBUFS);
+ return SOCKET_ERROR;
+ }
+
+ /* WSASendMsg is supported only for datagram and raw sockets. */
+ if (get_socket_type () != SOCK_DGRAM && get_socket_type () != SOCK_RAW)
+ msg->msg_controllen = 0;
+
+ /* If we actually have control data, copy it to local storage. Control
+ messages only handled by us have to be dropped from the msg_control
+ block, and we don't want to change user space data. */
+ tmp_pathbuf tp;
+ if (msg->msg_controllen)
+ {
+ void *local_cmsg = tp.c_get ();
+ memcpy (local_cmsg, msg->msg_control, msg->msg_controllen);
+ msg->msg_control = local_cmsg;
+ }
+
+ /* Check for control message we handle inside Cygwin. Right now this
+ only affects UDP sockets, so check here early. */
+ if (msg->msg_controllen && get_socket_type () == SOCK_DGRAM)
+ {
+ struct cmsghdr *cmsg;
+ bool dropped = false;
+
+ for (cmsg = CMSG_FIRSTHDR (msg);
+ cmsg;
+ cmsg = dropped ? cmsg : CMSG_NXTHDR (msg, cmsg))
+ {
+ dropped = false;
+ /* cmsg within bounds? */
+ if (cmsg->cmsg_len < sizeof (struct cmsghdr)
+ || cmsg->cmsg_len > (size_t) msg->msg_controllen
+ - ((uintptr_t) cmsg
+ - (uintptr_t) msg->msg_control))
+ {
+ set_errno (EINVAL);
+ return SOCKET_ERROR;
+ }
+ /* UDP_SEGMENT? Override gso_size for this single sendmsg. */
+ if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_SEGMENT)
+ {
+ /* 16 bit unsigned, as on Linux */
+ DWORD gso_size = *(uint16_t *) CMSG_DATA(cmsg);
+ int size = sizeof old_gso_size;
+ /* Save the old gso_size and set the requested one. */
+ if (::getsockopt (get_socket (), IPPROTO_UDP, UDP_SEGMENT,
+ (char *) &old_gso_size, &size) == SOCKET_ERROR
+ || ::setsockopt (get_socket (), IPPROTO_UDP, UDP_SEGMENT,
+ (char *) &gso_size, sizeof gso_size)
+ == SOCKET_ERROR)
+ {
+ set_winsock_errno ();
+ return SOCKET_ERROR;
+ }
+ /* Drop message from msgbuf, Windows doesn't know it. */
+ size_t cmsg_size = CMSG_ALIGN (cmsg->cmsg_len);
+ struct cmsghdr *cmsg_next = CMSG_NXTHDR (msg, cmsg);
+ if (cmsg_next)
+ memmove (cmsg, cmsg_next, (char *) msg->msg_control
+ + msg->msg_controllen
+ - (char *) cmsg_next);
+ msg->msg_controllen -= cmsg_size;
+ dropped = true;
+ /* Avoid infinite loop */
+ if (msg->msg_controllen <= 0)
+ {
+ cmsg = NULL;
+ msg->msg_controllen = 0;
+ }
+ }
+ }
+ }
+
+ /* Copy over msg_iov into an equivalent WSABUF array. */
+ WSABUF wsabuf[msg->msg_iovlen];
+ WSABUF *wsaptr = wsabuf;
+ const struct iovec *iovptr = msg->msg_iov;
+ for (int i = 0; i < msg->msg_iovlen; ++i)
+ {
+ wsaptr->len = iovptr->iov_len;
+ (wsaptr++)->buf = (char *) (iovptr++)->iov_base;
+ }
+
+ /* Eventually copy over to a WSAMSG and call send_internal with that. */
+ WSAMSG wsamsg = { msg->msg_name ? (struct sockaddr *) &sst : NULL, len,
+ wsabuf, (DWORD) msg->msg_iovlen,
+ { (DWORD) msg->msg_controllen,
+ msg->msg_controllen ? (char *) msg->msg_control : NULL },
+ 0 };
+ ret = send_internal (&wsamsg, flags);
+ if (old_gso_size != MAXDWORD)
+ ::setsockopt (get_socket (), IPPROTO_UDP, UDP_SEGMENT,
+ (char *) &old_gso_size, sizeof old_gso_size);
+ return ret;
+}
+
+ssize_t
+fhandler_socket_wsock::write (const void *in_ptr, size_t len)
+{
+ char *ptr = (char *) in_ptr;
+
+ /* size_t is 64 bit, but the len member in WSABUF is 32 bit.
+ Split buffer if necessary. */
+ DWORD bufcnt = len / UINT32_MAX + ((!len || (len % UINT32_MAX)) ? 1 : 0);
+ WSABUF wsabuf[bufcnt];
+ WSAMSG wsamsg = { NULL, 0, wsabuf, bufcnt, { 0, NULL }, 0 };
+ /* Don't use len as loop condition, it could be 0. */
+ for (WSABUF *wsaptr = wsabuf; bufcnt--; ++wsaptr)
+ {
+ wsaptr->len = MIN (len, UINT32_MAX);
+ wsaptr->buf = ptr;
+ len -= wsaptr->len;
+ ptr += wsaptr->len;
+ }
+ return send_internal (&wsamsg, 0);
+}
+
+ssize_t
+fhandler_socket_wsock::writev (const struct iovec *const iov, const int iovcnt,
+ ssize_t tot)
+{
+ WSABUF wsabuf[iovcnt];
+ WSABUF *wsaptr = wsabuf;
+ const struct iovec *iovptr = iov;
+ for (int i = 0; i < iovcnt; ++i)
+ {
+ wsaptr->len = iovptr->iov_len;
+ (wsaptr++)->buf = (char *) (iovptr++)->iov_base;
+ }
+ WSAMSG wsamsg = { NULL, 0, wsabuf, (DWORD) iovcnt, { 0, NULL}, 0 };
+ return send_internal (&wsamsg, 0);
+}
+
+#define TCP_MAXRT 5 /* Older systems don't support TCP_MAXRTMS
+ TCP_MAXRT takes secs, not msecs. */
+
+#ifndef SIO_TCP_SET_ACK_FREQUENCY
+#define SIO_TCP_SET_ACK_FREQUENCY _WSAIOW(IOC_VENDOR,23)
+#endif
+
+#define MAX_TCP_KEEPIDLE 32767
+#define MAX_TCP_KEEPCNT 255
+#define MAX_TCP_KEEPINTVL 32767
+
+#define FIXED_WSOCK_TCP_KEEPCNT 10
+
+int
+fhandler_socket_inet::set_keepalive (int keepidle, int keepcnt, int keepintvl)
+{
+ struct tcp_keepalive tka;
+ int so_keepalive = 0;
+ int len = sizeof so_keepalive;
+ int ret;
+ DWORD dummy;
+
+ /* Per MSDN,
+ https://docs.microsoft.com/en-us/windows/win32/winsock/sio-keepalive-vals
+ the subsequent keep-alive settings in struct tcp_keepalive are only used
+ if the onoff member is != 0. Request the current state of SO_KEEPALIVE,
+ then set the keep-alive options with onoff set to 1. On success, if
+ SO_KEEPALIVE was 0, restore to the original SO_KEEPALIVE setting. Per
+ the above MSDN doc, the SIO_KEEPALIVE_VALS settings are persistent
+ across switching SO_KEEPALIVE. */
+ ret = ::getsockopt (get_socket (), SOL_SOCKET, SO_KEEPALIVE,
+ (char *) &so_keepalive, &len);
+ if (ret == SOCKET_ERROR)
+ debug_printf ("getsockopt (SO_KEEPALIVE) failed, %u\n", WSAGetLastError ());
+ tka.onoff = 1;
+ tka.keepalivetime = keepidle * MSPERSEC;
+ /* WinSock TCP_KEEPCNT is fixed. But we still want that the keep-alive
+ times out after TCP_KEEPIDLE + TCP_KEEPCNT * TCP_KEEPINTVL secs.
+ To that end, we set keepaliveinterval so that
+
+ keepaliveinterval * FIXED_WSOCK_TCP_KEEPCNT == TCP_KEEPINTVL * TCP_KEEPCNT
+
+ FIXME? Does that make sense?
+
+ Sidenote: Given the max values, the entire operation fits into an int. */
+ tka.keepaliveinterval = MSPERSEC / FIXED_WSOCK_TCP_KEEPCNT * keepcnt
+ * keepintvl;
+ if (WSAIoctl (get_socket (), SIO_KEEPALIVE_VALS, (LPVOID) &tka, sizeof tka,
+ NULL, 0, &dummy, NULL, NULL) == SOCKET_ERROR)
+ {
+ set_winsock_errno ();
+ return -1;
+ }
+ if (!so_keepalive)
+ {
+ ret = ::setsockopt (get_socket (), SOL_SOCKET, SO_KEEPALIVE,
+ (const char *) &so_keepalive, sizeof so_keepalive);
+ if (ret == SOCKET_ERROR)
+ debug_printf ("setsockopt (SO_KEEPALIVE) failed, %u\n",
+ WSAGetLastError ());
+ }
+ return 0;
+}
+
+int
+fhandler_socket_inet::setsockopt (int level, int optname, const void *optval,
+ socklen_t optlen)
+{
+ bool ignore = false;
+ int ret = -1;
+ unsigned int winsock_val;
+
+ /* Preprocessing setsockopt. Set ignore to true if setsockopt call should
+ get skipped entirely. */
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_PEERCRED:
+ set_errno (ENOPROTOOPT);
+ return -1;
+
+ case SO_REUSEADDR:
+ /* Per POSIX we must not be able to reuse a complete duplicate of a
+ local TCP address (same IP, same port), even if SO_REUSEADDR has
+ been set. This behaviour is maintained in WinSock for backward
+ compatibility, while the WinSock standard behaviour of stream
+ socket binding is equivalent to the POSIX behaviour as if
+ SO_REUSEADDR has been set. The SO_EXCLUSIVEADDRUSE option has
+ been added to allow an application to request POSIX standard
+ behaviour in the non-SO_REUSEADDR case.
+
+ To emulate POSIX socket binding behaviour, note that SO_REUSEADDR
+ has been set but don't call setsockopt. Instead
+ fhandler_socket::bind sets SO_EXCLUSIVEADDRUSE if the application
+ did not set SO_REUSEADDR. */
+ if (optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return ret;
+ }
+ if (get_socket_type () == SOCK_STREAM)
+ ignore = true;
+ break;
+
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ if (optlen < (socklen_t) sizeof (struct timeval))
+ {
+ set_errno (EINVAL);
+ return ret;
+ }
+ if (timeval_to_ms ((struct timeval *) optval,
+ (optname == SO_RCVTIMEO) ? rcvtimeo ()
+ : sndtimeo ()))
+ ret = 0;
+ else
+ set_errno (EDOM);
+ return ret;
+
+ case SO_OOBINLINE:
+ /* Inline mode for out-of-band (OOB) data of winsock is
+ completely broken. That is, SIOCATMARK always returns
+ TRUE in inline mode. Due to this problem, application
+ cannot determine OOB data at all. Therefore the behavior
+ of a socket with SO_OOBINLINE set is simulated using
+ a socket with SO_OOBINLINE not set. In this fake inline
+ mode, the order of the OOB and non-OOB data is not
+ preserved. OOB data is read before non-OOB data sent
+ prior to the OOB data. However, this most likely is
+ not a problem in most cases. */
+ /* Here, instead of actually setting inline mode, simply
+ set the variable oobinline. */
+ oobinline = *(int *) optval ? true : false;
+ ignore = true;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_IP:
+ switch (optname)
+ {
+ case IP_TOS:
+ /* Winsock doesn't support setting the IP_TOS field with setsockopt
+ and TOS was never implemented for TCP anyway. setsockopt returns
+ WinSock error 10022, WSAEINVAL when trying to set the IP_TOS
+ field. We just return 0 instead. */
+ ignore = true;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_IPV6:
+ switch (optname)
+ {
+ case IPV6_TCLASS:
+ /* Unsupported */
+ ignore = true;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_TCP:
+ /* Check for stream socket early on, so we don't have to do this for
+ every option. Also, WinSock returns EINVAL. */
+ if (type != SOCK_STREAM)
+ {
+ set_errno (EOPNOTSUPP);
+ return -1;
+ }
+
+ switch (optname)
+ {
+ case TCP_MAXSEG:
+ /* Winsock doesn't support setting TCP_MAXSEG, only requesting it
+ via getsockopt. Make this a no-op. */
+ ignore = true;
+ break;
+
+ case TCP_QUICKACK:
+ /* Various sources on the net claim that TCP_QUICKACK is supported
+ by Windows, even using the same optname value of 12. However,
+ the ws2ipdef.h header calls this option TCP_CONGESTION_ALGORITHM
+ and there's no official statement, nor official documentation
+ confirming or denying this option is equivalent to Linux'
+ TCP_QUICKACK. Also, weirdly, this option takes values from 0..7.
+
+ There is another undocumented option to WSAIoctl called
+ SIO_TCP_SET_ACK_FREQUENCY which is already used by some
+ projects, so we're going to use it here, too, for now.
+
+ There's an open issue in the dotnet github,
+ https://github.com/dotnet/runtime/issues/798
+ Hopefully this clarifies the situation in the not too distant
+ future... */
+ {
+ DWORD dummy;
+ /* https://stackoverflow.com/questions/55034112/c-disable-delayed-ack-on-windows
+ claims that valid values for SIO_TCP_SET_ACK_FREQUENCY are
+ 1..255. In contrast to that, my own testing shows that
+ valid values are 0 and 1 exclusively. */
+ int freq = !!*(int *) optval;
+ if (WSAIoctl (get_socket (), SIO_TCP_SET_ACK_FREQUENCY, &freq,
+ sizeof freq, NULL, 0, &dummy, NULL, NULL)
+ == SOCKET_ERROR)
+ {
+ set_winsock_errno ();
+ return -1;
+ }
+ ignore = true;
+ tcp_quickack = freq ? true : false;
+ }
+ break;
+
+ case TCP_MAXRT:
+ /* Don't let this option slip through from user space. */
+ set_errno (EOPNOTSUPP);
+ return -1;
+
+ case TCP_USER_TIMEOUT:
+ if (!wincap.has_tcp_maxrtms ())
+ {
+ /* convert msecs to secs. Values < 1000 ms are converted to
+ 0 secs, just as in WinSock. */
+ winsock_val = *(unsigned int *) optval / MSPERSEC;
+ optname = TCP_MAXRT;
+ optval = (const void *) &winsock_val;
+ }
+ break;
+
+ case TCP_FASTOPEN:
+ /* Fake FastOpen on older systems. */
+ if (!wincap.has_tcp_fastopen ())
+ {
+ ignore = true;
+ tcp_fastopen = *(int *) optval ? true : false;
+ }
+ break;
+
+ case TCP_KEEPIDLE:
+ /* Handle TCP_KEEPIDLE on older systems. */
+ if (!wincap.has_linux_tcp_keepalive_sockopts ())
+ {
+ if (*(int *) optval < 1 || *(int *) optval > MAX_TCP_KEEPIDLE)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (set_keepalive (*(int *) optval, tcp_keepcnt, tcp_keepintvl))
+ return -1;
+ ignore = true;
+ tcp_keepidle = *(int *) optval;
+ }
+ break;
+
+ case TCP_KEEPCNT:
+ /* Fake TCP_KEEPCNT on older systems. */
+ if (!wincap.has_linux_tcp_keepalive_sockopts ())
+ {
+ if (*(int *) optval < 1 || *(int *) optval > MAX_TCP_KEEPCNT)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (set_keepalive (tcp_keepidle, *(int *) optval, tcp_keepintvl))
+ return -1;
+ ignore = true;
+ tcp_keepcnt = *(int *) optval;
+ }
+ break;
+
+ case TCP_KEEPINTVL:
+ /* Handle TCP_KEEPINTVL on older systems. */
+ if (!wincap.has_linux_tcp_keepalive_sockopts ())
+ {
+ if (*(int *) optval < 1 || *(int *) optval > MAX_TCP_KEEPINTVL)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (set_keepalive (tcp_keepidle, tcp_keepcnt, *(int *) optval))
+ return -1;
+ ignore = true;
+ tcp_keepintvl = *(int *) optval;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_UDP:
+ /* Check for dgram socket early on, so we don't have to do this for
+ every option. Also, WinSock returns EINVAL. */
+ if (type != SOCK_DGRAM)
+ {
+ set_errno (EOPNOTSUPP);
+ return -1;
+ }
+ if (optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return ret;
+ }
+ switch (optname)
+ {
+ case UDP_SEGMENT:
+ if (*(int *) optval < 0 || *(int *) optval > USHRT_MAX)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ break;
+
+ case UDP_GRO:
+ /* In contrast to Windows' UDP_RECV_MAX_COALESCED_SIZE option,
+ Linux' UDP_GRO option is just a bool. The max. packet size
+ is dynamically evaluated from the MRU. There's no easy,
+ reliable way to get the MRU. We assume that this is what Windows
+ will do internally anyway and, given UDP_RECV_MAX_COALESCED_SIZE
+ defines a *maximum* size for aggregated packages, we just choose
+ the maximum sensible value. FIXME? IP_MTU_DISCOVER / IP_MTU */
+ winsock_val = *(int *) optval ? USHRT_MAX : 0;
+ optval = &winsock_val;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Call Winsock setsockopt (or not) */
+ if (ignore)
+ ret = 0;
+ else
+ {
+ ret = ::setsockopt (get_socket (), level, optname, (const char *) optval,
+ optlen);
+ if (ret == SOCKET_ERROR)
+ {
+ set_winsock_errno ();
+ return ret;
+ }
+ }
+
+ if (optlen == (socklen_t) sizeof (int))
+ debug_printf ("setsockopt optval=%x", *(int *) optval);
+
+ /* Postprocessing setsockopt, setting fhandler_socket members, etc. */
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_REUSEADDR:
+ saw_reuseaddr (*(int *) optval);
+ break;
+
+ case SO_RCVBUF:
+ rmem (*(int *) optval);
+ break;
+
+ case SO_SNDBUF:
+ wmem (*(int *) optval);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+int
+fhandler_socket_inet::getsockopt (int level, int optname, const void *optval,
+ socklen_t *optlen)
+{
+ bool onebyte = false;
+ int ret = -1;
+
+ /* Preprocessing getsockopt. */
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_PEERCRED:
+ set_errno (ENOPROTOOPT);
+ return -1;
+
+ case SO_REUSEADDR:
+ {
+ unsigned int *reuseaddr = (unsigned int *) optval;
+
+ if (*optlen < (socklen_t) sizeof *reuseaddr)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ *reuseaddr = saw_reuseaddr();
+ *optlen = (socklen_t) sizeof *reuseaddr;
+ return 0;
+ }
+
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ {
+ struct timeval *time_out = (struct timeval *) optval;
+
+ if (*optlen < (socklen_t) sizeof *time_out)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ DWORD ms = (optname == SO_RCVTIMEO) ? rcvtimeo () : sndtimeo ();
+ if (ms == 0 || ms == INFINITE)
+ {
+ time_out->tv_sec = 0;
+ time_out->tv_usec = 0;
+ }
+ else
+ {
+ time_out->tv_sec = ms / MSPERSEC;
+ time_out->tv_usec = ((ms % MSPERSEC) * USPERSEC) / MSPERSEC;
+ }
+ *optlen = (socklen_t) sizeof *time_out;
+ return 0;
+ }
+
+ case SO_TYPE:
+ {
+ unsigned int *type = (unsigned int *) optval;
+ *type = get_socket_type ();
+ *optlen = (socklen_t) sizeof *type;
+ return 0;
+ }
+
+ case SO_OOBINLINE:
+ *(int *) optval = oobinline ? 1 : 0;
+ return 0;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_IP:
+ break;
+
+ case IPPROTO_TCP:
+ /* Check for stream socket early on, so we don't have to do this for
+ every option. Also, WinSock returns EINVAL. */
+ if (type != SOCK_STREAM)
+ {
+ set_errno (EOPNOTSUPP);
+ return -1;
+ }
+
+ switch (optname)
+ {
+ case TCP_QUICKACK:
+ *(int *) optval = tcp_quickack ? 1 : 0;
+ *optlen = sizeof (int);
+ return 0;
+
+ case TCP_MAXRT:
+ /* Don't let this option slip through from user space. */
+ set_errno (EOPNOTSUPP);
+ return -1;
+
+ case TCP_USER_TIMEOUT:
+ /* Older systems don't support TCP_MAXRTMS, just call TCP_MAXRT. */
+ if (!wincap.has_tcp_maxrtms ())
+ optname = TCP_MAXRT;
+ break;
+
+ case TCP_FASTOPEN:
+ /* Fake FastOpen on older systems */
+ if (!wincap.has_tcp_fastopen ())
+ {
+ *(int *) optval = tcp_fastopen ? 1 : 0;
+ *optlen = sizeof (int);
+ return 0;
+ }
+ break;
+
+ case TCP_KEEPIDLE:
+ /* Use stored value on older systems */
+ if (!wincap.has_linux_tcp_keepalive_sockopts ())
+ {
+ *(int *) optval = tcp_keepidle;
+ *optlen = sizeof (int);
+ return 0;
+ }
+ break;
+
+ case TCP_KEEPCNT:
+ /* Use stored value on older systems */
+ if (!wincap.has_linux_tcp_keepalive_sockopts ())
+ {
+ *(int *) optval = tcp_keepcnt;
+ *optlen = sizeof (int);
+ return 0;
+ }
+ break;
+
+ case TCP_KEEPINTVL:
+ /* Use stored value on older systems */
+ if (!wincap.has_linux_tcp_keepalive_sockopts ())
+ {
+ *(int *) optval = tcp_keepintvl;
+ *optlen = sizeof (int);
+ return 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_UDP:
+ /* Check for dgram socket early on, so we don't have to do this for
+ every option. Also, WinSock returns EINVAL. */
+ if (type != SOCK_DGRAM)
+ {
+ set_errno (EOPNOTSUPP);
+ return -1;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Call Winsock getsockopt */
+ ret = ::getsockopt (get_socket (), level, optname, (char *) optval,
+ (int *) optlen);
+ if (ret == SOCKET_ERROR)
+ {
+ set_winsock_errno ();
+ return ret;
+ }
+
+ /* Postprocessing getsockopt, setting fhandler_socket members, etc. Set
+ onebyte true for options returning BOOLEAN instead of a boolean DWORD. */
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_ERROR:
+ {
+ int *e = (int *) optval;
+ debug_printf ("WinSock SO_ERROR = %d", *e);
+ *e = find_winsock_errno (*e);
+ }
+ break;
+
+ case SO_KEEPALIVE:
+ case SO_DONTROUTE:
+ onebyte = true;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_TCP:
+ switch (optname)
+ {
+ case TCP_NODELAY:
+ onebyte = true;
+ break;
+
+ case TCP_MAXRT: /* After above conversion from TCP_USER_TIMEOUT */
+ /* convert secs to msecs */
+ *(unsigned int *) optval *= MSPERSEC;
+ break;
+
+ case TCP_FASTOPEN:
+ onebyte = true;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_UDP:
+ switch (optname)
+ {
+ case UDP_GRO:
+ /* Convert to bool option */
+ *(unsigned int *) optval = *(unsigned int *) optval ? 1 : 0;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (onebyte)
+ {
+ /* Regression in 6.0 kernel and later: instead of a 4 byte BOOL value, a
+ 1 byte BOOLEAN value is returned, in contrast to older systems and
+ the documentation. Since an int type is expected by the calling
+ application, we convert the result here. */
+ BOOLEAN *in = (BOOLEAN *) optval;
+ int *out = (int *) optval;
+ *out = *in;
+ *optlen = 4;
+ }
+
+ return ret;
+}
+
+int
+fhandler_socket_wsock::ioctl (unsigned int cmd, void *p)
+{
+ int res;
+
+ switch (cmd)
+ {
+ /* Here we handle only ioctl commands which are understood by Winsock.
+ However, we have a problem, which is, the different size of u_long
+ in Windows and 64 bit Cygwin. This affects the definitions of
+ FIOASYNC, etc, because they are defined in terms of sizeof(u_long).
+ So we have to use case labels which are independent of the sizeof
+ u_long. Since we're redefining u_long at the start of this file to
+ matching Winsock's idea of u_long, we can use the real definitions in
+ calls to Windows. In theory we also have to make sure to convert the
+ different ideas of u_long between the application and Winsock, but
+ fortunately, the parameters defined as u_long pointers are on Linux
+ and BSD systems defined as int pointer, so the applications will
+ use a type of the expected size. Hopefully. */
+ case FIOASYNC:
+ case _IOW('f', 125, u_long):
+ res = WSAAsyncSelect (get_socket (), winmsg, WM_ASYNCIO,
+ *(int *) p ? ASYNC_MASK : 0);
+ syscall_printf ("Async I/O on socket %s",
+ *(int *) p ? "started" : "cancelled");
+ async_io (*(int *) p != 0);
+ /* If async_io is switched off, revert the event handling. */
+ if (*(int *) p == 0)
+ WSAEventSelect (get_socket (), wsock_evt, EVENT_MASK);
+ break;
+ case FIONREAD:
+ case _IOR('f', 127, u_long):
+ /* Make sure to use the Winsock definition of FIONREAD. */
+ res = ::ioctlsocket (get_socket (), _IOR('f', 127, u_long), (u_long *) p);
+ if (res == SOCKET_ERROR)
+ set_winsock_errno ();
+ break;
+ case FIONBIO:
+ case SIOCATMARK:
+ /* Sockets are always non-blocking internally. So we just note the
+ state here. */
+ /* Convert the different idea of u_long in the definition of cmd. */
+ if (((cmd >> 16) & IOCPARM_MASK) == sizeof (unsigned long))
+ cmd = (cmd & ~(IOCPARM_MASK << 16)) | (sizeof (u_long) << 16);
+ if (cmd == FIONBIO)
+ {
+ syscall_printf ("socket is now %sblocking",
+ *(int *) p ? "non" : "");
+ set_nonblocking (*(int *) p);
+ res = 0;
+ }
+ else
+ res = ::ioctlsocket (get_socket (), cmd, (u_long *) p);
+ /* In winsock, the return value of SIOCATMARK is FALSE if
+ OOB data exists, TRUE otherwise. This is almost opposite
+ to expectation. */
+ /* SIOCATMARK = _IOR('s',7,u_long) */
+ if (cmd == _IOR('s',7,u_long) && !res)
+ *(u_long *)p = !*(u_long *)p;
+ break;
+ default:
+ res = fhandler_socket::ioctl (cmd, p);
+ break;
+ }
+ syscall_printf ("%d = ioctl_socket(%x, %p)", res, cmd, p);
+ return res;
+}
+
+int
+fhandler_socket_wsock::fcntl (int cmd, intptr_t arg)
+{
+ int res = 0;
+
+ switch (cmd)
+ {
+ case F_SETOWN:
+ {
+ pid_t pid = (pid_t) arg;
+ LOCK_EVENTS;
+ wsock_events->owner = pid;
+ UNLOCK_EVENTS;
+ debug_printf ("owner set to %d", pid);
+ }
+ break;
+ case F_GETOWN:
+ res = wsock_events->owner;
+ break;
+ default:
+ res = fhandler_socket::fcntl (cmd, arg);
+ break;
+ }
+ return res;
+}
diff --git a/winsup/cygwin/fhandler/socket_local.cc b/winsup/cygwin/fhandler/socket_local.cc
new file mode 100644
index 000000000..e4a88169b
--- /dev/null
+++ b/winsup/cygwin/fhandler/socket_local.cc
@@ -0,0 +1,1691 @@
+/* fhandler_socket_local.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. */
+
+#define __INSIDE_CYGWIN_NET__
+#define USE_SYS_TYPES_FD_SET
+
+#include "winsup.h"
+/* 2014-04-24: Current Mingw headers define sockaddr_in6 using u_long (8 byte)
+ because a redefinition for LP64 systems is missing. This leads to a wrong
+ definition and size of sockaddr_in6 when building with winsock headers.
+ This definition is also required to use the right u_long type in subsequent
+ function calls. */
+#undef u_long
+#define u_long __ms_u_long
+#include "ntsecapi.h"
+#include <w32api/ws2tcpip.h>
+#include <w32api/mswsock.h>
+#include <unistd.h>
+#include <asm/byteorder.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/param.h>
+#include <sys/statvfs.h>
+#include <cygwin/acl.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "wininfo.h"
+#include "ntdll.h"
+
+extern "C" {
+ int sscanf (const char *, const char *, ...);
+} /* End of "C" section */
+
+#define ASYNC_MASK (FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT)
+#define EVENT_MASK (FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE)
+
+#define LOCK_EVENTS \
+ if (wsock_mtx && \
+ WaitForSingleObject (wsock_mtx, INFINITE) != WAIT_FAILED) \
+ {
+
+#define UNLOCK_EVENTS \
+ ReleaseMutex (wsock_mtx); \
+ }
+
+static inline mode_t
+adjust_socket_file_mode (mode_t mode)
+{
+ /* Kludge: Don't allow to remove read bit on socket files for
+ user/group/other, if the accompanying write bit is set. It would
+ be nice to have exact permissions on a socket file, but it's
+ necessary that somebody able to access the socket can always read
+ the contents of the socket file to avoid spurious "permission
+ denied" messages. */
+ return mode | ((mode & (S_IWUSR | S_IWGRP | S_IWOTH)) << 1);
+}
+
+/* cygwin internal: map sockaddr into internet domain address */
+int
+get_inet_addr_local (const struct sockaddr *in, int inlen,
+ struct sockaddr_storage *out, int *outlen,
+ int *type = NULL, int *secret = NULL)
+{
+ int secret_buf [4];
+ int* secret_ptr = (secret ? : secret_buf);
+
+ /* Check for abstract socket. These are generated for AF_LOCAL datagram
+ sockets in recv_internal, to allow a datagram server to use sendto
+ after recvfrom. */
+ if (inlen >= (int) sizeof (in->sa_family) + 7
+ && in->sa_data[0] == '\0' && in->sa_data[1] == 'd'
+ && in->sa_data[6] == '\0')
+ {
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ sscanf (in->sa_data + 2, "%04hx", &addr.sin_port);
+ addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ *outlen = sizeof addr;
+ memcpy (out, &addr, *outlen);
+ return 0;
+ }
+
+ path_conv pc (in->sa_data, PC_SYM_FOLLOW);
+ if (pc.error)
+ {
+ set_errno (pc.error);
+ return SOCKET_ERROR;
+ }
+ if (!pc.exists ())
+ {
+ set_errno (ENOENT);
+ return SOCKET_ERROR;
+ }
+ /* Do NOT test for the file being a socket file here. The socket file
+ creation is not an atomic operation, so there is a chance that socket
+ files which are just in the process of being created are recognized
+ as non-socket files. To work around this problem we now create the
+ file with all sharing disabled. If the below NtOpenFile fails
+ with STATUS_SHARING_VIOLATION we know that the file already exists,
+ but the creating process isn't finished yet. So we yield and try
+ again, until we can either open the file successfully, or some error
+ other than STATUS_SHARING_VIOLATION occurs.
+ Since we now don't know if the file is actually a socket file, we
+ perform this check here explicitely. */
+ NTSTATUS status;
+ HANDLE fh;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+
+ pc.get_object_attr (attr, sec_none_nih);
+ do
+ {
+ status = NtOpenFile (&fh, GENERIC_READ | SYNCHRONIZE, &attr, &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_SYNCHRONOUS_IO_NONALERT
+ | FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_NON_DIRECTORY_FILE);
+ if (status == STATUS_SHARING_VIOLATION)
+ {
+ /* While we hope that the sharing violation is only temporary, we
+ also could easily get stuck here, waiting for a file in use by
+ some greedy Win32 application. Therefore we should never wait
+ endlessly without checking for signals and thread cancel event. */
+ pthread_testcancel ();
+ if (cygwait (NULL, cw_nowait, cw_sig_eintr) == WAIT_SIGNALED
+ && !_my_tls.call_signal_handler ())
+ {
+ set_errno (EINTR);
+ return SOCKET_ERROR;
+ }
+ yield ();
+ }
+ else if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return SOCKET_ERROR;
+ }
+ }
+ while (status == STATUS_SHARING_VIOLATION);
+ /* Now test for the SYSTEM bit. */
+ FILE_BASIC_INFORMATION fbi;
+ status = NtQueryInformationFile (fh, &io, &fbi, sizeof fbi,
+ FileBasicInformation);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return SOCKET_ERROR;
+ }
+ if (!(fbi.FileAttributes & FILE_ATTRIBUTE_SYSTEM))
+ {
+ NtClose (fh);
+ set_errno (EBADF);
+ return SOCKET_ERROR;
+ }
+ /* Eventually check the content and fetch the required information. */
+ char buf[128];
+ memset (buf, 0, sizeof buf);
+ status = NtReadFile (fh, NULL, NULL, NULL, &io, buf, 128, NULL, NULL);
+ NtClose (fh);
+ if (NT_SUCCESS (status))
+ {
+ struct sockaddr_in sin;
+ char ctype;
+ sin.sin_family = AF_INET;
+ if (strncmp (buf, SOCKET_COOKIE, strlen (SOCKET_COOKIE)))
+ {
+ set_errno (EBADF);
+ return SOCKET_ERROR;
+ }
+ sscanf (buf + strlen (SOCKET_COOKIE), "%hu %c %08x-%08x-%08x-%08x",
+ &sin.sin_port,
+ &ctype,
+ secret_ptr, secret_ptr + 1, secret_ptr + 2, secret_ptr + 3);
+ sin.sin_port = htons (sin.sin_port);
+ sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ memcpy (out, &sin, sizeof sin);
+ *outlen = sizeof sin;
+ if (type)
+ *type = (ctype == 's' ? SOCK_STREAM :
+ ctype == 'd' ? SOCK_DGRAM
+ : 0);
+ return 0;
+ }
+ __seterrno_from_nt_status (status);
+ return SOCKET_ERROR;
+}
+
+/* There's no DLL which exports the symbol WSARecvMsg. One has to call
+ WSAIoctl as below to fetch the function pointer. Why on earth did the
+ MS developers decide not to export a normal symbol for these extension
+ functions? */
+inline int
+get_ext_funcptr (SOCKET sock, void *funcptr)
+{
+ DWORD bret;
+ const GUID guid = WSAID_WSARECVMSG;
+ return WSAIoctl (sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
+ (void *) &guid, sizeof (GUID), funcptr, sizeof (void *),
+ &bret, NULL, NULL);
+}
+
+fhandler_socket_local::fhandler_socket_local () :
+ fhandler_socket_wsock (),
+ sun_path (NULL),
+ peer_sun_path (NULL),
+ status ()
+{
+}
+
+fhandler_socket_local::~fhandler_socket_local ()
+{
+ if (sun_path)
+ cfree (sun_path);
+ if (peer_sun_path)
+ cfree (peer_sun_path);
+}
+
+int
+fhandler_socket_local::socket (int af, int type, int protocol, int flags)
+{
+ SOCKET sock;
+ int ret;
+
+ if (type != SOCK_STREAM && type != SOCK_DGRAM)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (protocol != 0)
+ {
+ set_errno (EPROTONOSUPPORT);
+ return -1;
+ }
+ sock = ::socket (AF_INET, type, protocol);
+ if (sock == INVALID_SOCKET)
+ {
+ set_winsock_errno ();
+ return -1;
+ }
+ ret = set_socket_handle (sock, af, type, flags);
+ if (ret < 0)
+ ::closesocket (sock);
+ return ret;
+}
+
+int
+fhandler_socket_local::socketpair (int af, int type, int protocol, int flags,
+ fhandler_socket *_fh_out)
+{
+ SOCKET insock = INVALID_SOCKET;
+ SOCKET outsock = INVALID_SOCKET;
+ SOCKET sock = INVALID_SOCKET;
+ struct sockaddr_in sock_in, sock_out;
+ int len;
+ fhandler_socket_local *fh_out = reinterpret_cast<fhandler_socket_local *>
+ (_fh_out);
+
+ if (type != SOCK_STREAM && type != SOCK_DGRAM)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (protocol != 0)
+ {
+ set_errno (EPROTONOSUPPORT);
+ return -1;
+ }
+ /* create listening socket */
+ sock = ::socket (AF_INET, type, 0);
+ if (sock == INVALID_SOCKET)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ /* bind to unused port */
+ sock_in.sin_family = AF_INET;
+ sock_in.sin_port = 0;
+ sock_in.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ if (::bind (sock, (struct sockaddr *) &sock_in, sizeof (sock_in)) < 0)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ /* fetch socket name */
+ len = sizeof (sock_in);
+ if (::getsockname (sock, (struct sockaddr *) &sock_in, &len) < 0)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ /* on stream sockets, create listener */
+ if (type == SOCK_STREAM && ::listen (sock, 2) < 0)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ /* create connecting socket */
+ outsock = ::socket (AF_INET, type, 0);
+ if (outsock == INVALID_SOCKET)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ /* on datagram sockets, bind connecting socket */
+ if (type == SOCK_DGRAM)
+ {
+ sock_out.sin_family = AF_INET;
+ sock_out.sin_port = 0;
+ sock_out.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ if (::bind (outsock, (struct sockaddr *) &sock_out,
+ sizeof (sock_out)) < 0)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ /* ...and fetch name */
+ len = sizeof (sock_out);
+ if (::getsockname (outsock, (struct sockaddr *) &sock_out, &len) < 0)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ }
+ sock_in.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ if (type == SOCK_DGRAM)
+ sock_out.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ /* connect */
+ if (::connect (outsock, (struct sockaddr *) &sock_in, sizeof (sock_in)) < 0)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ if (type == SOCK_STREAM)
+ {
+ /* on stream sockets, accept connection and close listener */
+ len = sizeof (sock_in);
+ insock = ::accept (sock, (struct sockaddr *) &sock_in, &len);
+ if (insock == INVALID_SOCKET)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ ::closesocket (sock);
+ }
+ else
+ {
+ /* on datagram sockets, connect vice versa */
+ if (::connect (sock, (struct sockaddr *) &sock_out,
+ sizeof (sock_out)) < 0)
+ {
+ set_winsock_errno ();
+ goto err;
+ }
+ insock = sock;
+ }
+ sock = INVALID_SOCKET;
+
+ /* postprocessing */
+ connect_state (connected);
+ fh_out->connect_state (connected);
+ if (af == AF_LOCAL && type == SOCK_STREAM)
+ {
+ af_local_set_sockpair_cred ();
+ fh_out->af_local_set_sockpair_cred ();
+ }
+ if (set_socket_handle (insock, af, type, flags) < 0
+ || fh_out->set_socket_handle (outsock, af, type, flags) < 0)
+ goto err;
+
+ return 0;
+
+err:
+ if (sock != INVALID_SOCKET)
+ ::closesocket (sock);
+ if (insock != INVALID_SOCKET)
+ ::closesocket (insock);
+ if (outsock != INVALID_SOCKET)
+ ::closesocket (outsock);
+ return -1;
+}
+
+void
+fhandler_socket_local::af_local_set_sockpair_cred ()
+{
+ sec_pid = sec_peer_pid = getpid ();
+ sec_uid = sec_peer_uid = geteuid ();
+ sec_gid = sec_peer_gid = getegid ();
+}
+
+void
+fhandler_socket_local::af_local_setblocking (bool &async, bool &nonblocking)
+{
+ async = async_io ();
+ nonblocking = is_nonblocking ();
+ if (async)
+ {
+ WSAAsyncSelect (get_socket (), winmsg, 0, 0);
+ WSAEventSelect (get_socket (), wsock_evt, EVENT_MASK);
+ }
+ set_nonblocking (false);
+ async_io (false);
+}
+
+void
+fhandler_socket_local::af_local_unsetblocking (bool async, bool nonblocking)
+{
+ if (nonblocking)
+ set_nonblocking (true);
+ if (async)
+ {
+ WSAAsyncSelect (get_socket (), winmsg, WM_ASYNCIO, ASYNC_MASK);
+ async_io (true);
+ }
+}
+
+bool
+fhandler_socket_local::af_local_recv_secret ()
+{
+ int out[4] = { 0, 0, 0, 0 };
+ int rest = sizeof out;
+ char *ptr = (char *) out;
+ while (rest > 0)
+ {
+ int ret = recvfrom (ptr, rest, 0, NULL, NULL);
+ if (ret <= 0)
+ break;
+ rest -= ret;
+ ptr += ret;
+ }
+ if (rest == 0)
+ {
+ debug_printf ("Received af_local secret: %08x-%08x-%08x-%08x",
+ out[0], out[1], out[2], out[3]);
+ if (out[0] != connect_secret[0] || out[1] != connect_secret[1]
+ || out[2] != connect_secret[2] || out[3] != connect_secret[3])
+ {
+ debug_printf ("Receiving af_local secret mismatch");
+ return false;
+ }
+ }
+ else
+ debug_printf ("Receiving af_local secret failed");
+ return rest == 0;
+}
+
+bool
+fhandler_socket_local::af_local_send_secret ()
+{
+ int rest = sizeof connect_secret;
+ char *ptr = (char *) connect_secret;
+ while (rest > 0)
+ {
+ int ret = sendto (ptr, rest, 0, NULL, 0);
+ if (ret <= 0)
+ break;
+ rest -= ret;
+ ptr += ret;
+ }
+ debug_printf ("Sending af_local secret %s", rest == 0 ? "succeeded"
+ : "failed");
+ return rest == 0;
+}
+
+bool
+fhandler_socket_local::af_local_recv_cred ()
+{
+ struct ucred out = { (pid_t) 0, (uid_t) -1, (gid_t) -1 };
+ int rest = sizeof out;
+ char *ptr = (char *) &out;
+ while (rest > 0)
+ {
+ int ret = recvfrom (ptr, rest, 0, NULL, NULL);
+ if (ret <= 0)
+ break;
+ rest -= ret;
+ ptr += ret;
+ }
+ if (rest == 0)
+ {
+ debug_printf ("Received eid credentials: pid: %d, uid: %d, gid: %d",
+ out.pid, out.uid, out.gid);
+ sec_peer_pid = out.pid;
+ sec_peer_uid = out.uid;
+ sec_peer_gid = out.gid;
+ }
+ else
+ debug_printf ("Receiving eid credentials failed");
+ return rest == 0;
+}
+
+bool
+fhandler_socket_local::af_local_send_cred ()
+{
+ struct ucred in = { sec_pid, sec_uid, sec_gid };
+ int rest = sizeof in;
+ char *ptr = (char *) &in;
+ while (rest > 0)
+ {
+ int ret = sendto (ptr, rest, 0, NULL, 0);
+ if (ret <= 0)
+ break;
+ rest -= ret;
+ ptr += ret;
+ }
+ if (rest == 0)
+ debug_printf ("Sending eid credentials succeeded");
+ else
+ debug_printf ("Sending eid credentials failed");
+ return rest == 0;
+}
+
+int
+fhandler_socket_local::af_local_connect ()
+{
+ bool orig_async_io, orig_is_nonblocking;
+
+ if (get_socket_type () != SOCK_STREAM)
+ return 0;
+
+ debug_printf ("af_local_connect called, no_getpeereid=%d", no_getpeereid ());
+ if (no_getpeereid ())
+ return 0;
+
+ af_local_setblocking (orig_async_io, orig_is_nonblocking);
+ if (!af_local_send_secret () || !af_local_recv_secret ()
+ || !af_local_send_cred () || !af_local_recv_cred ())
+ {
+ debug_printf ("accept from unauthorized server");
+ ::shutdown (get_socket (), SD_BOTH);
+ WSASetLastError (WSAECONNREFUSED);
+ return -1;
+ }
+ af_local_unsetblocking (orig_async_io, orig_is_nonblocking);
+ return 0;
+}
+
+int
+fhandler_socket_local::af_local_accept ()
+{
+ bool orig_async_io, orig_is_nonblocking;
+
+ debug_printf ("af_local_accept called, no_getpeereid=%d", no_getpeereid ());
+ if (no_getpeereid ())
+ return 0;
+
+ af_local_setblocking (orig_async_io, orig_is_nonblocking);
+ if (!af_local_recv_secret () || !af_local_send_secret ()
+ || !af_local_recv_cred () || !af_local_send_cred ())
+ {
+ debug_printf ("connect from unauthorized client");
+ ::shutdown (get_socket (), SD_BOTH);
+ ::closesocket (get_socket ());
+ WSASetLastError (WSAECONNABORTED);
+ return -1;
+ }
+ af_local_unsetblocking (orig_async_io, orig_is_nonblocking);
+ return 0;
+}
+
+int
+fhandler_socket_local::af_local_set_no_getpeereid ()
+{
+ if (get_addr_family () != AF_LOCAL || get_socket_type () != SOCK_STREAM)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (connect_state () != unconnected)
+ {
+ set_errno (EALREADY);
+ return -1;
+ }
+
+ debug_printf ("no_getpeereid set");
+ no_getpeereid (true);
+ return 0;
+}
+
+void
+fhandler_socket_local::af_local_set_cred ()
+{
+ sec_pid = getpid ();
+ sec_uid = geteuid ();
+ sec_gid = getegid ();
+ sec_peer_pid = (pid_t) 0;
+ sec_peer_uid = (uid_t) -1;
+ sec_peer_gid = (gid_t) -1;
+}
+
+void
+fhandler_socket_local::af_local_copy (fhandler_socket_local *sock)
+{
+ sock->connect_secret[0] = connect_secret[0];
+ sock->connect_secret[1] = connect_secret[1];
+ sock->connect_secret[2] = connect_secret[2];
+ sock->connect_secret[3] = connect_secret[3];
+ sock->sec_pid = sec_pid;
+ sock->sec_uid = sec_uid;
+ sock->sec_gid = sec_gid;
+ sock->sec_peer_pid = sec_peer_pid;
+ sock->sec_peer_uid = sec_peer_uid;
+ sock->sec_peer_gid = sec_peer_gid;
+ sock->no_getpeereid (no_getpeereid ());
+}
+
+void
+fhandler_socket_local::af_local_set_secret (char *buf)
+{
+ if (!RtlGenRandom (connect_secret, sizeof (connect_secret)))
+ bzero ((char*) connect_secret, sizeof (connect_secret));
+ __small_sprintf (buf, "%08x-%08x-%08x-%08x",
+ connect_secret [0], connect_secret [1],
+ connect_secret [2], connect_secret [3]);
+}
+
+int
+fhandler_socket_local::dup (fhandler_base *child, int flags)
+{
+ if (get_flags () & O_PATH)
+ /* We're viewing the socket as a disk file, but fhandler_base::dup
+ suffices here. */
+ return fhandler_base::dup (child, flags);
+
+ fhandler_socket_local *fhs = (fhandler_socket_local *) child;
+ fhs->set_sun_path (get_sun_path ());
+ fhs->set_peer_sun_path (get_peer_sun_path ());
+ return fhandler_socket_wsock::dup (child, flags);
+}
+
+int
+fhandler_socket_local::open (int flags, mode_t mode)
+{
+ /* We don't support opening sockets unless O_PATH is specified. */
+ if (flags & O_PATH)
+ return open_fs (flags, mode);
+
+ set_errno (EOPNOTSUPP);
+ return 0;
+}
+
+int
+fhandler_socket_local::close ()
+{
+ if (get_flags () & O_PATH)
+ return fhandler_base::close ();
+ else
+ return fhandler_socket_wsock::close ();
+}
+
+int
+fhandler_socket_local::fcntl (int cmd, intptr_t arg)
+{
+ if (get_flags () & O_PATH)
+ /* We're viewing the socket as a disk file, but
+ fhandler_base::fcntl suffices here. */
+ return fhandler_base::fcntl (cmd, arg);
+ else
+ return fhandler_socket_wsock::fcntl (cmd, arg);
+}
+
+int
+fhandler_socket_local::fstat (struct stat *buf)
+{
+ if (!dev ().isfs ())
+ /* fstat called on a socket. */
+ return fhandler_socket_wsock::fstat (buf);
+
+ /* stat/lstat on a socket file or fstat on a socket opened w/ O_PATH. */
+ int res = fhandler_base::fstat_fs (buf);
+ if (!res)
+ {
+ buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFSOCK;
+ buf->st_size = 0;
+ }
+ return res;
+}
+
+int
+fhandler_socket_local::fstatvfs (struct statvfs *sfs)
+{
+ if (!dev ().isfs ())
+ /* fstatvfs called on a socket. */
+ return fhandler_socket_wsock::fstatvfs (sfs);
+
+ /* statvfs on a socket file or fstatvfs on a socket opened w/ O_PATH. */
+ 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);
+}
+
+int
+fhandler_socket_local::fchmod (mode_t newmode)
+{
+ if (!dev ().isfs ())
+ /* fchmod called on a socket. */
+ return fhandler_socket_wsock::fchmod (newmode);
+
+ /* chmod on a socket file. [We won't get here if fchmod is called
+ on a socket opened w/ O_PATH.] */
+ fhandler_disk_file fh (pc);
+ fh.get_device () = FH_FS;
+ return fh.fchmod (S_IFSOCK | adjust_socket_file_mode (newmode));
+}
+
+int
+fhandler_socket_local::fchown (uid_t uid, gid_t gid)
+{
+ if (!dev ().isfs ())
+ /* fchown called on a socket. */
+ return fhandler_socket_wsock::fchown (uid, gid);
+
+ /* chown/lchown on a socket file. [We won't get here if fchown is
+ called on a socket opened w/ O_PATH.] */
+ fhandler_disk_file fh (pc);
+ return fh.fchown (uid, gid);
+}
+
+int
+fhandler_socket_local::facl (int cmd, int nentries, aclent_t *aclbufp)
+{
+ if (!dev ().isfs ())
+ /* facl called on a socket. */
+ return fhandler_socket_wsock::facl (cmd, nentries, aclbufp);
+
+ /* facl on a socket file. [We won't get here if facl is called on a
+ socket opened w/ O_PATH.] */
+ fhandler_disk_file fh (pc);
+ return fh.facl (cmd, nentries, aclbufp);
+}
+
+int
+fhandler_socket_local::link (const char *newpath)
+{
+ if (!dev ().isfs ())
+ /* linkat w/ AT_EMPTY_PATH called on a socket not opened w/ O_PATH. */
+ return fhandler_socket_wsock::link (newpath);
+ /* link on a socket file or linkat w/ AT_EMPTY_PATH called on a
+ socket opened w/ O_PATH. */
+ fhandler_disk_file fh (pc);
+ if (get_flags () & O_PATH)
+ fh.set_handle (get_handle ());
+ return fh.link (newpath);
+}
+
+int
+fhandler_socket_local::bind (const struct sockaddr *name, int namelen)
+{
+ int res = -1;
+
+#define un_addr ((struct sockaddr_un *) name)
+ struct sockaddr_in sin;
+ int len = namelen - offsetof (struct sockaddr_un, sun_path);
+
+ /* Check that name is within bounds. Don't check if the string is
+ NUL-terminated, because there are projects out there which set
+ namelen to a value which doesn't cover the trailing NUL. */
+ if (len <= 1 || (len = strnlen (un_addr->sun_path, len)) > UNIX_PATH_MAX)
+ {
+ set_errno (len <= 1 ? (len == 1 ? ENOENT : EINVAL) : ENAMETOOLONG);
+ return -1;
+ }
+ /* Copy over the sun_path string into a buffer big enough to add a
+ trailing NUL. */
+ char sun_path[len + 1];
+ strncpy (sun_path, un_addr->sun_path, len);
+ sun_path[len] = '\0';
+
+ /* This isn't entirely foolproof, but we check first if the file exists
+ so we can return with EADDRINUSE before having bound the socket.
+ This allows an application to call bind again on the same socket using
+ another filename. If we bind first, the application will not be able
+ to call bind successfully ever again. */
+ path_conv pc (sun_path, PC_SYM_FOLLOW);
+ if (pc.error)
+ {
+ set_errno (pc.error);
+ return -1;
+ }
+ if (pc.exists ())
+ {
+ set_errno (EADDRINUSE);
+ return -1;
+ }
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = 0;
+ sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ if (::bind (get_socket (), (sockaddr *) &sin, len = sizeof sin))
+ {
+ syscall_printf ("AF_LOCAL: bind failed");
+ set_winsock_errno ();
+ return -1;
+ }
+ if (::getsockname (get_socket (), (sockaddr *) &sin, &len))
+ {
+ syscall_printf ("AF_LOCAL: getsockname failed");
+ set_winsock_errno ();
+ return -1;
+ }
+
+ sin.sin_port = ntohs (sin.sin_port);
+ debug_printf ("AF_LOCAL: socket bound to port %u", sin.sin_port);
+
+ mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+ DWORD fattr = FILE_ATTRIBUTE_SYSTEM;
+ if (!pc.has_acls ()
+ && !(mode & ~cygheap->umask & (S_IWUSR | S_IWGRP | S_IWOTH)))
+ fattr |= FILE_ATTRIBUTE_READONLY;
+ SECURITY_ATTRIBUTES sa = sec_none_nih;
+ NTSTATUS status;
+ HANDLE fh;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ ULONG access = DELETE | FILE_GENERIC_WRITE;
+
+ /* If the filesystem supports ACLs, we will overwrite the DACL after the
+ call to NtCreateFile. This requires a handle with READ_CONTROL and
+ WRITE_DAC access, otherwise get_file_sd and set_file_sd both have to
+ open the file again.
+ FIXME: On remote NTFS shares open sometimes fails because even the
+ creator of the file doesn't have the right to change the DACL.
+ I don't know what setting that is or how to recognize such a share,
+ so for now we don't request WRITE_DAC on remote drives. */
+ if (pc.has_acls () && !pc.isremote ())
+ access |= READ_CONTROL | WRITE_DAC | WRITE_OWNER;
+
+ status = NtCreateFile (&fh, access, pc.get_object_attr (attr, sa), &io,
+ NULL, fattr, 0, FILE_CREATE,
+ FILE_NON_DIRECTORY_FILE
+ | FILE_SYNCHRONOUS_IO_NONALERT
+ | FILE_OPEN_FOR_BACKUP_INTENT,
+ NULL, 0);
+ if (!NT_SUCCESS (status))
+ {
+ if (io.Information == FILE_EXISTS)
+ set_errno (EADDRINUSE);
+ else
+ __seterrno_from_nt_status (status);
+ }
+ else
+ {
+ if (pc.has_acls ())
+ set_created_file_access (fh, pc, mode);
+ char buf[sizeof (SOCKET_COOKIE) + 80];
+ __small_sprintf (buf, "%s%u %c ", SOCKET_COOKIE, sin.sin_port,
+ get_socket_type () == SOCK_STREAM ? 's'
+ : get_socket_type () == SOCK_DGRAM ? 'd' : '-');
+ af_local_set_secret (strchr (buf, '\0'));
+ DWORD blen = strlen (buf) + 1;
+ status = NtWriteFile (fh, NULL, NULL, NULL, &io, buf, blen, NULL, 0);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ FILE_DISPOSITION_INFORMATION fdi = { TRUE };
+ status = NtSetInformationFile (fh, &io, &fdi, sizeof fdi,
+ FileDispositionInformation);
+ if (!NT_SUCCESS (status))
+ debug_printf ("Setting delete dispostion failed, status = %y",
+ status);
+ }
+ else
+ {
+ set_sun_path (sun_path);
+ res = 0;
+ }
+ NtClose (fh);
+ }
+#undef un_addr
+
+ return res;
+}
+
+int
+fhandler_socket_local::connect (const struct sockaddr *name, int namelen)
+{
+ struct sockaddr_storage sst;
+ int type = 0;
+ bool reset = (name->sa_family == AF_UNSPEC
+ && get_socket_type () == SOCK_DGRAM);
+
+ if (reset)
+ {
+ if (connect_state () == unconnected)
+ return 0;
+ /* To reset a connected DGRAM socket, call Winsock's connect
+ function with the address member of the sockaddr structure
+ filled with zeroes. */
+ memset (&sst, 0, sizeof sst);
+ sst.ss_family = get_addr_family ();
+ }
+ else if (get_inet_addr_local (name, namelen, &sst, &namelen, &type,
+ connect_secret) == SOCKET_ERROR)
+ return SOCKET_ERROR;
+
+ if (get_socket_type () != type && !reset)
+ {
+ WSASetLastError (WSAEPROTOTYPE);
+ set_winsock_errno ();
+ return SOCKET_ERROR;
+ }
+
+ if (reset)
+ set_peer_sun_path (NULL);
+ else
+ set_peer_sun_path (name->sa_data);
+
+ /* Don't move af_local_set_cred into af_local_connect which may be called
+ via select, possibly running under another identity. Call early here,
+ because af_local_connect is called in wait_for_events. */
+ if (get_socket_type () == SOCK_STREAM)
+ af_local_set_cred ();
+
+ /* Initialize connect state to "connect_pending". In the SOCK_STREAM
+ case, the state is ultimately set to "connected" or "connect_failed" in
+ wait_for_events when the FD_CONNECT event occurs. Note that the
+ underlying OS sockets are always non-blocking in this case and a
+ successfully initiated non-blocking Winsock connect always returns
+ WSAEWOULDBLOCK. Thus it's safe to rely on event handling. For DGRAM
+ sockets, however, connect can return immediately.
+
+ Check for either unconnected or connect_failed since in both cases it's
+ allowed to retry connecting the socket. It's also ok (albeit ugly) to
+ call connect to check if a previous non-blocking connect finished.
+
+ Set connect_state before calling connect, otherwise a race condition with
+ an already running select or poll might occur. */
+ if (connect_state () == unconnected || connect_state () == connect_failed)
+ connect_state (connect_pending);
+
+ int res = ::connect (get_socket (), (struct sockaddr *) &sst, namelen);
+ if (!res)
+ {
+ if (reset)
+ connect_state (unconnected);
+ else
+ connect_state (connected);
+ }
+ else if (!is_nonblocking ()
+ && res == SOCKET_ERROR
+ && WSAGetLastError () == WSAEWOULDBLOCK)
+ res = wait_for_events (FD_CONNECT | FD_CLOSE, 0);
+
+ if (res)
+ {
+ DWORD err = WSAGetLastError ();
+
+ /* Some applications use the ugly technique to check if a non-blocking
+ connect succeeded by calling connect again, until it returns EISCONN.
+ This circumvents the event handling and connect_state is never set.
+ Thus we check for this situation here. */
+ if (err == WSAEISCONN)
+ connect_state (connected);
+ /* Winsock returns WSAEWOULDBLOCK if the non-blocking socket cannot be
+ conected immediately. Convert to POSIX/Linux compliant EINPROGRESS. */
+ else if (is_nonblocking () && err == WSAEWOULDBLOCK)
+ WSASetLastError (WSAEINPROGRESS);
+ /* Winsock returns WSAEINVAL if the socket is already a listener.
+ Convert to POSIX/Linux compliant EISCONN. */
+ else if (err == WSAEINVAL && connect_state () == listener)
+ WSASetLastError (WSAEISCONN);
+ /* Any other error except WSAEALREADY means the connect failed. */
+ else if (connect_state () == connect_pending && err != WSAEALREADY)
+ connect_state (connect_failed);
+ set_winsock_errno ();
+ }
+
+ return res;
+}
+
+int
+fhandler_socket_local::listen (int backlog)
+{
+ int res = ::listen (get_socket (), backlog);
+ if (res && WSAGetLastError () == WSAEINVAL)
+ {
+ /* It's perfectly valid to call listen on an unbound INET socket.
+ In this case the socket is automatically bound to an unused
+ port number, listening on all interfaces. On WinSock, listen
+ fails with WSAEINVAL when it's called on an unbound socket.
+ So we have to bind manually here to have POSIX semantics. */
+ if (get_addr_family () == AF_INET)
+ {
+ struct sockaddr_in sin;
+ sin.sin_family = AF_INET;
+ sin.sin_port = 0;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ if (!::bind (get_socket (), (struct sockaddr *) &sin, sizeof sin))
+ res = ::listen (get_socket (), backlog);
+ }
+ else if (get_addr_family () == AF_INET6)
+ {
+ struct sockaddr_in6 sin6;
+ memset (&sin6, 0, sizeof sin6);
+ sin6.sin6_family = AF_INET6;
+ if (!::bind (get_socket (), (struct sockaddr *) &sin6, sizeof sin6))
+ res = ::listen (get_socket (), backlog);
+ }
+ }
+ if (!res)
+ {
+ if (get_addr_family () == AF_LOCAL && get_socket_type () == SOCK_STREAM)
+ af_local_set_cred ();
+ connect_state (listener); /* gets set to connected on accepted socket. */
+ }
+ else
+ set_winsock_errno ();
+ return res;
+}
+
+int
+fhandler_socket_local::accept4 (struct sockaddr *peer, int *len, int flags)
+{
+ int ret = -1;
+ /* Allows NULL peer and len parameters. */
+ struct sockaddr_storage lpeer;
+ int llen = sizeof (struct sockaddr_storage);
+
+ /* Windows event handling does not check for the validity of the desired
+ flags so we have to do it here. */
+ if (connect_state () != listener)
+ {
+ WSASetLastError (WSAEINVAL);
+ set_winsock_errno ();
+ return -1;
+ }
+
+ SOCKET res = INVALID_SOCKET;
+ while (!(res = wait_for_events (FD_ACCEPT | FD_CLOSE, 0))
+ && (res = ::accept (get_socket (), (struct sockaddr *) &lpeer, &llen))
+ == INVALID_SOCKET
+ && WSAGetLastError () == WSAEWOULDBLOCK)
+ ;
+ if (res == INVALID_SOCKET)
+ set_winsock_errno ();
+ else
+ {
+ cygheap_fdnew fd;
+
+ if (fd >= 0)
+ {
+ fhandler_socket_local *sock = (fhandler_socket_local *)
+ build_fh_dev (dev ());
+ if (sock && sock->set_socket_handle (res, get_addr_family (),
+ get_socket_type (),
+ get_socket_flags ()) == 0)
+ {
+ sock->async_io (false); /* set_socket_handle disables async. */
+ sock->set_sun_path (get_sun_path ());
+ sock->set_peer_sun_path (get_peer_sun_path ());
+ if (get_socket_type () == SOCK_STREAM)
+ {
+ /* Don't forget to copy credentials from accepting
+ socket to accepted socket and start transaction
+ on accepted socket! */
+ af_local_copy (sock);
+ ret = sock->af_local_accept ();
+ if (ret == -1)
+ {
+ delete sock;
+ set_winsock_errno ();
+ return -1;
+ }
+ }
+ /* No locking necessary at this point. */
+ sock->wsock_events->events = wsock_events->events | FD_WRITE;
+ sock->wsock_events->owner = wsock_events->owner;
+ sock->connect_state (connected);
+ fd = sock;
+ if (fd <= 2)
+ set_std_handle (fd);
+ ret = fd;
+ if (peer)
+ {
+ /* FIXME: Right now we have no way to determine the
+ bound socket name of the peer's socket. For now
+ we just fake an unbound socket on the other side. */
+ static struct sockaddr_un un = { AF_LOCAL, "" };
+ memcpy (peer, &un, MIN (*len, (int) sizeof (un.sun_family)));
+ *len = (int) sizeof (un.sun_family);
+ }
+ }
+ else
+ delete sock;
+ }
+ if (ret == -1)
+ ::closesocket (res);
+ }
+ return ret;
+}
+
+int
+fhandler_socket_local::getsockname (struct sockaddr *name, int *namelen)
+{
+ struct sockaddr_un sun;
+
+ sun.sun_family = AF_LOCAL;
+ sun.sun_path[0] = '\0';
+ if (get_sun_path ())
+ strncat (sun.sun_path, get_sun_path (), UNIX_PATH_MAX - 1);
+ memcpy (name, &sun, MIN (*namelen, (int) SUN_LEN (&sun) + 1));
+ *namelen = (int) SUN_LEN (&sun) + (get_sun_path () ? 1 : 0);
+ return 0;
+}
+
+int
+fhandler_socket_local::getpeername (struct sockaddr *name, int *namelen)
+{
+ /* Always use a local big enough buffer and truncate later as necessary
+ per POSIX. WinSock unfortunately only returns WSAEFAULT if the buffer
+ is too small. */
+ struct sockaddr_storage sock;
+ int len = sizeof sock;
+ int res = ::getpeername (get_socket (), (struct sockaddr *) &sock, &len);
+ if (res)
+ set_winsock_errno ();
+ else
+ {
+ struct sockaddr_un sun;
+ memset (&sun, 0, sizeof sun);
+ sun.sun_family = AF_LOCAL;
+ sun.sun_path[0] = '\0';
+ if (get_peer_sun_path ())
+ strncat (sun.sun_path, get_peer_sun_path (), UNIX_PATH_MAX - 1);
+ memcpy (name, &sun, MIN (*namelen, (int) SUN_LEN (&sun) + 1));
+ *namelen = (int) SUN_LEN (&sun) + (get_peer_sun_path () ? 1 : 0);
+ }
+ return res;
+}
+
+ssize_t
+fhandler_socket_local::recv_internal (LPWSAMSG wsamsg, bool use_recvmsg)
+{
+ ssize_t res = 0;
+ DWORD ret = 0, wret;
+ int evt_mask = FD_READ;
+ LPWSABUF &wsabuf = wsamsg->lpBuffers;
+ ULONG &wsacnt = wsamsg->dwBufferCount;
+ static NO_COPY LPFN_WSARECVMSG WSARecvMsg;
+ int orig_namelen = wsamsg->namelen;
+
+ /* CV 2014-10-26: Do not check for the connect_state at this point. In
+ certain scenarios there's no way to check the connect state reliably.
+ Example (hexchat): Parent process creates socket, forks, child process
+ calls connect, parent process calls read. Even if the event handling
+ allows to check for FD_CONNECT in the parent, there is always yet another
+ scenario we can easily break. */
+
+ DWORD wait_flags = wsamsg->dwFlags;
+ bool waitall = !!(wait_flags & MSG_WAITALL);
+
+ /* Out-of-band data not supported by AF_LOCAL */
+ if (wsamsg->dwFlags & MSG_OOB)
+ {
+ set_errno (EOPNOTSUPP);
+ return SOCKET_ERROR;
+ }
+
+ wsamsg->dwFlags &= (MSG_PEEK | MSG_DONTROUTE);
+ if (use_recvmsg)
+ {
+ if (!WSARecvMsg
+ && get_ext_funcptr (get_socket (), &WSARecvMsg) == SOCKET_ERROR)
+ {
+ if (wsamsg->Control.len > 0)
+ {
+ set_winsock_errno ();
+ return SOCKET_ERROR;
+ }
+ use_recvmsg = false;
+ }
+ else /* Only MSG_PEEK is supported by WSARecvMsg. */
+ wsamsg->dwFlags &= MSG_PEEK;
+ }
+ if (waitall)
+ {
+ if (get_socket_type () != SOCK_STREAM)
+ {
+ WSASetLastError (WSAEOPNOTSUPP);
+ set_winsock_errno ();
+ return SOCKET_ERROR;
+ }
+ if (is_nonblocking () || (wsamsg->dwFlags & MSG_PEEK))
+ waitall = false;
+ }
+
+ /* Note: Don't call WSARecvFrom(MSG_PEEK) without actually having data
+ waiting in the buffers, otherwise the event handling gets messed up
+ for some reason. */
+ while (!(res = wait_for_events (evt_mask | FD_CLOSE, wait_flags))
+ || saw_shutdown_read ())
+ {
+ DWORD dwFlags = wsamsg->dwFlags;
+ if (use_recvmsg)
+ res = WSARecvMsg (get_socket (), wsamsg, &wret, NULL, NULL);
+ /* This is working around a really weird problem in WinSock.
+
+ Assume you create a socket, fork the process (thus duplicating
+ the socket), connect the socket in the child, then call recv
+ on the original socket handle in the parent process.
+ In this scenario, calls to WinSock's recvfrom and WSARecvFrom
+ in the parent will fail with WSAEINVAL, regardless whether both
+ address parameters, name and namelen, are NULL or point to valid
+ storage. However, calls to recv and WSARecv succeed as expected.
+ Per MSDN, WSAEINVAL in the context of recv means "The socket has not
+ been bound". It is as if the recvfrom functions test if the socket
+ is bound locally, but in the parent process, WinSock doesn't know
+ about that and fails, while the same test is omitted in the recv
+ functions.
+
+ This also covers another weird case: WinSock returns WSAEFAULT if
+ namelen is a valid pointer while name is NULL. Both parameters are
+ ignored for TCP sockets, so this only occurs when using UDP socket. */
+ else if (!wsamsg->name || get_socket_type () == SOCK_STREAM)
+ res = WSARecv (get_socket (), wsabuf, wsacnt, &wret, &dwFlags,
+ NULL, NULL);
+ else
+ res = WSARecvFrom (get_socket (), wsabuf, wsacnt, &wret,
+ &dwFlags, wsamsg->name, &wsamsg->namelen,
+ NULL, NULL);
+ if (!res)
+ {
+ ret += wret;
+ if (!waitall)
+ break;
+ while (wret && wsacnt)
+ {
+ if (wsabuf->len > wret)
+ {
+ wsabuf->len -= wret;
+ wsabuf->buf += wret;
+ wret = 0;
+ }
+ else
+ {
+ wret -= wsabuf->len;
+ ++wsabuf;
+ --wsacnt;
+ }
+ }
+ if (!wsacnt)
+ break;
+ }
+ else if (WSAGetLastError () != WSAEWOULDBLOCK)
+ break;
+ }
+
+ if (res)
+ {
+ /* According to SUSv3, errno isn't set in that case and no error
+ condition is returned. */
+ if (WSAGetLastError () == WSAEMSGSIZE)
+ ret += wret;
+ else if (!ret)
+ {
+ /* ESHUTDOWN isn't defined for recv in SUSv3. Simply EOF is returned
+ in this case. */
+ if (WSAGetLastError () == WSAESHUTDOWN)
+ ret = 0;
+ else
+ {
+ set_winsock_errno ();
+ return SOCKET_ERROR;
+ }
+ }
+ }
+
+ if (wsamsg->name != NULL && orig_namelen >= (int) sizeof (sa_family_t))
+ {
+ /* WSARecvFrom copied the sockaddr_in block to wsamsg->name. We have to
+ overwrite it with a sockaddr_un block. For datagram sockets we
+ generate a sockaddr_un with a filename analogue to abstract socket
+ names under Linux. See `man 7 unix' under Linux for a description. */
+ sockaddr_un *un = (sockaddr_un *) wsamsg->name;
+ un->sun_family = AF_LOCAL;
+ int len = orig_namelen - offsetof (struct sockaddr_un, sun_path);
+ if (len > 0)
+ {
+ if (get_socket_type () == SOCK_DGRAM)
+ {
+ if (len >= 7)
+ {
+ __small_sprintf (un->sun_path + 1, "d%04x",
+ ((struct sockaddr_in *) wsamsg->name)->sin_port);
+ wsamsg->namelen = offsetof (struct sockaddr_un, sun_path) + 7;
+ }
+ else
+ wsamsg->namelen = offsetof (struct sockaddr_un, sun_path) + 1;
+ un->sun_path[0] = '\0';
+ }
+ else if (!get_peer_sun_path ())
+ wsamsg->namelen = sizeof (sa_family_t);
+ else
+ {
+ memset (un->sun_path, 0, len);
+ strncpy (un->sun_path, get_peer_sun_path (), len);
+ if (un->sun_path[len - 1] == '\0')
+ len = strlen (un->sun_path) + 1;
+ if (len > UNIX_PATH_MAX)
+ len = UNIX_PATH_MAX;
+ wsamsg->namelen = offsetof (struct sockaddr_un, sun_path) + len;
+ }
+ }
+ }
+
+ return ret;
+}
+
+ssize_t
+fhandler_socket_local::sendto (const void *in_ptr, size_t len, int flags,
+ const struct sockaddr *to, int tolen)
+{
+ char *ptr = (char *) in_ptr;
+ struct sockaddr_storage sst;
+
+ /* Out-of-band data not supported by AF_LOCAL */
+ if (flags & MSG_OOB)
+ {
+ set_errno (EOPNOTSUPP);
+ return SOCKET_ERROR;
+ }
+
+ if (to && get_inet_addr_local (to, tolen, &sst, &tolen) == SOCKET_ERROR)
+ return SOCKET_ERROR;
+
+ /* size_t is 64 bit, but the len member in WSABUF is 32 bit.
+ Split buffer if necessary. */
+ DWORD bufcnt = len / UINT32_MAX + ((!len || (len % UINT32_MAX)) ? 1 : 0);
+ WSABUF wsabuf[bufcnt];
+ WSAMSG wsamsg = { to ? (struct sockaddr *) &sst : NULL, tolen,
+ wsabuf, bufcnt,
+ { 0, NULL },
+ 0 };
+ /* Don't use len as loop condition, it could be 0. */
+ for (WSABUF *wsaptr = wsabuf; bufcnt--; ++wsaptr)
+ {
+ wsaptr->len = MIN (len, UINT32_MAX);
+ wsaptr->buf = ptr;
+ len -= wsaptr->len;
+ ptr += wsaptr->len;
+ }
+ return send_internal (&wsamsg, flags);
+}
+
+ssize_t
+fhandler_socket_local::sendmsg (const struct msghdr *msg, int flags)
+{
+ /* TODO: Descriptor passing on AF_LOCAL sockets. */
+
+ struct sockaddr_storage sst;
+ int len = 0;
+
+ /* Out-of-band data not supported by AF_LOCAL */
+ if (flags & MSG_OOB)
+ {
+ set_errno (EOPNOTSUPP);
+ return SOCKET_ERROR;
+ }
+
+ if (msg->msg_name
+ && get_inet_addr_local ((struct sockaddr *) msg->msg_name,
+ msg->msg_namelen, &sst, &len) == SOCKET_ERROR)
+ return SOCKET_ERROR;
+
+ WSABUF wsabuf[msg->msg_iovlen];
+ WSABUF *wsaptr = wsabuf;
+ const struct iovec *iovptr = msg->msg_iov;
+ for (int i = 0; i < msg->msg_iovlen; ++i)
+ {
+ wsaptr->len = iovptr->iov_len;
+ (wsaptr++)->buf = (char *) (iovptr++)->iov_base;
+ }
+ /* Disappointing but true: Even if WSASendMsg is supported, it's only
+ supported for datagram and raw sockets. */
+ DWORD controllen = (DWORD) (get_socket_type () == SOCK_STREAM
+ || get_addr_family () == AF_LOCAL
+ ? 0 : msg->msg_controllen);
+ WSAMSG wsamsg = { msg->msg_name ? (struct sockaddr *) &sst : NULL, len,
+ wsabuf, (DWORD) msg->msg_iovlen,
+ { controllen, (char *) msg->msg_control },
+ 0 };
+ return send_internal (&wsamsg, flags);
+}
+
+void
+fhandler_socket_local::set_sun_path (const char *path)
+{
+ sun_path = path ? cstrdup (path) : NULL;
+}
+
+void
+fhandler_socket_local::set_peer_sun_path (const char *path)
+{
+ peer_sun_path = path ? cstrdup (path) : NULL;
+}
+
+int
+fhandler_socket_local::getpeereid (pid_t *pid, uid_t *euid, gid_t *egid)
+{
+ if (get_socket_type () != SOCK_STREAM)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (no_getpeereid ())
+ {
+ set_errno (ENOTSUP);
+ return -1;
+ }
+ if (connect_state () != connected)
+ {
+ set_errno (ENOTCONN);
+ return -1;
+ }
+
+ __try
+ {
+ if (pid)
+ *pid = sec_peer_pid;
+ if (euid)
+ *euid = sec_peer_uid;
+ if (egid)
+ *egid = sec_peer_gid;
+ return 0;
+ }
+ __except (EFAULT) {}
+ __endtry
+ return -1;
+}
+
+int
+fhandler_socket_local::setsockopt (int level, int optname, const void *optval,
+ socklen_t optlen)
+{
+ int ret = -1;
+
+ /* Preprocessing setsockopt. */
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_PEERCRED:
+ /* Switch off the AF_LOCAL handshake and thus SO_PEERCRED handling
+ for AF_LOCAL/SOCK_STREAM sockets. This allows to handle special
+ situations in which connect is called before a listening socket
+ accepts connections.
+ FIXME: In the long run we should find a more generic solution
+ which doesn't require a blocking handshake in accept/connect
+ to exchange SO_PEERCRED credentials. */
+ /* Temporary: Allow SO_PEERCRED to only be zeroed. Two ways to
+ accomplish this: pass NULL,0 for optval,optlen; or pass the
+ address,length of an '(int) 0' set up by the caller. */
+ if ((!optval && !optlen) ||
+ (optlen == (socklen_t) sizeof (int) && !*(int *) optval))
+ ret = af_local_set_no_getpeereid ();
+ else
+ set_errno (EINVAL);
+ return ret;
+
+ case SO_REUSEADDR:
+ saw_reuseaddr (*(int *) optval);
+ return 0;
+
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ if (optlen < (socklen_t) sizeof (struct timeval))
+ {
+ set_errno (EINVAL);
+ return ret;
+ }
+ if (timeval_to_ms ((struct timeval *) optval,
+ (optname == SO_RCVTIMEO) ? rcvtimeo ()
+ : sndtimeo ()))
+ return 0;
+ set_errno (EDOM);
+ return -1;
+
+ case SO_DEBUG:
+ case SO_RCVBUF:
+ case SO_RCVLOWAT:
+ case SO_SNDBUF:
+ case SO_SNDLOWAT:
+ break;
+
+ default:
+ /* AF_LOCAL sockets simply ignore all other SOL_SOCKET options. */
+ return 0;
+ }
+ break;
+
+ default:
+ set_errno (ENOPROTOOPT);
+ return -1;
+ }
+
+ /* Call Winsock setsockopt */
+ ret = ::setsockopt (get_socket (), level, optname, (const char *) optval,
+ optlen);
+ if (ret == SOCKET_ERROR)
+ {
+ set_winsock_errno ();
+ return ret;
+ }
+
+ if (optlen == (socklen_t) sizeof (int))
+ debug_printf ("setsockopt optval=%x", *(int *) optval);
+
+ /* Postprocessing setsockopt, setting fhandler_socket members, etc. */
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_RCVBUF:
+ rmem (*(int *) optval);
+ break;
+
+ case SO_SNDBUF:
+ wmem (*(int *) optval);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+int
+fhandler_socket_local::getsockopt (int level, int optname, const void *optval,
+ socklen_t *optlen)
+{
+ int ret = -1;
+
+ /* Preprocessing getsockopt.*/
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_PEERCRED:
+ {
+ struct ucred *cred = (struct ucred *) optval;
+
+ if (*optlen < (socklen_t) sizeof *cred)
+ {
+ set_errno (EINVAL);
+ return ret;
+ }
+ ret = getpeereid (&cred->pid, &cred->uid, &cred->gid);
+ if (!ret)
+ *optlen = (socklen_t) sizeof *cred;
+ return ret;
+ }
+
+ case SO_REUSEADDR:
+ {
+ unsigned int *reuseaddr = (unsigned int *) optval;
+
+ if (*optlen < (socklen_t) sizeof *reuseaddr)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ *reuseaddr = saw_reuseaddr();
+ *optlen = (socklen_t) sizeof *reuseaddr;
+ return 0;
+ }
+
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ {
+ struct timeval *time_out = (struct timeval *) optval;
+
+ if (*optlen < (socklen_t) sizeof *time_out)
+ {
+ set_errno (EINVAL);
+ return ret;
+ }
+ DWORD ms = (optname == SO_RCVTIMEO) ? rcvtimeo () : sndtimeo ();
+ if (ms == 0 || ms == INFINITE)
+ {
+ time_out->tv_sec = 0;
+ time_out->tv_usec = 0;
+ }
+ else
+ {
+ time_out->tv_sec = ms / MSPERSEC;
+ time_out->tv_usec = ((ms % MSPERSEC) * USPERSEC) / MSPERSEC;
+ }
+ *optlen = (socklen_t) sizeof *time_out;
+ return 0;
+ }
+
+ case SO_TYPE:
+ {
+ unsigned int *type = (unsigned int *) optval;
+ *type = get_socket_type ();
+ *optlen = (socklen_t) sizeof *type;
+ return 0;
+ }
+
+ case SO_ACCEPTCONN:
+ case SO_DEBUG:
+ case SO_ERROR:
+ case SO_RCVBUF:
+ case SO_RCVLOWAT:
+ case SO_SNDBUF:
+ case SO_SNDLOWAT:
+ break;
+
+ /* AF_LOCAL sockets simply ignore all other SOL_SOCKET options. */
+
+ case SO_LINGER:
+ {
+ struct linger *linger = (struct linger *) optval;
+ memset (linger, 0, sizeof *linger);
+ *optlen = (socklen_t) sizeof *linger;
+ return 0;
+ }
+
+ default:
+ {
+ unsigned int *val = (unsigned int *) optval;
+ *val = 0;
+ *optlen = (socklen_t) sizeof *val;
+ return 0;
+ }
+ }
+ break;
+
+ default:
+ set_errno (ENOPROTOOPT);
+ return -1;
+ }
+
+ /* Call Winsock getsockopt */
+ ret = ::getsockopt (get_socket (), level, optname, (char *) optval,
+ (int *) optlen);
+ if (ret == SOCKET_ERROR)
+ {
+ set_winsock_errno ();
+ return ret;
+ }
+
+ /* Postprocessing getsockopt, setting fhandler_socket members, etc. */
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_ERROR:
+ {
+ int *e = (int *) optval;
+ debug_printf ("WinSock SO_ERROR = %d", *e);
+ *e = find_winsock_errno (*e);
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
diff --git a/winsup/cygwin/fhandler/socket_unix.cc b/winsup/cygwin/fhandler/socket_unix.cc
new file mode 100644
index 000000000..0cd97f625
--- /dev/null
+++ b/winsup/cygwin/fhandler/socket_unix.cc
@@ -0,0 +1,2406 @@
+/* fhandler_socket_unix.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"
+
+GUID __cygwin_socket_guid = {
+ .Data1 = 0xefc1714d,
+ .Data2 = 0x7b19,
+ .Data3 = 0x4407,
+ .Data4 = { 0xba, 0xb3, 0xc5, 0xb1, 0xf9, 0x2c, 0xb8, 0x8c }
+};
+
+#ifdef __WITH_AF_UNIX
+
+#include <w32api/winioctl.h>
+#include <asm/byteorder.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/param.h>
+#include <sys/statvfs.h>
+#include <cygwin/acl.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "shared_info.h"
+#include "ntdll.h"
+#include "miscfuncs.h"
+#include "tls_pbuf.h"
+
+/*
+ Abstract socket:
+
+ An abstract socket is represented by a symlink in the native
+ NT namespace, within the Cygwin subdir in BasedNamedObjects.
+ So it's globally available but only exists as long as at least on
+ descriptor on the socket is open, as desired.
+
+ The name of the symlink is: "af-unix-<sun_path>"
+
+ <sun_path> is the transposed sun_path string, including the leading
+ NUL. The transposition is simplified in that it uses every byte
+ in the valid sun_path name as is, no extra multibyte conversion.
+ The content of the symlink is the basename of the underlying pipe.
+
+ Named socket:
+
+ A named socket is represented by a reparse point with a Cygwin-specific
+ tag and GUID. The GenericReparseBuffer content is the basename of the
+ underlying pipe.
+
+ Pipe:
+
+ The pipe is named \\.\pipe\cygwin-<installation_key>-unix-[sd]-<uniq_id>
+
+ - <installation_key> is the 8 byte hex Cygwin installation key
+ - [sd] is s for SOCK_STREAM, d for SOCK_DGRAM
+ - <uniq_id> is an 8 byte hex unique number
+
+ Note: We use MAX_PATH below for convenience where sufficient. It's
+ big enough to hold sun_paths as well as pipe names as well as packet
+ headers etc., so we don't have to use tmp_pathbuf as often.
+
+ Every packet sent to a peer is a combination of the socket name of the
+ local socket, the ancillary data, and the actual user data. The data
+ is always sent in this order. The header contains length information
+ for the entire packet, as well as for all three data blocks. The
+ combined maximum size of a packet is 64K, including the header.
+
+ A connecting, bound STREAM socket sends it's local sun_path once after
+ a successful connect. An already connected socket also sends its local
+ sun_path after a successful bind (border case, but still...). These
+ packages don't contain any other data (cmsg_len == 0, data_len == 0).
+
+ A bound DGRAM socket sends its sun_path with each sendmsg/sendto.
+*/
+class af_unix_pkt_hdr_t
+{
+ public:
+ uint16_t pckt_len; /* size of packet including header */
+ bool admin_pkg : 1; /* admin packets are marked as such */
+ shut_state shut_info : 2; /* _SHUT_RECV /_SHUT_SEND. */
+ uint8_t name_len; /* size of name, a sockaddr_un */
+ uint16_t cmsg_len; /* size of ancillary data block */
+ uint16_t data_len; /* size of user data */
+
+ af_unix_pkt_hdr_t (bool a, shut_state s, uint8_t n, uint16_t c, uint16_t d)
+ { init (a, s, n, c, d); }
+ void init (bool a, shut_state s, uint8_t n, uint16_t c, uint16_t d)
+ {
+ admin_pkg = a;
+ shut_info = s;
+ name_len = n;
+ cmsg_len = c;
+ data_len = d;
+ pckt_len = sizeof (*this) + name_len + cmsg_len + data_len;
+ }
+};
+
+#define AF_UNIX_PKT_OFFSETOF_NAME(phdr) \
+ (sizeof (af_unix_pkt_hdr_t))
+#define AF_UNIX_PKT_OFFSETOF_CMSG(phdr) \
+ (sizeof (af_unix_pkt_hdr_t) + (phdr)->name_len)
+#define AF_UNIX_PKT_OFFSETOF_DATA(phdr) \
+ ({ \
+ af_unix_pkt_hdr_t *_p = phdr; \
+ sizeof (af_unix_pkt_hdr_t) + (_p)->name_len + (_p)->cmsg_len; \
+ })
+#define AF_UNIX_PKT_NAME(phdr) \
+ ({ \
+ af_unix_pkt_hdr_t *_p = phdr; \
+ (struct sockaddr_un *)(((PBYTE)(_p)) \
+ + AF_UNIX_PKT_OFFSETOF_NAME (_p)); \
+ })
+#define AF_UNIX_PKT_CMSG(phdr) \
+ ({ \
+ af_unix_pkt_hdr_t *_p = phdr; \
+ (struct cmsghdr *)(((PBYTE)(_p)) + AF_UNIX_PKT_OFFSETOF_CMSG (_p)); \
+ })
+#define AF_UNIX_PKT_DATA(phdr) \
+ ({ \
+ af_unix_pkt_hdr_t _p = phdr; \
+ (void *)(((PBYTE)(_p)) + AF_UNIX_PKT_OFFSETOF_DATA (_p)); \
+ })
+
+/* Some error conditions on pipes have multiple status codes, unfortunately. */
+#define STATUS_PIPE_NO_INSTANCE_AVAILABLE(status) \
+ ({ NTSTATUS _s = (status); \
+ _s == STATUS_INSTANCE_NOT_AVAILABLE \
+ || _s == STATUS_PIPE_NOT_AVAILABLE \
+ || _s == STATUS_PIPE_BUSY; })
+
+#define STATUS_PIPE_IS_CLOSING(status) \
+ ({ NTSTATUS _s = (status); \
+ _s == STATUS_PIPE_CLOSING \
+ || _s == STATUS_PIPE_EMPTY; })
+
+#define STATUS_PIPE_INVALID(status) \
+ ({ NTSTATUS _s = (status); \
+ _s == STATUS_INVALID_INFO_CLASS \
+ || _s == STATUS_INVALID_PIPE_STATE \
+ || _s == STATUS_INVALID_READ_MODE; })
+
+#define STATUS_PIPE_MORE_DATA(status) \
+ ({ NTSTATUS _s = (status); \
+ _s == STATUS_BUFFER_OVERFLOW \
+ || _s == STATUS_MORE_PROCESSING_REQUIRED; })
+
+/* Default timeout value of connect: 20 secs, as on Linux. */
+#define AF_UNIX_CONNECT_TIMEOUT (-20 * NS100PERSEC)
+
+void
+sun_name_t::set (const struct sockaddr_un *name, socklen_t namelen)
+{
+ if (namelen < 0)
+ namelen = 0;
+ un_len = namelen < (__socklen_t) sizeof un ? namelen : sizeof un;
+ un.sun_family = AF_UNIX;
+ if (name && un_len)
+ memcpy (&un, name, un_len);
+ _nul[un_len] = '\0';
+}
+
+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;
+}
+
+/* Called from socket, socketpair, accept4 */
+int
+fhandler_socket_unix::create_shmem ()
+{
+ HANDLE sect;
+ OBJECT_ATTRIBUTES attr;
+ NTSTATUS status;
+ LARGE_INTEGER size = { .QuadPart = sizeof (af_unix_shmem_t) };
+ SIZE_T viewsize = sizeof (af_unix_shmem_t);
+ PVOID addr = NULL;
+
+ InitializeObjectAttributes (&attr, NULL, OBJ_INHERIT, NULL, NULL);
+ status = NtCreateSection (&sect, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY
+ | SECTION_MAP_READ | SECTION_MAP_WRITE,
+ &attr, &size, PAGE_READWRITE, SEC_COMMIT, NULL);
+ 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 = (af_unix_shmem_t *) addr;
+ return 0;
+}
+
+/* Called from dup, fixup_after_fork. Expects shmem_handle to be
+ valid. */
+int
+fhandler_socket_unix::reopen_shmem ()
+{
+ NTSTATUS status;
+ SIZE_T viewsize = PAGESIZE;
+ PVOID addr = NULL;
+
+ status = NtMapViewOfSection (shmem_handle, NtCurrentProcess (), &addr, 0,
+ PAGESIZE, NULL, &viewsize, ViewShare, 0,
+ PAGE_READWRITE);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ shmem = (af_unix_shmem_t *) addr;
+ return 0;
+}
+
+/* Character length of pipe name, excluding trailing NUL. */
+#define CYGWIN_PIPE_SOCKET_NAME_LEN 47
+
+/* Character position encoding the socket type in a pipe name. */
+#define CYGWIN_PIPE_SOCKET_TYPE_POS 29
+
+void
+fhandler_socket_unix::gen_pipe_name ()
+{
+ WCHAR pipe_name_buf[CYGWIN_PIPE_SOCKET_NAME_LEN + 1];
+ UNICODE_STRING pipe_name;
+
+ __small_swprintf (pipe_name_buf, L"cygwin-%S-unix-%C-%016_X",
+ &cygheap->installation_key,
+ get_type_char (),
+ get_unique_id ());
+ RtlInitUnicodeString (&pipe_name, pipe_name_buf);
+ pc.set_nt_native_path (&pipe_name);
+}
+
+HANDLE
+fhandler_socket_unix::create_abstract_link (const sun_name_t *sun,
+ PUNICODE_STRING pipe_name)
+{
+ WCHAR name[MAX_PATH];
+ OBJECT_ATTRIBUTES attr;
+ NTSTATUS status;
+ UNICODE_STRING uname;
+ HANDLE fh = NULL;
+
+ PWCHAR p = wcpcpy (name, L"af-unix-");
+ /* NUL bytes have no special meaning in an abstract socket name, so
+ we assume iso-8859-1 for simplicity and transpose the string.
+ transform_chars_af_unix is doing just that. */
+ p = transform_chars_af_unix (p, sun->un.sun_path, sun->un_len);
+ *p = L'\0';
+ RtlInitUnicodeString (&uname, name);
+ InitializeObjectAttributes (&attr, &uname, OBJ_CASE_INSENSITIVE,
+ get_shared_parent_dir (), NULL);
+ /* Fill symlink with name of pipe */
+ status = NtCreateSymbolicLinkObject (&fh, SYMBOLIC_LINK_ALL_ACCESS,
+ &attr, pipe_name);
+ if (!NT_SUCCESS (status))
+ {
+ if (status == STATUS_OBJECT_NAME_EXISTS
+ || status == STATUS_OBJECT_NAME_COLLISION)
+ set_errno (EADDRINUSE);
+ else
+ __seterrno_from_nt_status (status);
+ }
+ return fh;
+}
+
+struct rep_pipe_name_t
+{
+ USHORT Length;
+ WCHAR PipeName[1];
+};
+
+HANDLE
+fhandler_socket_unix::create_reparse_point (const sun_name_t *sun,
+ PUNICODE_STRING pipe_name)
+{
+ ULONG access;
+ HANDLE old_trans = NULL, trans = NULL;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ NTSTATUS status;
+ HANDLE fh = NULL;
+ PREPARSE_GUID_DATA_BUFFER rp;
+ rep_pipe_name_t *rep_pipe_name;
+
+ const DWORD data_len = offsetof (rep_pipe_name_t, PipeName)
+ + pipe_name->Length + sizeof (WCHAR);
+
+ path_conv pc (sun->un.sun_path, PC_SYM_FOLLOW);
+ if (pc.error)
+ {
+ set_errno (pc.error);
+ return NULL;
+ }
+ if (pc.exists ())
+ {
+ set_errno (EADDRINUSE);
+ return NULL;
+ }
+ /* We will overwrite the DACL after the call to NtCreateFile. This
+ requires READ_CONTROL and WRITE_DAC access, otherwise get_file_sd
+ and set_file_sd both have to open the file again.
+ FIXME: On remote NTFS shares open sometimes fails because even the
+ creator of the file doesn't have the right to change the DACL.
+ I don't know what setting that is or how to recognize such a share,
+ so for now we don't request WRITE_DAC on remote drives. */
+ access = DELETE | FILE_GENERIC_WRITE;
+ if (!pc.isremote ())
+ access |= READ_CONTROL | WRITE_DAC | WRITE_OWNER;
+ if ((pc.fs_flags () & FILE_SUPPORTS_TRANSACTIONS))
+ start_transaction (old_trans, trans);
+
+retry_after_transaction_error:
+ status = NtCreateFile (&fh, DELETE | FILE_GENERIC_WRITE,
+ pc.get_object_attr (attr, sec_none_nih), &io,
+ NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE,
+ FILE_SYNCHRONOUS_IO_NONALERT
+ | FILE_NON_DIRECTORY_FILE
+ | FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_OPEN_REPARSE_POINT,
+ NULL, 0);
+ if (NT_TRANSACTIONAL_ERROR (status) && trans)
+ {
+ stop_transaction (status, old_trans, trans);
+ goto retry_after_transaction_error;
+ }
+
+ if (!NT_SUCCESS (status))
+ {
+ if (io.Information == FILE_EXISTS)
+ set_errno (EADDRINUSE);
+ else
+ __seterrno_from_nt_status (status);
+ goto out;
+ }
+ rp = (PREPARSE_GUID_DATA_BUFFER)
+ alloca (REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + data_len);
+ rp->ReparseTag = IO_REPARSE_TAG_CYGUNIX;
+ rp->ReparseDataLength = data_len;
+ rp->Reserved = 0;
+ memcpy (&rp->ReparseGuid, CYGWIN_SOCKET_GUID, sizeof (GUID));
+ rep_pipe_name = (rep_pipe_name_t *) rp->GenericReparseBuffer.DataBuffer;
+ rep_pipe_name->Length = pipe_name->Length;
+ memcpy (rep_pipe_name->PipeName, pipe_name->Buffer, pipe_name->Length);
+ rep_pipe_name->PipeName[pipe_name->Length / sizeof (WCHAR)] = L'\0';
+ status = NtFsControlFile (fh, NULL, NULL, NULL, &io,
+ FSCTL_SET_REPARSE_POINT, rp,
+ REPARSE_GUID_DATA_BUFFER_HEADER_SIZE
+ + rp->ReparseDataLength, NULL, 0);
+ if (NT_SUCCESS (status))
+ {
+ mode_t perms = (S_IRWXU | S_IRWXG | S_IRWXO) & ~cygheap->umask;
+ set_created_file_access (fh, pc, perms);
+ NtClose (fh);
+ /* We don't have to keep the file open, but the caller needs to
+ get a value != NULL to know the file creation went fine. */
+ fh = INVALID_HANDLE_VALUE;
+ }
+ else if (!trans)
+ {
+ FILE_DISPOSITION_INFORMATION fdi = { TRUE };
+
+ __seterrno_from_nt_status (status);
+ status = NtSetInformationFile (fh, &io, &fdi, sizeof fdi,
+ FileDispositionInformation);
+ if (!NT_SUCCESS (status))
+ debug_printf ("Setting delete dispostion failed, status = %y",
+ status);
+ NtClose (fh);
+ fh = NULL;
+ }
+
+out:
+ if (trans)
+ stop_transaction (status, old_trans, trans);
+ return fh;
+}
+
+HANDLE
+fhandler_socket_unix::create_file (const sun_name_t *sun)
+{
+ if (sun->un_len <= (socklen_t) sizeof (sa_family_t)
+ || (sun->un_len == 3 && sun->un.sun_path[0] == '\0'))
+ {
+ set_errno (EINVAL);
+ return NULL;
+ }
+ if (sun->un.sun_path[0] == '\0')
+ return create_abstract_link (sun, pc.get_nt_native_path ());
+ return create_reparse_point (sun, pc.get_nt_native_path ());
+}
+
+int
+fhandler_socket_unix::open_abstract_link (sun_name_t *sun,
+ PUNICODE_STRING pipe_name)
+{
+ WCHAR name[MAX_PATH];
+ OBJECT_ATTRIBUTES attr;
+ NTSTATUS status;
+ UNICODE_STRING uname;
+ HANDLE fh;
+
+ PWCHAR p = wcpcpy (name, L"af-unix-");
+ p = transform_chars_af_unix (p, sun->un.sun_path, sun->un_len);
+ *p = L'\0';
+ RtlInitUnicodeString (&uname, name);
+ InitializeObjectAttributes (&attr, &uname, OBJ_CASE_INSENSITIVE,
+ get_shared_parent_dir (), NULL);
+ status = NtOpenSymbolicLinkObject (&fh, SYMBOLIC_LINK_QUERY, &attr);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ if (pipe_name)
+ status = NtQuerySymbolicLinkObject (fh, pipe_name, NULL);
+ NtClose (fh);
+ if (pipe_name)
+ {
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ /* Enforce NUL-terminated pipe name. */
+ pipe_name->Buffer[pipe_name->Length / sizeof (WCHAR)] = L'\0';
+ }
+ return 0;
+}
+
+int
+fhandler_socket_unix::open_reparse_point (sun_name_t *sun,
+ PUNICODE_STRING pipe_name)
+{
+ NTSTATUS status;
+ HANDLE fh;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ PREPARSE_GUID_DATA_BUFFER rp;
+ tmp_pathbuf tp;
+
+ path_conv pc (sun->un.sun_path, PC_SYM_FOLLOW);
+ if (pc.error)
+ {
+ set_errno (pc.error);
+ return -1;
+ }
+ if (!pc.exists ())
+ {
+ set_errno (ENOENT);
+ return -1;
+ }
+ pc.get_object_attr (attr, sec_none_nih);
+ do
+ {
+ status = NtOpenFile (&fh, FILE_GENERIC_READ, &attr, &io,
+ FILE_SHARE_VALID_FLAGS,
+ FILE_SYNCHRONOUS_IO_NONALERT
+ | FILE_NON_DIRECTORY_FILE
+ | FILE_OPEN_FOR_BACKUP_INTENT
+ | FILE_OPEN_REPARSE_POINT);
+ if (status == STATUS_SHARING_VIOLATION)
+ {
+ /* While we hope that the sharing violation is only temporary, we
+ also could easily get stuck here, waiting for a file in use by
+ some greedy Win32 application. Therefore we should never wait
+ endlessly without checking for signals and thread cancel event. */
+ pthread_testcancel ();
+ if (cygwait (NULL, cw_nowait, cw_sig_eintr) == WAIT_SIGNALED
+ && !_my_tls.call_signal_handler ())
+ {
+ set_errno (EINTR);
+ return -1;
+ }
+ yield ();
+ }
+ else if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ }
+ while (status == STATUS_SHARING_VIOLATION);
+ rp = (PREPARSE_GUID_DATA_BUFFER) tp.c_get ();
+ status = NtFsControlFile (fh, NULL, NULL, NULL, &io, FSCTL_GET_REPARSE_POINT,
+ NULL, 0, rp, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+ NtClose (fh);
+ if (rp->ReparseTag == IO_REPARSE_TAG_CYGUNIX
+ && memcmp (CYGWIN_SOCKET_GUID, &rp->ReparseGuid, sizeof (GUID)) == 0)
+ {
+ if (pipe_name)
+ {
+ rep_pipe_name_t *rep_pipe_name = (rep_pipe_name_t *)
+ rp->GenericReparseBuffer.DataBuffer;
+ pipe_name->Length = rep_pipe_name->Length;
+ /* pipe name in reparse point is NUL-terminated */
+ memcpy (pipe_name->Buffer, rep_pipe_name->PipeName,
+ rep_pipe_name->Length + sizeof (WCHAR));
+ }
+ return 0;
+ }
+ return -1;
+}
+
+int
+fhandler_socket_unix::open_file (sun_name_t *sun, int &type,
+ PUNICODE_STRING pipe_name)
+{
+ int ret = -1;
+
+ if (sun->un_len <= (socklen_t) sizeof (sa_family_t)
+ || (sun->un_len == 3 && sun->un.sun_path[0] == '\0'))
+ set_errno (EINVAL);
+ else if (sun->un.sun_path[0] == '\0')
+ ret = open_abstract_link (sun, pipe_name);
+ else
+ ret = open_reparse_point (sun, pipe_name);
+ if (!ret)
+ switch (pipe_name->Buffer[CYGWIN_PIPE_SOCKET_TYPE_POS])
+ {
+ case 'd':
+ type = SOCK_DGRAM;
+ break;
+ case 's':
+ type = SOCK_STREAM;
+ break;
+ default:
+ set_errno (EINVAL);
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+HANDLE
+fhandler_socket_unix::autobind (sun_name_t* sun)
+{
+ uint32_t id;
+ HANDLE fh;
+
+ do
+ {
+ /* Use only 5 hex digits (up to 2^20 sockets) for Linux compat */
+ set_unique_id ();
+ id = get_unique_id () & 0xfffff;
+ sun->un.sun_path[0] = '\0';
+ sun->un_len = sizeof (sa_family_t)
+ + 1 /* leading NUL */
+ + __small_sprintf (sun->un.sun_path + 1, "%5X", id);
+ }
+ while ((fh = create_abstract_link (sun, pc.get_nt_native_path ())) == NULL);
+ return fh;
+}
+
+wchar_t
+fhandler_socket_unix::get_type_char ()
+{
+ switch (get_socket_type ())
+ {
+ case SOCK_STREAM:
+ return 's';
+ case SOCK_DGRAM:
+ return 'd';
+ default:
+ return '?';
+ }
+}
+
+/* This also sets the pipe to message mode unconditionally. */
+void
+fhandler_socket_unix::set_pipe_non_blocking (bool nonblocking)
+{
+ if (get_handle ())
+ {
+ 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 (get_handle (), &io, &fpi, sizeof fpi,
+ FilePipeInformation);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtSetInformationFile(FilePipeInformation): %y", status);
+ }
+}
+
+/* Apart from being called from bind(), from_bind indicates that the caller
+ already locked state_lock, so send_sock_info doesn't lock, only unlocks
+ state_lock. */
+int
+fhandler_socket_unix::send_sock_info (bool from_bind)
+{
+ sun_name_t *sun;
+ size_t plen;
+ size_t clen = 0;
+ af_unix_pkt_hdr_t *packet;
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+
+ if (!from_bind)
+ {
+ state_lock ();
+ /* When called from connect, initialize credentials. accept4 already
+ did it (copied from listening socket). */
+ if (sock_cred ()->pid == 0)
+ set_cred ();
+ }
+ sun = sun_path ();
+ plen = sizeof *packet + sun->un_len;
+ /* When called from connect/accept4, send SCM_CREDENTIALS, too. */
+ if (!from_bind)
+ {
+ clen = CMSG_SPACE (sizeof (struct ucred));
+ plen += clen;
+ }
+ packet = (af_unix_pkt_hdr_t *) alloca (plen);
+ packet->init (true, _SHUT_NONE, sun->un_len, clen, 0);
+ if (sun)
+ memcpy (AF_UNIX_PKT_NAME (packet), &sun->un, sun->un_len);
+ if (!from_bind)
+ {
+ struct cmsghdr *cmsg = AF_UNIX_PKT_CMSG (packet);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_CREDENTIALS;
+ cmsg->cmsg_len = CMSG_LEN (sizeof (struct ucred));
+ memcpy (CMSG_DATA(cmsg), sock_cred (), sizeof (struct ucred));
+ }
+
+ state_unlock ();
+
+ /* The theory: Fire and forget. */
+ io_lock ();
+ set_pipe_non_blocking (true);
+ status = NtWriteFile (get_handle (), NULL, NULL, NULL, &io, packet,
+ packet->pckt_len, NULL, NULL);
+ set_pipe_non_blocking (is_nonblocking ());
+ io_unlock ();
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("Couldn't send my name: NtWriteFile: %y", status);
+ return -1;
+ }
+ return 0;
+}
+
+/* Checks if the next packet in the pipe is an administrative packet.
+ If so, it reads it from the pipe, handles it. Returns an error code. */
+int
+fhandler_socket_unix::grab_admin_pkg ()
+{
+ HANDLE evt;
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ /* MAX_PATH is more than sufficient for admin packets. */
+ PFILE_PIPE_PEEK_BUFFER pbuf = (PFILE_PIPE_PEEK_BUFFER) alloca (MAX_PATH);
+ if (!(evt = create_event ()))
+ return 0;
+ io_lock ();
+ ULONG ret_len = peek_pipe (pbuf, MAX_PATH, evt);
+ if (pbuf->NumberOfMessages == 0 || ret_len < sizeof (af_unix_pkt_hdr_t))
+ {
+ io_unlock ();
+ NtClose (evt);
+ return 0;
+ }
+ af_unix_pkt_hdr_t *packet = (af_unix_pkt_hdr_t *) pbuf->Data;
+ if (!packet->admin_pkg)
+ io_unlock ();
+ else
+ {
+ packet = (af_unix_pkt_hdr_t *) pbuf;
+ status = NtReadFile (get_handle (), evt, NULL, NULL, &io, packet,
+ MAX_PATH, NULL, NULL);
+ if (status == STATUS_PENDING)
+ {
+ /* Very short-lived */
+ status = NtWaitForSingleObject (evt, FALSE, NULL);
+ if (NT_SUCCESS (status))
+ status = io.Status;
+ }
+ io_unlock ();
+ if (NT_SUCCESS (status))
+ {
+ state_lock ();
+ if (packet->shut_info)
+ {
+ /* Peer's shutdown sends the SHUT flags as used by the peer.
+ They have to be reversed for our side. */
+ int shut_info = saw_shutdown ();
+ if (packet->shut_info & _SHUT_RECV)
+ shut_info |= _SHUT_SEND;
+ if (packet->shut_info & _SHUT_SEND)
+ shut_info |= _SHUT_RECV;
+ saw_shutdown (shut_info);
+ /* FIXME: anything else here? */
+ }
+ if (packet->name_len > 0)
+ peer_sun_path (AF_UNIX_PKT_NAME (packet), packet->name_len);
+ if (packet->cmsg_len > 0)
+ {
+ struct cmsghdr *cmsg = (struct cmsghdr *)
+ alloca (packet->cmsg_len);
+ memcpy (cmsg, AF_UNIX_PKT_CMSG (packet), packet->cmsg_len);
+ if (cmsg->cmsg_level == SOL_SOCKET
+ && cmsg->cmsg_type == SCM_CREDENTIALS)
+ peer_cred ((struct ucred *) CMSG_DATA(cmsg));
+ }
+ state_unlock ();
+ }
+ }
+ NtClose (evt);
+ return 0;
+}
+
+/* Returns an error code. Locking is not required when called from accept4,
+ user space doesn't know about this socket yet. */
+int
+fhandler_socket_unix::recv_peer_info ()
+{
+ HANDLE evt;
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ af_unix_pkt_hdr_t *packet;
+ struct sockaddr_un *un;
+ ULONG len;
+ int ret = 0;
+
+ if (!(evt = create_event ()))
+ return ENOBUFS;
+ len = sizeof *packet + sizeof *un + CMSG_SPACE (sizeof (struct ucred));
+ packet = (af_unix_pkt_hdr_t *) alloca (len);
+ set_pipe_non_blocking (false);
+ status = NtReadFile (get_handle (), evt, NULL, NULL, &io, packet, len,
+ NULL, NULL);
+ if (status == STATUS_PENDING)
+ {
+ DWORD ret;
+ LARGE_INTEGER timeout;
+
+ timeout.QuadPart = AF_UNIX_CONNECT_TIMEOUT;
+ ret = cygwait (evt, &timeout, cw_sig_eintr);
+ switch (ret)
+ {
+ case WAIT_OBJECT_0:
+ status = io.Status;
+ break;
+ case WAIT_TIMEOUT:
+ ret = ECONNABORTED;
+ break;
+ case WAIT_SIGNALED:
+ ret = EINTR;
+ break;
+ default:
+ ret = EPROTO;
+ break;
+ }
+ }
+ set_pipe_non_blocking (is_nonblocking ());
+ NtClose (evt);
+ if (!NT_SUCCESS (status) && ret == 0)
+ ret = geterrno_from_nt_status (status);
+ if (ret == 0)
+ {
+ if (packet->name_len > 0)
+ peer_sun_path (AF_UNIX_PKT_NAME (packet), packet->name_len);
+ if (packet->cmsg_len > 0)
+ {
+ struct cmsghdr *cmsg = (struct cmsghdr *) alloca (packet->cmsg_len);
+ memcpy (cmsg, AF_UNIX_PKT_CMSG (packet), packet->cmsg_len);
+ if (cmsg->cmsg_level == SOL_SOCKET
+ && cmsg->cmsg_type == SCM_CREDENTIALS)
+ peer_cred ((struct ucred *) CMSG_DATA(cmsg));
+ }
+ }
+ return ret;
+}
+
+HANDLE
+fhandler_socket_unix::create_pipe (bool single_instance)
+{
+ NTSTATUS status;
+ HANDLE npfsh;
+ HANDLE ph;
+ ACCESS_MASK access;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ ULONG sharing;
+ ULONG nonblocking;
+ ULONG max_instances;
+ LARGE_INTEGER timeout;
+
+ status = npfs_handle (npfsh);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return NULL;
+ }
+ access = GENERIC_READ | FILE_READ_ATTRIBUTES
+ | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES
+ | SYNCHRONIZE;
+ sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ InitializeObjectAttributes (&attr, pc.get_nt_native_path (),
+ OBJ_INHERIT | OBJ_CASE_INSENSITIVE,
+ npfsh, NULL);
+ nonblocking = is_nonblocking () ? FILE_PIPE_COMPLETE_OPERATION
+ : FILE_PIPE_QUEUE_OPERATION;
+ max_instances = single_instance ? 1 : -1;
+ timeout.QuadPart = -500000;
+ status = NtCreateNamedPipeFile (&ph, access, &attr, &io, sharing,
+ FILE_CREATE, 0,
+ FILE_PIPE_MESSAGE_TYPE,
+ FILE_PIPE_MESSAGE_MODE,
+ nonblocking, max_instances,
+ rmem (), wmem (), &timeout);
+ if (!NT_SUCCESS (status))
+ __seterrno_from_nt_status (status);
+ return ph;
+}
+
+HANDLE
+fhandler_socket_unix::create_pipe_instance ()
+{
+ NTSTATUS status;
+ HANDLE npfsh;
+ HANDLE ph;
+ ACCESS_MASK access;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ ULONG sharing;
+ ULONG nonblocking;
+ ULONG max_instances;
+ LARGE_INTEGER timeout;
+
+ status = npfs_handle (npfsh);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return NULL;
+ }
+ access = GENERIC_READ | FILE_READ_ATTRIBUTES
+ | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES
+ | SYNCHRONIZE;
+ sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ /* NPFS doesn't understand reopening by handle, unfortunately. */
+ InitializeObjectAttributes (&attr, pc.get_nt_native_path (), OBJ_INHERIT,
+ npfsh, NULL);
+ nonblocking = is_nonblocking () ? FILE_PIPE_COMPLETE_OPERATION
+ : FILE_PIPE_QUEUE_OPERATION;
+ max_instances = (get_socket_type () == SOCK_DGRAM) ? 1 : -1;
+ timeout.QuadPart = -500000;
+ status = NtCreateNamedPipeFile (&ph, access, &attr, &io, sharing,
+ FILE_OPEN, 0,
+ FILE_PIPE_MESSAGE_TYPE,
+ FILE_PIPE_MESSAGE_MODE,
+ nonblocking, max_instances,
+ rmem (), wmem (), &timeout);
+ if (!NT_SUCCESS (status))
+ __seterrno_from_nt_status (status);
+ return ph;
+}
+
+NTSTATUS
+fhandler_socket_unix::open_pipe (PUNICODE_STRING pipe_name, bool xchg_sock_info)
+{
+ NTSTATUS status;
+ HANDLE npfsh;
+ ACCESS_MASK access;
+ OBJECT_ATTRIBUTES attr;
+ IO_STATUS_BLOCK io;
+ ULONG sharing;
+ HANDLE ph = NULL;
+
+ status = npfs_handle (npfsh);
+ if (!NT_SUCCESS (status))
+ return status;
+ access = GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE;
+ InitializeObjectAttributes (&attr, pipe_name, OBJ_INHERIT, npfsh, NULL);
+ sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ status = NtOpenFile (&ph, access, &attr, &io, sharing, 0);
+ if (NT_SUCCESS (status))
+ {
+ set_handle (ph);
+ if (xchg_sock_info)
+ {
+ /* FIXME: Should we check for errors? */
+ send_sock_info (false);
+ recv_peer_info ();
+ }
+ }
+ return status;
+}
+
+struct conn_wait_info_t
+{
+ fhandler_socket_unix *fh;
+ UNICODE_STRING pipe_name;
+ WCHAR pipe_name_buf[CYGWIN_PIPE_SOCKET_NAME_LEN + 1];
+};
+
+/* Just hop to the wait_pipe_thread method. */
+DWORD
+connect_wait_func (LPVOID param)
+{
+ conn_wait_info_t *wait_info = (conn_wait_info_t *) param;
+ return wait_info->fh->wait_pipe_thread (&wait_info->pipe_name);
+}
+
+/* Start a waiter thread to wait for a pipe instance to become available.
+ in blocking mode, wait for the thread to finish. In nonblocking mode
+ just return with errno set to EINPROGRESS. */
+int
+fhandler_socket_unix::wait_pipe (PUNICODE_STRING pipe_name)
+{
+ conn_wait_info_t *wait_info;
+ DWORD waitret, err;
+ int ret = -1;
+ HANDLE thr, evt;
+ PVOID param;
+
+ if (!(cwt_termination_evt = create_event ()))
+ return -1;
+ wait_info = (conn_wait_info_t *) cmalloc (HEAP_3_FHANDLER, sizeof *wait_info);
+ if (!wait_info)
+ return -1;
+ wait_info->fh = this;
+ RtlInitEmptyUnicodeString (&wait_info->pipe_name, wait_info->pipe_name_buf,
+ sizeof wait_info->pipe_name_buf);
+ RtlCopyUnicodeString (&wait_info->pipe_name, pipe_name);
+
+ cwt_param = (PVOID) wait_info;
+ connect_wait_thr = CreateThread (NULL, PREFERRED_IO_BLKSIZE,
+ connect_wait_func, cwt_param, 0, NULL);
+ if (!connect_wait_thr)
+ {
+ cfree (wait_info);
+ __seterrno ();
+ goto out;
+ }
+ if (is_nonblocking ())
+ {
+ set_errno (EINPROGRESS);
+ goto out;
+ }
+
+ waitret = cygwait (connect_wait_thr, cw_infinite, cw_cancel | cw_sig_eintr);
+ if (waitret == WAIT_OBJECT_0)
+ GetExitCodeThread (connect_wait_thr, &err);
+ else
+ {
+ SetEvent (cwt_termination_evt);
+ NtWaitForSingleObject (connect_wait_thr, FALSE, NULL);
+ GetExitCodeThread (connect_wait_thr, &err);
+ waitret = WAIT_SIGNALED;
+ }
+ thr = InterlockedExchangePointer (&connect_wait_thr, NULL);
+ if (thr)
+ NtClose (thr);
+ param = InterlockedExchangePointer (&cwt_param, NULL);
+ if (param)
+ cfree (param);
+ switch (waitret)
+ {
+ case WAIT_CANCELED:
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ case WAIT_SIGNALED:
+ set_errno (EINTR);
+ break;
+ default:
+ so_error (err);
+ if (err)
+ set_errno (err);
+ else
+ ret = 0;
+ break;
+ }
+out:
+ evt = InterlockedExchangePointer (&cwt_termination_evt, NULL);
+ if (evt)
+ NtClose (evt);
+ return ret;
+}
+
+int
+fhandler_socket_unix::connect_pipe (PUNICODE_STRING pipe_name)
+{
+ NTSTATUS status;
+
+ /* Try connecting first. If it doesn't work, wait for the pipe
+ to become available. */
+ status = open_pipe (pipe_name, get_socket_type () != SOCK_DGRAM);
+ if (STATUS_PIPE_NO_INSTANCE_AVAILABLE (status))
+ return wait_pipe (pipe_name);
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ so_error (get_errno ());
+ return -1;
+ }
+ so_error (0);
+ return 0;
+}
+
+int
+fhandler_socket_unix::listen_pipe ()
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ HANDLE evt = NULL;
+ DWORD waitret = WAIT_OBJECT_0;
+ int ret = -1;
+
+ io.Status = STATUS_PENDING;
+ if (!is_nonblocking () && !(evt = create_event ()))
+ return -1;
+ status = NtFsControlFile (get_handle (), evt, NULL, NULL, &io,
+ FSCTL_PIPE_LISTEN, NULL, 0, NULL, 0);
+ if (status == STATUS_PENDING)
+ {
+ waitret = cygwait (evt ?: get_handle (), cw_infinite,
+ cw_cancel | cw_sig_eintr);
+ if (waitret == WAIT_OBJECT_0)
+ status = io.Status;
+ }
+ if (evt)
+ NtClose (evt);
+ if (waitret == WAIT_CANCELED)
+ pthread::static_cancel_self ();
+ else if (waitret == WAIT_SIGNALED)
+ set_errno (EINTR);
+ else if (status == STATUS_PIPE_LISTENING)
+ set_errno (EAGAIN);
+ else if (status == STATUS_SUCCESS || status == STATUS_PIPE_CONNECTED)
+ ret = 0;
+ else
+ __seterrno_from_nt_status (status);
+ return ret;
+}
+
+ULONG
+fhandler_socket_unix::peek_pipe (PFILE_PIPE_PEEK_BUFFER pbuf, ULONG psize,
+ HANDLE evt)
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+
+ status = NtFsControlFile (get_handle (), evt, NULL, NULL, &io,
+ FSCTL_PIPE_PEEK, NULL, 0, pbuf, psize);
+ if (status == STATUS_PENDING)
+ {
+ /* Very short-lived */
+ status = NtWaitForSingleObject (evt ?: get_handle (), FALSE, NULL);
+ if (NT_SUCCESS (status))
+ status = io.Status;
+ }
+ return NT_SUCCESS (status) ? (io.Information
+ - offsetof (FILE_PIPE_PEEK_BUFFER, Data))
+ : 0;
+}
+
+int
+fhandler_socket_unix::disconnect_pipe (HANDLE ph)
+{
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+
+ status = NtFsControlFile (ph, NULL, NULL, NULL, &io, FSCTL_PIPE_DISCONNECT,
+ NULL, 0, NULL, 0);
+ /* Short-lived. Don't use cygwait. We don't want to be interrupted. */
+ if (status == STATUS_PENDING
+ && NtWaitForSingleObject (ph, FALSE, NULL) == WAIT_OBJECT_0)
+ status = io.Status;
+ if (!NT_SUCCESS (status))
+ {
+ __seterrno_from_nt_status (status);
+ return -1;
+ }
+ return 0;
+}
+
+void
+fhandler_socket_unix::init_cred ()
+{
+ struct ucred *scred = shmem->sock_cred ();
+ struct ucred *pcred = shmem->peer_cred ();
+ scred->pid = pcred->pid = (pid_t) 0;
+ scred->uid = pcred->uid = (uid_t) -1;
+ scred->gid = pcred->gid = (gid_t) -1;
+}
+
+void
+fhandler_socket_unix::set_cred ()
+{
+ struct ucred *scred = shmem->sock_cred ();
+ scred->pid = myself->pid;
+ scred->uid = myself->uid;
+ scred->gid = myself->gid;
+}
+
+void
+fhandler_socket_unix::fixup_helper ()
+{
+ if (shmem_handle)
+ reopen_shmem ();
+ connect_wait_thr = NULL;
+ cwt_termination_evt = NULL;
+ cwt_param = NULL;
+}
+
+/* ========================== public methods ========================= */
+
+void
+fhandler_socket_unix::fixup_after_fork (HANDLE parent)
+{
+ fhandler_socket::fixup_after_fork (parent);
+ if (backing_file_handle && backing_file_handle != INVALID_HANDLE_VALUE)
+ fork_fixup (parent, backing_file_handle, "backing_file_handle");
+ if (shmem_handle)
+ fork_fixup (parent, shmem_handle, "shmem_handle");
+ fixup_helper ();
+}
+
+void
+fhandler_socket_unix::fixup_after_exec ()
+{
+ if (!close_on_exec ())
+ fixup_helper ();
+}
+
+void
+fhandler_socket_unix::set_close_on_exec (bool val)
+{
+ fhandler_base::set_close_on_exec (val);
+ if (backing_file_handle && backing_file_handle != INVALID_HANDLE_VALUE)
+ set_no_inheritance (backing_file_handle, val);
+ if (shmem_handle)
+ set_no_inheritance (shmem_handle, val);
+}
+
+fhandler_socket_unix::fhandler_socket_unix ()
+{
+}
+
+fhandler_socket_unix::~fhandler_socket_unix ()
+{
+}
+
+int
+fhandler_socket_unix::dup (fhandler_base *child, int flags)
+{
+ if (get_flags () & O_PATH)
+ /* We're viewing the socket as a disk file, but fhandler_base::dup
+ suffices here. */
+ return fhandler_base::dup (child, flags);
+
+ if (fhandler_socket::dup (child, flags))
+ {
+ __seterrno ();
+ return -1;
+ }
+ fhandler_socket_unix *fhs = (fhandler_socket_unix *) child;
+ if (backing_file_handle && backing_file_handle != INVALID_HANDLE_VALUE
+ && !DuplicateHandle (GetCurrentProcess (), backing_file_handle,
+ GetCurrentProcess (), &fhs->backing_file_handle,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ __seterrno ();
+ fhs->close ();
+ return -1;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), shmem_handle,
+ GetCurrentProcess (), &fhs->shmem_handle,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ __seterrno ();
+ fhs->close ();
+ return -1;
+ }
+ if (fhs->reopen_shmem () < 0)
+ {
+ __seterrno ();
+ fhs->close ();
+ return -1;
+ }
+ fhs->sun_path (sun_path ());
+ fhs->peer_sun_path (peer_sun_path ());
+ fhs->connect_wait_thr = NULL;
+ fhs->cwt_termination_evt = NULL;
+ fhs->cwt_param = NULL;
+ return 0;
+}
+
+/* Waiter thread method. Here we wait for a pipe instance to become
+ available and connect to it, if so. This function is running
+ asynchronously if called on a non-blocking pipe. The important
+ things to do:
+
+ - Set the peer pipe handle if successful
+ - Send own sun_path to peer if successful
+ - Set connect_state
+ - Set so_error for later call to select
+*/
+DWORD
+fhandler_socket_unix::wait_pipe_thread (PUNICODE_STRING pipe_name)
+{
+ HANDLE npfsh;
+ HANDLE evt;
+ LONG error = 0;
+ NTSTATUS status;
+ IO_STATUS_BLOCK io;
+ ULONG pwbuf_size;
+ PFILE_PIPE_WAIT_FOR_BUFFER pwbuf;
+ LONGLONG stamp;
+
+ status = npfs_handle (npfsh);
+ if (!NT_SUCCESS (status))
+ {
+ error = geterrno_from_nt_status (status);
+ goto out;
+ }
+ if (!(evt = create_event ()))
+ goto out;
+ pwbuf_size = offsetof (FILE_PIPE_WAIT_FOR_BUFFER, Name) + pipe_name->Length;
+ pwbuf = (PFILE_PIPE_WAIT_FOR_BUFFER) alloca (pwbuf_size);
+ pwbuf->Timeout.QuadPart = AF_UNIX_CONNECT_TIMEOUT;
+ pwbuf->NameLength = pipe_name->Length;
+ pwbuf->TimeoutSpecified = TRUE;
+ memcpy (pwbuf->Name, pipe_name->Buffer, pipe_name->Length);
+ stamp = get_clock (CLOCK_MONOTONIC)->n100secs ();
+ do
+ {
+ status = NtFsControlFile (npfsh, evt, NULL, NULL, &io, FSCTL_PIPE_WAIT,
+ pwbuf, pwbuf_size, NULL, 0);
+ if (status == STATUS_PENDING)
+ {
+ HANDLE w[2] = { evt, cwt_termination_evt };
+ switch (WaitForMultipleObjects (2, w, FALSE, INFINITE))
+ {
+ case WAIT_OBJECT_0:
+ status = io.Status;
+ break;
+ case WAIT_OBJECT_0 + 1:
+ default:
+ status = STATUS_THREAD_IS_TERMINATING;
+ break;
+ }
+ }
+ switch (status)
+ {
+ case STATUS_SUCCESS:
+ {
+ status = open_pipe (pipe_name, get_socket_type () != SOCK_DGRAM);
+ if (STATUS_PIPE_NO_INSTANCE_AVAILABLE (status))
+ {
+ /* Another concurrent connect grabbed the pipe instance
+ under our nose. Fix the timeout value and go waiting
+ again, unless the timeout has passed. */
+ pwbuf->Timeout.QuadPart -=
+ stamp - get_clock (CLOCK_MONOTONIC)->n100secs ();
+ if (pwbuf->Timeout.QuadPart >= 0)
+ {
+ status = STATUS_IO_TIMEOUT;
+ error = ETIMEDOUT;
+ }
+ }
+ else if (!NT_SUCCESS (status))
+ error = geterrno_from_nt_status (status);
+ }
+ break;
+ case STATUS_OBJECT_NAME_NOT_FOUND:
+ error = EADDRNOTAVAIL;
+ break;
+ case STATUS_IO_TIMEOUT:
+ error = ETIMEDOUT;
+ break;
+ case STATUS_INSUFFICIENT_RESOURCES:
+ error = ENOBUFS;
+ break;
+ case STATUS_THREAD_IS_TERMINATING:
+ error = EINTR;
+ break;
+ case STATUS_INVALID_DEVICE_REQUEST:
+ default:
+ error = EIO;
+ break;
+ }
+ }
+ while (STATUS_PIPE_NO_INSTANCE_AVAILABLE (status));
+out:
+ PVOID param = InterlockedExchangePointer (&cwt_param, NULL);
+ if (param)
+ cfree (param);
+ conn_lock ();
+ state_lock ();
+ so_error (error);
+ connect_state (error ? connect_failed : connected);
+ state_unlock ();
+ conn_unlock ();
+ return error;
+}
+
+int
+fhandler_socket_unix::socket (int af, int type, int protocol, int flags)
+{
+ if (type != SOCK_STREAM && type != SOCK_DGRAM)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (protocol != 0)
+ {
+ set_errno (EPROTONOSUPPORT);
+ return -1;
+ }
+ if (create_shmem () < 0)
+ return -1;
+ rmem (262144);
+ wmem (262144);
+ set_addr_family (AF_UNIX);
+ set_socket_type (type);
+ set_flags (O_RDWR | O_BINARY);
+ if (flags & SOCK_NONBLOCK)
+ set_nonblocking (true);
+ if (flags & SOCK_CLOEXEC)
+ set_close_on_exec (true);
+ init_cred ();
+ set_handle (NULL);
+ set_unique_id ();
+ set_ino (get_unique_id ());
+ return 0;
+}
+
+int
+fhandler_socket_unix::socketpair (int af, int type, int protocol, int flags,
+ fhandler_socket *fh_out)
+{
+ HANDLE pipe;
+ sun_name_t sun;
+ fhandler_socket_unix *fh = (fhandler_socket_unix *) fh_out;
+
+ if (type != SOCK_STREAM && type != SOCK_DGRAM)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (protocol != 0)
+ {
+ set_errno (EPROTONOSUPPORT);
+ return -1;
+ }
+
+ if (create_shmem () < 0)
+ return -1;
+ if (fh->create_shmem () < 0)
+ goto fh_shmem_failed;
+ /* socket() on both sockets */
+ rmem (262144);
+ fh->rmem (262144);
+ wmem (262144);
+ fh->wmem (262144);
+ set_addr_family (AF_UNIX);
+ fh->set_addr_family (AF_UNIX);
+ set_socket_type (type);
+ fh->set_socket_type (type);
+ set_cred ();
+ fh->set_cred ();
+ set_unique_id ();
+ set_ino (get_unique_id ());
+ /* bind/listen 1st socket */
+ gen_pipe_name ();
+ pipe = create_pipe (true);
+ if (!pipe)
+ goto create_pipe_failed;
+ set_handle (pipe);
+ sun_path (&sun);
+ fh->peer_sun_path (&sun);
+ connect_state (listener);
+ /* connect 2nd socket, even for DGRAM. There's no difference as far
+ as socketpairs are concerned. */
+ if (fh->open_pipe (pc.get_nt_native_path (), false) < 0)
+ goto fh_open_pipe_failed;
+ fh->connect_state (connected);
+ if (flags & SOCK_NONBLOCK)
+ {
+ set_nonblocking (true);
+ fh->set_nonblocking (true);
+ }
+ if (flags & SOCK_CLOEXEC)
+ {
+ set_close_on_exec (true);
+ fh->set_close_on_exec (true);
+ }
+ return 0;
+
+fh_open_pipe_failed:
+ NtClose (pipe);
+create_pipe_failed:
+ NtUnmapViewOfSection (NtCurrentProcess (), fh->shmem);
+ NtClose (fh->shmem_handle);
+fh_shmem_failed:
+ NtUnmapViewOfSection (NtCurrentProcess (), shmem);
+ NtClose (shmem_handle);
+ return -1;
+}
+
+/* Bind creates the backing file, generates the pipe name and sets
+ bind_state. On DGRAM sockets it also creates the pipe. On STREAM
+ sockets either listen or connect will do that. */
+int
+fhandler_socket_unix::bind (const struct sockaddr *name, int namelen)
+{
+ sun_name_t sun (name, namelen);
+ bool unnamed = (sun.un_len == sizeof sun.un.sun_family);
+ HANDLE pipe = NULL;
+
+ if (sun.un.sun_family != AF_UNIX)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ bind_lock ();
+ if (binding_state () == bind_pending)
+ {
+ set_errno (EALREADY);
+ bind_unlock ();
+ return -1;
+ }
+ if (binding_state () == bound)
+ {
+ set_errno (EINVAL);
+ bind_unlock ();
+ return -1;
+ }
+ binding_state (bind_pending);
+ bind_unlock ();
+ gen_pipe_name ();
+ if (get_socket_type () == SOCK_DGRAM)
+ {
+ pipe = create_pipe (true);
+ if (!pipe)
+ {
+ binding_state (unbound);
+ return -1;
+ }
+ set_handle (pipe);
+ }
+ backing_file_handle = unnamed ? autobind (&sun) : create_file (&sun);
+ if (!backing_file_handle)
+ {
+ set_handle (NULL);
+ if (pipe)
+ NtClose (pipe);
+ binding_state (unbound);
+ return -1;
+ }
+ state_lock ();
+ sun_path (&sun);
+ /* If we're already connected, send socket info to peer. In this case
+ send_sock_info calls state_unlock */
+ if (connect_state () == connected)
+ send_sock_info (true);
+ else
+ state_unlock ();
+ binding_state (bound);
+ return 0;
+}
+
+/* Create pipe on non-DGRAM sockets and set conn_state to listener. */
+int
+fhandler_socket_unix::listen (int backlog)
+{
+ if (get_socket_type () == SOCK_DGRAM)
+ {
+ set_errno (EOPNOTSUPP);
+ return -1;
+ }
+ bind_lock ();
+ while (binding_state () == bind_pending)
+ yield ();
+ if (binding_state () == unbound)
+ {
+ set_errno (EDESTADDRREQ);
+ bind_unlock ();
+ return -1;
+ }
+ bind_unlock ();
+ conn_lock ();
+ if (connect_state () != unconnected && connect_state () != connect_failed)
+ {
+ set_errno (connect_state () == listener ? EADDRINUSE : EINVAL);
+ conn_unlock ();
+ return -1;
+ }
+ HANDLE pipe = create_pipe (false);
+ if (!pipe)
+ {
+ connect_state (unconnected);
+ return -1;
+ }
+ set_handle (pipe);
+ state_lock ();
+ set_cred ();
+ state_unlock ();
+ connect_state (listener);
+ conn_unlock ();
+ return 0;
+}
+
+int
+fhandler_socket_unix::accept4 (struct sockaddr *peer, int *len, int flags)
+{
+ if (get_socket_type () != SOCK_STREAM)
+ {
+ set_errno (EOPNOTSUPP);
+ return -1;
+ }
+ if (connect_state () != listener
+ || (peer && (!len || *len < (int) sizeof (sa_family_t))))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (listen_pipe () == 0)
+ {
+ /* Our handle is now connected with a client. This handle is used
+ for the accepted socket. Our handle has to be replaced with a
+ new instance handle for the next accept. */
+ io_lock ();
+ HANDLE accepted = get_handle ();
+ HANDLE new_inst = create_pipe_instance ();
+ int error = ENOBUFS;
+ if (!new_inst)
+ io_unlock ();
+ else
+ {
+ /* Set new io handle. */
+ set_handle (new_inst);
+ io_unlock ();
+ /* Prepare new file descriptor. */
+ cygheap_fdnew fd;
+
+ if (fd >= 0)
+ {
+ fhandler_socket_unix *sock = (fhandler_socket_unix *)
+ build_fh_dev (dev ());
+ if (sock)
+ {
+ if (sock->create_shmem () < 0)
+ goto create_shmem_failed;
+
+ sock->set_addr_family (AF_UNIX);
+ sock->set_socket_type (get_socket_type ());
+ if (flags & SOCK_NONBLOCK)
+ sock->set_nonblocking (true);
+ if (flags & SOCK_CLOEXEC)
+ sock->set_close_on_exec (true);
+ sock->set_unique_id ();
+ sock->set_ino (sock->get_unique_id ());
+ sock->pc.set_nt_native_path (pc.get_nt_native_path ());
+ sock->connect_state (connected);
+ sock->binding_state (binding_state ());
+ sock->set_handle (accepted);
+
+ sock->sun_path (sun_path ());
+ sock->sock_cred (sock_cred ());
+ /* Send this socket info to connecting socket. */
+ sock->send_sock_info (false);
+ /* Fetch the packet sent by send_sock_info called by
+ connecting peer. */
+ error = sock->recv_peer_info ();
+ if (error == 0)
+ {
+ __try
+ {
+ if (peer)
+ {
+ sun_name_t *sun = sock->peer_sun_path ();
+ if (sun)
+ {
+ memcpy (peer, &sun->un,
+ MIN (*len, sun->un_len));
+ *len = sun->un_len;
+ }
+ else if (len)
+ *len = 0;
+ }
+ fd = sock;
+ if (fd <= 2)
+ set_std_handle (fd);
+ return fd;
+ }
+ __except (NO_ERROR)
+ {
+ error = EFAULT;
+ }
+ __endtry
+ }
+create_shmem_failed:
+ delete sock;
+ }
+ }
+ }
+ /* Ouch! We can't handle the client if we couldn't
+ create a new instance to accept more connections.*/
+ disconnect_pipe (accepted);
+ set_errno (error);
+ }
+ return -1;
+}
+
+int
+fhandler_socket_unix::connect (const struct sockaddr *name, int namelen)
+{
+ sun_name_t sun (name, namelen);
+ int peer_type;
+ WCHAR pipe_name_buf[CYGWIN_PIPE_SOCKET_NAME_LEN + 1];
+ UNICODE_STRING pipe_name;
+
+ /* Test and set connection state. */
+ conn_lock ();
+ if (connect_state () == connect_pending)
+ {
+ set_errno (EALREADY);
+ conn_unlock ();
+ return -1;
+ }
+ if (connect_state () == listener)
+ {
+ set_errno (EADDRINUSE);
+ conn_unlock ();
+ return -1;
+ }
+ if (connect_state () == connected && get_socket_type () != SOCK_DGRAM)
+ {
+ set_errno (EISCONN);
+ conn_unlock ();
+ return -1;
+ }
+ if (name->sa_family == AF_UNSPEC && get_socket_type () == SOCK_DGRAM)
+ {
+ connect_state (unconnected);
+ peer_sun_path (NULL);
+ conn_unlock ();
+ return 0;
+ }
+ connect_state (connect_pending);
+ conn_unlock ();
+ /* Check validity of name */
+ if (sun.un_len <= (int) sizeof (sa_family_t))
+ {
+ set_errno (EINVAL);
+ connect_state (unconnected);
+ return -1;
+ }
+ if (sun.un.sun_family != AF_UNIX)
+ {
+ set_errno (EAFNOSUPPORT);
+ connect_state (unconnected);
+ return -1;
+ }
+ if (sun.un_len == 3 && sun.un.sun_path[0] == '\0')
+ {
+ set_errno (EINVAL);
+ connect_state (unconnected);
+ return -1;
+ }
+ /* Check if peer address exists. */
+ RtlInitEmptyUnicodeString (&pipe_name, pipe_name_buf, sizeof pipe_name_buf);
+ if (open_file (&sun, peer_type, &pipe_name) < 0)
+ {
+ connect_state (unconnected);
+ return -1;
+ }
+ if (peer_type != get_socket_type ())
+ {
+ set_errno (EINVAL);
+ connect_state (unconnected);
+ return -1;
+ }
+ peer_sun_path (&sun);
+ if (get_socket_type () != SOCK_DGRAM)
+ {
+ if (connect_pipe (&pipe_name) < 0)
+ {
+ if (get_errno () != EINPROGRESS)
+ {
+ peer_sun_path (NULL);
+ connect_state (connect_failed);
+ }
+ return -1;
+ }
+ }
+ connect_state (connected);
+ return 0;
+}
+
+int
+fhandler_socket_unix::getsockname (struct sockaddr *name, int *namelen)
+{
+ sun_name_t *sun = sun_path ();
+
+ memcpy (name, sun, MIN (*namelen, sun->un_len));
+ *namelen = sun->un_len;
+ return 0;
+}
+
+int
+fhandler_socket_unix::getpeername (struct sockaddr *name, int *namelen)
+{
+ sun_name_t *sun = peer_sun_path ();
+ memcpy (name, sun, MIN (*namelen, sun->un_len));
+ *namelen = sun->un_len;
+ return 0;
+}
+
+int
+fhandler_socket_unix::shutdown (int how)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ IO_STATUS_BLOCK io;
+
+ if (how < SHUT_RD || how > SHUT_RDWR)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ /* Convert SHUT_RD/SHUT_WR/SHUT_RDWR to _SHUT_RECV/_SHUT_SEND bits. */
+ ++how;
+ state_lock ();
+ int old_shutdown_mask = saw_shutdown ();
+ int new_shutdown_mask = old_shutdown_mask | how;
+ if (new_shutdown_mask != old_shutdown_mask)
+ saw_shutdown (new_shutdown_mask);
+ state_unlock ();
+ if (new_shutdown_mask != old_shutdown_mask)
+ {
+ /* Send shutdown info to peer. Note that it's not necessarily fatal
+ if the info isn't sent here. The info will be reproduced by any
+ followup package sent to the peer. */
+ af_unix_pkt_hdr_t packet (true, (shut_state) new_shutdown_mask, 0, 0, 0);
+ io_lock ();
+ set_pipe_non_blocking (true);
+ status = NtWriteFile (get_handle (), NULL, NULL, NULL, &io, &packet,
+ packet.pckt_len, NULL, NULL);
+ set_pipe_non_blocking (is_nonblocking ());
+ io_unlock ();
+ }
+ if (!NT_SUCCESS (status))
+ {
+ debug_printf ("Couldn't send shutdown info: NtWriteFile: %y", status);
+ return -1;
+ }
+ return 0;
+}
+
+int
+fhandler_socket_unix::open (int flags, mode_t mode)
+{
+ /* We don't support opening sockets unless O_PATH is specified. */
+ if (flags & O_PATH)
+ return open_fs (flags, mode);
+
+ set_errno (EOPNOTSUPP);
+ return 0;
+}
+
+int
+fhandler_socket_unix::close ()
+{
+ if (get_flags () & O_PATH)
+ return fhandler_base::close ();
+
+ HANDLE evt = InterlockedExchangePointer (&cwt_termination_evt, NULL);
+ HANDLE thr = InterlockedExchangePointer (&connect_wait_thr, NULL);
+ if (thr)
+ {
+ if (evt)
+ SetEvent (evt);
+ NtWaitForSingleObject (thr, FALSE, NULL);
+ NtClose (thr);
+ }
+ if (evt)
+ NtClose (evt);
+ PVOID param = InterlockedExchangePointer (&cwt_param, NULL);
+ if (param)
+ cfree (param);
+ HANDLE hdl = InterlockedExchangePointer (&get_handle (), NULL);
+ if (hdl)
+ NtClose (hdl);
+ if (backing_file_handle && backing_file_handle != INVALID_HANDLE_VALUE)
+ NtClose (backing_file_handle);
+ HANDLE shm = InterlockedExchangePointer (&shmem_handle, NULL);
+ if (shm)
+ NtClose (shm);
+ param = InterlockedExchangePointer ((PVOID *) &shmem, NULL);
+ if (param)
+ NtUnmapViewOfSection (NtCurrentProcess (), param);
+ return 0;
+}
+
+int
+fhandler_socket_unix::getpeereid (pid_t *pid, uid_t *euid, gid_t *egid)
+{
+ int ret = -1;
+
+ if (get_socket_type () != SOCK_STREAM)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (connect_state () != connected)
+ set_errno (ENOTCONN);
+ else
+ {
+ __try
+ {
+ state_lock ();
+ struct ucred *pcred = peer_cred ();
+ if (pid)
+ *pid = pcred->pid;
+ if (euid)
+ *euid = pcred->uid;
+ if (egid)
+ *egid = pcred->gid;
+ state_unlock ();
+ ret = 0;
+ }
+ __except (EFAULT) {}
+ __endtry
+ }
+ return ret;
+}
+
+ssize_t
+fhandler_socket_unix::recvmsg (struct msghdr *msg, int flags)
+{
+ set_errno (EAFNOSUPPORT);
+ return -1;
+}
+
+ssize_t
+fhandler_socket_unix::recvfrom (void *ptr, size_t len, int flags,
+ struct sockaddr *from, int *fromlen)
+{
+ struct iovec iov;
+ struct msghdr msg;
+ ssize_t ret;
+
+ iov.iov_base = ptr;
+ iov.iov_len = len;
+ msg.msg_name = from;
+ msg.msg_namelen = from && fromlen ? *fromlen : 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ ret = recvmsg (&msg, flags);
+ if (ret >= 0 && from && fromlen)
+ *fromlen = msg.msg_namelen;
+ return ret;
+}
+
+void
+fhandler_socket_unix::read (void *ptr, size_t& len)
+{
+ set_errno (EAFNOSUPPORT);
+ len = 0;
+ struct iovec iov;
+ struct msghdr msg;
+
+ iov.iov_base = ptr;
+ iov.iov_len = len;
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ len = recvmsg (&msg, 0);
+}
+
+ssize_t
+fhandler_socket_unix::readv (const struct iovec *const iov, int iovcnt,
+ ssize_t tot)
+{
+ struct msghdr msg;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = (struct iovec *) iov;
+ msg.msg_iovlen = iovcnt;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ return recvmsg (&msg, 0);
+}
+
+ssize_t
+fhandler_socket_unix::sendmsg (const struct msghdr *msg, int flags)
+{
+ set_errno (EAFNOSUPPORT);
+ return -1;
+}
+
+ssize_t
+fhandler_socket_unix::sendto (const void *in_ptr, size_t len, int flags,
+ const struct sockaddr *to, int tolen)
+{
+ struct iovec iov;
+ struct msghdr msg;
+
+ iov.iov_base = (void *) in_ptr;
+ iov.iov_len = len;
+ msg.msg_name = (void *) to;
+ msg.msg_namelen = to ? tolen : 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ return sendmsg (&msg, flags);
+}
+
+ssize_t
+fhandler_socket_unix::write (const void *ptr, size_t len)
+{
+ struct iovec iov;
+ struct msghdr msg;
+
+ iov.iov_base = (void *) ptr;
+ iov.iov_len = len;
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ return sendmsg (&msg, 0);
+}
+
+ssize_t
+fhandler_socket_unix::writev (const struct iovec *const iov, int iovcnt,
+ ssize_t tot)
+{
+ struct msghdr msg;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = (struct iovec *) iov;
+ msg.msg_iovlen = iovcnt;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ return sendmsg (&msg, 0);
+}
+
+int
+fhandler_socket_unix::setsockopt (int level, int optname, const void *optval,
+ socklen_t optlen)
+{
+ /* Preprocessing setsockopt. */
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_PASSCRED:
+ if (optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ bool val;
+ val = !!*(int *) optval;
+ /* Using bind_lock here to make sure the autobind below is
+ covered. This is the only place to set so_passcred anyway. */
+ bind_lock ();
+ if (val && binding_state () == unbound)
+ {
+ sun_name_t sun;
+
+ binding_state (bind_pending);
+ backing_file_handle = autobind (&sun);
+ if (!backing_file_handle)
+ {
+ binding_state (unbound);
+ bind_unlock ();
+ return -1;
+ }
+ sun_path (&sun);
+ binding_state (bound);
+ }
+ so_passcred (val);
+ bind_unlock ();
+ break;
+
+ case SO_REUSEADDR:
+ if (optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ reuseaddr (!!*(int *) optval);
+ break;
+
+ case SO_RCVBUF:
+ if (optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ rmem (*(int *) optval);
+ break;
+
+ case SO_SNDBUF:
+ if (optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ wmem (*(int *) optval);
+ break;
+
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ if (optlen < (socklen_t) sizeof (struct timeval))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ if (!timeval_to_ms ((struct timeval *) optval,
+ (optname == SO_RCVTIMEO) ? rcvtimeo ()
+ : sndtimeo ()))
+ {
+ set_errno (EDOM);
+ return -1;
+ }
+ break;
+
+ default:
+ /* AF_UNIX sockets simply ignore all other SOL_SOCKET options. */
+ break;
+ }
+ break;
+
+ default:
+ set_errno (ENOPROTOOPT);
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+fhandler_socket_unix::getsockopt (int level, int optname, const void *optval,
+ socklen_t *optlen)
+{
+ /* Preprocessing getsockopt.*/
+ switch (level)
+ {
+ case SOL_SOCKET:
+ switch (optname)
+ {
+ case SO_ERROR:
+ {
+ if (*optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ int *e = (int *) optval;
+ LONG err;
+
+ err = so_error (0);
+ *e = err;
+ break;
+ }
+
+ case SO_PASSCRED:
+ {
+ if (*optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ int *e = (int *) optval;
+ *e = so_passcred ();
+ break;
+ }
+
+ case SO_PEERCRED:
+ {
+ struct ucred *cred = (struct ucred *) optval;
+
+ if (*optlen < (socklen_t) sizeof *cred)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ int ret = getpeereid (&cred->pid, &cred->uid, &cred->gid);
+ if (!ret)
+ *optlen = (socklen_t) sizeof *cred;
+ return ret;
+ }
+
+ case SO_REUSEADDR:
+ {
+ unsigned int *reuse = (unsigned int *) optval;
+
+ if (*optlen < (socklen_t) sizeof *reuse)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ *reuse = reuseaddr ();
+ *optlen = (socklen_t) sizeof *reuse;
+ break;
+ }
+
+ case SO_RCVBUF:
+ case SO_SNDBUF:
+ if (*optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ *(int *) optval = (optname == SO_RCVBUF) ? rmem () : wmem ();
+ break;
+
+ case SO_RCVTIMEO:
+ case SO_SNDTIMEO:
+ {
+ struct timeval *time_out = (struct timeval *) optval;
+
+ if (*optlen < (socklen_t) sizeof *time_out)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ DWORD ms = (optname == SO_RCVTIMEO) ? rcvtimeo () : sndtimeo ();
+ if (ms == 0 || ms == INFINITE)
+ {
+ time_out->tv_sec = 0;
+ time_out->tv_usec = 0;
+ }
+ else
+ {
+ time_out->tv_sec = ms / MSPERSEC;
+ time_out->tv_usec = ((ms % MSPERSEC) * USPERSEC) / MSPERSEC;
+ }
+ *optlen = (socklen_t) sizeof *time_out;
+ break;
+ }
+
+ case SO_TYPE:
+ {
+ if (*optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ unsigned int *type = (unsigned int *) optval;
+ *type = get_socket_type ();
+ *optlen = (socklen_t) sizeof *type;
+ break;
+ }
+
+ /* AF_UNIX sockets simply ignore all other SOL_SOCKET options. */
+
+ case SO_LINGER:
+ {
+ if (*optlen < (socklen_t) sizeof (struct linger))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ struct linger *linger = (struct linger *) optval;
+ memset (linger, 0, sizeof *linger);
+ *optlen = (socklen_t) sizeof *linger;
+ break;
+ }
+
+ default:
+ {
+ if (*optlen < (socklen_t) sizeof (int))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ unsigned int *val = (unsigned int *) optval;
+ *val = 0;
+ *optlen = (socklen_t) sizeof *val;
+ break;
+ }
+ }
+ break;
+
+ default:
+ set_errno (ENOPROTOOPT);
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+fhandler_socket_unix::ioctl (unsigned int cmd, void *p)
+{
+ int ret = -1;
+
+ switch (cmd)
+ {
+ case FIOASYNC:
+ case _IOW('f', 125, int):
+ break;
+ case FIONREAD:
+ case _IOR('f', 127, int):
+ case FIONBIO:
+ {
+ const bool was_nonblocking = is_nonblocking ();
+ set_nonblocking (*(int *) p);
+ const bool now_nonblocking = is_nonblocking ();
+ if (was_nonblocking != now_nonblocking)
+ set_pipe_non_blocking (now_nonblocking);
+ ret = 0;
+ break;
+ }
+ case SIOCATMARK:
+ break;
+ default:
+ ret = fhandler_socket::ioctl (cmd, p);
+ break;
+ }
+ return ret;
+}
+
+int
+fhandler_socket_unix::fcntl (int cmd, intptr_t arg)
+{
+ if (get_flags () & O_PATH)
+ /* We're viewing the socket as a disk file, but
+ fhandler_base::fcntl suffices here. */
+ return fhandler_base::fcntl (cmd, arg);
+
+ int ret = -1;
+
+ switch (cmd)
+ {
+ case F_SETOWN:
+ break;
+ case F_GETOWN:
+ break;
+ case F_SETFL:
+ {
+ const bool was_nonblocking = is_nonblocking ();
+ const int allowed_flags = O_APPEND | O_NONBLOCK;
+ int new_flags = arg & allowed_flags;
+ set_flags ((get_flags () & ~allowed_flags) | new_flags);
+ const bool now_nonblocking = is_nonblocking ();
+ if (was_nonblocking != now_nonblocking)
+ set_pipe_non_blocking (now_nonblocking);
+ ret = 0;
+ break;
+ }
+ default:
+ ret = fhandler_socket::fcntl (cmd, arg);
+ break;
+ }
+ return ret;
+}
+
+int
+fhandler_socket_unix::fstat (struct stat *buf)
+{
+ if (!dev ().isfs ())
+ /* fstat called on a socket. */
+ return fhandler_socket::fstat (buf);
+
+ /* stat/lstat on a socket file or fstat on a socket opened w/ O_PATH. */
+ int ret = fhandler_base::fstat_fs (buf);
+ if (!ret)
+ {
+ buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFSOCK;
+ buf->st_size = 0;
+ }
+ return ret;
+}
+
+int
+fhandler_socket_unix::fstatvfs (struct statvfs *sfs)
+{
+ if (!dev ().isfs ())
+ /* fstatvfs called on a socket. */
+ return fhandler_socket::fstatvfs (sfs);
+
+ /* statvfs on a socket file or fstatvfs on a socket opened w/ O_PATH. */
+ 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);
+}
+
+int
+fhandler_socket_unix::fchmod (mode_t newmode)
+{
+ if (!dev ().isfs ())
+ /* fchmod called on a socket. */
+ return fhandler_socket::fchmod (newmode);
+
+ /* chmod on a socket file. [We won't get here if fchmod is called
+ on a socket opened w/ O_PATH.] */
+ fhandler_disk_file fh (pc);
+ fh.get_device () = FH_FS;
+ /* Kludge: Don't allow to remove read bit on socket files for
+ user/group/other, if the accompanying write bit is set. It would
+ be nice to have exact permissions on a socket file, but it's
+ necessary that somebody able to access the socket can always read
+ the contents of the socket file to avoid spurious "permission
+ denied" messages. */
+ newmode |= (newmode & (S_IWUSR | S_IWGRP | S_IWOTH)) << 1;
+ return fh.fchmod (S_IFSOCK | newmode);
+}
+
+int
+fhandler_socket_unix::fchown (uid_t uid, gid_t gid)
+{
+ if (!dev ().isfs ())
+ /* fchown called on a socket. */
+ return fhandler_socket::fchown (uid, gid);
+
+ /* chown/lchown on a socket file. [We won't get here if fchown is
+ called on a socket opened w/ O_PATH.] */
+ fhandler_disk_file fh (pc);
+ return fh.fchown (uid, gid);
+}
+
+int
+fhandler_socket_unix::facl (int cmd, int nentries, aclent_t *aclbufp)
+{
+ if (!dev ().isfs ())
+ /* facl called on a socket. */
+ return fhandler_socket::facl (cmd, nentries, aclbufp);
+
+ /* facl on a socket file. [We won't get here if facl is called on a
+ socket opened w/ O_PATH.] */
+ fhandler_disk_file fh (pc);
+ return fh.facl (cmd, nentries, aclbufp);
+}
+
+int
+fhandler_socket_unix::link (const char *newpath)
+{
+ if (!dev ().isfs ())
+ /* linkat w/ AT_EMPTY_PATH called on a socket not opened w/ O_PATH. */
+ return fhandler_socket::link (newpath);
+ /* link on a socket file or linkat w/ AT_EMPTY_PATH called on a
+ socket opened w/ O_PATH. */
+ fhandler_disk_file fh (pc);
+ if (get_flags () & O_PATH)
+ fh.set_handle (get_handle ());
+ return fh.link (newpath);
+}
+
+#endif /* __WITH_AF_UNIX */
diff --git a/winsup/cygwin/fhandler/tape.cc b/winsup/cygwin/fhandler/tape.cc
new file mode 100644
index 000000000..0e235f16c
--- /dev/null
+++ b/winsup/cygwin/fhandler/tape.cc
@@ -0,0 +1,1530 @@
+/* fhandler_tape.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 "cygtls.h"
+#include <stdlib.h>
+#include <sys/mtio.h>
+#include <sys/param.h>
+#include <devioctl.h>
+#include <ntddstor.h>
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "shared_info.h"
+#include "sigproc.h"
+#include "child_info.h"
+
+/* Media changes and bus resets are sometimes reported and the function
+ hasn't been executed. We repeat all functions which return with one
+ of these error codes. */
+#define TAPE_FUNC(func) while ((lasterr = (func)) == ERROR_MEDIA_CHANGED) \
+ { \
+ initialize (drive, false); \
+ part (partition)->initialize (0); \
+ }
+
+/* Certain tape drives (known example: QUANTUM_ULTRIUM-HH6) return
+ ERROR_NOT_READY rather than ERROR_NO_MEDIA_IN_DRIVE if no media
+ is in the drive. Convert this to the only sane error code. */
+#define GET_TAPE_STATUS(_h) ({ \
+ int _err = GetTapeStatus (_h); \
+ if (_err == ERROR_NOT_READY) \
+ _err = ERROR_NO_MEDIA_IN_DRIVE; \
+ _err; \
+ })
+
+#define IS_BOT(err) ((err) == ERROR_BEGINNING_OF_MEDIA)
+
+#define IS_EOF(err) ((err) == ERROR_FILEMARK_DETECTED \
+ || (err) == ERROR_SETMARK_DETECTED)
+
+#define IS_SM(err) ((err) == ERROR_SETMARK_DETECTED)
+
+#define IS_EOD(err) ((err) == ERROR_END_OF_MEDIA \
+ || (err) == ERROR_EOM_OVERFLOW \
+ || (err) == ERROR_NO_DATA_DETECTED)
+
+#define IS_EOM(err) ((err) == ERROR_END_OF_MEDIA \
+ || (err) == ERROR_EOM_OVERFLOW)
+
+/**********************************************************************/
+/* mtinfo_part */
+
+void
+mtinfo_part::initialize (int64_t nblock)
+{
+ block = nblock;
+ if (block == 0)
+ file = fblock = 0;
+ else
+ file = fblock = -1;
+ smark = false;
+ emark = no_eof;
+}
+
+/**********************************************************************/
+/* mtinfo_drive */
+
+void
+mtinfo_drive::initialize (int num, bool first_time)
+{
+ drive = num;
+ partition = 0;
+ block = -1;
+ lock = unlocked;
+ if (first_time)
+ {
+ buffer_writes (true);
+ async_writes (false);
+ two_fm (false);
+ fast_eom (false);
+ auto_lock (false);
+ sysv (false);
+ nowait (false);
+ }
+ for (int i = 0; i < MAX_PARTITION_NUM; ++i)
+ part (i)->initialize ();
+}
+
+int
+mtinfo_drive::get_dp (HANDLE mt)
+{
+ DWORD len = sizeof _dp;
+ TAPE_FUNC (GetTapeParameters (mt, GET_TAPE_DRIVE_INFORMATION, &len, &_dp));
+ return error ("get_dp");
+}
+
+int
+mtinfo_drive::get_mp (HANDLE mt)
+{
+ DWORD len = sizeof _mp;
+ TAPE_FUNC (GetTapeParameters (mt, GET_TAPE_MEDIA_INFORMATION, &len, &_mp));
+ /* See GET_TAPE_STATUS comment. */
+ if (lasterr == ERROR_NOT_READY)
+ lasterr = ERROR_NO_MEDIA_IN_DRIVE;
+ return error ("get_mp");
+}
+
+int
+mtinfo_drive::open (HANDLE mt)
+{
+ /* First access after opening the device can return BUS RESET, but we
+ need the drive parameters, so just try again. */
+ while (get_dp (mt) == ERROR_BUS_RESET)
+ ;
+ get_mp (mt);
+ get_pos (mt);
+ if (partition < MAX_PARTITION_NUM && part (partition)->block != block)
+ part (partition)->initialize (block);
+ /* The following rewind in position 0 solves a problem which appears
+ * in case of multi volume archives (at least on NT4): The last ReadFile
+ * on the previous medium returns ERROR_NO_DATA_DETECTED. After media
+ * change, all subsequent ReadFile calls return ERROR_NO_DATA_DETECTED,
+ * too. The call to set_pos apparently reset some internal flags.
+ * FIXME: Is that really true or based on a misinterpretation? */
+ if (!block)
+ {
+ debug_printf ("rewind in position 0");
+ set_pos (mt, TAPE_REWIND, 0, false);
+ }
+ return error ("open");
+}
+
+int
+mtinfo_drive::close (HANDLE mt, bool rewind)
+{
+ lasterr = 0;
+ if (GET_TAPE_STATUS (mt) == ERROR_NO_MEDIA_IN_DRIVE)
+ dirty = clean;
+ if (dirty >= has_written)
+ {
+ /* If an async write is still pending, wait for completion. */
+ if (dirty == async_write_pending)
+ lasterr = async_wait (mt, NULL);
+ if (!lasterr)
+ {
+ /* if last operation was writing, write a filemark */
+ debug_printf ("writing filemark");
+ write_marks (mt, TAPE_FILEMARKS, two_fm () ? 2 : 1);
+ if (two_fm () && !lasterr && !rewind) /* Backspace over 2nd fmark. */
+ {
+ set_pos (mt, TAPE_SPACE_FILEMARKS, -1, false);
+ if (!lasterr)
+ part (partition)->fblock = 0; /* That's obvious, isn't it? */
+ }
+ }
+ }
+ else if (dirty == has_read && !rewind)
+ {
+ if (sysv ())
+ {
+ /* Under SYSV semantics, the tape is moved past the next file mark
+ after read. */
+ if (part (partition)->emark == no_eof)
+ set_pos (mt, TAPE_SPACE_FILEMARKS, 1, false);
+ else if (part (partition)->emark == eof_hit)
+ part (partition)->emark = eof;
+ }
+ else
+ {
+ /* Under BSD semantics, we must check if the filemark has been
+ inadvertendly crossed. If so cross the filemark backwards
+ and position the tape right before EOF. */
+ if (part (partition)->emark == eof_hit)
+ set_pos (mt, TAPE_SPACE_FILEMARKS, -1, false);
+ }
+ }
+ if (rewind)
+ {
+ debug_printf ("rewinding");
+ set_pos (mt, TAPE_REWIND, 0, false);
+ }
+ if (auto_lock () && lock == auto_locked)
+ prepare (mt, TAPE_UNLOCK);
+ dirty = clean;
+ return error ("close");
+}
+
+int
+mtinfo_drive::read (HANDLE mt, LPOVERLAPPED pov, void *ptr, size_t &ulen)
+{
+ BOOL ret;
+ DWORD bytes_read = 0;
+
+ if (GET_TAPE_STATUS (mt) == ERROR_NO_MEDIA_IN_DRIVE)
+ return lasterr = ERROR_NO_MEDIA_IN_DRIVE;
+ if (lasterr == ERROR_BUS_RESET)
+ {
+ ulen = 0;
+ goto out;
+ }
+ /* If an async write is still pending, wait for completion. */
+ if (dirty == async_write_pending)
+ lasterr = async_wait (mt, NULL);
+ dirty = clean;
+ if (part (partition)->emark == eof_hit)
+ {
+ part (partition)->emark = eof;
+ lasterr = ulen = 0;
+ goto out;
+ }
+ else if (part (partition)->emark == eod_hit)
+ {
+ part (partition)->emark = eod;
+ lasterr = ulen = 0;
+ goto out;
+ }
+ else if (part (partition)->emark == eod)
+ {
+ lasterr = ERROR_NO_DATA_DETECTED;
+ ulen = (size_t) -1;
+ goto out;
+ }
+ else if (part (partition)->emark == eom_hit)
+ {
+ part (partition)->emark = eom;
+ lasterr = ulen = 0;
+ goto out;
+ }
+ else if (part (partition)->emark == eom)
+ {
+ lasterr = ERROR_END_OF_MEDIA;
+ ulen = (size_t) -1;
+ goto out;
+ }
+ part (partition)->smark = false;
+ if (auto_lock () && lock < auto_locked)
+ prepare (mt, TAPE_LOCK, true);
+ ov = pov;
+ ov->Offset = ov->OffsetHigh = 0;
+ ret = ReadFile (mt, ptr, ulen, &bytes_read, ov);
+ lasterr = ret ? 0 : GetLastError ();
+ if (lasterr == ERROR_IO_PENDING)
+ lasterr = async_wait (mt, &bytes_read);
+ ulen = (size_t) bytes_read;
+ if (bytes_read > 0)
+ {
+ int32_t blocks_read = mp ()->BlockSize == 0
+ ? 1 : howmany (bytes_read, mp ()->BlockSize);
+ block += blocks_read;
+ part (partition)->block += blocks_read;
+ if (part (partition)->fblock >= 0)
+ part (partition)->fblock += blocks_read;
+ }
+ if (IS_EOF (lasterr))
+ {
+ block++;
+ part (partition)->block++;
+ if (part (partition)->file >= 0)
+ part (partition)->file++;
+ part (partition)->fblock = 0;
+ part (partition)->smark = IS_SM (lasterr);
+ part (partition)->emark = bytes_read > 0 ? eof_hit : eof;
+ lasterr = 0;
+ }
+ else if (IS_EOD (lasterr))
+ {
+ if (part (partition)->emark == eof)
+ part (partition)->emark = IS_EOM (lasterr) ? eom : eod;
+ else
+ {
+ part (partition)->emark = IS_EOM (lasterr) ? eom_hit : eod_hit;
+ lasterr = 0;
+ }
+ }
+ else
+ {
+ part (partition)->emark = no_eof;
+ /* This happens if the buffer is too small when in variable block
+ size mode. Linux returns ENOMEM here. We're doing the same. */
+ if (lasterr == ERROR_MORE_DATA)
+ lasterr = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ if (!lasterr)
+ dirty = has_read;
+out:
+ return error ("read");
+}
+
+int
+mtinfo_drive::async_wait (HANDLE mt, DWORD *bytes_written)
+{
+ DWORD written;
+
+ bool ret = GetOverlappedResult (mt, ov, &written, TRUE);
+ if (bytes_written)
+ *bytes_written = written;
+ return ret ? 0 : GetLastError ();
+}
+
+int
+mtinfo_drive::write (HANDLE mt, LPOVERLAPPED pov, const void *ptr, size_t &len)
+{
+ BOOL ret;
+ DWORD bytes_written = 0;
+ int async_err = 0;
+
+ if (GET_TAPE_STATUS (mt) == ERROR_NO_MEDIA_IN_DRIVE)
+ return lasterr = ERROR_NO_MEDIA_IN_DRIVE;
+ if (lasterr == ERROR_BUS_RESET)
+ {
+ len = 0;
+ return error ("write");
+ }
+ if (dirty == async_write_pending)
+ async_err = async_wait (mt, &bytes_written);
+ dirty = clean;
+ part (partition)->smark = false;
+ if (auto_lock () && lock < auto_locked)
+ prepare (mt, TAPE_LOCK, true);
+ ov = pov;
+ ov->Offset = ov->OffsetHigh = 0;
+ ret = WriteFile (mt, ptr, len, &bytes_written, ov);
+ lasterr = ret ? 0: GetLastError ();
+ if (lasterr == ERROR_IO_PENDING)
+ {
+ if (async_writes () && mp ()->BlockSize == 0)
+ dirty = async_write_pending;
+ else
+ /* Wait for completion if a non-async write. */
+ lasterr = async_wait (mt, &bytes_written);
+ }
+ len = (size_t) bytes_written;
+ if (bytes_written > 0)
+ {
+ int32_t blocks_written = mp ()->BlockSize == 0
+ ? 1 : howmany (bytes_written, mp ()->BlockSize);
+ block += blocks_written;
+ part (partition)->block += blocks_written;
+ if (part (partition)->fblock >= 0)
+ part (partition)->fblock += blocks_written;
+ }
+ if (!lasterr && async_err)
+ lasterr = async_err;
+ if (lasterr == ERROR_EOM_OVERFLOW)
+ part (partition)->emark = eom;
+ else if (lasterr == ERROR_END_OF_MEDIA)
+ ; // FIXME?: part (partition)->emark = eom_hit;
+ else
+ {
+ part (partition)->emark = no_eof;
+ if (!lasterr)
+ dirty = has_written;
+ else if (lasterr == ERROR_IO_PENDING)
+ dirty = async_write_pending;
+ }
+ return error ("write");
+}
+
+int
+mtinfo_drive::get_pos (HANDLE mt, int32_t *ppartition, int64_t *pblock)
+{
+ DWORD p;
+ ULARGE_INTEGER b;
+
+ TAPE_FUNC (GetTapePosition (mt, TAPE_LOGICAL_POSITION, &p,
+ &b.LowPart, &b.HighPart));
+ if (lasterr == ERROR_INVALID_FUNCTION)
+ TAPE_FUNC (GetTapePosition (mt, TAPE_ABSOLUTE_POSITION, &p,
+ &b.LowPart, &b.HighPart));
+ if (!lasterr)
+ {
+ if (p > 0)
+ partition = (int32_t) p - 1;
+ block = (int64_t) b.QuadPart;
+ if (ppartition)
+ *ppartition= partition;
+ if (pblock)
+ *pblock = block;
+ }
+ else
+ {
+ partition = 0;
+ block = -1;
+ }
+ return error ("get_pos");
+}
+
+int
+mtinfo_drive::_set_pos (HANDLE mt, int mode, int64_t count, int partition,
+ BOOL dont_wait)
+{
+ /* If an async write is still pending, wait for completion. */
+ if (dirty == async_write_pending)
+ lasterr = async_wait (mt, NULL);
+ dirty = clean;
+ LARGE_INTEGER c = { QuadPart:count };
+ TAPE_FUNC (SetTapePosition (mt, mode, partition, c.LowPart, c.HighPart,
+ dont_wait));
+ return lasterr;
+}
+
+int
+mtinfo_drive::set_pos (HANDLE mt, int mode, int64_t count, bool sfm_func)
+{
+ int err = 0;
+ int64_t undone = count;
+ BOOL dont_wait = FALSE;
+
+ switch (mode)
+ {
+ case TAPE_SPACE_RELATIVE_BLOCKS:
+ case TAPE_SPACE_FILEMARKS:
+ case TAPE_SPACE_SETMARKS:
+ if (!count)
+ {
+ lasterr = 0;
+ goto out;
+ }
+ break;
+ case TAPE_ABSOLUTE_BLOCK:
+ case TAPE_LOGICAL_BLOCK:
+ case TAPE_REWIND:
+ dont_wait = nowait () ? TRUE : FALSE;
+ break;
+ }
+ if (mode == TAPE_SPACE_FILEMARKS)
+ {
+ while (!err && undone > 0)
+ if (!(err = _set_pos (mt, mode, 1, 0, FALSE)) || IS_SM (err))
+ --undone;
+ while (!err && undone < 0)
+ if (!(err = _set_pos (mt, mode, -1, 0, FALSE)) || IS_SM (err))
+ ++undone;
+ }
+ else
+ err = _set_pos (mt, mode, count, 0, dont_wait);
+ switch (mode)
+ {
+ case TAPE_ABSOLUTE_BLOCK:
+ case TAPE_LOGICAL_BLOCK:
+ get_pos (mt);
+ part (partition)->initialize (block);
+ break;
+ case TAPE_REWIND:
+ if (!err)
+ {
+ block = 0;
+ part (partition)->initialize (0);
+ }
+ else
+ {
+ get_pos (mt);
+ part (partition)->initialize (block);
+ }
+ break;
+ case TAPE_SPACE_END_OF_DATA:
+ get_pos (mt);
+ part (partition)->initialize (block);
+ part (partition)->emark = IS_EOM (err) ? eom : eod;
+ break;
+ case TAPE_SPACE_FILEMARKS:
+ if (!err || IS_SM (err))
+ {
+ get_pos (mt);
+ part (partition)->block = block;
+ if (count > 0)
+ {
+ if (part (partition)->file >= 0)
+ part (partition)->file += count - undone;
+ part (partition)->fblock = 0;
+ part (partition)->smark = IS_SM (err);
+ }
+ else
+ {
+ if (part (partition)->file >= 0)
+ part (partition)->file += count - undone;
+ part (partition)->fblock = -1;
+ part (partition)->smark = false;
+ }
+ if (sfm_func)
+ err = set_pos (mt, mode, count > 0 ? -1 : 1, false);
+ else
+ part (partition)->emark = count > 0 ? eof : no_eof;
+ }
+ else if (IS_EOD (err))
+ {
+ get_pos (mt);
+ part (partition)->block = block;
+ if (part (partition)->file >= 0)
+ part (partition)->file += count - undone;
+ part (partition)->fblock = -1;
+ part (partition)->smark = false;
+ part (partition)->emark = IS_EOM (err) ? eom : eod;
+ }
+ else if (IS_BOT (err))
+ {
+ block = 0;
+ part (partition)->initialize (0);
+ }
+ else
+ {
+ get_pos (mt);
+ part (partition)->initialize (block);
+ }
+ break;
+ case TAPE_SPACE_RELATIVE_BLOCKS:
+ if (!err)
+ {
+ block += count;
+ part (partition)->block += count;
+ if (part (partition)->fblock >= 0)
+ part (partition)->fblock += count;
+ part (partition)->smark = false;
+ part (partition)->emark = no_eof;
+ }
+ else if (IS_EOF (err))
+ {
+ get_pos (mt);
+ part (partition)->block = block;
+ if (part (partition)->file >= 0)
+ part (partition)->file += count > 0 ? 1 : -1;
+ part (partition)->fblock = count > 0 ? 0 : -1;
+ part (partition)->smark = (count > 0 && IS_SM (err));
+ part (partition)->emark = count > 0 ? eof : no_eof;
+ }
+ else if (IS_EOD (err))
+ {
+ get_pos (mt);
+ part (partition)->fblock = block - part (partition)->block;
+ part (partition)->block = block;
+ part (partition)->smark = false;
+ part (partition)->emark = IS_EOM (err) ? eom : eod;
+ }
+ else if (IS_BOT (err))
+ {
+ block = 0;
+ part (partition)->initialize (0);
+ }
+ break;
+ case TAPE_SPACE_SETMARKS:
+ get_pos (mt);
+ part (partition)->block = block;
+ if (!err)
+ {
+ part (partition)->file = -1;
+ part (partition)->fblock = -1;
+ part (partition)->smark = true;
+ }
+ break;
+ }
+ lasterr = err;
+out:
+ return error ("set_pos");
+}
+
+int
+mtinfo_drive::create_partitions (HANDLE mt, int32_t count)
+{
+ if (dp ()->MaximumPartitionCount <= 1)
+ return ERROR_INVALID_PARAMETER;
+ if (set_pos (mt, TAPE_REWIND, 0, false))
+ goto out;
+ partition = 0;
+ part (partition)->initialize (0);
+ debug_printf ("Format tape with %s partition(s)", count <= 0 ? "one" : "two");
+ if (get_feature (TAPE_DRIVE_INITIATOR))
+ {
+ TAPE_FUNC (CreateTapePartition (mt, TAPE_INITIATOR_PARTITIONS,
+ count <= 0 ? 0 : 2, (DWORD) count));
+ }
+ else if (get_feature (TAPE_DRIVE_SELECT))
+ {
+ TAPE_FUNC (CreateTapePartition (mt, TAPE_SELECT_PARTITIONS,
+ count <= 0 ? 0 : 2, 0));
+ }
+ else if (get_feature (TAPE_DRIVE_FIXED))
+ {
+ /* This is supposed to work for Tandberg SLR drivers up to version
+ 1.6 which missed to set the TAPE_DRIVE_INITIATOR flag. According
+ to Tandberg, CreateTapePartition(TAPE_FIXED_PARTITIONS) apparently
+ does not ignore the dwCount parameter. Go figure! */
+ TAPE_FUNC (CreateTapePartition (mt, TAPE_FIXED_PARTITIONS,
+ count <= 0 ? 0 : 2, (DWORD) count));
+ }
+ else
+ lasterr = ERROR_INVALID_PARAMETER;
+out:
+ return error ("partition");
+}
+
+int
+mtinfo_drive::set_partition (HANDLE mt, int32_t count)
+{
+ if (count < 0 || (uint32_t) count >= MAX_PARTITION_NUM)
+ lasterr = ERROR_INVALID_PARAMETER;
+ else if ((DWORD) count >= dp ()->MaximumPartitionCount)
+ lasterr = ERROR_IO_DEVICE;
+ else
+ {
+ uint64_t part_block = part (count)->block >= 0 ? part (count)->block : 0;
+ int err = _set_pos (mt, TAPE_LOGICAL_BLOCK, part_block, count + 1, FALSE);
+ if (err)
+ {
+ int64_t sav_block = block;
+ int32_t sav_partition = partition;
+ get_pos (mt);
+ if (sav_partition != partition)
+ {
+ if (partition < MAX_PARTITION_NUM
+ && part (partition)->block != block)
+ part (partition)->initialize (block);
+ }
+ else if (sav_block != block && partition < MAX_PARTITION_NUM)
+ part (partition)->initialize (block);
+ lasterr = err;
+ }
+ else
+ {
+ partition = count;
+ if (part (partition)->block == -1)
+ part (partition)->initialize (0);
+ }
+ }
+ return error ("set_partition");
+}
+
+int
+mtinfo_drive::write_marks (HANDLE mt, int marktype, DWORD count)
+{
+ /* If an async write is still pending, wait for completion. */
+ if (dirty == async_write_pending)
+ {
+ lasterr = async_wait (mt, NULL);
+ dirty = has_written;
+ }
+ if (marktype != TAPE_SETMARKS)
+ dirty = clean;
+ if (marktype == TAPE_FILEMARKS
+ && !get_feature (TAPE_DRIVE_WRITE_FILEMARKS))
+ {
+ if (get_feature (TAPE_DRIVE_WRITE_LONG_FMKS))
+ marktype = TAPE_LONG_FILEMARKS;
+ else
+ marktype = TAPE_SHORT_FILEMARKS;
+ }
+ TAPE_FUNC (WriteTapemark (mt, marktype, count, FALSE));
+ int err = lasterr;
+ if (!err)
+ {
+ block += count;
+ part (partition)->block += count;
+ if (part (partition)->file >= 0)
+ part (partition)->file += count;
+ part (partition)->fblock = 0;
+ part (partition)->emark = eof;
+ part (partition)->smark = (marktype == TAPE_SETMARKS);
+ }
+ else
+ {
+ int64_t sav_block = block;
+ int32_t sav_partition = partition;
+ get_pos (mt);
+ if (sav_partition != partition)
+ {
+ if (partition < MAX_PARTITION_NUM
+ && part (partition)->block != block)
+ part (partition)->initialize (block);
+ }
+ else if (sav_block != block && partition < MAX_PARTITION_NUM)
+ part (partition)->initialize (block);
+ lasterr = err;
+ }
+ return error ("write_marks");
+}
+
+int
+mtinfo_drive::erase (HANDLE mt, int mode)
+{
+ switch (mode)
+ {
+ case TAPE_ERASE_SHORT:
+ if (!get_feature (TAPE_DRIVE_ERASE_SHORT))
+ mode = TAPE_ERASE_LONG;
+ break;
+ case TAPE_ERASE_LONG:
+ if (!get_feature (TAPE_DRIVE_ERASE_LONG))
+ mode = TAPE_ERASE_SHORT;
+ break;
+ }
+ TAPE_FUNC (EraseTape (mt, mode, nowait () ? TRUE : FALSE));
+ part (partition)->initialize (0);
+ return error ("erase");
+}
+
+int
+mtinfo_drive::prepare (HANDLE mt, int action, bool is_auto)
+{
+ BOOL dont_wait = FALSE;
+
+ /* If an async write is still pending, wait for completion. */
+ if (dirty == async_write_pending)
+ lasterr = async_wait (mt, NULL);
+ dirty = clean;
+ if (action == TAPE_UNLOAD || action == TAPE_LOAD || action == TAPE_TENSION)
+ dont_wait = nowait () ? TRUE : FALSE;
+ TAPE_FUNC (PrepareTape (mt, action, dont_wait));
+ /* Reset buffer after all successful preparations but lock and unlock. */
+ switch (action)
+ {
+ case TAPE_FORMAT:
+ case TAPE_UNLOAD:
+ case TAPE_LOAD:
+ initialize (drive, false);
+ break;
+ case TAPE_TENSION:
+ part (partition)->initialize (0);
+ break;
+ case TAPE_LOCK:
+ lock = lasterr ? lock_error : is_auto ? auto_locked : locked;
+ break;
+ case TAPE_UNLOCK:
+ lock = lasterr ? lock_error : unlocked;
+ break;
+ }
+ return error ("prepare");
+}
+
+int
+mtinfo_drive::set_compression (HANDLE mt, int32_t count)
+{
+ if (!get_feature (TAPE_DRIVE_SET_COMPRESSION))
+ return ERROR_INVALID_PARAMETER;
+ TAPE_SET_DRIVE_PARAMETERS sdp =
+ {
+ dp ()->ECC,
+ (BOOLEAN) (count ? TRUE : FALSE),
+ dp ()->DataPadding,
+ dp ()->ReportSetmarks,
+ dp ()->EOTWarningZoneSize
+ };
+ TAPE_FUNC (SetTapeParameters (mt, SET_TAPE_DRIVE_INFORMATION, &sdp));
+ int err = lasterr;
+ if (!err)
+ dp ()->Compression = sdp.Compression;
+ else
+ get_dp (mt);
+ lasterr = err;
+ return error ("set_compression");
+}
+
+int
+mtinfo_drive::set_blocksize (HANDLE mt, DWORD count)
+{
+ TAPE_SET_MEDIA_PARAMETERS smp = {count};
+ TAPE_FUNC (SetTapeParameters (mt, SET_TAPE_MEDIA_INFORMATION, &smp));
+ /* Make sure to update blocksize info! */
+ return lasterr ? error ("set_blocksize") : get_mp (mt);
+}
+
+int
+mtinfo_drive::get_status (HANDLE mt, struct mtget *get)
+{
+ int notape = 0;
+ DWORD tstat;
+
+ if (!get)
+ return ERROR_INVALID_PARAMETER;
+
+ tstat = GET_TAPE_STATUS (mt);
+ DWORD mpstat = (DWORD) get_mp (mt);
+
+ debug_printf ("GetTapeStatus: %u, get_mp: %d", tstat, mpstat);
+
+ if (tstat == ERROR_NO_MEDIA_IN_DRIVE || mpstat == ERROR_NO_MEDIA_IN_DRIVE)
+ notape = 1;
+
+ memset (get, 0, sizeof *get);
+
+ get->mt_type = MT_ISUNKNOWN;
+
+ if (!notape && get_feature (TAPE_DRIVE_SET_BLOCK_SIZE))
+ get->mt_dsreg = (mp ()->BlockSize << MT_ST_BLKSIZE_SHIFT)
+ & MT_ST_BLKSIZE_MASK;
+ else
+ get->mt_dsreg = (dp ()->DefaultBlockSize << MT_ST_BLKSIZE_SHIFT)
+ & MT_ST_BLKSIZE_MASK;
+
+ DWORD size = sizeof (GET_MEDIA_TYPES) + 10 * sizeof (DEVICE_MEDIA_INFO);
+ void *buf = alloca (size);
+ if (DeviceIoControl (mt, IOCTL_STORAGE_GET_MEDIA_TYPES_EX,
+ NULL, 0, buf, size, &size, NULL)
+ || GetLastError () == ERROR_MORE_DATA)
+ {
+ PGET_MEDIA_TYPES gmt = (PGET_MEDIA_TYPES) buf;
+ for (DWORD i = 0; i < gmt->MediaInfoCount; ++i)
+ {
+ PDEVICE_MEDIA_INFO dmi = &gmt->MediaInfo[i];
+ get->mt_type = dmi->DeviceSpecific.TapeInfo.MediaType;
+#define TINFO DeviceSpecific.TapeInfo
+ if (dmi->TINFO.MediaCharacteristics & MEDIA_CURRENTLY_MOUNTED)
+ {
+ get->mt_type = dmi->DeviceSpecific.TapeInfo.MediaType;
+ if (dmi->TINFO.BusType == BusTypeScsi)
+ get->mt_dsreg |=
+ (dmi->TINFO.BusSpecificData.ScsiInformation.DensityCode
+ << MT_ST_DENSITY_SHIFT)
+ & MT_ST_DENSITY_MASK;
+ break;
+ }
+#undef TINFO
+ }
+ }
+
+ if (!notape)
+ {
+ get->mt_resid = (partition & 0xffff)
+ | ((mp ()->PartitionCount & 0xffff) << 16);
+ get->mt_fileno = part (partition)->file;
+ get->mt_blkno = part (partition)->fblock;
+
+ if (get->mt_blkno != 0)
+ /* nothing to do */;
+ else if (get->mt_fileno == 0)
+ get->mt_gstat |= GMT_BOT (-1);
+ else
+ get->mt_gstat |= GMT_EOF (-1);
+ if (part (partition)->emark >= eod_hit)
+ get->mt_gstat |= GMT_EOD (-1);
+ if (part (partition)->emark >= eom_hit)
+ get->mt_gstat |= GMT_EOT (-1);
+
+ if (part (partition)->smark)
+ get->mt_gstat |= GMT_SM (-1);
+
+ get->mt_gstat |= GMT_ONLINE (-1);
+
+ if (mp ()->WriteProtected)
+ get->mt_gstat |= GMT_WR_PROT (-1);
+
+ get->mt_capacity = mp ()->Capacity.QuadPart;
+ get->mt_remaining = mp ()->Remaining.QuadPart;
+ }
+
+ if (notape)
+ get->mt_gstat |= GMT_DR_OPEN (-1);
+
+ if (buffer_writes ())
+ get->mt_gstat |= GMT_IM_REP_EN (-1); /* TODO: Async writes */
+
+ if (tstat == ERROR_DEVICE_REQUIRES_CLEANING)
+ get->mt_gstat |= GMT_CLN (-1);
+
+ /* Cygwin specials: */
+ if (dp ()->ReportSetmarks)
+ get->mt_gstat |= GMT_REP_SM (-1);
+ if (dp ()->DataPadding)
+ get->mt_gstat |= GMT_PADDING (-1);
+ if (dp ()->ECC)
+ get->mt_gstat |= GMT_HW_ECC (-1);
+ if (dp ()->Compression)
+ get->mt_gstat |= GMT_HW_COMP (-1);
+ if (two_fm ())
+ get->mt_gstat |= GMT_TWO_FM (-1);
+ if (fast_eom ())
+ get->mt_gstat |= GMT_FAST_MTEOM (-1);
+ if (auto_lock ())
+ get->mt_gstat |= GMT_AUTO_LOCK (-1);
+ if (sysv ())
+ get->mt_gstat |= GMT_SYSV (-1);
+ if (nowait ())
+ get->mt_gstat |= GMT_NOWAIT (-1);
+ if (async_writes ())
+ get->mt_gstat |= GMT_ASYNC (-1);
+
+ get->mt_erreg = 0; /* FIXME: No softerr counting */
+
+ get->mt_minblksize = dp ()->MinimumBlockSize;
+ get->mt_maxblksize = dp ()->MaximumBlockSize;
+ get->mt_defblksize = dp ()->DefaultBlockSize;
+ get->mt_featureslow = dp ()->FeaturesLow;
+ get->mt_featureshigh = dp ()->FeaturesHigh;
+ get->mt_eotwarningzonesize = dp ()->EOTWarningZoneSize;
+
+ return 0;
+}
+
+int
+mtinfo_drive::set_options (HANDLE mt, int32_t options)
+{
+ int32_t what = (options & MT_ST_OPTIONS);
+ bool call_setparams = false;
+ bool set;
+ TAPE_SET_DRIVE_PARAMETERS sdp =
+ {
+ dp ()->ECC,
+ dp ()->Compression,
+ dp ()->DataPadding,
+ dp ()->ReportSetmarks,
+ dp ()->EOTWarningZoneSize
+ };
+
+ lasterr = 0;
+ switch (what)
+ {
+ case 0:
+ if (options == 0 || options == 1)
+ {
+ buffer_writes ((options == 1));
+ }
+ break;
+ case MT_ST_BOOLEANS:
+ buffer_writes (!!(options & MT_ST_BUFFER_WRITES));
+ async_writes (!!(options & MT_ST_ASYNC_WRITES));
+ two_fm (!!(options & MT_ST_TWO_FM));
+ fast_eom (!!(options & MT_ST_FAST_MTEOM));
+ auto_lock (!!(options & MT_ST_AUTO_LOCK));
+ sysv (!!(options & MT_ST_SYSV));
+ nowait (!!(options & MT_ST_NOWAIT));
+ if (get_feature (TAPE_DRIVE_SET_ECC))
+ sdp.ECC = !!(options & MT_ST_ECC);
+ if (get_feature (TAPE_DRIVE_SET_PADDING))
+ sdp.DataPadding = !!(options & MT_ST_PADDING);
+ if (get_feature (TAPE_DRIVE_SET_REPORT_SMKS))
+ sdp.ReportSetmarks = !!(options & MT_ST_REPORT_SM);
+ if (sdp.ECC != dp ()->ECC || sdp.DataPadding != dp ()->DataPadding
+ || sdp.ReportSetmarks != dp ()->ReportSetmarks)
+ call_setparams = true;
+ break;
+ case MT_ST_SETBOOLEANS:
+ case MT_ST_CLEARBOOLEANS:
+ set = (what == MT_ST_SETBOOLEANS);
+ if (options & MT_ST_BUFFER_WRITES)
+ buffer_writes (set);
+ if (options & MT_ST_ASYNC_WRITES)
+ async_writes (set);
+ if (options & MT_ST_TWO_FM)
+ two_fm (set);
+ if (options & MT_ST_FAST_MTEOM)
+ fast_eom (set);
+ if (options & MT_ST_AUTO_LOCK)
+ auto_lock (set);
+ if (options & MT_ST_SYSV)
+ sysv (set);
+ if (options & MT_ST_NOWAIT)
+ nowait (set);
+ if (options & MT_ST_ECC)
+ sdp.ECC = set;
+ if (options & MT_ST_PADDING)
+ sdp.DataPadding = set;
+ if (options & MT_ST_REPORT_SM)
+ sdp.ReportSetmarks = set;
+ if (sdp.ECC != dp ()->ECC || sdp.DataPadding != dp ()->DataPadding
+ || sdp.ReportSetmarks != dp ()->ReportSetmarks)
+ call_setparams = true;
+ break;
+ case MT_ST_EOT_WZ_SIZE:
+ if (get_feature (TAPE_DRIVE_SET_EOT_WZ_SIZE))
+ {
+ sdp.EOTWarningZoneSize = (options & ~MT_ST_OPTIONS);
+ if (sdp.EOTWarningZoneSize != dp ()->EOTWarningZoneSize)
+ call_setparams = true;
+ }
+ break;
+ }
+ if (call_setparams)
+ {
+ TAPE_FUNC (SetTapeParameters (mt, SET_TAPE_DRIVE_INFORMATION, &sdp));
+ int err = lasterr;
+ if (!err)
+ {
+ dp ()->ECC = sdp.ECC;
+ dp ()->DataPadding = sdp.DataPadding;
+ dp ()->ReportSetmarks = sdp.ReportSetmarks;
+ }
+ else
+ get_dp (mt);
+ lasterr = err;
+ }
+ return error ("set_options");
+}
+
+int
+mtinfo_drive::ioctl (HANDLE mt, unsigned int cmd, void *buf)
+{
+ __try
+ {
+ if (cmd == MTIOCTOP)
+ {
+ struct mtop *op = (struct mtop *) buf;
+ if (lasterr == ERROR_BUS_RESET)
+ {
+ /* If a bus reset occurs, block further access to this device
+ until the user rewinds, unloads or in any other way tries
+ to maintain a well-known tape position. */
+ if (op->mt_op != MTREW && op->mt_op != MTOFFL
+ && op->mt_op != MTRETEN && op->mt_op != MTERASE
+ && op->mt_op != MTSEEK && op->mt_op != MTEOM)
+ return ERROR_BUS_RESET;
+ /* Try to maintain last lock state after bus reset. */
+ if (lock >= auto_locked && PrepareTape (mt, TAPE_LOCK, FALSE))
+ {
+ debug_printf ("Couldn't relock drive after bus reset.");
+ lock = unlocked;
+ }
+ }
+ switch (op->mt_op)
+ {
+ case MTRESET:
+ break;
+ case MTFSF:
+ set_pos (mt, TAPE_SPACE_FILEMARKS, op->mt_count, false);
+ break;
+ case MTBSF:
+ set_pos (mt, TAPE_SPACE_FILEMARKS, -op->mt_count, false);
+ break;
+ case MTFSR:
+ set_pos (mt, TAPE_SPACE_RELATIVE_BLOCKS, op->mt_count, false);
+ break;
+ case MTBSR:
+ set_pos (mt, TAPE_SPACE_RELATIVE_BLOCKS, -op->mt_count, false);
+ break;
+ case MTWEOF:
+ write_marks (mt, TAPE_FILEMARKS, op->mt_count);
+ break;
+ case MTREW:
+ set_pos (mt, TAPE_REWIND, 0, false);
+ break;
+ case MTOFFL:
+ case MTUNLOAD:
+ prepare (mt, TAPE_UNLOAD);
+ break;
+ case MTNOP:
+ lasterr = 0;
+ break;
+ case MTRETEN:
+ if (!get_feature (TAPE_DRIVE_TENSION))
+ lasterr = ERROR_INVALID_PARAMETER;
+ else if (!set_pos (mt, TAPE_REWIND, 0, false))
+ prepare (mt, TAPE_TENSION);
+ break;
+ case MTBSFM:
+ set_pos (mt, TAPE_SPACE_FILEMARKS, -op->mt_count, true);
+ break;
+ case MTFSFM:
+ set_pos (mt, TAPE_SPACE_FILEMARKS, op->mt_count, true);
+ break;
+ case MTEOM:
+ if (fast_eom () && get_feature (TAPE_DRIVE_END_OF_DATA))
+ set_pos (mt, TAPE_SPACE_END_OF_DATA, 0, false);
+ else
+ set_pos (mt, TAPE_SPACE_FILEMARKS, 32767, false);
+ break;
+ case MTERASE:
+ erase (mt, TAPE_ERASE_LONG);
+ break;
+ case MTRAS1:
+ case MTRAS2:
+ case MTRAS3:
+ lasterr = ERROR_INVALID_PARAMETER;
+ break;
+ case MTSETBLK:
+ if (!get_feature (TAPE_DRIVE_SET_BLOCK_SIZE))
+ {
+ lasterr = ERROR_INVALID_PARAMETER;
+ break;
+ }
+ if ((DWORD) op->mt_count == mp ()->BlockSize)
+ {
+ /* Nothing has changed. */
+ lasterr = 0;
+ break;
+ }
+ if ((op->mt_count == 0 && !get_feature (TAPE_DRIVE_VARIABLE_BLOCK))
+ || (op->mt_count > 0
+ && ((DWORD) op->mt_count < dp ()->MinimumBlockSize
+ || (DWORD) op->mt_count > dp ()->MaximumBlockSize)))
+ {
+ lasterr = ERROR_INVALID_PARAMETER;
+ break;
+ }
+ if (set_blocksize (mt, op->mt_count)
+ && lasterr == ERROR_INVALID_FUNCTION)
+ lasterr = ERROR_INVALID_BLOCK_LENGTH;
+ break;
+ case MTSEEK:
+ if (get_feature (TAPE_DRIVE_LOGICAL_BLK))
+ set_pos (mt, TAPE_LOGICAL_BLOCK, op->mt_count, false);
+ else if (!get_pos (mt))
+ set_pos (mt, TAPE_SPACE_RELATIVE_BLOCKS,
+ op->mt_count - block, false);
+ break;
+ case MTTELL:
+ if (!get_pos (mt))
+ op->mt_count = (int) block;
+ break;
+ case MTFSS:
+ set_pos (mt, TAPE_SPACE_SETMARKS, op->mt_count, false);
+ break;
+ case MTBSS:
+ set_pos (mt, TAPE_SPACE_SETMARKS, -op->mt_count, false);
+ break;
+ case MTWSM:
+ write_marks (mt, TAPE_SETMARKS, op->mt_count);
+ break;
+ case MTLOCK:
+ prepare (mt, TAPE_LOCK);
+ break;
+ case MTUNLOCK:
+ prepare (mt, TAPE_UNLOCK);
+ break;
+ case MTLOAD:
+ prepare (mt, TAPE_LOAD);
+ break;
+ case MTCOMPRESSION:
+ set_compression (mt, op->mt_count);
+ break;
+ case MTSETPART:
+ set_partition (mt, op->mt_count);
+ break;
+ case MTMKPART:
+ create_partitions (mt, op->mt_count);
+ break;
+ case MTSETDRVBUFFER:
+ set_options (mt, op->mt_count);
+ break;
+ case MTSETDENSITY:
+ default:
+ lasterr = ERROR_INVALID_PARAMETER;
+ break;
+ }
+ }
+ else if (cmd == MTIOCGET)
+ get_status (mt, (struct mtget *) buf);
+ else if (cmd == MTIOCPOS && !get_pos (mt))
+ ((struct mtpos *) buf)->mt_blkno = (long) block;
+ }
+ __except (NO_ERROR)
+ {
+ lasterr = ERROR_NOACCESS;
+ }
+ __endtry
+ return lasterr;
+}
+
+/**********************************************************************/
+/* mtinfo */
+
+void
+mtinfo::initialize ()
+{
+ for (unsigned i = 0; i < MAX_DRIVE_NUM; ++i)
+ drive (i)->initialize (i, true);
+}
+
+/**********************************************************************/
+/* fhandler_dev_tape */
+
+#define mt (cygwin_shared->mt)
+
+#define lock(err_ret_val) if (!_lock (false)) return (err_ret_val);
+
+inline bool
+fhandler_dev_tape::_lock (bool cancelable)
+{
+ /* O_NONBLOCK is only valid in a read or write call. Only those are
+ cancelable. */
+ DWORD timeout = cancelable && is_nonblocking () ? 0 : INFINITE;
+ switch (cygwait (mt_mtx, timeout,
+ cw_sig | cw_sig_restart | cw_cancel | cw_cancel_self))
+ {
+ case WAIT_OBJECT_0:
+ return true;
+ case WAIT_TIMEOUT:
+ set_errno (EAGAIN);
+ return false;
+ default:
+ __seterrno ();
+ return false;
+ }
+}
+
+inline int
+fhandler_dev_tape::unlock (int ret)
+{
+ ReleaseMutex (mt_mtx);
+ return ret;
+}
+
+fhandler_dev_tape::fhandler_dev_tape ()
+ : fhandler_dev_raw ()
+{
+ debug_printf ("unit: %d", dev ().get_minor ());
+}
+
+int
+fhandler_dev_tape::open (int flags, mode_t)
+{
+ int ret;
+
+ if (driveno () >= MAX_DRIVE_NUM)
+ {
+ set_errno (ENOENT);
+ return 0;
+ }
+ if (!(mt_mtx = CreateMutex (&sec_all, !!(flags & O_CLOEXEC), NULL)))
+ {
+ __seterrno ();
+ return 0;
+ }
+
+ /* The O_SYNC flag is not supported by the tape driver. Use the
+ MT_ST_BUFFER_WRITES and MT_ST_ASYNC_WRITES flags in the drive
+ settings instead. In turn, the MT_ST_BUFFER_WRITES is translated
+ into O_SYNC, which controls the FILE_WRITE_THROUGH flag in the
+ NtCreateFile call in fhandler_base::open. */
+ flags &= ~O_SYNC;
+ if (!mt.drive (driveno ())->buffer_writes ())
+ flags |= O_SYNC;
+
+ ret = fhandler_dev_raw::open (flags);
+ if (ret)
+ {
+ mt.drive (driveno ())->open (get_handle ());
+
+ /* In append mode, seek to beginning of next filemark */
+ if (flags & O_APPEND)
+ mt.drive (driveno ())->set_pos (get_handle (),
+ TAPE_SPACE_FILEMARKS, 1, true);
+
+ if (!(flags & O_DIRECT))
+ {
+ devbufsiz = mt.drive (driveno ())->dp ()->MaximumBlockSize;
+ devbufalign = 1;
+ devbufalloc = devbuf = new char [devbufsiz];
+ }
+ }
+ else
+ ReleaseMutex (mt_mtx);
+ return ret;
+}
+
+int
+fhandler_dev_tape::close ()
+{
+ int ret = 0;
+ int cret = 0;
+
+ if (!have_execed)
+ {
+ lock (-1);
+ ret = mt.drive (driveno ())->close (get_handle (), is_rewind_device ());
+ if (ret)
+ __seterrno_from_win_error (ret);
+ cret = fhandler_dev_raw::close ();
+ unlock (0);
+ }
+ if (ov.hEvent)
+ CloseHandle (ov.hEvent);
+ CloseHandle (mt_mtx);
+ return ret ? -1 : cret;
+}
+
+void
+fhandler_dev_tape::raw_read (void *ptr, size_t &ulen)
+{
+ char *buf = (char *) ptr;
+ size_t len = ulen;
+ size_t block_size;
+ size_t bytes_to_read;
+ size_t bytes_read = 0;
+ int ret = 0;
+
+ if (lastblk_to_read ())
+ {
+ lastblk_to_read (false);
+ ulen = 0;
+ return;
+ }
+ if (!_lock (true))
+ {
+ ulen = (size_t) -1;
+ return;
+ }
+ block_size = mt.drive (driveno ())->mp ()->BlockSize;
+ if (devbuf)
+ {
+ if (devbufend > devbufstart)
+ {
+ bytes_to_read = MIN (len, devbufend - devbufstart);
+ debug_printf ("read %lu bytes from buffer (rest %lu)",
+ bytes_to_read, devbufend - devbufstart - bytes_to_read);
+ memcpy (buf, devbuf + devbufstart, bytes_to_read);
+ len -= bytes_to_read;
+ bytes_read += bytes_to_read;
+ buf += bytes_to_read;
+ devbufstart += bytes_to_read;
+ if (devbufstart == devbufend)
+ devbufstart = devbufend = 0;
+ /* If a switch to variable block_size occured, just return the buffer
+ remains until the buffer is empty, then proceed with usual variable
+ block size handling (one block per read call). */
+ if (!block_size)
+ len = 0;
+ }
+ if (len > 0)
+ {
+ if (!ov.hEvent
+ && !(ov.hEvent = CreateEvent (&sec_none, TRUE, FALSE, NULL)))
+ debug_printf ("Creating event failed, %E");
+ size_t block_fit = !block_size ? len : rounddown(len, block_size);
+ if (block_fit)
+ {
+ debug_printf ("read %lu bytes from tape (rest %lu)",
+ block_fit, len - block_fit);
+ ret = mt.drive (driveno ())->read (get_handle (), &ov, buf,
+ block_fit);
+ if (ret)
+ __seterrno_from_win_error (ret);
+ else if (block_fit)
+ {
+ len -= block_fit;
+ bytes_read += block_fit;
+ buf += block_fit;
+ /* Only one block in each read call, please. */
+ if (!block_size)
+ len = 0;
+ }
+ else {
+ len = 0;
+ if (bytes_read)
+ lastblk_to_read (true);
+ }
+ }
+ if (!ret && len > 0)
+ {
+ debug_printf ("read %lu bytes from tape (one block)", block_size);
+ ret = mt.drive (driveno ())->read (get_handle (), &ov, devbuf,
+ block_size);
+ if (ret)
+ __seterrno_from_win_error (ret);
+ else if (block_size)
+ {
+ devbufstart = len;
+ devbufend = block_size;
+ bytes_read += len;
+ memcpy (buf, devbuf, len);
+ }
+ else if (bytes_read)
+ lastblk_to_read (true);
+ }
+ }
+ }
+ else
+ {
+ if (!ov.hEvent
+ && !(ov.hEvent = CreateEvent (&sec_none, TRUE, FALSE, NULL)))
+ debug_printf ("Creating event failed, %E");
+ bytes_read = ulen;
+ ret = mt.drive (driveno ())->read (get_handle (), &ov, ptr, bytes_read);
+ }
+ ulen = (ret ? (size_t) -1 : bytes_read);
+ unlock ();
+}
+
+ssize_t
+fhandler_dev_tape::raw_write (const void *ptr, size_t len)
+{
+ if (!_lock (true))
+ return -1;
+ if (!ov.hEvent && !(ov.hEvent = CreateEvent (&sec_none, TRUE, FALSE, NULL)))
+ debug_printf ("Creating event failed, %E");
+ int ret = mt.drive (driveno ())->write (get_handle (), &ov, ptr, len);
+ if (ret)
+ __seterrno_from_win_error (ret);
+ return unlock (ret ? -1 : (int) len);
+}
+
+off_t
+fhandler_dev_tape::lseek (off_t offset, int whence)
+{
+#if 1
+ /* On Linux lseek on tapes is a no-op. For now, let's keep the old code
+ intact but commented out, should incompatibilities arise. */
+ return 0;
+#else
+ struct mtop op;
+ struct mtpos pos;
+ DWORD block_size;
+ off_t ret = ILLEGAL_SEEK;
+
+ lock (ILLEGAL_SEEK);
+
+ debug_printf ("lseek (%s, %D, %d)", get_name (), offset, whence);
+
+ block_size = mt.drive (driveno ())->mp ()->BlockSize;
+ if (block_size == 0)
+ {
+ set_errno (EIO);
+ goto out;
+ }
+
+ if (ioctl (MTIOCPOS, &pos))
+ goto out;
+
+ switch (whence)
+ {
+ case SEEK_END:
+ op.mt_op = MTFSF;
+ op.mt_count = 1;
+ if (ioctl (MTIOCTOP, &op))
+ goto out;
+ break;
+ case SEEK_SET:
+ if (whence == SEEK_SET && offset < 0)
+ {
+ set_errno (EINVAL);
+ goto out;
+ }
+ break;
+ case SEEK_CUR:
+ break;
+ default:
+ set_errno (EINVAL);
+ goto out;
+ }
+
+ op.mt_op = MTFSR;
+ op.mt_count = offset / block_size
+ - (whence == SEEK_SET ? pos.mt_blkno : 0);
+
+ if (op.mt_count < 0)
+ {
+ op.mt_op = MTBSR;
+ op.mt_count = -op.mt_count;
+ }
+
+ if (ioctl (MTIOCTOP, &op) || ioctl (MTIOCPOS, &pos))
+ goto out;
+
+ ret = pos.mt_blkno * block_size;
+
+out:
+ return unlock (ret);
+#endif
+}
+
+int
+fhandler_dev_tape::fstat (struct stat *buf)
+{
+ int ret;
+
+ if (driveno () >= MAX_DRIVE_NUM)
+ {
+ set_errno (ENOENT);
+ return -1;
+ }
+ if (!(ret = fhandler_base::fstat (buf)))
+ buf->st_blocks = 0;
+ return ret;
+}
+
+int
+fhandler_dev_tape::dup (fhandler_base *child, int flags)
+{
+ lock (-1);
+ fhandler_dev_tape *fh = (fhandler_dev_tape *) child;
+ if (!DuplicateHandle (GetCurrentProcess (), mt_mtx,
+ GetCurrentProcess (), &fh->mt_mtx,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ debug_printf ("dup(%s) failed, mutex handle %p, %E",
+ get_name (), mt_mtx);
+ __seterrno ();
+ return unlock (-1);
+ }
+ fh->ov.hEvent = NULL;
+ if (ov.hEvent &&
+ !DuplicateHandle (GetCurrentProcess (), ov.hEvent,
+ GetCurrentProcess (), &fh->ov.hEvent,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ debug_printf ("dup(%s) failed, event handle %p, %E",
+ get_name (), ov.hEvent);
+ __seterrno ();
+ return unlock (-1);
+ }
+ return unlock (fhandler_dev_raw::dup (child, flags));
+}
+
+void
+fhandler_dev_tape::fixup_after_fork (HANDLE parent)
+{
+ fhandler_dev_raw::fixup_after_fork (parent);
+ fork_fixup (parent, mt_mtx, "mt_mtx");
+ if (ov.hEvent)
+ fork_fixup (parent, ov.hEvent, "ov.hEvent");
+}
+
+void
+fhandler_dev_tape::set_close_on_exec (bool val)
+{
+ fhandler_dev_raw::set_close_on_exec (val);
+ set_no_inheritance (mt_mtx, val);
+ if (ov.hEvent)
+ set_no_inheritance (ov.hEvent, val);
+}
+
+int
+fhandler_dev_tape::ioctl (unsigned int cmd, void *buf)
+{
+ int ret = 0;
+ lock (-1);
+ if (cmd == MTIOCTOP || cmd == MTIOCGET || cmd == MTIOCPOS)
+ {
+ ret = mt.drive (driveno ())->ioctl (get_handle (), cmd, buf);
+ if (ret)
+ __seterrno_from_win_error (ret);
+ return unlock (ret ? -1 : 0);
+ }
+ return unlock (fhandler_dev_raw::ioctl (cmd, buf));
+}
diff --git a/winsup/cygwin/fhandler/termios.cc b/winsup/cygwin/fhandler/termios.cc
new file mode 100644
index 000000000..328c73fcd
--- /dev/null
+++ b/winsup/cygwin/fhandler/termios.cc
@@ -0,0 +1,720 @@
+/* fhandler_termios.cc
+
+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 <stdlib.h>
+#include <ctype.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "sigproc.h"
+#include "pinfo.h"
+#include "tty.h"
+#include "cygtls.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "child_info.h"
+#include "ntdll.h"
+
+/* Wait time for some treminal mutexes. This is set to 0 when the
+ process calls CreateProcess() with DEBUG_PROCESS flag, because
+ the debuggie may be suspended while it grabs the mutex. Without
+ this, GDB may cause deadlock in console or pty I/O. */
+DWORD NO_COPY mutex_timeout = INFINITE;
+
+/* Common functions shared by tty/console */
+
+void
+fhandler_termios::tcinit (bool is_pty_master)
+{
+ /* Initial termios values */
+
+ if (is_pty_master || !tc ()->initialized ())
+ {
+ tc ()->ti.c_iflag = BRKINT | ICRNL | IXON | IUTF8;
+ tc ()->ti.c_oflag = OPOST | ONLCR;
+ tc ()->ti.c_cflag = B38400 | CS8 | CREAD;
+ tc ()->ti.c_lflag = ISIG | ICANON | ECHO | IEXTEN
+ | ECHOE | ECHOK | ECHOCTL | ECHOKE;
+
+ tc ()->ti.c_cc[VDISCARD] = CFLUSH;
+ tc ()->ti.c_cc[VEOL] = CEOL;
+ tc ()->ti.c_cc[VEOL2] = CEOL2;
+ tc ()->ti.c_cc[VEOF] = CEOF;
+ tc ()->ti.c_cc[VERASE] = CERASE;
+ tc ()->ti.c_cc[VINTR] = CINTR;
+ tc ()->ti.c_cc[VKILL] = CKILL;
+ tc ()->ti.c_cc[VLNEXT] = CLNEXT;
+ tc ()->ti.c_cc[VMIN] = CMIN;
+ tc ()->ti.c_cc[VQUIT] = CQUIT;
+ tc ()->ti.c_cc[VREPRINT] = CRPRNT;
+ tc ()->ti.c_cc[VSTART] = CSTART;
+ tc ()->ti.c_cc[VSTOP] = CSTOP;
+ tc ()->ti.c_cc[VSUSP] = CSUSP;
+ tc ()->ti.c_cc[VSWTC] = CSWTCH;
+ tc ()->ti.c_cc[VTIME] = CTIME;
+ tc ()->ti.c_cc[VWERASE] = CWERASE;
+
+ tc ()->ti.c_ispeed = tc ()->ti.c_ospeed = B38400;
+ tc ()->pgid = is_pty_master ? 0 : myself->pgid;
+ tc ()->initialized (true);
+ }
+}
+
+int
+fhandler_termios::tcsetpgrp (const pid_t pgid)
+{
+ termios_printf ("%s, pgid %d, sid %d, tsid %d", tc ()->ttyname (), pgid,
+ myself->sid, tc ()->getsid ());
+ if (myself->sid != tc ()->getsid ())
+ {
+ set_errno (EPERM);
+ return -1;
+ }
+ else if (pgid < 0)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ int res;
+ while (1)
+ {
+ res = bg_check (-SIGTTOU);
+
+ switch (res)
+ {
+ case bg_ok:
+ tc ()->setpgid (pgid);
+ if (tc ()->is_console && (strace.active () || !being_debugged ()))
+ tc ()->kill_pgrp (__SIGSETPGRP);
+ res = 0;
+ break;
+ case bg_signalled:
+ if (_my_tls.call_signal_handler ())
+ continue;
+ set_errno (EINTR);
+ fallthrough;
+ default:
+ res = -1;
+ break;
+ }
+ break;
+ }
+ return res;
+}
+
+int
+fhandler_termios::tcgetpgrp ()
+{
+ if (myself->ctty > 0 && myself->ctty == tc ()->ntty)
+ return tc ()->pgid;
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_pty_master::tcgetpgrp ()
+{
+ return tc ()->pgid;
+}
+
+static inline bool
+is_flush_sig (int sig)
+{
+ return sig == SIGINT || sig == SIGQUIT || sig == SIGTSTP;
+}
+
+void
+tty_min::kill_pgrp (int sig, pid_t target_pgid)
+{
+ target_pgid = target_pgid ?: pgid;
+ bool killself = false;
+ if (is_flush_sig (sig) && cygheap->ctty)
+ cygheap->ctty->sigflush ();
+ winpids pids ((DWORD) PID_MAP_RW);
+ siginfo_t si = {0};
+ si.si_signo = sig;
+ si.si_code = SI_KERNEL;
+ if (sig > 0 && sig < _NSIG)
+ last_sig = sig;
+
+ for (unsigned i = 0; i < pids.npids; i++)
+ {
+ _pinfo *p = pids[i];
+ if (!p || !p->exists () || p->ctty != ntty || p->pgid != target_pgid)
+ continue;
+ if (p->process_state & PID_NOTCYGWIN)
+ continue; /* Do not send signal to non-cygwin process to prevent
+ cmd.exe from crash. */
+ if (p == myself)
+ killself = sig != __SIGSETPGRP && !exit_state;
+ else
+ sig_send (p, si);
+ }
+ if (killself)
+ sig_send (myself, si);
+}
+
+int
+tty_min::is_orphaned_process_group (int pgid)
+{
+ /* An orphaned process group is a process group in which the parent
+ of every member is either itself a member of the group or is not
+ a member of the group's session. */
+ termios_printf ("checking pgid %d, my sid %d, my parent %d", pgid, myself->sid, myself->ppid);
+ winpids pids ((DWORD) 0);
+ for (unsigned i = 0; i < pids.npids; i++)
+ {
+ _pinfo *p = pids[i];
+ termios_printf ("checking pid %d - has pgid %d\n", p->pid, p->pgid);
+ if (!p || !p->exists () || p->pgid != pgid)
+ continue;
+ pinfo ppid (p->ppid);
+ if (!ppid)
+ continue;
+ termios_printf ("ppid->pgid %d, ppid->sid %d", ppid->pgid, ppid->sid);
+ if (ppid->pgid != pgid && ppid->sid == myself->sid)
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ bg_check: check that this process is either in the foreground process group,
+ or if the terminal operation is allowed for processes which are in a
+ background process group.
+
+ If the operation is not permitted by the terminal configuration for processes
+ which are a member of a background process group, return an error or raise a
+ signal as appropriate.
+
+ This handles the following terminal operations:
+
+ write: sig = SIGTTOU
+ read: sig = SIGTTIN
+ change terminal settings: sig = -SIGTTOU
+ (tcsetattr, tcsetpgrp, etc.)
+ peek (poll, select): sig = SIGTTIN, dontsignal = TRUE
+*/
+bg_check_types
+fhandler_termios::bg_check (int sig, bool dontsignal)
+{
+ /* Ignore errors:
+ - this process isn't in a process group
+ - tty is invalid
+
+ Everything is ok if:
+ - this process is in the foreground process group, or
+ - this tty is not the controlling tty for this process (???), or
+ - writing, when TOSTOP TTY mode is not set on this tty
+ */
+ if (!myself->pgid || !tc () || tc ()->getpgid () == myself->pgid ||
+ myself->ctty != tc ()->ntty ||
+ ((sig == SIGTTOU) && !(tc ()->ti.c_lflag & TOSTOP)))
+ return bg_ok;
+
+ /* sig -SIGTTOU is used to indicate a change to terminal settings, where
+ TOSTOP TTY mode isn't considered when determining if we need to send a
+ signal. */
+ if (sig < 0)
+ sig = -sig;
+
+ termios_printf ("%s, bg I/O pgid %d, tpgid %d, myctty %s", tc ()->ttyname (),
+ myself->pgid, tc ()->getpgid (), myctty ());
+
+ if (tc ()->getsid () == 0)
+ {
+ /* The pty has been closed by the master. Return an EOF
+ indication. FIXME: There is nothing to stop somebody
+ from reallocating this pty. I think this is the case
+ which is handled by unlockpt on a Unix system. */
+ termios_printf ("closed by master");
+ return bg_eof;
+ }
+
+ threadlist_t *tl_entry;
+ tl_entry = cygheap->find_tls (_main_tls);
+ int sigs_ignored =
+ ((void *) global_sigs[sig].sa_handler == (void *) SIG_IGN) ||
+ (_main_tls->sigmask & SIGTOMASK (sig));
+ cygheap->unlock_tls (tl_entry);
+
+ /* If the process is blocking or ignoring SIGTT*, then signals are not sent
+ and background IO is allowed */
+ if (sigs_ignored)
+ return bg_ok; /* Just allow the IO */
+ /* If the process group of the process is orphaned, return EIO */
+ else if (tc ()->is_orphaned_process_group (myself->pgid))
+ {
+ termios_printf ("process group is orphaned");
+ set_errno (EIO); /* This is an IO error */
+ return bg_error;
+ }
+ /* Otherwise, if signalling is desired, the signal is sent to all processes in
+ the process group */
+ else if (!dontsignal)
+ {
+ /* Don't raise a SIGTT* signal if we have already been
+ interrupted by another signal. */
+ if (cygwait ((DWORD) 0) != WAIT_SIGNALED)
+ {
+ siginfo_t si = {0};
+ si.si_signo = sig;
+ si.si_code = SI_KERNEL;
+ kill_pgrp (myself->pgid, si);
+ }
+ return bg_signalled;
+ }
+ return bg_ok;
+}
+
+#define set_input_done(x) input_done = input_done || (x)
+
+int
+fhandler_termios::eat_readahead (int n)
+{
+ int oralen = ralen ();
+ if (n < 0)
+ n = ralen () - raixget ();
+ if (n > 0 && ralen () > raixget ())
+ {
+ if ((int) (ralen () -= n) < (int) raixget ())
+ ralen () = raixget ();
+ /* If IUTF8 is set, the terminal is in UTF-8 mode. If so, we erase
+ a complete UTF-8 multibyte sequence on VERASE/VWERASE. Otherwise,
+ if we only erase a single byte, invalid unicode chars are left in
+ the input. */
+ if (tc ()->ti.c_iflag & IUTF8)
+ while (ralen () > raixget () &&
+ ((unsigned char) rabuf ()[ralen ()] & 0xc0) == 0x80)
+ --ralen ();
+ }
+ oralen = oralen - ralen ();
+ if (raixget () >= ralen ())
+ raixget () = raixput () = ralen () = 0;
+ else if (raixput () > ralen ())
+ raixput () = ralen ();
+
+ return oralen;
+}
+
+inline void
+fhandler_termios::echo_erase (int force)
+{
+ if (force || tc ()->ti.c_lflag & ECHO)
+ doecho ("\b \b", 3);
+}
+
+/* The basic policy is as follows:
+ - The signal generated by key press will be sent only to cygwin process.
+ - For non-cygwin process, CTRL_C_EVENT will be sent on Ctrl-C. */
+/* The inferior of GDB is an exception. GDB does not support to hook signal
+ even if the inferior is a cygwin app. As a result, inferior cannot be
+ continued after interruption by Ctrl-C if SIGINT was sent. Therefore,
+ CTRL_C_EVENT rather than SIGINT is sent to the inferior of GDB. */
+fhandler_termios::process_sig_state
+fhandler_termios::process_sigs (char c, tty* ttyp, fhandler_termios *fh)
+{
+ termios &ti = ttyp->ti;
+ pid_t pgid = ttyp->pgid;
+
+ /* The name *_nat stands for 'native' which means non-cygwin apps. */
+ bool ctrl_c_event_sent = false;
+ bool need_discard_input = false;
+ bool pg_with_nat = false; /* The process group has non-cygwin processes. */
+ bool need_send_sig = false; /* There is process which need the signal. */
+ bool nat_shell = false; /* The shell seems to be a non-cygwin process. */
+ bool cyg_reader = false; /* Cygwin process is reading the tty. */
+ bool with_debugger = false; /* GDB is debugging cygwin app. */
+ bool with_debugger_nat = false; /* GDB is debugging non-cygwin app. */
+
+ winpids pids ((DWORD) 0);
+ for (unsigned i = 0; i < pids.npids; i++)
+ {
+ _pinfo *p = pids[i];
+ /* PID_NOTCYGWIN: check this for non-cygwin process.
+ exec_dwProcessId == dwProcessId:
+ check this for GDB with non-cygwin inferior in pty
+ without pcon enabled. In this case, the inferior is not
+ cygwin process list. This condition is set true as
+ a marker for GDB with non-cygwin inferior in pty code.
+ !PID_CYGPARENT: check this for GDB with cygwin inferior or
+ cygwin apps started from non-cygwin shell. */
+ if (c == '\003' && p && p->ctty == ttyp->ntty && p->pgid == pgid
+ && ((p->process_state & PID_NOTCYGWIN)
+ || ((p->exec_dwProcessId == p->dwProcessId)
+ && ttyp->pty_input_state_eq (tty::to_nat))
+ || !(p->process_state & PID_CYGPARENT)))
+ {
+ /* Ctrl-C event will be sent only to the processes attaching
+ to the same console. Therefore, attach to the console to
+ which the target process is attaching before sending the
+ CTRL_C_EVENT. After sending the event, reattach to the
+ console to which the process was previously attached. */
+ DWORD resume_pid = 0;
+ if (fh && !fh->is_console ())
+ resume_pid =
+ fhandler_pty_common::attach_console_temporarily
+ (p->dwProcessId, fh->get_helper_pid ());
+ if (fh && p == myself && being_debugged ())
+ { /* Avoid deadlock in gdb on console. */
+ fh->tcflush(TCIFLUSH);
+ fh->release_input_mutex_if_necessary ();
+ }
+ /* CTRL_C_EVENT does not work for the process started with
+ CREATE_NEW_PROCESS_GROUP flag, so send CTRL_BREAK_EVENT
+ instead. */
+ if (p->process_state & PID_NEW_PG)
+ GenerateConsoleCtrlEvent (CTRL_BREAK_EVENT, p->dwProcessId);
+ else if ((!fh || fh->need_send_ctrl_c_event ()
+ || p->exec_dwProcessId == p->dwProcessId)
+ && !ctrl_c_event_sent)
+ {
+ GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0);
+ ctrl_c_event_sent = true;
+ }
+ if (fh && !fh->is_console ())
+ {
+ /* If a process on pseudo console is killed by Ctrl-C,
+ this process may take over the ownership of the
+ pseudo console because this process attached to it
+ before sending CTRL_C_EVENT. In this case, closing
+ pseudo console is necessary. */
+ fhandler_pty_slave::release_ownership_of_nat_pipe (ttyp, fh);
+ fhandler_pty_common::resume_from_temporarily_attach (resume_pid);
+ }
+ need_discard_input = true;
+ }
+ if (p && p->ctty == ttyp->ntty && p->pgid == pgid)
+ {
+ if (p->process_state & PID_NOTCYGWIN)
+ pg_with_nat = true; /* The process group has non-cygwin process */
+ if (!(p->process_state & PID_NOTCYGWIN))
+ need_send_sig = true; /* Process which needs signal exists */
+ if (!p->cygstarted)
+ nat_shell = true; /* The shell seems to a non-cygwin shell */
+ if (p->process_state & PID_TTYIN)
+ cyg_reader = true; /* Theh process is reading the tty */
+ if (!p->cygstarted && !(p->process_state & PID_NOTCYGWIN)
+ && (p->process_state & PID_DEBUGGED))
+ with_debugger = true; /* inferior is cygwin app */
+ if (!(p->process_state & PID_NOTCYGWIN)
+ && (p->exec_dwProcessId == p->dwProcessId) /* Check marker */
+ && ttyp->pty_input_state_eq (tty::to_nat)
+ && p->pid == pgid)
+ with_debugger_nat = true; /* inferior is non-cygwin app */
+ }
+ }
+ if ((with_debugger || with_debugger_nat) && need_discard_input)
+ {
+ if (!(ti.c_lflag & NOFLSH) && fh)
+ {
+ fh->eat_readahead (-1);
+ fh->discard_input ();
+ }
+ ti.c_lflag &= ~FLUSHO;
+ return done_with_debugger;
+ }
+ if (with_debugger_nat)
+ return not_signalled; /* Do not process slgnal keys further.
+ The non-cygwin inferior of GDB cannot receive
+ the signals. */
+ /* Send SIGQUIT to non-cygwin process. Non-cygwin app will not be alerted
+ by kill_pgrp(), however, QUIT key should quit the non-cygwin app
+ if it is started along with cygwin process from cygwin shell. */
+ if ((ti.c_lflag & ISIG) && CCEQ (ti.c_cc[VQUIT], c)
+ && pg_with_nat && need_send_sig && !nat_shell)
+ {
+ for (unsigned i = 0; i < pids.npids; i++)
+ {
+ _pinfo *p = pids[i];
+ if (p && p->ctty == ttyp->ntty && p->pgid == pgid
+ && (p->process_state & PID_NOTCYGWIN))
+ sig_send (p, SIGQUIT);
+ }
+ }
+ if ((ti.c_lflag & ISIG) && need_send_sig)
+ {
+ int sig;
+ if (CCEQ (ti.c_cc[VINTR], c))
+ sig = SIGINT;
+ else if (CCEQ (ti.c_cc[VQUIT], c))
+ sig = SIGQUIT;
+ else if (pg_with_nat) /* If the process group has a non-cygwin process,
+ it cannot be suspended correctly. Therefore,
+ do not send SIGTSTP. */
+ goto not_a_sig;
+ else if (CCEQ (ti.c_cc[VSUSP], c))
+ sig = SIGTSTP;
+ else
+ goto not_a_sig;
+
+ termios_printf ("got interrupt %d, sending signal %d", c, sig);
+ if (!(ti.c_lflag & NOFLSH) && fh)
+ {
+ fh->eat_readahead (-1);
+ fh->discard_input ();
+ }
+ if (fh)
+ fh->release_input_mutex_if_necessary ();
+ ttyp->kill_pgrp (sig, pgid);
+ if (fh)
+ fh->acquire_input_mutex_if_necessary (mutex_timeout);
+ ti.c_lflag &= ~FLUSHO;
+ return signalled;
+ }
+not_a_sig:
+ if ((ti.c_lflag & ISIG) && need_discard_input)
+ {
+ if (!(ti.c_lflag & NOFLSH) && fh)
+ {
+ fh->eat_readahead (-1);
+ fh->discard_input ();
+ }
+ ti.c_lflag &= ~FLUSHO;
+ return not_signalled_but_done;
+ }
+ bool to_nat = !cyg_reader && pg_with_nat;
+ return to_nat ? not_signalled_with_nat_reader : not_signalled;
+}
+
+bool
+fhandler_termios::process_stop_start (char c, tty *ttyp)
+{
+ termios &ti = ttyp->ti;
+ if (ti.c_iflag & IXON)
+ {
+ if (CCEQ (ti.c_cc[VSTOP], c))
+ {
+ ttyp->output_stopped = true;
+ return true;
+ }
+ else if (CCEQ (ti.c_cc[VSTART], c))
+ {
+restart_output:
+ ttyp->output_stopped = false;
+ return true;
+ }
+ else if ((ti.c_iflag & IXANY) && ttyp->output_stopped)
+ goto restart_output;
+ }
+ if ((ti.c_lflag & ICANON) && (ti.c_lflag & IEXTEN)
+ && CCEQ (ti.c_cc[VDISCARD], c))
+ {
+ ti.c_lflag ^= FLUSHO;
+ return true;
+ }
+ return false;
+}
+
+line_edit_status
+fhandler_termios::line_edit (const char *rptr, size_t nread, termios& ti,
+ ssize_t *bytes_read)
+{
+ line_edit_status ret = line_edit_ok;
+ char c;
+ int input_done = 0;
+ bool sawsig = false;
+ int iscanon = ti.c_lflag & ICANON;
+ size_t read_cnt = 0;
+
+ while (read_cnt < nread)
+ {
+ c = *rptr++;
+ read_cnt++;
+
+ paranoid_printf ("char %0o", c);
+
+ if (ti.c_iflag & ISTRIP)
+ c &= 0x7f;
+ bool disable_eof_key = false;
+ switch (process_sigs (c, get_ttyp (), this))
+ {
+ case signalled:
+ sawsig = true;
+ fallthrough;
+ case not_signalled_but_done:
+ case done_with_debugger:
+ get_ttyp ()->output_stopped = false;
+ continue;
+ case not_signalled_with_nat_reader:
+ disable_eof_key = true;
+ break;
+ default: /* Not signalled */
+ break;
+ }
+ if (process_stop_start (c, get_ttyp ()))
+ continue;
+ /* Check for special chars */
+ if (c == '\r')
+ {
+ if (ti.c_iflag & IGNCR)
+ continue;
+ if (ti.c_iflag & ICRNL)
+ {
+ c = '\n';
+ set_input_done (iscanon);
+ }
+ }
+ else if (c == '\n')
+ {
+ if (ti.c_iflag & INLCR)
+ c = '\r';
+ else
+ set_input_done (iscanon);
+ }
+
+ if (!iscanon)
+ /* nothing */;
+ else if (CCEQ (ti.c_cc[VERASE], c))
+ {
+ if (eat_readahead (1))
+ echo_erase ();
+ continue;
+ }
+ else if (CCEQ (ti.c_cc[VWERASE], c))
+ {
+ int ch;
+ do
+ if (!eat_readahead (1))
+ break;
+ else
+ echo_erase ();
+ while ((ch = peek_readahead (1)) >= 0 && !isspace (ch));
+ continue;
+ }
+ else if (CCEQ (ti.c_cc[VKILL], c))
+ {
+ int nchars = eat_readahead (-1);
+ if (ti.c_lflag & ECHO)
+ while (nchars--)
+ echo_erase (1);
+ continue;
+ }
+ else if (CCEQ (ti.c_cc[VREPRINT], c))
+ {
+ if (ti.c_lflag & ECHO)
+ {
+ doecho ("\n\r", 2);
+ doecho (rabuf (), ralen ());
+ }
+ continue;
+ }
+ else if (CCEQ (ti.c_cc[VEOF], c) && !disable_eof_key)
+ {
+ termios_printf ("EOF");
+ accept_input ();
+ ret = line_edit_input_done;
+ continue;
+ }
+ else if (CCEQ (ti.c_cc[VEOL], c) ||
+ CCEQ (ti.c_cc[VEOL2], c) ||
+ c == '\n')
+ {
+ set_input_done (1);
+ termios_printf ("EOL");
+ }
+
+ if (ti.c_iflag & IUCLC && isupper (c))
+ c = cyg_tolower (c);
+
+ put_readahead (c);
+ if (ti.c_lflag & ECHO)
+ doecho (&c, 1);
+ /* Write in chunks of 32 bytes to reduce the number of WriteFile calls
+ in non-canonical mode. */
+ if ((!iscanon && ralen () >= 32) || input_done)
+ {
+ int status = accept_input ();
+ if (status != 1)
+ {
+ ret = status ? line_edit_error : line_edit_pipe_full;
+ break;
+ }
+ ret = line_edit_input_done;
+ input_done = 0;
+ }
+ }
+
+ /* If we didn't write all bytes in non-canonical mode, write them now. */
+ if ((input_done || !iscanon) && ralen () > 0 && ret != line_edit_error)
+ {
+ int status;
+ int retry_count = 3;
+ while ((status = accept_input ()) != 1 &&
+ ralen () > 0 && --retry_count > 0)
+ cygwait ((DWORD) 10);
+ if (status != 1)
+ ret = status ? line_edit_error : line_edit_pipe_full;
+ else
+ ret = line_edit_input_done;
+ }
+
+ if (bytes_read)
+ *bytes_read = read_cnt;
+
+ if (sawsig)
+ ret = line_edit_signalled;
+
+ return ret;
+}
+
+off_t
+fhandler_termios::lseek (off_t, int)
+{
+ set_errno (ESPIPE);
+ return -1;
+}
+
+void
+fhandler_termios::sigflush ()
+{
+ /* FIXME: Checking get_ttyp() for NULL is not right since it should not
+ be NULL while this is alive. However, we can conceivably close a
+ ctty while exiting and that will zero this. */
+ if ((!have_execed || have_execed_cygwin) && tc ()
+ && (tc ()->getpgid () == myself->pgid)
+ && !(tc ()->ti.c_lflag & NOFLSH))
+ tcflush (TCIFLUSH);
+}
+
+pid_t
+fhandler_termios::tcgetsid ()
+{
+ if (myself->ctty > 0 && myself->ctty == tc ()->ntty)
+ return tc ()->getsid ();
+ set_errno (ENOTTY);
+ return -1;
+}
+
+int
+fhandler_termios::ioctl (int cmd, void *varg)
+{
+ if (cmd != TIOCSCTTY)
+ return 1; /* Not handled by this function */
+
+ int arg = (int) (intptr_t) varg;
+
+ if (arg != 0 && arg != 1)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ termios_printf ("myself->ctty %d, myself->sid %d, myself->pid %d, arg %d, tc()->getsid () %d\n",
+ myself->ctty, myself->sid, myself->pid, arg, tc ()->getsid ());
+ if (myself->ctty > 0 || myself->sid != myself->pid || (!arg && tc ()->getsid () > 0))
+ {
+ set_errno (EPERM);
+ return -1;
+ }
+
+ myself->ctty = -1;
+ myself->set_ctty (this, 0);
+ return 0;
+}
diff --git a/winsup/cygwin/fhandler/timerfd.cc b/winsup/cygwin/fhandler/timerfd.cc
new file mode 100644
index 000000000..9269494db
--- /dev/null
+++ b/winsup/cygwin/fhandler/timerfd.cc
@@ -0,0 +1,338 @@
+/* fhandler_timerfd.cc: fhandler for timerfd, public timerfd API
+
+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 "path.h"
+#include "fhandler.h"
+#include "pinfo.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "timerfd.h"
+#include <sys/timerfd.h>
+#include <cygwin/signal.h>
+
+fhandler_timerfd::fhandler_timerfd () :
+ fhandler_base ()
+{
+}
+
+char *
+fhandler_timerfd::get_proc_fd_name (char *buf)
+{
+ return strcpy (buf, "anon_inode:[timerfd]");
+}
+
+/* The timers connected to a descriptor are stored on the cygheap
+ together with their fhandler. */
+
+int
+fhandler_timerfd::timerfd (clockid_t clock_id, int flags)
+{
+ timerfd_tracker *tfd = (timerfd_tracker *)
+ ccalloc (HEAP_FHANDLER, 1, sizeof (timerfd_tracker));
+ if (!tfd)
+ {
+ set_errno (ENOMEM);
+ return -1;
+ }
+ new (tfd) timerfd_tracker ();
+ int ret = tfd->create (clock_id);
+ if (ret < 0)
+ {
+ cfree (tfd);
+ set_errno (-ret);
+ return -1;
+ }
+ if (flags & TFD_NONBLOCK)
+ set_nonblocking (true);
+ if (flags & TFD_CLOEXEC)
+ set_close_on_exec (true);
+ nohandle (true);
+ set_unique_id ();
+ set_ino (get_unique_id ());
+ set_flags (O_RDWR | O_BINARY);
+ timerid = (timer_t) tfd;
+ return 0;
+}
+
+int
+fhandler_timerfd::settime (int flags, const struct itimerspec *new_value,
+ struct itimerspec *old_value)
+{
+ int ret = -1;
+
+ __try
+ {
+ timerfd_tracker *tfd = (timerfd_tracker *) timerid;
+ ret = tfd->settime (flags, new_value, old_value);
+ if (ret < 0)
+ {
+ set_errno (-ret);
+ ret = -1;
+ }
+ }
+ __except (EFAULT) {}
+ __endtry
+ return ret;
+}
+
+int
+fhandler_timerfd::gettime (struct itimerspec *ovalue)
+{
+ int ret = -1;
+
+ __try
+ {
+ timerfd_tracker *tfd = (timerfd_tracker *) timerid;
+ ret = tfd->gettime (ovalue);
+ if (ret < 0)
+ {
+ set_errno (-ret);
+ ret = -1;
+ }
+ }
+ __except (EFAULT) {}
+ __endtry
+ return ret;
+}
+
+int
+fhandler_timerfd::fstat (struct stat *buf)
+{
+ int ret = fhandler_base::fstat (buf);
+ if (!ret)
+ {
+ buf->st_mode = S_IRUSR | S_IWUSR;
+ buf->st_dev = FH_TIMERFD;
+ buf->st_ino = get_unique_id ();
+ }
+ return ret;
+}
+
+void
+fhandler_timerfd::read (void *ptr, size_t& len)
+{
+ if (len < sizeof (LONG64))
+ {
+ set_errno (EINVAL);
+ len = (size_t) -1;
+ return;
+ }
+
+ __try
+ {
+ timerfd_tracker *tfd = (timerfd_tracker *) timerid;
+ LONG64 ret = tfd->wait (is_nonblocking ());
+ if (ret < 0)
+ {
+ set_errno (-ret);
+ __leave;
+ }
+ *(PLONG64) ptr = ret;
+ len = sizeof (LONG64);
+ return;
+ }
+ __except (EFAULT) {}
+ __endtry
+ len = (size_t) -1;
+ return;
+}
+
+ssize_t
+fhandler_timerfd::write (const void *, size_t)
+{
+ set_errno (EINVAL);
+ return -1;
+}
+
+HANDLE
+fhandler_timerfd::get_timerfd_handle ()
+{
+ __try
+ {
+ timerfd_tracker *tfd = (timerfd_tracker *) timerid;
+ return tfd->get_timerfd_handle ();
+ }
+ __except (EFAULT) {}
+ __endtry
+ return NULL;
+}
+
+int
+fhandler_timerfd::dup (fhandler_base *child, int flags)
+{
+ int ret = fhandler_base::dup (child, flags);
+
+ if (!ret)
+ {
+ fhandler_timerfd *fhc = (fhandler_timerfd *) child;
+ __try
+ {
+ timerfd_tracker *tfd = (timerfd_tracker *) fhc->timerid;
+ tfd->dup ();
+ ret = 0;
+ }
+ __except (EFAULT) {}
+ __endtry
+ }
+ return ret;
+}
+
+int
+fhandler_timerfd::ioctl (unsigned int cmd, void *p)
+{
+ int ret = -1;
+ uint64_t exp_cnt;
+
+ switch (cmd)
+ {
+ case TFD_IOC_SET_TICKS:
+ __try
+ {
+ timerfd_tracker *tfd = (timerfd_tracker *) timerid;
+
+ exp_cnt = *(uint64_t *) p;
+ ret = tfd->ioctl_set_ticks (exp_cnt);
+ if (ret < 0)
+ set_errno (-ret);
+ }
+ __except (EFAULT) {}
+ __endtry
+ break;
+ default:
+ ret = fhandler_base::ioctl (cmd, p);
+ break;
+ }
+ syscall_printf ("%d = ioctl_timerfd(%x, %p)", ret, cmd, p);
+ return ret;
+}
+
+void
+fhandler_timerfd::fixup_after_fork (HANDLE)
+{
+ __try
+ {
+ timerfd_tracker *tfd = (timerfd_tracker *) timerid;
+ tfd->fixup_after_fork ();
+ }
+ __except (EFAULT) {}
+ __endtry
+}
+
+void
+fhandler_timerfd::fixup_after_exec ()
+{
+ __try
+ {
+ timerfd_tracker *tfd = (timerfd_tracker *) timerid;
+ tfd->init_fixup_after_fork_exec ();
+ if (close_on_exec ())
+ timerfd_tracker::dtor (tfd);
+ else
+ tfd->fixup_after_exec ();
+ }
+ __except (EFAULT) {}
+ __endtry
+}
+
+int
+fhandler_timerfd::close ()
+{
+ int ret = -1;
+
+ __try
+ {
+ timerfd_tracker *tfd = (timerfd_tracker *) timerid;
+ timerfd_tracker::dtor (tfd);
+ ret = 0;
+ }
+ __except (EFAULT) {}
+ __endtry
+ return ret;
+}
+
+extern "C" int
+timerfd_create (clockid_t clock_id, int flags)
+{
+ int ret = -1;
+ fhandler_timerfd *fh;
+
+ debug_printf ("timerfd_create (%lu, %y)", clock_id, flags);
+
+ if (clock_id != CLOCK_REALTIME
+ && clock_id != CLOCK_MONOTONIC
+ && clock_id != CLOCK_BOOTTIME)
+ {
+ set_errno (EINVAL);
+ goto done;
+ }
+ if ((flags & ~(TFD_NONBLOCK | TFD_CLOEXEC)) != 0)
+ {
+ set_errno (EINVAL);
+ goto done;
+ }
+
+ {
+ /* Create new timerfd descriptor. */
+ cygheap_fdnew fd;
+
+ if (fd < 0)
+ goto done;
+ fh = (fhandler_timerfd *) build_fh_dev (*timerfd_dev);
+ if (fh && fh->timerfd (clock_id, flags) == 0)
+ {
+ fd = fh;
+ if (fd <= 2)
+ set_std_handle (fd);
+ ret = fd;
+ }
+ else
+ delete fh;
+ }
+
+done:
+ syscall_printf ("%R = timerfd_create (%lu, %y)", ret, clock_id, flags);
+ return ret;
+}
+
+extern "C" int
+timerfd_settime (int fd_in, int flags, const struct itimerspec *value,
+ struct itimerspec *ovalue)
+{
+ if ((flags & ~(TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET)) != 0)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+
+ cygheap_fdget fd (fd_in);
+ if (fd < 0)
+ return -1;
+ fhandler_timerfd *fh = fd->is_timerfd ();
+ if (!fh)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ return fh->settime (flags, value, ovalue);
+}
+
+extern "C" int
+timerfd_gettime (int fd_in, struct itimerspec *ovalue)
+{
+ cygheap_fdget fd (fd_in);
+ if (fd < 0)
+ return -1;
+ fhandler_timerfd *fh = fd->is_timerfd ();
+ if (!fh)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ return fh->gettime (ovalue);
+}
diff --git a/winsup/cygwin/fhandler/tty.cc b/winsup/cygwin/fhandler/tty.cc
new file mode 100644
index 000000000..0f9dec570
--- /dev/null
+++ b/winsup/cygwin/fhandler/tty.cc
@@ -0,0 +1,4272 @@
+/* fhandler_tty.cc
+
+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 <stdlib.h>
+#include <sys/param.h>
+#include <cygwin/acl.h>
+#include <cygwin/kd.h>
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "sigproc.h"
+#include "pinfo.h"
+#include "ntdll.h"
+#include "cygheap.h"
+#include "shared_info.h"
+#include "cygthread.h"
+#include "child_info.h"
+#include <asm/socket.h>
+#include "cygwait.h"
+#include "registry.h"
+#include "tls_pbuf.h"
+#include "winf.h"
+
+#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
+#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
+#endif /* PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE */
+
+extern "C" int sscanf (const char *, const char *, ...);
+
+#define close_maybe(h) \
+ do { \
+ if (h && h != INVALID_HANDLE_VALUE) \
+ CloseHandle (h); \
+ } while (0)
+
+/* pty master control pipe messages */
+struct pipe_request {
+ DWORD pid;
+};
+
+/* The name *nat* comes from 'native' which means non-cygwin
+ (native windows). They are used for non-cygwin process. */
+struct pipe_reply {
+ HANDLE from_master_nat;
+ HANDLE from_master;
+ HANDLE to_master_nat;
+ HANDLE to_master;
+ HANDLE to_slave_nat;
+ HANDLE to_slave;
+ DWORD error;
+};
+
+HANDLE attach_mutex;
+
+DWORD acquire_attach_mutex (DWORD t)
+{
+ if (!attach_mutex)
+ return WAIT_OBJECT_0;
+ return WaitForSingleObject (attach_mutex, t);
+}
+
+void release_attach_mutex (void)
+{
+ if (!attach_mutex)
+ return;
+ ReleaseMutex (attach_mutex);
+}
+
+inline static bool process_alive (DWORD pid);
+
+/* This functions looks for a process which attached to the same console
+ with current process and is matched to given conditions:
+ match: If true, return given pid if the process pid attaches to the
+ same console, otherwise, return 0. If false, return pid except
+ for given pid.
+ cygwin: return only process's pid which has cygwin pid.
+ stub_only: return only stub process's pid of non-cygwin process. */
+DWORD
+fhandler_pty_common::get_console_process_id (DWORD pid, bool match,
+ bool cygwin, bool stub_only)
+{
+ tmp_pathbuf tp;
+ DWORD *list = (DWORD *) tp.c_get ();
+ const DWORD buf_size = NT_MAX_PATH / sizeof (DWORD);
+
+ DWORD num = GetConsoleProcessList (list, buf_size);
+ if (num == 0 || num > buf_size)
+ return 0;
+
+ DWORD res_pri = 0, res = 0;
+ /* Last one is the oldest. */
+ /* https://github.com/microsoft/terminal/issues/95 */
+ for (int i = (int) num - 1; i >= 0; i--)
+ if ((match && list[i] == pid) || (!match && list[i] != pid))
+ {
+ if (!cygwin)
+ {
+ res_pri = list[i];
+ break;
+ }
+ else
+ {
+ pinfo p (cygwin_pid (list[i]));
+ if (!!p && p->exec_dwProcessId)
+ {
+ res_pri = stub_only ? p->exec_dwProcessId : list[i];
+ break;
+ }
+ if (!p && !res && process_alive (list[i]) && stub_only)
+ res = list[i];
+ if (!!p && !res && !stub_only)
+ res = list[i];
+ }
+ }
+ return res_pri ?: res;
+}
+
+static bool isHybrid; /* Set true if the active pipe is set to nat pipe
+ owned by myself even though the current process
+ is a cygwin process. */
+static HANDLE h_gdb_inferior; /* Handle of GDB inferior process. */
+
+static void
+set_switch_to_nat_pipe (HANDLE *in, HANDLE *out, HANDLE *err)
+{
+ cygheap_fdenum cfd (false);
+ int fd;
+ fhandler_base *replace_in = NULL, *replace_out = NULL, *replace_err = NULL;
+ fhandler_pty_slave *ptys = NULL;
+ while ((fd = cfd.next ()) >= 0)
+ {
+ if (*in == cfd->get_handle () ||
+ (fd == 0 && *in == GetStdHandle (STD_INPUT_HANDLE)))
+ replace_in = (fhandler_base *) cfd;
+ if (*out == cfd->get_output_handle () ||
+ (fd == 1 && *out == GetStdHandle (STD_OUTPUT_HANDLE)))
+ replace_out = (fhandler_base *) cfd;
+ if (*err == cfd->get_output_handle () ||
+ (fd == 2 && *err == GetStdHandle (STD_ERROR_HANDLE)))
+ replace_err = (fhandler_base *) cfd;
+ if (cfd->get_device () == (dev_t) myself->ctty)
+ {
+ fhandler_base *fh = cfd;
+ if (*in == fh->get_handle ()
+ || *out == fh->get_output_handle ()
+ || *err == fh->get_output_handle ())
+ ptys = (fhandler_pty_slave *) fh;
+ }
+ }
+ if (ptys)
+ ptys->set_switch_to_nat_pipe ();
+ if (replace_in)
+ *in = replace_in->get_handle_nat ();
+ if (replace_out)
+ *out = replace_out->get_output_handle_nat ();
+ if (replace_err)
+ *err = replace_err->get_output_handle_nat ();
+}
+
+/* Determine if the given path is cygwin binary. */
+static bool
+path_iscygexec_a_w (LPCSTR na, LPSTR ca, LPCWSTR nw, LPWSTR cw)
+{
+ path_conv path;
+ tmp_pathbuf tp;
+ char *prog =tp.c_get ();
+ if (na)
+ {
+ __small_sprintf (prog, "%s", na);
+ find_exec (prog, path);
+ }
+ else if (nw)
+ {
+ __small_sprintf (prog, "%W", nw);
+ find_exec (prog, path);
+ }
+ else
+ {
+ if (ca)
+ __small_sprintf (prog, "%s", ca);
+ else if (cw)
+ __small_sprintf (prog, "%W", cw);
+ else
+ return true;
+ char *p = prog;
+ char *p1;
+ do
+ if ((p1 = strchr (p, ' ')) || (p1 = p + strlen (p)))
+ {
+ p = p1;
+ if (*p == ' ')
+ {
+ *p = '\0';
+ find_exec (prog, path);
+ *p = ' ';
+ p ++;
+ }
+ else if (*p == '\0')
+ find_exec (prog, path);
+ }
+ while (!path.exists() && *p);
+ }
+ const char *argv[] = {"", NULL}; /* Dummy */
+ av av1;
+ av1.setup ("", path, "", 1, argv, false);
+ return path.iscygexec ();
+}
+
+bool
+fhandler_termios::path_iscygexec_a (LPCSTR n, LPSTR c)
+{
+ return path_iscygexec_a_w (n, c, NULL, NULL);
+}
+
+bool
+fhandler_termios::path_iscygexec_w (LPCWSTR n, LPWSTR c)
+{
+ return path_iscygexec_a_w (NULL, NULL, n, c);
+}
+
+static bool atexit_func_registered = false;
+static bool debug_process = false;
+
+static void
+atexit_func (void)
+{
+ if (isHybrid)
+ {
+ cygheap_fdenum cfd (false);
+ while (cfd.next () >= 0)
+ if (cfd->get_device () == (dev_t) myself->ctty)
+ {
+ DWORD force_switch_to = 0;
+ if (WaitForSingleObject (h_gdb_inferior, 0) == WAIT_TIMEOUT
+ && !debug_process)
+ force_switch_to = GetProcessId (h_gdb_inferior);
+ fhandler_base *fh = cfd;
+ fhandler_pty_slave *ptys = (fhandler_pty_slave *) fh;
+ tty *ttyp = (tty *) ptys->tc ();
+ bool stdin_is_ptys =
+ GetStdHandle (STD_INPUT_HANDLE) == ptys->get_handle ();
+ struct fhandler_pty_slave::handle_set_t handles =
+ {
+ ptys->get_handle_nat (),
+ ptys->get_input_available_event (),
+ ptys->input_mutex,
+ ptys->pipe_sw_mutex
+ };
+ fhandler_pty_slave::cleanup_for_non_cygwin_app (&handles, ttyp,
+ stdin_is_ptys,
+ force_switch_to);
+ break;
+ }
+ CloseHandle (h_gdb_inferior);
+ }
+}
+
+#define DEF_HOOK(name) static __typeof__ (name) *name##_Orig
+/* CreateProcess() is hooked for GDB etc. */
+DEF_HOOK (CreateProcessA);
+DEF_HOOK (CreateProcessW);
+
+static BOOL
+CreateProcessA_Hooked
+ (LPCSTR n, LPSTR c, LPSECURITY_ATTRIBUTES pa, LPSECURITY_ATTRIBUTES ta,
+ BOOL inh, DWORD f, LPVOID e, LPCSTR d,
+ LPSTARTUPINFOA si, LPPROCESS_INFORMATION pi)
+{
+ STARTUPINFOEXA siex = {0, };
+ if (si->cb == sizeof (STARTUPINFOEXA))
+ siex = *(STARTUPINFOEXA *)si;
+ else
+ siex.StartupInfo = *si;
+ STARTUPINFOA *siov = &siex.StartupInfo;
+ if (!(si->dwFlags & STARTF_USESTDHANDLES))
+ {
+ siov->dwFlags |= STARTF_USESTDHANDLES;
+ siov->hStdInput = GetStdHandle (STD_INPUT_HANDLE);
+ siov->hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
+ siov->hStdError = GetStdHandle (STD_ERROR_HANDLE);
+ }
+ bool path_iscygexec = fhandler_termios::path_iscygexec_a (n, c);
+ if (!path_iscygexec)
+ set_switch_to_nat_pipe (&siov->hStdInput, &siov->hStdOutput,
+ &siov->hStdError);
+ BOOL ret = CreateProcessA_Orig (n, c, pa, ta, inh, f, e, d, siov, pi);
+ h_gdb_inferior = pi->hProcess;
+ DuplicateHandle (GetCurrentProcess (), h_gdb_inferior,
+ GetCurrentProcess (), &h_gdb_inferior,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ debug_process = !!(f & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS));
+ if (debug_process)
+ mutex_timeout = 0; /* to avoid deadlock in GDB */
+ if (!atexit_func_registered && !path_iscygexec)
+ {
+ atexit (atexit_func);
+ atexit_func_registered = true;
+ }
+ return ret;
+}
+
+static BOOL
+CreateProcessW_Hooked
+ (LPCWSTR n, LPWSTR c, LPSECURITY_ATTRIBUTES pa, LPSECURITY_ATTRIBUTES ta,
+ BOOL inh, DWORD f, LPVOID e, LPCWSTR d,
+ LPSTARTUPINFOW si, LPPROCESS_INFORMATION pi)
+{
+ STARTUPINFOEXW siex = {0, };
+ if (si->cb == sizeof (STARTUPINFOEXW))
+ siex = *(STARTUPINFOEXW *)si;
+ else
+ siex.StartupInfo = *si;
+ STARTUPINFOW *siov = &siex.StartupInfo;
+ if (!(si->dwFlags & STARTF_USESTDHANDLES))
+ {
+ siov->dwFlags |= STARTF_USESTDHANDLES;
+ siov->hStdInput = GetStdHandle (STD_INPUT_HANDLE);
+ siov->hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
+ siov->hStdError = GetStdHandle (STD_ERROR_HANDLE);
+ }
+ bool path_iscygexec = fhandler_termios::path_iscygexec_w (n, c);
+ if (!path_iscygexec)
+ set_switch_to_nat_pipe (&siov->hStdInput, &siov->hStdOutput,
+ &siov->hStdError);
+ BOOL ret = CreateProcessW_Orig (n, c, pa, ta, inh, f, e, d, siov, pi);
+ h_gdb_inferior = pi->hProcess;
+ DuplicateHandle (GetCurrentProcess (), h_gdb_inferior,
+ GetCurrentProcess (), &h_gdb_inferior,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ debug_process = !!(f & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS));
+ if (debug_process)
+ mutex_timeout = 0; /* to avoid deadlock in GDB */
+ if (!atexit_func_registered && !path_iscygexec)
+ {
+ atexit (atexit_func);
+ atexit_func_registered = true;
+ }
+ return ret;
+}
+
+static void
+convert_mb_str (UINT cp_to, char *ptr_to, size_t *len_to,
+ UINT cp_from, const char *ptr_from, size_t len_from,
+ mbstate_t *mbp)
+{
+ tmp_pathbuf tp;
+ wchar_t *wbuf = tp.w_get ();
+ int wlen = 0;
+ char *tmpbuf = tp.c_get ();
+ memcpy (tmpbuf, mbp->__value.__wchb, mbp->__count);
+ if (mbp->__count + len_from > NT_MAX_PATH)
+ len_from = NT_MAX_PATH - mbp->__count;
+ memcpy (tmpbuf + mbp->__count, ptr_from, len_from);
+ int total_len = mbp->__count + len_from;
+ mbp->__count = 0;
+ int mblen = 0;
+ for (const char *p = tmpbuf; p < tmpbuf + total_len; p += mblen)
+ /* Max bytes in multibyte char supported is 4. */
+ for (mblen = 1; mblen <= 4; mblen ++)
+ {
+ /* Try conversion */
+ int l = MultiByteToWideChar (cp_from, MB_ERR_INVALID_CHARS,
+ p, mblen,
+ wbuf + wlen, NT_MAX_PATH - wlen);
+ if (l)
+ { /* Conversion Success */
+ wlen += l;
+ break;
+ }
+ else if (mblen == 4)
+ { /* Conversion Fail */
+ l = MultiByteToWideChar (cp_from, 0, p, 1,
+ wbuf + wlen, NT_MAX_PATH - wlen);
+ wlen += l;
+ mblen = 1;
+ break;
+ }
+ else if (p + mblen == tmpbuf + total_len)
+ { /* Multibyte char incomplete */
+ memcpy (mbp->__value.__wchb, p, mblen);
+ mbp->__count = mblen;
+ break;
+ }
+ /* Retry conversion with extended length */
+ }
+ *len_to = WideCharToMultiByte (cp_to, 0, wbuf, wlen,
+ ptr_to, *len_to, NULL, NULL);
+}
+
+static bool
+bytes_available (DWORD& n, HANDLE h)
+{
+ DWORD navail, nleft;
+ navail = nleft = 0;
+ bool succeeded = PeekNamedPipe (h, NULL, 0, NULL, &navail, &nleft);
+ if (succeeded)
+ /* nleft should always be the right choice unless something has written 0
+ bytes to the pipe. In that pathological case we return the actual number
+ of bytes available in the pipe. See cgf-000008 for more details. */
+ n = nleft ?: navail;
+ else
+ {
+ termios_printf ("PeekNamedPipe(%p) failed, %E", h);
+ n = 0;
+ }
+ debug_only_printf ("n %u, nleft %u, navail %u", n, nleft, navail);
+ return succeeded;
+}
+
+bool
+fhandler_pty_common::bytes_available (DWORD &n)
+{
+ return ::bytes_available (n, get_handle ());
+}
+
+#ifdef DEBUGGING
+static class mutex_stack
+{
+public:
+ const char *fn;
+ int ln;
+ const char *tname;
+} ostack[100];
+
+static int osi;
+#endif /*DEBUGGING*/
+
+void
+fhandler_pty_master::flush_to_slave ()
+{
+ if (get_readahead_valid () && !(get_ttyp ()->ti.c_lflag & ICANON))
+ accept_input ();
+}
+
+void
+fhandler_pty_master::discard_input ()
+{
+ DWORD bytes_in_pipe;
+ char buf[1024];
+ DWORD n;
+
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ while (::bytes_available (bytes_in_pipe, from_master) && bytes_in_pipe)
+ ReadFile (from_master, buf, sizeof(buf), &n, NULL);
+ ResetEvent (input_available_event);
+ if (!get_ttyp ()->pcon_activated)
+ while (::bytes_available (bytes_in_pipe, from_master_nat) && bytes_in_pipe)
+ ReadFile (from_master_nat, buf, sizeof(buf), &n, NULL);
+ get_ttyp ()->discard_input = true;
+ ReleaseMutex (input_mutex);
+}
+
+DWORD
+fhandler_pty_common::__acquire_output_mutex (const char *fn, int ln,
+ DWORD ms)
+{
+ if (strace.active ())
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): pty output_mutex (%p): waiting %d ms", ln, output_mutex, ms);
+ DWORD res = WaitForSingleObject (output_mutex, ms);
+ if (res == WAIT_OBJECT_0)
+ {
+#ifndef DEBUGGING
+ if (strace.active ())
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): pty output_mutex: acquired", ln, res);
+#else
+ ostack[osi].fn = fn;
+ ostack[osi].ln = ln;
+ ostack[osi].tname = mythreadname ();
+ termios_printf ("acquired for %s:%d, osi %d", fn, ln, osi);
+ osi++;
+#endif
+ }
+ return res;
+}
+
+void
+fhandler_pty_common::__release_output_mutex (const char *fn, int ln)
+{
+ if (ReleaseMutex (output_mutex))
+ {
+#ifndef DEBUGGING
+ if (strace.active ())
+ strace.prntf (_STRACE_TERMIOS, fn, "(%d): pty output_mutex(%p) released", ln, output_mutex);
+#else
+ if (osi > 0)
+ osi--;
+ termios_printf ("released(%p) at %s:%d, osi %d", output_mutex, fn, ln, osi);
+ termios_printf (" for %s:%d (%s)", ostack[osi].fn, ostack[osi].ln, ostack[osi].tname);
+ ostack[osi].ln = -ln;
+#endif
+ }
+#ifdef DEBUGGING
+ else if (osi > 0)
+ {
+ system_printf ("couldn't release output mutex but we seem to own it, %E");
+ try_to_debug ();
+ }
+#endif
+}
+
+/* Process pty input. */
+
+void
+fhandler_pty_master::doecho (const void *str, DWORD len)
+{
+ ssize_t towrite = len;
+ if (!process_opost_output (echo_w, str, towrite, true,
+ get_ttyp (), is_nonblocking ()))
+ termios_printf ("Write to echo pipe failed, %E");
+}
+
+int
+fhandler_pty_master::accept_input ()
+{
+ DWORD bytes_left;
+ int ret = 1;
+
+ WaitForSingleObject (input_mutex, mutex_timeout);
+
+ char *p = rabuf () + raixget ();
+ bytes_left = eat_readahead (-1);
+
+ HANDLE write_to = get_output_handle ();
+ tmp_pathbuf tp;
+ if (to_be_read_from_nat_pipe ()
+ && get_ttyp ()->pty_input_state == tty::to_nat)
+ {
+ /* This code is reached if non-cygwin app is foreground and
+ pseudo console is not enabled. */
+ write_to = to_slave_nat; /* write to nat pipe rather than cyg pipe. */
+
+ /* Charset conversion for non-cygwin apps. */
+ UINT cp_to;
+ pinfo pinfo_target = pinfo (get_ttyp ()->invisible_console_pid);
+ DWORD target_pid = 0;
+ if (pinfo_target)
+ target_pid = pinfo_target->dwProcessId;
+ if (target_pid)
+ {
+ /* Slave attaches to a different console than master.
+ Therefore reattach here. */
+ DWORD resume_pid =
+ attach_console_temporarily (target_pid, helper_pid);
+ cp_to = GetConsoleCP ();
+ resume_from_temporarily_attach (resume_pid);
+ }
+ else
+ cp_to = GetConsoleCP ();
+
+ if (get_ttyp ()->term_code_page != cp_to)
+ {
+ static mbstate_t mbp;
+ char *mbbuf = tp.c_get ();
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (cp_to, mbbuf, &nlen,
+ get_ttyp ()->term_code_page, p, bytes_left, &mbp);
+ p = mbbuf;
+ bytes_left = nlen;
+ }
+ }
+
+ if (!bytes_left)
+ {
+ termios_printf ("sending EOF to slave");
+ get_ttyp ()->read_retval = 0;
+ }
+ else
+ {
+ BOOL rc = TRUE;
+ DWORD written = 0;
+
+ paranoid_printf ("about to write %u chars to slave", bytes_left);
+ /* Write line by line for transfer input. */
+ char *p0 = p;
+ char *p_cr = (char *) memchr (p0, '\r', bytes_left - (p0 - p));
+ char *p_lf = (char *) memchr (p0, '\n', bytes_left - (p0 - p));
+ DWORD n;
+ while (p_cr || p_lf)
+ {
+ char *p1 =
+ p_cr ? (p_lf ? ((p_cr + 1 == p_lf)
+ ? p_lf : min(p_cr, p_lf)) : p_cr) : p_lf;
+ n = p1 - p0 + 1;
+ rc = WriteFile (write_to, p0, n, &n, NULL);
+ if (rc)
+ written += n;
+ p0 = p1 + 1;
+ p_cr = (char *) memchr (p0, '\r', bytes_left - (p0 - p));
+ p_lf = (char *) memchr (p0, '\n', bytes_left - (p0 - p));
+ }
+ if (rc && (n = bytes_left - (p0 - p)))
+ {
+ rc = WriteFile (write_to, p0, n, &n, NULL);
+ if (rc)
+ written += n;
+ }
+ if (!rc && written == 0)
+ {
+ debug_printf ("error writing to pipe %p %E", write_to);
+ get_ttyp ()->read_retval = -1;
+ puts_readahead (p, bytes_left);
+ ret = -1;
+ }
+ else
+ {
+ get_ttyp ()->read_retval = 1;
+ p += written;
+ bytes_left -= written;
+ if (bytes_left > 0)
+ {
+ debug_printf ("to_slave pipe is full");
+ puts_readahead (p, bytes_left);
+ ret = 0;
+ }
+ }
+ }
+
+ if (write_to == get_output_handle ())
+ SetEvent (input_available_event); /* Set input_available_event only when
+ the data is written to cyg pipe. */
+ ReleaseMutex (input_mutex);
+ return ret;
+}
+
+bool
+fhandler_pty_master::hit_eof ()
+{
+ if (get_ttyp ()->was_opened && !get_ttyp ()->slave_alive ())
+ {
+ /* We have the only remaining open handle to this pty, and
+ the slave pty has been opened at least once. We treat
+ this as EOF. */
+ termios_printf ("all other handles closed");
+ return 1;
+ }
+ return 0;
+}
+
+/* Process pty output requests */
+
+int
+fhandler_pty_master::process_slave_output (char *buf, size_t len, int pktmode_on)
+{
+ size_t rlen;
+ char outbuf[OUT_BUFFER_SIZE];
+ DWORD n;
+ DWORD echo_cnt;
+ int rc = 0;
+
+ flush_to_slave ();
+
+ if (len == 0)
+ goto out;
+
+ for (;;)
+ {
+ n = echo_cnt = 0;
+ for (;;)
+ {
+ /* Check echo pipe first. */
+ if (::bytes_available (echo_cnt, echo_r) && echo_cnt > 0)
+ break;
+ if (!bytes_available (n))
+ goto err;
+ if (n)
+ break;
+ if (hit_eof ())
+ {
+ set_errno (EIO);
+ rc = -1;
+ goto out;
+ }
+ /* tclush can finish here. */
+ if (!buf)
+ goto out;
+
+ if (is_nonblocking ())
+ {
+ set_errno (EAGAIN);
+ rc = -1;
+ goto out;
+ }
+ pthread_testcancel ();
+ if (cygwait (NULL, 10, cw_sig_eintr) == WAIT_SIGNALED
+ && !_my_tls.call_signal_handler ())
+ {
+ set_errno (EINTR);
+ rc = -1;
+ goto out;
+ }
+ flush_to_slave ();
+ }
+
+ /* Set RLEN to the number of bytes to read from the pipe. */
+ rlen = len;
+
+ char *optr;
+ optr = buf;
+ if (pktmode_on && buf)
+ {
+ *optr++ = TIOCPKT_DATA;
+ rlen -= 1;
+ }
+
+ if (rlen == 0)
+ {
+ rc = optr - buf;
+ goto out;
+ }
+
+ if (rlen > sizeof outbuf)
+ rlen = sizeof outbuf;
+
+ /* If echo pipe has data (something has been typed or pasted), prefer
+ it over slave output. */
+ if (echo_cnt > 0)
+ {
+ if (!ReadFile (echo_r, outbuf, rlen, &n, NULL))
+ {
+ termios_printf ("ReadFile on echo pipe failed, %E");
+ goto err;
+ }
+ }
+ else if (!ReadFile (get_handle (), outbuf, rlen, &n, NULL))
+ {
+ termios_printf ("ReadFile failed, %E");
+ goto err;
+ }
+
+ termios_printf ("bytes read %u", n);
+
+ if (!buf || ((get_ttyp ()->ti.c_lflag & FLUSHO)
+ && !get_ttyp ()->mask_flusho))
+ continue; /* Discard read data */
+
+ memcpy (optr, outbuf, n);
+ optr += n;
+ rc = optr - buf;
+ break;
+
+ err:
+ if (GetLastError () == ERROR_BROKEN_PIPE)
+ rc = 0;
+ else
+ {
+ __seterrno ();
+ rc = -1;
+ }
+ break;
+ }
+
+out:
+ if (buf)
+ set_mask_flusho (false);
+ termios_printf ("returning %d", rc);
+ return rc;
+}
+
+/* pty slave stuff */
+
+fhandler_pty_slave::fhandler_pty_slave (int unit)
+ : fhandler_pty_common (), inuse (NULL), output_handle_nat (NULL),
+ io_handle_nat (NULL), slave_reading (NULL), num_reader (0)
+{
+ if (unit >= 0)
+ dev ().parse (DEV_PTYS_MAJOR, unit);
+}
+
+int
+fhandler_pty_slave::open (int flags, mode_t)
+{
+ HANDLE pty_owner;
+ HANDLE from_master_nat_local, from_master_local;
+ HANDLE to_master_nat_local, to_master_local;
+ HANDLE *handles[] =
+ {
+ &from_master_nat_local, &input_available_event, &input_mutex, &inuse,
+ &output_mutex, &to_master_nat_local, &pty_owner, &to_master_local,
+ &from_master_local, &pipe_sw_mutex,
+ NULL
+ };
+
+ for (HANDLE **h = handles; *h; h++)
+ **h = NULL;
+
+ _tc = cygwin_shared->tty[get_minor ()];
+
+ tcinit (false);
+
+ cygwin_shared->tty.attach (get_minor ());
+
+ /* Create synchronisation events */
+ char buf[MAX_PATH];
+
+ const char *errmsg = NULL;
+
+ if (!(output_mutex = get_ttyp ()->open_output_mutex (MAXIMUM_ALLOWED)))
+ {
+ errmsg = "open output mutex failed, %E";
+ goto err;
+ }
+ if (!(input_mutex = get_ttyp ()->open_input_mutex (MAXIMUM_ALLOWED)))
+ {
+ errmsg = "open input mutex failed, %E";
+ goto err;
+ }
+ if (!(pipe_sw_mutex
+ = get_ttyp ()->open_mutex (PIPE_SW_MUTEX, MAXIMUM_ALLOWED)))
+ {
+ errmsg = "open pipe switch mutex failed, %E";
+ goto err;
+ }
+ shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ());
+ if (!(input_available_event = OpenEvent (MAXIMUM_ALLOWED, TRUE, buf)))
+ {
+ errmsg = "open input event failed, %E";
+ goto err;
+ }
+
+ /* FIXME: Needs a method to eliminate tty races */
+ {
+ /* Create security attribute. Default permissions are 0620. */
+ security_descriptor sd;
+ sd.malloc (sizeof (SECURITY_DESCRIPTOR));
+ RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION);
+ SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE };
+ if (!create_object_sd_from_attribute (myself->uid, myself->gid,
+ S_IFCHR | S_IRUSR | S_IWUSR | S_IWGRP,
+ sd))
+ sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR) sd;
+ acquire_output_mutex (mutex_timeout);
+ inuse = get_ttyp ()->create_inuse (&sa);
+ get_ttyp ()->was_opened = true;
+ release_output_mutex ();
+ }
+
+ if (!get_ttyp ()->from_master_nat () || !get_ttyp ()->from_master ()
+ || !get_ttyp ()->to_master_nat () || !get_ttyp ()->to_master ())
+ {
+ errmsg = "pty handles have been closed";
+ set_errno (EACCES);
+ goto err_no_errno;
+ }
+
+ /* Three case for duplicating the pipe handles:
+ - Either we're the master. In this case, just duplicate the handles.
+ - Or, we have the right to open the master process for handle duplication.
+ In this case, just duplicate the handles.
+ - Or, we have to ask the master process itself. In this case, send our
+ pid to the master process and check the reply. The reply contains
+ either the handles, or an error code which tells us why we didn't
+ get the handles. */
+ if (myself->pid == get_ttyp ()->master_pid)
+ {
+ /* This is the most common case, just calling openpty. */
+ termios_printf ("dup handles within myself.");
+ pty_owner = GetCurrentProcess ();
+ }
+ else
+ {
+ pinfo p (get_ttyp ()->master_pid);
+ if (!p)
+ termios_printf ("*** couldn't find pty master");
+ else
+ {
+ pty_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE, p->dwProcessId);
+ if (pty_owner)
+ termios_printf ("dup handles directly since I'm the owner");
+ }
+ }
+ if (pty_owner)
+ {
+ if (!DuplicateHandle (pty_owner, get_ttyp ()->from_master_nat (),
+ GetCurrentProcess (),
+ &from_master_nat_local, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("can't duplicate input from %u/%p, %E",
+ get_ttyp ()->master_pid,
+ get_ttyp ()->from_master_nat ());
+ __seterrno ();
+ goto err_no_msg;
+ }
+ if (!DuplicateHandle (pty_owner, get_ttyp ()->from_master (),
+ GetCurrentProcess (),
+ &from_master_local, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("can't duplicate input from %u/%p, %E",
+ get_ttyp ()->master_pid,
+ get_ttyp ()->from_master ());
+ __seterrno ();
+ goto err_no_msg;
+ }
+ if (!DuplicateHandle (pty_owner, get_ttyp ()->to_master_nat (),
+ GetCurrentProcess (),
+ &to_master_nat_local, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ errmsg = "can't duplicate output, %E";
+ goto err;
+ }
+ if (!DuplicateHandle (pty_owner, get_ttyp ()->to_master (),
+ GetCurrentProcess (),
+ &to_master_local, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ errmsg = "can't duplicate output for cygwin, %E";
+ goto err;
+ }
+ if (pty_owner != GetCurrentProcess ())
+ CloseHandle (pty_owner);
+ }
+ else
+ {
+ pipe_request req = { GetCurrentProcessId () };
+ pipe_reply repl;
+ DWORD len;
+
+ __small_sprintf (buf, "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, get_minor ());
+ termios_printf ("dup handles via master control pipe %s", buf);
+ if (!CallNamedPipe (buf, &req, sizeof req, &repl, sizeof repl,
+ &len, 500))
+ {
+ errmsg = "can't call master, %E";
+ goto err;
+ }
+ from_master_nat_local = repl.from_master_nat;
+ from_master_local = repl.from_master;
+ to_master_nat_local = repl.to_master_nat;
+ to_master_local = repl.to_master;
+ if (!from_master_nat_local || !from_master_local
+ || !to_master_nat_local || !to_master_local)
+ {
+ SetLastError (repl.error);
+ errmsg = "error duplicating pipes, %E";
+ goto err;
+ }
+ }
+ VerifyHandle (from_master_nat_local);
+ VerifyHandle (from_master_local);
+ VerifyHandle (to_master_nat_local);
+ VerifyHandle (to_master_local);
+
+ termios_printf ("duplicated from_master_nat %p->%p from pty_owner",
+ get_ttyp ()->from_master_nat (), from_master_nat_local);
+ termios_printf ("duplicated from_master %p->%p from pty_owner",
+ get_ttyp ()->from_master (), from_master_local);
+ termios_printf ("duplicated to_master_nat %p->%p from pty_owner",
+ get_ttyp ()->to_master_nat (), to_master_nat_local);
+ termios_printf ("duplicated to_master %p->%p from pty_owner",
+ get_ttyp ()->to_master (), to_master_local);
+
+ set_handle_nat (from_master_nat_local);
+ set_handle (from_master_local);
+ set_output_handle_nat (to_master_nat_local);
+ set_output_handle (to_master_local);
+
+ if (_major (myself->ctty) == DEV_CONS_MAJOR
+ && !(!pinfo (myself->ppid) && getenv ("ConEmuPID")))
+ /* This process is supposed to be a master process which is
+ running on console. Invisible console will be created in
+ primary slave process to prevent overriding code page
+ of root console by setup_locale(). */
+ /* ... except for ConEmu cygwin-connector in which this
+ code does not work as expected because it calls Win32
+ API directly rather than cygwin read()/write(). Due to
+ this behaviour, protection based on attach_mutex does
+ not take effect. */
+ get_ttyp ()->need_invisible_console = true;
+ else if (_major (myself->ctty) != DEV_CONS_MAJOR
+ && (!get_ttyp ()->invisible_console_pid
+ || !pinfo (get_ttyp ()->invisible_console_pid)))
+ /* Create a new invisible console for each pty to isolate
+ CTRL_C_EVENTs between ptys. */
+ get_ttyp ()->need_invisible_console = true;
+ else
+ {
+ acquire_attach_mutex (mutex_timeout);
+ fhandler_console::need_invisible ();
+ release_attach_mutex ();
+ }
+
+ set_open_status ();
+ return 1;
+
+err:
+ if (GetLastError () == ERROR_FILE_NOT_FOUND)
+ set_errno (ENXIO);
+ else
+ __seterrno ();
+err_no_errno:
+ termios_printf (errmsg);
+err_no_msg:
+ for (HANDLE **h = handles; *h; h++)
+ if (**h && **h != INVALID_HANDLE_VALUE)
+ CloseHandle (**h);
+ return 0;
+}
+
+bool
+fhandler_pty_slave::open_setup (int flags)
+{
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ myself->set_ctty (this, flags);
+ report_tty_counts (this, "opened", "");
+ return fhandler_base::open_setup (flags);
+}
+
+void
+fhandler_pty_slave::cleanup ()
+{
+ /* This used to always call fhandler_pty_common::close when we were execing
+ but that caused multiple closes of the handles associated with this pty.
+ Since close_all_files is not called until after the cygwin process has
+ synced or before a non-cygwin process has exited, it should be safe to
+ just close this normally. cgf 2006-05-20 */
+ report_tty_counts (this, "closed", "");
+ fhandler_base::cleanup ();
+}
+
+int
+fhandler_pty_slave::close ()
+{
+ termios_printf ("closing last open %s handle", ttyname ());
+ if (inuse && !CloseHandle (inuse))
+ termios_printf ("CloseHandle (inuse), %E");
+ if (!ForceCloseHandle (input_available_event))
+ termios_printf ("CloseHandle (input_available_event<%p>), %E", input_available_event);
+ if (!ForceCloseHandle (get_output_handle_nat ()))
+ termios_printf ("CloseHandle (get_output_handle_nat ()<%p>), %E",
+ get_output_handle_nat ());
+ if (!ForceCloseHandle (get_handle_nat ()))
+ termios_printf ("CloseHandle (get_handle_nat ()<%p>), %E",
+ get_handle_nat ());
+ fhandler_pty_common::close ();
+ if (!ForceCloseHandle (output_mutex))
+ termios_printf ("CloseHandle (output_mutex<%p>), %E", output_mutex);
+ if (get_ttyp ()->invisible_console_pid
+ && !pinfo (get_ttyp ()->invisible_console_pid))
+ get_ttyp ()->invisible_console_pid = 0;
+ return 0;
+}
+
+int
+fhandler_pty_slave::init (HANDLE h, DWORD a, mode_t)
+{
+ int flags = 0;
+
+ a &= GENERIC_READ | GENERIC_WRITE;
+ if (a == GENERIC_READ)
+ flags = O_RDONLY;
+ if (a == GENERIC_WRITE)
+ flags = O_WRONLY;
+ if (a == (GENERIC_READ | GENERIC_WRITE))
+ flags = O_RDWR;
+
+ int ret = open_with_arch (flags);
+
+ if (ret && !cygwin_finished_initializing && !being_debugged ())
+ {
+ /* This only occurs when called from dtable::init_std_file_from_handle
+ We have been started from a non-Cygwin process. So we should become
+ pty process group leader.
+ TODO: Investigate how SIGTTIN should be handled with pure-windows
+ programs. */
+ pinfo p (tc ()->getpgid ());
+ /* We should only grab this when the process group owner for this
+ pty is a non-cygwin process or we've been started directly
+ from a non-Cygwin process with no Cygwin ancestry. */
+ if (!p || ISSTATE (p, PID_NOTCYGWIN))
+ {
+ termios_printf ("Setting process group leader to %d since %W(%d) is not a cygwin process",
+ myself->pgid, p->progname, p->pid);
+ tc ()->setpgid (myself->pgid);
+ }
+ }
+
+ if (h != INVALID_HANDLE_VALUE)
+ CloseHandle (h); /* Reopened by open */
+
+ return ret;
+}
+
+void
+fhandler_pty_slave::set_switch_to_nat_pipe (void)
+{
+ if (!isHybrid)
+ {
+ isHybrid = true;
+ setup_locale ();
+ myself->exec_dwProcessId = myself->dwProcessId; /* Set this as a marker
+ for tty::nat_fg()
+ and process_sigs() */
+ bool stdin_is_ptys = GetStdHandle (STD_INPUT_HANDLE) == get_handle ();
+ setup_for_non_cygwin_app (false, NULL, stdin_is_ptys);
+ }
+}
+
+inline static bool
+process_alive (DWORD pid)
+{
+ /* This function is very similar to _pinfo::alive(), however, this
+ can be used for non-cygwin process which is started from non-cygwin
+ shell. In addition, this checks exit code as well. */
+ if (pid == 0)
+ return false;
+ HANDLE h = OpenProcess (PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
+ if (h == NULL)
+ return false;
+ DWORD exit_code;
+ BOOL r = GetExitCodeProcess (h, &exit_code);
+ CloseHandle (h);
+ if (r && exit_code == STILL_ACTIVE)
+ return true;
+ return false;
+}
+
+inline static bool
+nat_pipe_owner_self (DWORD pid)
+{
+ return (pid == (myself->exec_dwProcessId ?: myself->dwProcessId));
+}
+
+/* This function is called from some pty slave functions. The purpose
+ of this function is cleaning up the nat pipe state which is marked
+ as active but actually not used anymore. This is needed only when
+ non-cygwin process is not terminated cleanly. */
+void
+fhandler_pty_slave::reset_switch_to_nat_pipe (void)
+{
+ if (h_gdb_inferior)
+ {
+ if (WaitForSingleObject (h_gdb_inferior, 0) == WAIT_TIMEOUT)
+ {
+ if (isHybrid)
+ get_ttyp ()->wait_fwd ();
+ }
+ else
+ {
+ CloseHandle (h_gdb_inferior);
+ h_gdb_inferior = NULL;
+ mutex_timeout = INFINITE;
+ if (isHybrid)
+ {
+ if (get_ttyp ()->getpgid () == myself->pgid
+ && GetStdHandle (STD_INPUT_HANDLE) == get_handle ()
+ && get_ttyp ()->pty_input_state_eq (tty::to_nat))
+ {
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_cyg, get_handle_nat (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+ if (get_ttyp ()->master_is_running_as_service
+ && get_ttyp ()->pcon_activated)
+ /* If the master is running as service, re-attaching to
+ the console of the parent process will fail.
+ Therefore, never close pseudo console here. */
+ return;
+ bool need_restore_handles = get_ttyp ()->pcon_activated;
+ WaitForSingleObject (pipe_sw_mutex, INFINITE);
+ if (get_ttyp ()->pcon_activated)
+ close_pseudoconsole (get_ttyp ());
+ else
+ hand_over_only (get_ttyp ());
+ ReleaseMutex (pipe_sw_mutex);
+ if (need_restore_handles)
+ {
+ pinfo p (get_ttyp ()->master_pid);
+ HANDLE pty_owner =
+ OpenProcess (PROCESS_DUP_HANDLE, FALSE, p->dwProcessId);
+ if (pty_owner)
+ {
+ CloseHandle (get_handle_nat ());
+ DuplicateHandle (pty_owner,
+ get_ttyp ()->from_master_nat (),
+ GetCurrentProcess (), &get_handle_nat (),
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (get_output_handle_nat ());
+ DuplicateHandle (pty_owner,
+ get_ttyp ()->to_master_nat (),
+ GetCurrentProcess (),
+ &get_output_handle_nat (),
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (pty_owner);
+ }
+ else
+ {
+ char pipe[MAX_PATH];
+ __small_sprintf (pipe,
+ "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, get_minor ());
+ pipe_request req = { GetCurrentProcessId () };
+ pipe_reply repl;
+ DWORD len;
+ if (!CallNamedPipe (pipe, &req, sizeof req,
+ &repl, sizeof repl, &len, 500))
+ return; /* What can we do? */
+ CloseHandle (get_handle_nat ());
+ set_handle_nat (repl.from_master_nat);
+ CloseHandle (get_output_handle_nat ());
+ set_output_handle_nat (repl.to_master_nat);
+ }
+ }
+ myself->exec_dwProcessId = 0;
+ isHybrid = false;
+ }
+ }
+ }
+ if (isHybrid)
+ return;
+ if (get_ttyp ()->pcon_start) /* Pseudo console initialization is on going */
+ return;
+ DWORD wait_ret = WaitForSingleObject (pipe_sw_mutex, mutex_timeout);
+ if (wait_ret == WAIT_TIMEOUT)
+ return;
+ if (!nat_pipe_owner_self (get_ttyp ()->nat_pipe_owner_pid)
+ && process_alive (get_ttyp ()->nat_pipe_owner_pid))
+ {
+ /* There is a process which owns nat pipe. */
+ ReleaseMutex (pipe_sw_mutex);
+ return;
+ }
+ /* Clean up nat pipe state */
+ get_ttyp ()->pty_input_state = tty::to_cyg;
+ get_ttyp ()->nat_pipe_owner_pid = 0;
+ get_ttyp ()->switch_to_nat_pipe = false;
+ get_ttyp ()->pcon_activated = false;
+ ReleaseMutex (pipe_sw_mutex);
+}
+
+ssize_t
+fhandler_pty_slave::write (const void *ptr, size_t len)
+{
+ ssize_t towrite = len;
+
+ bg_check_types bg = bg_check (SIGTTOU);
+ if (bg <= bg_eof)
+ return (ssize_t) bg;
+
+ termios_printf ("pty%d, write(%p, %lu)", get_minor (), ptr, len);
+
+ push_process_state process_state (PID_TTYOU);
+
+ acquire_output_mutex (mutex_timeout);
+ if (!process_opost_output (get_output_handle (), ptr, towrite, false,
+ get_ttyp (), is_nonblocking ()))
+ {
+ DWORD err = GetLastError ();
+ termios_printf ("WriteFile failed, %E");
+ switch (err)
+ {
+ case ERROR_NO_DATA:
+ err = ERROR_IO_DEVICE;
+ fallthrough;
+ default:
+ __seterrno_from_win_error (err);
+ }
+ towrite = -1;
+ }
+ release_output_mutex ();
+
+ return towrite;
+}
+
+/* This function is called from some slave pty reading functions
+ to switch active pipe to cygwin pipe (not nat pipe) temporarily
+ even though there is another process which owns nat pipe. This
+ is needed if cygwin process reads input while another non-cygwin
+ process is running at the same time.
+ (e.g. Something like "cat | non-cygwin-app") */
+void
+fhandler_pty_slave::mask_switch_to_nat_pipe (bool mask, bool xfer)
+{
+ char name[MAX_PATH];
+ shared_name (name, TTY_SLAVE_READING, get_minor ());
+ HANDLE masked = OpenEvent (READ_CONTROL, FALSE, name);
+ CloseHandle (masked);
+
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ if (mask)
+ {
+ if (InterlockedIncrement (&num_reader) == 1)
+ slave_reading = CreateEvent (&sec_none_nih, TRUE, FALSE, name);
+ }
+ else if (InterlockedDecrement (&num_reader) == 0)
+ CloseHandle (slave_reading);
+
+ /* This is needed when cygwin-app is started from non-cygwin app if
+ pseudo console is disabled. */
+ bool need_xfer = get_ttyp ()->nat_fg (get_ttyp ()->getpgid ())
+ && get_ttyp ()->switch_to_nat_pipe && !get_ttyp ()->pcon_activated;
+
+ /* In GDB, transfer input based on setpgid() does not work because
+ GDB may not set terminal process group properly. Therefore,
+ transfer input here if isHybrid is set. */
+ bool need_gdb_xfer =
+ isHybrid && GetStdHandle (STD_INPUT_HANDLE) == get_handle ();
+ if (!!masked != mask && xfer && (need_gdb_xfer || need_xfer))
+ {
+ if (mask && get_ttyp ()->pty_input_state_eq (tty::to_nat))
+ {
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_cyg, get_handle_nat (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ }
+ else if (!mask && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
+ {
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_nat, get_handle (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ }
+ }
+ ReleaseMutex (input_mutex);
+}
+
+/* Return true when the non-cygwin app is reading the input. */
+bool
+fhandler_pty_common::to_be_read_from_nat_pipe (void)
+{
+ if (!get_ttyp ()->switch_to_nat_pipe)
+ return false;
+
+ char name[MAX_PATH];
+ shared_name (name, TTY_SLAVE_READING, get_minor ());
+ HANDLE masked = OpenEvent (READ_CONTROL, FALSE, name);
+ CloseHandle (masked);
+
+ if (masked) /* The foreground process is cygwin process */
+ return false;
+
+ if (!pinfo (get_ttyp ()->getpgid ()))
+ /* GDB may set invalid process group for non-cygwin process. */
+ return true;
+
+ return get_ttyp ()->nat_fg (get_ttyp ()->getpgid ());
+}
+
+void
+fhandler_pty_slave::read (void *ptr, size_t& len)
+{
+ ssize_t totalread = 0;
+ int vmin = 0;
+ int vtime = 0; /* Initialized to prevent -Wuninitialized warning */
+ size_t readlen;
+ DWORD bytes_in_pipe;
+ char buf[INP_BUFFER_SIZE];
+ DWORD time_to_wait;
+ char *ptr0 = (char *) ptr;
+
+ bg_check_types bg = bg_check (SIGTTIN);
+ if (bg <= bg_eof)
+ {
+ len = (size_t) bg;
+ return;
+ }
+
+ termios_printf ("read(%p, %lu) handle %p", ptr, len, get_handle ());
+
+ push_process_state process_state (PID_TTYIN);
+
+ if (ptr) /* Indicating not tcflush(). */
+ mask_switch_to_nat_pipe (true, true);
+
+ if (is_nonblocking () || !ptr) /* Indicating tcflush(). */
+ time_to_wait = 0;
+ else if ((get_ttyp ()->ti.c_lflag & ICANON))
+ time_to_wait = INFINITE;
+ else
+ {
+ vmin = get_ttyp ()->ti.c_cc[VMIN];
+ if (vmin > INP_BUFFER_SIZE)
+ vmin = INP_BUFFER_SIZE;
+ vtime = get_ttyp ()->ti.c_cc[VTIME];
+ if (vmin < 0)
+ vmin = 0;
+ if (vtime < 0)
+ vtime = 0;
+ if (!vmin && !vtime)
+ time_to_wait = 0;
+ else
+ time_to_wait = !vtime ? INFINITE : 100 * vtime;
+ }
+
+wait_retry:
+ while (len)
+ {
+ switch (cygwait (input_available_event, time_to_wait))
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_SIGNALED:
+ if (totalread > 0)
+ goto out;
+ termios_printf ("wait catched signal");
+ set_sig_errno (EINTR);
+ totalread = -1;
+ goto out;
+ case WAIT_CANCELED:
+ process_state.pop ();
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ case WAIT_TIMEOUT:
+ termios_printf ("wait timed out, time_to_wait %u", time_to_wait);
+ /* No error condition when called from tcflush. */
+ if (!totalread && ptr)
+ {
+ set_sig_errno (EAGAIN);
+ totalread = -1;
+ }
+ goto out;
+ default:
+ termios_printf ("wait for input event failed, %E");
+ if (!totalread)
+ {
+ __seterrno ();
+ totalread = -1;
+ }
+ goto out;
+ }
+ /* Now that we know that input is available we have to grab the
+ input mutex. */
+ switch (cygwait (input_mutex, 1000))
+ {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED_0:
+ break;
+ case WAIT_SIGNALED:
+ if (totalread > 0)
+ goto out;
+ termios_printf ("wait for mutex caught signal");
+ set_sig_errno (EINTR);
+ totalread = -1;
+ goto out;
+ case WAIT_CANCELED:
+ process_state.pop ();
+ pthread::static_cancel_self ();
+ /*NOTREACHED*/
+ case WAIT_TIMEOUT:
+ termios_printf ("failed to acquire input mutex after input event "
+ "arrived");
+ /* If we have a timeout, we can simply handle this failure to
+ grab the mutex as an EAGAIN situation. Otherwise, if this
+ is an infinitely blocking read, restart the loop. */
+ if (time_to_wait != INFINITE)
+ {
+ if (!totalread)
+ {
+ set_sig_errno (EAGAIN);
+ totalread = -1;
+ }
+ goto out;
+ }
+ continue;
+ default:
+ termios_printf ("wait for input mutex failed, %E");
+ if (!totalread)
+ {
+ __seterrno ();
+ totalread = -1;
+ }
+ goto out;
+ }
+ if (!IsEventSignalled (input_available_event))
+ { /* Maybe another thread has processed input. */
+ ReleaseMutex (input_mutex);
+ goto wait_retry;
+ }
+
+ if (!bytes_available (bytes_in_pipe))
+ {
+ ReleaseMutex (input_mutex);
+ set_errno (EIO);
+ totalread = -1;
+ goto out;
+ }
+
+ if (ptr && !bytes_in_pipe && !vmin && !time_to_wait)
+ {
+ ReleaseMutex (input_mutex);
+ mask_switch_to_nat_pipe (false, false);
+ len = (size_t) bytes_in_pipe;
+ return;
+ }
+
+ readlen = bytes_in_pipe ? MIN (len, sizeof (buf)) : 0;
+ if (get_ttyp ()->ti.c_lflag & ICANON && ptr)
+ readlen = MIN (bytes_in_pipe, readlen);
+
+#if 0
+ /* Why on earth is the read length reduced to vmin, even if more bytes
+ are available *and* len is bigger *and* the local buf is big enough?
+ Disable this code for now, it looks like a remnant of old. */
+ if (ptr && vmin && readlen > (unsigned) vmin)
+ readlen = vmin;
+#endif
+
+ DWORD n = 0;
+ if (readlen)
+ {
+ termios_printf ("reading %lu bytes (vtime %d)", readlen, vtime);
+ if (!ReadFile (get_handle (), buf, readlen, &n, NULL))
+ {
+ termios_printf ("read failed, %E");
+ ReleaseMutex (input_mutex);
+ set_errno (EIO);
+ totalread = -1;
+ goto out;
+ }
+ else
+ {
+ /* MSDN states that 5th prameter can be used to determine total
+ number of bytes in pipe, but for some reason this number doesn't
+ change after successful read. So we have to peek into the pipe
+ again to see if input is still available */
+ if (!bytes_available (bytes_in_pipe))
+ {
+ ReleaseMutex (input_mutex);
+ set_errno (EIO);
+ totalread = -1;
+ goto out;
+ }
+ if (n)
+ {
+ if (!(!ptr && len == UINT_MAX)) /* not tcflush() */
+ len -= n;
+ totalread += n;
+ if (ptr)
+ {
+ memcpy (ptr, buf, n);
+ ptr = (char *) ptr + n;
+ }
+ }
+ }
+ }
+
+ if (!bytes_in_pipe)
+ ResetEvent (input_available_event);
+
+ ReleaseMutex (input_mutex);
+
+ if (!ptr)
+ {
+ if (!bytes_in_pipe)
+ break;
+ continue;
+ }
+
+ if (get_ttyp ()->read_retval < 0) // read error
+ {
+ set_errno (-get_ttyp ()->read_retval);
+ totalread = -1;
+ break;
+ }
+ if (get_ttyp ()->read_retval == 0) //EOF
+ {
+ termios_printf ("saw EOF");
+ break;
+ }
+ if (get_ttyp ()->ti.c_lflag & ICANON || is_nonblocking ())
+ break;
+ if (vmin && totalread >= vmin)
+ break;
+
+ /* vmin == 0 && vtime == 0:
+ * we've already read all input, if any, so return immediately
+ * vmin == 0 && vtime > 0:
+ * we've waited for input 10*vtime ms in WFSO(input_available_event),
+ * no matter whether any input arrived, we shouldn't wait any longer,
+ * so return immediately
+ * vmin > 0 && vtime == 0:
+ * here, totalread < vmin, so continue waiting until more data
+ * arrive
+ * vmin > 0 && vtime > 0:
+ * similar to the previous here, totalread < vmin, and timer
+ * hadn't expired -- WFSO(input_available_event) != WAIT_TIMEOUT,
+ * so "restart timer" and wait until more data arrive
+ */
+
+ if (vmin == 0)
+ break;
+ }
+out:
+ termios_printf ("%d = read(%p, %lu)", totalread, ptr, len);
+ len = (size_t) totalread;
+ if (ptr0)
+ { /* Not tcflush() */
+ bool saw_eol = totalread > 0 && strchr ("\r\n", ptr0[totalread -1]);
+ mask_switch_to_nat_pipe (false, saw_eol || len == 0);
+ }
+}
+
+int
+fhandler_pty_slave::dup (fhandler_base *child, int flags)
+{
+ /* This code was added in Oct 2001 for some undisclosed reason.
+ However, setting the controlling tty on a dup causes rxvt to
+ hang when the parent does a dup since the controlling pgid changes.
+ Specifically testing for -2 (ctty has been setsid'ed) works around
+ this problem. However, it's difficult to see scenarios in which you
+ have a dup'able fd, no controlling tty, and not having run setsid.
+ So, we might want to consider getting rid of the set_ctty in tty-like dup
+ methods entirely at some point */
+ if (myself->ctty != -2)
+ myself->set_ctty (this, flags);
+ report_tty_counts (child, "duped slave", "");
+ return 0;
+}
+
+int
+fhandler_pty_master::dup (fhandler_base *child, int)
+{
+ report_tty_counts (child, "duped master", "");
+ return 0;
+}
+
+int
+fhandler_pty_slave::tcgetattr (struct termios *t)
+{
+ *t = get_ttyp ()->ti;
+
+ /* Workaround for rlwrap */
+ cygheap_fdenum cfd (false);
+ while (cfd.next () >= 0)
+ if (cfd->get_major () == DEV_PTYM_MAJOR
+ && cfd->get_minor () == get_minor ())
+ {
+ if (get_ttyp ()->pcon_start)
+ t->c_lflag &= ~(ICANON | ECHO);
+ if (get_ttyp ()->pcon_activated)
+ t->c_iflag &= ~ICRNL;
+ break;
+ }
+ return 0;
+}
+
+int
+fhandler_pty_slave::tcsetattr (int, const struct termios *t)
+{
+ acquire_output_mutex (mutex_timeout);
+ get_ttyp ()->ti = *t;
+ release_output_mutex ();
+ return 0;
+}
+
+int
+fhandler_pty_slave::tcflush (int queue)
+{
+ int ret = 0;
+
+ termios_printf ("tcflush(%d) handle %p", queue, get_handle ());
+
+ if (queue == TCIFLUSH || queue == TCIOFLUSH)
+ {
+ size_t len = UINT_MAX;
+ read (NULL, len);
+ ret = ((int) len) >= 0 ? 0 : -1;
+ }
+ if (queue == TCOFLUSH || queue == TCIOFLUSH)
+ {
+ /* do nothing for now. */
+ }
+
+ termios_printf ("%d=tcflush(%d)", ret, queue);
+ return ret;
+}
+
+int
+fhandler_pty_slave::ioctl (unsigned int cmd, void *arg)
+{
+ termios_printf ("ioctl (%x)", cmd);
+ int res = fhandler_termios::ioctl (cmd, arg);
+ if (res <= 0)
+ return res;
+
+ if (myself->pgid && get_ttyp ()->getpgid () != myself->pgid
+ && (unsigned) myself->ctty == FHDEV (DEV_PTYS_MAJOR, get_minor ())
+ && (get_ttyp ()->ti.c_lflag & TOSTOP))
+ {
+ /* background process */
+ termios_printf ("bg ioctl pgid %d, tpgid %d, %s", myself->pgid,
+ get_ttyp ()->getpgid (), myctty ());
+ raise (SIGTTOU);
+ }
+
+ int retval;
+ switch (cmd)
+ {
+ case TIOCGWINSZ:
+ case TIOCSWINSZ:
+ break;
+ case TIOCGPGRP:
+ {
+ pid_t pid = this->tcgetpgrp ();
+ if (pid < 0)
+ retval = -1;
+ else
+ {
+ *((pid_t *) arg) = pid;
+ retval = 0;
+ }
+ }
+ goto out;
+ case TIOCSPGRP:
+ retval = this->tcsetpgrp ((pid_t) (intptr_t) arg);
+ goto out;
+ case FIONREAD:
+ {
+ DWORD n;
+ if (!bytes_available (n))
+ {
+ set_errno (EINVAL);
+ retval = -1;
+ }
+ else
+ {
+ *(int *) arg = (int) n;
+ retval = 0;
+ }
+ }
+ goto out;
+ default:
+ return fhandler_base::ioctl (cmd, arg);
+ }
+
+ acquire_output_mutex (mutex_timeout);
+
+ get_ttyp ()->cmd = cmd;
+ get_ttyp ()->ioctl_retval = 0;
+ switch (cmd)
+ {
+ case TIOCGWINSZ:
+ get_ttyp ()->arg.winsize = get_ttyp ()->winsize;
+ *(struct winsize *) arg = get_ttyp ()->arg.winsize;
+ get_ttyp ()->winsize = get_ttyp ()->arg.winsize;
+ break;
+ case TIOCSWINSZ:
+ if (get_ttyp ()->winsize.ws_row != ((struct winsize *) arg)->ws_row
+ || get_ttyp ()->winsize.ws_col != ((struct winsize *) arg)->ws_col
+ || get_ttyp ()->winsize.ws_ypixel != ((struct winsize *) arg)->ws_ypixel
+ || get_ttyp ()->winsize.ws_xpixel != ((struct winsize *) arg)->ws_xpixel
+ )
+ {
+ if (get_ttyp ()->pcon_activated && get_ttyp ()->nat_pipe_owner_pid)
+ resize_pseudo_console ((struct winsize *) arg);
+ get_ttyp ()->arg.winsize = *(struct winsize *) arg;
+ get_ttyp ()->winsize = *(struct winsize *) arg;
+ get_ttyp ()->kill_pgrp (SIGWINCH);
+ }
+ break;
+ }
+
+ release_output_mutex ();
+ retval = get_ttyp ()->ioctl_retval;
+ if (retval < 0)
+ {
+ set_errno (-retval);
+ retval = -1;
+ }
+
+out:
+ termios_printf ("%d = ioctl(%x)", retval, cmd);
+ return retval;
+}
+
+int
+fhandler_pty_slave::fstat (struct stat *st)
+{
+ fhandler_base::fstat (st);
+
+ bool to_close = false;
+ if (!input_available_event)
+ {
+ char buf[MAX_PATH];
+ shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ());
+ input_available_event = OpenEvent (READ_CONTROL, TRUE, buf);
+ if (input_available_event)
+ to_close = true;
+ }
+ st->st_mode = S_IFCHR;
+ if (!input_available_event
+ || get_object_attribute (input_available_event, &st->st_uid, &st->st_gid,
+ &st->st_mode))
+ {
+ /* If we can't access the ACL, or if the tty doesn't actually exist,
+ then fake uid and gid to strict, system-like values. */
+ st->st_mode = S_IFCHR | S_IRUSR | S_IWUSR;
+ st->st_uid = 18;
+ st->st_gid = 544;
+ }
+ if (to_close)
+ CloseHandle (input_available_event);
+ return 0;
+}
+
+int
+fhandler_pty_slave::facl (int cmd, int nentries, aclent_t *aclbufp)
+{
+ int res = -1;
+ bool to_close = false;
+ security_descriptor sd;
+ mode_t attr = S_IFCHR;
+
+ switch (cmd)
+ {
+ case SETACL:
+ if (!aclsort (nentries, 0, aclbufp))
+ set_errno (ENOTSUP);
+ break;
+ case GETACL:
+ if (!aclbufp)
+ {
+ set_errno (EFAULT);
+ break;
+ }
+ fallthrough;
+ case GETACLCNT:
+ if (!input_available_event)
+ {
+ char buf[MAX_PATH];
+ shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ());
+ input_available_event = OpenEvent (READ_CONTROL, TRUE, buf);
+ if (input_available_event)
+ to_close = true;
+ }
+ if (!input_available_event
+ || get_object_sd (input_available_event, sd))
+ {
+ res = get_posix_access (NULL, &attr, NULL, NULL, aclbufp, nentries);
+ if (aclbufp && res == MIN_ACL_ENTRIES)
+ {
+ aclbufp[0].a_perm = S_IROTH | S_IWOTH;
+ aclbufp[0].a_id = 18;
+ aclbufp[1].a_id = 544;
+ }
+ break;
+ }
+ if (cmd == GETACL)
+ res = get_posix_access (sd, &attr, NULL, NULL, aclbufp, nentries);
+ else
+ res = get_posix_access (sd, &attr, NULL, NULL, NULL, 0);
+ break;
+ default:
+ set_errno (EINVAL);
+ break;
+ }
+ if (to_close)
+ CloseHandle (input_available_event);
+ return res;
+}
+
+/* Helper function for fchmod and fchown, which just opens all handles
+ and signals success via bool return. */
+bool
+fhandler_pty_slave::fch_open_handles (bool chown)
+{
+ char buf[MAX_PATH];
+ DWORD write_access = WRITE_DAC | (chown ? WRITE_OWNER : 0);
+
+ _tc = cygwin_shared->tty[get_minor ()];
+ shared_name (buf, INPUT_AVAILABLE_EVENT, get_minor ());
+ input_available_event = OpenEvent (READ_CONTROL | write_access,
+ TRUE, buf);
+ output_mutex = get_ttyp ()->open_output_mutex (write_access);
+ input_mutex = get_ttyp ()->open_input_mutex (write_access);
+ pipe_sw_mutex = get_ttyp ()->open_mutex (PIPE_SW_MUTEX, write_access);
+ inuse = get_ttyp ()->open_inuse (write_access);
+ if (!input_available_event || !output_mutex || !input_mutex || !inuse)
+ {
+ __seterrno ();
+ return false;
+ }
+ return true;
+}
+
+/* Helper function for fchmod and fchown, which sets the new security
+ descriptor on all objects representing the pty. */
+int
+fhandler_pty_slave::fch_set_sd (security_descriptor &sd, bool chown)
+{
+ security_descriptor sd_old;
+
+ get_object_sd (input_available_event, sd_old);
+ if (!set_object_sd (input_available_event, sd, chown)
+ && !set_object_sd (output_mutex, sd, chown)
+ && !set_object_sd (input_mutex, sd, chown)
+ && !set_object_sd (inuse, sd, chown))
+ return 0;
+ set_object_sd (input_available_event, sd_old, chown);
+ set_object_sd (output_mutex, sd_old, chown);
+ set_object_sd (input_mutex, sd_old, chown);
+ set_object_sd (inuse, sd_old, chown);
+ return -1;
+}
+
+/* Helper function for fchmod and fchown, which closes all object handles in
+ the pty. */
+void
+fhandler_pty_slave::fch_close_handles ()
+{
+ close_maybe (input_available_event);
+ close_maybe (output_mutex);
+ close_maybe (input_mutex);
+ close_maybe (inuse);
+}
+
+int
+fhandler_pty_slave::fchmod (mode_t mode)
+{
+ int ret = -1;
+ bool to_close = false;
+ security_descriptor sd;
+ uid_t uid;
+ gid_t gid;
+ mode_t orig_mode = S_IFCHR;
+
+ if (!input_available_event)
+ {
+ to_close = true;
+ if (!fch_open_handles (false))
+ goto errout;
+ }
+ sd.malloc (sizeof (SECURITY_DESCRIPTOR));
+ RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION);
+ if (!get_object_attribute (input_available_event, &uid, &gid, &orig_mode)
+ && !create_object_sd_from_attribute (uid, gid, S_IFCHR | mode, sd))
+ ret = fch_set_sd (sd, false);
+errout:
+ if (to_close)
+ fch_close_handles ();
+ return ret;
+}
+
+int
+fhandler_pty_slave::fchown (uid_t uid, gid_t gid)
+{
+ int ret = -1;
+ bool to_close = false;
+ security_descriptor sd;
+ uid_t o_uid;
+ gid_t o_gid;
+ mode_t mode = S_IFCHR;
+
+ if (uid == ILLEGAL_UID && gid == ILLEGAL_GID)
+ return 0;
+ if (!input_available_event)
+ {
+ to_close = true;
+ if (!fch_open_handles (true))
+ goto errout;
+ }
+ sd.malloc (sizeof (SECURITY_DESCRIPTOR));
+ RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION);
+ if (!get_object_attribute (input_available_event, &o_uid, &o_gid, &mode))
+ {
+ if (uid == ILLEGAL_UID)
+ uid = o_uid;
+ if (gid == ILLEGAL_GID)
+ gid = o_gid;
+ if (uid == o_uid && gid == o_gid)
+ ret = 0;
+ else if (!create_object_sd_from_attribute (uid, gid, mode, sd))
+ ret = fch_set_sd (sd, true);
+ }
+errout:
+ if (to_close)
+ fch_close_handles ();
+ return ret;
+}
+
+/*******************************************************
+ fhandler_pty_master
+*/
+fhandler_pty_master::fhandler_pty_master (int unit)
+ : fhandler_pty_common (), pktmode (0), master_ctl (NULL),
+ master_thread (NULL), from_master_nat (NULL), to_master_nat (NULL),
+ from_slave_nat (NULL), to_slave_nat (NULL), echo_r (NULL), echo_w (NULL),
+ dwProcessId (0), to_master (NULL), from_master (NULL),
+ master_fwd_thread (NULL)
+{
+ if (unit >= 0)
+ dev ().parse (DEV_PTYM_MAJOR, unit);
+ set_name ("/dev/ptmx");
+}
+
+int
+fhandler_pty_master::open (int flags, mode_t)
+{
+ if (!setup ())
+ return 0;
+ set_open_status ();
+ dwProcessId = GetCurrentProcessId ();
+ return 1;
+}
+
+bool
+fhandler_pty_master::open_setup (int flags)
+{
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+ char buf[sizeof ("opened pty master for ptyNNNNNNNNNNN")];
+ __small_sprintf (buf, "opened pty master for pty%d", get_minor ());
+ report_tty_counts (this, buf, "");
+ return fhandler_base::open_setup (flags);
+}
+
+off_t
+fhandler_pty_common::lseek (off_t, int)
+{
+ set_errno (ESPIPE);
+ return -1;
+}
+
+int
+fhandler_pty_common::close ()
+{
+ termios_printf ("pty%d <%p,%p> closing",
+ get_minor (), get_handle (), get_output_handle ());
+ if (!ForceCloseHandle (input_mutex))
+ termios_printf ("CloseHandle (input_mutex<%p>), %E", input_mutex);
+ if (!ForceCloseHandle (pipe_sw_mutex))
+ termios_printf ("CloseHandle (pipe_sw_mutex<%p>), %E", pipe_sw_mutex);
+ if (!ForceCloseHandle1 (get_handle (), from_pty))
+ termios_printf ("CloseHandle (get_handle ()<%p>), %E", get_handle ());
+ if (!ForceCloseHandle1 (get_output_handle (), to_pty))
+ termios_printf ("CloseHandle (get_output_handle ()<%p>), %E",
+ get_output_handle ());
+
+ return 0;
+}
+
+void
+fhandler_pty_common::resize_pseudo_console (struct winsize *ws)
+{
+ COORD size;
+ size.X = ws->ws_col;
+ size.Y = ws->ws_row;
+ HPCON_INTERNAL hpcon_local;
+ HANDLE pcon_owner =
+ OpenProcess (PROCESS_DUP_HANDLE, FALSE, get_ttyp ()->nat_pipe_owner_pid);
+ DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_write_pipe,
+ GetCurrentProcess (), &hpcon_local.hWritePipe,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ acquire_attach_mutex (mutex_timeout);
+ ResizePseudoConsole ((HPCON) &hpcon_local, size);
+ release_attach_mutex ();
+ CloseHandle (pcon_owner);
+ CloseHandle (hpcon_local.hWritePipe);
+}
+
+void
+fhandler_pty_master::cleanup ()
+{
+ report_tty_counts (this, "closing master", "");
+ if (archetype)
+ from_master_nat = from_master =
+ to_master_nat = to_master = from_slave_nat = to_slave_nat = NULL;
+ fhandler_base::cleanup ();
+}
+
+int
+fhandler_pty_master::close ()
+{
+ OBJECT_BASIC_INFORMATION obi;
+ NTSTATUS status;
+
+ termios_printf ("closing from_master_nat(%p)/from_master(%p)/to_master_nat(%p)/to_master(%p) since we own them(%u)",
+ from_master_nat, from_master,
+ to_master_nat, to_master, dwProcessId);
+ if (cygwin_finished_initializing)
+ {
+ if (master_ctl && get_ttyp ()->master_pid == myself->pid)
+ {
+ char buf[MAX_PATH];
+ pipe_request req = { (DWORD) -1 };
+ pipe_reply repl;
+ DWORD len;
+
+ __small_sprintf (buf, "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, get_minor ());
+ acquire_output_mutex (mutex_timeout);
+ if (master_ctl)
+ {
+ CallNamedPipe (buf, &req, sizeof req, &repl, sizeof repl, &len,
+ 500);
+ CloseHandle (master_ctl);
+ master_thread->detach ();
+ get_ttyp ()->set_master_ctl_closed ();
+ master_ctl = NULL;
+ }
+ release_output_mutex ();
+ get_ttyp ()->stop_fwd_thread = true;
+ WriteFile (to_master_nat, "", 0, &len, NULL);
+ master_fwd_thread->detach ();
+ if (helper_goodbye)
+ {
+ SetEvent (helper_goodbye);
+ WaitForSingleObject (helper_h_process, INFINITE);
+ CloseHandle (helper_h_process);
+ CloseHandle (helper_goodbye);
+ helper_pid = 0;
+ helper_h_process = 0;
+ helper_goodbye = NULL;
+ }
+ }
+ }
+
+ /* Check if the last master handle has been closed. If so, set
+ input_available_event to wake up potentially waiting slaves. */
+ acquire_output_mutex (mutex_timeout);
+ status = NtQueryObject (get_output_handle (), ObjectBasicInformation,
+ &obi, sizeof obi, NULL);
+ fhandler_pty_common::close ();
+ release_output_mutex ();
+ if (!ForceCloseHandle (output_mutex))
+ termios_printf ("CloseHandle (output_mutex<%p>), %E", output_mutex);
+ if (!NT_SUCCESS (status))
+ debug_printf ("NtQueryObject: %y", status);
+ else if (obi.HandleCount == 1)
+ {
+ termios_printf ("Closing last master of pty%d", get_minor ());
+ if (get_ttyp ()->getsid () > 0)
+ kill (get_ttyp ()->getsid (), SIGHUP);
+ SetEvent (input_available_event);
+ }
+
+ if (!ForceCloseHandle (from_master_nat))
+ termios_printf ("error closing from_master_nat %p, %E", from_master_nat);
+ if (!ForceCloseHandle (to_master_nat))
+ termios_printf ("error closing to_master_nat %p, %E", to_master_nat);
+ from_master_nat = to_master_nat = NULL;
+ if (!ForceCloseHandle (from_slave_nat))
+ termios_printf ("error closing from_slave_nat %p, %E", from_slave_nat);
+ from_slave_nat = NULL;
+ if (!ForceCloseHandle (to_master))
+ termios_printf ("error closing to_master %p, %E", to_master);
+ to_master = NULL;
+ ForceCloseHandle (echo_r);
+ ForceCloseHandle (echo_w);
+ echo_r = echo_w = NULL;
+ if (to_slave_nat)
+ ForceCloseHandle (to_slave_nat);
+ to_slave_nat = NULL;
+
+ if (have_execed || get_ttyp ()->master_pid != myself->pid)
+ termios_printf ("not clearing: %d, master_pid %d",
+ have_execed, get_ttyp ()->master_pid);
+ if (!ForceCloseHandle (input_available_event))
+ termios_printf ("CloseHandle (input_available_event<%p>), %E",
+ input_available_event);
+
+ /* The from_master must be closed last so that the same pty is not
+ allocated before cleaning up the other corresponding instances. */
+ if (!ForceCloseHandle (from_master))
+ termios_printf ("error closing from_master %p, %E", from_master);
+ from_master = NULL;
+
+ return 0;
+}
+
+ssize_t
+fhandler_pty_master::write (const void *ptr, size_t len)
+{
+ ssize_t ret;
+ char *p = (char *) ptr;
+ termios &ti = tc ()->ti;
+
+ bg_check_types bg = bg_check (SIGTTOU);
+ if (bg <= bg_eof)
+ return (ssize_t) bg;
+
+ push_process_state process_state (PID_TTYOU);
+
+ if (get_ttyp ()->pcon_start)
+ { /* Reaches here when pseudo console initialization is on going. */
+ /* Pseudo condole support uses "CSI6n" to get cursor position.
+ If the reply for "CSI6n" is divided into multiple writes,
+ pseudo console sometimes does not recognize it. Therefore,
+ put them together into wpbuf and write all at once. */
+ static const int wpbuf_len = strlen ("\033[32768;32868R");
+ static char wpbuf[wpbuf_len];
+ static int ixput = 0;
+ static int state = 0;
+
+ DWORD n;
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ for (size_t i = 0; i < len; i++)
+ {
+ if (p[i] == '\033')
+ {
+ if (ixput)
+ line_edit (wpbuf, ixput, ti, &ret);
+ ixput = 0;
+ state = 1;
+ }
+ if (state == 1)
+ {
+ if (ixput < wpbuf_len)
+ wpbuf[ixput++] = p[i];
+ else
+ {
+ if (!get_ttyp ()->req_xfer_input)
+ WriteFile (to_slave_nat, wpbuf, ixput, &n, NULL);
+ ixput = 0;
+ wpbuf[ixput++] = p[i];
+ }
+ }
+ else
+ line_edit (p + i, 1, ti, &ret);
+ if (state == 1 && p[i] == 'R')
+ state = 2;
+ }
+ if (state == 2)
+ {
+ /* req_xfer_input is true if "ESC[6n" was sent just for
+ triggering transfer_input() in master. In this case,
+ the responce sequence should not be written. */
+ if (!get_ttyp ()->req_xfer_input)
+ WriteFile (to_slave_nat, wpbuf, ixput, &n, NULL);
+ ixput = 0;
+ state = 0;
+ get_ttyp ()->req_xfer_input = false;
+ get_ttyp ()->pcon_start = false;
+ }
+ ReleaseMutex (input_mutex);
+
+ if (!get_ttyp ()->pcon_start)
+ { /* Pseudo console initialization has been done in above code. */
+ pinfo pp (get_ttyp ()->pcon_start_pid);
+ bool pcon_fg = (pp && get_ttyp ()->getpgid () == pp->pgid);
+ /* GDB may set WINPID rather than cygwin PID to process group
+ when the debugged process is a non-cygwin process.*/
+ pcon_fg |= !pinfo (get_ttyp ()->getpgid ());
+ if (get_ttyp ()->switch_to_nat_pipe && pcon_fg
+ && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
+ {
+ /* This accept_input() call is needed in order to transfer input
+ which is not accepted yet to non-cygwin pipe. */
+ if (get_readahead_valid ())
+ accept_input ();
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ fhandler_pty_slave::transfer_input (tty::to_nat, from_master,
+ get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+ get_ttyp ()->pcon_start_pid = 0;
+ }
+
+ return len;
+ }
+
+ /* Write terminal input to to_slave_nat pipe instead of output_handle
+ if current application is native console application. */
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ if (to_be_read_from_nat_pipe () && get_ttyp ()->pcon_activated
+ && get_ttyp ()->pty_input_state == tty::to_nat)
+ { /* Reaches here when non-cygwin app is foreground and pseudo console
+ is activated. */
+ tmp_pathbuf tp;
+ char *buf = (char *) ptr;
+ size_t nlen = len;
+ if (get_ttyp ()->term_code_page != CP_UTF8)
+ {
+ static mbstate_t mbp;
+ buf = tp.c_get ();
+ nlen = NT_MAX_PATH;
+ convert_mb_str (CP_UTF8, buf, &nlen,
+ get_ttyp ()->term_code_page, (const char *) ptr, len,
+ &mbp);
+ }
+
+ for (size_t i = 0; i < nlen; i++)
+ {
+ process_sig_state r = process_sigs (buf[i], get_ttyp (), this);
+ if (r == done_with_debugger)
+ {
+ for (size_t j = i; j < nlen - 1; j++)
+ buf[j] = buf[j + 1];
+ nlen--;
+ i--;
+ }
+ process_stop_start (buf[i], get_ttyp ());
+ }
+
+ DWORD n;
+ WriteFile (to_slave_nat, buf, nlen, &n, NULL);
+ ReleaseMutex (input_mutex);
+
+ return len;
+ }
+
+ /* The code path reaches here when pseudo console is not activated
+ or cygwin process is foreground even though pseudo console is
+ activated. */
+
+ /* This input transfer is needed when cygwin-app which is started from
+ non-cygwin app is terminated if pseudo console is disabled. */
+ if (to_be_read_from_nat_pipe () && !get_ttyp ()->pcon_activated
+ && get_ttyp ()->pty_input_state == tty::to_cyg)
+ {
+ acquire_attach_mutex (mutex_timeout);
+ fhandler_pty_slave::transfer_input (tty::to_nat, from_master,
+ get_ttyp (), input_available_event);
+ release_attach_mutex ();
+ }
+ ReleaseMutex (input_mutex);
+
+ line_edit_status status = line_edit (p, len, ti, &ret);
+ if (status > line_edit_signalled && status != line_edit_pipe_full)
+ ret = -1;
+ return ret;
+}
+
+void
+fhandler_pty_master::read (void *ptr, size_t& len)
+{
+ bg_check_types bg = bg_check (SIGTTIN);
+ if (bg <= bg_eof)
+ {
+ len = (size_t) bg;
+ return;
+ }
+ push_process_state process_state (PID_TTYIN);
+ len = (size_t) process_slave_output ((char *) ptr, len, pktmode);
+}
+
+int
+fhandler_pty_master::tcgetattr (struct termios *t)
+{
+ *t = cygwin_shared->tty[get_minor ()]->ti;
+ /* Workaround for rlwrap v0.40 or later */
+ if (get_ttyp ()->pcon_start)
+ t->c_lflag &= ~(ICANON | ECHO);
+ if (get_ttyp ()->pcon_activated)
+ t->c_iflag &= ~ICRNL;
+ return 0;
+}
+
+int
+fhandler_pty_master::tcsetattr (int, const struct termios *t)
+{
+ cygwin_shared->tty[get_minor ()]->ti = *t;
+ return 0;
+}
+
+int
+fhandler_pty_master::tcflush (int queue)
+{
+ int ret = 0;
+
+ termios_printf ("tcflush(%d) handle %p", queue, get_handle ());
+
+ if (queue == TCIFLUSH || queue == TCIOFLUSH)
+ ret = process_slave_output (NULL, OUT_BUFFER_SIZE, 0);
+ if (queue == TCOFLUSH || queue == TCIOFLUSH)
+ {
+ /* do nothing for now. */
+ }
+
+ termios_printf ("%d=tcflush(%d)", ret, queue);
+ return ret;
+}
+
+int
+fhandler_pty_master::ioctl (unsigned int cmd, void *arg)
+{
+ int res = fhandler_termios::ioctl (cmd, arg);
+ if (res <= 0)
+ return res;
+
+ switch (cmd)
+ {
+ case TIOCPKT:
+ pktmode = *(int *) arg;
+ break;
+ case TIOCGWINSZ:
+ *(struct winsize *) arg = get_ttyp ()->winsize;
+ break;
+ case TIOCSWINSZ:
+ if (get_ttyp ()->winsize.ws_row != ((struct winsize *) arg)->ws_row
+ || get_ttyp ()->winsize.ws_col != ((struct winsize *) arg)->ws_col
+ || get_ttyp ()->winsize.ws_ypixel != ((struct winsize *) arg)->ws_ypixel
+ || get_ttyp ()->winsize.ws_xpixel != ((struct winsize *) arg)->ws_xpixel
+ )
+ {
+ if (get_ttyp ()->pcon_activated && get_ttyp ()->nat_pipe_owner_pid)
+ resize_pseudo_console ((struct winsize *) arg);
+ get_ttyp ()->winsize = *(struct winsize *) arg;
+ get_ttyp ()->kill_pgrp (SIGWINCH);
+ }
+ break;
+ case TIOCGPGRP:
+ *((pid_t *) arg) = this->tcgetpgrp ();
+ break;
+ case TIOCSPGRP:
+ return this->tcsetpgrp ((pid_t) (intptr_t) arg);
+ case FIONREAD:
+ {
+ DWORD n;
+ if (!bytes_available (n))
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ *(int *) arg = (int) n;
+ }
+ break;
+ default:
+ return fhandler_base::ioctl (cmd, arg);
+ }
+ return 0;
+}
+
+int
+fhandler_pty_master::ptsname_r (char *buf, size_t buflen)
+{
+ char tmpbuf[TTY_NAME_MAX];
+
+ __ptsname (tmpbuf, get_minor ());
+ if (buflen <= strlen (tmpbuf))
+ {
+ set_errno (ERANGE);
+ return ERANGE;
+ }
+ strcpy (buf, tmpbuf);
+ return 0;
+}
+
+void
+fhandler_pty_common::set_close_on_exec (bool val)
+{
+ // Cygwin processes will handle this specially on exec.
+ close_on_exec (val);
+}
+
+void
+fhandler_pty_slave::setup_locale (void)
+{
+ extern UINT __eval_codepage_from_internal_charset ();
+
+ if (!get_ttyp ()->term_code_page)
+ {
+ get_ttyp ()->term_code_page = __eval_codepage_from_internal_charset ();
+ acquire_attach_mutex (mutex_timeout);
+ SetConsoleCP (get_ttyp ()->term_code_page);
+ SetConsoleOutputCP (get_ttyp ()->term_code_page);
+ release_attach_mutex ();
+ }
+}
+
+bg_check_types
+fhandler_pty_slave::bg_check (int sig, bool dontsignal)
+{
+ reset_switch_to_nat_pipe ();
+ return fhandler_termios::bg_check (sig, dontsignal);
+}
+
+void
+fhandler_pty_slave::fixup_after_fork (HANDLE parent)
+{
+ create_invisible_console ();
+
+ // fork_fixup (parent, inuse, "inuse");
+ // fhandler_pty_common::fixup_after_fork (parent);
+ report_tty_counts (this, "inherited", "");
+}
+
+void
+fhandler_pty_slave::fixup_after_exec ()
+{
+ if (!close_on_exec ())
+ fixup_after_fork (NULL); /* No parent handle required. */
+
+ /* Hook Console API */
+#define DO_HOOK(module, name) \
+ if (!name##_Orig) \
+ { \
+ void *api = hook_api (module, #name, (void *) name##_Hooked); \
+ name##_Orig = (__typeof__ (name) *) api; \
+ /*if (api) system_printf (#name " hooked.");*/ \
+ }
+ /* CreateProcess() is hooked for GDB etc. */
+ DO_HOOK (NULL, CreateProcessA);
+ DO_HOOK (NULL, CreateProcessW);
+}
+
+/* This thread function handles the master control pipe. It waits for a
+ client to connect. Then it checks if the client process has permissions
+ to access the tty handles. If so, it opens the client process and
+ duplicates the handles into that process. If that fails, it sends a reply
+ with at least one handle set to NULL and an error code. Last but not
+ least, the client is disconnected and the thread waits for the next client.
+
+ A special case is when the master side of the tty is about to be closed.
+ The client side is the fhandler_pty_master::close function and it sends
+ a PID -1 in that case. A check is performed that the request to leave
+ really comes from the master process itself.
+
+ Since there's always only one pipe instance, there's a chance that clients
+ have to wait to connect to the master control pipe. Therefore the client
+ calls to CallNamedPipe should have a big enough timeout value. For now this
+ is 500ms. Hope that's enough. */
+
+/* The function pty_master_thread() should be static because the instance
+ is deleted if the master is dup()'ed and the original is closed. In
+ this case, dup()'ed instance still exists, therefore, master thread
+ is also still alive even though the instance has been deleted. As a
+ result, accesing member variables in this function causes access
+ violation. */
+
+DWORD
+fhandler_pty_master::pty_master_thread (const master_thread_param_t *p)
+{
+ bool exit = false;
+ GENERIC_MAPPING map = { EVENT_QUERY_STATE, EVENT_MODIFY_STATE, 0,
+ EVENT_QUERY_STATE | EVENT_MODIFY_STATE };
+ pipe_request req;
+ DWORD len;
+ security_descriptor sd;
+ HANDLE token;
+ PRIVILEGE_SET ps;
+ DWORD pid;
+ NTSTATUS status;
+
+ termios_printf ("Entered");
+ while (!exit && (ConnectNamedPipe (p->master_ctl, NULL)
+ || GetLastError () == ERROR_PIPE_CONNECTED))
+ {
+ pipe_reply repl = { NULL, NULL, NULL, NULL, NULL, NULL, 0 };
+ bool deimp = false;
+ NTSTATUS allow = STATUS_ACCESS_DENIED;
+ ACCESS_MASK access = EVENT_MODIFY_STATE;
+ HANDLE client = NULL;
+
+ if (!ReadFile (p->master_ctl, &req, sizeof req, &len, NULL))
+ {
+ termios_printf ("ReadFile, %E");
+ goto reply;
+ }
+ if (!GetNamedPipeClientProcessId (p->master_ctl, &pid))
+ pid = req.pid;
+ if (get_object_sd (p->input_available_event, sd))
+ {
+ termios_printf ("get_object_sd, %E");
+ goto reply;
+ }
+ cygheap->user.deimpersonate ();
+ deimp = true;
+ if (!ImpersonateNamedPipeClient (p->master_ctl))
+ {
+ termios_printf ("ImpersonateNamedPipeClient, %E");
+ goto reply;
+ }
+ status = NtOpenThreadToken (GetCurrentThread (), TOKEN_QUERY, TRUE,
+ &token);
+ if (!NT_SUCCESS (status))
+ {
+ termios_printf ("NtOpenThreadToken, %y", status);
+ SetLastError (RtlNtStatusToDosError (status));
+ goto reply;
+ }
+ len = sizeof ps;
+ status = NtAccessCheck (sd, token, access, &map, &ps, &len, &access,
+ &allow);
+ NtClose (token);
+ if (!NT_SUCCESS (status))
+ {
+ termios_printf ("NtAccessCheck, %y", status);
+ SetLastError (RtlNtStatusToDosError (status));
+ goto reply;
+ }
+ if (!RevertToSelf ())
+ {
+ termios_printf ("RevertToSelf, %E");
+ goto reply;
+ }
+ if (req.pid == (DWORD) -1) /* Request to finish thread. */
+ {
+ /* Check if the requesting process is the master process itself. */
+ if (pid == GetCurrentProcessId ())
+ exit = true;
+ goto reply;
+ }
+ if (NT_SUCCESS (allow))
+ {
+ client = OpenProcess (PROCESS_DUP_HANDLE, FALSE, pid);
+ if (!client)
+ {
+ termios_printf ("OpenProcess, %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->from_master_nat,
+ client, &repl.from_master_nat,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (from_master_nat), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->from_master,
+ client, &repl.from_master,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (from_master), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->to_master_nat,
+ client, &repl.to_master_nat,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (to_master_nat), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->to_master,
+ client, &repl.to_master,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (to_master), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->to_slave_nat,
+ client, &repl.to_slave_nat,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (to_slave_nat), %E");
+ goto reply;
+ }
+ if (!DuplicateHandle (GetCurrentProcess (), p->to_slave,
+ client, &repl.to_slave,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ termios_printf ("DuplicateHandle (to_slave), %E");
+ goto reply;
+ }
+ }
+reply:
+ repl.error = GetLastError ();
+ if (client)
+ CloseHandle (client);
+ if (deimp)
+ cygheap->user.reimpersonate ();
+ sd.free ();
+ termios_printf ("Reply: from %p, to %p, error %u",
+ repl.from_master_nat, repl.to_master_nat, repl.error );
+ if (!WriteFile (p->master_ctl, &repl, sizeof repl, &len, NULL))
+ termios_printf ("WriteFile, %E");
+ if (!DisconnectNamedPipe (p->master_ctl))
+ termios_printf ("DisconnectNamedPipe, %E");
+ }
+ termios_printf ("Leaving");
+ return 0;
+}
+
+static DWORD
+pty_master_thread (VOID *arg)
+{
+ fhandler_pty_master::master_thread_param_t p;
+ ((fhandler_pty_master *) arg)->get_master_thread_param (&p);
+ return fhandler_pty_master::pty_master_thread (&p);
+}
+
+/* The function pty_master_fwd_thread() should be static because the
+ instance is deleted if the master is dup()'ed and the original is
+ closed. In this case, dup()'ed instance still exists, therefore,
+ master forwarding thread is also still alive even though the instance
+ has been deleted. As a result, accesing member variables in this
+ function causes access violation. */
+
+DWORD
+fhandler_pty_master::pty_master_fwd_thread (const master_fwd_thread_param_t *p)
+{
+ DWORD rlen;
+ tmp_pathbuf tp;
+ char *outbuf = tp.c_get ();
+ char *mbbuf = tp.c_get ();
+ static mbstate_t mbp;
+
+ termios_printf ("Started.");
+ for (;;)
+ {
+ p->ttyp->fwd_last_time = GetTickCount ();
+ DWORD n;
+ p->ttyp->fwd_not_empty =
+ ::bytes_available (n, p->from_slave_nat) && n;
+ if (!ReadFile (p->from_slave_nat, outbuf, NT_MAX_PATH, &rlen, NULL))
+ {
+ termios_printf ("ReadFile for forwarding failed, %E");
+ break;
+ }
+ if (p->ttyp->stop_fwd_thread)
+ break;
+ ssize_t wlen = rlen;
+ char *ptr = outbuf;
+ if (p->ttyp->pcon_activated)
+ {
+ /* Avoid setting window title to "cygwin-console-helper.exe" */
+ int state = 0;
+ int start_at = 0;
+ for (DWORD i=0; i<rlen; i++)
+ if (outbuf[i] == '\033')
+ {
+ start_at = i;
+ state = 1;
+ continue;
+ }
+ else if ((state == 1 && outbuf[i] == ']') ||
+ (state == 2 && outbuf[i] == '0') ||
+ (state == 3 && outbuf[i] == ';'))
+ {
+ state ++;
+ continue;
+ }
+ else if (state == 4 && outbuf[i] == '\a')
+ {
+ const char *helper_str = "\\bin\\cygwin-console-helper.exe";
+ if (memmem (&outbuf[start_at], i + 1 - start_at,
+ helper_str, strlen (helper_str)))
+ {
+ memmove (&outbuf[start_at], &outbuf[i+1], rlen-i-1);
+ rlen = wlen = start_at + rlen - i - 1;
+ }
+ state = 0;
+ continue;
+ }
+ else if (outbuf[i] == '\a')
+ {
+ state = 0;
+ continue;
+ }
+
+ /* Remove CSI > Pm m */
+ state = 0;
+ start_at = 0;
+ for (DWORD i = 0; i < rlen; i++)
+ if (outbuf[i] == '\033')
+ {
+ start_at = i;
+ state = 1;
+ continue;
+ }
+ else if ((state == 1 && outbuf[i] == '[')
+ || (state == 2 && outbuf[i] == '>'))
+ {
+ state ++;
+ continue;
+ }
+ else if (state == 3 && (isdigit (outbuf[i]) || outbuf[i] == ';'))
+ continue;
+ else if (state == 3 && outbuf[i] == 'm')
+ {
+ memmove (&outbuf[start_at], &outbuf[i+1], rlen-i-1);
+ rlen = wlen = start_at + rlen - i - 1;
+ state = 0;
+ i = start_at - 1;
+ continue;
+ }
+ else
+ state = 0;
+
+ /* Remove OSC Ps ; ? BEL/ST */
+ for (DWORD i = 0; i < rlen; i++)
+ if (state == 0 && outbuf[i] == '\033')
+ {
+ start_at = i;
+ state = 1;
+ continue;
+ }
+ else if ((state == 1 && outbuf[i] == ']')
+ || (state == 2 && outbuf[i] == ';')
+ || (state == 3 && outbuf[i] == '?')
+ || (state == 4 && outbuf[i] == '\033'))
+ {
+ state ++;
+ continue;
+ }
+ else if (state == 2 && isdigit (outbuf[i]))
+ continue;
+ else if ((state == 4 && outbuf[i] == '\a')
+ || (state == 5 && outbuf[i] == '\\'))
+ {
+ memmove (&outbuf[start_at], &outbuf[i+1], rlen-i-1);
+ rlen = wlen = start_at + rlen - i - 1;
+ state = 0;
+ i = start_at - 1;
+ continue;
+ }
+ else
+ state = 0;
+
+ if (p->ttyp->term_code_page != CP_UTF8)
+ {
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (p->ttyp->term_code_page, mbbuf, &nlen,
+ CP_UTF8, ptr, wlen, &mbp);
+
+ ptr = mbbuf;
+ wlen = rlen = nlen;
+ }
+
+ /* OPOST processing was already done in pseudo console,
+ so just write it to to_master. */
+ DWORD written;
+ while (rlen>0)
+ {
+ if (!WriteFile (p->to_master, ptr, wlen, &written, NULL))
+ {
+ termios_printf ("WriteFile for forwarding failed, %E");
+ break;
+ }
+ ptr += written;
+ wlen = (rlen -= written);
+ }
+ continue;
+ }
+
+ UINT cp_from;
+ pinfo pinfo_target = pinfo (p->ttyp->invisible_console_pid);
+ DWORD target_pid = 0;
+ if (pinfo_target)
+ target_pid = pinfo_target->dwProcessId;
+ if (target_pid)
+ {
+ /* Slave attaches to a different console than master.
+ Therefore reattach here. */
+ DWORD resume_pid =
+ attach_console_temporarily (target_pid, p->helper_pid);
+ cp_from = GetConsoleOutputCP ();
+ resume_from_temporarily_attach (resume_pid);
+ }
+ else
+ cp_from = GetConsoleOutputCP ();
+
+ if (p->ttyp->term_code_page != cp_from)
+ {
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (p->ttyp->term_code_page, mbbuf, &nlen,
+ cp_from, ptr, wlen, &mbp);
+
+ ptr = mbbuf;
+ wlen = rlen = nlen;
+ }
+
+ WaitForSingleObject (p->output_mutex, mutex_timeout);
+ while (rlen>0)
+ {
+ if (!process_opost_output (p->to_master, ptr, wlen,
+ true /* disable output_stopped */,
+ p->ttyp, false))
+ {
+ termios_printf ("WriteFile for forwarding failed, %E");
+ break;
+ }
+ ptr += wlen;
+ wlen = (rlen -= wlen);
+ }
+ ReleaseMutex (p->output_mutex);
+ }
+ return 0;
+}
+
+static DWORD
+pty_master_fwd_thread (VOID *arg)
+{
+ fhandler_pty_master::master_fwd_thread_param_t p;
+ ((fhandler_pty_master *) arg)->get_master_fwd_thread_param (&p);
+ return fhandler_pty_master::pty_master_fwd_thread (&p);
+}
+
+inline static bool
+is_running_as_service (void)
+{
+ return check_token_membership (well_known_service_sid)
+ || cygheap->user.saved_sid () == well_known_system_sid;
+}
+
+bool
+fhandler_pty_master::setup ()
+{
+ int res;
+ security_descriptor sd;
+ SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE };
+
+ /* Find an unallocated pty to use. */
+ /* Create a pipe for cygwin app (master to slave) simultaneously. */
+ int unit = cygwin_shared->tty.allocate (from_master, get_output_handle ());
+ if (unit < 0)
+ return false;
+
+ ProtectHandle1 (get_output_handle (), to_pty);
+
+ tty& t = *cygwin_shared->tty[unit];
+ _tc = (tty_min *) &t;
+
+ tcinit (true); /* Set termios information. Force initialization. */
+
+ const char *errstr = NULL;
+ DWORD pipe_mode = PIPE_NOWAIT;
+
+ if (!SetNamedPipeHandleState (get_output_handle (), &pipe_mode, NULL, NULL))
+ termios_printf ("can't set output_handle(%p) to non-blocking mode",
+ get_output_handle ());
+
+ /* Pipe for non-cygwin apps (slave to master) */
+ char pipename[sizeof ("ptyNNNN-from-master-nat")];
+ __small_sprintf (pipename, "pty%d-to-master-nat", unit);
+ res = fhandler_pipe::create (&sec_none, &from_slave_nat, &to_master_nat,
+ fhandler_pty_common::pipesize, pipename, 0);
+ if (res)
+ {
+ errstr = "output pipe for non-cygwin apps";
+ goto err;
+ }
+
+ /* Pipe for cygwin apps (slave to master) */
+ __small_sprintf (pipename, "pty%d-to-master", unit);
+ res = fhandler_pipe::create (&sec_none, &get_handle (), &to_master,
+ fhandler_pty_common::pipesize, pipename, 0);
+ if (res)
+ {
+ errstr = "output pipe";
+ goto err;
+ }
+
+ /* Pipe for non-cygwin apps (master to slave) */
+ __small_sprintf (pipename, "pty%d-from-master-nat", unit);
+ /* FILE_FLAG_OVERLAPPED is specified here in order to prevent
+ PeekNamedPipe() from blocking in transfer_input().
+ Accordig to the official document, in order to access the handle
+ opened with FILE_FLAG_OVERLAPPED, it is mandatory to pass the
+ OVERLAPP structure, but in fact, it seems that the access will
+ fallback to the blocking access if it is not specified. */
+ res = fhandler_pipe::create (&sec_none, &from_master_nat, &to_slave_nat,
+ fhandler_pty_common::pipesize, pipename,
+ FILE_FLAG_OVERLAPPED);
+ if (res)
+ {
+ errstr = "input pipe";
+ goto err;
+ }
+
+ ProtectHandle1 (get_handle (), from_pty);
+
+ __small_sprintf (pipename, "pty%d-echoloop", unit);
+ res = fhandler_pipe::create (&sec_none, &echo_r, &echo_w,
+ fhandler_pty_common::pipesize, pipename, 0);
+ if (res)
+ {
+ errstr = "echo pipe";
+ goto err;
+ }
+
+ /* Create security attribute. Default permissions are 0620. */
+ sd.malloc (sizeof (SECURITY_DESCRIPTOR));
+ RtlCreateSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION);
+ if (!create_object_sd_from_attribute (myself->uid, myself->gid,
+ S_IFCHR | S_IRUSR | S_IWUSR | S_IWGRP,
+ sd))
+ sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR) sd;
+
+ /* Carefully check that the input_available_event didn't already exist.
+ This is a measure to make sure that the event security descriptor
+ isn't occupied by a malicious process. We must make sure that the
+ event's security descriptor is what we expect it to be. */
+ if (!(input_available_event = t.get_event (errstr = INPUT_AVAILABLE_EVENT,
+ &sa, TRUE))
+ || GetLastError () == ERROR_ALREADY_EXISTS)
+ goto err;
+
+ char buf[MAX_PATH];
+ errstr = shared_name (buf, OUTPUT_MUTEX, unit);
+ if (!(output_mutex = CreateMutex (&sa, FALSE, buf)))
+ goto err;
+
+ errstr = shared_name (buf, INPUT_MUTEX, unit);
+ if (!(input_mutex = CreateMutex (&sa, FALSE, buf)))
+ goto err;
+
+ errstr = shared_name (buf, PIPE_SW_MUTEX, unit);
+ if (!(pipe_sw_mutex = CreateMutex (&sa, FALSE, buf)))
+ goto err;
+
+ if (!attach_mutex)
+ attach_mutex = CreateMutex (&sa, FALSE, NULL);
+
+ /* Create master control pipe which allows the master to duplicate
+ the pty pipe handles to processes which deserve it. */
+ __small_sprintf (buf, "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, unit);
+ master_ctl = CreateNamedPipe (buf, PIPE_ACCESS_DUPLEX
+ | FILE_FLAG_FIRST_PIPE_INSTANCE,
+ PIPE_WAIT | PIPE_TYPE_MESSAGE
+ | PIPE_READMODE_MESSAGE
+ | PIPE_REJECT_REMOTE_CLIENTS,
+ 1, 4096, 4096, 0, &sec_all_nih);
+ if (master_ctl == INVALID_HANDLE_VALUE)
+ {
+ errstr = "pty master control pipe";
+ goto err;
+ }
+
+ thread_param_copied_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ master_thread = new cygthread (::pty_master_thread, this, "ptym");
+ if (!master_thread)
+ {
+ errstr = "pty master control thread";
+ goto err;
+ }
+ WaitForSingleObject (thread_param_copied_event, INFINITE);
+
+ if (wincap.has_broken_attach_console ()
+ && _major (myself->ctty) == DEV_CONS_MAJOR
+ && !(!pinfo (myself->ppid) && getenv ("ConEmuPID")))
+ {
+ HANDLE hello = CreateEvent (&sec_none, true, false, NULL);
+ HANDLE goodbye = CreateEvent (&sec_none, true, false, NULL);
+ WCHAR cmd[MAX_PATH];
+ path_conv helper ("/bin/cygwin-console-helper.exe");
+ size_t len = helper.get_wide_win32_path_len ();
+ helper.get_wide_win32_path (cmd);
+ __small_swprintf (cmd + len, L" %p %p", hello, goodbye);
+
+ STARTUPINFOEXW si;
+ PROCESS_INFORMATION pi;
+ ZeroMemory (&si, sizeof (si));
+ si.StartupInfo.cb = sizeof (STARTUPINFOEXW);
+
+ SIZE_T bytesRequired;
+ InitializeProcThreadAttributeList (NULL, 1, 0, &bytesRequired);
+ si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)
+ HeapAlloc (GetProcessHeap (), 0, bytesRequired);
+ InitializeProcThreadAttributeList (si.lpAttributeList,
+ 1, 0, &bytesRequired);
+ HANDLE handles_to_inherit[] = {hello, goodbye};
+ UpdateProcThreadAttribute (si.lpAttributeList,
+ 0,
+ PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
+ handles_to_inherit,
+ sizeof (handles_to_inherit),
+ NULL, NULL);
+ if (CreateProcessW (NULL, cmd, &sec_none, &sec_none,
+ TRUE, EXTENDED_STARTUPINFO_PRESENT,
+ NULL, NULL, &si.StartupInfo, &pi))
+ {
+ WaitForSingleObject (hello, INFINITE);
+ CloseHandle (hello);
+ CloseHandle (pi.hThread);
+ helper_goodbye = goodbye;
+ helper_pid = pi.dwProcessId;
+ helper_h_process = pi.hProcess;
+ }
+ else
+ {
+ CloseHandle (hello);
+ CloseHandle (goodbye);
+ }
+ DeleteProcThreadAttributeList (si.lpAttributeList);
+ HeapFree (GetProcessHeap (), 0, si.lpAttributeList);
+ }
+
+ master_fwd_thread = new cygthread (::pty_master_fwd_thread, this, "ptymf");
+ if (!master_fwd_thread)
+ {
+ errstr = "pty master forwarding thread";
+ goto err;
+ }
+ WaitForSingleObject (thread_param_copied_event, INFINITE);
+ CloseHandle (thread_param_copied_event);
+
+ t.set_from_master_nat (from_master_nat);
+ t.set_from_master (from_master);
+ t.set_to_master_nat (to_master_nat);
+ t.set_to_master (to_master);
+ t.set_to_slave_nat (to_slave_nat);
+ t.set_to_slave (get_output_handle ());
+ t.winsize.ws_col = 80;
+ t.winsize.ws_row = 25;
+ t.master_pid = myself->pid;
+
+ dev ().parse (DEV_PTYM_MAJOR, unit);
+
+ t.master_is_running_as_service = is_running_as_service ();
+
+ termios_printf ("this %p, pty%d opened - from_pty <%p,%p>, to_pty %p",
+ this, unit, from_slave_nat, get_handle (),
+ get_output_handle ());
+ return true;
+
+err:
+ __seterrno ();
+ close_maybe (from_slave_nat);
+ close_maybe (to_slave_nat);
+ close_maybe (get_handle ());
+ close_maybe (get_output_handle ());
+ close_maybe (input_available_event);
+ close_maybe (output_mutex);
+ close_maybe (input_mutex);
+ close_maybe (from_master_nat);
+ close_maybe (to_master_nat);
+ close_maybe (to_master);
+ close_maybe (echo_r);
+ close_maybe (echo_w);
+ close_maybe (master_ctl);
+ /* The from_master must be closed last so that the same pty is not
+ allocated before cleaning up the other corresponding instances. */
+ close_maybe (from_master);
+ termios_printf ("pty%d open failed - failed to create %s", unit, errstr);
+ return false;
+}
+
+void
+fhandler_pty_master::fixup_after_fork (HANDLE parent)
+{
+ DWORD wpid = GetCurrentProcessId ();
+ fhandler_pty_master *arch = (fhandler_pty_master *) archetype;
+ if (arch->dwProcessId != wpid)
+ {
+ tty& t = *get_ttyp ();
+ if (myself->pid == t.master_pid)
+ {
+ t.set_from_master_nat (arch->from_master_nat);
+ t.set_from_master (arch->from_master);
+ t.set_to_master_nat (arch->to_master_nat);
+ t.set_to_master (arch->to_master);
+ }
+ arch->dwProcessId = wpid;
+ }
+ from_master_nat = arch->from_master_nat;
+ from_master = arch->from_master;
+ to_master_nat = arch->to_master_nat;
+ to_master = arch->to_master;
+#if 0 /* Not sure if this is necessary. */
+ from_slave_nat = arch->from_slave_nat;
+ to_slave_nat = arch->to_slave_nat;
+#endif
+ report_tty_counts (this, "inherited master", "");
+}
+
+void
+fhandler_pty_master::fixup_after_exec ()
+{
+ if (!close_on_exec ())
+ fixup_after_fork (spawn_info->parent);
+ else
+ from_master_nat = from_master = to_master_nat = to_master =
+ from_slave_nat = to_slave_nat = NULL;
+}
+
+BOOL
+fhandler_pty_common::process_opost_output (HANDLE h, const void *ptr,
+ ssize_t& len, bool is_echo,
+ tty *ttyp, bool is_nonblocking)
+{
+ ssize_t towrite = len;
+ BOOL res = TRUE;
+ if (ttyp->ti.c_lflag & FLUSHO)
+ return res; /* Discard write data */
+ while (towrite)
+ {
+ if (!is_echo)
+ {
+ if (ttyp->output_stopped && is_nonblocking)
+ {
+ if (towrite < len)
+ break;
+ else
+ {
+ set_errno (EAGAIN);
+ len = -1;
+ return TRUE;
+ }
+ }
+ while (ttyp->output_stopped)
+ cygwait (10);
+ }
+
+ if (!(ttyp->ti.c_oflag & OPOST)) // raw output mode
+ {
+ DWORD n = MIN (OUT_BUFFER_SIZE, towrite);
+ res = WriteFile (h, ptr, n, &n, NULL);
+ if (!res)
+ break;
+ ptr = (char *) ptr + n;
+ towrite -= n;
+ }
+ else // post-process output
+ {
+ char outbuf[OUT_BUFFER_SIZE + 1];
+ char *buf = (char *)ptr;
+ DWORD n = 0;
+ ssize_t rc = 0;
+ while (n < OUT_BUFFER_SIZE && rc < towrite)
+ {
+ switch (buf[rc])
+ {
+ case '\r':
+ if ((ttyp->ti.c_oflag & ONOCR)
+ && ttyp->column == 0)
+ {
+ rc++;
+ continue;
+ }
+ if (ttyp->ti.c_oflag & OCRNL)
+ {
+ outbuf[n++] = '\n';
+ rc++;
+ }
+ else
+ {
+ outbuf[n++] = buf[rc++];
+ ttyp->column = 0;
+ }
+ break;
+ case '\n':
+ if (ttyp->ti.c_oflag & ONLCR)
+ {
+ outbuf[n++] = '\r';
+ ttyp->column = 0;
+ }
+ if (ttyp->ti.c_oflag & ONLRET)
+ ttyp->column = 0;
+ outbuf[n++] = buf[rc++];
+ break;
+ default:
+ outbuf[n++] = buf[rc++];
+ ttyp->column++;
+ break;
+ }
+ }
+ res = WriteFile (h, outbuf, n, &n, NULL);
+ if (!res)
+ break;
+ ptr = (char *) ptr + rc;
+ towrite -= rc;
+ }
+ }
+ len -= towrite;
+ return res;
+}
+
+ /* Pseudo console supprot is realized using a tricky technic.
+ PTY need the pseudo console handles, however, they cannot
+ be retrieved by normal procedure. Therefore, run a helper
+ process in a pseudo console and get them from the helper.
+ Slave process will attach to the pseudo console in the
+ helper process using AttachConsole(). */
+bool
+fhandler_pty_slave::setup_pseudoconsole ()
+{
+ /* If the legacy console mode is enabled, pseudo console seems
+ not to work as expected. To determine console mode, registry
+ key ForceV2 in HKEY_CURRENT_USER\Console is checked. */
+ reg_key reg (HKEY_CURRENT_USER, KEY_READ, L"Console", NULL);
+ if (reg.error ())
+ return false;
+ if (reg.get_dword (L"ForceV2", 1) == 0)
+ {
+ termios_printf ("Pseudo console is disabled "
+ "because the legacy console mode is enabled.");
+ return false;
+ }
+
+ HANDLE hpConIn, hpConOut;
+ if (get_ttyp ()->pcon_activated)
+ { /* The pseudo console is already activated. */
+ if (GetStdHandle (STD_INPUT_HANDLE) == get_handle ())
+ { /* Send CSI6n just for requesting transfer input. */
+ DWORD n;
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ get_ttyp ()->req_xfer_input = true; /* indicates that this "ESC[6n"
+ is just for transfer input */
+ get_ttyp ()->pcon_start = true;
+ get_ttyp ()->pcon_start_pid = myself->pid;
+ WriteFile (get_output_handle (), "\033[6n", 4, &n, NULL);
+ ReleaseMutex (input_mutex);
+ while (get_ttyp ()->pcon_start_pid)
+ /* wait for completion of transfer_input() in master::write(). */
+ Sleep (1);
+ }
+ /* Attach to the pseudo console which already exits. */
+ HANDLE pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE,
+ get_ttyp ()->nat_pipe_owner_pid);
+ if (pcon_owner == NULL)
+ return false;
+ DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in,
+ GetCurrentProcess (), &hpConIn,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_out,
+ GetCurrentProcess (), &hpConOut,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (pcon_owner);
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ AttachConsole (get_ttyp ()->nat_pipe_owner_pid);
+ init_console_handler (false);
+ release_attach_mutex ();
+ goto skip_create;
+ }
+
+ STARTUPINFOEXW si;
+ PROCESS_INFORMATION pi;
+ HANDLE hello, goodbye;
+ HANDLE hr, hw;
+ HPCON hpcon;
+
+ do
+ { /* Create new pseudo console */
+ COORD size = {
+ (SHORT) get_ttyp ()->winsize.ws_col,
+ (SHORT) get_ttyp ()->winsize.ws_row
+ };
+ const DWORD inherit_cursor = 1;
+ hpcon = NULL;
+ SetLastError (ERROR_SUCCESS);
+ HRESULT res = CreatePseudoConsole (size, get_handle_nat (),
+ get_output_handle_nat (),
+ inherit_cursor, &hpcon);
+ if (res != S_OK || GetLastError () == ERROR_PROC_NOT_FOUND)
+ {
+ if (res != S_OK)
+ system_printf ("CreatePseudoConsole() failed. %08x %08x\n",
+ GetLastError (), res);
+ goto fallback;
+ }
+
+ SIZE_T bytesRequired;
+ InitializeProcThreadAttributeList (NULL, 2, 0, &bytesRequired);
+ ZeroMemory (&si, sizeof (si));
+ si.StartupInfo.cb = sizeof (STARTUPINFOEXW);
+ si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)
+ HeapAlloc (GetProcessHeap (), 0, bytesRequired);
+ if (si.lpAttributeList == NULL)
+ goto cleanup_pseudo_console;
+ if (!InitializeProcThreadAttributeList (si.lpAttributeList,
+ 2, 0, &bytesRequired))
+ goto cleanup_heap;
+ if (!UpdateProcThreadAttribute (si.lpAttributeList,
+ 0,
+ PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
+ hpcon, sizeof (hpcon), NULL, NULL))
+
+ goto cleanup_heap;
+
+ hello = CreateEvent (&sec_none, true, false, NULL);
+ goodbye = CreateEvent (&sec_none, true, false, NULL);
+ CreatePipe (&hr, &hw, &sec_none, 0);
+
+ HANDLE handles_to_inherit[] = {hello, goodbye, hw};
+ if (!UpdateProcThreadAttribute (si.lpAttributeList,
+ 0,
+ PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
+ handles_to_inherit,
+ sizeof (handles_to_inherit),
+ NULL, NULL))
+ goto cleanup_event_and_pipes;
+
+ /* Execute helper process */
+ WCHAR cmd[MAX_PATH];
+ path_conv helper ("/bin/cygwin-console-helper.exe");
+ size_t len = helper.get_wide_win32_path_len ();
+ helper.get_wide_win32_path (cmd);
+ __small_swprintf (cmd + len, L" %p %p %p", hello, goodbye, hw);
+ si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
+ si.StartupInfo.hStdInput = NULL;
+ si.StartupInfo.hStdOutput = NULL;
+ si.StartupInfo.hStdError = NULL;
+
+ get_ttyp ()->pcon_activated = true;
+ get_ttyp ()->pcon_start = true;
+ get_ttyp ()->pcon_start_pid = myself->pid;
+ if (!CreateProcessW (NULL, cmd, &sec_none, &sec_none,
+ TRUE, EXTENDED_STARTUPINFO_PRESENT,
+ NULL, NULL, &si.StartupInfo, &pi))
+ goto cleanup_event_and_pipes;
+
+ for (;;)
+ {
+ DWORD wait_result = WaitForSingleObject (hello, 500);
+ if (wait_result == WAIT_OBJECT_0)
+ break;
+ if (wait_result != WAIT_TIMEOUT)
+ goto cleanup_helper_with_hello;
+ DWORD exit_code;
+ if (!GetExitCodeProcess (pi.hProcess, &exit_code))
+ goto cleanup_helper_with_hello;
+ if (exit_code == STILL_ACTIVE)
+ continue;
+ if (exit_code != 0 ||
+ WaitForSingleObject (hello, 500) != WAIT_OBJECT_0)
+ goto cleanup_helper_with_hello;
+ break;
+ }
+ CloseHandle (hello);
+ CloseHandle (pi.hThread);
+
+ /* Duplicate pseudo console handles */
+ DWORD rlen;
+ char buf[64];
+ if (!ReadFile (hr, buf, sizeof (buf), &rlen, NULL))
+ goto cleanup_helper_process;
+ buf[rlen] = '\0';
+ sscanf (buf, "StdHandles=%p,%p", &hpConIn, &hpConOut);
+ if (!DuplicateHandle (pi.hProcess, hpConIn,
+ GetCurrentProcess (), &hpConIn, 0,
+ TRUE, DUPLICATE_SAME_ACCESS))
+ goto cleanup_helper_process;
+ if (!DuplicateHandle (pi.hProcess, hpConOut,
+ GetCurrentProcess (), &hpConOut, 0,
+ TRUE, DUPLICATE_SAME_ACCESS))
+ goto cleanup_pcon_in;
+
+ CloseHandle (hr);
+ CloseHandle (hw);
+ DeleteProcThreadAttributeList (si.lpAttributeList);
+ HeapFree (GetProcessHeap (), 0, si.lpAttributeList);
+
+ /* Attach to pseudo console */
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ AttachConsole (pi.dwProcessId);
+ init_console_handler (false);
+ release_attach_mutex ();
+
+ /* Terminate helper process */
+ SetEvent (goodbye);
+ WaitForSingleObject (pi.hProcess, INFINITE);
+ CloseHandle (goodbye);
+ CloseHandle (pi.hProcess);
+ }
+ while (false);
+
+skip_create:
+ do
+ {
+ /* Fixup handles */
+ HANDLE orig_input_handle_nat = get_handle_nat ();
+ HANDLE orig_output_handle_nat = get_output_handle_nat ();
+ cygheap_fdenum cfd (false);
+ while (cfd.next () >= 0)
+ if (cfd->get_device () == get_device ())
+ {
+ fhandler_base *fh = cfd;
+ fhandler_pty_slave *ptys = (fhandler_pty_slave *) fh;
+ if (ptys->get_handle_nat () == orig_input_handle_nat)
+ ptys->set_handle_nat (hpConIn);
+ if (ptys->get_output_handle_nat () == orig_output_handle_nat)
+ ptys->set_output_handle_nat (hpConOut);
+ }
+ CloseHandle (orig_input_handle_nat);
+ CloseHandle (orig_output_handle_nat);
+ }
+ while (false);
+
+ if (!process_alive (get_ttyp ()->nat_pipe_owner_pid))
+ get_ttyp ()->nat_pipe_owner_pid = myself->exec_dwProcessId;
+
+ if (hpcon && nat_pipe_owner_self (get_ttyp ()->nat_pipe_owner_pid))
+ {
+ HPCON_INTERNAL *hp = (HPCON_INTERNAL *) hpcon;
+ get_ttyp ()->h_pcon_write_pipe = hp->hWritePipe;
+ get_ttyp ()->h_pcon_condrv_reference = hp->hConDrvReference;
+ get_ttyp ()->h_pcon_conhost_process = hp->hConHostProcess;
+ DuplicateHandle (GetCurrentProcess (), hpConIn,
+ GetCurrentProcess (), &get_ttyp ()->h_pcon_in,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), hpConOut,
+ GetCurrentProcess (), &get_ttyp ()->h_pcon_out,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ /* Discard the pseudo console handler container here.
+ Reconstruct it temporary when it is needed. */
+ HeapFree (GetProcessHeap (), 0, hp);
+ }
+
+ acquire_attach_mutex (mutex_timeout);
+ if (get_ttyp ()->previous_code_page)
+ SetConsoleCP (get_ttyp ()->previous_code_page);
+ if (get_ttyp ()->previous_output_code_page)
+ SetConsoleOutputCP (get_ttyp ()->previous_output_code_page);
+
+ if (get_ttyp ()->getpgid () == myself->pgid)
+ {
+ termios &t = get_ttyp ()->ti;
+ DWORD mode;
+ /* Set input mode */
+ GetConsoleMode (hpConIn, &mode);
+ mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
+ if (t.c_lflag & ECHO)
+ mode |= ENABLE_ECHO_INPUT;
+ if (t.c_lflag & ICANON)
+ mode |= ENABLE_LINE_INPUT;
+ if (mode & ENABLE_ECHO_INPUT && !(mode & ENABLE_LINE_INPUT))
+ /* This is illegal, so turn off the echo here, and fake it
+ when we read the characters */
+ mode &= ~ENABLE_ECHO_INPUT;
+ if (t.c_lflag & ISIG)
+ mode |= ENABLE_PROCESSED_INPUT;
+ SetConsoleMode (hpConIn, mode);
+ /* Set output mode */
+ GetConsoleMode (hpConOut, &mode);
+ mode &= ~DISABLE_NEWLINE_AUTO_RETURN;
+ if (!(t.c_oflag & OPOST) || !(t.c_oflag & ONLCR))
+ mode |= DISABLE_NEWLINE_AUTO_RETURN;
+ SetConsoleMode (hpConOut, mode);
+ }
+ release_attach_mutex ();
+
+ return true;
+
+cleanup_helper_with_hello:
+ CloseHandle (hello);
+ CloseHandle (pi.hThread);
+ goto cleanup_helper_process;
+cleanup_pcon_in:
+ CloseHandle (hpConIn);
+cleanup_helper_process:
+ SetEvent (goodbye);
+ WaitForSingleObject (pi.hProcess, INFINITE);
+ CloseHandle (pi.hProcess);
+ goto skip_close_hello;
+cleanup_event_and_pipes:
+ CloseHandle (hello);
+skip_close_hello:
+ get_ttyp ()->pcon_start = false;
+ get_ttyp ()->pcon_start_pid = 0;
+ get_ttyp ()->pcon_activated = false;
+ CloseHandle (goodbye);
+ CloseHandle (hr);
+ CloseHandle (hw);
+cleanup_heap:
+ HeapFree (GetProcessHeap (), 0, si.lpAttributeList);
+cleanup_pseudo_console:
+ if (hpcon)
+ {
+ HPCON_INTERNAL *hp = (HPCON_INTERNAL *) hpcon;
+ HANDLE tmp = hp->hConHostProcess;
+ ClosePseudoConsole (hpcon);
+ CloseHandle (tmp);
+ }
+fallback:
+ return false;
+}
+
+/* Find a process to which the ownership of nat pipe should be handed over */
+DWORD
+fhandler_pty_slave::get_winpid_to_hand_over (tty *ttyp,
+ DWORD force_switch_to)
+{
+ DWORD switch_to = 0;
+ if (force_switch_to)
+ {
+ switch_to = force_switch_to;
+ ttyp->setpgid (force_switch_to + MAX_PID);
+ }
+ else if (nat_pipe_owner_self (ttyp->nat_pipe_owner_pid))
+ {
+ /* Search another native process which attaches to the same console */
+ DWORD current_pid = myself->exec_dwProcessId ?: myself->dwProcessId;
+ switch_to = get_console_process_id (current_pid, false, true, true);
+ if (!switch_to)
+ switch_to = get_console_process_id (current_pid, false, true, false);
+ }
+ return switch_to;
+}
+
+void
+fhandler_pty_slave::hand_over_only (tty *ttyp, DWORD force_switch_to)
+{
+ if (nat_pipe_owner_self (ttyp->nat_pipe_owner_pid))
+ {
+ DWORD switch_to = get_winpid_to_hand_over (ttyp, force_switch_to);
+ if (switch_to)
+ /* The process switch_to takes over the ownership of the nat pipe. */
+ ttyp->nat_pipe_owner_pid = switch_to;
+ else
+ {
+ /* Abandon the ownership of the nat pipe */
+ ttyp->nat_pipe_owner_pid = 0;
+ ttyp->switch_to_nat_pipe = false;
+ }
+ }
+}
+
+/* The function close_pseudoconsole() should be static so that it can
+ be called even after the fhandler_pty_slave instance is deleted. */
+void
+fhandler_pty_slave::close_pseudoconsole (tty *ttyp, DWORD force_switch_to)
+{
+ DWORD switch_to = get_winpid_to_hand_over (ttyp, force_switch_to);
+ acquire_attach_mutex (mutex_timeout);
+ ttyp->previous_code_page = GetConsoleCP ();
+ ttyp->previous_output_code_page = GetConsoleOutputCP ();
+ release_attach_mutex ();
+ if (nat_pipe_owner_self (ttyp->nat_pipe_owner_pid))
+ { /* I am owner of the nat pipe. */
+ if (switch_to)
+ {
+ /* Change pseudo console owner to another process (switch_to). */
+ HANDLE new_owner =
+ OpenProcess (PROCESS_DUP_HANDLE, FALSE, switch_to);
+ HANDLE new_write_pipe = NULL;
+ HANDLE new_condrv_reference = NULL;
+ HANDLE new_conhost_process = NULL;
+ HANDLE new_pcon_in = NULL, new_pcon_out = NULL;
+ DuplicateHandle (GetCurrentProcess (),
+ ttyp->h_pcon_write_pipe,
+ new_owner, &new_write_pipe,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (),
+ ttyp->h_pcon_condrv_reference,
+ new_owner, &new_condrv_reference,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (),
+ ttyp->h_pcon_conhost_process,
+ new_owner, &new_conhost_process,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), ttyp->h_pcon_in,
+ new_owner, &new_pcon_in,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), ttyp->h_pcon_out,
+ new_owner, &new_pcon_out,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (new_owner);
+ CloseHandle (ttyp->h_pcon_write_pipe);
+ CloseHandle (ttyp->h_pcon_condrv_reference);
+ CloseHandle (ttyp->h_pcon_conhost_process);
+ CloseHandle (ttyp->h_pcon_in);
+ CloseHandle (ttyp->h_pcon_out);
+ ttyp->nat_pipe_owner_pid = switch_to;
+ ttyp->h_pcon_write_pipe = new_write_pipe;
+ ttyp->h_pcon_condrv_reference = new_condrv_reference;
+ ttyp->h_pcon_conhost_process = new_conhost_process;
+ ttyp->h_pcon_in = new_pcon_in;
+ ttyp->h_pcon_out = new_pcon_out;
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ pinfo p (myself->ppid);
+ if (!p || !AttachConsole (p->dwProcessId))
+ AttachConsole (ATTACH_PARENT_PROCESS);
+ init_console_handler (false);
+ release_attach_mutex ();
+ }
+ else
+ { /* Close pseudo console and abandon the ownership of the nat pipe. */
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ pinfo p (myself->ppid);
+ if (!p || !AttachConsole (p->dwProcessId))
+ AttachConsole (ATTACH_PARENT_PROCESS);
+ init_console_handler (false);
+ release_attach_mutex ();
+ /* Reconstruct pseudo console handler container here for close */
+ HPCON_INTERNAL *hp =
+ (HPCON_INTERNAL *) HeapAlloc (GetProcessHeap (), 0,
+ sizeof (HPCON_INTERNAL));
+ hp->hWritePipe = ttyp->h_pcon_write_pipe;
+ hp->hConDrvReference = ttyp->h_pcon_condrv_reference;
+ hp->hConHostProcess = ttyp->h_pcon_conhost_process;
+ /* HeapFree() will be called in ClosePseudoConsole() */
+ ClosePseudoConsole ((HPCON) hp);
+ CloseHandle (ttyp->h_pcon_conhost_process);
+ ttyp->pcon_activated = false;
+ ttyp->switch_to_nat_pipe = false;
+ ttyp->nat_pipe_owner_pid = 0;
+ ttyp->pcon_start = false;
+ ttyp->pcon_start_pid = 0;
+ }
+ }
+ else
+ { /* Just detach from the pseudo console if I am not owner. */
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole ();
+ pinfo p (myself->ppid);
+ if (!p || !AttachConsole (p->dwProcessId))
+ AttachConsole (ATTACH_PARENT_PROCESS);
+ init_console_handler (false);
+ release_attach_mutex ();
+ }
+}
+
+static bool
+has_ansi_escape_sequences (const WCHAR *env)
+{
+ /* Retrieve TERM name */
+ const char *term = NULL;
+ char term_str[260];
+ if (env)
+ {
+ for (const WCHAR *p = env; *p != L'\0'; p += wcslen (p) + 1)
+ if (swscanf (p, L"TERM=%236s", term_str) == 1)
+ {
+ term = term_str;
+ break;
+ }
+ }
+ else
+ term = getenv ("TERM");
+
+ if (!term)
+ return false;
+
+ /* If cursor_home is not "\033[H", terminal is not supposed to
+ support ANSI escape sequences. */
+ char tinfo[260];
+ __small_sprintf (tinfo, "/usr/share/terminfo/%02x/%s", term[0], term);
+ path_conv path (tinfo);
+ WCHAR wtinfo[260];
+ path.get_wide_win32_path (wtinfo);
+ HANDLE h;
+ h = CreateFileW (wtinfo, GENERIC_READ, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, 0, NULL);
+ if (h == NULL)
+ return false;
+ char terminfo[4096];
+ DWORD n;
+ ReadFile (h, terminfo, sizeof (terminfo), &n, 0);
+ CloseHandle (h);
+
+ int num_size = 2;
+ if (*(int16_t *)terminfo == 01036 /* MAGIC2 */)
+ num_size = 4;
+ const int name_pos = 12; /* Position of terminal name */
+ const int name_size = *(int16_t *) (terminfo + 2);
+ const int bool_count = *(int16_t *) (terminfo + 4);
+ const int num_count = *(int16_t *) (terminfo + 6);
+ const int str_count = *(int16_t *) (terminfo + 8);
+ const int str_size = *(int16_t *) (terminfo + 10);
+ const int cursor_home = 12; /* cursor_home entry index */
+ if (cursor_home >= str_count)
+ return false;
+ int str_idx_pos = name_pos + name_size + bool_count + num_size * num_count;
+ if (str_idx_pos & 1)
+ str_idx_pos ++;
+ const int16_t *str_idx = (int16_t *) (terminfo + str_idx_pos);
+ const char *str_table = (const char *) (str_idx + str_count);
+ if (str_idx + cursor_home >= (int16_t *) (terminfo + n))
+ return false;
+ if (str_idx[cursor_home] == -1)
+ return false;
+ const char *cursor_home_str = str_table + str_idx[cursor_home];
+ if (cursor_home_str >= str_table + str_size)
+ return false;
+ if (cursor_home_str >= terminfo + n)
+ return false;
+ if (strcmp (cursor_home_str, "\033[H") != 0)
+ return false;
+ return true;
+}
+
+bool
+fhandler_pty_slave::term_has_pcon_cap (const WCHAR *env)
+{
+ if (get_ttyp ()->pcon_cap_checked)
+ return get_ttyp ()->has_csi6n;
+
+ DWORD n;
+ char buf[1024];
+ char *p;
+ int len;
+ int wait_cnt = 0;
+
+ /* Check if terminal has ANSI escape sequence. */
+ if (!has_ansi_escape_sequences (env))
+ goto maybe_dumb;
+
+ /* Check if terminal has CSI6n */
+ WaitForSingleObject (pipe_sw_mutex, INFINITE);
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ /* Set pcon_activated and pcon_start so that the response
+ will sent to io_handle_nat rather than io_handle. */
+ get_ttyp ()->pcon_activated = true;
+ /* pcon_start will be cleared in master write() when CSI6n is responded. */
+ get_ttyp ()->pcon_start = true;
+ WriteFile (get_output_handle (), "\033[6n", 4, &n, NULL);
+ ReleaseMutex (input_mutex);
+ p = buf;
+ len = sizeof (buf) - 1;
+ do
+ {
+ if (::bytes_available (n, get_handle_nat ()) && n)
+ {
+ ReadFile (get_handle_nat (), p, len, &n, NULL);
+ p += n;
+ len -= n;
+ *p = '\0';
+ char *p1 = strrchr (buf, '\033');
+ int x, y;
+ char c;
+ if (p1 == NULL || sscanf (p1, "\033[%d;%d%c", &y, &x, &c) != 3
+ || c != 'R')
+ continue;
+ wait_cnt = 0;
+ break;
+ }
+ else if (++wait_cnt > 100) /* Timeout */
+ goto not_has_csi6n;
+ else
+ Sleep (1);
+ }
+ while (len);
+ get_ttyp ()->pcon_activated = false;
+ get_ttyp ()->nat_pipe_owner_pid = 0;
+ ReleaseMutex (pipe_sw_mutex);
+ if (len == 0)
+ goto not_has_csi6n;
+
+ get_ttyp ()->has_csi6n = true;
+ get_ttyp ()->pcon_cap_checked = true;
+
+ return true;
+
+not_has_csi6n:
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ /* If CSI6n is not responded, pcon_start is not cleared
+ in master write(). Therefore, clear it here manually. */
+ get_ttyp ()->pcon_start = false;
+ get_ttyp ()->pcon_activated = false;
+ ReleaseMutex (input_mutex);
+ ReleaseMutex (pipe_sw_mutex);
+maybe_dumb:
+ get_ttyp ()->pcon_cap_checked = true;
+ return false;
+}
+
+void
+fhandler_pty_slave::create_invisible_console ()
+{
+ if (get_ttyp ()->need_invisible_console)
+ {
+ /* Detach from console device and create new invisible console. */
+ acquire_attach_mutex (mutex_timeout);
+ FreeConsole();
+ fhandler_console::need_invisible (true);
+ init_console_handler (false);
+ release_attach_mutex ();
+ get_ttyp ()->need_invisible_console = false;
+ get_ttyp ()->invisible_console_pid = myself->pid;
+ }
+ if (get_ttyp ()->invisible_console_pid
+ && !pinfo (get_ttyp ()->invisible_console_pid))
+ /* If primary slave process does not exist anymore,
+ this process becomes the primary. */
+ get_ttyp ()->invisible_console_pid = myself->pid;
+}
+
+void
+fhandler_pty_master::get_master_thread_param (master_thread_param_t *p)
+{
+ p->from_master_nat = from_master_nat;
+ p->from_master = from_master;
+ p->to_master_nat = to_master_nat;
+ p->to_master = to_master;
+ p->to_slave_nat = to_slave_nat;
+ p->to_slave = get_output_handle ();
+ p->master_ctl = master_ctl;
+ p->input_available_event = input_available_event;
+ SetEvent (thread_param_copied_event);
+}
+
+void
+fhandler_pty_master::get_master_fwd_thread_param (master_fwd_thread_param_t *p)
+{
+ p->to_master = to_master;
+ p->from_slave_nat = from_slave_nat;
+ p->output_mutex = output_mutex;
+ p->ttyp = get_ttyp ();
+ p->helper_pid = helper_pid;
+ SetEvent (thread_param_copied_event);
+}
+
+#define ALT_PRESSED (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)
+#define CTRL_PRESSED (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)
+void
+fhandler_pty_slave::transfer_input (tty::xfer_dir dir, HANDLE from, tty *ttyp,
+ HANDLE input_available_event)
+{
+ HANDLE to;
+ if (dir == tty::to_nat)
+ to = ttyp->to_slave_nat ();
+ else
+ to = ttyp->to_slave ();
+
+ pinfo p (ttyp->master_pid);
+ HANDLE pty_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE, p->dwProcessId);
+ if (pty_owner)
+ {
+ DuplicateHandle (pty_owner, to, GetCurrentProcess (), &to,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (pty_owner);
+ }
+ else
+ {
+ char pipe[MAX_PATH];
+ __small_sprintf (pipe,
+ "\\\\.\\pipe\\cygwin-%S-pty%d-master-ctl",
+ &cygheap->installation_key, ttyp->get_minor ());
+ pipe_request req = { GetCurrentProcessId () };
+ pipe_reply repl;
+ DWORD len;
+ if (!CallNamedPipe (pipe, &req, sizeof req,
+ &repl, sizeof repl, &len, 500))
+ return; /* What can we do? */
+ if (dir == tty::to_nat)
+ to = repl.to_slave_nat;
+ else
+ to = repl.to_slave;
+ }
+
+ UINT cp_from = 0, cp_to = 0;
+
+ if (dir == tty::to_nat)
+ {
+ cp_from = ttyp->term_code_page;
+ if (ttyp->pcon_activated)
+ cp_to = CP_UTF8;
+ else
+ cp_to = GetConsoleCP ();
+ }
+ else
+ {
+ cp_from = GetConsoleCP ();
+ cp_to = ttyp->term_code_page;
+ }
+
+ tmp_pathbuf tp;
+ char *buf = tp.c_get ();
+
+ bool transfered = false;
+
+ if (dir == tty::to_cyg && ttyp->pcon_activated)
+ { /* from handle is console handle */
+ /* Reaches here for nat->cyg case with pcon activated. */
+ INPUT_RECORD r[INREC_SIZE];
+ DWORD n;
+ while (PeekConsoleInputA (from, r, INREC_SIZE, &n) && n)
+ {
+ ReadConsoleInputA (from, r, n, &n);
+ if (ttyp->discard_input)
+ continue;
+ int len = 0;
+ char *ptr = buf;
+ for (DWORD i = 0; i < n; i++)
+ if (r[i].EventType == KEY_EVENT && r[i].Event.KeyEvent.bKeyDown)
+ {
+ DWORD ctrl_key_state = r[i].Event.KeyEvent.dwControlKeyState;
+ if (r[i].Event.KeyEvent.uChar.AsciiChar)
+ {
+ if ((ctrl_key_state & ALT_PRESSED)
+ && r[i].Event.KeyEvent.uChar.AsciiChar <= 0x7f)
+ buf[len++] = '\033'; /* Meta */
+ buf[len++] = r[i].Event.KeyEvent.uChar.AsciiChar;
+ }
+ /* Allow Ctrl-Space to emit ^@ */
+ else if (r[i].Event.KeyEvent.wVirtualKeyCode == '2'
+ && (ctrl_key_state & CTRL_PRESSED)
+ && !(ctrl_key_state & ALT_PRESSED))
+ buf[len++] = '\0';
+ else
+ { /* arrow/function keys */
+ /* FIXME: The current code generates cygwin terminal
+ sequence rather than xterm sequence. */
+ char tmp[16];
+ const char *add =
+ fhandler_console::get_nonascii_key (r[i], tmp);
+ if (add)
+ {
+ strcpy (buf + len, add);
+ len += strlen (add);
+ }
+ }
+ }
+ if (cp_to != cp_from)
+ {
+ static mbstate_t mbp;
+ char *mbbuf = tp.c_get ();
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (cp_to, mbbuf, &nlen, cp_from, buf, len, &mbp);
+ ptr = mbbuf;
+ len = nlen;
+ }
+ /* Call WriteFile() line by line */
+ char *p0 = ptr;
+ char *p_cr = (char *) memchr (p0, '\r', len - (p0 - ptr));
+ char *p_lf = (char *) memchr (p0, '\n', len - (p0 - ptr));
+ while (p_cr || p_lf)
+ {
+ char *p1 =
+ p_cr ? (p_lf ? ((p_cr + 1 == p_lf)
+ ? p_lf : min(p_cr, p_lf)) : p_cr) : p_lf;
+ *p1 = '\n';
+ n = p1 - p0 + 1;
+ if (n && WriteFile (to, p0, n, &n, NULL) && n)
+ transfered = true;
+ p0 = p1 + 1;
+ p_cr = (char *) memchr (p0, '\r', len - (p0 - ptr));
+ p_lf = (char *) memchr (p0, '\n', len - (p0 - ptr));
+ }
+ n = len - (p0 - ptr);
+ if (n && WriteFile (to, p0, n, &n, NULL) && n)
+ transfered = true;
+ }
+ }
+ else
+ { /* Reaches here when both cyg->nat and nat->cyg cases with
+ pcon not activated or cyg->nat case with pcon activated. */
+ DWORD bytes_in_pipe;
+ while (::bytes_available (bytes_in_pipe, from) && bytes_in_pipe)
+ {
+ DWORD n = MIN (bytes_in_pipe, NT_MAX_PATH);
+ ReadFile (from, buf, n, &n, NULL);
+ if (ttyp->discard_input)
+ continue;
+ char *ptr = buf;
+ if (dir == tty::to_nat)
+ {
+ char *p = buf;
+ if (ttyp->pcon_activated)
+ while ((p = (char *) memchr (p, '\n', n - (p - buf))))
+ *p = '\r';
+ else
+ while ((p = (char *) memchr (p, '\r', n - (p - buf))))
+ *p = '\n';
+ }
+ if (cp_to != cp_from)
+ {
+ static mbstate_t mbp;
+ char *mbbuf = tp.c_get ();
+ size_t nlen = NT_MAX_PATH;
+ convert_mb_str (cp_to, mbbuf, &nlen, cp_from, buf, n, &mbp);
+ ptr = mbbuf;
+ n = nlen;
+ }
+ if (n && WriteFile (to, ptr, n, &n, NULL) && n)
+ transfered = true;;
+ }
+ }
+
+ /* Fix input_available_event which indicates availability in cyg pipe. */
+ if (dir == tty::to_nat) /* all data is transfered to nat pipe,
+ so no data available in cyg pipe. */
+ ResetEvent (input_available_event);
+ else if (transfered) /* There is data transfered to cyg pipe. */
+ SetEvent (input_available_event);
+ ttyp->pty_input_state = dir;
+ ttyp->discard_input = false;
+}
+
+void
+fhandler_pty_slave::cleanup_before_exit ()
+{
+ if (myself->process_state & PID_NOTCYGWIN)
+ get_ttyp ()->wait_fwd ();
+}
+
+void
+fhandler_pty_slave::get_duplicated_handle_set (handle_set_t *p)
+{
+ DuplicateHandle (GetCurrentProcess (), get_handle_nat (),
+ GetCurrentProcess (), &p->from_master_nat,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), input_available_event,
+ GetCurrentProcess (), &p->input_available_event,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), input_mutex,
+ GetCurrentProcess (), &p->input_mutex,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+ DuplicateHandle (GetCurrentProcess (), pipe_sw_mutex,
+ GetCurrentProcess (), &p->pipe_sw_mutex,
+ 0, 0, DUPLICATE_SAME_ACCESS);
+}
+
+void
+fhandler_pty_slave::close_handle_set (handle_set_t *p)
+{
+ CloseHandle (p->from_master_nat);
+ p->from_master_nat = NULL;
+ CloseHandle (p->input_available_event);
+ p->input_available_event = NULL;
+ CloseHandle (p->input_mutex);
+ p->input_mutex = NULL;
+ CloseHandle (p->pipe_sw_mutex);
+ p->pipe_sw_mutex = NULL;
+}
+
+void
+fhandler_pty_slave::setup_for_non_cygwin_app (bool nopcon, PWCHAR envblock,
+ bool stdin_is_ptys)
+{
+ if (disable_pcon || !term_has_pcon_cap (envblock))
+ nopcon = true;
+ WaitForSingleObject (pipe_sw_mutex, INFINITE);
+ /* Setting switch_to_nat_pipe is necessary even if pseudo console
+ will not be activated. */
+ fhandler_base *fh = ::cygheap->fdtab[0];
+ if (fh && fh->get_major () == DEV_PTYS_MAJOR)
+ {
+ fhandler_pty_slave *ptys = (fhandler_pty_slave *) fh;
+ ptys->get_ttyp ()->switch_to_nat_pipe = true;
+ if (!process_alive (ptys->get_ttyp ()->nat_pipe_owner_pid))
+ ptys->get_ttyp ()->nat_pipe_owner_pid = myself->exec_dwProcessId;
+ }
+ bool pcon_enabled = false;
+ if (!nopcon)
+ pcon_enabled = setup_pseudoconsole ();
+ ReleaseMutex (pipe_sw_mutex);
+ /* For pcon enabled case, transfer_input() is called in master::write() */
+ if (!pcon_enabled && get_ttyp ()->getpgid () == myself->pgid
+ && stdin_is_ptys && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
+ {
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_nat, get_handle (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+}
+
+void
+fhandler_pty_slave::cleanup_for_non_cygwin_app (handle_set_t *p, tty *ttyp,
+ bool stdin_is_ptys,
+ DWORD force_switch_to)
+{
+ ttyp->wait_fwd ();
+ if (ttyp->getpgid () == myself->pgid && stdin_is_ptys
+ && ttyp->pty_input_state_eq (tty::to_nat))
+ {
+ WaitForSingleObject (p->input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_cyg, p->from_master_nat, ttyp,
+ p->input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (p->input_mutex);
+ }
+ WaitForSingleObject (p->pipe_sw_mutex, INFINITE);
+ if (ttyp->pcon_activated)
+ close_pseudoconsole (ttyp, force_switch_to);
+ else
+ hand_over_only (ttyp, force_switch_to);
+ ReleaseMutex (p->pipe_sw_mutex);
+}
+
+void
+fhandler_pty_slave::setpgid_aux (pid_t pid)
+{
+ reset_switch_to_nat_pipe ();
+
+ WaitForSingleObject (pipe_sw_mutex, INFINITE);
+ bool was_nat_fg = get_ttyp ()->nat_fg (tc ()->pgid);
+ bool nat_fg = get_ttyp ()->nat_fg (pid);
+ if (!was_nat_fg && nat_fg && get_ttyp ()->switch_to_nat_pipe
+ && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
+ {
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_nat, get_handle (), get_ttyp (),
+ input_available_event);
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+ else if (was_nat_fg && !nat_fg && get_ttyp ()->switch_to_nat_pipe
+ && get_ttyp ()->pty_input_state_eq (tty::to_nat))
+ {
+ bool attach_restore = false;
+ HANDLE from = get_handle_nat ();
+ DWORD resume_pid = 0;
+ WaitForSingleObject (input_mutex, mutex_timeout);
+ if (get_ttyp ()->pcon_activated && get_ttyp ()->nat_pipe_owner_pid
+ && !get_console_process_id (get_ttyp ()->nat_pipe_owner_pid, true))
+ {
+ HANDLE pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE,
+ get_ttyp ()->nat_pipe_owner_pid);
+ DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in,
+ GetCurrentProcess (), &from,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ CloseHandle (pcon_owner);
+ DWORD target_pid = get_ttyp ()->nat_pipe_owner_pid;
+ resume_pid = attach_console_temporarily (target_pid, 0);
+ attach_restore = true;
+ }
+ else
+ acquire_attach_mutex (mutex_timeout);
+ transfer_input (tty::to_cyg, from, get_ttyp (), input_available_event);
+ if (attach_restore)
+ resume_from_temporarily_attach (resume_pid);
+ else
+ release_attach_mutex ();
+ ReleaseMutex (input_mutex);
+ }
+ ReleaseMutex (pipe_sw_mutex);
+}
+
+bool
+fhandler_pty_master::need_send_ctrl_c_event ()
+{
+ /* If pseudo console is activated, sending CTRL_C_EVENT to non-cygwin
+ apps will be done in pseudo console, therefore, sending it in
+ fhandler_pty_master::write() duplicates that event for non-cygwin
+ apps. So return false if pseudo console is activated. */
+ return !(to_be_read_from_nat_pipe () && get_ttyp ()->pcon_activated
+ && get_ttyp ()->pty_input_state == tty::to_nat);
+}
+
+void
+fhandler_pty_slave::release_ownership_of_nat_pipe (tty *ttyp,
+ fhandler_termios *fh)
+{
+ if (fh->get_major () == DEV_PTYM_MAJOR
+ && nat_pipe_owner_self (ttyp->nat_pipe_owner_pid))
+ {
+ fhandler_pty_master *ptym = (fhandler_pty_master *) fh;
+ WaitForSingleObject (ptym->pipe_sw_mutex, INFINITE);
+ if (ttyp->pcon_activated)
+ /* Do not acquire/release_attach_mutex() here because
+ it has done in fhandler_termios::process_sigs(). */
+ close_pseudoconsole (ttyp);
+ else
+ hand_over_only (ttyp);
+ ReleaseMutex (ptym->pipe_sw_mutex);
+ }
+}
+
+DWORD
+fhandler_pty_common::attach_console_temporarily (DWORD target_pid,
+ DWORD helper_pid)
+{
+ DWORD resume_pid = 0;
+ acquire_attach_mutex (mutex_timeout);
+ pinfo pinfo_resume (myself->ppid);
+ if (helper_pid)
+ resume_pid = helper_pid;
+ else if (pinfo_resume)
+ resume_pid = pinfo_resume->dwProcessId;
+ if (!resume_pid)
+ resume_pid = get_console_process_id (myself->dwProcessId, false);
+ bool console_exists = fhandler_console::exists ();
+ if (!console_exists || resume_pid)
+ {
+ FreeConsole ();
+ AttachConsole (target_pid);
+ init_console_handler (false);
+ }
+ return console_exists ? resume_pid : (DWORD) -1;
+}
+
+void
+fhandler_pty_common::resume_from_temporarily_attach (DWORD resume_pid)
+{
+ bool console_exists = (resume_pid != (DWORD) -1);
+ if (!console_exists || resume_pid)
+ {
+ FreeConsole ();
+ if (console_exists)
+ if (!resume_pid || !AttachConsole (resume_pid))
+ AttachConsole (ATTACH_PARENT_PROCESS);
+ init_console_handler (false);
+ }
+ release_attach_mutex ();
+}
diff --git a/winsup/cygwin/fhandler/virtual.cc b/winsup/cygwin/fhandler/virtual.cc
new file mode 100644
index 000000000..21ff4f35e
--- /dev/null
+++ b/winsup/cygwin/fhandler/virtual.cc
@@ -0,0 +1,275 @@
+/* fhandler_virtual.cc: base fhandler class for virtual filesystems
+
+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 <cygwin/acl.h>
+#include <sys/statvfs.h>
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "sync.h"
+#include "child_info.h"
+
+#include <dirent.h>
+
+fhandler_virtual::fhandler_virtual ():
+ fhandler_base (), filebuf (NULL), fileid (-1)
+{
+}
+
+fhandler_virtual::~fhandler_virtual ()
+{
+ if (filebuf)
+ {
+ cfree (filebuf);
+ filebuf = NULL;
+ }
+}
+
+void
+fhandler_virtual::fixup_after_exec ()
+{
+}
+
+DIR *
+fhandler_virtual::opendir (int fd)
+{
+ DIR *dir;
+ DIR *res = NULL;
+ size_t len;
+
+ if (!virt_ftype_isdir (exists ()))
+ set_errno (ENOTDIR);
+ else if ((len = strlen (get_name ())) > PATH_MAX - 3)
+ set_errno (ENAMETOOLONG);
+ else if ((dir = (DIR *) malloc (sizeof (DIR))) == NULL)
+ set_errno (ENOMEM);
+ else if ((dir->__d_dirname = (char *) malloc (len + 3)) == NULL)
+ {
+ free (dir);
+ set_errno (ENOMEM);
+ }
+ else if ((dir->__d_dirent =
+ (struct dirent *) malloc (sizeof (struct dirent))) == NULL)
+ {
+ free (dir->__d_dirname);
+ free (dir);
+ set_errno (ENOMEM);
+ }
+ else
+ {
+ strcpy (dir->__d_dirname, get_name ());
+ dir->__d_dirent->__d_version = __DIRENT_VERSION;
+ dir->__d_cookie = __DIRENT_COOKIE;
+ dir->__handle = INVALID_HANDLE_VALUE;
+ dir->__d_position = 0;
+ dir->__flags = 0;
+
+ if (fd >= 0)
+ {
+ dir->__d_fd = fd;
+ dir->__fh = this;
+ res = dir;
+ }
+ else
+ {
+ cygheap_fdnew cfd;
+ if (cfd >= 0 && open (O_RDONLY, 0))
+ {
+ cfd = this;
+ dir->__d_fd = cfd;
+ dir->__fh = this;
+ res = dir;
+ }
+ }
+ close_on_exec (true);
+ }
+
+ syscall_printf ("%p = opendir (%s)", res, get_name ());
+ return res;
+}
+
+long
+fhandler_virtual::telldir (DIR * dir)
+{
+ return dir->__d_position;
+}
+
+void
+fhandler_virtual::seekdir (DIR * dir, long loc)
+{
+ dir->__flags |= dirent_saw_dot | dirent_saw_dot_dot;
+ dir->__d_position = loc;
+}
+
+void
+fhandler_virtual::rewinddir (DIR * dir)
+{
+ dir->__d_position = 0;
+ dir->__flags |= dirent_saw_dot | dirent_saw_dot_dot;
+}
+
+int
+fhandler_virtual::closedir (DIR * dir)
+{
+ return 0;
+}
+
+off_t
+fhandler_virtual::lseek (off_t offset, int whence)
+{
+ /*
+ * On Linux, when you lseek within a /proc file,
+ * the contents of the file are updated.
+ */
+ if (!fill_filebuf ())
+ return (off_t) -1;
+ switch (whence)
+ {
+ case SEEK_SET:
+ position = offset;
+ break;
+ case SEEK_CUR:
+ position += offset;
+ break;
+ case SEEK_END:
+ position = filesize + offset;
+ break;
+ default:
+ set_errno (EINVAL);
+ return (off_t) -1;
+ }
+ return position;
+}
+
+int
+fhandler_virtual::dup (fhandler_base * child, int flags)
+{
+ int ret = fhandler_base::dup (child, flags);
+
+ if (!ret)
+ {
+ fhandler_virtual *fhproc_child = (fhandler_virtual *) child;
+ fhproc_child->filebuf = (char *) cmalloc_abort (HEAP_BUF, filesize);
+ memcpy (fhproc_child->filebuf, filebuf, filesize);
+ }
+ return ret;
+}
+
+int
+fhandler_virtual::close ()
+{
+ if (!have_execed)
+ {
+ if (filebuf)
+ {
+ cfree (filebuf);
+ filebuf = NULL;
+ }
+ }
+ return 0;
+}
+
+void
+fhandler_virtual::read (void *ptr, size_t& len)
+{
+ if (len == 0)
+ return;
+ if (diropen)
+ {
+ set_errno (EISDIR);
+ len = (size_t) -1;
+ return;
+ }
+ if (!filebuf)
+ {
+ len = (size_t) 0;
+ return;
+ }
+ if ((ssize_t) len > filesize - position)
+ len = (size_t) (filesize - position);
+ if ((ssize_t) len < 0)
+ len = 0;
+ else
+ memcpy (ptr, filebuf + position, len);
+ position += len;
+}
+
+ssize_t
+fhandler_virtual::write (const void *ptr, size_t len)
+{
+ set_errno (EACCES);
+ return -1;
+}
+
+/* low-level open for all proc files */
+int
+fhandler_virtual::open (int flags, mode_t mode)
+{
+ rbinary (true);
+ wbinary (true);
+
+ set_flags ((flags & ~O_TEXT) | O_BINARY);
+
+ return 1;
+}
+
+virtual_ftype_t
+fhandler_virtual::exists ()
+{
+ return virt_none;
+}
+
+bool
+fhandler_virtual::fill_filebuf ()
+{
+ return true;
+}
+
+int
+fhandler_virtual::fchmod (mode_t mode)
+{
+ /* Same as on Linux. */
+ set_errno (EPERM);
+ return -1;
+}
+
+int
+fhandler_virtual::fchown (uid_t uid, gid_t gid)
+{
+ /* Same as on Linux. */
+ set_errno (EPERM);
+ return -1;
+}
+
+int
+fhandler_virtual::facl (int cmd, int nentries, aclent_t *aclbufp)
+{
+ int res = fhandler_base::facl (cmd, nentries, aclbufp);
+ if (res >= 0 && cmd == GETACL)
+ {
+ aclbufp[0].a_perm = (S_IRUSR | (pc.isdir () ? S_IXUSR : 0)) >> 6;
+ aclbufp[1].a_perm = (S_IRGRP | (pc.isdir () ? S_IXGRP : 0)) >> 3;
+ aclbufp[2].a_perm = S_IROTH | (pc.isdir () ? S_IXOTH : 0);
+ }
+ return res;
+}
+
+int
+fhandler_virtual::fstatvfs (struct statvfs *sfs)
+{
+ /* Virtual file system. Just return an empty buffer with a few values
+ set to something useful. Just as on Linux. */
+ memset (sfs, 0, sizeof (*sfs));
+ sfs->f_bsize = sfs->f_frsize = 4096;
+ sfs->f_flag = ST_RDONLY;
+ sfs->f_namemax = NAME_MAX;
+ return 0;
+}
diff --git a/winsup/cygwin/fhandler/windows.cc b/winsup/cygwin/fhandler/windows.cc
new file mode 100644
index 000000000..90ab48daa
--- /dev/null
+++ b/winsup/cygwin/fhandler/windows.cc
@@ -0,0 +1,161 @@
+/* fhandler_windows.cc: code to access windows message queues.
+
+ Written by Sergey S. Okhapkin (sos@prospect.com.ru).
+ Feedback and testing by Andy Piper (andyp@parallax.co.uk).
+
+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 "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+#include "sigproc.h"
+#include "thread.h"
+
+
+/*
+The following unix-style calls are supported:
+
+ open ("/dev/windows", flags, mode=0)
+ - create a unix fd for message queue.
+
+ read (fd, buf, len)
+ - return next message from queue. buf must point to MSG
+ structure, len must be >= sizeof (MSG). If read is set to
+ non-blocking and the queue is empty, read call returns -1
+ immediately with errno set to EAGAIN, otherwise it blocks
+ untill the message will be received.
+
+ write (fd, buf, len)
+ - send a message pointed by buf. len argument ignored.
+
+ ioctl (fd, command, *param)
+ - control read()/write() behavior.
+ ioctl (fd, WINDOWS_POST, NULL): write() will PostMessage();
+ ioctl (fd, WINDOWS_SEND, NULL): write() will SendMessage();
+ ioctl (fd, WINDOWS_HWND, &hWnd): read() messages for
+ hWnd window.
+
+ select () call marks read fd when any message posted to queue.
+*/
+
+fhandler_windows::fhandler_windows ()
+ : fhandler_base (), hWnd_ (NULL), method_ (WINDOWS_POST)
+{
+}
+
+int
+fhandler_windows::open (int flags, mode_t mode)
+{
+ return fhandler_base::open ((flags & ~O_TEXT) | O_BINARY, mode);
+}
+
+ssize_t
+fhandler_windows::write (const void *buf, size_t)
+{
+ MSG *ptr = (MSG *) buf;
+
+ if (method_ == WINDOWS_POST)
+ {
+ if (!PostMessageW (ptr->hwnd, ptr->message, ptr->wParam, ptr->lParam))
+ {
+ __seterrno ();
+ return -1;
+ }
+ }
+ else if (!SendNotifyMessageW (ptr->hwnd, ptr->message, ptr->wParam,
+ ptr->lParam))
+ {
+ __seterrno ();
+ return -1;
+ }
+ return sizeof (MSG);
+}
+
+void
+fhandler_windows::read (void *buf, size_t& len)
+{
+ MSG *ptr = (MSG *) buf;
+
+ if (len < sizeof (MSG))
+ {
+ set_errno (EINVAL);
+ len = (size_t) -1;
+ return;
+ }
+
+ HANDLE w4[2];
+ wait_signal_arrived here (w4[0]);
+ DWORD cnt = 1;
+ if ((w4[1] = pthread::get_cancel_event ()) != NULL)
+ ++cnt;
+ for (;;)
+ {
+ switch (MsgWaitForMultipleObjectsEx (cnt, w4,
+ is_nonblocking () ? 0 : INFINITE,
+ QS_ALLINPUT | QS_ALLPOSTMESSAGE,
+ MWMO_INPUTAVAILABLE))
+ {
+ case WAIT_OBJECT_0:
+ if (_my_tls.call_signal_handler ())
+ continue;
+ len = (size_t) -1;
+ set_errno (EINTR);
+ break;
+ case WAIT_OBJECT_0 + 1:
+ if (cnt > 1) /* WAIT_OBJECT_0 + 1 is the cancel event object. */
+ {
+ pthread::static_cancel_self ();
+ break;
+ }
+ fallthrough;
+ case WAIT_OBJECT_0 + 2:
+ if (!PeekMessageW (ptr, hWnd_, 0, 0, PM_REMOVE))
+ {
+ len = (size_t) -1;
+ __seterrno ();
+ }
+ else if (ptr->message == WM_QUIT)
+ len = 0;
+ else
+ len = sizeof (MSG);
+ break;
+ case WAIT_TIMEOUT:
+ len = (size_t) -1;
+ set_errno (EAGAIN);
+ break;
+ default:
+ len = (size_t) -1;
+ __seterrno ();
+ break;
+ }
+ break;
+ }
+}
+
+int
+fhandler_windows::ioctl (unsigned int cmd, void *val)
+{
+ switch (cmd)
+ {
+ case WINDOWS_POST:
+ case WINDOWS_SEND:
+ method_ = cmd;
+ break;
+ case WINDOWS_HWND:
+ if (val == NULL)
+ {
+ set_errno (EINVAL);
+ return -1;
+ }
+ hWnd_ = * ((HWND *) val);
+ break;
+ default:
+ return fhandler_base::ioctl (cmd, val);
+ }
+ return 0;
+}
diff --git a/winsup/cygwin/fhandler/zero.cc b/winsup/cygwin/fhandler/zero.cc
new file mode 100644
index 000000000..9d8fe004d
--- /dev/null
+++ b/winsup/cygwin/fhandler/zero.cc
@@ -0,0 +1,37 @@
+/* fhandler_dev_zero.cc: code to access /dev/zero
+
+ Written by DJ Delorie (dj@cygnus.com)
+
+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 "security.h"
+#include "cygerrno.h"
+#include "path.h"
+#include "fhandler.h"
+
+fhandler_dev_zero::fhandler_dev_zero ()
+ : fhandler_base ()
+{
+}
+
+ssize_t
+fhandler_dev_zero::write (const void *, size_t len)
+{
+ if (get_device () == FH_FULL)
+ {
+ set_errno (ENOSPC);
+ return -1;
+ }
+ return len;
+}
+
+void
+fhandler_dev_zero::read (void *ptr, size_t& len)
+{
+ memset (ptr, 0, len);
+}