| 1 | //===-- lib/runtime/io-error.cpp --------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "flang-rt/runtime/io-error.h" |
| 10 | #include "config.h" |
| 11 | #include "flang-rt/runtime/tools.h" |
| 12 | #include "flang/Runtime/magic-numbers.h" |
| 13 | #include <cerrno> |
| 14 | #include <cstdarg> |
| 15 | #include <cstdio> |
| 16 | #include <cstring> |
| 17 | |
| 18 | namespace Fortran::runtime::io { |
| 19 | RT_OFFLOAD_API_GROUP_BEGIN |
| 20 | |
| 21 | void IoErrorHandler::SignalError(int iostatOrErrno, const char *msg, ...) { |
| 22 | // Note that IOMSG= alone without IOSTAT=/END=/EOR=/ERR= does not suffice |
| 23 | // for error recovery (see F'2018 subclause 12.11). |
| 24 | switch (iostatOrErrno) { |
| 25 | case IostatOk: |
| 26 | return; |
| 27 | case IostatEnd: |
| 28 | if ((flags_ & (hasIoStat | hasEnd)) || |
| 29 | ((flags_ & hasErr) && (flags_ & hasRec))) { |
| 30 | // EOF goes to ERR= when REC= is present |
| 31 | if (ioStat_ == IostatOk || ioStat_ < IostatEnd) { |
| 32 | ioStat_ = IostatEnd; |
| 33 | } |
| 34 | return; |
| 35 | } |
| 36 | break; |
| 37 | case IostatEor: |
| 38 | if (flags_ & (hasIoStat | hasEor)) { |
| 39 | if (ioStat_ == IostatOk || ioStat_ < IostatEor) { |
| 40 | ioStat_ = IostatEor; // least priority |
| 41 | } |
| 42 | return; |
| 43 | } |
| 44 | break; |
| 45 | default: |
| 46 | if (flags_ & (hasIoStat | hasErr)) { |
| 47 | if (ioStat_ <= 0) { |
| 48 | ioStat_ = iostatOrErrno; // priority over END=/EOR= |
| 49 | if (msg && (flags_ & hasIoMsg)) { |
| 50 | #if !defined(RT_DEVICE_COMPILATION) |
| 51 | char buffer[256]; |
| 52 | va_list ap; |
| 53 | va_start(ap, msg); |
| 54 | std::vsnprintf(buffer, sizeof buffer, msg, ap); |
| 55 | va_end(ap); |
| 56 | #else |
| 57 | const char *buffer = "not implemented yet: IOSTAT with varargs" ; |
| 58 | #endif |
| 59 | ioMsg_ = SaveDefaultCharacter( |
| 60 | buffer, Fortran::runtime::strlen(buffer) + 1, *this); |
| 61 | } |
| 62 | } |
| 63 | return; |
| 64 | } |
| 65 | break; |
| 66 | } |
| 67 | // I/O error not caught! |
| 68 | if (msg) { |
| 69 | #if !defined(RT_DEVICE_COMPILATION) |
| 70 | va_list ap; |
| 71 | va_start(ap, msg); |
| 72 | CrashArgs(msg, ap); |
| 73 | va_end(ap); |
| 74 | #else |
| 75 | Crash("not implemented yet: IOSTAT with varargs" ); |
| 76 | #endif |
| 77 | } else if (const char *errstr{IostatErrorString(iostatOrErrno)}) { |
| 78 | Crash(errstr); |
| 79 | } else { |
| 80 | #if !defined(RT_DEVICE_COMPILATION) |
| 81 | Crash("I/O error (errno=%d): %s" , iostatOrErrno, |
| 82 | std::strerror(iostatOrErrno)); |
| 83 | #else |
| 84 | Crash("I/O error (errno=%d)" , iostatOrErrno); |
| 85 | #endif |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | void IoErrorHandler::SignalError(int iostatOrErrno) { |
| 90 | SignalError(iostatOrErrno, nullptr); |
| 91 | } |
| 92 | |
| 93 | void IoErrorHandler::Forward( |
| 94 | int ioStatOrErrno, const char *msg, std::size_t length) { |
| 95 | if (ioStatOrErrno != IostatOk) { |
| 96 | if (msg) { |
| 97 | SignalError(ioStatOrErrno, "%.*s" , static_cast<int>(length), msg); |
| 98 | } else { |
| 99 | SignalError(ioStatOrErrno); |
| 100 | } |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | void IoErrorHandler::SignalEnd() { SignalError(IostatEnd); } |
| 105 | |
| 106 | void IoErrorHandler::SignalEor() { SignalError(IostatEor); } |
| 107 | |
| 108 | void IoErrorHandler::SignalPendingError() { |
| 109 | int error{pendingError_}; |
| 110 | pendingError_ = IostatOk; |
| 111 | SignalError(error); |
| 112 | } |
| 113 | |
| 114 | void IoErrorHandler::SignalErrno() { SignalError(errno); } |
| 115 | |
| 116 | bool IoErrorHandler::GetIoMsg(char *buffer, std::size_t bufferLength) { |
| 117 | const char *msg{ioMsg_.get()}; |
| 118 | if (!msg) { |
| 119 | msg = IostatErrorString(ioStat_ == IostatOk ? pendingError_ : ioStat_); |
| 120 | } |
| 121 | if (msg) { |
| 122 | ToFortranDefaultCharacter(buffer, bufferLength, msg); |
| 123 | return true; |
| 124 | } |
| 125 | |
| 126 | // Following code is taken from llvm/lib/Support/Errno.cpp |
| 127 | // in LLVM v9.0.1 with inadequate modification for Fortran, |
| 128 | // since rectified. |
| 129 | bool ok{false}; |
| 130 | #if defined(RT_DEVICE_COMPILATION) |
| 131 | // strerror_r is not available on device. |
| 132 | msg = "errno description is not available on device" ; |
| 133 | #elif HAVE_STRERROR_R |
| 134 | // strerror_r is thread-safe. |
| 135 | #if defined(__GLIBC__) && defined(_GNU_SOURCE) |
| 136 | // glibc defines its own incompatible version of strerror_r |
| 137 | // which may not use the buffer supplied. |
| 138 | msg = ::strerror_r(ioStat_, buffer, bufferLength); |
| 139 | #else |
| 140 | ok = ::strerror_r(ioStat_, buffer, bufferLength) == 0; |
| 141 | #endif |
| 142 | #elif HAVE_DECL_STRERROR_S // "Windows Secure API" |
| 143 | ok = ::strerror_s(buffer, bufferLength, ioStat_) == 0; |
| 144 | #else |
| 145 | // Copy the thread un-safe result of strerror into |
| 146 | // the buffer as fast as possible to minimize impact |
| 147 | // of collision of strerror in multiple threads. |
| 148 | msg = strerror(ioStat_); |
| 149 | #endif |
| 150 | if (msg) { |
| 151 | ToFortranDefaultCharacter(buffer, bufferLength, msg); |
| 152 | return true; |
| 153 | } else if (ok) { |
| 154 | std::size_t copied{Fortran::runtime::strlen(buffer)}; |
| 155 | if (copied < bufferLength) { |
| 156 | std::memset(buffer + copied, ' ', bufferLength - copied); |
| 157 | } |
| 158 | return true; |
| 159 | } else { |
| 160 | return false; |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | RT_OFFLOAD_API_GROUP_END |
| 165 | } // namespace Fortran::runtime::io |
| 166 | |