// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "bin/process.h"

#include "bin/dartutils.h"
#include "bin/io_buffer.h"
#include "bin/namespace.h"
#include "bin/platform.h"
#include "bin/socket.h"
#include "bin/utils.h"
#include "platform/syslog.h"

#include "include/dart_api.h"

namespace dart {
namespace bin {

static constexpr int kProcessIdNativeField = 0;

// Extract an array of C strings from a list of Dart strings.
static char** ExtractCStringList(Dart_Handle strings,
                                 Dart_Handle status_handle,
                                 const char* error_msg,
                                 intptr_t* length) {
  static constexpr intptr_t kMaxArgumentListLength = 1024 * 1024;
  ASSERT(Dart_IsList(strings));
  intptr_t len = 0;
  Dart_Handle result = Dart_ListLength(strings, &len);
  ThrowIfError(result);
  // Protect against user-defined list implementations that can have
  // arbitrary length.
  if ((len < 0) || (len > kMaxArgumentListLength)) {
    result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0);
    ThrowIfError(result);
    result = DartUtils::SetStringField(status_handle, "_errorMessage",
                                       "Max argument list length exceeded");
    ThrowIfError(result);
    return nullptr;
  }
  *length = len;
  char** string_args;
  string_args =
      reinterpret_cast<char**>(Dart_ScopeAllocate(len * sizeof(*string_args)));
  for (int i = 0; i < len; i++) {
    Dart_Handle arg = Dart_ListGetAt(strings, i);
    ThrowIfError(arg);
    if (!Dart_IsString(arg)) {
      result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0);
      ThrowIfError(result);
      result =
          DartUtils::SetStringField(status_handle, "_errorMessage", error_msg);
      ThrowIfError(result);
      return nullptr;
    }
    string_args[i] = const_cast<char*>(DartUtils::GetStringValue(arg));
  }
  return string_args;
}

bool Process::ModeIsAttached(ProcessStartMode mode) {
  return (mode == kNormal) || (mode == kInheritStdio);
}

bool Process::ModeHasStdio(ProcessStartMode mode) {
  return (mode == kNormal) || (mode == kDetachedWithStdio);
}

void Process::ClearAllSignalHandlers() {
  for (intptr_t i = 1; i <= kLastSignal; i++) {
    ClearSignalHandler(i, ILLEGAL_PORT);
  }
}

void FUNCTION_NAME(Process_Start)(Dart_NativeArguments args) {
  Dart_Handle process = Dart_GetNativeArgument(args, 0);
  intptr_t process_stdin;
  intptr_t process_stdout;
  intptr_t process_stderr;
  intptr_t exit_event;
  Namespace* namespc = Namespace::GetNamespace(args, 1);
  Dart_Handle status_handle = Dart_GetNativeArgument(args, 11);
  Dart_Handle path_handle = Dart_GetNativeArgument(args, 2);
  Dart_Handle result;

#if DART_HOST_OS_IOS
  // Do the iOS check here because the return value of Process::Start is
  // interpreted as a error with 0 meaning success while `ProcessException`
  // (which will be constructed with `_errorCode`) interprets 0 to mean that
  // no OS error code was available.
  result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0);
  ThrowIfError(result);
  result = DartUtils::SetStringField(
      status_handle, "_errorMessage",
      "Starting new processes is not supported on iOS");
  ThrowIfError(result);
  Dart_SetBooleanReturnValue(args, false);
  return;
