/* * 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 #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_SHLWAPI_H # include #endif #ifdef HAVE_SYS_PARAM_H # include #endif #ifdef HAVE_SYS_MOUNT_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_SYS_VFS_H # include #endif #ifdef HAVE_SYS_STATFS_H # include #endif #ifdef HAVE_SYS_STATVFS_H # include #endif #ifdef HAVE_SYS_MOUNT_H # include #endif #ifdef HAVE_MNTENT_H # include #endif #ifdef HAVE_PATHS_H # include #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 existence 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 /* That's 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; }