// Copyright (c) 2014 Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#include <string>

#include "breakpad_googletest_includes.h"
#include "client/linux/handler/exception_handler.h"
#include "client/linux/microdump_writer/microdump_writer.h"
#include "common/linux/eintr_wrapper.h"
#include "common/linux/ignore_ret.h"
#include "common/scoped_ptr.h"
#include "common/tests/auto_tempdir.h"
#include "common/using_std_string.h"

using namespace google_breakpad;

namespace {

typedef testing::Test MicrodumpWriterTest;

TEST(MicrodumpWriterTest, Setup) {
  int fds[2];
  ASSERT_NE(-1, pipe(fds));

  AutoTempDir temp_dir;
  string stderr_file = temp_dir.path() + "/stderr.log";
  int err_fd = open(stderr_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
  ASSERT_NE(-1, err_fd);

  const pid_t child = fork();
  if (child == 0) {
    close(fds[1]);
    char b;
    IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
    close(fds[0]);
    syscall(__NR_exit);
  }
  close(fds[0]);

  ExceptionHandler::CrashContext context;
  memset(&context, 0, sizeof(context));

  // Set a non-zero tid to avoid tripping asserts.
  context.tid = child;

  // Push some extra mapping to check the MappingList logic.
  const uint32_t memory_size = sysconf(_SC_PAGESIZE);
  const char* kMemoryName = "libfoo.so";
  const uint8_t kModuleGUID[sizeof(MDGUID)] = {
     0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
     0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
  };

  MappingInfo info;
  info.start_addr = memory_size;
  info.size = memory_size;
  info.offset = 42;
  strcpy(info.name, kMemoryName);

  MappingList mappings;
  MappingEntry mapping;
  mapping.first = info;
  memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
  mappings.push_back(mapping);

  // Redirect temporarily stderr to the stderr.log file.
  int save_err = dup(STDERR_FILENO);
  ASSERT_NE(-1, save_err);
  ASSERT_NE(-1, dup2(err_fd, STDERR_FILENO));

  ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings));

  // Revert stderr back to the console.
  dup2(save_err, STDERR_FILENO);
  close(save_err);

  // Read back the stderr file and check for the microdump marker.
  fsync(err_fd);
  lseek(err_fd, 0, SEEK_SET);
  const size_t kBufSize = 64 * 1024;
  scoped_array<char> buf(new char[kBufSize]);
  ASSERT_GT(read(err_fd, buf.get(), kBufSize), 0);

  ASSERT_NE(static_cast<char*>(0), strstr(
      buf.get(), "-----BEGIN BREAKPAD MICRODUMP-----"));
  ASSERT_NE(static_cast<char*>(0), strstr(
      buf.get(), "-----END BREAKPAD MICRODUMP-----"));

#ifdef __LP64__
  ASSERT_NE(static_cast<char*>(0), strstr(
      buf.get(), "M 0000000000001000 000000000000002A 0000000000001000 "
      "33221100554477668899AABBCCDDEEFF0 libfoo.so"));
#else
  ASSERT_NE(static_cast<char*>(0), strstr(
      buf.get(), "M 00001000 0000002A 00001000 "
      "33221100554477668899AABBCCDDEEFF0 libfoo.so"));
#endif

  close(err_fd);
  close(fds[1]);
}

}  // namespace