#endif

  // The Dart code verifies that the path implements the String
  // interface. However, only builtin Strings are handled by
  // GetStringValue.
  if (!Dart_IsString(path_handle)) {
    result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0);
    ThrowIfError(result);
    result = DartUtils::SetStringField(status_handle, "_errorMessage",
                                       "Path must be a builtin string");
    ThrowIfError(result);
    Dart_SetBooleanReturnValue(args, false);
    return;
  }
  const char* path = DartUtils::GetStringValue(path_handle);
  Dart_Handle arguments = Dart_GetNativeArgument(args, 3);
  intptr_t args_length = 0;
  const char** string_args = const_cast<const char**>(
      ExtractCStringList(arguments, status_handle,
                         "Arguments must be builtin strings", &args_length));
  if (string_args == nullptr) {
    Dart_SetBooleanReturnValue(args, false);
    return;
  }
  Dart_Handle working_directory_handle = Dart_GetNativeArgument(args, 4);
  // Defaults to the current working directory.
  const char* working_directory = nullptr;
  if (Dart_IsString(working_directory_handle)) {
    working_directory = DartUtils::GetStringValue(working_directory_handle);
  } else if (!Dart_IsNull(working_directory_handle)) {
    result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0);
    ThrowIfError(result);
    result =
        DartUtils::SetStringField(status_handle, "_errorMessage",
                                  "WorkingDirectory must be a builtin string");
    ThrowIfError(result);
    Dart_SetBooleanReturnValue(args, false);
    return;
  }
  Dart_Handle environment = Dart_GetNativeArgument(args, 5);
  intptr_t environment_length = 0;
  char** string_environment = nullptr;
  if (!Dart_IsNull(environment)) {
    string_environment = ExtractCStringList(
        environment, status_handle,
        "Environment values must be builtin strings", &environment_length);
    if (string_environment == nullptr) {
      Dart_SetBooleanReturnValue(args, false);
      return;
    }
  }
  int64_t mode =
      DartUtils::GetInt64ValueCheckRange(Dart_GetNativeArgument(args, 6), 0, 3);
  Dart_Handle stdin_handle = Dart_GetNativeArgument(args, 7);
  Dart_Handle stdout_handle = Dart_GetNativeArgument(args, 8);
  Dart_Handle stderr_handle = Dart_GetNativeArgument(args, 9);
  Dart_Handle exit_handle = Dart_GetNativeArgument(args, 10);
  intptr_t pid = -1;
  char* os_error_message = nullptr;

  int error_code = Process::Start(
      namespc, path, string_args, args_length, working_directory,
      string_environment, environment_length,
      static_cast<ProcessStartMode>(mode), &process_stdout, &process_stdin,
      &process_stderr, &pid, &exit_event, &os_error_message);
  if (error_code == 0) {
    if (Process::ModeHasStdio(static_cast<ProcessStartMode>(mode))) {
      Socket::SetSocketIdNativeField(stdin_handle, process_stdin,
                                     Socket::kFinalizerNormal);
      Socket::SetSocketIdNativeField(stdout_handle, process_stdout,
                                     Socket::kFinalizerNormal);
      Socket::SetSocketIdNativeField(stderr_handle, process_stderr,
                                     Socket::kFinalizerNormal);
    }
    if (Process::ModeIsAttached(static_cast<ProcessStartMode>(mode))) {
      Socket::SetSocketIdNativeField(exit_handle, exit_event,
                                     Socket::kFinalizerNormal);
    }
    Process::SetProcessIdNativeField(process, pid);
  } else {
    result =
        DartUtils::SetIntegerField(status_handle, "_errorCode", error_code);
    ThrowIfError(result);

    const char* error_message = (os_error_message != nullptr)
                                    ? os_error_message
                                    : "Failed to get error message";
    Dart_Handle val = DartUtils::NewString(error_message);
    if (Dart_IsError(val)) {
      // Try to clean the message from non-ASCII characters.
      const intptr_t len = strlen(error_message);
      char* ascii_message =
          reinterpret_cast<char*>(Dart_ScopeAllocate(len + 1));
      for (intptr_t i = 0; i < len; i++) {
        if (static_cast<uint8_t>(error_message[i]) < 0x80) {
          ascii_message[i] = error_message[i];
        } else {
          ascii_message[i] = '?';
        }
      }
      ascii_message[len] = '\0';

      val = DartUtils::NewStringFormatted(
          "Failed to start %s. OS returned an error (code %d) which can't be "
          "fully converted to Dart string (%s): %s",
          path, error_code, Dart_GetError(val), ascii_message);
    }
    result = Dart_SetField(status_handle, DartUtils::NewString("_errorMessage"),
                           val);
    ThrowIfError(result);
  }
  Dart_SetBooleanReturnValue(args, error_code == 0);
}

