forked from OSchip/llvm-project
327 lines
8.1 KiB
C++
327 lines
8.1 KiB
C++
|
//===-- runtime/file.cpp ----------------------------------------*- C++ -*-===//
|
||
|
//
|
||
|
// 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 "file.h"
|
||
|
#include "magic-numbers.h"
|
||
|
#include "memory.h"
|
||
|
#include "tools.h"
|
||
|
#include <cerrno>
|
||
|
#include <cstring>
|
||
|
#include <fcntl.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
namespace Fortran::runtime::io {
|
||
|
|
||
|
void OpenFile::Open(const char *path, std::size_t pathLength,
|
||
|
const char *status, std::size_t statusLength, const char *action,
|
||
|
std::size_t actionLength, IoErrorHandler &handler) {
|
||
|
CriticalSection criticalSection{lock_};
|
||
|
RUNTIME_CHECK(handler, fd_ < 0);
|
||
|
int flags{0};
|
||
|
static const char *actions[]{"READ", "WRITE", "READWRITE", nullptr};
|
||
|
switch (IdentifyValue(action, actionLength, actions)) {
|
||
|
case 0: flags = O_RDONLY; break;
|
||
|
case 1: flags = O_WRONLY; break;
|
||
|
case 2: flags = O_RDWR; break;
|
||
|
default:
|
||
|
handler.Crash(
|
||
|
"Invalid ACTION='%.*s'", action, static_cast<int>(actionLength));
|
||
|
}
|
||
|
if (!status) {
|
||
|
status = "UNKNOWN", statusLength = 7;
|
||
|
}
|
||
|
static const char *statuses[]{
|
||
|
"OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr};
|
||
|
switch (IdentifyValue(status, statusLength, statuses)) {
|
||
|
case 0: // STATUS='OLD'
|
||
|
if (!path && fd_ >= 0) {
|
||
|
// TODO: Update OpenFile in situ; can ACTION be changed?
|
||
|
return;
|
||
|
}
|
||
|
break;
|
||
|
case 1: // STATUS='NEW'
|
||
|
flags |= O_CREAT | O_EXCL;
|
||
|
break;
|
||
|
case 2: // STATUS='SCRATCH'
|
||
|
if (path_.get()) {
|
||
|
handler.Crash("FILE= must not appear with STATUS='SCRATCH'");
|
||
|
path_.reset();
|
||
|
}
|
||
|
{
|
||
|
char path[]{"/tmp/Fortran-Scratch-XXXXXX"};
|
||
|
fd_ = ::mkstemp(path);
|
||
|
if (fd_ < 0) {
|
||
|
handler.SignalErrno();
|
||
|
}
|
||
|
::unlink(path);
|
||
|
}
|
||
|
return;
|
||
|
case 3: // STATUS='REPLACE'
|
||
|
flags |= O_CREAT | O_TRUNC;
|
||
|
break;
|
||
|
case 4: // STATUS='UNKNOWN'
|
||
|
if (fd_ >= 0) {
|
||
|
return;
|
||
|
}
|
||
|
flags |= O_CREAT;
|
||
|
break;
|
||
|
default:
|
||
|
handler.Crash(
|
||
|
"Invalid STATUS='%.*s'", status, static_cast<int>(statusLength));
|
||
|
}
|
||
|
// If we reach this point, we're opening a new file
|
||
|
if (fd_ >= 0) {
|
||
|
if (::close(fd_) != 0) {
|
||
|
handler.SignalErrno();
|
||
|
}
|
||
|
}
|
||
|
path_ = SaveDefaultCharacter(path, pathLength, handler);
|
||
|
if (!path_.get()) {
|
||
|
handler.Crash(
|
||
|
"FILE= is required unless STATUS='OLD' and unit is connected");
|
||
|
}
|
||
|
fd_ = ::open(path_.get(), flags, 0600);
|
||
|
if (fd_ < 0) {
|
||
|
handler.SignalErrno();
|
||
|
}
|
||
|
pending_.reset();
|
||
|
knownSize_.reset();
|
||
|
}
|
||
|
|
||
|
void OpenFile::Close(
|
||
|
const char *status, std::size_t statusLength, IoErrorHandler &handler) {
|
||
|
CriticalSection criticalSection{lock_};
|
||
|
CheckOpen(handler);
|
||
|
pending_.reset();
|
||
|
knownSize_.reset();
|
||
|
static const char *statuses[]{"KEEP", "DELETE", nullptr};
|
||
|
switch (IdentifyValue(status, statusLength, statuses)) {
|
||
|
case 0: break;
|
||
|
case 1:
|
||
|
if (path_.get()) {
|
||
|
::unlink(path_.get());
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
if (status) {
|
||
|
handler.Crash(
|
||
|
"Invalid STATUS='%.*s'", status, static_cast<int>(statusLength));
|
||
|
}
|
||
|
}
|
||
|
path_.reset();
|
||
|
if (fd_ >= 0) {
|
||
|
if (::close(fd_) != 0) {
|
||
|
handler.SignalErrno();
|
||
|
}
|
||
|
fd_ = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::size_t OpenFile::Read(Offset at, char *buffer, std::size_t minBytes,
|
||
|
std::size_t maxBytes, IoErrorHandler &handler) {
|
||
|
if (maxBytes == 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
CriticalSection criticalSection{lock_};
|
||
|
CheckOpen(handler);
|
||
|
if (!Seek(at, handler)) {
|
||
|
return 0;
|
||
|
}
|
||
|
if (maxBytes < minBytes) {
|
||
|
minBytes = maxBytes;
|
||
|
}
|
||
|
std::size_t got{0};
|
||
|
while (got < minBytes) {
|
||
|
auto chunk{::read(fd_, buffer + got, maxBytes - got)};
|
||
|
if (chunk == 0) {
|
||
|
handler.SignalEnd();
|
||
|
break;
|
||
|
}
|
||
|
if (chunk < 0) {
|
||
|
auto err{errno};
|
||
|
if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
||
|
handler.SignalError(err);
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
position_ += chunk;
|
||
|
got += chunk;
|
||
|
}
|
||
|
}
|
||
|
return got;
|
||
|
}
|
||
|
|
||
|
std::size_t OpenFile::Write(
|
||
|
Offset at, const char *buffer, std::size_t bytes, IoErrorHandler &handler) {
|
||
|
if (bytes == 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
CriticalSection criticalSection{lock_};
|
||
|
CheckOpen(handler);
|
||
|
if (!Seek(at, handler)) {
|
||
|
return 0;
|
||
|
}
|
||
|
std::size_t put{0};
|
||
|
while (put < bytes) {
|
||
|
auto chunk{::write(fd_, buffer + put, bytes - put)};
|
||
|
if (chunk >= 0) {
|
||
|
position_ += chunk;
|
||
|
put += chunk;
|
||
|
} else {
|
||
|
auto err{errno};
|
||
|
if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
||
|
handler.SignalError(err);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (knownSize_ && position_ > *knownSize_) {
|
||
|
knownSize_ = position_;
|
||
|
}
|
||
|
return put;
|
||
|
}
|
||
|
|
||
|
void OpenFile::Truncate(Offset at, IoErrorHandler &handler) {
|
||
|
CriticalSection criticalSection{lock_};
|
||
|
CheckOpen(handler);
|
||
|
if (!knownSize_ || *knownSize_ != at) {
|
||
|
if (::ftruncate(fd_, at) != 0) {
|
||
|
handler.SignalErrno();
|
||
|
}
|
||
|
knownSize_ = at;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The operation is performed immediately; the results are saved
|
||
|
// to be claimed by a later WAIT statement.
|
||
|
// TODO: True asynchronicity
|
||
|
int OpenFile::ReadAsynchronously(
|
||
|
Offset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) {
|
||
|
CriticalSection criticalSection{lock_};
|
||
|
CheckOpen(handler);
|
||
|
int iostat{0};
|
||
|
for (std::size_t got{0}; got < bytes;) {
|
||
|
#if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
|
||
|
auto chunk{::pread(fd_, buffer + got, bytes - got, at)};
|
||
|
#else
|
||
|
auto chunk{RawSeek(at) ? ::read(fd_, buffer + got, bytes - got) : -1};
|
||
|
#endif
|
||
|
if (chunk == 0) {
|
||
|
iostat = FORTRAN_RUNTIME_IOSTAT_END;
|
||
|
break;
|
||
|
}
|
||
|
if (chunk < 0) {
|
||
|
auto err{errno};
|
||
|
if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
||
|
iostat = err;
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
at += chunk;
|
||
|
got += chunk;
|
||
|
}
|
||
|
}
|
||
|
return PendingResult(handler, iostat);
|
||
|
}
|
||
|
|
||
|
// TODO: True asynchronicity
|
||
|
int OpenFile::WriteAsynchronously(
|
||
|
Offset at, const char *buffer, std::size_t bytes, IoErrorHandler &handler) {
|
||
|
CriticalSection criticalSection{lock_};
|
||
|
CheckOpen(handler);
|
||
|
int iostat{0};
|
||
|
for (std::size_t put{0}; put < bytes;) {
|
||
|
#if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
|
||
|
auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)};
|
||
|
#else
|
||
|
auto chunk{RawSeek(at) ? ::write(fd_, buffer + put, bytes - put) : -1};
|
||
|
#endif
|
||
|
if (chunk >= 0) {
|
||
|
at += chunk;
|
||
|
put += chunk;
|
||
|
} else {
|
||
|
auto err{errno};
|
||
|
if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
||
|
iostat = err;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return PendingResult(handler, iostat);
|
||
|
}
|
||
|
|
||
|
void OpenFile::Wait(int id, IoErrorHandler &handler) {
|
||
|
std::optional<int> ioStat;
|
||
|
{
|
||
|
CriticalSection criticalSection{lock_};
|
||
|
Pending *prev{nullptr};
|
||
|
for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) {
|
||
|
if (p->id == id) {
|
||
|
ioStat = p->ioStat;
|
||
|
if (prev) {
|
||
|
prev->next.reset(p->next.release());
|
||
|
} else {
|
||
|
pending_.reset(p->next.release());
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (ioStat) {
|
||
|
handler.SignalError(*ioStat);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OpenFile::WaitAll(IoErrorHandler &handler) {
|
||
|
while (true) {
|
||
|
int ioStat;
|
||
|
{
|
||
|
CriticalSection criticalSection{lock_};
|
||
|
if (pending_) {
|
||
|
ioStat = pending_->ioStat;
|
||
|
pending_.reset(pending_->next.release());
|
||
|
} else {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
handler.SignalError(ioStat);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OpenFile::CheckOpen(Terminator &terminator) {
|
||
|
RUNTIME_CHECK(terminator, fd_ >= 0);
|
||
|
}
|
||
|
|
||
|
bool OpenFile::Seek(Offset at, IoErrorHandler &handler) {
|
||
|
if (at == position_) {
|
||
|
return true;
|
||
|
} else if (RawSeek(at)) {
|
||
|
position_ = at;
|
||
|
return true;
|
||
|
} else {
|
||
|
handler.SignalErrno();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool OpenFile::RawSeek(Offset at) {
|
||
|
#ifdef _LARGEFILE64_SOURCE
|
||
|
return ::lseek64(fd_, at, SEEK_SET) == 0;
|
||
|
#else
|
||
|
return ::lseek(fd_, at, SEEK_SET) == 0;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
int OpenFile::PendingResult(Terminator &terminator, int iostat) {
|
||
|
int id{nextId_++};
|
||
|
pending_.reset(&New<Pending>{}(terminator, id, iostat, std::move(pending_)));
|
||
|
return id;
|
||
|
}
|
||
|
}
|