/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/process_util.h"

#include <windows.h>
#include <winternl.h>
#include <psapi.h>
#include <io.h>
#ifndef STDOUT_FILENO
#  define STDOUT_FILENO 1
#endif

#include "base/command_line.h"
#include "base/histogram.h"
#include "base/logging.h"
#include "base/win_util.h"

#include "mozilla/ipc/LaunchError.h"
#include "mozilla/Result.h"

#include <algorithm>
#include <stdio.h>
#include <stdlib.h>

namespace {

typedef BOOL(WINAPI* InitializeProcThreadAttributeListFn)(
    LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, DWORD dwAttributeCount,
    DWORD dwFlags, PSIZE_T lpSize);

typedef BOOL(WINAPI* DeleteProcThreadAttributeListFn)(
    LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList);

typedef BOOL(WINAPI* UpdateProcThreadAttributeFn)(
    LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, DWORD dwFlags,
    DWORD_PTR Attribute, PVOID lpValue, SIZE_T cbSize, PVOID lpPreviousValue,
    PSIZE_T lpReturnSize);

static InitializeProcThreadAttributeListFn InitializeProcThreadAttributeListPtr;
static DeleteProcThreadAttributeListFn DeleteProcThreadAttributeListPtr;
static UpdateProcThreadAttributeFn UpdateProcThreadAttributePtr;

static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG");

}  // namespace

namespace base {

ProcessId GetCurrentProcId() { return ::GetCurrentProcessId(); }

ProcessHandle GetCurrentProcessHandle() { return ::GetCurrentProcess(); }

bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) {
  // TODO(phajdan.jr): Take even more permissions out of this list.
  ProcessHandle result =
      OpenProcess(PROCESS_DUP_HANDLE | PROCESS_TERMINATE |
                      PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
                  FALSE, pid);

  if (result == NULL) {
    return false;
  }

  *handle = result;
  return true;
}

bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) {
  ProcessHandle result =
      OpenProcess(PROCESS_DUP_HANDLE | PROCESS_TERMINATE |
                      PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE,
                  FALSE, pid);

  if (result == NULL) {
    return false;
  }

  *handle = result;
  return true;
}

void CloseProcessHandle(ProcessHandle process) {
  // closing a handle twice on Windows can be catastrophic - after the first
  // close the handle value may be reused, so the second close will kill that
  // other new handle.
  BOOL ok = CloseHandle(process);
  DCHECK(ok);
}

ProcessId GetProcId(ProcessHandle process) {
  if (process == base::kInvalidProcessHandle || process == nullptr) {
    return 0;
  }
  // This returns 0 if we have insufficient rights to query the process handle.
  // Invalid handles or non-process handles will cause a diagnostic assert.
  ProcessId result = GetProcessId(process);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  CHECK(result != 0 || GetLastError() != ERROR_INVALID_HANDLE)
  << "process handle = " << process;
#endif
  return result;
}

// from sandbox_policy_base.cc in a later version of the chromium ipc code...
bool IsInheritableHandle(HANDLE handle) {
  if (!handle) return false;
  if (handle == INVALID_HANDLE_VALUE) return false;
  // File handles (FILE_TYPE_DISK) and pipe handles are known to be
  // inheritable.  Console handles (FILE_TYPE_CHAR) are not
  // inheritable via PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
  DWORD handle_type = GetFileType(handle);
  return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE;
}

void LoadThreadAttributeFunctions() {
  HMODULE kernel32 = GetModuleHandle(L"kernel32.dll");
  InitializeProcThreadAttributeListPtr =
      reinterpret_cast<InitializeProcThreadAttributeListFn>(
          GetProcAddress(kernel32, "InitializeProcThreadAttributeList"));
  DeleteProcThreadAttributeListPtr =
      reinterpret_cast<DeleteProcThreadAttributeListFn>(
          GetProcAddress(kernel32, "DeleteProcThreadAttributeList"));
  UpdateProcThreadAttributePtr = reinterpret_cast<UpdateProcThreadAttributeFn>(
      GetProcAddress(kernel32, "UpdateProcThreadAttribute"));
}

