/*
 * @mindmaze_header@
 */
#if HAVE_CONFIG_H
# include <config.h>
#endif

#include "utils-win32.h"

#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <aclapi.h>
#include <sddl.h>

#include "mmerrno.h"
#include "mmlog.h"
#include "mmlib.h"
#include "mmthread.h"

// https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-offlineuseraccounts-offlinedomainaccounts-offlinedomainaccount-sid
#define SID_STRING_MAXLEN 256

static
int set_access_mode(struct w32_create_file_options* opts, int oflags)
{
	switch (oflags & (_O_RDONLY|_O_WRONLY| _O_RDWR)) {
	case _O_RDONLY:
		opts->access_mode = GENERIC_READ;
		break;

	case _O_WRONLY:
		opts->access_mode = GENERIC_WRITE;
		break;

	case _O_RDWR:
		opts->access_mode = (GENERIC_READ | GENERIC_WRITE);
		break;

	default:
		mm_raise_error(EINVAL,
		               "Invalid combination of file access mode");
		return -1;
	}

	return 0;
}


static
int set_creation_mode(struct w32_create_file_options* opts, int oflags)
{
	switch (oflags & (_O_TRUNC|_O_CREAT|_O_EXCL)) {
	case 0:
	case _O_EXCL:
		opts->creation_mode = OPEN_EXISTING;
		break;

	case _O_CREAT:
		opts->creation_mode = OPEN_ALWAYS;
		break;

	case _O_TRUNC:
	case _O_TRUNC|_O_EXCL:
		opts->creation_mode = TRUNCATE_EXISTING;
		break;

	case _O_CREAT|_O_EXCL:
	case _O_CREAT|_O_TRUNC|_O_EXCL:
		opts->creation_mode = CREATE_NEW;
		break;

	case _O_CREAT|_O_TRUNC:
		opts->creation_mode = CREATE_ALWAYS;
		break;

	default:
		mm_crash("Previous cases should have covered all possibilities");
	}

	return 0;
}


LOCAL_SYMBOL
int set_w32_create_file_options(struct w32_create_file_options* opts,
                                int oflags)
{
	if (set_access_mode(opts, oflags)
	    || set_creation_mode(opts, oflags))
		return -1;

	opts->file_attribute = FILE_ATTRIBUTE_NORMAL;
	return 0;
}


/**
 * open_handle() - Helper to open file from UTF-8 path
 * @path:       UTF-8 path fo the file to open
 * @access:     desired access to file (dwDesiredAccess in CreateFile())
 * @creat:      action to take on a file or device that exists or does not
 *              exist. (dwCreationDisposition argument in CreateFile())
 * @sec:        Security descriptor to use if the file is created. If NULL,
 *              the file associated with the returned handle is assigned a
 *              default security descriptor.
 * @flags:      Flags and attributes. (dwFlagsAndAttributes in CreateFile())
 *
 * Return: in case of success, the handle of the file opened or created.
 * Otherwise INVALID_HANDLE_VALUE. Please note that this function is meant to
 * be helper for implement public operation and as such does not set error
 * state for the sake of more informative error reporting (The caller has
 * indeed the context of the call. It may retrieve error with GetLastError())
 */
LOCAL_SYMBOL
HANDLE open_handle(const char* path, DWORD access, DWORD creat,
                   SECURITY_DESCRIPTOR* sec, DWORD flags)
{
	HANDLE hnd = INVALID_HANDLE_VALUE;
	char16_t* path_u16 = NULL;
	int path_u16_len;

	SECURITY_ATTRIBUTES sec_attrs = {
		.nLength = sizeof(SECURITY_ATTRIBUTES),
		.lpSecurityDescriptor = sec,
		.bInheritHandle = FALSE,
	};

	// Get size for converted path into UTF-16
	path_u16_len = get_utf16_buffer_len_from_utf8(path);
	if (path_u16_len < 0)
		return INVALID_HANDLE_VALUE;

	// Allocate temporary UTF-16 path
	path_u16 = mm_malloca(path_u16_len*sizeof(*path_u16));
	if (!path_u16)
		return INVALID_HANDLE_VALUE;

	// Convert to UTF-16 and open/create file
	conv_utf8_to_utf16(path_u16, path_u16_len, path);
	hnd = CreateFileW(path_u16, access, FILE_SHARE_ALL, &sec_attrs,
	                  creat, flags, NULL);

	mm_freea(path_u16);

	return hnd;
}


