[flang] Implement GET_ENVIRONMENT_VARIABLE(LENGTH)

Search for the environment variable in the envp string passed to
ProgramStart. This doesn't work if the main program isn't Fortran.

Differential Revision: https://reviews.llvm.org/D111394

GitOrigin-RevId: fc2ba5e53d47a07f505ddee2441b3768e446624c
diff --git a/runtime/command.cpp b/runtime/command.cpp
index 5fc2d28..81daf64 100644
--- a/runtime/command.cpp
+++ b/runtime/command.cpp
@@ -10,6 +10,7 @@
 #include "environment.h"
 #include "stat.h"
 #include "flang/Runtime/descriptor.h"
+#include <cstdlib>
 #include <limits>
 
 namespace Fortran::runtime {
@@ -79,4 +80,27 @@
 
   return StatOk;
 }
+
+static std::size_t LengthWithoutTrailingSpaces(const Descriptor &d) {
+  std::size_t s{d.ElementBytes() - 1};
+  while (*d.OffsetElement(s) == ' ') {
+    --s;
+  }
+  return s + 1;
+}
+
+std::int64_t RTNAME(EnvVariableLength)(const Descriptor &name, bool trim_name) {
+  std::size_t nameLength{
+      trim_name ? LengthWithoutTrailingSpaces(name) : name.ElementBytes()};
+  if (nameLength == 0) {
+    return 0;
+  }
+
+  const char *value{
+      executionEnvironment.GetEnv(name.OffsetElement(), nameLength)};
+  if (!value) {
+    return 0;
+  }
+  return std::strlen(value);
+}
 } // namespace Fortran::runtime
diff --git a/runtime/environment.cpp b/runtime/environment.cpp
index badcbbc..2ba8faf 100644
--- a/runtime/environment.cpp
+++ b/runtime/environment.cpp
@@ -68,4 +68,28 @@
 
   // TODO: Set RP/ROUND='PROCESSOR_DEFINED' from environment
 }
+
+const char *ExecutionEnvironment::GetEnv(
+    const char *name, std::size_t name_length) {
+  if (!envp) {
+    // TODO: Ask std::getenv.
+    return nullptr;
+  }
+
+  // envp is an array of strings of the form "name=value".
+  for (const char **var{envp}; *var != nullptr; ++var) {
+    const char *eq{std::strchr(*var, '=')};
+    if (!eq) {
+      // Found a malformed environment string, just ignore it.
+      continue;
+    }
+    if (static_cast<std::size_t>(eq - *var) != name_length) {
+      continue;
+    }
+    if (std::memcmp(*var, name, name_length) == 0) {
+      return eq + 1;
+    }
+  }
+  return nullptr;
+}
 } // namespace Fortran::runtime
diff --git a/runtime/environment.h b/runtime/environment.h
index 5e6b7b8..1d04cb1 100644
--- a/runtime/environment.h
+++ b/runtime/environment.h
@@ -29,6 +29,7 @@
 
 struct ExecutionEnvironment {
   void Configure(int argc, const char *argv[], const char *envp[]);
+  const char *GetEnv(const char *name, std::size_t name_length);
 
   int argc;
   const char **argv;
diff --git a/unittests/Runtime/CommandTest.cpp b/unittests/Runtime/CommandTest.cpp
index a13addd..194860f 100644
--- a/unittests/Runtime/CommandTest.cpp
+++ b/unittests/Runtime/CommandTest.cpp
@@ -24,12 +24,25 @@
   return descriptor;
 }
 
+static OwningPtr<Descriptor> CharDescriptor(const char *value) {
+  std::size_t n{std::strlen(value)};
+  OwningPtr<Descriptor> descriptor{Descriptor::Create(
+      sizeof(char), n, nullptr, 0, nullptr, CFI_attribute_allocatable)};
+  if (descriptor->Allocate() != 0) {
+    return nullptr;
+  }
+  std::memcpy(descriptor->OffsetElement(), value, n);
+  return descriptor;
+}
+
 class CommandFixture : public ::testing::Test {
 protected:
   CommandFixture(int argc, const char *argv[]) {
     RTNAME(ProgramStart)(argc, argv, {});
   }
 
+  CommandFixture(const char *envp[]) { RTNAME(ProgramStart)(0, nullptr, envp); }
+
   std::string GetPaddedStr(const char *text, std::size_t len) const {
     std::string res{text};
     assert(res.length() <= len && "No room to pad");
@@ -175,3 +188,20 @@
   EXPECT_GT(RTNAME(ArgumentValue)(-1, nullptr, errMsg.get()), 0);
   CheckDescriptorEqStr(errMsg.get(), "Inv");
 }
+
+static const char *env[]{"NAME=value", nullptr};
+class EnvironmentVariables : public CommandFixture {
+protected:
+  EnvironmentVariables() : CommandFixture(env) {}
+};
+
+TEST_F(EnvironmentVariables, Length) {
+  EXPECT_EQ(5, RTNAME(EnvVariableLength)(*CharDescriptor("NAME")));
+  EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor("DOESNT_EXIST")));
+
+  EXPECT_EQ(5, RTNAME(EnvVariableLength)(*CharDescriptor("NAME  ")));
+  EXPECT_EQ(0,
+      RTNAME(EnvVariableLength)(*CharDescriptor("NAME "), /*trim_name=*/false));
+
+  EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor("     ")));
+}