// Copyright (c) 2011 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 "build/build_config.h"

#include "chrome/browser/nacl_host/nacl_process_host.h"

#if defined(OS_POSIX)
#include <fcntl.h>
#endif

#include "base/command_line.h"
#include "base/metrics/nacl_histogram.h"
#include "base/utf_string_conversions.h"
#include "base/win/windows_version.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/logging_chrome.h"
#include "chrome/common/nacl_cmd_line.h"
#include "chrome/common/nacl_messages.h"
#include "chrome/common/render_messages.h"
#include "chrome/browser/renderer_host/chrome_render_message_filter.h"
#include "ipc/ipc_switches.h"
#include "native_client/src/shared/imc/nacl_imc.h"

#if defined(OS_POSIX)
#include "ipc/ipc_channel_posix.h"
#elif defined(OS_WIN)
#include "chrome/browser/nacl_host/nacl_broker_service_win.h"
#endif

namespace {

#if !defined(DISABLE_NACL)
void SetCloseOnExec(nacl::Handle fd) {
#if defined(OS_POSIX)
  int flags = fcntl(fd, F_GETFD);
  CHECK(flags != -1);
  int rc = fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
  CHECK(rc == 0);
#endif
}
#endif

}  // namespace

struct NaClProcessHost::NaClInternal {
  std::vector<nacl::Handle> sockets_for_renderer;
  std::vector<nacl::Handle> sockets_for_sel_ldr;
};

NaClProcessHost::NaClProcessHost(const std::wstring& url)
    : BrowserChildProcessHost(NACL_LOADER_PROCESS),
      reply_msg_(NULL),
      internal_(new NaClInternal()),
      running_on_wow64_(false) {
  set_name(url);
#if defined(OS_WIN)
  running_on_wow64_ = (base::win::OSInfo::GetInstance()->wow64_status() ==
      base::win::OSInfo::WOW64_ENABLED);
#endif
}

NaClProcessHost::~NaClProcessHost() {
  if (!reply_msg_)
    return;

  // nacl::Close() is not available at link time if DISABLE_NACL is
  // defined, but we still compile a bunch of other code from this
  // file anyway.  TODO(mseaborn): Make this less messy.
#ifndef DISABLE_NACL
  for (size_t i = 0; i < internal_->sockets_for_renderer.size(); i++) {
    nacl::Close(internal_->sockets_for_renderer[i]);
  }
  for (size_t i = 0; i < internal_->sockets_for_sel_ldr.size(); i++) {
    nacl::Close(internal_->sockets_for_sel_ldr[i]);
  }
#endif

  // OnProcessLaunched didn't get called because the process couldn't launch.
  // Don't keep the renderer hanging.
  reply_msg_->set_reply_error();
  chrome_render_message_filter_->Send(reply_msg_);
}

bool NaClProcessHost::Launch(
    ChromeRenderMessageFilter* chrome_render_message_filter,
    int socket_count,
    IPC::Message* reply_msg) {
#ifdef DISABLE_NACL
  NOTIMPLEMENTED() << "Native Client disabled at build time";
  return false;
#else
  // Place an arbitrary limit on the number of sockets to limit
  // exposure in case the renderer is compromised.  We can increase
  // this if necessary.
  if (socket_count > 8) {
    return false;
  }

  // Rather than creating a socket pair in the renderer, and passing
  // one side through the browser to sel_ldr, socket pairs are created
  // in the browser and then passed to the renderer and sel_ldr.
  //
  // This is mainly for the benefit of Windows, where sockets cannot
  // be passed in messages, but are copied via DuplicateHandle().
  // This means the sandboxed renderer cannot send handles to the
  // browser process.

  for (int i = 0; i < socket_count; i++) {
    nacl::Handle pair[2];
    // Create a connected socket
    if (nacl::SocketPair(pair) == -1)
      return false;
    internal_->sockets_for_renderer.push_back(pair[0]);
    internal_->sockets_for_sel_ldr.push_back(pair[1]);
    SetCloseOnExec(pair[0]);
    SetCloseOnExec(pair[1]);
  }

  // Launch the process
  if (!LaunchSelLdr()) {
    return false;
  }
  UmaNaclHistogramEnumeration(NACL_STARTED);
  chrome_render_message_filter_ = chrome_render_message_filter;
  reply_msg_ = reply_msg;

  return true;
#endif  // DISABLE_NACL
}

bool NaClProcessHost::LaunchSelLdr() {
  if (!CreateChannel())
    return false;

  // Build command line for nacl.
  FilePath exe_path = GetChildPath(true);
  if (exe_path.empty())
    return false;

  CommandLine* cmd_line = new CommandLine(exe_path);
  nacl::CopyNaClCommandLineArguments(cmd_line);

  cmd_line->AppendSwitchASCII(switches::kProcessType,
                              switches::kNaClLoaderProcess);

  cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id());

  SetCrashReporterCommandLine(cmd_line);

  // On Windows we might need to start the broker process to launch a new loader
#if defined(OS_WIN)
  if (running_on_wow64_) {
    return NaClBrokerService::GetInstance()->LaunchLoader(
        this, ASCIIToWide(channel_id()));
  } else {
    BrowserChildProcessHost::Launch(FilePath(), cmd_line);
  }