/**
 * get_file_id_info_from_handle() - get file and volume ids
 * @hnd:        handle of an opened file
 * @info:       pointer to FILE_ID_INFO struct to fill
 *
 * This function is similar to GetFileInformationByHandleEx() called with
 * FileIdInfo with fallback in case of buggy volume type:
 * GetFileInformationByHandleEx() returns error when called on file on FAT32.
 *
 * Returns: 0 in case of success, -1 otherwise.
 */
LOCAL_SYMBOL
int get_file_id_info_from_handle(HANDLE hnd, FILE_ID_INFO* id_info)
{
	BY_HANDLE_FILE_INFORMATION file_info;
	DWORD id[4];

	// First try with normal call
	if (GetFileInformationByHandleEx(hnd, FileIdInfo, id_info,
	                                 sizeof(*id_info)))
		return 0;

	// GetFileInformationByHandleEx() usually fail with file handle in
	// FAT32. Let's fall back on GetFileInformationByHandle()
	if (!GetFileInformationByHandle(hnd, &file_info))
		return mm_raise_from_w32err("failed to get file info");

	// Convert 2 DWORDs file ID into FILE_ID_128
	id[0] = file_info.nFileIndexLow;
	id[1] = file_info.nFileIndexHigh;
	id[2] = 0;
	id[3] = 0;
	memcpy(&id_info->FileId, id, sizeof(id));

	id_info->VolumeSerialNumber = file_info.dwVolumeSerialNumber;
	return 0;
}


/**************************************************************************
 *                                                                        *
 *                       Access control setup                             *
 *                                                                        *
 **************************************************************************/

#define MIN_ACCESS_FLAGS \
	(READ_CONTROL | SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_READ_EA)
#define MIN_OWNER_ACCESS_FLAGS \
	(DELETE | WRITE_DAC | WRITE_OWNER | FILE_WRITE_EA | \
	 FILE_WRITE_ATTRIBUTES)

#define IS_DACL_EMPTY(acl, dacl_present) \
	(((acl) == NULL) && ((dacl_present) == TRUE))

#define IS_DACL_NULL(acl, dacl_present) \
	(((acl) == NULL) && ((dacl_present) == FALSE))

/**
 * union local_sid - type to preallocate buffer for SID on stack
 * @sid:         SID structure (not complete to hold a SID)
 * @buffer:      buffer large enough to hold any SID
 */
union local_sid {
	SID sid;
	char buffer[SECURITY_MAX_SID_SIZE];
};

static union local_sid everyone;

MM_CONSTRUCTOR(wellknown_sid)
{
	DWORD len = sizeof(everyone);

	CreateWellKnownSid(WinWorldSid, NULL, &everyone.sid, &len);
}


static
mode_t access_to_mode(DWORD access)
{
	mode_t mode = 0;

	mode |= (access & FILE_READ_DATA) ? S_IREAD : 0;
	mode |= (access & FILE_WRITE_DATA) ? S_IWRITE : 0;
	mode |= (access & FILE_EXECUTE) ? S_IEXEC : 0;

	return mode;
}


static
DWORD get_access_from_perm_bits(mode_t mode)
{
	DWORD access = MIN_ACCESS_FLAGS;

	access |= (mode & S_IREAD) ? FILE_GENERIC_READ : 0;
	access |= (mode & S_IWRITE) ? FILE_GENERIC_WRITE : 0;
	access |= (mode & S_IEXEC) ? FILE_GENERIC_EXECUTE : 0;

	return access;
}


/**
 * get_caller_sids() - retrieve owner and primary group SIDs of caller
 * @owner:       pointer to buffer that will receive the owner SID.
 *               Ignored if NULL.
 * @primary_group: pointer to buffer that will receive the primary group SID.
 *                 Ignored if NULL
 *
 * Return: 0 in case of success, -1 otherwise. Please note that this
 * function does not set error state. Use GetLastError() to retrieve the
 * origin of error.
 */
