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 | |