[flang] Correct INQUIRE(POSITION= & PAD=)

INQUIRE(POSITION=)'s results need to reflect the POSITION=
specifier used for the OPEN statement until the unit has been
repositioned.  Preserve the POSITION= from OPEN and used it
for INQUIRE(POSITION=) until is becomes obsolete.

INQUIRE(PAD=) is implemented here in the case of an unconnected unit
with Fortran 2018 semantics; i.e., "UNDEFINED", rather than Fortran 90's
"YES"/"NO" (see 4.3.6 para 2).  Apparent failures with F'90-only tests
will persist with INQUIRE(PAD=); these discrepancies don't seem to warrant
an option or environment variable.

To make the implementation of INQUIRE more closely match the language
in the standard, rename IsOpen() to IsConnected(), and use it explicitly
for the various INQUIRE specifiers.

Differential Revision: https://reviews.llvm.org/D114755
diff --git a/flang/runtime/connection.h b/flang/runtime/connection.h
index 0eb2038..6e7e0a6 100644
--- a/flang/runtime/connection.h
+++ b/flang/runtime/connection.h
@@ -25,7 +25,8 @@
 inline bool IsRecordFile(Access a) { return a != Access::Stream; }
 
 // These characteristics of a connection are immutable after being
-// established in an OPEN statement.
+// established in an OPEN statement, except for recordLength,
+// which is immutable only when isFixedRecordLength is true.
 struct ConnectionAttributes {
   Access access{Access::Sequential}; // ACCESS='SEQUENTIAL', 'DIRECT', 'STREAM'
   std::optional<bool> isUnformatted; // FORM='UNFORMATTED' if true
diff --git a/flang/runtime/file.cpp b/flang/runtime/file.cpp
index b8e349b..aaa2e30 100644
--- a/flang/runtime/file.cpp
+++ b/flang/runtime/file.cpp
@@ -149,6 +149,7 @@
     knownSize_ = 0;
     mayPosition_ = true;
   }
+  openPosition_ = position; // for INQUIRE(POSITION=)
 }
 
 void OpenFile::Predefine(int fd) {
@@ -208,7 +209,7 @@
         break;
       }
     } else {
-      position_ += chunk;
+      SetPosition(position_ + chunk);
       got += chunk;
     }
   }
@@ -228,7 +229,7 @@
   while (put < bytes) {
     auto chunk{::write(fd_, buffer + put, bytes - put)};
     if (chunk >= 0) {
-      position_ += chunk;
+      SetPosition(position_ + chunk);
       put += chunk;
     } else {
       auto err{errno};
@@ -351,6 +352,20 @@
   }
 }
 
+Position OpenFile::InquirePosition() const {
+  if (openPosition_) { // from OPEN statement
+    return *openPosition_;
+  } else { // unit has been repositioned since opening
+    if (position_ == knownSize_.value_or(position_ + 1)) {
+      return Position::Append;
+    } else if (position_ == 0 && mayPosition_) {
+      return Position::Rewind;
+    } else {
+      return Position::AsIs; // processor-dependent & no common behavior
+    }
+  }
+}
+
 void OpenFile::CheckOpen(const Terminator &terminator) {
   RUNTIME_CHECK(terminator, fd_ >= 0);
 }