// Creates and returns a "thread attribute list" to pass to the child process.
// On return, is a pointer to a THREAD_ATTRIBUTE_LIST or NULL if either the
// functions we need aren't available (eg, XP or earlier) or the functions we
// need failed.
// The result of this function must be passed to FreeThreadAttributeList.
// Note that the pointer to the HANDLE array ends up embedded in the result of
// this function and must stay alive until FreeThreadAttributeList is called,
// hence it is passed in so the owner is the caller of this function.
LPPROC_THREAD_ATTRIBUTE_LIST CreateThreadAttributeList(HANDLE* handlesToInherit,
                                                       int handleCount) {
  if (!InitializeProcThreadAttributeListPtr ||
      !DeleteProcThreadAttributeListPtr || !UpdateProcThreadAttributePtr)
    LoadThreadAttributeFunctions();
  // shouldn't happen as we are only called for Vista+, but better safe than
  // sorry...
  if (!InitializeProcThreadAttributeListPtr ||
      !DeleteProcThreadAttributeListPtr || !UpdateProcThreadAttributePtr)
    return NULL;

  SIZE_T threadAttrSize;
  LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;

  if (!(*InitializeProcThreadAttributeListPtr)(NULL, 1, 0, &threadAttrSize) &&
      GetLastError() != ERROR_INSUFFICIENT_BUFFER)
    goto fail;
  lpAttributeList =
      reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(threadAttrSize));
  if (!lpAttributeList || !(*InitializeProcThreadAttributeListPtr)(
                              lpAttributeList, 1, 0, &threadAttrSize))
    goto fail;

  if (!(*UpdateProcThreadAttributePtr)(
          lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
          handlesToInherit, sizeof(handlesToInherit[0]) * handleCount, NULL,
          NULL)) {
    (*DeleteProcThreadAttributeListPtr)(lpAttributeList);
    goto fail;
  }
  return lpAttributeList;

fail:
  if (lpAttributeList) free(lpAttributeList);
  return NULL;
}

// Frees the data returned by CreateThreadAttributeList.
void FreeThreadAttributeList(LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList) {
  // must be impossible to get a NULL DeleteProcThreadAttributeListPtr, as
  // we already checked it existed when creating the data we are now freeing.
  (*DeleteProcThreadAttributeListPtr)(lpAttributeList);
  free(lpAttributeList);
}

// The next two functions are from chromium/base/environment.cc
//
// Parses a null-terminated input string of an environment block. The key is
// placed into the given string, and the total length of the line, including
// the terminating null, is returned.
static size_t ParseEnvLine(const NativeEnvironmentString::value_type* input,
                           NativeEnvironmentString* key) {
  // Skip to the equals or end of the string, this is the key.
  size_t cur = 0;
  while (input[cur] && input[cur] != '=') cur++;
  *key = NativeEnvironmentString(&input[0], cur);

  // Now just skip to the end of the string.
  while (input[cur]) cur++;
  return cur + 1;
}

std::wstring AlterEnvironment(const wchar_t* env,
                              const EnvironmentMap& changes) {
  std::wstring result;

  // First copy all unmodified values to the output.
  size_t cur_env = 0;
  std::wstring key;
  while (env[cur_env]) {
    const wchar_t* line = &env[cur_env];
    size_t line_length = ParseEnvLine(line, &key);

    // Keep only values not specified in the change vector.
    EnvironmentMap::const_iterator found_change = changes.find(key);
    if (found_change == changes.end()) result.append(line, line_length);

    cur_env += line_length;
  }

  // Now append all modified and new values.
  for (EnvironmentMap::const_iterator i = changes.begin(); i != changes.end();
       ++i) {
    if (!i->second.empty()) {
      result.append(i->first);
      result.push_back('=');
      result.append(i->second);
      result.push_back(0);
    }
  }

  // An additional null marks the end of the list. We always need a double-null
  // in case nothing was added above.
  if (result.empty()) result.push_back(0);
  result.push_back(0);
  return result;
}

