// Copyright 2016 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 "base/memory/shared_memory_helper.h"

#if defined(OS_CHROMEOS)
#include <sys/resource.h>
#include <sys/time.h>

#include "base/debug/alias.h"
#endif  // defined(OS_CHROMEOS)

#include "base/threading/thread_restrictions.h"

namespace base {

struct ScopedPathUnlinkerTraits {
  static const FilePath* InvalidValue() { return nullptr; }

  static void Free(const FilePath* path) {
    if (unlink(path->value().c_str()))
      PLOG(WARNING) << "unlink";
  }
};

// Unlinks the FilePath when the object is destroyed.
using ScopedPathUnlinker =
    ScopedGeneric<const FilePath*, ScopedPathUnlinkerTraits>;

#if !defined(OS_ANDROID)
bool CreateAnonymousSharedMemory(const SharedMemoryCreateOptions& options,
                                 ScopedFD* fd,
                                 ScopedFD* readonly_fd,
                                 FilePath* path) {
#if defined(OS_LINUX)
  // It doesn't make sense to have a open-existing private piece of shmem
  DCHECK(!options.open_existing_deprecated);
#endif  // defined(OS_LINUX)
  // Q: Why not use the shm_open() etc. APIs?
  // A: Because they're limited to 4mb on OS X.  FFFFFFFUUUUUUUUUUU
  FilePath directory;
  ScopedPathUnlinker path_unlinker;
  if (!GetShmemTempDir(options.executable, &directory))
    return false;

  fd->reset(base::CreateAndOpenFdForTemporaryFileInDir(directory, path));

  if (!fd->is_valid())
    return false;

  // Deleting the file prevents anyone else from mapping it in (making it
  // private), and prevents the need for cleanup (once the last fd is
  // closed, it is truly freed).
  path_unlinker.reset(path);

  if (options.share_read_only) {
    // Also open as readonly so that we can GetReadOnlyHandle.
    readonly_fd->reset(HANDLE_EINTR(open(path->value().c_str(), O_RDONLY)));
    if (!readonly_fd->is_valid()) {
      DPLOG(ERROR) << "open(\"" << path->value() << "\", O_RDONLY) failed";
      fd->reset();
      return false;
    }
  }
  return true;
}

bool PrepareMapFile(ScopedFD fd,
                    ScopedFD readonly_fd,
                    int* mapped_file,
                    int* readonly_mapped_file) {
  DCHECK_EQ(-1, *mapped_file);
  DCHECK_EQ(-1, *readonly_mapped_file);
  if (!fd.is_valid())
    return false;

  // This function theoretically can block on the disk, but realistically
  // the temporary files we create will just go into the buffer cache
  // and be deleted before they ever make it out to disk.
  base::ThreadRestrictions::ScopedAllowIO allow_io;

  if (readonly_fd.is_valid()) {
    struct stat st = {};
    if (fstat(fd.get(), &st))
      NOTREACHED();

    struct stat readonly_st = {};
    if (fstat(readonly_fd.get(), &readonly_st))
      NOTREACHED();
    if (st.st_dev != readonly_st.st_dev || st.st_ino != readonly_st.st_ino) {
      LOG(ERROR) << "writable and read-only inodes don't match; bailing";
      return false;
    }
  }

  *mapped_file = HANDLE_EINTR(dup(fd.get()));
  if (*mapped_file == -1) {
    NOTREACHED() << "Call to dup failed, errno=" << errno;

#if defined(OS_CHROMEOS)
    if (errno == EMFILE) {
      // We're out of file descriptors and are probably about to crash somewhere
      // else in Chrome anyway. Let's collect what FD information we can and
      // crash.
      // Added for debugging crbug.com/733718
      int original_fd_limit = 16384;
      struct rlimit rlim;
      if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
        original_fd_limit = rlim.rlim_cur;
        if (rlim.rlim_max > rlim.rlim_cur) {
          // Increase fd limit so breakpad has a chance to write a minidump.
          rlim.rlim_cur = rlim.rlim_max;
          if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
            PLOG(ERROR) << "setrlimit() failed";
          }
        }
      } else {
        PLOG(ERROR) << "getrlimit() failed";
      }

      const char kFileDataMarker[] = "FDATA";
      char buf[PATH_MAX];
      char fd_path[PATH_MAX];
      char crash_buffer[32 * 1024] = {0};
      char* crash_ptr = crash_buffer;
      base::debug::Alias(crash_buffer);

      // Put a marker at the start of our data so we can confirm where it
      // begins.
      crash_ptr = strncpy(crash_ptr, kFileDataMarker, strlen(kFileDataMarker));
      for (int i = original_fd_limit; i >= 0; --i) {
        memset(buf, 0, arraysize(buf));
        memset(fd_path, 0, arraysize(fd_path));
        snprintf(fd_path, arraysize(fd_path) - 1, "/proc/self/fd/%d", i);
        ssize_t count = readlink(fd_path, buf, arraysize(buf) - 1);
        if (count < 0) {
          PLOG(ERROR) << "readlink failed for: " << fd_path;
          continue;
        }

        if (crash_ptr + count + 1 < crash_buffer + arraysize(crash_buffer)) {
          crash_ptr = strncpy(crash_ptr, buf, count + 1);
        }
        LOG(ERROR) << i << ": " << buf;
      }
      LOG(FATAL) << "Logged for file descriptor exhaustion, crashing now";
    }
#endif  // defined(OS_CHROMEOS)
  }
  *readonly_mapped_file = readonly_fd.release();

  return true;
}
#endif  // !defined(OS_ANDROID)

}  // namespace base