@@ -359,7 +374,7 @@
   if (at == position_) {
     return true;
   } else if (RawSeek(at)) {
-    position_ = at;
+    SetPosition(at);
     return true;
   } else {
     handler.SignalErrno();
diff --git a/flang/runtime/file.h b/flang/runtime/file.h
index 3770b19..cde3c19 100644
--- a/flang/runtime/file.h
+++ b/flang/runtime/file.h
@@ -39,7 +39,7 @@
   bool isTerminal() const { return isTerminal_; }
   std::optional<FileOffset> knownSize() const { return knownSize_; }
 
-  bool IsOpen() const { return fd_ >= 0; }
+  bool IsConnected() const { return fd_ >= 0; }
   void Open(OpenStatus, std::optional<Action>, Position, IoErrorHandler &);
   void Predefine(int fd);
   void Close(CloseStatus, IoErrorHandler &);
@@ -66,6 +66,9 @@
   void Wait(int id, IoErrorHandler &);
   void WaitAll(IoErrorHandler &);
 
+  // INQUIRE(POSITION=)
+  Position InquirePosition() const;
+
 private:
   struct Pending {
     int id;
@@ -78,6 +81,10 @@
   bool RawSeek(FileOffset);
   bool RawSeekToEnd();
   int PendingResult(const Terminator &, int);
+  void SetPosition(FileOffset pos) {
+    position_ = pos;
+    openPosition_.reset();
+  }
 
   int fd_{-1};
   OwningPtr<char> path_;
@@ -86,6 +93,7 @@
   bool mayWrite_{false};
   bool mayPosition_{false};
   bool mayAsynchronous_{false};
+  std::optional<Position> openPosition_; // from Open(); reset after positioning
   FileOffset position_{0};
   std::optional<FileOffset> knownSize_;
   bool isTerminal_{false};
diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp
index 10560bf..d003390 100644
--- a/flang/runtime/io-stmt.cpp
+++ b/flang/runtime/io-stmt.cpp
@@ -824,26 +824,35 @@
   const char *str{nullptr};
   switch (inquiry) {
   case HashInquiryKeyword("ACCESS"):
-    switch (unit().access) {
-    case Access::Sequential:
-      str = "SEQUENTIAL";
-      break;
-    case Access::Direct:
-      str = "DIRECT";
-      break;
-    case Access::Stream:
-      str = "STREAM";
-      break;
+    if (!unit().IsConnected()) {
+      str = "UNDEFINED";
+    } else {
+      switch (unit().access) {
+      case Access::Sequential:
+        str = "SEQUENTIAL";
+        break;
+      case Access::Direct:
+        str = "DIRECT";
+        break;
+      case Access::Stream:
+        str = "STREAM";
+        break;
+      }
     }
     break;
   case HashInquiryKeyword("ACTION"):
-    str = unit().mayWrite() ? unit().mayRead() ? "READWRITE" : "WRITE" : "READ";
+    str = !unit().IsConnected() ? "UNDEFINED"
+        : unit().mayWrite()     ? unit().mayRead() ? "READWRITE" : "WRITE"
+                                : "READ";
     break;
   case HashInquiryKeyword("ASYNCHRONOUS"):
-    str = unit().mayAsynchronous() ? "YES" : "NO";
+    str = !unit().IsConnected()    ? "UNDEFINED"
+        : unit().mayAsynchronous() ? "YES"
+                                   : "NO";
     break;
   case HashInquiryKeyword("BLANK"):
-    str = unit().isUnformatted.value_or(true)   ? "UNDEFINED"
+    str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
+        ? "UNDEFINED"
         : unit().modes.editingFlags & blankZero ? "ZERO"
                                                 : "NULL";
     break;
@@ -854,12 +863,13 @@
     str = unit().swapEndianness() ? "SWAP" : "NATIVE";
     break;
   case HashInquiryKeyword("DECIMAL"):
-    str = unit().isUnformatted.value_or(true)      ? "UNDEFINED"
+    str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
+        ? "UNDEFINED"
         : unit().modes.editingFlags & decimalComma ? "COMMA"
                                                    : "POINT";
     break;
   case HashInquiryKeyword("DELIM"):
-    if (unit().isUnformatted.value_or(true)) {
+    if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) {
       str = "UNDEFINED";
     } else {
       switch (unit().modes.delim) {
@@ -876,23 +886,26 @@
     }
     break;
   case HashInquiryKeyword("DIRECT"):
-    str = unit().access == Access::Direct ||
+    str = !unit().IsConnected() ? "UNKNOWN"
+        : unit().access == Access::Direct ||
             (unit().mayPosition() && unit().isFixedRecordLength)
         ? "YES"
         : "NO";
     break;
   case HashInquiryKeyword("ENCODING"):
-    str = unit().isUnformatted.value_or(true) ? "UNDEFINED"
+    str = !unit().IsConnected()               ? "UNKNOWN"
+        : unit().isUnformatted.value_or(true) ? "UNDEFINED"
         : unit().isUTF8                       ? "UTF-8"
                                               : "ASCII";
     break;
   case HashInquiryKeyword("FORM"):
-    str = !unit().isUnformatted ? "UNDEFINED"
-        : *unit().isUnformatted ? "UNFORMATTED"
-                                : "FORMATTED";
+    str = !unit().IsConnected() || !unit().isUnformatted ? "UNDEFINED"
+        : *unit().isUnformatted                          ? "UNFORMATTED"
+                                                         : "FORMATTED";
     break;
   case HashInquiryKeyword("FORMATTED"):
-    str = !unit().isUnformatted ? "UNKNOWN"
+    str = !unit().IsConnected() ? "UNDEFINED"
+        : !unit().isUnformatted ? "UNKNOWN"
         : *unit().isUnformatted ? "NO"
                                 : "YES";
     break;
@@ -903,33 +916,38 @@
     }
     break;
   case HashInquiryKeyword("PAD"):
-    str = unit().isUnformatted.value_or(true) ? "UNDEFINED"
-        : unit().modes.pad                    ? "YES"
-                                              : "NO";
+    str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
+        ? "UNDEFINED"
+        : unit().modes.pad ? "YES"
+                           : "NO";
     break;
   case HashInquiryKeyword("POSITION"):
-    if (unit().access == Access::Direct) {
+    if (!unit().IsConnected() || unit().access == Access::Direct) {
       str = "UNDEFINED";
     } else {
-      auto size{unit().knownSize()};
-      auto pos{unit().position()};
-      if (pos == size.value_or(pos + 1)) {
-        str = "APPEND";
-      } else if (pos == 0 && unit().mayPosition()) {
+      switch (unit().InquirePosition()) {
+      case Position::Rewind:
         str = "REWIND";
-      } else {
-        str = "ASIS"; // processor-dependent & no common behavior
+        break;
+      case Position::Append:
+        str = "APPEND";
+        break;
+      case Position::AsIs:
+        str = "ASIS";
+        break;
       }
     }
     break;
   case HashInquiryKeyword("READ"):
-    str = unit().mayRead() ? "YES" : "NO";
+    str = !unit().IsConnected() ? "UNDEFINED" : unit().mayRead() ? "YES" : "NO";
     break;
   case HashInquiryKeyword("READWRITE"):
-    str = unit().mayRead() && unit().mayWrite() ? "YES" : "NO";
+    str = !unit().IsConnected()                 ? "UNDEFINED"
+        : unit().mayRead() && unit().mayWrite() ? "YES"
+                                                : "NO";
     break;
   case HashInquiryKeyword("ROUND"):
-    if (unit().isUnformatted.value_or(true)) {
+    if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) {
       str = "UNDEFINED";
     } else {
       switch (unit().modes.round) {
@@ -954,23 +972,28 @@
   case HashInquiryKeyword("SEQUENTIAL"):
     // "NO" for Direct, since Sequential would not work if
     // the unit were reopened without RECL=.
-    str = unit().access == Access::Sequential ? "YES" : "NO";
+    str = !unit().IsConnected()               ? "UNKNOWN"
+        : unit().access == Access::Sequential ? "YES"
+                                              : "NO";
     break;
   case HashInquiryKeyword("SIGN"):
-    str = unit().isUnformatted.value_or(true)  ? "UNDEFINED"
+    str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
+        ? "UNDEFINED"
         : unit().modes.editingFlags & signPlus ? "PLUS"
                                                : "SUPPRESS";
     break;
   case HashInquiryKeyword("STREAM"):
-    str = unit().access == Access::Stream ? "YES" : "NO";
-    break;
-  case HashInquiryKeyword("WRITE"):
-    str = unit().mayWrite() ? "YES" : "NO";
+    str = !unit().IsConnected()           ? "UNKNOWN"
+        : unit().access == Access::Stream ? "YES"
+                                          : "NO";
     break;
   case HashInquiryKeyword("UNFORMATTED"):
-    str = !unit().isUnformatted ? "UNKNOWN"
-        : *unit().isUnformatted ? "YES"
-                                : "NO";
+    str = !unit().IsConnected() || !unit().isUnformatted ? "UNKNOWN"
+        : *unit().isUnformatted                          ? "YES"
+                                                         : "NO";
+    break;
+  case HashInquiryKeyword("WRITE"):
+    str = !unit().IsConnected() ? "UNKNOWN" : unit().mayWrite() ? "YES" : "NO";
     break;
   }
   if (str) {
@@ -991,7 +1014,7 @@
     result = unit().path() != nullptr;
     return true;
   case HashInquiryKeyword("OPENED"):
-    result = true;
+    result = unit().IsConnected();
     return true;
   case HashInquiryKeyword("PENDING"):
     result = false; // asynchronous I/O is not implemented
@@ -1023,13 +1046,15 @@
     }
     return true;
   case HashInquiryKeyword("NUMBER"):
-    result = unit().unitNumber();
+    result = unit().IsConnected() ? unit().unitNumber() : -1;
     return true;
   case HashInquiryKeyword("POS"):
     result = unit().position();
     return true;
   case HashInquiryKeyword("RECL"):
-    if (unit().access == Access::Stream) {
+    if (!unit().IsConnected()) {
+      result = -1;
+    } else if (unit().access == Access::Stream) {
       result = -2;
     } else if (unit().isFixedRecordLength && unit().recordLength) {
       result = *unit().recordLength;
@@ -1038,10 +1063,11 @@
     }
     return true;
   case HashInquiryKeyword("SIZE"):
-    if (auto size{unit().knownSize()}) {
-      result = *size;
-    } else {
-      result = -1;
+    result = -1;
+    if (unit().IsConnected()) {
+      if (auto size{unit().knownSize()}) {
+        result = *size;
+      }
     }
     return true;
   default:
diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp
index e24914b..f1d2b2c 100644
--- a/flang/runtime/unit.cpp
+++ b/flang/runtime/unit.cpp
@@ -107,7 +107,7 @@
   swapEndianness_ = convert == Convert::Swap ||
       (convert == Convert::LittleEndian && !isHostLittleEndian) ||
       (convert == Convert::BigEndian && isHostLittleEndian);
-  if (IsOpen()) {
+  if (IsConnected()) {
     bool isSamePath{newPath.get() && path() && pathLength() == newPathLength &&
         std::memcmp(path(), newPath.get(), newPathLength) == 0};
     if (status && *status != OpenStatus::Old && isSamePath) {
diff --git a/flang/runtime/unit.h b/flang/runtime/unit.h
index 4f7a18e..a63921c9 100644
--- a/flang/runtime/unit.h
+++ b/flang/runtime/unit.h
@@ -35,6 +35,8 @@
                          public FileFrame<ExternalFileUnit> {
 public:
   explicit ExternalFileUnit(int unitNumber) : unitNumber_{unitNumber} {}
+  ~ExternalFileUnit() {}
+
   int unitNumber() const { return unitNumber_; }
   bool swapEndianness() const { return swapEndianness_; }
   bool createdForInternalChildIo() const { return createdForInternalChildIo_; }