// Copyright 2015 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "test/win/child_launcher.h"

#include "gtest/gtest.h"
#include "test/errors.h"
#include "util/win/command_line.h"

namespace crashpad {
namespace test {

ChildLauncher::ChildLauncher(const base::FilePath& executable,
                             const std::wstring& command_line)
    : executable_(executable),
      command_line_(command_line),
      process_handle_(),
      main_thread_handle_(),
      stdout_read_handle_(),
      stdin_write_handle_() {}

ChildLauncher::~ChildLauncher() {
  if (process_handle_.is_valid())
    WaitForExit();
}

void ChildLauncher::Start() {
  ASSERT_FALSE(process_handle_.is_valid());
  ASSERT_FALSE(main_thread_handle_.is_valid());
  ASSERT_FALSE(stdout_read_handle_.is_valid());

  // Create pipes for the stdin/stdout of the child.
  SECURITY_ATTRIBUTES security_attributes = {0};
  security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
  security_attributes.bInheritHandle = true;

  HANDLE stdout_read;
  HANDLE stdout_write;
  ASSERT_TRUE(CreatePipe(&stdout_read, &stdout_write, &security_attributes, 0))
      << ErrorMessage("CreatePipe");
  stdout_read_handle_.reset(stdout_read);
  ScopedFileHANDLE write_handle(stdout_write);
  ASSERT_TRUE(
      SetHandleInformation(stdout_read_handle_.get(), HANDLE_FLAG_INHERIT, 0))
      << ErrorMessage("SetHandleInformation");

  HANDLE stdin_read;
  HANDLE stdin_write;
  ASSERT_TRUE(CreatePipe(&stdin_read, &stdin_write, &security_attributes, 0))
      << ErrorMessage("CreatePipe");
  stdin_write_handle_.reset(stdin_write);
  ScopedFileHANDLE read_handle(stdin_read);
  ASSERT_TRUE(
      SetHandleInformation(stdin_write_handle_.get(), HANDLE_FLAG_INHERIT, 0))
      << ErrorMessage("SetHandleInformation");

  STARTUPINFO startup_info = {0};
  startup_info.cb = sizeof(startup_info);
  startup_info.hStdInput = read_handle.get();
  startup_info.hStdOutput = write_handle.get();
  startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
  EXPECT_NE(startup_info.hStdError, INVALID_HANDLE_VALUE)
      << ErrorMessage("GetStdHandle");
  startup_info.dwFlags = STARTF_USESTDHANDLES;
  PROCESS_INFORMATION process_information;
  std::wstring command_line;
  AppendCommandLineArgument(executable_.value(), &command_line);
  command_line += L" ";
  command_line += command_line_;
  ASSERT_TRUE(CreateProcess(executable_.value().c_str(),
                            &command_line[0],
                            nullptr,
                            nullptr,
                            true,
                            0,
                            nullptr,
                            nullptr,
                            &startup_info,
                            &process_information))
      << ErrorMessage("CreateProcess");
  // Take ownership of the two process handles returned.
  main_thread_handle_.reset(process_information.hThread);
  process_handle_.reset(process_information.hProcess);
}

DWORD ChildLauncher::WaitForExit() {
  EXPECT_TRUE(process_handle_.is_valid());
  EXPECT_EQ(WaitForSingleObject(process_handle_.get(), INFINITE), WAIT_OBJECT_0)
      << ErrorMessage("WaitForSingleObject");
  DWORD exit_code = 0;
  EXPECT_TRUE(GetExitCodeProcess(process_handle_.get(), &exit_code))
      << ErrorMessage("GetExitCodeProcess");
  process_handle_.reset();
  return exit_code;
}

}  // namespace test
}  // namespace crashpad
