/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/* exported createPipe, libc, win32 */

// ctypes is either already available in the chrome worker scope, or defined
// in scope via loadSubScript.
/* global ctypes */

// This file is loaded into the same scope as subprocess_shared.js.
/* import-globals-from subprocess_shared.js */

const LIBC_CHOICES = ["kernel32.dll"];

var win32 = {
  // On Windows 64, winapi_abi is an alias for default_abi.
  WINAPI: ctypes.winapi_abi,

  VOID: ctypes.void_t,

  BYTE: ctypes.uint8_t,
  WORD: ctypes.uint16_t,
  DWORD: ctypes.uint32_t,
  LONG: ctypes.long,
  LARGE_INTEGER: ctypes.int64_t,
  ULONGLONG: ctypes.uint64_t,

  UINT: ctypes.unsigned_int,
  UCHAR: ctypes.unsigned_char,

  BOOL: ctypes.bool,

  HANDLE: ctypes.voidptr_t,
  PVOID: ctypes.voidptr_t,
  LPVOID: ctypes.voidptr_t,

  CHAR: ctypes.char,
  WCHAR: ctypes.jschar,

  ULONG_PTR: ctypes.uintptr_t,

  SIZE_T: ctypes.size_t,
  PSIZE_T: ctypes.size_t.ptr,
};

Object.assign(win32, {
  DWORD_PTR: win32.ULONG_PTR,

  LPSTR: win32.CHAR.ptr,
  LPWSTR: win32.WCHAR.ptr,

  LPBYTE: win32.BYTE.ptr,
  LPDWORD: win32.DWORD.ptr,
  LPHANDLE: win32.HANDLE.ptr,

  // This is an opaque type.
  PROC_THREAD_ATTRIBUTE_LIST: ctypes.char.array(),
  LPPROC_THREAD_ATTRIBUTE_LIST: ctypes.char.ptr,
});

Object.assign(win32, {
  LPCSTR: win32.LPSTR,
  LPCWSTR: win32.LPWSTR,
  LPCVOID: win32.LPVOID,
});

Object.assign(win32, {
  INVALID_HANDLE_VALUE: ctypes.cast(ctypes.int64_t(-1), win32.HANDLE),
  NULL_HANDLE_VALUE: ctypes.cast(ctypes.uintptr_t(0), win32.HANDLE),

  CREATE_SUSPENDED: 0x00000004,
  CREATE_NEW_CONSOLE: 0x00000010,
  CREATE_UNICODE_ENVIRONMENT: 0x00000400,
  CREATE_NO_WINDOW: 0x08000000,
  CREATE_BREAKAWAY_FROM_JOB: 0x01000000,
  EXTENDED_STARTUPINFO_PRESENT: 0x00080000,

  STARTF_USESTDHANDLES: 0x0100,

  DUPLICATE_CLOSE_SOURCE: 0x01,
  DUPLICATE_SAME_ACCESS: 0x02,

  ERROR_HANDLE_EOF: 38,
  ERROR_BROKEN_PIPE: 109,
  ERROR_INSUFFICIENT_BUFFER: 122,
  ERROR_ABANDONED_WAIT_0: 735,
  ERROR_IO_INCOMPLETE: 996,
  ERROR_IO_PENDING: 997,

  FILE_ATTRIBUTE_NORMAL: 0x00000080,
  FILE_FLAG_OVERLAPPED: 0x40000000,

  GENERIC_WRITE: 0x40000000,

  OPEN_EXISTING: 0x00000003,

  PIPE_TYPE_BYTE: 0x00,

  PIPE_ACCESS_INBOUND: 0x01,
  PIPE_ACCESS_OUTBOUND: 0x02,
  PIPE_ACCESS_DUPLEX: 0x03,

  PIPE_WAIT: 0x00,
  PIPE_NOWAIT: 0x01,

  STILL_ACTIVE: 259,

  PROC_THREAD_ATTRIBUTE_HANDLE_LIST: 0x00020002,

  JobObjectAssociateCompletionPortInformation: 7,
  JobObjectBasicLimitInformation: 2,
  JobObjectExtendedLimitInformation: 9,

  JOB_OBJECT_LIMIT_BREAKAWAY_OK: 0x00000800,
  JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: 8,
  JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: 4,
  JOB_OBJECT_MSG_EXIT_PROCESS: 7,

  // These constants are 32-bit unsigned integers, but Windows defines
  // them as negative integers cast to an unsigned type.
  STD_INPUT_HANDLE: -10 + 0x100000000,
  STD_OUTPUT_HANDLE: -11 + 0x100000000,
  STD_ERROR_HANDLE: -12 + 0x100000000,

  WAIT_TIMEOUT: 0x00000102,
});

