Enable GN to build & run on z/OS

All changes are guarded in source files by OS_ZOS macro (defined
ifdef __MVS__), and in build/gen.py by platform.is_zos().

Summary of changes:
- build: add ninja template file
- gen: updates to generate the ninja build files, and add dependency
  on zoslib (https://github.com/ibmruntimes/zoslib) to use its APIs
- config: define OS_ZOS and OS_POSIX
- src: include OS host "zos" in SetSystemVarsLocked()
- src: use APIs from zoslib for thread_local impl'n, GetExePath(),
  sem_init/post/wait/destroy(), and a basic version of clock_gettime()
- src: workaround a Woz compiler bug (arg to std::pair)
- src: use utime, stat, fd_set, exclude headers
- src: use stat, mode, mktemp
- src: change to not assume O_RDONLY is 0 on all platforms (2 on z/OS)

Note: files generated by GN must be manually tagged, so they can be read:
$ find . -type f -name "*.ninja*" -exec chtag -tc819 {} \;

Change-Id: Id06e12c9ca88424a9495a5a2d1ed4e2575185196
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/11520
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/README.md b/README.md
index f62e28f..2f5d232 100644
--- a/README.md
+++ b/README.md
@@ -101,10 +101,20 @@
 in `PATH`, so you'll want to run from a Visual Studio command prompt, or
 similar.
 
-On Linux and Mac, the default compiler is `clang++`, a recent version is
+On Linux, Mac and z/OS, the default compiler is `clang++`, a recent version is
 expected to be found in `PATH`. This can be overridden by setting `CC`, `CXX`,
 and `AR`.
 
+On z/OS, building GN requires [ZOSLIB](https://github.com/ibmruntimes/zoslib) to be
+installed, as described at that URL. When building with `build/gen.py`, use the option
+`--zoslib-dir` to specify the path to [ZOSLIB](https://github.com/ibmruntimes/zoslib):
+
+    cd gn
+    python build/gen.py --zoslib-dir /path/to/zoslib
+
+By default, if you don't specify `--zoslib-dir`, `gn/build/gen.py` expects to find
+`zoslib` directory under `gn/third_party/`.
+
 ## Examples
 
 There is a simple example in [examples/simple_build](examples/simple_build)
diff --git a/build/build_zos.ninja.template b/build/build_zos.ninja.template
new file mode 100644
index 0000000..f9f0c96
--- /dev/null
+++ b/build/build_zos.ninja.template
@@ -0,0 +1,13 @@
+rule cxx
+  command = $cxx $includes $cflags -c $in -o $out
+  description = CXX $out
+  depfile = $out.d
+  deps = gcc
+
+rule alink_thin
+  command = rm -f $out && $ar rcsT $out $in
+  description = AR $out
+
+rule link
+  command = $ld $ldflags -o $out $in $libs $solibs
+  description = LINK $out
diff --git a/build/gen.py b/build/gen.py
index 922bf46..1f23cc4 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -23,7 +23,6 @@
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 REPO_ROOT = os.path.dirname(SCRIPT_DIR)
 
-
 class Platform(object):
   """Represents a host/target platform."""
   def __init__(self, platform):
@@ -55,10 +54,12 @@
       self._platform = 'haiku'
     elif self._platform.startswith('sunos'):
       self._platform = 'solaris'
+    elif self._platform.startswith('zos'):
+      self._platform = 'zos'
 
   @staticmethod
   def known_platforms():
-    return ['linux', 'darwin', 'mingw', 'msys', 'msvc', 'aix', 'fuchsia', 'freebsd', 'netbsd', 'openbsd', 'haiku', 'solaris']
+    return ['linux', 'darwin', 'mingw', 'msys', 'msvc', 'aix', 'fuchsia', 'freebsd', 'netbsd', 'openbsd', 'haiku', 'solaris', 'zos']
 
   def platform(self):
     return self._platform
@@ -93,6 +94,9 @@
   def is_posix(self):
     return self._platform in ['linux', 'freebsd', 'darwin', 'aix', 'openbsd', 'haiku', 'solaris', 'msys', 'netbsd']
 
+  def is_zos(self):
+    return self._platform == 'zos'
+
 
 def main(argv):
   parser = optparse.OptionParser(description=sys.modules[__name__].__doc__)
@@ -129,6 +133,16 @@
                           'library, or \'-l<name>\' on POSIX systems. Can be ' +
                           'used multiple times. Useful to link custom malloc ' +
                           'or cpu profiling libraries.'))
+  if sys.platform == 'zos':
+    parser.add_option('--zoslib-dir',
+                      action='store',
+                      default='../third_party/zoslib',
+                      dest='zoslib_dir',
+                      help=('Specify the path of ZOSLIB directory, to link ' +
+                            'with <ZOSLIB_DIR>/install/lib/libzoslib.a, and ' +
+                            'add -I<ZOSLIB_DIR>/install/include to the compile ' +
+                            'commands. See README.md for details.'))
+
   options, args = parser.parse_args(argv)
 
   if args:
@@ -218,6 +232,7 @@
       'haiku': 'build_haiku.ninja.template',
       'solaris': 'build_linux.ninja.template',
       'netbsd': 'build_linux.ninja.template',
+      'zos': 'build_zos.ninja.template',
   }[platform.platform()])
 
   with open(template_filename) as f:
