| 1 | //===-- lib/runtime/extensions.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 | // These C-coded entry points with Fortran-mangled names implement legacy |
| 10 | // extensions that will eventually be implemented in Fortran. |
| 11 | |
| 12 | #include "flang/Runtime/extensions.h" |
| 13 | #include "unit.h" |
| 14 | #include "flang-rt/runtime/descriptor.h" |
| 15 | #include "flang-rt/runtime/terminator.h" |
| 16 | #include "flang-rt/runtime/tools.h" |
| 17 | #include "flang/Runtime/command.h" |
| 18 | #include "flang/Runtime/entry-names.h" |
| 19 | #include "flang/Runtime/io-api.h" |
| 20 | #include "flang/Runtime/iostat-consts.h" |
| 21 | #include <chrono> |
| 22 | #include <cstdio> |
| 23 | #include <cstring> |
| 24 | #include <ctime> |
| 25 | #include <signal.h> |
| 26 | #include <stdlib.h> |
| 27 | #include <thread> |
| 28 | |
| 29 | #ifdef _WIN32 |
| 30 | #define WIN32_LEAN_AND_MEAN |
| 31 | #define NOMINMAX |
| 32 | #include <windows.h> |
| 33 | |
| 34 | #include <synchapi.h> |
| 35 | |
| 36 | inline void CtimeBuffer(char *buffer, size_t bufsize, const time_t cur_time, |
| 37 | Fortran::runtime::Terminator terminator) { |
| 38 | int error{ctime_s(buffer, bufsize, &cur_time)}; |
| 39 | RUNTIME_CHECK(terminator, error == 0); |
| 40 | } |
| 41 | #elif _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE || \ |
| 42 | defined(_POSIX_SOURCE) |
| 43 | inline void CtimeBuffer(char *buffer, size_t bufsize, const time_t cur_time, |
| 44 | Fortran::runtime::Terminator terminator) { |
| 45 | const char *res{ctime_r(timer: &cur_time, buf: buffer)}; |
| 46 | RUNTIME_CHECK(terminator, res != nullptr); |
| 47 | } |
| 48 | #else |
| 49 | inline void CtimeBuffer(char *buffer, size_t bufsize, const time_t cur_time, |
| 50 | Fortran::runtime::Terminator terminator) { |
| 51 | buffer[0] = '\0'; |
| 52 | terminator.Crash("fdate is not supported." ); |
| 53 | } |
| 54 | #endif |
| 55 | |
| 56 | #ifndef _WIN32 |
| 57 | // posix-compliant and has getlogin_r and F_OK |
| 58 | #include <unistd.h> |
| 59 | #else |
| 60 | #include <direct.h> |
| 61 | #endif |
| 62 | |
| 63 | extern "C" { |
| 64 | |
| 65 | namespace Fortran::runtime { |
| 66 | |
| 67 | gid_t RTNAME(GetGID)() { |
| 68 | #ifdef _WIN32 |
| 69 | // Group IDs don't exist on Windows, return 1 to avoid errors |
| 70 | return 1; |
| 71 | #else |
| 72 | return getgid(); |
| 73 | #endif |
| 74 | } |
| 75 | |
| 76 | uid_t RTNAME(GetUID)() { |
| 77 | #ifdef _WIN32 |
| 78 | // User IDs don't exist on Windows, return 1 to avoid errors |
| 79 | return 1; |
| 80 | #else |
| 81 | return getuid(); |
| 82 | #endif |
| 83 | } |
| 84 | |
| 85 | void GetUsernameEnvVar(const char *envName, char *arg, std::int64_t length) { |
| 86 | Descriptor name{*Descriptor::Create( |
| 87 | 1, std::strlen(envName) + 1, const_cast<char *>(envName), 0)}; |
| 88 | Descriptor value{*Descriptor::Create(1, length, arg, 0)}; |
| 89 | |
| 90 | RTNAME(GetEnvVariable) |
| 91 | (name, &value, nullptr, false, nullptr, __FILE__, __LINE__); |
| 92 | } |
| 93 | |
| 94 | namespace io { |
| 95 | // SUBROUTINE FLUSH(N) |
| 96 | // FLUSH N |
| 97 | // END |
| 98 | void FORTRAN_PROCEDURE_NAME(flush)(const int &unit) { |
| 99 | Cookie cookie{IONAME(BeginFlush)(unit, __FILE__, __LINE__)}; |
| 100 | IONAME(EndIoStatement)(cookie); |
| 101 | } |
| 102 | } // namespace io |
| 103 | |
| 104 | // CALL FDATE(DATE) |
| 105 | void FORTRAN_PROCEDURE_NAME(fdate)(char *arg, std::int64_t length) { |
| 106 | // Day Mon dd hh:mm:ss yyyy\n\0 is 26 characters, e.g. |
| 107 | // Tue May 26 21:51:03 2015\n\0 |
| 108 | char str[26]; |
| 109 | // Insufficient space, fill with spaces and return. |
| 110 | if (length < 24) { |
| 111 | std::memset(s: arg, c: ' ', n: length); |
| 112 | return; |
| 113 | } |
| 114 | |
| 115 | Terminator terminator{__FILE__, __LINE__}; |
| 116 | std::time_t current_time; |
| 117 | std::time(timer: ¤t_time); |
| 118 | CtimeBuffer(str, sizeof(str), current_time, terminator); |
| 119 | |
| 120 | // Pad space on the last two byte `\n\0`, start at index 24 included. |
| 121 | CopyAndPad(arg, str, length, 24); |
| 122 | } |
| 123 | |
| 124 | std::intptr_t RTNAME(Malloc)(std::size_t size) { |
| 125 | return reinterpret_cast<std::intptr_t>(std::malloc(size: size)); |
| 126 | } |
| 127 | |
| 128 | // RESULT = IARGC() |
| 129 | std::int32_t FORTRAN_PROCEDURE_NAME(iargc)() { return RTNAME(ArgumentCount)(); } |
| 130 | |
| 131 | // CALL GETARG(N, ARG) |
| 132 | void FORTRAN_PROCEDURE_NAME(getarg)( |
| 133 | std::int32_t &n, char *arg, std::int64_t length) { |
| 134 | Descriptor value{*Descriptor::Create(1, length, arg, 0)}; |
| 135 | (void)RTNAME(GetCommandArgument)( |
| 136 | n, &value, nullptr, nullptr, __FILE__, __LINE__); |
| 137 | } |
| 138 | |
| 139 | // CALL GETLOG(USRNAME) |
| 140 | void FORTRAN_PROCEDURE_NAME(getlog)(char *arg, std::int64_t length) { |
| 141 | #if _REENTRANT || _POSIX_C_SOURCE >= 199506L |
| 142 | if (length >= 1 && getlogin_r(arg, length) == 0) { |
| 143 | auto loginLen{std::strlen(arg)}; |
| 144 | std::memset( |
| 145 | arg + loginLen, ' ', static_cast<std::size_t>(length) - loginLen); |
| 146 | return; |
| 147 | } |
| 148 | #endif |
| 149 | #if _WIN32 |
| 150 | GetUsernameEnvVar("USERNAME" , arg, length); |
| 151 | #else |
| 152 | GetUsernameEnvVar("LOGNAME" , arg, length); |
| 153 | #endif |
| 154 | } |
| 155 | |
| 156 | void RTNAME(Free)(std::intptr_t ptr) { |
| 157 | std::free(reinterpret_cast<void *>(ptr)); |
| 158 | } |
| 159 | |
| 160 | std::int64_t RTNAME(Signal)(std::int64_t number, void (*handler)(int)) { |
| 161 | // using auto for portability: |
| 162 | // on Windows, this is a void * |
| 163 | // on POSIX, this has the same type as handler |
| 164 | auto result = signal(number, handler); |
| 165 | |
| 166 | // GNU defines the intrinsic as returning an integer, not a pointer. So we |
| 167 | // have to reinterpret_cast |
| 168 | return static_cast<int64_t>(reinterpret_cast<std::uintptr_t>(result)); |
| 169 | } |
| 170 | |
| 171 | // CALL SLEEP(SECONDS) |
| 172 | void RTNAME(Sleep)(std::int64_t seconds) { |
| 173 | // ensure that conversion to unsigned makes sense, |
| 174 | // sleep(0) is an immidiate return anyway |
| 175 | if (seconds < 1) { |
| 176 | return; |
| 177 | } |
| 178 | #if _WIN32 |
| 179 | Sleep(seconds * 1000); |
| 180 | #else |
| 181 | sleep(seconds); |
| 182 | #endif |
| 183 | } |
| 184 | |
| 185 | // TODO: not supported on Windows |
| 186 | #ifndef _WIN32 |
| 187 | std::int64_t FORTRAN_PROCEDURE_NAME(access)(const char *name, |
| 188 | std::int64_t nameLength, const char *mode, std::int64_t modeLength) { |
| 189 | std::int64_t ret{-1}; |
| 190 | if (nameLength <= 0 || modeLength <= 0 || !name || !mode) { |
| 191 | return ret; |
| 192 | } |
| 193 | |
| 194 | // ensure name is null terminated |
| 195 | char *newName{nullptr}; |
| 196 | if (name[nameLength - 1] != '\0') { |
| 197 | newName = static_cast<char *>(std::malloc(nameLength + 1)); |
| 198 | std::memcpy(newName, name, nameLength); |
| 199 | newName[nameLength] = '\0'; |
| 200 | name = newName; |
| 201 | } |
| 202 | |
| 203 | // calculate mode |
| 204 | bool read{false}; |
| 205 | bool write{false}; |
| 206 | bool execute{false}; |
| 207 | bool exists{false}; |
| 208 | int imode{0}; |
| 209 | |
| 210 | for (std::int64_t i = 0; i < modeLength; ++i) { |
| 211 | switch (mode[i]) { |
| 212 | case 'r': |
| 213 | read = true; |
| 214 | break; |
| 215 | case 'w': |
| 216 | write = true; |
| 217 | break; |
| 218 | case 'x': |
| 219 | execute = true; |
| 220 | break; |
| 221 | case ' ': |
| 222 | exists = true; |
| 223 | break; |
| 224 | default: |
| 225 | // invalid mode |
| 226 | goto cleanup; |
| 227 | } |
| 228 | } |
| 229 | if (!read && !write && !execute && !exists) { |
| 230 | // invalid mode |
| 231 | goto cleanup; |
| 232 | } |
| 233 | |
| 234 | if (!read && !write && !execute) { |
| 235 | imode = F_OK; |
| 236 | } else { |
| 237 | if (read) { |
| 238 | imode |= R_OK; |
| 239 | } |
| 240 | if (write) { |
| 241 | imode |= W_OK; |
| 242 | } |
| 243 | if (execute) { |
| 244 | imode |= X_OK; |
| 245 | } |
| 246 | } |
| 247 | ret = access(name, imode); |
| 248 | |
| 249 | cleanup: |
| 250 | if (newName) { |
| 251 | free(newName); |
| 252 | } |
| 253 | return ret; |
| 254 | } |
| 255 | #endif |
| 256 | |
| 257 | // CHDIR(DIR) |
| 258 | int RTNAME(Chdir)(const char *name) { |
| 259 | // chdir alias seems to be deprecated on Windows. |
| 260 | #ifndef _WIN32 |
| 261 | return chdir(name); |
| 262 | #else |
| 263 | return _chdir(name); |
| 264 | #endif |
| 265 | } |
| 266 | |
| 267 | int FORTRAN_PROCEDURE_NAME(hostnm)(char *hn, int length) { |
| 268 | std::int32_t status{0}; |
| 269 | |
| 270 | if (!hn || length < 0) { |
| 271 | return EINVAL; |
| 272 | } |
| 273 | |
| 274 | #ifdef _WIN32 |
| 275 | DWORD dwSize{static_cast<DWORD>(length)}; |
| 276 | |
| 277 | // Note: Winsock has gethostname(), but use Win32 API GetComputerNameEx(), |
| 278 | // in order to avoid adding dependency on Winsock. |
| 279 | if (!GetComputerNameExA(ComputerNameDnsHostname, hn, &dwSize)) { |
| 280 | status = GetLastError(); |
| 281 | } |
| 282 | #else |
| 283 | if (gethostname(hn, length) < 0) { |
| 284 | status = errno; |
| 285 | } |
| 286 | #endif |
| 287 | |
| 288 | if (status == 0) { |
| 289 | // Find zero terminator and fill the string from the |
| 290 | // zero terminator to the end with spaces |
| 291 | char *str_end{hn + length}; |
| 292 | char *str_zero{std::find(hn, str_end, '\0')}; |
| 293 | std::fill(first: str_zero, last: str_end, value: ' '); |
| 294 | } |
| 295 | |
| 296 | return status; |
| 297 | } |
| 298 | |
| 299 | int FORTRAN_PROCEDURE_NAME(ierrno)() { return errno; } |
| 300 | |
| 301 | void FORTRAN_PROCEDURE_NAME(qsort)(int *array, int *len, int *isize, |
| 302 | int (*compar)(const void *, const void *)) { |
| 303 | qsort(array, *len, *isize, compar); |
| 304 | } |
| 305 | |
| 306 | // PERROR(STRING) |
| 307 | void RTNAME(Perror)(const char *str) { perror(str); } |
| 308 | |
| 309 | // GNU extension function TIME() |
| 310 | std::int64_t RTNAME(time)() { return time(nullptr); } |
| 311 | |
| 312 | // Extension procedures related to I/O |
| 313 | |
| 314 | namespace io { |
| 315 | std::int32_t RTNAME(Fseek)(int unitNumber, std::int64_t zeroBasedPos, |
| 316 | int whence, const char *sourceFileName, int lineNumber) { |
| 317 | if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) { |
| 318 | Terminator terminator{sourceFileName, lineNumber}; |
| 319 | IoErrorHandler handler{terminator}; |
| 320 | if (unit->Fseek( |
| 321 | zeroBasedPos, static_cast<enum FseekWhence>(whence), handler)) { |
| 322 | return IostatOk; |
| 323 | } else { |
| 324 | return IostatCannotReposition; |
| 325 | } |
| 326 | } else { |
| 327 | return IostatBadUnitNumber; |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | std::int64_t RTNAME(Ftell)(int unitNumber) { |
| 332 | if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) { |
| 333 | return unit->InquirePos() - 1; // zero-based result |
| 334 | } else { |
| 335 | return -1; |
| 336 | } |
| 337 | } |
| 338 | } // namespace io |
| 339 | |
| 340 | } // namespace Fortran::runtime |
| 341 | } // extern "C" |
| 342 | |