Object.assign(win32, {
  JOBOBJECT_BASIC_LIMIT_INFORMATION: new ctypes.StructType(
    "JOBOBJECT_BASIC_LIMIT_INFORMATION",
    [
      { PerProcessUserTimeLimit: win32.LARGE_INTEGER },
      { PerJobUserTimeLimit: win32.LARGE_INTEGER },
      { LimitFlags: win32.DWORD },
      { MinimumWorkingSetSize: win32.SIZE_T },
      { MaximumWorkingSetSize: win32.SIZE_T },
      { ActiveProcessLimit: win32.DWORD },
      { Affinity: win32.ULONG_PTR },
      { PriorityClass: win32.DWORD },
      { SchedulingClass: win32.DWORD },
    ]
  ),

  IO_COUNTERS: new ctypes.StructType("IO_COUNTERS", [
    { ReadOperationCount: win32.ULONGLONG },
    { WriteOperationCount: win32.ULONGLONG },
    { OtherOperationCount: win32.ULONGLONG },
    { ReadTransferCount: win32.ULONGLONG },
    { WriteTransferCount: win32.ULONGLONG },
    { OtherTransferCount: win32.ULONGLONG },
  ]),
});

Object.assign(win32, {
  JOBOBJECT_ASSOCIATE_COMPLETION_PORT: new ctypes.StructType(
    "JOBOBJECT_ASSOCIATE_COMPLETION_PORT",
    [{ CompletionKey: win32.PVOID }, { CompletionPort: win32.HANDLE }]
  ),
  JOBOBJECT_EXTENDED_LIMIT_INFORMATION: new ctypes.StructType(
    "JOBOBJECT_EXTENDED_LIMIT_INFORMATION",
    [
      { BasicLimitInformation: win32.JOBOBJECT_BASIC_LIMIT_INFORMATION },
      { IoInfo: win32.IO_COUNTERS },
      { ProcessMemoryLimit: win32.SIZE_T },
      { JobMemoryLimit: win32.SIZE_T },
      { PeakProcessMemoryUsed: win32.SIZE_T },
      { PeakJobMemoryUsed: win32.SIZE_T },
    ]
  ),

  OVERLAPPED: new ctypes.StructType("OVERLAPPED", [
    { Internal: win32.ULONG_PTR },
    { InternalHigh: win32.ULONG_PTR },
    { Offset: win32.DWORD },
    { OffsetHigh: win32.DWORD },
    { hEvent: win32.HANDLE },
  ]),

  PROCESS_INFORMATION: new ctypes.StructType("PROCESS_INFORMATION", [
    { hProcess: win32.HANDLE },
    { hThread: win32.HANDLE },
    { dwProcessId: win32.DWORD },
    { dwThreadId: win32.DWORD },
  ]),

  SECURITY_ATTRIBUTES: new ctypes.StructType("SECURITY_ATTRIBUTES", [
    { nLength: win32.DWORD },
    { lpSecurityDescriptor: win32.LPVOID },
    { bInheritHandle: win32.BOOL },
  ]),

  STARTUPINFOW: new ctypes.StructType("STARTUPINFOW", [
    { cb: win32.DWORD },
    { lpReserved: win32.LPWSTR },
    { lpDesktop: win32.LPWSTR },
    { lpTitle: win32.LPWSTR },
    { dwX: win32.DWORD },
    { dwY: win32.DWORD },
    { dwXSize: win32.DWORD },
    { dwYSize: win32.DWORD },
    { dwXCountChars: win32.DWORD },
    { dwYCountChars: win32.DWORD },
    { dwFillAttribute: win32.DWORD },
    { dwFlags: win32.DWORD },
    { wShowWindow: win32.WORD },
    { cbReserved2: win32.WORD },
    { lpReserved2: win32.LPBYTE },
    { hStdInput: win32.HANDLE },
    { hStdOutput: win32.HANDLE },
    { hStdError: win32.HANDLE },
  ]),
});

Object.assign(win32, {
  STARTUPINFOEXW: new ctypes.StructType("STARTUPINFOEXW", [
    { StartupInfo: win32.STARTUPINFOW },
    { lpAttributeList: win32.LPPROC_THREAD_ATTRIBUTE_LIST },
  ]),
});