@@ -319,6 +334,9 @@
       os.path.relpath(os.path.join(REPO_ROOT, 'src'), os.path.dirname(path)),
       '.',
   ]
+  if platform.is_zos():
+    include_dirs += [ options.zoslib_dir + '/install/include' ]
+
   libs = []
 
   if not platform.is_msvc():
@@ -337,8 +355,8 @@
       ldflags.extend(['-fdata-sections', '-ffunction-sections'])
       if platform.is_darwin():
         ldflags.append('-Wl,-dead_strip')
-      elif not platform.is_aix() and not platform.is_solaris():
-        # Garbage collection is done by default on aix.
+      elif not platform.is_aix() and not platform.is_solaris() and not platform.is_zos():
+        # Garbage collection is done by default on aix, and option is unsupported on z/OS.
         ldflags.append('-Wl,--gc-sections')
 
       # Omit all symbol information from the output file.
@@ -349,7 +367,8 @@
           ldflags.append('-Wl,-s')
         elif platform.is_solaris():
           ldflags.append('-Wl,--strip-all')
-        else:
+        elif not platform.is_zos():
+          # /bin/ld on z/OS doesn't have an equivalent option.
           ldflags.append('-Wl,-strip-all')
 
       # Enable identical code-folding.
@@ -405,6 +424,11 @@
     elif platform.is_haiku():
       cflags.append('-fPIC')
       cflags.extend(['-D_BSD_SOURCE'])
+    elif platform.is_zos():
+      cflags.append('-fzos-le-char-mode=ascii')
+      cflags.append('-Wno-unused-function')
+      cflags.append('-D_OPEN_SYS_FILE_EXT')
+      cflags.append('-DPATH_MAX=1024')
 
     if platform.is_posix() and not platform.is_haiku():
       ldflags.append('-pthread')
@@ -745,7 +769,7 @@
       ], 'libs': []},
   }
 
-  if platform.is_posix():
+  if platform.is_posix() or platform.is_zos():
     static_libraries['base']['sources'].extend([
         'src/base/files/file_enumerator_posix.cc',
         'src/base/files/file_posix.cc',
@@ -754,6 +778,9 @@
         'src/base/posix/safe_strerror.cc',
     ])
 