static
int get_caller_sids(SID* owner, SID* primary_group)
{
	HANDLE htoken = INVALID_HANDLE_VALUE;
	char tmp[128];
	TOKEN_OWNER* owner_info;
	TOKEN_PRIMARY_GROUP* group_info;
	DWORD len;
	int rv = -1;

	// Open access token of the caller
	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htoken))
		goto exit;

	// Get Owner SID
	if (owner) {
		len = sizeof(tmp);
		owner_info = (TOKEN_OWNER*)tmp;
		if (!GetTokenInformation(htoken, TokenOwner,
		                         owner_info, len, &len))
			goto exit;

		CopySid(SECURITY_MAX_SID_SIZE, owner,
		        owner_info->Owner);
	}

	// Get Primary group SID
	if (primary_group) {
		len = sizeof(tmp);
		group_info = (TOKEN_PRIMARY_GROUP*)tmp;
		if (!GetTokenInformation(htoken, TokenPrimaryGroup,
		                         group_info, len, &len))
			goto exit;

		CopySid(SECURITY_MAX_SID_SIZE, primary_group,
		        group_info->PrimaryGroup);
	}

	rv = 0;

exit:
	safe_closehandle(htoken);
	return rv;
}


static int owner_sid_strlen = -1;
static char16_t owner_sid_str[SID_STRING_MAXLEN];


static
void init_owner_sid_str(void)
{
	union local_sid owner;
	char16_t* sidstr = NULL;

	if (get_caller_sids(&owner.sid, NULL)
	    || !ConvertSidToStringSidW(&owner.sid, &sidstr)) {
		mm_raise_from_w32err("could not initialize owner SID string");
		return;
	}

	owner_sid_strlen = wcslen(sidstr);
	memcpy(owner_sid_str, sidstr, (owner_sid_strlen+1)*sizeof(*sidstr));

	LocalFree(sidstr);
}


LOCAL_SYMBOL
const char16_t* get_caller_string_sid_u16(void)
{
	static mm_thr_once_t sid_init_once = MM_THR_ONCE_INIT;

	// Initialize owner SID string only once: the owner SID of a process
	// cannot be changed
	mm_thr_once(&sid_init_once, init_owner_sid_str);

	if (owner_sid_strlen < 0)
		return NULL;

	return owner_sid_str;
}


/**
 * local_secdesc_resize() - accommodate local secdesc for a new size
 * @lsd:        pointer to local_secdesc structure to initialize
 * @size:       needed size for SECURITY_DESCRIPTOR
 *
 * Return: 0 in case of success, -1 otherwise. Please note that this
 * function does not set error state. Use GetLastError() to retrieve the
 * origin of error.
 */
static
int local_secdesc_resize(struct local_secdesc* lsd, size_t size)
{
	local_secdesc_deinit(lsd);

	if (size <= sizeof(lsd->buffer)) {
		lsd->sd = (SECURITY_DESCRIPTOR*)lsd->buffer;
		return 0;
	}

	lsd->sd = malloc(size);
	return lsd->sd ? 0 : -1;
}


/**
 * local_secdesc_init_from_mode() - init from permission bits
 * @lsd:        pointer to local_secdesc structure to initialize
 * @mode:       permission bits
 *
 * This function initialize a security descriptor in self-relative format in
 * @lsd->sd from the @mode argument and the access token of the caller. This
 * function is mostly called directly or indirectly when creating a new
 * object on filesystem (file, directory, named pipe, ...).
 *
 * Return: 0 in case of success, -1 otherwise. Please note that this
 * function does not set error state. Use GetLastError() to retrieve the
 * origin of error.
 */