void FUNCTION_NAME(Process_Wait)(Dart_NativeArguments args) {
  Dart_Handle process = Dart_GetNativeArgument(args, 0);
  Socket* process_stdin =
      Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 1));
  Socket* process_stdout =
      Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 2));
  Socket* process_stderr =
      Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 3));
  Socket* exit_event =
      Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 4));
  ProcessResult result;
  intptr_t pid;
  Process::GetProcessIdNativeField(process, &pid);
  bool success = Process::Wait(pid, process_stdin->fd(), process_stdout->fd(),
                               process_stderr->fd(), exit_event->fd(), &result);
  // Process::Wait() closes the file handles, so blow away the fds in the
  // Sockets so that they don't get picked up by the finalizer on _NativeSocket.
  process_stdin->CloseFd();
  process_stdout->CloseFd();
  process_stderr->CloseFd();
  exit_event->CloseFd();
  if (success) {
    Dart_Handle out = result.stdout_data();
    ThrowIfError(out);
    Dart_Handle err = result.stderr_data();
    ThrowIfError(err);
    Dart_Handle list = Dart_NewList(4);
    Dart_ListSetAt(list, 0, Dart_NewInteger(pid));
    Dart_ListSetAt(list, 1, Dart_NewInteger(result.exit_code()));
    Dart_ListSetAt(list, 2, out);
    Dart_ListSetAt(list, 3, err);
    Dart_SetReturnValue(args, list);
  } else {
    Dart_Handle error = DartUtils::NewDartOSError();
    Process::Kill(pid, 9);
    Dart_ThrowException(error);
  }
}

void FUNCTION_NAME(Process_KillPid)(Dart_NativeArguments args) {
  intptr_t pid = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 0));
  intptr_t signal = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 1));
  bool success = Process::Kill(pid, signal);
  Dart_SetBooleanReturnValue(args, success);
}

void FUNCTION_NAME(Process_Exit)(Dart_NativeArguments args) {
  int64_t status = 0;
  // Ignore result if passing invalid argument and just exit 0.
  DartUtils::GetInt64Value(Dart_GetNativeArgument(args, 0), &status);
  Process::RunExitHook(status);
  Dart_ExitIsolate();
  // We're not doing a full VM shutdown with Dart_Cleanup, which might block,
  // and other VM threads may be accessing state with global destructors, so
  // we skip global destructors by using _exit instead of exit.
  Platform::_Exit(static_cast<int>(status));
}

void FUNCTION_NAME(Process_SetExitCode)(Dart_NativeArguments args) {
  int64_t status = 0;
  // Ignore result if passing invalid argument and just set exit code to 0.
  DartUtils::GetInt64Value(Dart_GetNativeArgument(args, 0), &status);
  Process::SetGlobalExitCode(status);
}

void FUNCTION_NAME(Process_GetExitCode)(Dart_NativeArguments args) {
  Dart_SetIntegerReturnValue(args, Process::GlobalExitCode());
}

void FUNCTION_NAME(Process_Sleep)(Dart_NativeArguments args) {
  ScopedBlockingCall blocker;
  int64_t milliseconds = 0;
  // Ignore result if passing invalid argument and just set exit code to 0.
  DartUtils::GetInt64Value(Dart_GetNativeArgument(args, 0), &milliseconds);
  TimerUtils::Sleep(milliseconds);
}

void FUNCTION_NAME(Process_Pid)(Dart_NativeArguments args) {
  // Ignore result if passing invalid argument and just set exit code to 0.
  intptr_t pid = -1;
  Dart_Handle process = Dart_GetNativeArgument(args, 0);
  if (Dart_IsNull(process)) {
    pid = Process::CurrentProcessId();
  } else {
    Process::GetProcessIdNativeField(process, &pid);
  }
  Dart_SetIntegerReturnValue(args, pid);
}

void FUNCTION_NAME(Process_SetSignalHandler)(Dart_NativeArguments args) {
  intptr_t signal = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 0));
  intptr_t id = Process::SetSignalHandler(signal);
  if (id == -1) {
    Dart_SetReturnValue(args, DartUtils::NewDartOSError());
  } else {
    Dart_SetIntegerReturnValue(args, id);
  }
}

