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