LOCAL_SYMBOL
int local_secdesc_init_from_mode(struct local_secdesc* lsd, mode_t mode)
{
	PACL acl = NULL;
	SECURITY_DESCRIPTOR abs_sd;
	DWORD acl_size, access;
	union local_sid owner, group;
	DWORD len;
	int rv = -1;

	// Initial local secdesc functional field
	lsd->sd = NULL;

	// Get SID values
	if (get_caller_sids(&owner.sid, &group.sid))
		goto exit;

	// Initialize tmp_sd in absolute format
	InitializeSecurityDescriptor(&abs_sd, SECURITY_DESCRIPTOR_REVISION);

	acl_size = sizeof(ACL) + 3*sizeof(ACCESS_ALLOWED_ACE);
	acl_size += GetLengthSid(&owner.sid) - sizeof(DWORD);
	acl_size += GetLengthSid(&group.sid) - sizeof(DWORD);
	acl_size += GetLengthSid(&everyone.sid) - sizeof(DWORD);
	acl = mm_malloca(acl_size);
	InitializeAcl(acl, acl_size, ACL_REVISION);

	// Set owner permissions
	access = get_access_from_perm_bits(mode)|MIN_OWNER_ACCESS_FLAGS;
	if (!AddAccessAllowedAce(acl, ACL_REVISION, access, &owner.sid))
		goto exit;

	// Set group member permission
	access = get_access_from_perm_bits(mode << 3);
	if (!AddAccessAllowedAce(acl, ACL_REVISION, access, &group.sid))
		goto exit;

	// Set everyone permission
	access = get_access_from_perm_bits(mode << 6);
	if (!AddAccessAllowedAce(acl, ACL_REVISION, access, &everyone.sid))
		goto exit;

	// Add ACL into security descriptor and return a self-relative version
	if (!SetSecurityDescriptorDacl(&abs_sd, TRUE, acl, FALSE))
		goto exit;

	// Transform the security descriptor in absolute format into a
	// self-relative format and put it in lsd buffer
	len = sizeof(lsd->buffer);
	lsd->sd = (SECURITY_DESCRIPTOR*)lsd->buffer;
	while (!MakeSelfRelativeSD(&abs_sd, lsd->sd, &len)) {
		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER
		    || local_secdesc_resize(lsd, len))
			goto exit;
	}

	rv = 0;

exit:
	if (rv)
		local_secdesc_deinit(lsd);

	mm_freea(acl);
	return rv;
}


/**
 * local_secdesc_init_from_handle() - init from securable object handle
 * @lsd:        pointer to local_secdesc structure to initialize
 * @hnd:        handle to the securable object (most of time file handle)
 *
 * This function initializes a copy of security descriptor of @hnd in
 * self-relative format in @lsd->sd.
 *
 * Return: 0 in case of success, -1 otherwise. Please note that this
 * function does not set error state. Use GetLastError() to retrieve the
 * origin of error.
 */
LOCAL_SYMBOL
int local_secdesc_init_from_handle(struct local_secdesc* lsd, HANDLE hnd)
{
	int rv;
	DWORD len;
	SECURITY_INFORMATION requested_info = OWNER_SECURITY_INFORMATION
	                                      | GROUP_SECURITY_INFORMATION
	                                      | DACL_SECURITY_INFORMATION;

	// Start with using lsd->buffer as backing buffer of secuity descriptor
	lsd->sd = (SECURITY_DESCRIPTOR*)lsd->buffer;
	len = sizeof(lsd->buffer);

	rv = 0;
	while (!GetKernelObjectSecurity(hnd, requested_info,
	                                lsd->sd, len, &len)) {

		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER
		    || local_secdesc_resize(lsd, len)) {
			rv = -1;
			local_secdesc_deinit(lsd);
			break;
		}
	}

	return rv;
}


/**
 * local_secdesc_deinit() - Cleanup a initialized local secdesc.
 * @lsd:        pointer to an initialized local secdesc
 */
LOCAL_SYMBOL
void local_secdesc_deinit(struct local_secdesc* lsd)
{
	if ((char*)lsd->sd != lsd->buffer)
		free(lsd->sd);

	lsd->sd = NULL;
}


/**
 * local_secdesc_get_mode() - Get permission bits from secdesc
 * @lsd:        pointer to an initialized local secdesc
 *
 * Return: permission bits inferred from the security descriptor.
 */
