text: /*
   * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
   *                         University Research and Technology
   *                         Corporation.  All rights reserved.
   * Copyright (c) 2004-2007 The University of Tennessee and The University
   *                         of Tennessee Research Foundation.  All rights
   *                         reserved.
   * Copyright (c) 2004-2005 High Performance Computing Center Stuttgart,
   *                         University of Stuttgart.  All rights reserved.
   * Copyright (c) 2004-2005 The Regents of the University of California.
   *                         All rights reserved.
   * Copyright (c) 2009-2018 Cisco Systems, Inc.  All rights reserved
   * Copyright (c) 2010      IBM Corporation.  All rights reserved.
   * Copyright (c) 2012-2013 Los Alamos National Security, LLC.
   *                         All rights reserved.
   * Copyright (c) 2014      Intel, Inc.  All rights reserved.
   * Copyright (c) 2016      University of Houston. All rights reserved.
   * Copyright (c) 2016      Research Organization for Information Science
   *                         and Technology (RIST). All rights reserved.
   * $COPYRIGHT$
   *
   * Additional copyrights may follow
   *
   * $HEADER$
   */
  
  #include "opal_config.h"
  #include <errno.h>
  #include <stdlib.h>
  #include <string.h>
  #ifdef HAVE_UNISTD_H
  #    include <unistd.h>
  #endif
  #ifdef HAVE_SHLWAPI_H
  #    include <shlwapi.h>
  #endif
  #ifdef HAVE_SYS_PARAM_H
  #    include <sys/param.h>
  #endif
  #ifdef HAVE_SYS_MOUNT_H
  #    include <sys/mount.h>
  #endif
  #ifdef HAVE_SYS_TYPES_H
  #    include <sys/types.h>
  #endif
  #ifdef HAVE_SYS_STAT_H
  #    include <sys/stat.h>
  #endif
  #ifdef HAVE_SYS_VFS_H
  #    include <sys/vfs.h>
  #endif
  #ifdef HAVE_SYS_STATFS_H
  #    include <sys/statfs.h>
  #endif
  #ifdef HAVE_SYS_STATVFS_H
  #    include <sys/statvfs.h>
  #endif
  #ifdef HAVE_SYS_MOUNT_H
  #    include <sys/mount.h>
  #endif
  #ifdef HAVE_MNTENT_H
  #    include <mntent.h>
  #endif
  #ifdef HAVE_PATHS_H
  #    include <paths.h>
  #endif
  
  #ifdef _PATH_MOUNTED
  #    define MOUNTED_FILE _PATH_MOUNTED
  #else
  #    define MOUNTED_FILE "/etc/mtab"
  #endif
  
  #include "opal/util/argv.h"
  #include "opal/util/os_path.h"
  #include "opal/util/output.h"
  #include "opal/util/path.h"
  #include "opal/util/printf.h"
  #include "opal_stdint.h"
  
  /*
   * Sanity check to ensure we have either statfs or statvfs
   */
  #if !defined(HAVE_STATFS) && !defined(HAVE_STATVFS)
  #    error Must have either statfs() or statvfs()
  #endif
  
  /*
   * Note that some OS's (e.g., NetBSD and Solaris) have statfs(), but
   * no struct statfs (!).  So check to make sure we have struct statfs
   * before allowing the use of statfs().
   */
  #if defined(HAVE_STATFS) \
      && (defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_TYPE))
  #    define USE_STATFS 1
  #endif
  
  static void path_env_load(char *path, int *pargc, char ***pargv);
  static char *list_env_get(char *var, char **list);
  
  bool opal_path_is_absolute(const char *path)
  {
      if (OPAL_PATH_SEP[0] == *path) {
          return true;
      }
      return false;
  }
  
  /**
   *  Locates a file with certain permissions
   */
  char *opal_path_find(char *fname, char **pathv, int mode, char **envv)
  {
      char *fullpath;
      char *delimit;
      char *env;
      char *pfix;
      int i;
  
      /* If absolute path is given, return it without searching. */
      if (opal_path_is_absolute(fname)) {
          return opal_path_access(fname, NULL, mode);
      }
  
      /* Initialize. */
  
      fullpath = NULL;
      i = 0;
  
      /* Consider each directory until the file is found.  Thus, the
         order of directories is important. */
  
      while (pathv[i] && NULL == fullpath) {
  
          /* Replace environment variable at the head of the string. */
          if ('$' == *pathv[i]) {
              delimit = strchr(pathv[i], OPAL_PATH_SEP[0]);
              if (delimit) {
                  *delimit = '\0';
              }
              env = list_env_get(pathv[i] + 1, envv);
              if (delimit) {
                  *delimit = OPAL_PATH_SEP[0];
              }
              if (NULL != env) {
                  if (!delimit) {
                      fullpath = opal_path_access(fname, env, mode);
                  } else {
                      opal_asprintf(&pfix, "%s%s", env, delimit);
                      fullpath = opal_path_access(fname, pfix, mode);
                      free(pfix);
                  }
              }
          } else {
              fullpath = opal_path_access(fname, pathv[i], mode);
          }
          i++;
      }
      return opal_make_filename_os_friendly(fullpath);
  }
  
  /*
   * Locates a file with certain permissions from a list of search paths
   */
  char *opal_path_findv(char *fname, int mode, char **envv, char *wrkdir)
  {
      char **dirv;
      char *fullpath;
      char *path;
      int dirc;
      int i;
      bool found_dot = false;
  
      /* Set the local search paths. */
  
      dirc = 0;
      dirv = NULL;
  
      if (NULL != (path = list_env_get("PATH", envv))) {
          path_env_load(path, &dirc, &dirv);
      }
  
      /* Replace the "." path by the working directory. */
  
      if (NULL != wrkdir) {
          for (i = 0; i < dirc; ++i) {
              if (0 == strcmp(dirv[i], ".")) {
                  found_dot = true;
                  free(dirv[i]);
                  dirv[i] = strdup(wrkdir);
                  if (NULL == dirv[i]) {
                      return NULL;
                  }
              }
          }
      }
  
      /* If we didn't find "." in the path and we have a wrkdir, append
         the wrkdir to the end of the path */
  
      if (!found_dot && NULL != wrkdir) {
          opal_argv_append(&dirc, &dirv, wrkdir);
      }
  
      if (NULL == dirv) {
          return NULL;
      }
      fullpath = opal_path_find(fname, dirv, mode, envv);
      opal_argv_free(dirv);
      return fullpath;
  }
  
  /**
   *  Forms a complete pathname and checks it for existance and
   *  permissions
   *
   *  Accepts:
   *      -fname File name
   *      -path  Path prefix
   *      -mode  Target permissions which must be satisfied
   *
   *  Returns:
   *      -Full pathname of located file Success
   *      -NULL Failure
   */
  char *opal_path_access(char *fname, char *path, int mode)
  {
      char *fullpath = NULL;
      struct stat buf;
      bool relative;
  
      /* Allocate space for the full pathname. */
      if (NULL == path) {
          fullpath = opal_os_path(false, fname, NULL);
      } else {
          relative = !opal_path_is_absolute(path);
          fullpath = opal_os_path(relative, path, fname, NULL);
      }
      if (NULL == fullpath) {
          return NULL;
      }
      /* first check to see - is this a file or a directory? We
       * only want files
       */
      if (0 != stat(fullpath, &buf)) {
          /* couldn't stat the path - obviously, this also meets the
           * existence check, if that was requested
           */
          free(fullpath);
          return NULL;
      }
  
      if (!(S_IFREG & buf.st_mode) && !(S_IFLNK & buf.st_mode)) {
          /* this isn't a regular file or a symbolic link, so
           * ignore it
           */
          free(fullpath);
          return NULL;
      }
  
      /* check the permissions */
      if ((X_OK & mode) && !(S_IXUSR & buf.st_mode)) {
          /* if they asked us to check executable permission,
           * and that isn't set, then return NULL
           */
          free(fullpath);
          return NULL;
      }
      if ((R_OK & mode) && !(S_IRUSR & buf.st_mode)) {
          /* if they asked us to check read permission,
           * and that isn't set, then return NULL
           */
          free(fullpath);
          return NULL;
      }
      if ((W_OK & mode) && !(S_IWUSR & buf.st_mode)) {
          /* if they asked us to check write permission,
           * and that isn't set, then return NULL
           */
          free(fullpath);
          return NULL;
      }
  
      /* must have met all criteria! */
      return fullpath;
  }
  
  /**
   *
   *  Loads argument array with $PATH env var.
   *
   *  Accepts
   *      -path String containing the $PATH
   *      -argc Pointer to argc
   *      -argv Pointer to list of argv
   */
  static void path_env_load(char *path, int *pargc, char ***pargv)
  {
      char *p;
      char saved;
  
      if (NULL == path) {
          *pargc = 0;
          return;
      }
  
      /* Loop through the paths (delimited by PATHENVSEP), adding each
         one to argv. */
  
      while ('\0' != *path) {
  
          /* Locate the delimiter. */
  
          for (p = path; *p && (*p != OPAL_ENV_SEP); ++p) {
              continue;
          }
  
          /* Add the path. */
  
          if (p != path) {
              saved = *p;
              *p = '\0';
              opal_argv_append(pargc, pargv, path);
              *p = saved;
              path = p;
          }
  
          /* Skip past the delimiter, if present. */
  
          if (*path) {
              ++path;
          }
      }
  }
  
  /**
   *  Gets value of variable in list or environment. Looks in the list first
   *
   *  Accepts:
   *      -var  String variable
   *      -list Pointer to environment list
   *
   *  Returns:
   *      -List Pointer to environment list Success
   *      -NULL Failure
   */
  static char *list_env_get(char *var, char **list)
  {
      size_t n;
  
      if (NULL != list) {
          n = strlen(var);
  
          while (NULL != *list) {
              if ((0 == strncmp(var, *list, n)) && ('=' == (*list)[n])) {
                  return (*list + n + 1);
              }
              ++list;
          }
      }
      return getenv(var);
  }
  
  /**
   * Try to figure out the absolute path based on the application name
   * (usually argv[0]). If the path is already absolute return a copy, if
   * it start with . look into the current directory, if not dig into
   * the $PATH.
   * In case of error or if executable was not found (as an example if
   * the application did a cwd between the start and this call), the
   * function will return NULL. Otherwise, an newly allocated string
   * will be returned.
   */
  char *opal_find_absolute_path(char *app_name)
  {
      char *abs_app_name;
      char cwd[OPAL_PATH_MAX], *pcwd;
  
      if (opal_path_is_absolute(app_name)) { /* already absolute path */
          abs_app_name = app_name;
      } else if ('.' == app_name[0] || NULL != strchr(app_name, OPAL_PATH_SEP[0])) {
          /* the app is in the current directory or below it */
          pcwd = getcwd(cwd, OPAL_PATH_MAX);
          if (NULL == pcwd) {
              /* too bad there is no way we can get the app absolute name */
              return NULL;
          }
          abs_app_name = opal_os_path(false, pcwd, app_name, NULL);
      } else {
          /* Otherwise try to search for the application in the PATH ... */
          abs_app_name = opal_path_findv(app_name, X_OK, NULL, NULL);
      }
  
      if (NULL != abs_app_name) {
          char *resolved_path = (char *) malloc(OPAL_PATH_MAX);
          char *ret;
          ret = realpath(abs_app_name, resolved_path);
          if (abs_app_name != app_name) {
              free(abs_app_name);
          }
          if (NULL == ret) {
              free(resolved_path);
              resolved_path = NULL;
          }
          return resolved_path;
      }
      return NULL;
  }
  
  /**
   * Read real FS type from /etc/mtab, needed to translate autofs fs type into real fs type
   * TODO: solaris? OSX?
   * Limitations: autofs on solaris/osx will be assumed as "nfs" type
   */
  
  static char *opal_check_mtab(char *dev_path)
  {
  
  #ifdef HAVE_MNTENT_H
      FILE *mtab = NULL;
      struct mntent *part = NULL;
  
      if ((mtab = setmntent(MOUNTED_FILE, "r")) != NULL) {
          while (NULL != (part = getmntent(mtab))) {
              if ((NULL != part->mnt_dir) && (NULL != part->mnt_type)
                  && (0 == strcmp(part->mnt_dir, dev_path))) {
                  endmntent(mtab);
                  return strdup(part->mnt_type);
              }
          }
          endmntent(mtab);
      }
  #endif
      return NULL;
  }
  
  /**
   * @brief Figure out, whether fname is on network file system
   *
   * Try to figure out, whether the file name specified through fname is
   * on any network file system (currently NFS, Lustre, Panasas and GPFS).
   *
   * If the file is not created, the parent directory is checked.
   * This allows checking for NFS prior to opening the file.
   *
   * @fname[in]          File name to check
   * @fstype[out]        File system type if retval is true
   *
   * @retval true                If fname is on NFS, Lustre, Panasas or GPFS
   * @retval false               otherwise
   *
   *
   * Linux:
   *   statfs(const char *path, struct statfs *buf);
   *          with fsid_t  f_fsid;  (in kernel struct{ int val[2] };)
   *          return 0 success, -1 on failure with errno set.
   *   statvfs (const char *path, struct statvfs *buf);
   *          with unsigned long  f_fsid;   -- returns wrong info
   *          return 0 success, -1 on failure with errno set.
   * Solaris:
   *   statvfs (const char *path, struct statvfs *buf);
   *          with f_basetype, contains a string of length FSTYPSZ
   *          return 0 success, -1 on failure with errno set.
   * FreeBSD:
   *   statfs(const char *path, struct statfs *buf);
   *          with f_fstypename, contains a string of length MFSNAMELEN
   *          return 0 success, -1 on failure with errno set.
   *          compliant with: 4.4BSD.
   * NetBSD:
   *   statvfs (const char *path, struct statvfs *buf);
   *          with f_fstypename, contains a string of length VFS_NAMELEN
   *          return 0 success, -1 on failure with errno set.
   * Mac OSX (10.6.2 through 10.9):
   *   statvfs(const char * restrict path, struct statvfs * restrict buf);
   *          with fsid    Not meaningful in this implementation.
   *          is just a wrapper around statfs()
   *   statfs(const char *path, struct statfs *buf);
   *          with f_fstypename, contains a string of length MFSTYPENAMELEN
   *          return 0 success, -1 on failure with errno set.
   */
  #ifndef LL_SUPER_MAGIC
  #    define LL_SUPER_MAGIC 0x0BD00BD0 /* Lustre magic number */
  #endif
  #ifndef NFS_SUPER_MAGIC
  #    define NFS_SUPER_MAGIC 0x6969
  #endif
  #ifndef PAN_KERNEL_FS_CLIENT_SUPER_MAGIC
  #    define PAN_KERNEL_FS_CLIENT_SUPER_MAGIC 0xAAD7AAEA /* Panasas FS */
  #endif
  #ifndef GPFS_SUPER_MAGIC
  #    define GPFS_SUPER_MAGIC 0x47504653 /* Thats GPFS in ASCII */
  #endif
  #ifndef AUTOFS_SUPER_MAGIC
  #    define AUTOFS_SUPER_MAGIC 0x0187
  #endif
  #ifndef PVFS2_SUPER_MAGIC
  #    define PVFS2_SUPER_MAGIC 0x20030528
  #endif
  
  #define MASK2 0xffff
  #define MASK4 0xffffffff
  
  bool opal_path_nfs(char *fname, char **ret_fstype)
  {
      int i;
      int fsrc = -1;
      int vfsrc = -1;
      int trials;
      char *file = strdup(fname);
  #if defined(USE_STATFS)
      struct statfs fsbuf;
  #endif
  #if defined(HAVE_STATVFS)
      struct statvfs vfsbuf;
  #endif
      /*
       * Be sure to update the test (test/util/opal_path_nfs.c)
       * while adding a new Network/Cluster Filesystem here
       */
      static struct fs_types_t {
          unsigned long long f_fsid;
          unsigned long long f_mask;
          const char *f_fsname;
      } fs_types[] = {{LL_SUPER_MAGIC, MASK4, "lustre"},
                      {NFS_SUPER_MAGIC, MASK2, "nfs"},
                      {AUTOFS_SUPER_MAGIC, MASK2, "autofs"},
                      {PAN_KERNEL_FS_CLIENT_SUPER_MAGIC, MASK4, "panfs"},
                      {GPFS_SUPER_MAGIC, MASK4, "gpfs"},
                      {PVFS2_SUPER_MAGIC, MASK4, "pvfs2"}};
  #define FS_TYPES_NUM (int) (sizeof(fs_types) / sizeof(fs_types[0]))
  
      /*
       * First, get the OS-dependent struct stat(v)fs buf.  This may
       * return the ESTALE error on NFS, if the underlying file/path has
       * changed.
       */
  again:
  #if defined(USE_STATFS)
      trials = 5;
      do {
          fsrc = statfs(file, &fsbuf);
      } while (-1 == fsrc && ESTALE == errno && (0 < --trials));
  #endif
  #if defined(HAVE_STATVFS)
      trials = 5;
      do {
          vfsrc = statvfs(file, &vfsbuf);
      } while (-1 == vfsrc && ESTALE == errno && (0 < --trials));
  #endif
  
      /* In case some error with the current filename, try the parent
         directory */
      if (-1 == fsrc && -1 == vfsrc) {
          char *last_sep;
  
          OPAL_OUTPUT_VERBOSE((10, 0,
                               "opal_path_nfs: stat(v)fs on file:%s failed errno:%d directory:%s\n",
                               fname, errno, file));
          if (EPERM == errno) {
              free(file);
              if (NULL != ret_fstype) {
                  *ret_fstype = NULL;
              }
              return false;
          }
  
          last_sep = strrchr(file, OPAL_PATH_SEP[0]);
          /* Stop the search, when we have searched past root '/' */
          if (NULL == last_sep || (1 == strlen(last_sep) && OPAL_PATH_SEP[0] == *last_sep)) {
              free(file);
              if (NULL != ret_fstype) {
                  *ret_fstype = NULL;
              }
              return false;
          }
          *last_sep = '\0';
  
          goto again;
      }
  
      /* Next, extract the magic value */
      for (i = 0; i < FS_TYPES_NUM; i++) {
  #if defined(USE_STATFS)
          /* These are uses of struct statfs */
  #    if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
          if (0 == fsrc
              && 0
                     == strncasecmp(fs_types[i].f_fsname, fsbuf.f_fstypename,
                                    sizeof(fsbuf.f_fstypename))) {
              goto found;
          }
  #    endif
  #    if defined(HAVE_STRUCT_STATFS_F_TYPE)
          if (0 == fsrc && fs_types[i].f_fsid == (fsbuf.f_type & fs_types[i].f_mask)) {
              goto found;
          }
  #    endif
  #endif
  
  #if defined(HAVE_STATVFS)
          /* These are uses of struct statvfs */
  #    if defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
          if (0 == vfsrc
              && 0
                     == strncasecmp(fs_types[i].f_fsname, vfsbuf.f_basetype,
                                    sizeof(vfsbuf.f_basetype))) {
              goto found;
          }
  #    endif
  #    if defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
          if (0 == vfsrc
              && 0
                     == strncasecmp(fs_types[i].f_fsname, vfsbuf.f_fstypename,
                                    sizeof(vfsbuf.f_fstypename))) {
              goto found;
          }
  #    endif
  #endif
      }
  
      free(file);
      if (NULL != ret_fstype) {
          *ret_fstype = NULL;
      }
      return false;
  
  found:
  
      free(file);
      if (AUTOFS_SUPER_MAGIC == fs_types[i].f_fsid) {
          char *fs_type = opal_check_mtab(fname);
          int x;
          if (NULL != fs_type) {
              for (x = 0; x < FS_TYPES_NUM; x++) {
                  if (AUTOFS_SUPER_MAGIC == fs_types[x].f_fsid) {
                      continue;
                  }
                  if (0 == strcasecmp(fs_types[x].f_fsname, fs_type)) {
                      OPAL_OUTPUT_VERBOSE(
                          (10, 0, "opal_path_nfs: file:%s on fs:%s\n", fname, fs_type));
                      free(fs_type);
                      if (NULL != ret_fstype) {
                          *ret_fstype = strdup(fs_types[x].f_fsname);
                      }
                      return true;
                  }
              }
              free(fs_type);
              if (NULL != ret_fstype) {
                  *ret_fstype = NULL;
              }
              return false;
          }
      }
  
      OPAL_OUTPUT_VERBOSE((10, 0, "opal_path_nfs: file:%s on fs:%s\n", fname, fs_types[i].f_fsname));
      if (NULL != ret_fstype) {
          *ret_fstype = strdup(fs_types[i].f_fsname);
      }
      return true;
  
  #undef FS_TYPES_NUM
  }
  
  int opal_path_df(const char *path, uint64_t *out_avail)
  {
      int rc = -1;
      int trials = 5;
      int err = 0;
  #if defined(USE_STATFS)
      struct statfs buf;
  #elif defined(HAVE_STATVFS)
      struct statvfs buf;
  #endif
  
      if (NULL == path || NULL == out_avail) {
          return OPAL_ERROR;
      }
      *out_avail = 0;
  
      do {
  #if defined(USE_STATFS)
          rc = statfs(path, &buf);
  #elif defined(HAVE_STATVFS)
          rc = statvfs(path, &buf);
  #endif
          err = errno;
      } while (-1 == rc && ESTALE == err && (--trials > 0));
  
      if (-1 == rc) {
          OPAL_OUTPUT_VERBOSE((10, 2,
                               "opal_path_df: stat(v)fs on "
                               "path: %s failed with errno: %d (%s)\n",
                               path, err, strerror(err)));
          return OPAL_ERROR;
      }
  
      /* now set the amount of free space available on path */
      /* sometimes buf.f_bavail is negative */
      *out_avail = (uint64_t) buf.f_bsize * (uint64_t)((long) buf.f_bavail < 0 ? 0 : buf.f_bavail);
  
      OPAL_OUTPUT_VERBOSE((10, 2,
                           "opal_path_df: stat(v)fs states "
                           "path: %s has %" PRIu64 " B of free space.",
                           path, *out_avail));
  
      return OPAL_SUCCESS;
  }
  