var libc = new Library("libc", LIBC_CHOICES, {
  AssignProcessToJobObject: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* hJob */,
    win32.HANDLE /* hProcess */,
  ],

  CancelIo: [win32.WINAPI, win32.BOOL, win32.HANDLE /* hFile */],

  CloseHandle: [win32.WINAPI, win32.BOOL, win32.HANDLE /* hObject */],

  CreateFileW: [
    win32.WINAPI,
    win32.HANDLE,
    win32.LPWSTR /* lpFileName */,
    win32.DWORD /* dwDesiredAccess */,
    win32.DWORD /* dwShareMode */,
    win32.SECURITY_ATTRIBUTES.ptr /* opt lpSecurityAttributes */,
    win32.DWORD /* dwCreationDisposition */,
    win32.DWORD /* dwFlagsAndAttributes */,
    win32.HANDLE /* opt hTemplateFile */,
  ],

  CreateIoCompletionPort: [
    win32.WINAPI,
    win32.HANDLE,
    win32.HANDLE /* FileHandle */,
    win32.HANDLE /* opt ExistingCompletionPort */,
    win32.ULONG_PTR /* CompletionKey */,
    win32.DWORD /* NumberOfConcurrentThreads */,
  ],

  CreateJobObjectW: [
    win32.WINAPI,
    win32.HANDLE,
    win32.SECURITY_ATTRIBUTES.ptr /* opt lpJobAttributes */,
    win32.LPWSTR /* lpName */,
  ],

  CreateNamedPipeW: [
    win32.WINAPI,
    win32.HANDLE,
    win32.LPWSTR /* lpName */,
    win32.DWORD /* dwOpenMode */,
    win32.DWORD /* dwPipeMode */,
    win32.DWORD /* nMaxInstances */,
    win32.DWORD /* nOutBufferSize */,
    win32.DWORD /* nInBufferSize */,
    win32.DWORD /* nDefaultTimeOut */,
    win32.SECURITY_ATTRIBUTES.ptr /* opt lpSecurityAttributes */,
  ],

  CreatePipe: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPHANDLE /* out hReadPipe */,
    win32.LPHANDLE /* out hWritePipe */,
    win32.SECURITY_ATTRIBUTES.ptr /* opt lpPipeAttributes */,
    win32.DWORD /* nSize */,
  ],

  CreateProcessW: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPCWSTR /* lpApplicationName */,
    win32.LPWSTR /* lpCommandLine */,
    win32.SECURITY_ATTRIBUTES.ptr /* lpProcessAttributes */,
    win32.SECURITY_ATTRIBUTES.ptr /* lpThreadAttributes */,
    win32.BOOL /* bInheritHandle */,
    win32.DWORD /* dwCreationFlags */,
    win32.LPVOID /* opt lpEnvironment */,
    win32.LPCWSTR /* opt lpCurrentDirectory */,
    win32.STARTUPINFOW.ptr /* lpStartupInfo */,
    win32.PROCESS_INFORMATION.ptr /* out lpProcessInformation */,
  ],

  DeleteProcThreadAttributeList: [
    win32.WINAPI,
    win32.VOID,
    win32.LPPROC_THREAD_ATTRIBUTE_LIST /* in/out lpAttributeList */,
  ],

  DuplicateHandle: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* hSourceProcessHandle */,
    win32.HANDLE /* hSourceHandle */,
    win32.HANDLE /* hTargetProcessHandle */,
    win32.LPHANDLE /* out lpTargetHandle */,
    win32.DWORD /* dwDesiredAccess */,
    win32.BOOL /* bInheritHandle */,
    win32.DWORD /* dwOptions */,
  ],

  FreeEnvironmentStringsW: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPCWSTR /* lpszEnvironmentBlock */,
  ],

  GetCurrentProcess: [win32.WINAPI, win32.HANDLE],

  GetCurrentProcessId: [win32.WINAPI, win32.DWORD],

  GetEnvironmentStringsW: [win32.WINAPI, win32.LPCWSTR],

  GetExitCodeProcess: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* hProcess */,
    win32.LPDWORD /* lpExitCode */,
  ],

  GetOverlappedResult: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* hFile */,
    win32.OVERLAPPED.ptr /* lpOverlapped */,
    win32.LPDWORD /* lpNumberOfBytesTransferred */,
    win32.BOOL /* bWait */,
  ],

  GetQueuedCompletionStatus: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* CompletionPort */,
    win32.LPDWORD /* lpNumberOfBytesTransferred */,
    win32.ULONG_PTR.ptr /* out lpCompletionKey */,
    win32.OVERLAPPED.ptr.ptr /* out lpOverlapped */,
    win32.DWORD /* dwMilliseconds */,
  ],

  GetStdHandle: [win32.WINAPI, win32.HANDLE, win32.DWORD /* nStdHandle */],

  InitializeProcThreadAttributeList: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPPROC_THREAD_ATTRIBUTE_LIST /* out opt lpAttributeList */,
    win32.DWORD /* dwAttributeCount */,
    win32.DWORD /* dwFlags */,
    win32.PSIZE_T /* in/out lpSize */,
  ],

  PostQueuedCompletionStatus: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* CompletionPort */,
    win32.DWORD /* dwNumberOfBytesTransferred */,
    win32.ULONG_PTR /* dwCompletionKey */,
    win32.OVERLAPPED.ptr /* opt lpOverlapped */,
  ],

  ReadFile: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* hFile */,
    win32.LPVOID /* out lpBuffer */,
    win32.DWORD /* nNumberOfBytesToRead */,
    win32.LPDWORD /* opt out lpNumberOfBytesRead */,
    win32.OVERLAPPED.ptr /* opt in/out lpOverlapped */,
  ],

  ResumeThread: [win32.WINAPI, win32.DWORD, win32.HANDLE /* hThread */],

  SetInformationJobObject: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* hJob */,
    ctypes.int /* JobObjectInfoClass */,
    win32.LPVOID /* lpJobObjectInfo */,
    win32.DWORD /* cbJobObjectInfoLengt */,
  ],

  TerminateJobObject: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* hJob */,
    win32.UINT /* uExitCode */,
  ],

  TerminateProcess: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* hProcess */,
    win32.UINT /* uExitCode */,
  ],

  UpdateProcThreadAttribute: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPPROC_THREAD_ATTRIBUTE_LIST /* in/out lpAttributeList */,
    win32.DWORD /* dwFlags */,
    win32.DWORD_PTR /* Attribute */,
    win32.PVOID /* lpValue */,
    win32.SIZE_T /* cbSize */,
    win32.PVOID /* out opt lpPreviousValue */,
    win32.PSIZE_T /* opt lpReturnSize */,
  ],

  WriteFile: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE /* hFile */,
    win32.LPCVOID /* lpBuffer */,
    win32.DWORD /* nNumberOfBytesToRead */,
    win32.LPDWORD /* opt out lpNumberOfBytesWritten */,
    win32.OVERLAPPED.ptr /* opt in/out lpOverlapped */,
  ],
});