LOCAL_SYMBOL
mode_t local_secdesc_get_mode(struct local_secdesc* lsd)
{
	PSID owner_sid = NULL, group_sid = NULL, sid = NULL;
	PACL acl = NULL;
	ACCESS_ALLOWED_ACE* ace = NULL;
	BOOL defaulted, dacl_present;
	mode_t mode;
	int i;

	GetSecurityDescriptorOwner(lsd->sd, &owner_sid, &defaulted);
	GetSecurityDescriptorGroup(lsd->sd, &group_sid, &defaulted);
	GetSecurityDescriptorDacl(lsd->sd, &dacl_present, &acl, &defaulted);

	// null DACL means all access while empty dacl means no access. See
	// https://msdn.microsoft.com/en-us/library/windows/desktop/aa446648(v=vs.85).aspx

	if (IS_DACL_NULL(acl, dacl_present))
		return 0777;

	if (IS_DACL_EMPTY(acl, dacl_present))
		return 0;

	// Scan all "Access Allowed" entries and interpret them as owner,
	// group or other permission depending on SID in the ACE
	mode = 0;
	for (i = 0; i < acl->AceCount; i++) {
		GetAce(acl, i, (LPVOID*)&ace);

		if (ace->Header.AceType != ACCESS_ALLOWED_ACE_TYPE)
			continue;

		sid = (PSID)(&ace->SidStart);

		if (owner_sid && EqualSid(sid, owner_sid)) {
			mode |= access_to_mode(ace->Mask);
		} else if (group_sid && EqualSid(sid, group_sid)) {
			mode |= (access_to_mode(ace->Mask) >> 3);
		} else if (EqualSid(sid, &everyone.sid)) {
			mode |= (access_to_mode(ace->Mask) >> 6);
		}
	}

	return mode;
}

/**************************************************************************
 *                                                                        *
 *                       Win32 Error reporting                            *
 *                                                                        *
 **************************************************************************/
/**
 * get_errcode_from_w32err() - translate Win32 error into error code
 * @w32err:     error value returned by GetLastError()
 *
 * Return: translated error code usable in mm_raise_error()
 */
LOCAL_SYMBOL
int get_errcode_from_w32err(DWORD w32err)
{
	switch (w32err) {

	case ERROR_TOO_MANY_OPEN_FILES:
	case WSAEMFILE:                 return EMFILE;
	case ERROR_FILE_EXISTS:
	case ERROR_ALREADY_EXISTS:      return EEXIST;
	case ERROR_INVALID_NAME:
	case ERROR_PATH_NOT_FOUND:
	case ERROR_FILE_NOT_FOUND:      return ENOENT;
	case ERROR_INVALID_HANDLE:      return EBADF;
	case ERROR_OUTOFMEMORY:         return ENOMEM;
	case ERROR_ACCESS_DENIED:
	case WSAEACCES:                 return EACCES;
	case ERROR_INVALID_PARAMETER:
	case ERROR_INVALID_ACCESS:
	case ERROR_INVALID_DATA:
	case WSAEINVAL:                 return EINVAL;
	case ERROR_BAD_EXE_FORMAT:      return ENOEXEC;
	case WSAESHUTDOWN:
	case ERROR_NO_DATA:
	case ERROR_BROKEN_PIPE:         return EPIPE;
	case ERROR_NOT_SAME_DEVICE:     return EXDEV;
	case WSASYSNOTREADY:
	case ERROR_DIRECTORY_NOT_SUPPORTED: return EISDIR;
	case ERROR_NOT_SUPPORTED:       return ENOTSUP;
	case WSAENETDOWN:               return ENETDOWN;
	case WSAENETUNREACH:            return ENETUNREACH;
	case WSAVERNOTSUPPORTED:        return ENOSYS;
	case WSAEINPROGRESS:            return EINPROGRESS;
	case WSAECONNRESET:             return ECONNRESET;
	case WSAEFAULT:                 return EFAULT;
	case WSAEMSGSIZE:               return EMSGSIZE;
	case WSAENOBUFS:                return ENOBUFS;
	case WSAEISCONN:                return EISCONN;
	case WSAENOTCONN:               return ENOTCONN;
	case WSAENOTSOCK:               return ENOTSOCK;
	case WSAECONNREFUSED:           return ECONNREFUSED;
	case WSAEDESTADDRREQ:           return EDESTADDRREQ;
	case WSAEADDRINUSE:             return EADDRINUSE;
	case WSAEADDRNOTAVAIL:          return EADDRNOTAVAIL;
	case WSAEOPNOTSUPP:             return EOPNOTSUPP;
	case WSAEINTR:                  return EINTR;
	case WSAENOPROTOOPT:            return ENOPROTOOPT;
	case WSAEPFNOSUPPORT:
	case WSAEAFNOSUPPORT:           return EAFNOSUPPORT;
	case WSAESOCKTNOSUPPORT:
	case WSAEPROTOTYPE:             return EPROTOTYPE;
	case WSAEPROTONOSUPPORT:        return EPROTONOSUPPORT;
	case WAIT_TIMEOUT:
	case WSAETIMEDOUT:              return ETIMEDOUT;
	case ERROR_NO_UNICODE_TRANSLATION: return EILSEQ;

	default:
		return EIO;
	}
}