+  if platform.is_zos():
+    libs.extend([ options.zoslib_dir + '/install/lib/libzoslib.a' ])
+
   if platform.is_windows():
     static_libraries['base']['sources'].extend([
         'src/base/files/file_enumerator_win.cc',
diff --git a/src/base/files/file.h b/src/base/files/file.h
index b286f69..737e30a 100644
--- a/src/base/files/file.h
+++ b/src/base/files/file.h
@@ -23,7 +23,8 @@
 namespace base {
 
 #if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
-    defined(OS_HAIKU) || defined(OS_MSYS) || defined(OS_ANDROID) && __ANDROID_API__ < 21
+    defined(OS_HAIKU) || defined(OS_MSYS) || defined(OS_ZOS) || \
+    defined(OS_ANDROID) && __ANDROID_API__ < 21
 typedef struct stat stat_wrapper_t;
 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
 typedef struct stat64 stat_wrapper_t;
diff --git a/src/base/files/file_posix.cc b/src/base/files/file_posix.cc
index 349cbfe..b1f9f5e 100644
--- a/src/base/files/file_posix.cc
+++ b/src/base/files/file_posix.cc
@@ -25,7 +25,8 @@
 namespace {
 
 #if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
-    defined(OS_HAIKU) || defined(OS_MSYS) || defined(OS_ANDROID) && __ANDROID_API__ < 21
+    defined(OS_HAIKU) || defined(OS_MSYS) || defined(OS_ZOS) || \
+    defined(OS_ANDROID) && __ANDROID_API__ < 21
 int CallFstat(int fd, stat_wrapper_t* sb) {
   return fstat(fd, sb);
 }
@@ -93,7 +94,7 @@
   int64_t last_accessed_nsec = stat_info.st_atimespec.tv_nsec;
   time_t creation_time_sec = stat_info.st_ctimespec.tv_sec;
   int64_t creation_time_nsec = stat_info.st_ctimespec.tv_nsec;
-#elif defined(OS_AIX)
+#elif defined(OS_AIX) || defined(OS_ZOS)
   time_t last_modified_sec = stat_info.st_mtime;
   int64_t last_modified_nsec = 0;
   time_t last_accessed_sec = stat_info.st_atime;
@@ -329,7 +330,6 @@
   DCHECK(!IsValid());
 
   int open_flags = 0;
-  created_ = false;
 
   if (flags & FLAG_CREATE_ALWAYS) {
     DCHECK(!open_flags);
@@ -348,12 +348,12 @@
     open_flags |= O_RDWR;
   } else if (flags & FLAG_WRITE) {
     open_flags |= O_WRONLY;
-  } else if (!(flags & FLAG_READ)) {
+  } else if (flags & FLAG_READ) {
+    open_flags |= O_RDONLY;
+  } else {
     NOTREACHED();
   }
 
-  static_assert(O_RDONLY == 0, "O_RDONLY must equal zero");
-
   int mode = S_IRUSR | S_IWUSR;
   int descriptor = HANDLE_EINTR(open(path.value().c_str(), open_flags, mode));
 
@@ -362,9 +362,6 @@
     return;
   }
 
-  if (flags & FLAG_CREATE_ALWAYS)
-    created_ = true;
-
   error_details_ = FILE_OK;
   file_.reset(descriptor);
 }
diff --git a/src/base/files/file_util_posix.cc b/src/base/files/file_util_posix.cc
index 7ee1645..d7ea938 100644
--- a/src/base/files/file_util_posix.cc
+++ b/src/base/files/file_util_posix.cc
@@ -14,7 +14,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
-#include <sys/param.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
@@ -47,6 +46,10 @@
 #include <grp.h>
 #endif
 
+#if !defined(OS_ZOS)
+#include <sys/param.h>
+#endif
+
 // We need to do this on AIX due to some inconsistencies in how AIX
 // handles XOPEN_SOURCE and ALL_SOURCE.
 #if defined(OS_AIX)
@@ -58,7 +61,8 @@
 namespace {
 
 #if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
-    defined(OS_HAIKU) || defined(OS_MSYS) || defined(OS_ANDROID) && __ANDROID_API__ < 21
+    defined(OS_HAIKU) || defined(OS_MSYS) || defined(OS_ZOS) || \
+    defined(OS_ANDROID) && __ANDROID_API__ < 21
 int CallStat(const char* path, stat_wrapper_t* sb) {
   return stat(path, sb);
 }
@@ -143,6 +147,7 @@
 // Appends |mode_char| to |mode| before the optional character set encoding; see
 // https://www.gnu.org/software/libc/manual/html_node/Opening-Streams.html for
 // details.
+#if !defined(OS_ZOS)
 std::string AppendModeCharacter(std::string_view mode, char mode_char) {
   std::string result(mode);
   size_t comma_pos = result.find(',');
@@ -150,8 +155,9 @@
                 mode_char);
   return result;
 }
-#endif
+#endif  // !OS_ZOS
 
+#endif  // !OS_MACOSX
 }  // namespace
 
 FilePath MakeAbsoluteFilePath(const FilePath& input) {
@@ -382,11 +388,25 @@
 
   // this should be OK since mkdtemp just replaces characters in place
   char* buffer = const_cast<char*>(sub_dir_string.c_str());
+#if !defined(OS_ZOS)
   char* dtemp = mkdtemp(buffer);
   if (!dtemp) {
     DPLOG(ERROR) << "mkdtemp";
     return false;
   }
+#else
+  // TODO(gabylb) - zos: currently no mkdtemp on z/OS.
+  // Get a unique temp filename, which should also be unique as a directory name
+  char* dtemp = mktemp(buffer);
+  if (!dtemp) {
+    DPLOG(ERROR) << "mktemp";
+    return false;
+  }
+  if (mkdir(dtemp, S_IRWXU)) {
+    DPLOG(ERROR) << "mkdir";
+    return false;
+  }
+#endif
   *new_dir = FilePath(dtemp);
   return true;
 }
@@ -482,7 +502,7 @@
       strchr(mode, 'e') == nullptr ||
       (strchr(mode, ',') != nullptr && strchr(mode, 'e') > strchr(mode, ',')));
   FILE* result = nullptr;
