diff options
Diffstat (limited to 'winsup/cygwin/fhandler')
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, ®ion); + 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 (§, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY + | SECTION_MAP_READ | SECTION_MAP_WRITE, + &attr, &size, PAGE_READWRITE, SEC_COMMIT, NULL); + if (status == STATUS_OBJECT_NAME_COLLISION) + already_exists = true; + } + if (only_open || already_exists) + status = NtOpenSection (§, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY + | SECTION_MAP_READ | SECTION_MAP_WRITE, &attr); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + status = NtMapViewOfSection (sect, NtCurrentProcess (), &addr, 0, viewsize, + NULL, &viewsize, ViewShare, 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + NtClose (sect); + __seterrno_from_nt_status (status); + return -1; + } + shmem_handle = sect; + shmem = (fifo_shmem_t *) addr; + return already_exists ? 1 : 0; +} + +/* shmem_handle must be valid when this is called. */ +int +fhandler_fifo::reopen_shmem () +{ + NTSTATUS status; + SIZE_T viewsize = sizeof (fifo_shmem_t); + PVOID addr = NULL; + + status = NtMapViewOfSection (shmem_handle, NtCurrentProcess (), &addr, + 0, viewsize, NULL, &viewsize, ViewShare, + 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + shmem = (fifo_shmem_t *) addr; + return 0; +} + +/* On first creation, map and commit one page of memory. */ +int +fhandler_fifo::create_shared_fc_handler () +{ + HANDLE sect; + OBJECT_ATTRIBUTES attr; + NTSTATUS status; + LARGE_INTEGER size + = { .QuadPart = (LONGLONG) (SH_FC_HANDLER_PAGES * wincap.page_size ()) }; + SIZE_T viewsize = get_shared_fc_handler_committed () ?: wincap.page_size (); + PVOID addr = NULL; + UNICODE_STRING uname; + WCHAR shared_fc_name[MAX_PATH]; + + __small_swprintf (shared_fc_name, L"fifo-shared-fc.%08x.%016X", get_dev (), + get_ino ()); + RtlInitUnicodeString (&uname, shared_fc_name); + InitializeObjectAttributes (&attr, &uname, OBJ_INHERIT, + get_shared_parent_dir (), NULL); + status = NtCreateSection (§, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY + | SECTION_MAP_READ | SECTION_MAP_WRITE, &attr, + &size, PAGE_READWRITE, SEC_RESERVE, NULL); + if (status == STATUS_OBJECT_NAME_COLLISION) + status = NtOpenSection (§, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY + | SECTION_MAP_READ | SECTION_MAP_WRITE, &attr); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + status = NtMapViewOfSection (sect, NtCurrentProcess (), &addr, 0, viewsize, + NULL, &viewsize, ViewShare, 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + NtClose (sect); + __seterrno_from_nt_status (status); + return -1; + } + shared_fc_hdl = sect; + shared_fc_handler = (fifo_client_handler *) addr; + if (!get_shared_fc_handler_committed ()) + set_shared_fc_handler_committed (viewsize); + set_shared_shandlers (viewsize / sizeof (fifo_client_handler)); + return 0; +} + +/* shared_fc_hdl must be valid when this is called. */ +int +fhandler_fifo::reopen_shared_fc_handler () +{ + NTSTATUS status; + SIZE_T viewsize = get_shared_fc_handler_committed (); + PVOID addr = NULL; + + status = NtMapViewOfSection (shared_fc_hdl, NtCurrentProcess (), + &addr, 0, viewsize, NULL, &viewsize, + ViewShare, 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + shared_fc_handler = (fifo_client_handler *) addr; + return 0; +} + +int +fhandler_fifo::remap_shared_fc_handler (size_t nbytes) +{ + NTSTATUS status; + SIZE_T viewsize = roundup2 (nbytes, wincap.page_size ()); + PVOID addr = NULL; + + if (viewsize > SH_FC_HANDLER_PAGES * wincap.page_size ()) + { + set_errno (ENOMEM); + return -1; + } + + NtUnmapViewOfSection (NtCurrentProcess (), shared_fc_handler); + status = NtMapViewOfSection (shared_fc_hdl, NtCurrentProcess (), + &addr, 0, viewsize, NULL, &viewsize, + ViewShare, 0, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return -1; + } + shared_fc_handler = (fifo_client_handler *) addr; + set_shared_fc_handler_committed (viewsize); + set_shared_shandlers (viewsize / sizeof (fc_handler[0])); + return 0; +} + +int +fhandler_fifo::open (int flags, mode_t) +{ + int saved_errno = 0, shmem_res = 0; + + if (flags & O_PATH) + return open_fs (flags); + + /* Determine what we're doing with this fhandler: reading, writing, both */ + switch (flags & O_ACCMODE) + { + case O_RDONLY: + reader = true; + break; + case O_WRONLY: + writer = true; + break; + case O_RDWR: + reader = writer = duplexer = true; + break; + default: + set_errno (EINVAL); + goto err; + } + + debug_only_printf ("reader %d, writer %d, duplexer %d", reader, writer, duplexer); + set_flags (flags); + if (reader && !duplexer) + nohandle (true); + + /* Create control events for this named pipe */ + char char_sa_buf[1024]; + LPSECURITY_ATTRIBUTES sa_buf; + sa_buf = sec_user_cloexec (flags & O_CLOEXEC, (PSECURITY_ATTRIBUTES) char_sa_buf, + cygheap->user.sid()); + + char npbuf[MAX_PATH]; + __small_sprintf (npbuf, "r-event.%08x.%016X", get_dev (), get_ino ()); + if (!(read_ready = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err; + } + npbuf[0] = 'w'; + if (!(write_ready = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_read_ready; + } + npbuf[0] = 'o'; + if (!(writer_opening = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_write_ready; + } + + /* If we're reading, create the shared memory and the shared + fc_handler memory, create some events, start the + fifo_reader_thread, signal read_ready, and wait for a writer. */ + if (reader) + { + /* Create/open shared memory. */ + if ((shmem_res = create_shmem ()) < 0) + goto err_close_writer_opening; + else if (shmem_res == 0) + debug_printf ("shmem created"); + else + debug_printf ("shmem existed; ok if we're not the first reader"); + if (create_shared_fc_handler () < 0) + goto err_close_shmem; + npbuf[0] = 'n'; + if (!(owner_needed_evt = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_shared_fc_handler; + } + npbuf[0] = 'f'; + if (!(owner_found_evt = CreateEvent (sa_buf, true, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_owner_needed_evt; + } + npbuf[0] = 'u'; + if (!(update_needed_evt = CreateEvent (sa_buf, false, false, npbuf))) + { + debug_printf ("CreateEvent for %s failed, %E", npbuf); + __seterrno (); + goto err_close_owner_found_evt; + } + if (!(cancel_evt = create_event ())) + goto err_close_update_needed_evt; + if (!(thr_sync_evt = create_event ())) + goto err_close_cancel_evt; + + me.winpid = GetCurrentProcessId (); + me.fh = this; + nreaders_lock (); + if (inc_nreaders () == 1) + { + /* Reinitialize _sh_fc_handler_updated, which starts as 0. */ + shared_fc_handler_updated (true); + set_owner (me); + } + new cygthread (fifo_reader_thread, this, "fifo_reader", thr_sync_evt); + SetEvent (read_ready); + nreaders_unlock (); + + /* If we're a duplexer, we need a handle for writing. */ + if (duplexer) + { + HANDLE ph = NULL; + NTSTATUS status; + + nwriters_lock (); + inc_nwriters (); + SetEvent (write_ready); + nwriters_unlock (); + + while (1) + { + status = open_pipe (ph); + if (NT_SUCCESS (status)) + { + set_handle (ph); + set_pipe_non_blocking (ph, flags & O_NONBLOCK); + break; + } + else if (status == STATUS_OBJECT_NAME_NOT_FOUND) + { + /* The pipe hasn't been created yet. */ + yield (); + continue; + } + else + { + __seterrno_from_nt_status (status); + nohandle (true); + goto err_close_reader; + } + } + } + /* Not a duplexer; wait for a writer to connect if we're blocking. */ + else if (!wait (write_ready)) + goto err_close_reader; + goto success; + } + + /* If we're writing, wait for read_ready, connect to the pipe, open + the shared memory, and signal write_ready. */ + if (writer) + { + NTSTATUS status; + + /* Don't let a reader see EOF at this point. */ + SetEvent (writer_opening); + while (1) + { + if (!wait (read_ready)) + { + ResetEvent (writer_opening); + goto err_close_writer_opening; + } + status = open_pipe (get_handle ()); + if (NT_SUCCESS (status)) + goto writer_shmem; + else if (status == STATUS_OBJECT_NAME_NOT_FOUND) + { + /* The pipe hasn't been created yet or there's no longer + a reader open. */ + yield (); + continue; + } + else if (STATUS_PIPE_NO_INSTANCE_AVAILABLE (status)) + break; + else + { + debug_printf ("create of writer failed"); + __seterrno_from_nt_status (status); + ResetEvent (writer_opening); + goto err_close_writer_opening; + } + } + + /* We should get here only if the system is heavily loaded + and/or many writers are trying to connect simultaneously */ + while (1) + { + if (!wait (read_ready)) + { + ResetEvent (writer_opening); + goto err_close_writer_opening; + } + status = wait_open_pipe (get_handle ()); + if (NT_SUCCESS (status)) + goto writer_shmem; + else if (status == STATUS_IO_TIMEOUT) + continue; + else + { + debug_printf ("create of writer failed"); + __seterrno_from_nt_status (status); + ResetEvent (writer_opening); + goto err_close_writer_opening; + } + } + } +writer_shmem: + if (create_shmem (true) < 0) + goto err_close_writer_opening; +/* writer_success: */ + set_pipe_non_blocking (get_handle (), flags & O_NONBLOCK); + nwriters_lock (); + inc_nwriters (); + SetEvent (write_ready); + ResetEvent (writer_opening); + nwriters_unlock (); +success: + if (!select_sem) + { + __small_sprintf (npbuf, "semaphore.%08x.%016X", get_dev (), get_ino ()); + select_sem = CreateSemaphore (sa_buf, 0, INT32_MAX, npbuf); + } + return 1; +err_close_reader: + saved_errno = get_errno (); + close (); + set_errno (saved_errno); + return 0; +/* err_close_thr_sync_evt: */ +/* NtClose (thr_sync_evt); */ +err_close_cancel_evt: + NtClose (cancel_evt); +err_close_update_needed_evt: + NtClose (update_needed_evt); +err_close_owner_found_evt: + NtClose (owner_found_evt); +err_close_owner_needed_evt: + NtClose (owner_needed_evt); +err_close_shared_fc_handler: + NtUnmapViewOfSection (NtCurrentProcess (), shared_fc_handler); + NtClose (shared_fc_hdl); +err_close_shmem: + NtUnmapViewOfSection (NtCurrentProcess (), shmem); + NtClose (shmem_handle); +err_close_writer_opening: + NtClose (writer_opening); +err_close_write_ready: + NtClose (write_ready); +err_close_read_ready: + NtClose (read_ready); +err: + if (get_handle ()) + NtClose (get_handle ()); + return 0; +} + +off_t +fhandler_fifo::lseek (off_t offset, int whence) +{ + debug_printf ("(%D, %d)", offset, whence); + set_errno (ESPIPE); + return -1; +} + +bool +fhandler_fifo::wait (HANDLE h) +{ +#ifdef DEBUGGING + const char *what; + if (h == read_ready) + what = "reader"; + else + what = "writer"; +#endif + /* Set the wait to zero for non-blocking I/O-related events. */ + DWORD wait = ((h == read_ready || h == write_ready) + && get_flags () & O_NONBLOCK) ? 0 : INFINITE; + + debug_only_printf ("waiting for %s", what); + /* Wait for the event. Set errno, as appropriate if something goes wrong. */ + switch (cygwait (h, wait)) + { + case WAIT_OBJECT_0: + debug_only_printf ("successfully waited for %s", what); + return true; + case WAIT_SIGNALED: + debug_only_printf ("interrupted by signal while waiting for %s", what); + set_errno (EINTR); + return false; + case WAIT_CANCELED: + debug_only_printf ("cancellable interruption while waiting for %s", what); + pthread::static_cancel_self (); /* never returns */ + break; + case WAIT_TIMEOUT: + if (h == write_ready) + { + debug_only_printf ("wait timed out waiting for write but will still open reader since non-blocking mode"); + return true; + } + else + { + set_errno (ENXIO); + return false; + } + break; + default: + debug_only_printf ("unknown error while waiting for %s", what); + __seterrno (); + return false; + } +} + +/* Called from raw_read and select.cc:peek_fifo. */ +int +fhandler_fifo::take_ownership (DWORD timeout) +{ + int ret = 0; + + owner_lock (); + if (get_owner () == me) + { + owner_unlock (); + return 0; + } + set_pending_owner (me); + /* Wake up my fifo_reader_thread. */ + owner_needed (); + if (get_owner ()) + /* Wake up the owner and request an update of the shared fc_handlers. */ + SetEvent (update_needed_evt); + owner_unlock (); + /* The reader threads should now do the transfer. */ + switch (WaitForSingleObject (owner_found_evt, timeout)) + { + case WAIT_OBJECT_0: + owner_lock (); + if (get_owner () != me) + { + debug_printf ("owner_found_evt signaled, but I'm not the owner"); + ret = -1; + } + owner_unlock (); + break; + case WAIT_TIMEOUT: + debug_printf ("timed out"); + ret = -1; + break; + default: + debug_printf ("WFSO failed, %E"); + ret = -1; + break; + } + return ret; +} + +void +fhandler_fifo::release_select_sem (const char *from) +{ + LONG n_release; + if (reader) /* Number of select() call. */ + n_release = get_obj_handle_count (select_sem) + - get_obj_handle_count (read_ready); + else /* Number of select() and reader */ + n_release = get_obj_handle_count (select_sem) + - get_obj_handle_count (get_handle ()); + debug_printf("%s(%s) release %d", from, + reader ? "reader" : "writer", n_release); + if (n_release) + ReleaseSemaphore (select_sem, n_release, NULL); +} + +/* Read from a non-blocking pipe and wait for completion. */ +static NTSTATUS +nt_read (HANDLE h, HANDLE evt, PIO_STATUS_BLOCK pio, void *in_ptr, size_t& len) +{ + NTSTATUS status; + + ResetEvent (evt); + status = NtReadFile (h, evt, NULL, NULL, pio, in_ptr, len, NULL, NULL); + if (status == STATUS_PENDING) + { + /* Very short-lived */ + status = NtWaitForSingleObject (evt, FALSE, NULL); + if (NT_SUCCESS (status)) + status = pio->Status; + } + return status; +} + +void +fhandler_fifo::raw_read (void *in_ptr, size_t& len) +{ + HANDLE evt; + + if (!len) + return; + + if (!(evt = CreateEvent (NULL, false, false, NULL))) + { + __seterrno (); + len = (size_t) -1; + return; + } + + while (1) + { + int nconnected = 0; + + /* No one else can take ownership while we hold the reading_lock. */ + reading_lock (); + if (take_ownership (10) < 0) + goto maybe_retry; + + fifo_client_lock (); + /* Poll the connected clients for input. Make three passes. + + On the first pass, just try to read from the client from + which we last read successfully. This should minimize + interleaving of writes from different clients. + + On the second pass, just try to read from the clients in the + state fc_input_avail. This should be more efficient if + select has been called and detected input available. + + On the third pass, try to read from all connected clients. */ + + /* First pass. */ + int j; + for (j = 0; j < nhandlers; j++) + if (fc_handler[j].last_read) + break; + if (j < nhandlers && fc_handler[j].get_state () < fc_connected) + { + fc_handler[j].last_read = false; + j = nhandlers; + } + if (j < nhandlers) + { + NTSTATUS status; + IO_STATUS_BLOCK io; + + status = nt_read (fc_handler[j].h, evt, &io, in_ptr, len); + switch (status) + { + case STATUS_SUCCESS: + case STATUS_BUFFER_OVERFLOW: + if (io.Information > 0) + { + len = io.Information; + goto unlock_out; + } + break; + case STATUS_PIPE_EMPTY: + /* Update state in case it's fc_input_avail. */ + fc_handler[j].set_state (fc_connected); + break; + case STATUS_PIPE_BROKEN: + fc_handler[j].set_state (fc_disconnected); + break; + default: + debug_printf ("nt_read status %y", status); + fc_handler[j].set_state (fc_error); + break; + } + } + + /* Second pass. */ + for (int i = 0; i < nhandlers; i++) + if (fc_handler[i].get_state () == fc_input_avail) + { + NTSTATUS status; + IO_STATUS_BLOCK io; + + status = nt_read (fc_handler[i].h, evt, &io, in_ptr, len); + switch (status) + { + case STATUS_SUCCESS: + case STATUS_BUFFER_OVERFLOW: + if (io.Information > 0) + { + len = io.Information; + if (j < nhandlers) + fc_handler[j].last_read = false; + fc_handler[i].last_read = true; + goto unlock_out; + } + break; + case STATUS_PIPE_EMPTY: + /* No input available after all. */ + fc_handler[i].set_state (fc_connected); + break; + case STATUS_PIPE_BROKEN: + fc_handler[i].set_state (fc_disconnected); + break; + default: + debug_printf ("nt_read status %y", status); + fc_handler[i].set_state (fc_error); + break; + } + } + + /* Third pass. */ + for (int i = 0; i < nhandlers; i++) + if (fc_handler[i].get_state () >= fc_connected) + { + NTSTATUS status; + IO_STATUS_BLOCK io; + + nconnected++; + status = nt_read (fc_handler[i].h, evt, &io, in_ptr, len); + switch (status) + { + case STATUS_SUCCESS: + case STATUS_BUFFER_OVERFLOW: + if (io.Information > 0) + { + len = io.Information; + if (j < nhandlers) + fc_handler[j].last_read = false; + fc_handler[i].last_read = true; + goto unlock_out; + } + break; + case STATUS_PIPE_EMPTY: + break; + case STATUS_PIPE_BROKEN: + fc_handler[i].set_state (fc_disconnected); + nconnected--; + break; + default: + debug_printf ("nt_read status %y", status); + fc_handler[i].set_state (fc_error); + nconnected--; + break; + } + } + if (!nconnected && hit_eof ()) + { + len = 0; + goto unlock_out; + } + fifo_client_unlock (); +maybe_retry: + reading_unlock (); + if (is_nonblocking ()) + { + set_errno (EAGAIN); + len = (size_t) -1; + goto out; + } + else + { + /* Allow interruption and don't hog the CPU. */ + DWORD waitret = cygwait (select_sem, 1, cw_cancel | cw_sig_eintr); + if (waitret == WAIT_CANCELED) + pthread::static_cancel_self (); + else if (waitret == WAIT_SIGNALED) + { + if (_my_tls.call_signal_handler ()) + continue; + else + { + set_errno (EINTR); + len = (size_t) -1; + goto out; + } + } + } + /* We might have been closed by a signal handler or another thread. */ + if (isclosed ()) + { + set_errno (EBADF); + len = (size_t) -1; + goto out; + } + } +unlock_out: + fifo_client_unlock (); + reading_unlock (); +out: + if (select_sem) + release_select_sem ("raw_read"); + CloseHandle (evt); +} + +int +fhandler_fifo::fstat (struct stat *buf) +{ + if (reader || writer || duplexer) + { + /* fhandler_fifo::open has been called, and O_PATH is not set. + We don't want to call fhandler_base::fstat. In the writer + and duplexer cases we have a handle, but it's a pipe handle + rather than a file handle, so it's not suitable for stat. In + the reader case we don't have a handle, but + fhandler_base::fstat would call fhandler_base::open, which + would modify the flags and status_flags. */ + fhandler_disk_file fh (pc); + fh.get_device () = FH_FS; + int res = fh.fstat (buf); + buf->st_dev = buf->st_rdev = dev (); + buf->st_mode = dev ().mode (); + buf->st_size = 0; + return res; + } + return fhandler_base::fstat (buf); +} + +int +fhandler_fifo::fstatvfs (struct statvfs *sfs) +{ + if (get_flags () & O_PATH) + /* We already have a handle. */ + { + HANDLE h = get_handle (); + if (h) + return fstatvfs_by_handle (h, sfs); + } + + fhandler_disk_file fh (pc); + fh.get_device () = FH_FS; + return fh.fstatvfs (sfs); +} + +void +fhandler_fifo::close_all_handlers () +{ + fifo_client_lock (); + for (int i = 0; i < nhandlers; i++) + fc_handler[i].close (); + nhandlers = 0; + fifo_client_unlock (); +} + +/* Return previous state. */ +fifo_client_connect_state +fifo_client_handler::query_and_set_state () +{ + IO_STATUS_BLOCK io; + FILE_PIPE_LOCAL_INFORMATION fpli; + NTSTATUS status; + fifo_client_connect_state prev_state = get_state (); + + if (!h) + { + set_state (fc_unknown); + goto out; + } + + status = NtQueryInformationFile (h, &io, &fpli, + sizeof (fpli), FilePipeLocalInformation); + if (!NT_SUCCESS (status)) + { + debug_printf ("NtQueryInformationFile status %y", status); + set_state (fc_error); + } + else if (fpli.ReadDataAvailable > 0) + set_state (fc_input_avail); + else + switch (fpli.NamedPipeState) + { + case FILE_PIPE_DISCONNECTED_STATE: + set_state (fc_disconnected); + break; + case FILE_PIPE_LISTENING_STATE: + set_state (fc_listening); + break; + case FILE_PIPE_CONNECTED_STATE: + set_state (fc_connected); + break; + case FILE_PIPE_CLOSING_STATE: + set_state (fc_closing); + break; + default: + set_state (fc_error); + break; + } +out: + return prev_state; +} + +void +fhandler_fifo::cancel_reader_thread () +{ + if (cancel_evt) + SetEvent (cancel_evt); + if (thr_sync_evt) + WaitForSingleObject (thr_sync_evt, INFINITE); +} + +int +fhandler_fifo::close () +{ + if (select_sem) + { + release_select_sem ("close"); + NtClose (select_sem); + } + if (writer) + { + nwriters_lock (); + if (dec_nwriters () == 0) + ResetEvent (write_ready); + nwriters_unlock (); + } + if (reader) + { + /* If we're the owner, we can't close our fc_handlers if a new + owner might need to duplicate them. */ + bool close_fc_ok = false; + + cancel_reader_thread (); + nreaders_lock (); + if (dec_nreaders () == 0) + { + close_fc_ok = true; + ResetEvent (read_ready); + ResetEvent (owner_needed_evt); + ResetEvent (owner_found_evt); + set_owner (null_fr_id); + set_prev_owner (null_fr_id); + set_pending_owner (null_fr_id); + set_shared_nhandlers (0); + } + else + { + owner_lock (); + if (get_owner () != me) + close_fc_ok = true; + else + { + set_owner (null_fr_id); + set_prev_owner (me); + if (!get_pending_owner ()) + owner_needed (); + } + owner_unlock (); + } + nreaders_unlock (); + while (!close_fc_ok) + { + if (WaitForSingleObject (owner_found_evt, 1) == WAIT_OBJECT_0) + close_fc_ok = true; + else + { + nreaders_lock (); + if (!nreaders ()) + { + close_fc_ok = true; + nreaders_unlock (); + } + else + { + nreaders_unlock (); + owner_lock (); + if (get_owner () || get_prev_owner () != me) + close_fc_ok = true; + owner_unlock (); + } + } + } + close_all_handlers (); + if (fc_handler) + free (fc_handler); + if (owner_needed_evt) + NtClose (owner_needed_evt); + if (owner_found_evt) + NtClose (owner_found_evt); + if (update_needed_evt) + NtClose (update_needed_evt); + if (cancel_evt) + NtClose (cancel_evt); + if (thr_sync_evt) + NtClose (thr_sync_evt); + if (shared_fc_handler) + NtUnmapViewOfSection (NtCurrentProcess (), shared_fc_handler); + if (shared_fc_hdl) + NtClose (shared_fc_hdl); + } + if (shmem) + NtUnmapViewOfSection (NtCurrentProcess (), shmem); + if (shmem_handle) + NtClose (shmem_handle); + if (read_ready) + NtClose (read_ready); + if (write_ready) + NtClose (write_ready); + if (writer_opening) + NtClose (writer_opening); + if (nohandle ()) + return 0; + else + return fhandler_base::close (); +} + +/* If we have a write handle (i.e., we're a duplexer or a writer), + keep the nonblocking state of the windows pipe in sync with our + nonblocking state. */ +int +fhandler_fifo::fcntl (int cmd, intptr_t arg) +{ + if (cmd != F_SETFL || nohandle () || (get_flags () & O_PATH)) + return fhandler_base::fcntl (cmd, arg); + + const bool was_nonblocking = is_nonblocking (); + int res = fhandler_base::fcntl (cmd, arg); + const bool now_nonblocking = is_nonblocking (); + if (now_nonblocking != was_nonblocking) + set_pipe_non_blocking (get_handle (), now_nonblocking); + return res; +} + +int +fhandler_fifo::dup (fhandler_base *child, int flags) +{ + fhandler_fifo *fhf = NULL; + + if (get_flags () & O_PATH) + return fhandler_base::dup (child, flags); + + if (fhandler_base::dup (child, flags)) + goto err; + + fhf = (fhandler_fifo *) child; + if (!DuplicateHandle (GetCurrentProcess (), read_ready, + GetCurrentProcess (), &fhf->read_ready, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err; + } + if (!DuplicateHandle (GetCurrentProcess (), write_ready, + GetCurrentProcess (), &fhf->write_ready, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_read_ready; + } + if (!DuplicateHandle (GetCurrentProcess (), writer_opening, + GetCurrentProcess (), &fhf->writer_opening, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_write_ready; + } + if (!DuplicateHandle (GetCurrentProcess (), shmem_handle, + GetCurrentProcess (), &fhf->shmem_handle, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_writer_opening; + } + if (fhf->reopen_shmem () < 0) + goto err_close_shmem_handle; + if (select_sem && + !DuplicateHandle (GetCurrentProcess (), select_sem, + GetCurrentProcess (), &fhf->select_sem, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_shmem; + } + if (reader) + { + /* Make sure the child starts unlocked. */ + fhf->fifo_client_unlock (); + + /* Clear fc_handler list; the child never starts as owner. */ + fhf->nhandlers = fhf->shandlers = 0; + fhf->fc_handler = NULL; + + if (!DuplicateHandle (GetCurrentProcess (), shared_fc_hdl, + GetCurrentProcess (), &fhf->shared_fc_hdl, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_select_sem; + } + if (fhf->reopen_shared_fc_handler () < 0) + goto err_close_shared_fc_hdl; + if (!DuplicateHandle (GetCurrentProcess (), owner_needed_evt, + GetCurrentProcess (), &fhf->owner_needed_evt, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_shared_fc_handler; + } + if (!DuplicateHandle (GetCurrentProcess (), owner_found_evt, + GetCurrentProcess (), &fhf->owner_found_evt, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_owner_needed_evt; + } + if (!DuplicateHandle (GetCurrentProcess (), update_needed_evt, + GetCurrentProcess (), &fhf->update_needed_evt, + 0, !(flags & O_CLOEXEC), DUPLICATE_SAME_ACCESS)) + { + __seterrno (); + goto err_close_owner_found_evt; + } + if (!(fhf->cancel_evt = create_event ())) + goto err_close_update_needed_evt; + if (!(fhf->thr_sync_evt = create_event ())) + goto err_close_cancel_evt; + inc_nreaders (); + fhf->me.fh = fhf; + new cygthread (fifo_reader_thread, fhf, "fifo_reader", fhf->thr_sync_evt); + } + if (writer) + inc_nwriters (); + return 0; +err_close_cancel_evt: + NtClose (fhf->cancel_evt); +err_close_update_needed_evt: + NtClose (fhf->update_needed_evt); +err_close_owner_found_evt: + NtClose (fhf->owner_found_evt); +err_close_owner_needed_evt: + NtClose (fhf->owner_needed_evt); +err_close_shared_fc_handler: + NtUnmapViewOfSection (GetCurrentProcess (), fhf->shared_fc_handler); +err_close_shared_fc_hdl: + NtClose (fhf->shared_fc_hdl); +err_close_select_sem: + NtClose (fhf->select_sem); +err_close_shmem: + NtUnmapViewOfSection (GetCurrentProcess (), fhf->shmem); +err_close_shmem_handle: + NtClose (fhf->shmem_handle); +err_close_writer_opening: + NtClose (fhf->writer_opening); +err_close_write_ready: + NtClose (fhf->write_ready); +err_close_read_ready: + NtClose (fhf->read_ready); +err: + return -1; +} + +void +fhandler_fifo::fixup_after_fork (HANDLE parent) +{ + fhandler_base::fixup_after_fork (parent); + fork_fixup (parent, read_ready, "read_ready"); + fork_fixup (parent, write_ready, "write_ready"); + fork_fixup (parent, writer_opening, "writer_opening"); + fork_fixup (parent, shmem_handle, "shmem_handle"); + if (reopen_shmem () < 0) + api_fatal ("Can't reopen shared memory during fork, %E"); + if (select_sem) + fork_fixup (parent, select_sem, "select_sem"); + if (reader) + { + /* Make sure the child starts unlocked. */ + fifo_client_unlock (); + + fork_fixup (parent, shared_fc_hdl, "shared_fc_hdl"); + if (reopen_shared_fc_handler () < 0) + api_fatal ("Can't reopen shared fc_handler memory during fork, %E"); + fork_fixup (parent, owner_needed_evt, "owner_needed_evt"); + fork_fixup (parent, owner_found_evt, "owner_found_evt"); + fork_fixup (parent, update_needed_evt, "update_needed_evt"); + if (close_on_exec ()) + /* Prevent a later attempt to close the non-inherited + pipe-instance handles copied from the parent. */ + nhandlers = 0; + if (!(cancel_evt = create_event ())) + api_fatal ("Can't create reader thread cancel event during fork, %E"); + if (!(thr_sync_evt = create_event ())) + api_fatal ("Can't create reader thread sync event during fork, %E"); + inc_nreaders (); + me.winpid = GetCurrentProcessId (); + new cygthread (fifo_reader_thread, this, "fifo_reader", thr_sync_evt); + } + if (writer) + inc_nwriters (); +} + +void +fhandler_fifo::fixup_after_exec () +{ + fhandler_base::fixup_after_exec (); + if (close_on_exec ()) + return; + if (reopen_shmem () < 0) + api_fatal ("Can't reopen shared memory during exec, %E"); + if (reader) + { + /* Make sure the child starts unlocked. */ + fifo_client_unlock (); + + if (reopen_shared_fc_handler () < 0) + api_fatal ("Can't reopen shared fc_handler memory during exec, %E"); + fc_handler = NULL; + nhandlers = shandlers = 0; + if (!(cancel_evt = create_event ())) + api_fatal ("Can't create reader thread cancel event during exec, %E"); + if (!(thr_sync_evt = create_event ())) + api_fatal ("Can't create reader thread sync event during exec, %E"); + /* At this moment we're a new reader. The count will be + decremented when the parent closes. */ + inc_nreaders (); + me.winpid = GetCurrentProcessId (); + new cygthread (fifo_reader_thread, this, "fifo_reader", thr_sync_evt); + } + if (writer) + inc_nwriters (); +} + +void +fhandler_fifo::set_close_on_exec (bool val) +{ + fhandler_base::set_close_on_exec (val); + set_no_inheritance (read_ready, val); + set_no_inheritance (write_ready, val); + set_no_inheritance (writer_opening, val); + if (reader) + { + set_no_inheritance (owner_needed_evt, val); + set_no_inheritance (owner_found_evt, val); + set_no_inheritance (update_needed_evt, val); + fifo_client_lock (); + for (int i = 0; i < nhandlers; i++) + set_no_inheritance (fc_handler[i].h, val); + fifo_client_unlock (); + } + if (select_sem) + set_no_inheritance (select_sem, val); +} 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 (µcode, 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 *) ∈ + 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 (§, 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); +} |