/**
 * write_w32err_msg() - write error message associated to Win32 error
 * @w32err:     Win32 error code (for example from GetLastError())
 * @len:        length of buffer in @errmsg
 * @buff:       buffer that must receive the error message
 *
 * This writes in @buff the error message associated with Win32 error code
 * specified by @w32err. If @len is too short, the error message will be
 * truncated. The resulting string will be null-terminated (if @len is not 0).
 */
LOCAL_SYMBOL
void write_w32err_msg(DWORD w32err, size_t len, char* buff)
{
	if (len == 0)
		return;

	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
	              | FORMAT_MESSAGE_IGNORE_INSERTS
	              | FORMAT_MESSAGE_MAX_WIDTH_MASK,
	              NULL, w32err, 0, buff, len-1, NULL);

	// Replace '%' character in win32 message to avoid messing with
	// error message formatting in mm_raise_error_vfull()
	for (; *buff != '\0'; buff++) {
		if (*buff == '%')
			*buff = ' ';
	}
}


LOCAL_SYMBOL
int mm_raise_from_w32err_full(const char* module, const char* func,
                              const char* srcfile, int srcline,
                              const char* desc, ...)
{
	DWORD w32err = GetLastError();
	int errcode, ret;
	size_t len;
	va_list args;
	char errmsg[512];

	len = snprintf(errmsg, sizeof(errmsg), "%s : ", desc);

	// Append Win32 error message if space is remaining on string
	write_w32err_msg(w32err, sizeof(errmsg)-len, errmsg+len);

	// Translate win32 error into mmlib error code
	errcode = get_errcode_from_w32err(w32err);

	va_start(args, desc);
	ret = mm_raise_error_vfull(errcode, module, func,
	                           srcfile, srcline, NULL, errmsg, args);
	va_end(args);

	return ret;
}


/**************************************************************************
 *                                                                        *
 *                  File descriptor mmlib metadata                        *
 *                                                                        *
 **************************************************************************/

/**
 * is_cygpty_pipe() - Test a pipe handle is a cygwin PTY
 * @hnd:        a handle to a pipe
 *
 * cygwin/msys often use mintty to emulate terminal. When this happens, the
 * handle associated with the terminal is not of the type Console but a
 * named pipe connected to a local server which implements the xterm
 * protocol. The name of the pipe is very specific to it and can be
 * recognized easily. Example of read handle of PTY1 when using msys:
 * \msys-dd50a72ab4668b33-pty1-from-master
 *
 * Return: 1 if @hnd has been recognized to be cygwin/msys PTY, 0 otherwise
 */
