// Copyright 2019 Google LLC
//
// 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
//
//     https://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.

// Implementation of the sandbox2::Result class.

#include "sandboxed_api/sandbox2/result.h"

#include <sys/resource.h>

#include <cstdlib>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "sandboxed_api/config.h"
#include "sandboxed_api/sandbox2/syscall.h"
#include "sandboxed_api/sandbox2/util.h"

namespace sandbox2 {

Result& Result::operator=(const Result& other) {
  final_status_ = other.final_status_;
  reason_code_ = other.reason_code_;
  stack_trace_ = other.stack_trace_;
  if (other.regs_) {
    regs_ = std::make_unique<Regs>(*other.regs_);
  } else {
    regs_.reset(nullptr);
  }
  if (other.syscall_) {
    syscall_ = std::make_unique<Syscall>(*other.syscall_);
  } else {
    syscall_.reset(nullptr);
  }
  prog_name_ = other.prog_name_;
  proc_maps_ = other.proc_maps_;
  rusage_monitor_ = other.rusage_monitor_;
  rusage_sandboxee_ = other.rusage_sandboxee_;
  return *this;
}

std::string Result::GetStackTrace() const {
  return absl::StrJoin(stack_trace_, " ");
}

absl::Status Result::ToStatus() const {
  switch (final_status()) {
    case OK:
      if (reason_code() == 0) {
        return absl::OkStatus();
      }
      break;
    case TIMEOUT:
      return absl::DeadlineExceededError(ToString());
    default:
      break;
  }
  return absl::InternalError(ToString());
}

std::string Result::ToString() const {
  std::string result;
  switch (final_status()) {
    case sandbox2::Result::UNSET:
      result = absl::StrCat("UNSET - Code: ", reason_code());
      break;
    case sandbox2::Result::OK:
      result = absl::StrCat("OK - Exit code: ", reason_code());
      break;
    case sandbox2::Result::SETUP_ERROR:
      result = absl::StrCat(
          "SETUP_ERROR - Code: ",
          ReasonCodeEnumToString(static_cast<ReasonCodeEnum>(reason_code())));
      break;
    case sandbox2::Result::VIOLATION:
      if (syscall_) {
        result = absl::StrCat("SYSCALL VIOLATION - Violating Syscall ",
                              syscall_->GetDescription(),
                              " Stack: ", GetStackTrace());
      } else if (reason_code() == sandbox2::Result::VIOLATION_NETWORK) {
        result = absl::StrCat("NETWORK VIOLATION: ", GetNetworkViolation());
      } else {
        result = "SYSCALL VIOLATION - Unknown Violation";
      }
      break;
    case sandbox2::Result::SIGNALED:
      result = absl::StrCat("Process terminated with a SIGNAL - Signal: ",
                            util::GetSignalName(reason_code()),
                            " Stack: ", GetStackTrace());
      break;
    case sandbox2::Result::TIMEOUT:
      result = absl::StrCat("Process TIMEOUT - Code: ", reason_code(),
                            " Stack: ", GetStackTrace());
      break;
    case sandbox2::Result::EXTERNAL_KILL:
      result = absl::StrCat("Process killed by user - Code: ", reason_code(),
                            " Stack: ", GetStackTrace());
      break;
    case sandbox2::Result::INTERNAL_ERROR:
      result = absl::StrCat(
          "INTERNAL_ERROR - Code: ",
          ReasonCodeEnumToString(static_cast<ReasonCodeEnum>(reason_code())));
      break;
    default:
      result =
          absl::StrCat("<UNKNOWN>(", final_status(), ") Code: ", reason_code());
  }
  if constexpr (sapi::sanitizers::IsAny()) {
    absl::StrAppend(
        &result,
        " - Warning: this executor is built with ASAN, MSAN or TSAN, chances "
        "are the sandboxee is too, which is incompatible with sandboxing.");
  } else {
    if (
        getenv("COVERAGE") != nullptr) {
      absl::StrAppend(
          &result,
          " - Warning: this executor is built with coverage enabled, chances "
          "are the sandboxee too, which is incompatible with sandboxing.");
    }
  }
  return result;
}

std::string Result::StatusEnumToString(StatusEnum value) {
  switch (value) {
    case sandbox2::Result::UNSET:
      return "UNSET";
    case sandbox2::Result::OK:
      return "OK";
    case sandbox2::Result::SETUP_ERROR:
      return "SETUP_ERROR";
    case sandbox2::Result::VIOLATION:
      return "VIOLATION";
    case sandbox2::Result::SIGNALED:
      return "SIGNALED";
    case sandbox2::Result::TIMEOUT:
      return "TIMEOUT";
    case sandbox2::Result::EXTERNAL_KILL:
      return "EXTERNAL_KILL";
    case sandbox2::Result::INTERNAL_ERROR:
      return "INTERNAL_ERROR";
  }
  return "UNKNOWN";
}

std::string Result::ReasonCodeEnumToString(ReasonCodeEnum value) {
  switch (value) {
    case sandbox2::Result::UNSUPPORTED_ARCH:
      return "UNSUPPORTED_ARCH";
    case sandbox2::Result::FAILED_TIMERS:
      return "FAILED_TIMERS";
    case sandbox2::Result::FAILED_SIGNALS:
      return "FAILED_SIGNALS";
    case sandbox2::Result::FAILED_SUBPROCESS:
      return "FAILED_SUBPROCESS";
    case sandbox2::Result::FAILED_NOTIFY:
      return "FAILED_NOTIFY";
    case sandbox2::Result::FAILED_CONNECTION:
      return "FAILED_CONNECTION";
    case sandbox2::Result::FAILED_WAIT:
      return "FAILED_WAIT";
    case sandbox2::Result::FAILED_NAMESPACES:
      return "FAILED_NAMESPACES";
    case sandbox2::Result::FAILED_PTRACE:
      return "FAILED_PTRACE";
    case sandbox2::Result::FAILED_IPC:
      return "FAILED_IPC";
    case sandbox2::Result::FAILED_LIMITS:
      return "FAILED_LIMITS";
    case sandbox2::Result::FAILED_CWD:
      return "FAILED_CWD";
    case sandbox2::Result::FAILED_POLICY:
      return "FAILED_POLICY";
    case sandbox2::Result::FAILED_STORE:
      return "FAILED_STORE";
    case sandbox2::Result::FAILED_FETCH:
      return "FAILED_FETCH";
    case sandbox2::Result::FAILED_GETEVENT:
      return "FAILED_GETEVENT";
    case sandbox2::Result::FAILED_MONITOR:
      return "FAILED_MONITOR";
    case sandbox2::Result::FAILED_KILL:
      return "FAILED_KILL";
    case sandbox2::Result::FAILED_INTERRUPT:
      return "FAILED_INTERRUPT";
    case sandbox2::Result::FAILED_CHILD:
      return "FAILED_CHILD";
    case sandbox2::Result::FAILED_INSPECT:
      return "FAILED_INSPECT";
    case sandbox2::Result::VIOLATION_SYSCALL:
      return "VIOLATION_SYSCALL";
    case sandbox2::Result::VIOLATION_ARCH:
      return "VIOLATION_ARCH";
    case sandbox2::Result::VIOLATION_NETWORK:
      return "VIOLATION_NETWORK";
  }
  return absl::StrCat("UNKNOWN: ", value);
}

}  // namespace sandbox2