Result<Ok, LaunchError> LaunchApp(const std::wstring& cmdline,
                                  const LaunchOptions& options,
                                  ProcessHandle* process_handle) {
  // We want to inherit the std handles so dump() statements and assertion
  // messages in the child process can be seen - but we *do not* want to
  // blindly have all handles inherited.  Vista and later has a technique
  // where only specified handles are inherited - so we use this technique.
  // If that fails we just don't inherit anything.
  DWORD dwCreationFlags = 0;
  BOOL bInheritHandles = FALSE;

  // We use a STARTUPINFOEX, but if we can't do the thread attribute thing, we
  // just pass the size of a STARTUPINFO.
  STARTUPINFOEX startup_info_ex;
  ZeroMemory(&startup_info_ex, sizeof(startup_info_ex));
  STARTUPINFO& startup_info = startup_info_ex.StartupInfo;
  startup_info.cb = sizeof(startup_info);
  startup_info.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEOFFFEEDBACK;
  startup_info.wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW;

  // Per the comment in CreateThreadAttributeList, lpAttributeList will contain
  // a pointer to handlesToInherit, so make sure they have the same lifetime.
  LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
  std::vector<HANDLE> handlesToInherit;
  for (HANDLE h : options.handles_to_inherit) {
    if (SetHandleInformation(h, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ==
        0) {
      MOZ_DIAGNOSTIC_CRASH("SetHandleInformation failed");
      return Err(
          LaunchError::FromWin32Error("SetHandleInformation", GetLastError()));
    }
    handlesToInherit.push_back(h);
  }

  // setup our handle array first - if we end up with no handles that can
  // be inherited we can avoid trying to do the ThreadAttributeList dance...
  HANDLE stdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
  HANDLE stdErr = ::GetStdHandle(STD_ERROR_HANDLE);

  if (IsInheritableHandle(stdOut)) handlesToInherit.push_back(stdOut);
  if (stdErr != stdOut && IsInheritableHandle(stdErr))
    handlesToInherit.push_back(stdErr);

  if (!handlesToInherit.empty()) {
    lpAttributeList = CreateThreadAttributeList(handlesToInherit.data(),
                                                handlesToInherit.size());
    if (lpAttributeList) {
      // it's safe to inherit handles, so arrange for that...
      startup_info.cb = sizeof(startup_info_ex);
      startup_info.dwFlags |= STARTF_USESTDHANDLES;
      startup_info.hStdOutput = stdOut;
      startup_info.hStdError = stdErr;
      startup_info.hStdInput = INVALID_HANDLE_VALUE;
      startup_info_ex.lpAttributeList = lpAttributeList;
      dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
      bInheritHandles = TRUE;
    }
  }

  dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
  if (options.start_independent) {
    dwCreationFlags |= CREATE_BREAKAWAY_FROM_JOB;
  }

  LPTCH original_environment = GetEnvironmentStrings();
  base::NativeEnvironmentString new_environment =
      AlterEnvironment(original_environment, options.env_map);
  // Ignore return value? What can we do?
  FreeEnvironmentStrings(original_environment);
  LPVOID new_env_ptr = (void*)new_environment.data();

  PROCESS_INFORMATION process_info;

  BOOL createdOK = CreateProcess(
      NULL, const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, bInheritHandles,
      dwCreationFlags, new_env_ptr, NULL, &startup_info, &process_info);
  if (lpAttributeList) FreeThreadAttributeList(lpAttributeList);
  if (!createdOK) {
    DLOG(WARNING) << "CreateProcess Failed: " << GetLastError();
    return Err(LaunchError::FromWin32Error("CreateProcess", GetLastError()));
  }

  gProcessLog.print("==> process %d launched child process %d (%S)\n",
                    GetCurrentProcId(), process_info.dwProcessId,
                    cmdline.c_str());

  // Handles must be closed or they will leak
  CloseHandle(process_info.hThread);

  if (options.wait) WaitForSingleObject(process_info.hProcess, INFINITE);

  // If the caller wants the process handle, we won't close it.
  if (process_handle) {
    *process_handle = process_info.hProcess;
  } else {
    CloseHandle(process_info.hProcess);
  }
  return Ok();
}

Result<Ok, LaunchError> LaunchApp(const CommandLine& cl,
                                  const LaunchOptions& options,
                                  ProcessHandle* process_handle) {
  return LaunchApp(cl.command_line_string(), options, process_handle);
}

bool KillProcess(ProcessHandle process, int exit_code) {
  // INVALID_HANDLE_VALUE is not actually an invalid handle value, but
  // instead is the value returned by GetCurrentProcess().  nullptr,
  // in contrast, *is* an invalid handle value.  Both values are too
  // easy to accidentally try to kill, and neither is legitimately
  // used by this function's callers, so reject them.
  if (!process || process == INVALID_HANDLE_VALUE) {
    CHROMIUM_LOG(WARNING)
        << "base::KillProcess refusing to terminate process handle " << process;
    return false;
  }
  bool result = (TerminateProcess(process, exit_code) != FALSE);
  if (!result) {
    DLOG(ERROR) << "Unable to terminate process: " << GetLastError();
  }
  return result;
}

}  // namespace base

namespace mozilla {

EnvironmentLog::EnvironmentLog(const char* varname, size_t len) {
  wchar_t wvarname[len];
  std::copy(varname, varname + len, wvarname);
  const wchar_t* e = _wgetenv(wvarname);
  if (e && *e) {
    fname_ = e;
  }
}

void EnvironmentLog::print(const char* format, ...) {
  if (!fname_.size()) return;

  FILE* f;
  if (fname_.compare(L"-") == 0) {
    f = fdopen(dup(STDOUT_FILENO), "a");
  } else {
    f = _wfopen(fname_.c_str(), L"a");
  }

  if (!f) return;

  va_list a;
  va_start(a, format);
  vfprintf(f, format, a);
  va_end(a);
  fclose(f);
}

}  // namespace mozilla
