/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "SandboxLogging.h"

#ifdef ANDROID
#  include <android/log.h>
#endif
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

#include "base/posix/eintr_wrapper.h"

#ifdef __GLIBC_PREREQ
#  if __GLIBC_PREREQ(2, 32)
#    define HAVE_STRERRORNAME_NP 1
#  endif
#endif

namespace mozilla {

// Alters an iovec array to remove the first `toDrop` bytes.  This
// complexity is necessary because writev can return a short write
// (e.g., if stderr is a pipe and the buffer is almost full).
static void IOVecDrop(struct iovec* iov, int iovcnt, size_t toDrop) {
  while (toDrop > 0 && iovcnt > 0) {
    size_t toDropHere = std::min(toDrop, iov->iov_len);
    iov->iov_base = static_cast<char*>(iov->iov_base) + toDropHere;
    iov->iov_len -= toDropHere;
    toDrop -= toDropHere;
    ++iov;
    --iovcnt;
  }
}

void SandboxLogError(const char* message) {
#ifdef ANDROID
  // This uses writev internally and appears to be async signal safe.
  __android_log_write(ANDROID_LOG_ERROR, "Sandbox", message);
#endif
  static char logPrefixProcess[16];
  static const ssize_t printfSize =
      ::base::strings::SafeSPrintf(logPrefixProcess, "[%d] ", getpid());
  static const size_t pidSize =
      std::clamp(static_cast<size_t>(printfSize), static_cast<size_t>(0),
                 static_cast<size_t>(sizeof(logPrefixProcess) - 1));
  static const char logPrefix[] = "Sandbox: ", logSuffix[] = "\n";
  struct iovec iovs[4] = {
      {const_cast<char*>(logPrefixProcess), pidSize},
      {const_cast<char*>(logPrefix), sizeof(logPrefix) - 1},
      {const_cast<char*>(message), strlen(message)},
      {const_cast<char*>(logSuffix), sizeof(logSuffix) - 1},
  };
  while (iovs[2].iov_len > 0) {
    ssize_t written = HANDLE_EINTR(writev(STDERR_FILENO, iovs, 4));
    if (written <= 0) {
      break;
    }
    IOVecDrop(iovs, 4, static_cast<size_t>(written));
  }
}

ssize_t GetLibcErrorName(char* aBuf, size_t aSize, int aErr) {
  const char* errStr;

#ifdef HAVE_STRERRORNAME_NP
  // This is the function we'd like to have, but it's a glibc
  // extension and present only in newer versions.
  errStr = strerrorname_np(aErr);
#else
  // Otherwise, handle most of the basic / common errors with a big
  // switch statement.
  switch (aErr) {

#  define HANDLE_ERR(x) \
    case x:             \
      errStr = #x;      \
      break

    // errno-base.h
    HANDLE_ERR(EPERM);
    HANDLE_ERR(ENOENT);
    HANDLE_ERR(ESRCH);
    HANDLE_ERR(EINTR);
    HANDLE_ERR(EIO);
    HANDLE_ERR(ENXIO);
    HANDLE_ERR(E2BIG);
    HANDLE_ERR(ENOEXEC);
    HANDLE_ERR(EBADF);
    HANDLE_ERR(ECHILD);
    HANDLE_ERR(EAGAIN);
    HANDLE_ERR(ENOMEM);
    HANDLE_ERR(EACCES);
    HANDLE_ERR(EFAULT);
    HANDLE_ERR(ENOTBLK);
    HANDLE_ERR(EBUSY);
    HANDLE_ERR(EEXIST);
    HANDLE_ERR(EXDEV);
    HANDLE_ERR(ENODEV);
    HANDLE_ERR(ENOTDIR);
    HANDLE_ERR(EISDIR);
    HANDLE_ERR(EINVAL);
    HANDLE_ERR(ENFILE);
    HANDLE_ERR(EMFILE);
    HANDLE_ERR(ENOTTY);
    HANDLE_ERR(ETXTBSY);
    HANDLE_ERR(EFBIG);
    HANDLE_ERR(ENOSPC);
    HANDLE_ERR(ESPIPE);
    HANDLE_ERR(EROFS);
    HANDLE_ERR(EMLINK);
    HANDLE_ERR(EPIPE);
    HANDLE_ERR(EDOM);
    HANDLE_ERR(ERANGE);

    // selected other errors
    HANDLE_ERR(ENAMETOOLONG);
    HANDLE_ERR(ENOSYS);
    HANDLE_ERR(ENOTEMPTY);
    HANDLE_ERR(ELOOP);
    HANDLE_ERR(ENOTSOCK);
    HANDLE_ERR(EMSGSIZE);
    HANDLE_ERR(ECONNRESET);
    HANDLE_ERR(ECONNREFUSED);
    HANDLE_ERR(EHOSTUNREACH);
    HANDLE_ERR(ESTALE);

#  undef HANDLE_ERR

    default:
      errStr = nullptr;
  }
#endif  // no strerrorname_np

  if (errStr) {
    return base::strings::SafeSNPrintf(aBuf, aSize, "%s", errStr);
  }

  return base::strings::SafeSNPrintf(aBuf, aSize, "error %d", aErr);
}

}  // namespace mozilla
