//===-- runtime/io-error.cpp ----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "io-error.h"
#include "config.h"
#include "tools.h"
#include "flang/Runtime/magic-numbers.h"
#include <cerrno>
#include <cstdarg>
#include <cstdio>
#include <cstring>
namespace Fortran::runtime::io {
RT_OFFLOAD_API_GROUP_BEGIN
void IoErrorHandler::SignalError(int iostatOrErrno, const char *msg, ...) {
// Note that IOMSG= alone without IOSTAT=/END=/EOR=/ERR= does not suffice
// for error recovery (see F'2018 subclause 12.11).
switch (iostatOrErrno) {
case IostatOk:
return;
case IostatEnd:
if (flags_ & (hasIoStat | hasEnd)) {
if (ioStat_ == IostatOk || ioStat_ < IostatEnd) {
ioStat_ = IostatEnd;
}
return;
}
break;
case IostatEor:
if (flags_ & (hasIoStat | hasEor)) {
if (ioStat_ == IostatOk || ioStat_ < IostatEor) {
ioStat_ = IostatEor; // least priority
}
return;
}
break;
default:
if (flags_ & (hasIoStat | hasErr)) {
if (ioStat_ <= 0) {
ioStat_ = iostatOrErrno; // priority over END=/EOR=
if (msg && (flags_ & hasIoMsg)) {
#if !defined(RT_DEVICE_COMPILATION)
char buffer[256];
va_list ap;
va_start(ap, msg);
std::vsnprintf(buffer, sizeof buffer, msg, ap);
va_end(ap);
#else
const char *buffer = "not implemented yet: IOSTAT with varargs";
#endif
ioMsg_ = SaveDefaultCharacter(
buffer, Fortran::runtime::strlen(buffer) + 1, *this);
}
}
return;
}
break;
}
// I/O error not caught!
if (msg) {
#if !defined(RT_DEVICE_COMPILATION)
va_list ap;
va_start(ap, msg);
CrashArgs(msg, ap);
va_end(ap);
#else
Crash("not implemented yet: IOSTAT with varargs");
#endif
} else if (const char *errstr{IostatErrorString(iostatOrErrno)}) {
Crash(errstr);
} else {
#if !defined(RT_DEVICE_COMPILATION)
Crash("I/O error (errno=%d): %s", iostatOrErrno,
std::strerror(iostatOrErrno));
#else
Crash("I/O error (errno=%d)", iostatOrErrno);
#endif
}
}
void IoErrorHandler::SignalError(int iostatOrErrno) {
SignalError(iostatOrErrno, nullptr);
}
void IoErrorHandler::Forward(
int ioStatOrErrno, const char *msg, std::size_t length) {
if (ioStatOrErrno != IostatOk) {
if (msg) {
SignalError(ioStatOrErrno, "%.*s", static_cast<int>(length), msg);
} else {
SignalError(ioStatOrErrno);
}
}
}
void IoErrorHandler::SignalEnd() { SignalError(IostatEnd); }
void IoErrorHandler::SignalEor() { SignalError(IostatEor); }
void IoErrorHandler::SignalPendingError() {
int error{pendingError_};
pendingError_ = IostatOk;
SignalError(error);
}
void IoErrorHandler::SignalErrno() { SignalError(errno); }
bool IoErrorHandler::GetIoMsg(char *buffer, std::size_t bufferLength) {
const char *msg{ioMsg_.get()};
if (!msg) {
msg = IostatErrorString(ioStat_ == IostatOk ? pendingError_ : ioStat_);
}
if (msg) {
ToFortranDefaultCharacter(buffer, bufferLength, msg);
return true;
}
// Following code is taken from llvm/lib/Support/Errno.cpp
// in LLVM v9.0.1 with inadequate modification for Fortran,
// since rectified.
bool ok{false};
#if defined(RT_DEVICE_COMPILATION)
// strerror_r is not available on device.
msg = "errno description is not available on device";
#elif HAVE_STRERROR_R
// strerror_r is thread-safe.
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
// glibc defines its own incompatible version of strerror_r
// which may not use the buffer supplied.
msg = ::strerror_r(ioStat_, buffer, bufferLength);
#else
ok = ::strerror_r(ioStat_, buffer, bufferLength) == 0;
#endif
#elif HAVE_DECL_STRERROR_S // "Windows Secure API"
ok = ::strerror_s(buffer, bufferLength, ioStat_) == 0;
#else
// Copy the thread un-safe result of strerror into
// the buffer as fast as possible to minimize impact
// of collision of strerror in multiple threads.
msg = strerror(ioStat_);
#endif
if (msg) {
ToFortranDefaultCharacter(buffer, bufferLength, msg);
return true;
} else if (ok) {
std::size_t copied{Fortran::runtime::strlen(buffer)};
if (copied < bufferLength) {
std::memset(buffer + copied, ' ', bufferLength - copied);
}
return true;
} else {
return false;
}
}
RT_OFFLOAD_API_GROUP_END
} // namespace Fortran::runtime::io