/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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 <folly/lang/SafeAssert.h>

#include <algorithm>
#include <cerrno>
#include <cstdarg>

#include <folly/detail/FileUtilDetail.h>
#include <folly/lang/ToAscii.h>
#include <folly/portability/SysTypes.h>
#include <folly/portability/Windows.h>

#if defined(_WIN32)

#include <fileapi.h> // @manual

#else

// @lint-ignore CLANGTIDY
#include <unistd.h>

#endif

//  This header takes care to have minimal dependencies.

namespace folly {
namespace detail {

namespace {

//  script (centos):
//
//  for e in $(
//      cat /usr/include/asm*/errno*.h | awk '{print $2}' | grep -P '^E' | sort
//  ) ; do
//    echo "#if defined($e)"
//    echo "    FOLLY_DETAIL_ERROR($e),"
//    echo "#endif"
//  done

#define FOLLY_DETAIL_ERROR(name) \
  { name, #name }
constexpr std::pair<int, const char*> errors[] = {
#if defined(E2BIG)
    FOLLY_DETAIL_ERROR(E2BIG),
#endif
#if defined(EACCES)
    FOLLY_DETAIL_ERROR(EACCES),
#endif
#if defined(EADDRINUSE)
    FOLLY_DETAIL_ERROR(EADDRINUSE),
#endif
#if defined(EADDRNOTAVAIL)
    FOLLY_DETAIL_ERROR(EADDRNOTAVAIL),
#endif
#if defined(EADV)
    FOLLY_DETAIL_ERROR(EADV),
#endif
#if defined(EAFNOSUPPORT)
    FOLLY_DETAIL_ERROR(EAFNOSUPPORT),
#endif
#if defined(EAGAIN)
    FOLLY_DETAIL_ERROR(EAGAIN),
#endif
#if defined(EALREADY)
    FOLLY_DETAIL_ERROR(EALREADY),
#endif
#if defined(EBADE)
    FOLLY_DETAIL_ERROR(EBADE),
#endif
#if defined(EBADF)
    FOLLY_DETAIL_ERROR(EBADF),
#endif
#if defined(EBADFD)
    FOLLY_DETAIL_ERROR(EBADFD),
#endif
#if defined(EBADMSG)
    FOLLY_DETAIL_ERROR(EBADMSG),
#endif
#if defined(EBADR)
    FOLLY_DETAIL_ERROR(EBADR),
#endif
#if defined(EBADRQC)
    FOLLY_DETAIL_ERROR(EBADRQC),
#endif
#if defined(EBADSLT)
    FOLLY_DETAIL_ERROR(EBADSLT),
#endif
#if defined(EBFONT)
    FOLLY_DETAIL_ERROR(EBFONT),
#endif
#if defined(EBUSY)
    FOLLY_DETAIL_ERROR(EBUSY),
#endif
#if defined(ECANCELED)
    FOLLY_DETAIL_ERROR(ECANCELED),
#endif
#if defined(ECHILD)
    FOLLY_DETAIL_ERROR(ECHILD),
#endif
#if defined(ECHRNG)
    FOLLY_DETAIL_ERROR(ECHRNG),
#endif
#if defined(ECOMM)
    FOLLY_DETAIL_ERROR(ECOMM),
#endif
#if defined(ECONNABORTED)
    FOLLY_DETAIL_ERROR(ECONNABORTED),
#endif
#if defined(ECONNREFUSED)
    FOLLY_DETAIL_ERROR(ECONNREFUSED),
#endif
#if defined(ECONNRESET)
    FOLLY_DETAIL_ERROR(ECONNRESET),
#endif
#if defined(EDEADLK)
    FOLLY_DETAIL_ERROR(EDEADLK),
#endif
#if defined(EDEADLOCK)
    FOLLY_DETAIL_ERROR(EDEADLOCK),
#endif
#if defined(EDESTADDRREQ)
    FOLLY_DETAIL_ERROR(EDESTADDRREQ),
#endif
#if defined(EDOM)
    FOLLY_DETAIL_ERROR(EDOM),
#endif
#if defined(EDOTDOT)
    FOLLY_DETAIL_ERROR(EDOTDOT),
#endif
#if defined(EDQUOT)
    FOLLY_DETAIL_ERROR(EDQUOT),
#endif
#if defined(EEXIST)
    FOLLY_DETAIL_ERROR(EEXIST),
#endif
#if defined(EFAULT)
    FOLLY_DETAIL_ERROR(EFAULT),
#endif
#if defined(EFBIG)
    FOLLY_DETAIL_ERROR(EFBIG),
#endif
#if defined(EHOSTDOWN)
    FOLLY_DETAIL_ERROR(EHOSTDOWN),
#endif
#if defined(EHOSTUNREACH)
    FOLLY_DETAIL_ERROR(EHOSTUNREACH),
#endif
#if defined(EHWPOISON)
    FOLLY_DETAIL_ERROR(EHWPOISON),
#endif
#if defined(EIDRM)
    FOLLY_DETAIL_ERROR(EIDRM),
#endif
#if defined(EILSEQ)
    FOLLY_DETAIL_ERROR(EILSEQ),
#endif
#if defined(EINPROGRESS)
    FOLLY_DETAIL_ERROR(EINPROGRESS),
#endif
#if defined(EINTR)
    FOLLY_DETAIL_ERROR(EINTR),
#endif
#if defined(EINVAL)
    FOLLY_DETAIL_ERROR(EINVAL),
#endif
#if defined(EIO)
    FOLLY_DETAIL_ERROR(EIO),
#endif
#if defined(EISCONN)
    FOLLY_DETAIL_ERROR(EISCONN),
#endif
#if defined(EISDIR)
    FOLLY_DETAIL_ERROR(EISDIR),
#endif
#if defined(EISNAM)
    FOLLY_DETAIL_ERROR(EISNAM),
#endif
#if defined(EKEYEXPIRED)
    FOLLY_DETAIL_ERROR(EKEYEXPIRED),
#endif
#if defined(EKEYREJECTED)
    FOLLY_DETAIL_ERROR(EKEYREJECTED),
#endif
#if defined(EKEYREVOKED)
    FOLLY_DETAIL_ERROR(EKEYREVOKED),
#endif
#if defined(EL2HLT)
    FOLLY_DETAIL_ERROR(EL2HLT),
#endif
#if defined(EL2NSYNC)
    FOLLY_DETAIL_ERROR(EL2NSYNC),
#endif
#if defined(EL3HLT)
    FOLLY_DETAIL_ERROR(EL3HLT),
#endif
#if defined(EL3RST)
    FOLLY_DETAIL_ERROR(EL3RST),
#endif
#if defined(ELIBACC)
    FOLLY_DETAIL_ERROR(ELIBACC),
#endif
#if defined(ELIBBAD)
    FOLLY_DETAIL_ERROR(ELIBBAD),
#endif
#if defined(ELIBEXEC)
    FOLLY_DETAIL_ERROR(ELIBEXEC),
#endif
#if defined(ELIBMAX)
    FOLLY_DETAIL_ERROR(ELIBMAX),
#endif
#if defined(ELIBSCN)
    FOLLY_DETAIL_ERROR(ELIBSCN),
#endif
#if defined(ELNRNG)
    FOLLY_DETAIL_ERROR(ELNRNG),
#endif
#if defined(ELOOP)
    FOLLY_DETAIL_ERROR(ELOOP),
#endif
#if defined(EMEDIUMTYPE)
    FOLLY_DETAIL_ERROR(EMEDIUMTYPE),
#endif
#if defined(EMFILE)
    FOLLY_DETAIL_ERROR(EMFILE),
#endif
#if defined(EMLINK)
    FOLLY_DETAIL_ERROR(EMLINK),
#endif
#if defined(EMSGSIZE)
    FOLLY_DETAIL_ERROR(EMSGSIZE),
#endif
#if defined(EMULTIHOP)
    FOLLY_DETAIL_ERROR(EMULTIHOP),
#endif
#if defined(ENAMETOOLONG)
    FOLLY_DETAIL_ERROR(ENAMETOOLONG),
#endif
#if defined(ENAVAIL)
    FOLLY_DETAIL_ERROR(ENAVAIL),
#endif
#if defined(ENETDOWN)
    FOLLY_DETAIL_ERROR(ENETDOWN),
#endif
#if defined(ENETRESET)
    FOLLY_DETAIL_ERROR(ENETRESET),
#endif
#if defined(ENETUNREACH)
    FOLLY_DETAIL_ERROR(ENETUNREACH),
#endif
#if defined(ENFILE)
    FOLLY_DETAIL_ERROR(ENFILE),
#endif
#if defined(ENOANO)
    FOLLY_DETAIL_ERROR(ENOANO),
#endif
#if defined(ENOBUFS)
    FOLLY_DETAIL_ERROR(ENOBUFS),
#endif
#if defined(ENOCSI)
    FOLLY_DETAIL_ERROR(ENOCSI),
#endif
#if defined(ENODATA)
    FOLLY_DETAIL_ERROR(ENODATA),
#endif
#if defined(ENODEV)
    FOLLY_DETAIL_ERROR(ENODEV),
#endif
#if defined(ENOENT)
    FOLLY_DETAIL_ERROR(ENOENT),
#endif
#if defined(ENOEXEC)
    FOLLY_DETAIL_ERROR(ENOEXEC),
#endif
#if defined(ENOKEY)
    FOLLY_DETAIL_ERROR(ENOKEY),
#endif
#if defined(ENOLCK)
    FOLLY_DETAIL_ERROR(ENOLCK),
#endif
#if defined(ENOLINK)
    FOLLY_DETAIL_ERROR(ENOLINK),
#endif
#if defined(ENOMEDIUM)
    FOLLY_DETAIL_ERROR(ENOMEDIUM),
#endif
#if defined(ENOMEM)
    FOLLY_DETAIL_ERROR(ENOMEM),
#endif
#if defined(ENOMSG)
    FOLLY_DETAIL_ERROR(ENOMSG),
#endif
#if defined(ENONET)
    FOLLY_DETAIL_ERROR(ENONET),
#endif
#if defined(ENOPKG)
    FOLLY_DETAIL_ERROR(ENOPKG),
#endif
#if defined(ENOPROTOOPT)
    FOLLY_DETAIL_ERROR(ENOPROTOOPT),
#endif
#if defined(ENOSPC)
    FOLLY_DETAIL_ERROR(ENOSPC),
#endif
#if defined(ENOSR)
    FOLLY_DETAIL_ERROR(ENOSR),
#endif
#if defined(ENOSTR)
    FOLLY_DETAIL_ERROR(ENOSTR),
#endif
#if defined(ENOSYS)
    FOLLY_DETAIL_ERROR(ENOSYS),
#endif
#if defined(ENOTBLK)
    FOLLY_DETAIL_ERROR(ENOTBLK),
#endif
#if defined(ENOTCONN)
    FOLLY_DETAIL_ERROR(ENOTCONN),
#endif
#if defined(ENOTDIR)
    FOLLY_DETAIL_ERROR(ENOTDIR),
#endif
#if defined(ENOTEMPTY)
    FOLLY_DETAIL_ERROR(ENOTEMPTY),
#endif
#if defined(ENOTNAM)
    FOLLY_DETAIL_ERROR(ENOTNAM),
#endif
#if defined(ENOTRECOVERABLE)
    FOLLY_DETAIL_ERROR(ENOTRECOVERABLE),
#endif
#if defined(ENOTSOCK)
    FOLLY_DETAIL_ERROR(ENOTSOCK),
#endif
#if defined(ENOTTY)
    FOLLY_DETAIL_ERROR(ENOTTY),
#endif
#if defined(ENOTUNIQ)
    FOLLY_DETAIL_ERROR(ENOTUNIQ),
#endif
#if defined(ENXIO)
    FOLLY_DETAIL_ERROR(ENXIO),
#endif
#if defined(EOPNOTSUPP)
    FOLLY_DETAIL_ERROR(EOPNOTSUPP),
#endif
#if defined(EOVERFLOW)
    FOLLY_DETAIL_ERROR(EOVERFLOW),
#endif
#if defined(EOWNERDEAD)
    FOLLY_DETAIL_ERROR(EOWNERDEAD),
#endif
#if defined(EPERM)
    FOLLY_DETAIL_ERROR(EPERM),
#endif
#if defined(EPFNOSUPPORT)
    FOLLY_DETAIL_ERROR(EPFNOSUPPORT),
#endif
#if defined(EPIPE)
    FOLLY_DETAIL_ERROR(EPIPE),
#endif
#if defined(EPROTO)
    FOLLY_DETAIL_ERROR(EPROTO),
#endif
#if defined(EPROTONOSUPPORT)
    FOLLY_DETAIL_ERROR(EPROTONOSUPPORT),
#endif
#if defined(EPROTOTYPE)
    FOLLY_DETAIL_ERROR(EPROTOTYPE),
#endif
#if defined(ERANGE)
    FOLLY_DETAIL_ERROR(ERANGE),
#endif
#if defined(EREMCHG)
    FOLLY_DETAIL_ERROR(EREMCHG),
#endif
#if defined(EREMOTE)
    FOLLY_DETAIL_ERROR(EREMOTE),
#endif
#if defined(EREMOTEIO)
    FOLLY_DETAIL_ERROR(EREMOTEIO),
#endif
#if defined(ERESTART)
    FOLLY_DETAIL_ERROR(ERESTART),
#endif
#if defined(ERFKILL)
    FOLLY_DETAIL_ERROR(ERFKILL),
#endif
#if defined(EROFS)
    FOLLY_DETAIL_ERROR(EROFS),
#endif
#if defined(ESHUTDOWN)
    FOLLY_DETAIL_ERROR(ESHUTDOWN),
#endif
#if defined(ESOCKTNOSUPPORT)
    FOLLY_DETAIL_ERROR(ESOCKTNOSUPPORT),
#endif
#if defined(ESPIPE)
    FOLLY_DETAIL_ERROR(ESPIPE),
#endif
#if defined(ESRCH)
    FOLLY_DETAIL_ERROR(ESRCH),
#endif
#if defined(ESRMNT)
    FOLLY_DETAIL_ERROR(ESRMNT),
#endif
#if defined(ESTALE)
    FOLLY_DETAIL_ERROR(ESTALE),
#endif
#if defined(ESTRPIPE)
    FOLLY_DETAIL_ERROR(ESTRPIPE),
#endif
#if defined(ETIME)
    FOLLY_DETAIL_ERROR(ETIME),
#endif
#if defined(ETIMEDOUT)
    FOLLY_DETAIL_ERROR(ETIMEDOUT),
#endif
#if defined(ETOOMANYREFS)
    FOLLY_DETAIL_ERROR(ETOOMANYREFS),
#endif
#if defined(ETXTBSY)
    FOLLY_DETAIL_ERROR(ETXTBSY),
#endif
#if defined(EUCLEAN)
    FOLLY_DETAIL_ERROR(EUCLEAN),
#endif
#if defined(EUNATCH)
    FOLLY_DETAIL_ERROR(EUNATCH),
#endif
#if defined(EUSERS)
    FOLLY_DETAIL_ERROR(EUSERS),
#endif
#if defined(EWOULDBLOCK)
    FOLLY_DETAIL_ERROR(EWOULDBLOCK),
#endif
#if defined(EXDEV)
    FOLLY_DETAIL_ERROR(EXDEV),
#endif
#if defined(EXFULL)
    FOLLY_DETAIL_ERROR(EXFULL),
#endif
};
#undef FOLLY_DETAIL_ERROR

#if defined(_WIN32)

constexpr int stderr_fileno = 2;

ssize_t write(int fh, void const* buf, size_t count) {
  auto r = _write(fh, buf, static_cast<unsigned int>(count));
  if ((r > 0 && size_t(r) != count) || (r == -1 && errno == ENOSPC)) {
    // Writing to a pipe with a full buffer doesn't generate
    // any error type, unless it caused us to write exactly 0
    // bytes, so we have to see if we have a pipe first. We
    // don't touch the errno for anything else.
    HANDLE h = (HANDLE)_get_osfhandle(fh);
    if (GetFileType(h) == FILE_TYPE_PIPE) {
      DWORD state = 0;
      if (GetNamedPipeHandleState(
              h, &state, nullptr, nullptr, nullptr, nullptr, 0)) {
        if ((state & PIPE_NOWAIT) == PIPE_NOWAIT) {
          errno = EAGAIN;
          return -1;
        }
      }
    }
  }
  return r;
}

int fsync(int fh) {
  HANDLE h = (HANDLE)_get_osfhandle(fh);
  if (!FlushFileBuffers(h)) {
    return -1;
  }
  return 0;
}

#else

constexpr int stderr_fileno = STDERR_FILENO;

#endif

#if !defined(_WIN32) && !defined(_POSIX_FSYNC)

int fsync(int fh) {
  return 0;
}

#endif

void writeStderr(const char* s, size_t len) {
  fileutil_detail::wrapFull(write, stderr_fileno, const_cast<char*>(s), len);
}
void writeStderr(const char* s) {
  writeStderr(s, strlen(s));
}
void flushStderr() {
  fileutil_detail::wrapNoInt(fsync, stderr_fileno);
}

[[noreturn, FOLLY_ATTR_GNU_COLD]] void safe_assert_terminate_v(
    safe_assert_arg const* arg_, int const error, va_list msg) noexcept {
  auto const& arg = *arg_;
  char buf[to_ascii_size_max_decimal<uint64_t>];

  if (arg.expr) {
    writeStderr("\n\nAssertion failure: ");
    writeStderr(arg.expr);
  }
  if (*arg.msg_types != safe_assert_msg_type::term) {
    writeStderr("\nMessage: ");
    auto msg_types = arg.msg_types;
    bool stop = false;
    while (!stop) {
      switch (*msg_types++) {
        case safe_assert_msg_type::term:
          stop = true;
          break;
        case safe_assert_msg_type::cstr:
          writeStderr(va_arg(msg, char const*));
          break;
        case safe_assert_msg_type::ui64:
          writeStderr(buf, to_ascii_decimal(buf, va_arg(msg, uint64_t)));
          break;
      }
    }
  }
  writeStderr("\nFile: ");
  writeStderr(arg.file);
  writeStderr("\nLine: ");
  writeStderr(buf, to_ascii_decimal(buf, arg.line));
  writeStderr("\nFunction: ");
  writeStderr(arg.function);
  if (error) {
    // if errno is set, print the number and the symbolic constant
    // the symbolic constant is necessary since actual numbers may vary
    // for simplicity, do not attempt to mimic strerror printing descriptions
    writeStderr("\nError: ");
    writeStderr(buf, to_ascii_decimal(buf, error));
    writeStderr(" (");
    // the list is not required to be sorted; but the program is about to die
    auto const pred = [=](auto const e) { return e.first == error; };
    auto const it = std::find_if(std::begin(errors), std::end(errors), pred);
    writeStderr(it != std::end(errors) ? it->second : "<unknown>");
    writeStderr(")");
  }
  writeStderr("\n");
  flushStderr();
  abort();
}

} // namespace

template <>
void safe_assert_terminate<0>(safe_assert_arg const* arg, ...) noexcept {
  va_list msg;
  va_start(msg, arg);
  safe_assert_terminate_v(arg, 0, msg);
  va_end(msg);
}

template <>
void safe_assert_terminate<1>(safe_assert_arg const* arg, ...) noexcept {
  va_list msg;
  va_start(msg, arg);
  safe_assert_terminate_v(arg, errno, msg);
  va_end(msg);
}

} // namespace detail
} // namespace folly