#elif defined(OS_POSIX)
  BrowserChildProcessHost::Launch(true,  // use_zygote
                                  base::environment_vector(),
                                  cmd_line);
#endif

  return true;
}

void NaClProcessHost::OnProcessLaunchedByBroker(base::ProcessHandle handle) {
  set_handle(handle);
  OnProcessLaunched();
}

base::TerminationStatus NaClProcessHost::GetChildTerminationStatus(
    int* exit_code) {
  if (running_on_wow64_)
    return base::GetTerminationStatus(handle(), exit_code);
  return BrowserChildProcessHost::GetChildTerminationStatus(exit_code);
}

void NaClProcessHost::OnChildDied() {
#if defined(OS_WIN)
  NaClBrokerService::GetInstance()->OnLoaderDied();
#endif
  BrowserChildProcessHost::OnChildDied();
}

void NaClProcessHost::OnProcessLaunched() {
  std::vector<nacl::FileDescriptor> handles_for_renderer;
  base::ProcessHandle nacl_process_handle;

  for (size_t i = 0; i < internal_->sockets_for_renderer.size(); i++) {
#if defined(OS_WIN)
    // Copy the handle into the renderer process.
    HANDLE handle_in_renderer;
    DuplicateHandle(base::GetCurrentProcessHandle(),
                    reinterpret_cast<HANDLE>(
                        internal_->sockets_for_renderer[i]),
                    chrome_render_message_filter_->peer_handle(),
                    &handle_in_renderer,
                    GENERIC_READ | GENERIC_WRITE,
                    FALSE,
                    DUPLICATE_CLOSE_SOURCE);
    handles_for_renderer.push_back(
        reinterpret_cast<nacl::FileDescriptor>(handle_in_renderer));
#else
    // No need to dup the imc_handle - we don't pass it anywhere else so
    // it cannot be closed.
    nacl::FileDescriptor imc_handle;
    imc_handle.fd = internal_->sockets_for_renderer[i];
    imc_handle.auto_close = true;
    handles_for_renderer.push_back(imc_handle);
#endif
  }

#if defined(OS_WIN)
  // Copy the process handle into the renderer process.
  DuplicateHandle(base::GetCurrentProcessHandle(),
                  handle(),
                  chrome_render_message_filter_->peer_handle(),
                  &nacl_process_handle,
                  PROCESS_DUP_HANDLE,
                  FALSE,
                  0);
#else
  // We use pid as process handle on Posix
  nacl_process_handle = handle();
#endif

  // Get the pid of the NaCl process
  base::ProcessId nacl_process_id = base::GetProcId(handle());

  ViewHostMsg_LaunchNaCl::WriteReplyParams(
      reply_msg_, handles_for_renderer, nacl_process_handle, nacl_process_id);
  chrome_render_message_filter_->Send(reply_msg_);
  chrome_render_message_filter_ = NULL;
  reply_msg_ = NULL;
  internal_->sockets_for_renderer.clear();

  SendStartMessage();
}

void NaClProcessHost::SendStartMessage() {
  std::vector<nacl::FileDescriptor> handles_for_sel_ldr;
  for (size_t i = 0; i < internal_->sockets_for_sel_ldr.size(); i++) {
#if defined(OS_WIN)
    HANDLE channel;
    if (!DuplicateHandle(GetCurrentProcess(),
                         reinterpret_cast<HANDLE>(
                             internal_->sockets_for_sel_ldr[i]),
                         handle(),
                         &channel,
                         GENERIC_READ | GENERIC_WRITE,
                         FALSE, DUPLICATE_CLOSE_SOURCE)) {
      return;
    }
    handles_for_sel_ldr.push_back(
        reinterpret_cast<nacl::FileDescriptor>(channel));
#else
    nacl::FileDescriptor channel;
    channel.fd = dup(internal_->sockets_for_sel_ldr[i]);
    if (channel.fd < 0) {
      LOG(ERROR) << "Failed to dup() a file descriptor";
      return;
    }
    channel.auto_close = true;
    handles_for_sel_ldr.push_back(channel);
#endif
  }

#if defined(OS_MACOSX)
  // For dynamic loading support, NaCl requires a file descriptor that
  // was created in /tmp, since those created with shm_open() are not
  // mappable with PROT_EXEC.  Rather than requiring an extra IPC
  // round trip out of the sandbox, we create an FD here.
  base::SharedMemory memory_buffer;
  if (!memory_buffer.CreateAnonymous(/* size= */ 1)) {
    LOG(ERROR) << "Failed to allocate memory buffer";
    return;
  }
  nacl::FileDescriptor memory_fd;
  memory_fd.fd = dup(memory_buffer.handle().fd);
  if (memory_fd.fd < 0) {
    LOG(ERROR) << "Failed to dup() a file descriptor";
    return;
  }
  memory_fd.auto_close = true;
  handles_for_sel_ldr.push_back(memory_fd);
#endif

  Send(new NaClProcessMsg_Start(handles_for_sel_ldr));
  internal_->sockets_for_sel_ldr.clear();
}

bool NaClProcessHost::OnMessageReceived(const IPC::Message& msg) {
  NOTREACHED() << "Invalid message with type = " << msg.type();
  return false;
}

bool NaClProcessHost::CanShutdown() {
  return true;
}