static
int is_cygpty_pipe(HANDLE hnd)
{
	char buff[256];
	FILE_NAME_INFO* info = (FILE_NAME_INFO*)buff;
	char* name_u8;
	char orig[8] = "";
	char dir[16] = "";
	int r, len;

	// Get name associated with the handle... Advertised buffer size is
	// smaller to ensure we can write null terminator at the end
	// of filename. Also we don't bother to get the name if buffer is
	// not sufficient, because in this case, the name definitively does
	// not fit the PTY name pattern
	if (!GetFileInformationByHandleEx(hnd, FileNameInfo,
	                                  info, sizeof(buff)-sizeof(WCHAR)))
		return 0;

	// Add null terminator to filename
	len = info->FileNameLength/sizeof(*info->FileName);
	info->FileName[len] = L'\0';

	// Convert filename in utf8
	name_u8 = alloca(len + 1);
	if (conv_utf16_to_utf8(name_u8, len+1, info->FileName) < 0)
		return 0;

	// PTY from cygwin/msys have a name of the form:
	// \(cygwin|msys)-[number_in_hexa]-ptyN-(from-master|to-master)
	// (the actual \Device\NamedPipe part is striped by
	// GetFileInformationByHandleEx(FileNameInfo)
	r = sscanf(name_u8,
	           "\\%7[a-z]-%*[0-9a-f]-pty%*u-%15[a-z]-master",
	           orig,
	           dir);
	if ((r == 2)
	    && (!strcmp(orig, "msys") || !strcmp(orig, "cygwin"))
	    && (!strcmp(dir, "from") || !strcmp(dir, "to")))
		return 1;

	return 0;
}


/**
 * guess_fd_info() - inspect fd and associated handle and guess type info
 * @fd:         file descriptor to inspect
 *
 * Once the type is guessed, it is stored in mmlib fd metadata so that it will
 * not be guessed the next time @fd is encountered... Of course, until
 * mm_close() is called on @fd.
 *
 * Return: a FD_TYPE_* constant different from FD_TYPE_UNKNOWN in case of
 * success, -1 in case of failure.
 */
static NOINLINE
int guess_fd_info(int fd)
{
	int info, tmode;
	HANDLE hnd;
	DWORD mode, type;

	hnd = (HANDLE)_get_osfhandle(fd);
	if (hnd == INVALID_HANDLE_VALUE)
		return -1;

	type = GetFileType(hnd);

	info = FD_TYPE_NORMAL;
	if ((type == FILE_TYPE_CHAR) && GetConsoleMode(hnd, &mode)) {
		info = FD_TYPE_CONSOLE | FD_FLAG_TEXT | FD_FLAG_ISATTY;
		goto exit;
	} else if (type == FILE_TYPE_PIPE) {
		info = FD_TYPE_PIPE;
		if (is_cygpty_pipe(hnd)) {
			info |= FD_FLAG_ISATTY;
			goto exit;
		}
	}

	// Detect whether the file is in text mode
	tmode = _setmode(fd, _O_BINARY);
	if (tmode != -1) {
		_setmode(fd, tmode);
		if (tmode != _O_BINARY)
			info |= FD_FLAG_TEXT;
	}

exit:
	set_fd_info(fd, info);
	return info;
}

// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio
#define MAX_FD  2048


static unsigned char fd_infos[MAX_FD] = {0};


/**
 * get_fd_info_checked() - validate file descriptor and retrieve its info
 * @fd:         file descriptor whose info has to be retrieved
 *
 * Return: If successful, a non-negative file descriptor info. If @fd cannot be
 * a valid file descriptor -1 is returned (please note that error state is NOT
 * set in such a case).
 */
LOCAL_SYMBOL
int get_fd_info_checked(int fd)
{
	int info;

	if ((fd < 0) || (fd >= MAX_FD))
		return -1;

	info = fd_infos[fd];
	if (UNLIKELY(info == FD_TYPE_UNKNOWN))
		info = guess_fd_info(fd);

	return info;
}


/**
 * get_fd_info() - get file descriptor info (no validation check)
 * @fd:         file descriptor whose info has to be retrieved
 *
 * Return: file descriptor info
 */
LOCAL_SYMBOL
int get_fd_info(int fd)
{
	return fd_infos[fd];
}