-#if defined(OS_MACOSX)
+#if defined(OS_MACOSX) || defined(OS_ZOS)
   // macOS does not provide a mode character to set O_CLOEXEC; see
   // https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man3/fopen.3.html.
   const char* the_mode = mode;
@@ -493,7 +513,7 @@
   do {
     result = fopen(filename.value().c_str(), the_mode);
   } while (!result && errno == EINTR);
-#if defined(OS_MACOSX)
+#if defined(OS_MACOSX) || defined(OS_ZOS)
   // Mark the descriptor as close-on-exec.
   if (result)
     SetCloseOnExec(fileno(result));
diff --git a/src/base/logging.cc b/src/base/logging.cc
index ee60c47..ee9e109 100644
--- a/src/base/logging.cc
+++ b/src/base/logging.cc
@@ -28,13 +28,15 @@
 
 #if defined(OS_POSIX) || defined(OS_FUCHSIA)
 #include <errno.h>
-#include <paths.h>
 #include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#if !defined(OS_ZOS)
+#include <paths.h>
+#endif
 #endif
 
 #include <algorithm>
diff --git a/src/gn/args.cc b/src/gn/args.cc
index c21beea..2b67313 100644
--- a/src/gn/args.cc
+++ b/src/gn/args.cc
@@ -320,6 +320,8 @@
   os = "solaris";
 #elif defined(OS_NETBSD)
   os = "netbsd";
+#elif defined(OS_ZOS)
+  os = "zos";
 #else
 #error Unknown OS type.
 #endif
diff --git a/src/gn/exec_process.cc b/src/gn/exec_process.cc
index cc778d8..69d631e 100644
--- a/src/gn/exec_process.cc
+++ b/src/gn/exec_process.cc
@@ -23,6 +23,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <signal.h>
+#include <sys/time.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
diff --git a/src/gn/function_write_file_unittest.cc b/src/gn/function_write_file_unittest.cc
index 5ee6123..8a8ef9a 100644
--- a/src/gn/function_write_file_unittest.cc
+++ b/src/gn/function_write_file_unittest.cc
@@ -15,6 +15,8 @@
 
 #if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_HAIKU) || defined(OS_MSYS)
 #include <sys/time.h>
+#elif defined(OS_ZOS)
+#include <utime.h>
 #endif
 
 #if defined(OS_WIN)
@@ -92,6 +94,8 @@
 #elif defined(OS_AIX) || defined(OS_HAIKU) || defined(OS_SOLARIS)
   struct timeval times[2] = {};
   ASSERT_EQ(utimes(foo_name.value().c_str(), times), 0);
+#elif defined(OS_ZOS)
+  ASSERT_EQ(utime(foo_name.value().c_str(), NULL), 0);
 #else
   struct timeval times[2] = {};
   ASSERT_EQ(futimes(foo_file.GetPlatformFile(), times), 0);
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc
index 6bcb204..b4b71d6 100644
--- a/src/gn/ninja_rust_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -926,10 +926,14 @@
   target.source_types_used().Set(SourceFile::SOURCE_RS);
   target.rust_values().set_crate_root(main);
   target.rust_values().crate_name() = "foo_bar";
+
+  const char* lib = "lib1";
   target.config_values().externs().push_back(
-      std::pair("lib1", LibFile(SourceFile("//foo/lib1.rlib"))));
+      std::pair(lib, LibFile(SourceFile("//foo/lib1.rlib"))));
+  lib = "lib2";
   target.config_values().externs().push_back(
-      std::pair("lib2", LibFile("lib2.rlib")));
+      std::pair(lib, LibFile("lib2.rlib")));
+
   target.SetToolchain(setup.toolchain());
   ASSERT_TRUE(target.OnResolved(&err));
 
diff --git a/src/gn/string_atom.cc b/src/gn/string_atom.cc
index 7842fdd..5701a96 100644
--- a/src/gn/string_atom.cc
+++ b/src/gn/string_atom.cc
@@ -204,11 +204,22 @@
   KeySet local_set_;
 };
 