void FUNCTION_NAME(Process_ClearSignalHandler)(Dart_NativeArguments args) {
  intptr_t signal = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 0));
  Process::ClearSignalHandler(signal, Dart_GetMainPortId());
}

Dart_Handle Process::GetProcessIdNativeField(Dart_Handle process,
                                             intptr_t* pid) {
  return Dart_GetNativeInstanceField(process, kProcessIdNativeField, pid);
}

Dart_Handle Process::SetProcessIdNativeField(Dart_Handle process,
                                             intptr_t pid) {
  return Dart_SetNativeInstanceField(process, kProcessIdNativeField, pid);
}

void FUNCTION_NAME(SystemEncodingToString)(Dart_NativeArguments args) {
  Dart_Handle bytes = Dart_GetNativeArgument(args, 0);
  intptr_t bytes_length = 0;
  Dart_Handle result = Dart_ListLength(bytes, &bytes_length);
  ThrowIfError(result);
  uint8_t* buffer = Dart_ScopeAllocate(bytes_length + 1);
  result = Dart_ListGetAsBytes(bytes, 0, buffer, bytes_length);
  buffer[bytes_length] = '\0';
  ThrowIfError(result);
  intptr_t len;
  char* str = StringUtils::ConsoleStringToUtf8(reinterpret_cast<char*>(buffer),
                                               bytes_length, &len);
  if (str == nullptr) {
    Dart_ThrowException(DartUtils::NewDartUnsupportedError(
        "SystemEncodingToString not supported on this operating system"));
  }
  result = Dart_NewStringFromUTF8(reinterpret_cast<const uint8_t*>(str), len);
  ThrowIfError(result);
  Dart_SetReturnValue(args, result);
}

void FUNCTION_NAME(StringToSystemEncoding)(Dart_NativeArguments args) {
  Dart_Handle str = Dart_GetNativeArgument(args, 0);
  char* utf8;
  intptr_t utf8_len;
  Dart_Handle result =
      Dart_StringToUTF8(str, reinterpret_cast<uint8_t**>(&utf8), &utf8_len);
  ThrowIfError(result);
  intptr_t system_len;
  const char* system_string =
      StringUtils::Utf8ToConsoleString(utf8, utf8_len, &system_len);
  if (system_string == nullptr) {
    Dart_ThrowException(DartUtils::NewDartUnsupportedError(
        "StringToSystemEncoding not supported on this operating system"));
  }
  uint8_t* buffer = nullptr;
  Dart_Handle external_array = IOBuffer::Allocate(system_len, &buffer);
  if (Dart_IsNull(external_array)) {
    Dart_SetReturnValue(args, DartUtils::NewDartOSError());
    return;
  }
  if (!Dart_IsError(external_array)) {
    memmove(buffer, system_string, system_len);
  }
  Dart_SetReturnValue(args, external_array);
}

void FUNCTION_NAME(ProcessInfo_CurrentRSS)(Dart_NativeArguments args) {
  int64_t current_rss = Process::CurrentRSS();
  if (current_rss < 0) {
    Dart_SetReturnValue(args, DartUtils::NewDartOSError());
    return;
  }
  Dart_SetIntegerReturnValue(args, current_rss);
}

void FUNCTION_NAME(ProcessInfo_MaxRSS)(Dart_NativeArguments args) {
  int64_t max_rss = Process::MaxRSS();
  if (max_rss < 0) {
    Dart_SetReturnValue(args, DartUtils::NewDartOSError());
    return;
  }
  Dart_SetIntegerReturnValue(args, max_rss);
}

void Process::GetRSSInformation(int64_t* max_rss, int64_t* current_rss) {
  ASSERT(max_rss != nullptr);
  ASSERT(current_rss != nullptr);
  // Max RSS should be queried after current RSS to produce
  // consistent values as current RSS can grow beyond max RSS which
  // was queried before.
  *current_rss = Process::CurrentRSS();
  *max_rss = Process::MaxRSS();
}

}  // namespace bin
}  // namespace dart
