// Copyright 2014 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 "minidump/minidump_file_writer.h"

#include <utility>

#include "base/check_op.h"
#include "base/logging.h"
#include "build/build_config.h"
#include "minidump/minidump_crashpad_info_writer.h"
#include "minidump/minidump_exception_writer.h"
#include "minidump/minidump_handle_writer.h"
#include "minidump/minidump_memory_info_writer.h"
#include "minidump/minidump_memory_writer.h"
#include "minidump/minidump_misc_info_writer.h"
#include "minidump/minidump_module_writer.h"
#ifdef CLIENT_STACKTRACES_ENABLED
#include "minidump/minidump_stacktrace_writer.h"
#endif
#include "minidump/minidump_system_info_writer.h"
#include "minidump/minidump_thread_id_map.h"
#include "minidump/minidump_thread_name_list_writer.h"
#include "minidump/minidump_thread_writer.h"
#include "minidump/minidump_unloaded_module_writer.h"
#include "minidump/minidump_user_extension_stream_data_source.h"
#include "minidump/minidump_user_stream_writer.h"
#include "minidump/minidump_writer_util.h"
#include "snapshot/exception_snapshot.h"
#include "snapshot/module_snapshot.h"
#include "snapshot/process_snapshot.h"
#include "snapshot/thread_snapshot.h"
#include "util/file/file_writer.h"
#include "util/numeric/safe_assignment.h"