+#if !defined(OS_ZOS)
 thread_local ThreadLocalCache s_local_cache;
+#else
+// TODO(gabylb) - zos: thread_local not yet supported, use zoslib's impl'n:
+static ThreadLocalCache s_tlc;
+__tlssim<ThreadLocalCache*> __g_s_local_cache_impl(&s_tlc);
+#define s_local_cache (*__g_s_local_cache_impl.access())
+#endif
 
 }  // namespace
 
 StringAtom::StringAtom() : value_(kEmptyString) {}
 
 StringAtom::StringAtom(std::string_view str) noexcept
+#ifndef OS_ZOS
     : value_(*s_local_cache.find(str)) {}
+#else
+    : value_(*s_local_cache->find(str)) {}
+#endif
diff --git a/src/util/build_config.h b/src/util/build_config.h
index 667f009..5c4793e 100644
--- a/src/util/build_config.h
+++ b/src/util/build_config.h
@@ -56,6 +56,9 @@
 #define OS_ASMJS 1
 #elif defined(__HAIKU__)
 #define OS_HAIKU 1
+#elif defined(__MVS__)
+#include "zos-base.h"
+#define OS_ZOS 1
 #else
 #error Please add support for your platform in build_config.h
 #endif
@@ -74,7 +77,7 @@
     defined(OS_FREEBSD) || defined(OS_LINUX) || defined(OS_MACOSX) || \
     defined(OS_NACL) || defined(OS_NETBSD) || defined(OS_OPENBSD) ||  \
     defined(OS_QNX) || defined(OS_SOLARIS) || defined(OS_HAIKU) || \
-    defined(OS_MSYS)
+    defined(OS_MSYS) || defined(OS_ZOS)
 #define OS_POSIX 1
 #endif
 
diff --git a/src/util/exe_path.cc b/src/util/exe_path.cc
index b67318c..7ae9024 100644
--- a/src/util/exe_path.cc
+++ b/src/util/exe_path.cc
@@ -104,6 +104,16 @@
   return base::FilePath(raw);
 }
 
+#elif defined(OS_ZOS)
+
+base::FilePath GetExePath() {
+  char path[PATH_MAX];
+  if (__getexepath(path, sizeof(path), getpid()) != 0) {
+    return base::FilePath();
+  }
+  return base::FilePath(path);
+}
+
 #else
 
 base::FilePath GetExePath() {
diff --git a/src/util/msg_loop.cc b/src/util/msg_loop.cc
index 1566532..1797c90 100644
--- a/src/util/msg_loop.cc
+++ b/src/util/msg_loop.cc
@@ -8,7 +8,13 @@
 
 namespace {
 
+#if !defined(OS_ZOS)
 thread_local MsgLoop* g_current;
+#else
+// TODO(gabylb) - zos: thread_local not yet supported, use zoslib's impl'n:
+__tlssim<MsgLoop*> __g_current_impl(nullptr);
+#define g_current (*__g_current_impl.access())
+#endif
 }
 
 MsgLoop::MsgLoop() {
diff --git a/src/util/semaphore.h b/src/util/semaphore.h
index 2952cae..f74b8f8 100644
--- a/src/util/semaphore.h
+++ b/src/util/semaphore.h
@@ -15,6 +15,8 @@
 #include <windows.h>
 #elif defined(OS_MACOSX)
 #include <dispatch/dispatch.h>
+#elif defined(OS_ZOS)
+#include "zos-semaphore.h"
 #elif defined(OS_POSIX)
 #include <semaphore.h>
 #else
diff --git a/src/util/sys_info.cc b/src/util/sys_info.cc
index 8124e07..c8dc590 100644
--- a/src/util/sys_info.cc
+++ b/src/util/sys_info.cc
@@ -36,6 +36,8 @@
     arch = "x86_64";
   } else if (os == "AIX" || os == "OS400") {
     arch = "ppc64";
+  } else if (std::string(info.sysname) == "OS/390") {
+    arch = "s390x";
   }
   return arch;
 #elif defined(OS_WIN)
@@ -56,7 +58,10 @@
 }
 
 int NumberOfProcessors() {
-#if defined(OS_POSIX)
+#if defined(OS_ZOS)
+  return __get_num_online_cpus();
+
+#elif defined(OS_POSIX)
   // sysconf returns the number of "logical" (not "physical") processors on both
   // Mac and Linux.  So we get the number of max available "logical" processors.
   //