// Custom constant to use as CompletionKey / dwCompletionKey.
// See also IOCPKeyGenin subprocess_win.worker.js.
win32.IOCP_COMPLETION_KEY_WAKE_WORKER = 1;

let nextNamedPipeId = 0;

win32.Handle = function (handle) {
  return ctypes.CDataFinalizer(win32.HANDLE(handle), libc.CloseHandle);
};

win32.createPipe = function (secAttr, readFlags = 0, writeFlags = 0, size = 0) {
  readFlags |= win32.PIPE_ACCESS_INBOUND;
  writeFlags |= win32.FILE_ATTRIBUTE_NORMAL;

  if (size == 0) {
    size = 4096;
  }

  let pid = libc.GetCurrentProcessId();
  let pipeName = String.raw`\\.\Pipe\SubProcessPipe.${pid}.${nextNamedPipeId++}`;

  let readHandle = libc.CreateNamedPipeW(
    pipeName,
    readFlags,
    win32.PIPE_TYPE_BYTE | win32.PIPE_WAIT,
    1 /* number of connections */,
    size /* output buffer size */,
    size /* input buffer size */,
    0 /* timeout */,
    secAttr.address()
  );

  let isInvalid = handle =>
    String(handle) == String(win32.INVALID_HANDLE_VALUE);

  if (isInvalid(readHandle)) {
    return [];
  }

  let writeHandle = libc.CreateFileW(
    pipeName,
    win32.GENERIC_WRITE,
    0,
    secAttr.address(),
    win32.OPEN_EXISTING,
    writeFlags,
    null
  );

  if (isInvalid(writeHandle)) {
    libc.CloseHandle(readHandle);
    return [];
  }

  return [win32.Handle(readHandle), win32.Handle(writeHandle)];
};

win32.createThreadAttributeList = function (handles) {
  try {
    void libc.InitializeProcThreadAttributeList;
    void libc.DeleteProcThreadAttributeList;
    void libc.UpdateProcThreadAttribute;
  } catch (e) {
    // This is only supported in Windows Vista and later.
    return null;
  }

  let size = win32.SIZE_T();
  if (
    !libc.InitializeProcThreadAttributeList(null, 1, 0, size.address()) &&
    ctypes.winLastError != win32.ERROR_INSUFFICIENT_BUFFER
  ) {
    return null;
  }

  let attrList = win32.PROC_THREAD_ATTRIBUTE_LIST(size.value);

  if (!libc.InitializeProcThreadAttributeList(attrList, 1, 0, size.address())) {
    return null;
  }

  let ok = libc.UpdateProcThreadAttribute(
    attrList,
    0,
    win32.PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
    handles,
    handles.constructor.size,
    null,
    null
  );

  if (!ok) {
    libc.DeleteProcThreadAttributeList(attrList);
    return null;
  }

  return attrList;
};