/**
 * set_fd_info() - set file descriptor info (no validation check)
 * @fd:         file descriptor whose info has to be set
 */
LOCAL_SYMBOL
void set_fd_info(int fd, int info)
{
	fd_infos[fd] = info;
}


/**
 * close_all_known_fds() - forcingly close all open file descriptors
 *
 * Use this function only in case of unusual cleanup process.
 * Beware: this is not thread safe. Use only this function if the other
 * threads cannot oipen new fds or if it does not matter
 */
LOCAL_SYMBOL
void close_all_known_fds(void)
{
	int fd;

	for (fd = 0; fd < MAX_FD; fd++) {
		if (fd_infos[fd] == FD_TYPE_UNKNOWN)
			continue;

		_close(fd);
		fd_infos[fd] = FD_TYPE_UNKNOWN;
	}
}


/**************************************************************************
 *                                                                        *
 *                         UTF-8/UTF-16 conversion                        *
 *                                                                        *
 **************************************************************************/

/**
 * get_utf16_buffer_len_from_utf8() - get size needed for the UTF-16 string
 * @utf8_str:   null terminated UTF-8 encoded string
 *
 * Return: number of UTF-16 code unit (ie char16_t) needed to hold the UTF-16
 * encoded string that would be equivalent to @utf8_str (this includes the
 * NUL termination).
 */
LOCAL_SYMBOL
int get_utf16_buffer_len_from_utf8(const char* utf8_str)
{
	int len;

	len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
	                          utf8_str, -1,
	                          NULL, 0);

	return (len == 0) ? -1 : len;
}


/**
 * conv_utf8_to_utf16() - convert UTF-8 string into a UTF-16 string
 * @utf16_str:  buffer receiving the converted UTF-16 string
 * @utf16_len:  length of @utf16_len in code unit (char16_t)
 * @utf8_str:   null terminated UTF-8 encoded string
 *
 * This function convert the string @utf8_str encoded in UTF-8 into UTF-16
 * and store the result in @utf16_str. The length @utf16_len of this buffer
 * must be large enough to hold the whole transformed string including NUL
 * termination. Use get_utf16_buffer_size_from_utf8() to allocate the
 * necessary size.
 *
 * Return: O in case of success, -1 otherwise with error state set
 */
LOCAL_SYMBOL
int conv_utf8_to_utf16(char16_t* utf16_str, int utf16_len, const char* utf8_str)
{
	int len;

	len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
	                          utf8_str, -1, utf16_str, utf16_len);

	return (len == 0) ? -1 : len;
}


/**
 * get_utf8_buffer_size_from_utf16() - get size needed for the UTF-8 string
 * @utf16_str:   null terminated UTF-16 encoded string
 *
 * Return: number of UTF-8 code unit (ie char) needed to hold the UTF-8
 * encoded string that would be equivalent to @utf16_str (this includes the
 * NUL termination).
 */
LOCAL_SYMBOL
int get_utf8_buffer_len_from_utf16(const char16_t* utf16_str)
{
	int len;
	len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
	                          utf16_str, -1, NULL, 0, NULL, NULL);

	return (len == 0) ? -1 : len;
}


/**
 * conv_utf16_to_utf8() - convert UTF-16 string into a UTF-8 string
 * @utf8_str:  buffer receiving the converted UTF-8 string
 * @utf8_len:  length of @utf8_len in code unit (char16_t)
 * @utf16_str:   null terminated UTF-16 encoded string
 *
 * This function convert the string @utf16_str encoded in UTF-16 into UTF-8
 * and store the result in @utf8_str. The length @utf8_len of this buffer
 * must be large enough to hold the whole transformed string including NUL
 * termination. Use get_utf8_buffer_size_from_utf16() to allocate the
 * necessary size.
 *
 * Return: O in case of success, -1 otherwise with error state set
 */
LOCAL_SYMBOL
int conv_utf16_to_utf8(char* utf8_str, int utf8_len, const char16_t* utf16_str)
{
	int len;
	len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
	                          utf16_str, -1, utf8_str, utf8_len,
	                          NULL, NULL);

	return (len == 0) ? -1 : len;
}