namespace crashpad {

MinidumpFileWriter::MinidumpFileWriter()
    : MinidumpWritable(), header_(), streams_(), stream_types_() {
  // Don’t set the signature field right away. Leave it set to 0, so that a
  // partially-written minidump file isn’t confused for a complete and valid
  // one. The header will be rewritten in WriteToFile().
  header_.Signature = 0;

  header_.Version = MINIDUMP_VERSION;
  header_.CheckSum = 0;
  header_.Flags = MiniDumpNormal;
}

MinidumpFileWriter::~MinidumpFileWriter() {}

void MinidumpFileWriter::InitializeFromSnapshot(
    const ProcessSnapshot* process_snapshot) {
  DCHECK_EQ(state(), kStateMutable);
  DCHECK_EQ(header_.Signature, 0u);
  DCHECK_EQ(header_.TimeDateStamp, 0u);
  DCHECK_EQ(static_cast<MINIDUMP_TYPE>(header_.Flags), MiniDumpNormal);
  DCHECK(streams_.empty());

  // This time is truncated to an integer number of seconds, not rounded, for
  // compatibility with the truncation of process_snapshot->ProcessStartTime()
  // done by MinidumpMiscInfoWriter::InitializeFromSnapshot(). Handling both
  // timestamps in the same way allows the highest-fidelity computation of
  // process uptime as the difference between the two values.
  timeval snapshot_time;
  process_snapshot->SnapshotTime(&snapshot_time);
  SetTimestamp(snapshot_time.tv_sec);

  const SystemSnapshot* system_snapshot = process_snapshot->System();
  auto system_info = std::make_unique<MinidumpSystemInfoWriter>();
  system_info->InitializeFromSnapshot(system_snapshot);
  bool add_stream_result = AddStream(std::move(system_info));
  DCHECK(add_stream_result);

  auto misc_info = std::make_unique<MinidumpMiscInfoWriter>();
  misc_info->InitializeFromSnapshot(process_snapshot);
  if (misc_info->HasXStateData())
    header_.Flags = header_.Flags | MiniDumpWithAvxXStateContext;

  add_stream_result = AddStream(std::move(misc_info));
  DCHECK(add_stream_result);

  auto memory_list = std::make_unique<MinidumpMemoryListWriter>();
  auto thread_list = std::make_unique<MinidumpThreadListWriter>();
  thread_list->SetMemoryListWriter(memory_list.get());
  MinidumpThreadIDMap thread_id_map;
  thread_list->InitializeFromSnapshot(process_snapshot->Threads(),
                                      &thread_id_map);
  add_stream_result = AddStream(std::move(thread_list));
  DCHECK(add_stream_result);

  bool has_thread_name = false;
  for (const ThreadSnapshot* thread_snapshot : process_snapshot->Threads()) {
    if (!thread_snapshot->ThreadName().empty()) {
      has_thread_name = true;
      break;
    }
  }
  if (has_thread_name) {
    auto thread_name_list = std::make_unique<MinidumpThreadNameListWriter>();
    thread_name_list->InitializeFromSnapshot(process_snapshot->Threads(),
                                             thread_id_map);
    add_stream_result = AddStream(std::move(thread_name_list));
    DCHECK(add_stream_result);
  }

  const ExceptionSnapshot* exception_snapshot = process_snapshot->Exception();
  if (exception_snapshot) {
    auto exception = std::make_unique<MinidumpExceptionWriter>();
#if BUILDFLAG(IS_IOS)
    // It's expected that iOS intermediate dumps can be written with missing
    // information, but it's better to try and report as much as possible
    // rather than drop the incomplete minidump.
    constexpr bool allow_missing_thread_id_from_map = true;
#else
    constexpr bool allow_missing_thread_id_from_map = false;
#endif
    exception->InitializeFromSnapshot(
        exception_snapshot, thread_id_map, allow_missing_thread_id_from_map);
    add_stream_result = AddStream(std::move(exception));
    DCHECK(add_stream_result);
  }

  auto module_list = std::make_unique<MinidumpModuleListWriter>();
  module_list->InitializeFromSnapshot(process_snapshot->Modules());
  add_stream_result = AddStream(std::move(module_list));
  DCHECK(add_stream_result);

#ifdef CLIENT_STACKTRACES_ENABLED
  auto stacktrace_list = std::make_unique<MinidumpStacktraceListWriter>();
  stacktrace_list->InitializeFromSnapshot(
      process_snapshot->Threads(), thread_id_map, exception_snapshot);
  add_stream_result = AddStream(std::move(stacktrace_list));
  DCHECK(add_stream_result);
#endif

  auto unloaded_modules = process_snapshot->UnloadedModules();
  if (!unloaded_modules.empty()) {
    auto unloaded_module_list =
        std::make_unique<MinidumpUnloadedModuleListWriter>();
    unloaded_module_list->InitializeFromSnapshot(unloaded_modules);
    add_stream_result = AddStream(std::move(unloaded_module_list));
    DCHECK(add_stream_result);
  }

  auto crashpad_info = std::make_unique<MinidumpCrashpadInfoWriter>();
  crashpad_info->InitializeFromSnapshot(process_snapshot);

  // Since the MinidumpCrashpadInfo stream is an extension, it’s safe to not add
  // it to the minidump file if it wouldn’t carry any useful information.
  if (crashpad_info->IsUseful()) {
    add_stream_result = AddStream(std::move(crashpad_info));
    DCHECK(add_stream_result);
  }

  std::vector<const MemoryMapRegionSnapshot*> memory_map_snapshot =
      process_snapshot->MemoryMap();
  if (!memory_map_snapshot.empty()) {
    auto memory_info_list = std::make_unique<MinidumpMemoryInfoListWriter>();
    memory_info_list->InitializeFromSnapshot(memory_map_snapshot);
    add_stream_result = AddStream(std::move(memory_info_list));
    DCHECK(add_stream_result);
  }

  std::vector<HandleSnapshot> handles_snapshot = process_snapshot->Handles();
  if (!handles_snapshot.empty()) {
    auto handle_data_writer = std::make_unique<MinidumpHandleDataWriter>();
    handle_data_writer->InitializeFromSnapshot(handles_snapshot);
    add_stream_result = AddStream(std::move(handle_data_writer));
    DCHECK(add_stream_result);
  }

  memory_list->AddFromSnapshot(process_snapshot->ExtraMemory());
  if (exception_snapshot) {
    memory_list->AddFromSnapshot(exception_snapshot->ExtraMemory());
  }

  // These user streams must be added last. Otherwise, a user stream with the
  // same type as a well-known stream could preempt the well-known stream. As it
  // stands now, earlier-discovered user streams can still preempt
  // later-discovered ones. The well-known memory list stream is added after
  // these user streams, but only with a check here to avoid adding a user
  // stream that would preempt the memory list stream.
  for (const auto& module : process_snapshot->Modules()) {
    for (const UserMinidumpStream* stream : module->CustomMinidumpStreams()) {
      if (stream->stream_type() == kMinidumpStreamTypeMemoryList) {
        LOG(WARNING) << "discarding duplicate stream of type "
                     << stream->stream_type();
        continue;
      }
      auto user_stream = std::make_unique<MinidumpUserStreamWriter>();
      user_stream->InitializeFromSnapshot(stream);
      AddStream(std::move(user_stream));
    }
  }

  // The memory list stream should be added last. This keeps the “extra memory”
  // at the end so that if the minidump file is truncated, other, more critical
  // data is more likely to be preserved. Note that non-“extra” memory regions
  // will not have to ride at the end of the file. Thread stack memory, for
  // example, exists as a children of threads, and appears alongside them in the
  // file, despite also being mentioned by the memory list stream.
  add_stream_result = AddStream(std::move(memory_list));
  DCHECK(add_stream_result);
}

void MinidumpFileWriter::SetTimestamp(time_t timestamp) {
  DCHECK_EQ(state(), kStateMutable);

  internal::MinidumpWriterUtil::AssignTimeT(&header_.TimeDateStamp, timestamp);
}

bool MinidumpFileWriter::AddStream(
    std::unique_ptr<internal::MinidumpStreamWriter> stream) {
  DCHECK_EQ(state(), kStateMutable);

  MinidumpStreamType stream_type = stream->StreamType();

  auto rv = stream_types_.insert(stream_type);
  if (!rv.second) {
    LOG(WARNING) << "discarding duplicate stream of type " << stream_type;
    return false;
  }

  streams_.push_back(std::move(stream));

  DCHECK_EQ(streams_.size(), stream_types_.size());
  return true;
}

bool MinidumpFileWriter::AddUserExtensionStream(
    std::unique_ptr<MinidumpUserExtensionStreamDataSource>
        user_extension_stream_data) {
  DCHECK_EQ(state(), kStateMutable);

  auto user_stream = std::make_unique<MinidumpUserStreamWriter>();
  user_stream->InitializeFromUserExtensionStream(
      std::move(user_extension_stream_data));

  return AddStream(std::move(user_stream));
}

bool MinidumpFileWriter::WriteEverything(FileWriterInterface* file_writer) {
  return WriteMinidump(file_writer, true);
}

bool MinidumpFileWriter::WriteMinidump(FileWriterInterface* file_writer,
                                       bool allow_seek) {
  DCHECK_EQ(state(), kStateMutable);

  FileOffset start_offset = -1;
  if (allow_seek) {
    start_offset = file_writer->Seek(0, SEEK_CUR);
    if (start_offset < 0) {
      return false;
    }
  } else {
    header_.Signature = MINIDUMP_SIGNATURE;
  }

  if (!MinidumpWritable::WriteEverything(file_writer)) {
    return false;
  }

  if (!allow_seek)
    return true;

  FileOffset end_offset = file_writer->Seek(0, SEEK_CUR);
  if (end_offset < 0) {
    return false;
  }

  // Now that the entire minidump file has been completely written, go back to
  // the beginning and rewrite the header with the correct signature to identify
  // it as a valid minidump file.
  header_.Signature = MINIDUMP_SIGNATURE;

  if (file_writer->Seek(start_offset, SEEK_SET) < 0) {
    return false;
  }

  if (!file_writer->Write(&header_, sizeof(header_))) {
    return false;
  }

  // Seek back to the end of the file, in case some non-minidump content will be
  // written to the file after the minidump content.
  return file_writer->Seek(end_offset, SEEK_SET) >= 0;
}

bool MinidumpFileWriter::Freeze() {
  DCHECK_EQ(state(), kStateMutable);

  if (!MinidumpWritable::Freeze()) {
    return false;
  }

  size_t stream_count = streams_.size();
  CHECK_EQ(stream_count, stream_types_.size());

  if (!AssignIfInRange(&header_.NumberOfStreams, stream_count)) {
    LOG(ERROR) << "stream_count " << stream_count << " out of range";
    return false;
  }

  return true;
}

size_t MinidumpFileWriter::SizeOfObject() {
  DCHECK_GE(state(), kStateFrozen);
  DCHECK_EQ(streams_.size(), stream_types_.size());

  return sizeof(header_) + streams_.size() * sizeof(MINIDUMP_DIRECTORY);
}

std::vector<internal::MinidumpWritable*> MinidumpFileWriter::Children() {
  DCHECK_GE(state(), kStateFrozen);
  DCHECK_EQ(streams_.size(), stream_types_.size());

  std::vector<MinidumpWritable*> children;
  for (const auto& stream : streams_) {
    children.push_back(stream.get());
  }

  return children;
}

bool MinidumpFileWriter::WillWriteAtOffsetImpl(FileOffset offset) {
  DCHECK_EQ(state(), kStateFrozen);
  DCHECK_EQ(offset, 0);
  DCHECK_EQ(streams_.size(), stream_types_.size());

  auto directory_offset = streams_.empty() ? 0 : offset + sizeof(header_);
  if (!AssignIfInRange(&header_.StreamDirectoryRva, directory_offset)) {
    LOG(ERROR) << "offset " << directory_offset << " out of range";
    return false;
  }

  return MinidumpWritable::WillWriteAtOffsetImpl(offset);
}

bool MinidumpFileWriter::WriteObject(FileWriterInterface* file_writer) {
  DCHECK_EQ(state(), kStateWritable);
  DCHECK_EQ(streams_.size(), stream_types_.size());

  WritableIoVec iov;
  iov.iov_base = &header_;
  iov.iov_len = sizeof(header_);
  std::vector<WritableIoVec> iovecs(1, iov);

  for (const auto& stream : streams_) {
    iov.iov_base = stream->DirectoryListEntry();
    iov.iov_len = sizeof(MINIDUMP_DIRECTORY);
    iovecs.push_back(iov);
  }

  return file_writer->WriteIoVec(&iovecs);
}

}  // namespace crashpad
