1 /*
2  * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
3  *                         University Research and Technology
4  *                         Corporation.  All rights reserved.
5  * Copyright (c) 2004-2007 The University of Tennessee and The University
6  *                         of Tennessee Research Foundation.  All rights
7  *                         reserved.
8  * Copyright (c) 2004-2005 High Performance Computing Center Stuttgart,
9  *                         University of Stuttgart.  All rights reserved.
10  * Copyright (c) 2004-2005 The Regents of the University of California.
11  *                         All rights reserved.
12  * Copyright (c) 2009-2014 Cisco Systems, Inc.  All rights reserved.
13  * Copyright (c) 2010      IBM Corporation.  All rights reserved.
14  * Copyright (c) 2012-2013 Los Alamos National Security, LLC.
15  *                         All rights reserved.
16  * Copyright (c) 2014      Intel, Inc.  All rights reserved.
17  * Copyright (c) 2016      University of Houston. All rights reserved.
18  * Copyright (c) 2016      Research Organization for Information Science
19  *                         and Technology (RIST). All rights reserved.
20  * $COPYRIGHT$
21  *
22  * Additional copyrights may follow
23  *
24  * $HEADER$
25  */
26 
27 #include "opal_config.h"
28 #include <stdlib.h>
29 #include <string.h>
30 #include <errno.h>
31 #ifdef HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34 #ifdef HAVE_SHLWAPI_H
35 #include <shlwapi.h>
36 #endif
37 #ifdef HAVE_SYS_PARAM_H
38 #include <sys/param.h>
39 #endif
40 #ifdef HAVE_SYS_MOUNT_H
41 #include <sys/mount.h>
42 #endif
43 #ifdef HAVE_SYS_TYPES_H
44 #include <sys/types.h>
45 #endif
46 #ifdef HAVE_SYS_STAT_H
47 #include <sys/stat.h>
48 #endif
49 #ifdef HAVE_SYS_VFS_H
50 #include <sys/vfs.h>
51 #endif
52 #ifdef HAVE_SYS_STATFS_H
53 #include <sys/statfs.h>
54 #endif
55 #ifdef HAVE_SYS_STATVFS_H
56 #include <sys/statvfs.h>
57 #endif
58 #ifdef HAVE_SYS_MOUNT_H
59 #include <sys/mount.h>
60 #endif
61 #ifdef HAVE_MNTENT_H
62 #include <mntent.h>
63 #endif
64 #ifdef HAVE_PATHS_H
65 #include <paths.h>
66 #endif
67 
68 #ifdef _PATH_MOUNTED
69 #define MOUNTED_FILE _PATH_MOUNTED
70 #else
71 #define MOUNTED_FILE "/etc/mtab"
72 #endif
73 
74 
75 #include "opal_stdint.h"
76 #include "opal/util/output.h"
77 #include "opal/util/path.h"
78 #include "opal/util/os_path.h"
79 #include "opal/util/argv.h"
80 
81 /*
82  * Sanity check to ensure we have either statfs or statvfs
83  */
84 #if !defined(HAVE_STATFS) && !defined(HAVE_STATVFS)
85 #error Must have either statfs() or statvfs()
86 #endif
87 
88 /*
89  * Note that some OS's (e.g., NetBSD and Solaris) have statfs(), but
90  * no struct statfs (!).  So check to make sure we have struct statfs
91  * before allowing the use of statfs().
92  */
93 #if defined(HAVE_STATFS) && (defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) || \
94                              defined(HAVE_STRUCT_STATFS_F_TYPE))
95 #define USE_STATFS 1
96 #endif
97 
98 static void path_env_load(char *path, int *pargc, char ***pargv);
99 static char *list_env_get(char *var, char **list);
100 
opal_path_is_absolute(const char * path)101 bool opal_path_is_absolute( const char *path )
102 {
103     if( OPAL_PATH_SEP[0] == *path ) {
104         return true;
105     }
106     return false;
107 }
108 
109 /**
110  *  Locates a file with certain permissions
111  */
opal_path_find(char * fname,char ** pathv,int mode,char ** envv)112 char *opal_path_find(char *fname, char **pathv, int mode, char **envv)
113 {
114     char *fullpath;
115     char *delimit;
116     char *env;
117     char *pfix;
118     int i;
119 
120     /* If absolute path is given, return it without searching. */
121     if( opal_path_is_absolute(fname) ) {
122         return opal_path_access(fname, NULL, mode);
123     }
124 
125     /* Initialize. */
126 
127     fullpath = NULL;
128     i = 0;
129 
130     /* Consider each directory until the file is found.  Thus, the
131        order of directories is important. */
132 
133     while (pathv[i] && NULL == fullpath) {
134 
135         /* Replace environment variable at the head of the string. */
136         if ('$' == *pathv[i]) {
137             delimit = strchr(pathv[i], OPAL_PATH_SEP[0]);
138             if (delimit) {
139                 *delimit = '\0';
140             }
141             env = list_env_get(pathv[i]+1, envv);
142             if (delimit) {
143                 *delimit = OPAL_PATH_SEP[0];
144             }
145             if (NULL != env) {
146                 if (!delimit) {
147                     fullpath = opal_path_access(fname, env, mode);
148                 } else {
149                     pfix = (char*) malloc(strlen(env) + strlen(delimit) + 1);
150                     if (NULL == pfix) {
151                         return NULL;
152                     }
153                     strcpy(pfix, env);
154                     strcat(pfix, delimit);
155                     fullpath = opal_path_access(fname, pfix, mode);
156                     free(pfix);
157                 }
158             }
159         }
160         else {
161             fullpath = opal_path_access(fname, pathv[i], mode);
162         }
163         i++;
164     }
165     return opal_make_filename_os_friendly(fullpath);
166 }
167 
168 /*
169  * Locates a file with certain permissions from a list of search paths
170  */
opal_path_findv(char * fname,int mode,char ** envv,char * wrkdir)171 char *opal_path_findv(char *fname, int mode, char **envv, char *wrkdir)
172 {
173     char **dirv;
174     char *fullpath;
175     char *path;
176     int dirc;
177     int i;
178     bool found_dot = false;
179 
180     /* Set the local search paths. */
181 
182     dirc = 0;
183     dirv = NULL;
184 
185     if (NULL != (path = list_env_get("PATH", envv))) {
186         path_env_load(path, &dirc, &dirv);
187     }
188 
189     /* Replace the "." path by the working directory. */
190 
191     if (NULL != wrkdir) {
192         for (i = 0; i < dirc; ++i) {
193             if (0 == strcmp(dirv[i], ".")) {
194                 found_dot = true;
195                 free(dirv[i]);
196                 dirv[i] = strdup(wrkdir);
197                 if (NULL == dirv[i]){
198                     return NULL;
199                 }
200             }
201         }
202     }
203 
204     /* If we didn't find "." in the path and we have a wrkdir, append
205        the wrkdir to the end of the path */
206 
207     if (!found_dot && NULL != wrkdir) {
208         opal_argv_append(&dirc, &dirv, wrkdir);
209     }
210 
211     if(NULL == dirv)
212         return NULL;
213     fullpath = opal_path_find(fname, dirv, mode, envv);
214     opal_argv_free(dirv);
215     return fullpath;
216 }
217 
218 
219 /**
220  *  Forms a complete pathname and checks it for existance and
221  *  permissions
222  *
223  *  Accepts:
224  *      -fname File name
225  *      -path  Path prefix
226  *      -mode  Target permissions which must be satisfied
227  *
228  *  Returns:
229  *      -Full pathname of located file Success
230  *      -NULL Failure
231  */
opal_path_access(char * fname,char * path,int mode)232 char *opal_path_access(char *fname, char *path, int mode)
233 {
234     char *fullpath = NULL;
235     struct stat buf;
236     bool relative;
237 
238     /* Allocate space for the full pathname. */
239     if (NULL == path) {
240         fullpath = opal_os_path(false, fname, NULL);
241     } else {
242         relative = !opal_path_is_absolute(path);
243         fullpath = opal_os_path(relative, path, fname, NULL);
244     }
245     if (NULL == fullpath) {
246         return NULL;
247     }
248     /* first check to see - is this a file or a directory? We
249      * only want files
250      */
251     if (0 != stat(fullpath, &buf)) {
252         /* couldn't stat the path - obviously, this also meets the
253          * existence check, if that was requested
254          */
255         free(fullpath);
256         return NULL;
257     }
258 
259     if (!(S_IFREG & buf.st_mode) &&
260         !(S_IFLNK & buf.st_mode)) {
261         /* this isn't a regular file or a symbolic link, so
262          * ignore it
263          */
264         free(fullpath);
265         return NULL;
266     }
267 
268     /* check the permissions */
269     if ((X_OK & mode) && !(S_IXUSR & buf.st_mode)) {
270         /* if they asked us to check executable permission,
271          * and that isn't set, then return NULL
272          */
273         free(fullpath);
274         return NULL;
275     }
276     if ((R_OK & mode) && !(S_IRUSR & buf.st_mode)) {
277         /* if they asked us to check read permission,
278          * and that isn't set, then return NULL
279          */
280         free(fullpath);
281         return NULL;
282     }
283     if ((W_OK & mode) && !(S_IWUSR & buf.st_mode)) {
284         /* if they asked us to check write permission,
285          * and that isn't set, then return NULL
286          */
287         free(fullpath);
288         return NULL;
289     }
290 
291     /* must have met all criteria! */
292     return fullpath;
293 }
294 
295 
296 /**
297  *
298  *  Loads argument array with $PATH env var.
299  *
300  *  Accepts
301  *      -path String contiaing the $PATH
302  *      -argc Pointer to argc
303  *      -argv Pointer to list of argv
304  */
path_env_load(char * path,int * pargc,char *** pargv)305 static void path_env_load(char *path, int *pargc, char ***pargv)
306 {
307     char *p;
308     char saved;
309 
310     if (NULL == path) {
311         *pargc = 0;
312         return;
313     }
314 
315     /* Loop through the paths (delimited by PATHENVSEP), adding each
316        one to argv. */
317 
318     while ('\0' != *path) {
319 
320         /* Locate the delimiter. */
321 
322         for (p = path; *p && (*p != OPAL_ENV_SEP); ++p) {
323             continue;
324         }
325 
326         /* Add the path. */
327 
328         if (p != path) {
329             saved = *p;
330             *p = '\0';
331             opal_argv_append(pargc, pargv, path);
332             *p = saved;
333             path = p;
334         }
335 
336         /* Skip past the delimiter, if present. */
337 
338         if (*path) {
339             ++path;
340         }
341     }
342 }
343 
344 
345 /**
346  *  Gets value of variable in list or environment. Looks in the list first
347  *
348  *  Accepts:
349  *      -var  String variable
350  *      -list Pointer to environment list
351  *
352  *  Returns:
353  *      -List Pointer to environment list Success
354  *      -NULL Failure
355  */
list_env_get(char * var,char ** list)356 static char *list_env_get(char *var, char **list)
357 {
358     size_t n;
359 
360     if (NULL != list) {
361         n = strlen(var);
362 
363         while (NULL != *list) {
364             if ((0 == strncmp(var, *list, n)) && ('=' == (*list)[n])) {
365                 return (*list + n + 1);
366             }
367             ++list;
368         }
369     }
370     return getenv(var);
371 }
372 
373 /**
374  * Try to figure out the absolute path based on the application name
375  * (usually argv[0]). If the path is already absolute return a copy, if
376  * it start with . look into the current directory, if not dig into
377  * the $PATH.
378  * In case of error or if executable was not found (as an example if
379  * the application did a cwd between the start and this call), the
380  * function will return NULL. Otherwise, an newly allocated string
381  * will be returned.
382  */
opal_find_absolute_path(char * app_name)383 char* opal_find_absolute_path( char* app_name )
384 {
385     char* abs_app_name;
386     char cwd[OPAL_PATH_MAX], *pcwd;
387 
388     if( opal_path_is_absolute(app_name) ) { /* already absolute path */
389         abs_app_name = app_name;
390     } else if ( '.' == app_name[0] ||
391                NULL != strchr(app_name, OPAL_PATH_SEP[0])) {
392         /* the app is in the current directory or below it */
393         pcwd = getcwd( cwd, OPAL_PATH_MAX );
394         if( NULL == pcwd ) {
395             /* too bad there is no way we can get the app absolute name */
396             return NULL;
397         }
398         abs_app_name = opal_os_path( false, pcwd, app_name, NULL );
399     } else {
400         /* Otherwise try to search for the application in the PATH ... */
401         abs_app_name = opal_path_findv( app_name, X_OK, NULL, NULL );
402     }
403 
404     if( NULL != abs_app_name ) {
405         char* resolved_path = (char*)malloc(OPAL_PATH_MAX);
406         realpath( abs_app_name, resolved_path );
407         if( abs_app_name != app_name ) free(abs_app_name);
408         return resolved_path;
409     }
410     return NULL;
411 }
412 
413 /**
414  * Read real FS type from /etc/mtab, needed to translate autofs fs type into real fs type
415  * TODO: solaris? OSX?
416  * Limitations: autofs on solaris/osx will be assumed as "nfs" type
417  */
418 
opal_check_mtab(char * dev_path)419 static char *opal_check_mtab(char *dev_path)
420 {
421 
422 #ifdef HAVE_MNTENT_H
423     FILE * mtab = NULL;
424     struct mntent * part = NULL;
425 
426     if ((mtab = setmntent(MOUNTED_FILE, "r")) != NULL) {
427         while (NULL != (part = getmntent(mtab))) {
428             if ((NULL != part->mnt_dir) &&
429                 (NULL != part->mnt_type) &&
430                 (0 == strcmp(part->mnt_dir, dev_path)))
431             {
432                 endmntent(mtab);
433                 return strdup(part->mnt_type);
434             }
435         }
436         endmntent(mtab);
437     }
438 #endif
439     return NULL;
440 }
441 
442 
443 /**
444  * @brief Figure out, whether fname is on network file system
445  *
446  * Try to figure out, whether the file name specified through fname is
447  * on any network file system (currently NFS, Lustre, Panasas and GPFS).
448  *
449  * If the file is not created, the parent directory is checked.
450  * This allows checking for NFS prior to opening the file.
451  *
452  * @fname[in]          File name to check
453  * @fstype[out]        File system type if retval is true
454  *
455  * @retval true                If fname is on NFS, Lustre, Panasas or GPFS
456  * @retval false               otherwise
457  *
458  *
459  * Linux:
460  *   statfs(const char *path, struct statfs *buf);
461  *          with fsid_t  f_fsid;  (in kernel struct{ int val[2] };)
462  *          return 0 success, -1 on failure with errno set.
463  *   statvfs (const char *path, struct statvfs *buf);
464  *          with unsigned long  f_fsid;   -- returns wrong info
465  *          return 0 success, -1 on failure with errno set.
466  * Solaris:
467  *   statvfs (const char *path, struct statvfs *buf);
468  *          with f_basetype, contains a string of length FSTYPSZ
469  *          return 0 success, -1 on failure with errno set.
470  * FreeBSD:
471  *   statfs(const char *path, struct statfs *buf);
472  *          with f_fstypename, contains a string of length MFSNAMELEN
473  *          return 0 success, -1 on failure with errno set.
474  *          compliant with: 4.4BSD.
475  * NetBSD:
476  *   statvfs (const char *path, struct statvfs *buf);
477  *          with f_fstypename, contains a string of length VFS_NAMELEN
478  *          return 0 success, -1 on failure with errno set.
479  * Mac OSX (10.6.2 through 10.9):
480  *   statvfs(const char * restrict path, struct statvfs * restrict buf);
481  *          with fsid    Not meaningful in this implementation.
482  *          is just a wrapper around statfs()
483  *   statfs(const char *path, struct statfs *buf);
484  *          with f_fstypename, contains a string of length MFSTYPENAMELEN
485  *          return 0 success, -1 on failure with errno set.
486  */
487 #ifndef LL_SUPER_MAGIC
488 #define LL_SUPER_MAGIC                    0x0BD00BD0     /* Lustre magic number */
489 #endif
490 #ifndef NFS_SUPER_MAGIC
491 #define NFS_SUPER_MAGIC                   0x6969
492 #endif
493 #ifndef PAN_KERNEL_FS_CLIENT_SUPER_MAGIC
494 #define PAN_KERNEL_FS_CLIENT_SUPER_MAGIC  0xAAD7AAEA     /* Panasas FS */
495 #endif
496 #ifndef GPFS_SUPER_MAGIC
497 #define GPFS_SUPER_MAGIC  0x47504653    /* Thats GPFS in ASCII */
498 #endif
499 #ifndef AUTOFS_SUPER_MAGIC
500 #define AUTOFS_SUPER_MAGIC 0x0187
501 #endif
502 #ifndef PVFS2_SUPER_MAGIC
503 #define PVFS2_SUPER_MAGIC 0x20030528
504 #endif
505 
506 #define MASK2        0xffff
507 #define MASK4    0xffffffff
508 
opal_path_nfs(char * fname,char ** ret_fstype)509 bool opal_path_nfs(char *fname, char **ret_fstype)
510 {
511     int i;
512     int fsrc = -1;
513     int vfsrc = -1;
514     int trials;
515     char * file = strdup (fname);
516 #if defined(USE_STATFS)
517     struct statfs fsbuf;
518 #endif
519 #if defined(HAVE_STATVFS)
520     struct statvfs vfsbuf;
521 #endif
522     /*
523      * Be sure to update the test (test/util/opal_path_nfs.c)
524      * while adding a new Network/Cluster Filesystem here
525      */
526     static struct fs_types_t {
527         unsigned long long f_fsid;
528         unsigned long long f_mask;
529         const char * f_fsname;
530     } fs_types[] = {
531         {LL_SUPER_MAGIC,                   MASK4, "lustre"},
532         {NFS_SUPER_MAGIC,                  MASK2, "nfs"},
533         {AUTOFS_SUPER_MAGIC,               MASK2, "autofs"},
534         {PAN_KERNEL_FS_CLIENT_SUPER_MAGIC, MASK4, "panfs"},
535         {GPFS_SUPER_MAGIC,                 MASK4, "gpfs"},
536         {PVFS2_SUPER_MAGIC,                MASK4, "pvfs2"}
537     };
538 #define FS_TYPES_NUM (int)(sizeof (fs_types)/sizeof (fs_types[0]))
539 
540     /*
541      * First, get the OS-dependent struct stat(v)fs buf.  This may
542      * return the ESTALE error on NFS, if the underlying file/path has
543      * changed.
544      */
545 again:
546 #if defined(USE_STATFS)
547     trials = 5;
548     do {
549         fsrc = statfs(file, &fsbuf);
550     } while (-1 == fsrc && ESTALE == errno && (0 < --trials));
551 #endif
552 #if defined(HAVE_STATVFS)
553     trials = 5;
554     do {
555         vfsrc = statvfs(file, &vfsbuf);
556     } while (-1 == vfsrc && ESTALE == errno && (0 < --trials));
557 #endif
558 
559     /* In case some error with the current filename, try the parent
560        directory */
561     if (-1 == fsrc && -1 == vfsrc) {
562         char * last_sep;
563 
564         OPAL_OUTPUT_VERBOSE((10, 0, "opal_path_nfs: stat(v)fs on file:%s failed errno:%d directory:%s\n",
565                              fname, errno, file));
566         if (EPERM == errno) {
567             free(file);
568             if ( NULL != ret_fstype ) {
569                 *ret_fstype = NULL;
570             }
571             return false;
572         }
573 
574         last_sep = strrchr(file, OPAL_PATH_SEP[0]);
575         /* Stop the search, when we have searched past root '/' */
576         if (NULL == last_sep || (1 == strlen(last_sep) &&
577             OPAL_PATH_SEP[0] == *last_sep)) {
578             free (file);
579             if ( NULL != ret_fstype ) {
580                 *ret_fstype=NULL;
581             }
582             return false;
583         }
584         *last_sep = '\0';
585 
586         goto again;
587     }
588 
589     /* Next, extract the magic value */
590     for (i = 0; i < FS_TYPES_NUM; i++) {
591 #if defined(USE_STATFS)
592         /* These are uses of struct statfs */
593 #    if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
594         if (0 == fsrc &&
595             0 == strncasecmp(fs_types[i].f_fsname, fsbuf.f_fstypename,
596                              sizeof(fsbuf.f_fstypename))) {
597             goto found;
598         }
599 #    endif
600 #    if defined(HAVE_STRUCT_STATFS_F_TYPE)
601         if (0 == fsrc &&
602             fs_types[i].f_fsid == (fsbuf.f_type & fs_types[i].f_mask)) {
603             goto found;
604         }
605 #    endif
606 #endif
607 
608 #if defined(HAVE_STATVFS)
609         /* These are uses of struct statvfs */
610 #    if defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
611         if (0 == vfsrc &&
612             0 == strncasecmp(fs_types[i].f_fsname, vfsbuf.f_basetype,
613                              sizeof(vfsbuf.f_basetype))) {
614             goto found;
615         }
616 #    endif
617 #    if defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME)
618         if (0 == vfsrc &&
619             0 == strncasecmp(fs_types[i].f_fsname, vfsbuf.f_fstypename,
620                              sizeof(vfsbuf.f_fstypename))) {
621             goto found;
622         }
623 #    endif
624 #endif
625     }
626 
627     free (file);
628     if ( NULL != ret_fstype ) {
629         *ret_fstype=NULL;
630     }
631     return false;
632 
633 found:
634 
635     free (file);
636     if (AUTOFS_SUPER_MAGIC == fs_types[i].f_fsid) {
637         char *fs_type = opal_check_mtab(fname);
638         int x;
639         if (NULL != fs_type) {
640             for (x = 0; x < FS_TYPES_NUM; x++) {
641                 if (AUTOFS_SUPER_MAGIC == fs_types[x].f_fsid) {
642                     continue;
643                 }
644                 if (0 == strcasecmp(fs_types[x].f_fsname, fs_type)) {
645                     OPAL_OUTPUT_VERBOSE((10, 0, "opal_path_nfs: file:%s on fs:%s\n", fname, fs_type));
646                     free(fs_type);
647                     if ( NULL != ret_fstype ) {
648                         *ret_fstype = strdup(fs_types[x].f_fsname);
649                     }
650                     return true;
651                 }
652             }
653             free(fs_type);
654             if ( NULL != ret_fstype ) {
655                 *ret_fstype=NULL;
656             }
657             return false;
658         }
659     }
660 
661     OPAL_OUTPUT_VERBOSE((10, 0, "opal_path_nfs: file:%s on fs:%s\n",
662                 fname, fs_types[i].f_fsname));
663     if ( NULL != ret_fstype ) {
664         *ret_fstype = strdup (fs_types[i].f_fsname);
665     }
666     return true;
667 
668 #undef FS_TYPES_NUM
669 }
670 
671 int
opal_path_df(const char * path,uint64_t * out_avail)672 opal_path_df(const char *path,
673              uint64_t *out_avail)
674 {
675     int rc = -1;
676     int trials = 5;
677     int err = 0;
678 #if defined(USE_STATFS)
679     struct statfs buf;
680 #elif defined(HAVE_STATVFS)
681     struct statvfs buf;
682 #endif
683 
684     if (NULL == path || NULL == out_avail) {
685         return OPAL_ERROR;
686     }
687     *out_avail = 0;
688 
689     do {
690 #if defined(USE_STATFS)
691         rc = statfs(path, &buf);
692 #elif defined(HAVE_STATVFS)
693         rc = statvfs(path, &buf);
694 #endif
695         err = errno;
696     } while (-1 == rc && ESTALE == err && (--trials > 0));
697 
698     if (-1 == rc) {
699         OPAL_OUTPUT_VERBOSE((10, 2, "opal_path_df: stat(v)fs on "
700                              "path: %s failed with errno: %d (%s)\n",
701                              path, err, strerror(err)));
702         return OPAL_ERROR;
703     }
704 
705     /* now set the amount of free space available on path */
706                                /* sometimes buf.f_bavail is negative */
707     *out_avail = (uint64_t)buf.f_bsize * (uint64_t)((long)buf.f_bavail < 0 ? 0 : buf.f_bavail);
708 
709     OPAL_OUTPUT_VERBOSE((10, 2, "opal_path_df: stat(v)fs states "
710                          "path: %s has %"PRIu64 " B of free space.",
711                          path, *out_avail));
712 
713     return OPAL_SUCCESS;
714 }
715