1 /*
2 ** Copyright (C) 2001-2020 by Carnegie Mellon University.
3 **
4 ** @OPENSOURCE_LICENSE_START@
5 ** See license information in ../../LICENSE.txt
6 ** @OPENSOURCE_LICENSE_END@
7 */
8 
9 /*
10 **  sku-filesys.c
11 **
12 **    A collection of utility routines dealing with the file system.
13 **
14 **    Suresh L Konda
15 */
16 
17 
18 #include <silk/silk.h>
19 
20 RCSIDENT("$SiLK: sku-filesys.c ef14e54179be 2020-04-14 21:57:45Z mthomas $");
21 
22 #include <silk/utils.h>
23 
24 
25 /* DEFINES AND TYPEDEFS */
26 
27 /* Maximum size to attempt to mmap at a time */
28 #define DEFAULT_MAX_MMAPSIZE ((size_t)1 << 26)
29 
30 
31 /* FUNCTION DEFINITIONS */
32 
33 char *
skBasename_r(char * dest,const char * src,size_t dest_size)34 skBasename_r(
35     char               *dest,
36     const char         *src,
37     size_t              dest_size)
38 {
39     const char *startp;
40     const char *endp;
41     size_t src_len;
42 
43     /* check input: need space for {'.', '\0'} minimally */
44     if (!dest || dest_size < 2) {
45         return NULL;
46     }
47 
48     /* degenerate cases */
49     if (!src || (0 == (src_len = strlen(src)))) {
50         return strncpy(dest, ".", 2);
51     }
52 
53     startp = strrchr(src, '/');
54     if (!startp) {
55         /* no slash; return what we were given */
56         startp = src;
57         endp = src + src_len;
58     } else if ('\0' != *(startp+1)) {
59         /* typical case: "/bin/cat" */
60         ++startp;
61         endp = src + src_len;
62     } else {
63         /* we could have "/", "///", "usr/", or "/usr/lib/" */
64         while (startp > src && *startp == '/') {
65             /* remove trailing '/' */
66             --startp;
67         }
68         endp = startp + 1;
69         /* go backward until find '/'; startp is char after the '/' */
70         while (startp > src) {
71             --startp;
72             if (*startp == '/') {
73                 ++startp;
74                 break;
75             }
76         }
77     }
78 
79     /* need to grab everything between startp and endp */
80     src_len = endp - startp;
81     if (src_len > dest_size-1) {
82         return NULL;
83     }
84     strncpy(dest, startp, src_len);
85     dest[src_len] = '\0';
86 
87     return dest;
88 }
89 
90 
91 char *
skDirname_r(char * dest,const char * src,size_t dest_size)92 skDirname_r(
93     char               *dest,
94     const char         *src,
95     size_t              dest_size)
96 {
97     const char *endp;
98     size_t src_len;
99 
100     /* check input: need space for {'.', '\0'} minimally */
101     if (!dest || dest_size < 2) {
102         return NULL;
103     }
104 
105     /* degenerate cases */
106     if (!src || !(endp = strrchr(src, '/'))) {
107         return strncpy(dest, ".", 2);
108     }
109 
110     if ('\0' == *(endp+1)) {
111         /* we could have "/", "///", "usr/", or "/usr/lib/" */
112         while (endp > src && *endp == '/') {
113             /* remove trailing '/' */
114             --endp;
115         }
116         while (endp > src && *endp != '/') {
117             /* skip basename */
118             --endp;
119         }
120         if (*endp != '/') {
121             /* we're at start of string */
122             return strncpy(dest, ".", 2);
123         }
124     }
125 
126     /* handle duplicate '/' chars */
127     while (endp > src && *endp == '/') {
128         --endp;
129     }
130 
131     src_len = endp - src + 1;
132     if (src_len > dest_size-1) {
133         return NULL;
134     }
135 
136     strncpy(dest, src, src_len);
137     dest[src_len] = '\0';
138 
139     return dest;
140 }
141 
142 
143 char *
skBasename(const char * src)144 skBasename(
145     const char         *src)
146 {
147     static char dest[PATH_MAX]; /* return pointer */
148 
149     return skBasename_r(dest, src, sizeof(dest));
150 }
151 
152 
153 char *
skDirname(const char * src)154 skDirname(
155     const char         *src)
156 {
157     static char dest[PATH_MAX]; /* return pointer */
158 
159     return skDirname_r(dest, src, sizeof(dest));
160 }
161 
162 
163 int
isFIFO(const char * name)164 isFIFO(
165     const char         *name)
166 {
167     struct stat stBuf;
168     if (stat(name, &stBuf) == -1) {
169         return 0;
170     }
171     return (S_ISFIFO(stBuf.st_mode));
172 }
173 
174 
175 int
skDirExists(const char * dName)176 skDirExists(
177     const char         *dName)
178 {
179     struct stat stBuf;
180     if (stat(dName, &stBuf) == -1) {
181         return 0;                   /* does not exist */
182     }
183     /* return a 1 only if this is a directory */
184     return S_ISDIR(stBuf.st_mode);
185 }
186 
187 
188 int
skFileExists(const char * fName)189 skFileExists(
190     const char         *fName)
191 {
192     struct stat stBuf;
193     if (stat(fName, &stBuf) == -1) {
194         return 0;                   /* does not exist */
195     }
196     /* return a 1 only if this is a regular file */
197     return (S_ISREG(stBuf.st_mode) || S_ISFIFO(stBuf.st_mode));
198 }
199 
200 
201 off_t
skFileSize(const char * fName)202 skFileSize(
203     const char         *fName)
204 {
205     struct stat stBuf;
206     if (stat(fName, &stBuf) == -1) {
207         return 0;                   /* does not exist */
208     }
209     /* it exists. return the size */
210     return stBuf.st_size;
211 }
212 
213 
214 /*
215  *    Lock or unlock the file 'fd'.  See header for details.
216  */
217 int
skFileSetLock(int fd,short type,int cmd)218 skFileSetLock(
219     int                 fd,
220     short               type,
221     int                 cmd)
222 {
223     struct flock lock;
224 
225     lock.l_type = type;
226     lock.l_start = 0;             /* at SOF */
227     lock.l_whence = SEEK_SET;     /* SOF */
228     lock.l_len = 0;               /* EOF */
229 
230     if (fcntl(fd, cmd, &lock) != -1) {
231         /* success */
232         return 0;
233     }
234 
235     return -1;
236 }
237 
238 
239 /* Find the file 'base_name' and return its full path in 'buf'.  See
240  * the header for details. */
241 char *
skFindFile(const char * base_name,char * buf,size_t bufsize,int verbose)242 skFindFile(
243     const char         *base_name,
244     char               *buf,
245     size_t              bufsize,
246     int                 verbose)
247 {
248     const char *app_name = skAppName();
249     char *silkpath = getenv(ENV_SILK_PATH);
250     size_t len = 0;
251     int rv;
252 
253     /* check inputs */
254     if (!base_name || !buf || bufsize <= 1) {
255         return NULL;
256     }
257 
258     /* if base_name begins with a slash, use it */
259     if (base_name[0] == '/') {
260         strncpy(buf, base_name, bufsize);
261         buf[bufsize - 1] = '\0';
262         return buf;
263     }
264 
265     /* Check in $SILK_PATH/share/silk and $SILK_PATH/share */
266     if (silkpath) {
267         rv = snprintf(buf, bufsize, "%s/share/silk/%s", silkpath, base_name);
268         if ((size_t)rv < bufsize && skFileExists(buf)) {
269             return buf;
270         }
271         rv = snprintf(buf, bufsize, "%s/share/%s", silkpath, base_name);
272         if ((size_t)rv < bufsize && skFileExists(buf)) {
273             return buf;
274         }
275     }
276 
277     /* Look in binarypath/../share.  First, get the parent directory of
278      * the executable and store in 'buf'. */
279     if (app_name == (char *)NULL) {
280         goto ERROR;
281     }
282     if (NULL == skAppDirParentDir(buf, bufsize)) {
283         buf[0] = '\0';
284         goto ERROR;
285     }
286     len = strlen(buf);
287 
288     /* Now append "/share/silk/<file>" to it */
289     rv = snprintf((buf+len), (bufsize - len - 1), "/share/silk/%s", base_name);
290     if ((size_t)rv < bufsize && skFileExists(buf)) {
291         return buf;
292     }
293 
294     /* Try appending "/share/<file>" to it */
295     rv = snprintf((buf+len), (bufsize - len - 1), "/share/%s", base_name);
296     if ((size_t)rv < bufsize && skFileExists(buf)) {
297         return buf;
298     }
299 
300   ERROR:
301     if (verbose) {
302 #define ERR_MSG                                                        \
303     "Cannot find file '%s' in $" ENV_SILK_PATH "/share/silk/,\n"       \
304     "\tin $" ENV_SILK_PATH "/share/, in $" ENV_SILK_PATH "/, "
305 
306         if (!app_name) {
307             skAppPrintErr((ERR_MSG "and application not registered"),
308                           base_name);
309         }
310         else if ('\0' == buf[0]) {
311             skAppPrintErr((ERR_MSG "and cannot obtain full path to\n"
312                            "\tthe application '%s'"),
313                           base_name, app_name);
314         }
315         else {
316             buf[len] = '\0';
317             skAppPrintErr((ERR_MSG "nor in the share/silk/ and share/\n"
318                            "\tsubdirectories under %s/"),
319                           base_name, buf);
320         }
321 #undef ERR_MSG
322     }
323 
324     return NULL;
325 }
326 
327 
328 char *
skFindPluginPath(const char * dlPath,char * path,size_t path_size,const char * verbose_prefix)329 skFindPluginPath(
330     const char         *dlPath,
331     char               *path,
332     size_t              path_size,
333     const char         *verbose_prefix)
334 {
335 #ifndef SILK_SUBDIR_PLUGINS
336     return NULL;
337 #else
338     const char *subdir[] = SILK_SUBDIR_PLUGINS;
339     char *silkPath;
340     size_t len;
341     int i;
342     int8_t checkSilkPath = 1;
343     int8_t checkExec = 1;
344 
345     /* put path into known state */
346     path[0] = '\0';
347 
348     if (strchr(dlPath, '/')) {
349         return NULL;
350     }
351 
352     /* if dlPath does not contain a slash, first look for the plugin in
353      * the SILK_SUBDIR_PLUGINS subdirectory of the environment variable
354      * named by ENV_SILK_PATH.  If the plugin does not exist there, pass
355      * the dlPath as given to dlopen() which will use LD_LIBRARY_PATH or
356      * equivalent.
357      */
358     while (checkSilkPath || checkExec) {
359         if (checkSilkPath) {
360             checkSilkPath = 0;
361             if (NULL == (silkPath = getenv(ENV_SILK_PATH))) {
362                 continue;
363             }
364             strncpy(path, silkPath, path_size);
365         } else if (checkExec) {
366             checkExec = 0;
367             if (NULL == skAppDirParentDir(path, path_size)) {
368                 /* cannot find executeable path */
369                 continue;
370             }
371         }
372         path[path_size-1] = '\0';
373         len = strlen(path);
374         for (i = 0; subdir[i]; ++i) {
375             snprintf(path + len, path_size - len - 1, "/%s/%s",
376                      subdir[i], dlPath);
377             path[path_size-1] = '\0';
378             if (verbose_prefix) {
379                 skAppPrintErr("%s%s", verbose_prefix, path);
380             }
381             if (skFileExists(path)) {
382                 return path;
383             }
384         }
385     }
386 
387     /* file does not exist.  Fall back to LD_LIBRARY_PATH */
388     path[0] = '\0';
389     return NULL;
390 #endif /* SILK_SUBDIR_PLUGINS */
391 }
392 
393 
394 /* open a file, stream, or process */
395 int
skFileptrOpen(sk_fileptr_t * file,skstream_mode_t io_mode)396 skFileptrOpen(
397     sk_fileptr_t       *file,
398     skstream_mode_t     io_mode)
399 {
400 #ifdef SILK_CLOBBER_ENVAR
401     const char *clobber_env;
402 #endif
403     char gzip_cmd[16 + PATH_MAX];
404     const char *gzip_mode;
405     const char *fopen_mode;
406     struct stat stbuf;
407     size_t len;
408     int flags;
409     int mode;
410     int fd;
411     int rv;
412 
413     assert(file);
414     if (NULL == file->of_name) {
415         return SK_FILEPTR_ERR_INVALID;
416     }
417     switch (io_mode) {
418       case SK_IO_READ:
419       case SK_IO_WRITE:
420       case SK_IO_APPEND:
421         break;
422       default:
423         return SK_FILEPTR_ERR_INVALID; /* NOTREACHED */
424     }
425 
426     /* handle stdio */
427     if (0 == strcmp(file->of_name, "-")) {
428         file->of_type = SK_FILEPTR_IS_STDIO;
429         switch (io_mode) {
430           case SK_IO_READ:
431             file->of_fp = stdin;
432             return 0;
433 
434           case SK_IO_WRITE:
435           case SK_IO_APPEND:
436             file->of_fp = stdout;
437             return 0;
438         }
439     }
440     if (0 == strcmp(file->of_name, "stdin")) {
441         if (SK_IO_READ == io_mode) {
442             file->of_fp = stdin;
443             file->of_type = SK_FILEPTR_IS_STDIO;
444             return 0;
445         }
446         return SK_FILEPTR_ERR_WRITE_STDIN;
447     }
448     if (0 == strcmp(file->of_name, "stdout")) {
449         if (SK_IO_READ == io_mode) {
450             return SK_FILEPTR_ERR_READ_STDOUT;
451         }
452         file->of_fp = stdout;
453         file->of_type = SK_FILEPTR_IS_STDIO;
454         return 0;
455     }
456     if (0 == strcmp(file->of_name, "stderr")) {
457         if (SK_IO_READ == io_mode) {
458             return SK_FILEPTR_ERR_READ_STDERR;
459         }
460         file->of_fp = stderr;
461         file->of_type = SK_FILEPTR_IS_STDIO;
462         return 0;
463     }
464 
465     /* check whether file->of_name indicates file is compressed */
466     len = strlen(file->of_name);
467     if (len > 3 && 0 == strcmp(&file->of_name[len-3], ".gz")) {
468         switch (io_mode) {
469           case SK_IO_READ:
470             gzip_mode = "-d";
471             fopen_mode = "r";
472             break;
473           case SK_IO_WRITE:
474             if (skFileExists(file->of_name)) {
475 #ifdef SILK_CLOBBER_ENVAR
476                 if ((clobber_env = getenv(SILK_CLOBBER_ENVAR)) != NULL
477                     && *clobber_env && *clobber_env != '0')
478                 {
479                     /* overwrite existing files */
480                 } else
481 #endif  /* SILK_CLOBBER_ENVAR */
482                 {
483                     errno = EEXIST;
484                     return SK_FILEPTR_ERR_ERRNO;
485                 }
486             }
487             gzip_mode = ">";
488             fopen_mode = "w";
489             break;
490           case SK_IO_APPEND:
491             if (skFileExists(file->of_name)) {
492                 gzip_mode = ">>";
493             } else {
494                 gzip_mode = ">";
495             }
496             fopen_mode = "w";
497             break;
498           default:
499             skAbort();
500         }
501 
502         rv = snprintf(gzip_cmd, sizeof(gzip_cmd), "gzip -c %s '%s'",
503                       gzip_mode, file->of_name);
504         if (sizeof(gzip_cmd) < (size_t)rv) {
505             return SK_FILEPTR_ERR_TOO_LONG;
506         }
507         errno = 0;
508         file->of_fp = popen(gzip_cmd, fopen_mode);
509         if (NULL == file->of_fp) {
510             if (errno) {
511                 return SK_FILEPTR_ERR_ERRNO;
512             }
513             return SK_FILEPTR_ERR_POPEN;
514         }
515         file->of_type = SK_FILEPTR_IS_PROCESS;
516         return 0;
517     }
518 
519     /* handle a standard fopen() for read */
520     if (SK_IO_READ == io_mode) {
521         file->of_fp = fopen(file->of_name, "r");
522         if (NULL == file->of_fp) {
523             return SK_FILEPTR_ERR_ERRNO;
524         }
525         file->of_type = SK_FILEPTR_IS_FILE;
526         return 0;
527     }
528 
529     /* handle a standard fopen() for write or append.  use open()
530      * first, since it gives us better control. */
531 
532     fopen_mode = "w";
533     /* standard mode of 0666 */
534     mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
535     /* assume creating previously non-existent file */
536     flags = O_WRONLY | O_CREAT | O_EXCL;
537 
538     /* try to open as a brand new file */
539     fd = open(file->of_name, flags, mode);
540     if (fd == -1) {
541         rv = errno;
542         if ((rv == EEXIST)
543             && (0 == stat(file->of_name, &stbuf)))
544         {
545             /* file exists.  Try again with different flags when the
546              * mode is append, the file is a FIFO, the file is a
547              * character device ("/dev/null"), or the SILK_CLOBBER
548              * envar is set. */
549             if (io_mode == SK_IO_APPEND) {
550                 flags = O_WRONLY | O_APPEND;
551                 fopen_mode = "a";
552             } else if (S_ISFIFO(stbuf.st_mode)) {
553                 flags = O_WRONLY;
554             } else if (S_ISCHR(stbuf.st_mode)) {
555                 flags = O_WRONLY | O_NOCTTY;
556 #ifdef SILK_CLOBBER_ENVAR
557             } else if ((clobber_env = getenv(SILK_CLOBBER_ENVAR)) != NULL
558                        && *clobber_env && *clobber_env != '0')
559             {
560                 /* overwrite existing file */
561                 flags = O_WRONLY | O_TRUNC;
562 #endif  /* SILK_CLOBBER_ENVAR */
563             } else {
564                 errno = rv;
565                 return SK_FILEPTR_ERR_ERRNO;
566             }
567 
568             /* try again with the new flags */
569             fd = open(file->of_name, flags, mode);
570         }
571         /* if we (still) have an error, return */
572         if (fd == -1) {
573             return SK_FILEPTR_ERR_ERRNO;
574         }
575     }
576     file->of_fp = fdopen(fd, fopen_mode);
577     if (NULL == file->of_fp) {
578         rv = errno;
579         close(fd);
580         errno = rv;
581         return SK_FILEPTR_ERR_ERRNO;
582     }
583 
584     file->of_type = SK_FILEPTR_IS_FILE;
585     return 0;
586 }
587 
588 
589 int
skFileptrClose(sk_fileptr_t * file,sk_msg_fn_t err_fn)590 skFileptrClose(
591     sk_fileptr_t       *file,
592     sk_msg_fn_t         err_fn)
593 {
594     int rv = 0;
595 
596     assert(file);
597     if (NULL == file->of_fp) {
598         return 0;
599     }
600 
601     switch (file->of_type) {
602       case SK_FILEPTR_IS_STDIO:
603         /* ignore if reading stdin */
604         if (file->of_fp != stdin) {
605             rv = fflush(file->of_fp);
606             if (EOF == rv && err_fn) {
607                 err_fn("Error flushing %s: %s",
608                        (file->of_name ? file->of_name : "stream"),
609                        strerror(errno));
610             }
611         }
612         break;
613 
614       case SK_FILEPTR_IS_FILE:
615         rv = fclose(file->of_fp);
616         if (EOF == rv && err_fn) {
617             if (file->of_name) {
618                 err_fn("Error closing file '%s': %s",
619                        file->of_name, strerror(errno));
620             } else {
621                 err_fn("Error closing file: %s",
622                        strerror(errno));
623             }
624         }
625         break;
626 
627       case SK_FILEPTR_IS_PROCESS:
628         rv = pclose(file->of_fp);
629         if (err_fn) {
630             if (-1 == rv) {
631                 if (file->of_name) {
632                     err_fn("Error closing output process for '%s'",
633                            file->of_name);
634                 } else {
635                     err_fn("Error closing output process");
636                 }
637             } else if (127 == rv) {
638                 if (file->of_name) {
639                     err_fn("Error starting subprocess for '%s'",
640                            file->of_name);
641                 } else {
642                     err_fn("Error starting subprocess");
643                 }
644             }
645         }
646         break;
647 
648       default:
649         skAbortBadCase(file->of_type);
650     }
651 
652     return rv;
653 }
654 
655 
656 const char *
skFileptrStrerror(int errnum)657 skFileptrStrerror(
658     int                 errnum)
659 {
660     static char buf[128];
661 
662     switch ((sk_fileptr_status_t)errnum) {
663       case SK_FILEPTR_OK:
664         return "Success";
665       case SK_FILEPTR_ERR_ERRNO:
666         return strerror(errno);
667       case SK_FILEPTR_ERR_WRITE_STDIN:
668         return "Cannot write to the standard input";
669       case SK_FILEPTR_ERR_READ_STDOUT:
670         return "Cannot read from the standard output";
671       case SK_FILEPTR_ERR_READ_STDERR:
672         return "Cannot read from the standard error";
673       case SK_FILEPTR_ERR_POPEN:
674         return "Failed to open process";
675       case SK_FILEPTR_ERR_TOO_LONG:
676         return "Path name is too long";
677       case SK_FILEPTR_ERR_INVALID:
678         return "Invalid input to function";
679       case SK_FILEPTR_PAGER_IGNORED:
680         return "Not paging the output";
681     }
682 
683     snprintf(buf, sizeof(buf), "Unrecognized skFileptrOpen() return value %d\n",
684              errnum);
685     return buf;
686 }
687 
688 
689 /* open file 'FName' for read (mode==0) or write (mode==1).  See header. */
690 int
skOpenFile(const char * FName,int mode,FILE ** fp,int * isPipe)691 skOpenFile(
692     const char         *FName,
693     int                 mode,
694     FILE              **fp,
695     int                *isPipe)
696 {
697     char cmd[1024];
698     const char *cp;
699     int fd;
700     unsigned char magic[2];
701     ssize_t num_read;
702 
703     /* after this while() loop, 'cp' will be NULL if 'FName' is NOT
704      * compressed, or non-NULL if 'FName' is compressed. */
705     cp = FName;
706     while (NULL != (cp = strstr(cp, ".gz"))) {
707         if (*(cp + 3) == '\0') {
708             /* file ends with ".gz".  Treat it as compressed.  (In
709              * truth, this can be fooled by a bad mkstemp-based
710              * filename as below, but we'll worry about that
711              * later.) */
712             break;
713         } else if (*(cp + 3) == '.') {
714             /* Treat a file that contains ".gz." as a potential
715              * compressed file.  We do this to handle compressed files
716              * have had mkstemp() extensions added to them.  This is
717              * hackish, but it is simple and covers that common case
718              * with few false positives.  We then, if possible, check
719              * to see if it really is compressed by looking for the
720              * gzip magic number (31 139 (see RFC1952)) in the first
721              * two bytes.  We do not do this, however, if we are
722              * writing to the file or if we are working with a named
723              * pipe.  Despite the ability to search for the two-byte
724              * marker in a named pipe stream, we have no way to put
725              * the bytes back for normal processing.  Another solution
726              * would be to use gzopen() and gzread()---which again
727              * won't work for writing---and either accept their
728              * overhead when working with uncompressed files, or have
729              * librw do special things with compressed files.  There
730              * is really no good solution here.  Hopefully soon we
731              * will be using LZO compression in the body of the data
732              * files, but still have an uncompresed SiLK header. */
733             if ((1 == mode) || isFIFO(FName)) {
734                 /* We will assume it is compressed if we are writing
735                  * to the file, or it is a FIFO, since we can't get
736                  * more information (such as a magic number) from the
737                  * file. */
738                 break;
739             }
740             fd = open(FName, O_RDONLY);
741             if (-1 == fd) {
742                 /* We couldn't open the file.  Pass it on for normal
743                  * processing and error handling. */
744                 break;
745             }
746             /* Read the first two bytes of the file. */
747             num_read = read(fd, magic, 2);
748             if ((num_read != 2) || (magic[0] != 31) || (magic[1] != 139)) {
749                 /* This cannot be a gzip compressed file, as it does
750                  * not contain the gzip magic number.  Setting cp to
751                  * NULL indicates that the file is not compressed. */
752                 cp = NULL;
753             }
754             close(fd);
755             break;
756         } else {
757             cp += 3;
758         }
759     }
760 
761     if (NULL == cp) {
762         /* Regular file or named pipe */
763         *isPipe = 0;
764         *fp = fopen(FName, mode ? "w" : "r");
765     } else if (mode == 0 && skFileExists(FName) == 0) {
766         /* Attempting to read from non-existent gzip */
767         *fp = NULL;
768     } else {
769         /* Either writing to gzip or reading from existing gzip */
770         if ((sizeof(cmd) - 16u) < strlen(FName)) {
771             return 1;
772         }
773         *isPipe = 1;
774         snprintf(cmd, sizeof(cmd), "gzip %s '%s'",
775                  (mode ? ">" : "-d -c"), FName);
776         *fp = popen(cmd, mode ? "w" : "r");
777     }
778 
779     if (*fp == (FILE *)NULL) {
780         if (mode == 0 && skFileExists(FName) == 0) {
781             skAppPrintErr("Cannot open non-existant file '%s'", FName);
782         } else {
783             skAppPrintErr("Unable to open file '%s' for %s",
784                           FName, mode ? "writing" : "reading");
785         }
786         return 1;                   /* error */
787     }
788 
789     return 0;
790 }
791 
792 
793 /*
794  *  status = skMakeDir(path);
795  *
796  *    Make the complete directory path to 'path', including parent(s)
797  *    if required.  Return 0 on success.  Return 1 on failure and
798  *    sets errno.
799  */
800 int
skMakeDir(const char * directory)801 skMakeDir(
802     const char         *directory)
803 {
804     int rv = 1; /* return value */
805     int rv_err = 0; /* errno to set */
806     size_t dir_len;
807     char *cp;
808     char *dir_buf = NULL;
809     char **slash_list = NULL;
810     int slash_count = 0;
811     const mode_t dirMode = /* 0775 */
812         S_IRWXU | S_IRGRP | S_IWGRP |  S_IXGRP | S_IROTH | S_IXOTH;
813 
814     assert(directory);
815 
816     /* Try common case first, where only trailing directory is
817      * missing.  AIX does not always set errno to EEXIST for an
818      * existing directory, so call skDirExists() as a back-up test. */
819     errno = 0;
820     if (0 == mkdir(directory, dirMode)
821         || errno == EEXIST
822         || skDirExists(directory))
823     {
824         return 0;
825     }
826 
827     dir_len = strlen(directory);
828     if (0 == dir_len) {
829         /* ENOENT is what ``mkdir("")'' returns */
830         rv_err = ENOENT;
831         goto END;
832     }
833 
834     /* make a copy of the directory name that we can modify, and malloc
835      * an array of char*'s that will point to the slashes ('/') in that
836      * dir_buf. */
837     if (((dir_buf = strdup(directory)) == NULL)
838         || ((slash_list = (char**)malloc(dir_len * sizeof(char*))) == NULL))
839     {
840         rv_err = errno;
841         goto END;
842     }
843 
844     /* point cp at the end of the buffer, then work backward until we
845      * find a slash.  Convert the slash to a '\0' and see if the parent
846      * dir exists.  If it does not, keep shorting the directory
847      * path--stashing the locations of the '/' in slash_list[]--until we
848      * find an existing parent directory.  If the parent dir does exist,
849      * change the '\0' back to a '/' and start making child
850      * directories. */
851     cp = &(dir_buf[dir_len]);
852 
853     for (;;) {
854         /* search for dir-sep */
855         while (cp > dir_buf && *cp != '/') {
856             --cp;
857         }
858         if (cp == dir_buf) {
859             /* can't search past start of string */
860             break;
861         }
862         /* see if parent dir exists... */
863         *cp = '\0';
864         if (skDirExists(dir_buf)) {
865             /* ...it does, we can start making child directories */
866             *cp = '/';
867             break;
868         }
869         /* ...else it does not. Store this location and continue up the path */
870         slash_list[slash_count] = cp;
871         ++slash_count;
872     }
873 
874     /* dir_buf should contain a directory we can create */
875     for (;;) {
876         /* make the child directory */
877         if (0 != mkdir(dir_buf, dirMode)) {
878             /* perhaps another thread or process created the directory? */
879             rv_err = errno;
880             if (rv_err != EEXIST && !skDirExists(dir_buf)) {
881                 goto END;
882             }
883         }
884         if (slash_count == 0) {
885             /* we should have created the entire path */
886             assert(0 == strcmp(dir_buf, directory));
887             break;
888         }
889         /* convert this '\0' back to a '/' and make next child dir */
890         --slash_count;
891         *(slash_list[slash_count]) = '/';
892     }
893 
894     /* success! */
895     rv = 0;
896 
897   END:
898     /* cleanup buffers */
899     if (dir_buf) {
900         free(dir_buf);
901     }
902     if (slash_list) {
903         free(slash_list);
904     }
905     if (rv) {
906         errno = rv_err;
907     }
908     return rv;
909 }
910 
911 
912 /*
913  * skCopyFile:
914  *      Copies the file "source" to "dest".  "Dest" may be a file or a
915  *      directory.
916  * Input: char * source
917  *        char * dest
918  * Output: 0 on success, errno on failure.
919  */
920 int
skCopyFile(const char * srcPath,const char * destPath)921 skCopyFile(
922     const char         *srcPath,
923     const char         *destPath)
924 {
925     static size_t max_mapsize = DEFAULT_MAX_MMAPSIZE;
926     int fdin = -1, fdout = -1;
927     void *src = NULL, *dst = NULL;
928     const char *dest = NULL;
929     char destBuf[PATH_MAX];
930     char base[PATH_MAX];
931     struct stat st;
932     int saveerrno;
933     int rv;
934     off_t orv;
935     ssize_t wrv;
936     off_t offset;
937     off_t size;
938     size_t mapsize = 0;
939     int pagesize = sysconf(_SC_PAGESIZE);
940 
941     max_mapsize -= max_mapsize % pagesize;
942 
943     /* Handle dest being a directory */
944     if (skDirExists(destPath)) {
945         skBasename_r(base, srcPath, sizeof(base));
946         rv = snprintf(destBuf, sizeof(destBuf), "%s/%s", destPath, base);
947         if (rv == -1) {
948             return EIO;
949         }
950         if ((unsigned int)rv > (sizeof(destBuf) - 1)) {
951             return ENAMETOOLONG;
952         }
953         dest = destBuf;
954     } else {
955         dest = destPath;
956     }
957 
958     /* Open source */
959     fdin = open(srcPath, O_RDONLY);
960     if (fdin == -1) {
961         goto copy_error;
962     }
963 
964     /* Get source info */
965     rv = fstat(fdin, &st);
966     if (rv == -1) {
967         goto copy_error;
968     }
969     size = st.st_size;
970 
971     /* Open dest */
972     fdout = open(dest, O_RDWR | O_CREAT | O_TRUNC, st.st_mode);
973     if (fdout == -1) {
974         goto copy_error;
975     }
976 
977     /* Resize dest to source size (For mmap.  See APUE [Stevens].) */
978     orv = lseek(fdout, size - 1, SEEK_SET);
979     if (orv == -1) {
980         goto copy_error;
981     }
982     wrv = write(fdout, "", 1);
983     if (wrv != 1) {
984         goto copy_error;
985     }
986 
987     offset = 0;
988     while (size) {
989         mapsize = (size > (off_t)max_mapsize) ? max_mapsize : (size_t)size;
990 
991         /* Map source */
992         src = mmap(0, mapsize, PROT_READ, MAP_SHARED, fdin, offset);
993         if (src == MAP_FAILED) {
994             if (errno == ENOMEM) {
995                 max_mapsize >>= 1;
996                 max_mapsize -= max_mapsize % pagesize;
997                 continue;
998             }
999             goto copy_error;
1000         }
1001         /* Map dest */
1002         dst = mmap(0, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED,
1003                    fdout, offset);
1004         if (dst == MAP_FAILED) {
1005             if (errno == ENOMEM) {
1006                 rv = munmap(src, mapsize);
1007                 if (rv != 0) {
1008                     goto copy_error;
1009                 }
1010                 max_mapsize >>= 1;
1011                 max_mapsize -= max_mapsize % pagesize;
1012                 continue;
1013             }
1014             goto copy_error;
1015         }
1016 
1017         /* Copy src to dest */
1018         memcpy(dst, src, mapsize);
1019 
1020         /* Close src and dest */
1021         rv = munmap(src, mapsize);
1022         if (rv != 0) {
1023             goto copy_error;
1024         }
1025         rv = munmap(dst, mapsize);
1026         if (rv != 0) {
1027             goto copy_error;
1028         }
1029 
1030         offset += mapsize;
1031         size -= mapsize;
1032     }
1033 
1034     rv = close(fdin);
1035     fdin = -1;
1036     if (rv == -1) {
1037         goto copy_error;
1038     }
1039 
1040     rv = close(fdout);
1041     fdout = -1;
1042     if (rv == -1) {
1043         goto copy_error;
1044     }
1045 
1046     return 0;
1047 
1048   copy_error:
1049     saveerrno = errno;
1050 
1051     if (fdin != -1) {
1052         close (fdin);
1053     }
1054     if (fdout != -1) {
1055         close (fdout);
1056     }
1057     if (src) {
1058         munmap(src, mapsize);
1059     }
1060     if (dst) {
1061         munmap(dst, mapsize);
1062     }
1063     if (fdout != -1 || dst) {
1064         unlink(dest);
1065     }
1066 
1067     return saveerrno;
1068 }
1069 
1070 
1071 /*
1072  * skMoveFile:
1073  *      Moves the file "source" to "dest".  "Dest" may be a file or a
1074  *      directory.
1075  * Input: char * source
1076  *        char * dest
1077  * Output: 0 on success, errno on failure.
1078  */
1079 int
skMoveFile(const char * srcPath,const char * destPath)1080 skMoveFile(
1081     const char         *srcPath,
1082     const char         *destPath)
1083 {
1084     const char *dest;
1085     char destBuf[PATH_MAX];
1086     char base[PATH_MAX];
1087     int rv;
1088     int saveerrno;
1089 
1090     /* Handle dest being a directory */
1091     if (skDirExists(destPath)) {
1092         skBasename_r(base, srcPath, sizeof(base));
1093         rv = snprintf(destBuf, sizeof(destBuf), "%s/%s", destPath, base);
1094         if (rv == -1) {
1095             return EIO;
1096         }
1097         if ((unsigned int)rv > (sizeof(destBuf) - 1)) {
1098             return ENAMETOOLONG;
1099         }
1100         dest = destBuf;
1101     } else {
1102         dest = destPath;
1103     }
1104 
1105     /* Attempt a simple move */
1106     rv = rename(srcPath, dest);
1107     if (rv == -1) {
1108         if (errno != EXDEV) {
1109             return errno;
1110         }
1111 
1112         /* Across filesystems, so copy and delete. */
1113         rv = skCopyFile(srcPath, dest);
1114         if (rv != 0) {
1115             return rv;
1116         }
1117         rv = unlink(srcPath);
1118         if (rv == -1) {
1119             saveerrno = errno;
1120             unlink(dest);
1121             return saveerrno;
1122         }
1123     }
1124 
1125     return 0;
1126 }
1127 
1128 
1129 /* return the temporary directory. */
1130 const char *
skTempDir(const char * user_temp_dir,sk_msg_fn_t err_fn)1131 skTempDir(
1132     const char         *user_temp_dir,
1133     sk_msg_fn_t         err_fn)
1134 {
1135     const char *tmp_dir = NULL;
1136 
1137     /* Use the user's option if given, or SILK_TMPDIR, TMPDIR, or the
1138      * default */
1139     if (NULL == tmp_dir) {
1140         tmp_dir = user_temp_dir;
1141     }
1142     if (NULL == tmp_dir) {
1143         tmp_dir = getenv(SK_TEMPDIR_ENVAR1);
1144     }
1145     if (NULL == tmp_dir) {
1146         tmp_dir = getenv(SK_TEMPDIR_ENVAR2);
1147     }
1148 #ifdef SK_TEMPDIR_DEFAULT
1149     if (NULL == tmp_dir) {
1150         tmp_dir = SK_TEMPDIR_DEFAULT;
1151     }
1152 #endif /* SK_TEMPDIR_DEFAULT */
1153     if (NULL == tmp_dir) {
1154         if (err_fn) {
1155             err_fn("Cannot find a value for the temporary directory.");
1156         }
1157         return NULL;
1158     }
1159     if ( !skDirExists(tmp_dir)) {
1160         if (err_fn) {
1161             err_fn("Temporary directory '%s' does not exist", tmp_dir);
1162         }
1163         return NULL;
1164     }
1165     return tmp_dir;
1166 }
1167 
1168 
1169 /* paginate the output.  see utils.h */
1170 int
skOpenPagerWhenStdoutTty(FILE ** output_stream,char ** pager)1171 skOpenPagerWhenStdoutTty(
1172     FILE              **output_stream,
1173     char              **pager)
1174 {
1175     FILE *out;
1176     char *pg;
1177     pid_t pid;
1178     int wait_status;
1179 
1180     /* verify input; deference the input variables */
1181     assert(output_stream);
1182     assert(pager);
1183     out = *output_stream;
1184     pg = *pager;
1185 
1186     /* don't page if output is not "stdout" */
1187     if (NULL == out) {
1188         out = stdout;
1189     } else if (out != stdout) {
1190         /* no change */
1191         return 0;
1192     }
1193 
1194     /* don't page a non-terminal */
1195     if ( !FILEIsATty(out)) {
1196         if (pg) {
1197             /* pager explicitly given but ignored */
1198             skAppPrintErr("Ignoring the --pager switch");
1199         }
1200         return 0;
1201     }
1202 
1203     /* get pager from environment if not passed in */
1204     if (NULL == pg) {
1205         pg = getenv("SILK_PAGER");
1206         if (NULL == pg) {
1207             pg = getenv("PAGER");
1208         }
1209     }
1210 
1211     /* a NULL or an empty string pager means do nothing */
1212     if ((NULL == pg) || ('\0' == pg[0])) {
1213         return 0;
1214     }
1215 
1216 #if 1
1217     /* invoke the pager */
1218     out = popen(pg, "w");
1219     if (NULL == out) {
1220         skAppPrintErr("Unable to invoke pager '%s'", pg);
1221         return -1;
1222     }
1223 
1224     /* see if pager started.  There is a race condition here, and this
1225      * assumes we have only one child, which should be true. */
1226     pid = wait4(0, &wait_status, WNOHANG, NULL);
1227     if (pid) {
1228         skAppPrintErr("Unable to invoke pager '%s'", pg);
1229         return -1;
1230     }
1231 #else
1232     {
1233     int pipe_des[2];
1234 
1235     /* create pipe and fork */
1236     if (pipe(pipe_des) == -1) {
1237         skAppPrintErr("Cannot create pipe: %s", strerror(errno));
1238         return -1;
1239     }
1240     pid = fork();
1241     if (pid < 0) {
1242         skAppPrintErr("Cannot fork: %s", strerror(errno));
1243         return -1;
1244     }
1245 
1246     if (pid == 0) {
1247         /* CHILD */
1248 
1249         /* close output side of pipe; set input to stdin */
1250         close(pipe_des[1]);
1251         if (pipe_des[0] != STDIN_FILENO) {
1252             dup2(pipe_des[0], STDIN_FILENO);
1253             close(pipe_des[0]);
1254         }
1255 
1256         /* invoke pager */
1257         execlp(pg, NULL);
1258         skAppPrintErr("Unable to invoke pager '%s': %s",
1259                       pg, strerror(errno));
1260         _exit(EXIT_FAILURE);
1261     }
1262 
1263     /* PARENT */
1264 
1265     /* close input side of pipe */
1266     close(pipe_des[0]);
1267 
1268     /* try to open the write side of the pipe */
1269     out = fdopen(pipe_des[1], "w");
1270     if (NULL == out) {
1271         skAppPrintErr("Cannot fdopen: %s", strerror(errno));
1272         return -1;
1273     }
1274 
1275     /* it'd be nice to have a slight pause here to give child time to
1276      * die if command cannot be exec'ed, but it's not worth the
1277      * trouble to use select(), and sleep(1) is too long. */
1278 
1279     /* see if child died unexpectedly */
1280     if (waitpid(pid, &wait_status, WNOHANG)) {
1281         skAppPrintErr("Unable to invoke pager '%s'", pg);
1282         return -1;
1283     }
1284     }
1285 #endif /* 1: whether to use popen() */
1286 
1287     /* looks good. set the output variables and return */
1288     *pager = pg;
1289     *output_stream = out;
1290     return 1;
1291 }
1292 
1293 
1294 int
skFileptrOpenPager(sk_fileptr_t * file,const char * pager)1295 skFileptrOpenPager(
1296     sk_fileptr_t       *file,
1297     const char         *pager)
1298 {
1299     FILE *out;
1300     const char *pg;
1301     pid_t pid;
1302     int wait_status;
1303 
1304     /* verify input; deference the input variables */
1305     assert(file);
1306 
1307     /* don't page if output is not "stdout" */
1308     if (NULL == file->of_fp) {
1309         /* assume output is stdout */
1310     } else if (file->of_fp != stdout) {
1311         /* no change */
1312         return SK_FILEPTR_PAGER_IGNORED;
1313     }
1314 
1315     /* don't page a non-terminal */
1316     if (!FILEIsATty(stdout)) {
1317         return SK_FILEPTR_PAGER_IGNORED;
1318     }
1319 
1320     /* get pager from environment if not passed in */
1321     if (pager) {
1322         pg = pager;
1323     } else {
1324         pg = getenv("SILK_PAGER");
1325         if (NULL == pg) {
1326             pg = getenv("PAGER");
1327         }
1328     }
1329 
1330     /* a NULL or an empty string pager means do nothing */
1331     if ((NULL == pg) || ('\0' == pg[0])) {
1332         return SK_FILEPTR_PAGER_IGNORED;
1333     }
1334 
1335     /* invoke the pager */
1336     out = popen(pg, "w");
1337     if (NULL == out) {
1338         return SK_FILEPTR_ERR_POPEN;
1339     }
1340 
1341     /* see if pager started.  There is a race condition here, and this
1342      * assumes we have only one child, which should be true. */
1343     pid = wait4(0, &wait_status, WNOHANG, NULL);
1344     if (pid) {
1345         pclose(out);
1346         return SK_FILEPTR_ERR_POPEN;
1347     }
1348 
1349     /* looks good. set the output variables and return */
1350     file->of_name = (char*)pg;
1351     file->of_fp   = out;
1352     file->of_type = SK_FILEPTR_IS_PROCESS;
1353 
1354     return SK_FILEPTR_OK;
1355 }
1356 
1357 
1358 /* Close the pager */
1359 void
skClosePager(FILE * pager_stream,const char * pager)1360 skClosePager(
1361     FILE               *pager_stream,
1362     const char         *pager)
1363 {
1364     if (pager_stream && (pager_stream != stdout)) {
1365         if (-1 == pclose(pager_stream)) {
1366             skAppPrintErr("Error closing pager '%s'", pager);
1367         }
1368     }
1369 }
1370 
1371 
1372 /* Get a line from a file */
1373 int
skGetLine(char * out_buffer,size_t buf_size,FILE * stream,const char * comment_start)1374 skGetLine(
1375     char               *out_buffer,
1376     size_t              buf_size,
1377     FILE               *stream,
1378     const char         *comment_start)
1379 {
1380     int line_count = 0;
1381     size_t len;
1382     char *eol;
1383 
1384     assert(out_buffer && buf_size);
1385 
1386     /* read until end of file */
1387     while (!feof(stream)) {
1388         memset(out_buffer, '\0', buf_size);
1389         if (fgets(out_buffer, buf_size, stream) == NULL) {
1390             continue;
1391         }
1392         line_count++;
1393 
1394         /* find end of line */
1395         eol = strchr(out_buffer, '\n');
1396         if (out_buffer == eol) {
1397             /* empty line; ignore */
1398             continue;
1399         }
1400 
1401         if (eol != NULL) {
1402             /* expected behavior: read an entire line */
1403             *eol = '\0';
1404         } else if (feof(stream)) {
1405             /* okay: last line did not end in newline */
1406         } else {
1407             /* bad: line was longer than buf_size.  read
1408              * until newline or eof, then throw away the line */
1409             while (fgets(out_buffer, buf_size, stream)
1410                    && !strchr(out_buffer, '\n'))
1411                 ; /* empty */
1412             continue;
1413         }
1414 
1415         /* Terminate line at first comment char */
1416         if (comment_start && *comment_start
1417             && (NULL != (eol = strstr(out_buffer, comment_start))))
1418         {
1419             if (out_buffer == eol) {
1420                 /* only a comment */
1421                 continue;
1422             }
1423             *eol = '\0';
1424         }
1425 
1426         /* find first non-space character */
1427         len = strspn(out_buffer, " \t\v\f\r");
1428         if ((out_buffer + len) == eol) {
1429             /* whitespace only */
1430             continue;
1431         }
1432 
1433         return line_count;
1434     }
1435 
1436     out_buffer[0] = '\0';
1437     return 0;
1438 }
1439 
1440 
1441 /* check that char after % in command is in conversion_chars */
1442 size_t
skSubcommandStringCheck(const char * command,const char * conversion_chars)1443 skSubcommandStringCheck(
1444     const char         *command,
1445     const char         *conversion_chars)
1446 {
1447     const char *cp;
1448 
1449     assert(command);
1450     assert(conversion_chars);
1451 
1452     cp = command;
1453     while (NULL != (cp = strchr(cp, (int)'%'))) {
1454         ++cp;
1455         switch (*cp) {
1456           case '%':
1457             break;
1458           case '\0':
1459             return cp - command;
1460           default:
1461             if (NULL == strchr(conversion_chars, (int)*cp)) {
1462                 return cp - command;
1463             }
1464             break;
1465         }
1466         ++cp;
1467     }
1468     return 0;
1469 }
1470 
1471 
1472 /* return a new string that is command with conversions expanded */
1473 char *
skSubcommandStringFill(const char * command,const char * conversion_chars,...)1474 skSubcommandStringFill(
1475     const char         *command,
1476     const char         *conversion_chars,
1477     ...)
1478 {
1479     char *expanded_cmd;
1480     char *expansion;
1481     const char *cp;
1482     const char *sp;
1483     char *exp_cp;
1484     va_list args;
1485     size_t len;
1486 
1487     /* Determine length of buffer needed for the expanded command
1488      * string and allocate it.  */
1489     cp = command;
1490     len = 0;
1491     while (NULL != (sp = strchr(cp, (int)'%'))) {
1492         len += sp - cp;
1493         cp = sp + 1;
1494         if ('%' == *cp) {
1495             ++len;
1496         } else {
1497             sp = strchr(conversion_chars, (int)*cp);
1498             if (NULL == sp || '\0' == *sp) {
1499                 return NULL;
1500             }
1501             va_start(args, conversion_chars);
1502             do {
1503                 expansion = va_arg(args, char *);
1504                 --sp;
1505             } while (sp >= conversion_chars);
1506             len += strlen(expansion);
1507             va_end(args);
1508         }
1509         ++cp;
1510     }
1511     len += strlen(cp);
1512     expanded_cmd = (char*)malloc(len + 1);
1513     if (NULL == expanded_cmd) {
1514         return NULL;
1515     }
1516 
1517     /* Copy command into buffer, handling %-expansions */
1518     cp = command;
1519     exp_cp = expanded_cmd;
1520     while (NULL != (sp = strchr(cp, (int)'%'))) {
1521         /* copy text we just jumped over */
1522         strncpy(exp_cp, cp, sp - cp);
1523         exp_cp += sp - cp;
1524         cp = sp + 1;
1525         /* handle conversion */
1526         if ('%' == *cp) {
1527             *exp_cp = '%';
1528             ++exp_cp;
1529         } else {
1530             sp = strchr(conversion_chars, (int)*cp);
1531             assert(sp && *sp);
1532             va_start(args, conversion_chars);
1533             do {
1534                 expansion = va_arg(args, char *);
1535                 --sp;
1536             } while (sp >= conversion_chars);
1537             strcpy(exp_cp, expansion);
1538             exp_cp = strchr(exp_cp, (int)'\0');
1539             va_end(args);
1540         }
1541         ++cp;
1542         assert(len >= (size_t)(exp_cp - expanded_cmd));
1543     }
1544     strcpy(exp_cp, cp);
1545     expanded_cmd[len] = '\0';
1546 
1547     return expanded_cmd;
1548 }
1549 
1550 
1551 /*
1552  *    Use the global 'environ' variable to get the environment table.
1553  *    On macOS, we must use _NSGetEnviron() to get the environment.
1554  */
1555 #if defined(SK_HAVE_DECL__NSGETENVIRON) && SK_HAVE_DECL__NSGETENVIRON
1556 #include <crt_externs.h>
1557 #define SK_ENVIRON_TABLE *_NSGetEnviron()
1558 #define SK_COPY_ENVIRONMENT 1
1559 #elif defined(SK_HAVE_DECL_ENVIRON) && SK_HAVE_DECL_ENVIRON
1560 #define SK_ENVIRON_TABLE environ
1561 #define SK_COPY_ENVIRONMENT 1
1562 #endif
1563 #ifndef SK_COPY_ENVIRONMENT
1564 #define SK_COPY_ENVIRONMENT 0
1565 #endif
1566 
1567 
1568 #if SK_COPY_ENVIRONMENT
1569 /**
1570  *    Free the copy of the environment that was allocated by
1571  *    skSubcommandCopyEnvironment().
1572  */
1573 static void
skSubcommandFreeEnvironment(char ** env_copy)1574 skSubcommandFreeEnvironment(
1575     char              **env_copy)
1576 {
1577     size_t i;
1578 
1579     if (env_copy) {
1580         for (i = 0; env_copy[i]; ++i) {
1581             free(env_copy[i]);
1582         }
1583         free(env_copy);
1584     }
1585 }
1586 
1587 /**
1588  *    Make and return a copy of the current environment.
1589  */
1590 static char **
skSubcommandCopyEnvironment(void)1591 skSubcommandCopyEnvironment(
1592     void)
1593 {
1594     char **env = SK_ENVIRON_TABLE;
1595     char **env_copy = NULL;
1596     const size_t step = 100;
1597     size_t sz = 0;
1598     size_t i;
1599 
1600     /* Add 1 for the terminating NULL */
1601     for (i = 0; env[i]; ++i) {
1602         if (i == sz) {
1603             char **old = env_copy;
1604             env_copy = (char **)realloc(old, (sz + step + 1) * sizeof(char *));
1605             if (NULL == env_copy) {
1606                 old[i] = (char *)NULL;
1607                 skSubcommandFreeEnvironment(old);
1608                 return NULL;
1609             }
1610             sz += step;
1611         }
1612         env_copy[i] = strdup(env[i]);
1613         if (NULL == env_copy[i]) {
1614             skSubcommandFreeEnvironment(env_copy);
1615             return NULL;
1616         }
1617     }
1618     if (env_copy) {
1619         env_copy[i] = (char *)NULL;
1620     } else {
1621         env_copy = (char **)calloc(1, sizeof(char *));
1622     }
1623     return env_copy;
1624 }
1625 #endif  /* SK_COPY_ENVIRONMENT */
1626 
1627 
1628 /* run the command */
1629 static long int
skSubcommandExecuteHelper(const char * cmd_string,char * const cmd_array[])1630 skSubcommandExecuteHelper(
1631     const char         *cmd_string,
1632     char * const        cmd_array[])
1633 {
1634     sigset_t sigs;
1635     pid_t pid;
1636 
1637 #if SK_COPY_ENVIRONMENT
1638     char **env_copy = skSubcommandCopyEnvironment();
1639     if (NULL == env_copy) {
1640         return -1;
1641     }
1642 #endif  /* SK_COPY_ENVIRONMENT */
1643 
1644     /* Parent (original process) forks to create Child 1 */
1645     pid = fork();
1646     if (-1 == pid) {
1647         return -1;
1648     }
1649 
1650     /* Parent reaps Child 1 and returns */
1651     if (0 != pid) {
1652 #if SK_COPY_ENVIRONMENT
1653         skSubcommandFreeEnvironment(env_copy);
1654 #endif
1655         /* Wait for Child 1 to exit. */
1656         while (waitpid(pid, NULL, 0) == -1) {
1657             if (EINTR != errno) {
1658                 return -2;
1659             }
1660         }
1661         return (long)pid;
1662     }
1663 
1664     /* Change our process group, so a server program using this
1665      * library that is waiting for any of its children (by process
1666      * group) won't wait for this child. */
1667     setpgid(0, 0);
1668 
1669     /* Child 1 forks to create Child 2 */
1670     pid = fork();
1671     if (pid == -1) {
1672         skAppPrintSyserror("Child could not fork for to run command");
1673         _exit(EXIT_FAILURE);
1674     }
1675 
1676     /* Child 1 immediately exits, so Parent can stop waiting */
1677     if (pid != 0) {
1678         _exit(EXIT_SUCCESS);
1679     }
1680 
1681     /* Only Child 2 makes it here */
1682 
1683     /* Unmask signals */
1684     sigemptyset(&sigs);
1685     sigprocmask(SIG_SETMASK, &sigs, NULL);
1686 
1687     /* Execute the command */
1688 #if SK_COPY_ENVIRONMENT
1689     if (cmd_string) {
1690         execle("/bin/sh", "sh", "-c", cmd_string, (char*)NULL, env_copy);
1691     } else {
1692         execve(cmd_array[0], cmd_array, env_copy);
1693     }
1694 #else  /* SK_COPY_ENVIRONMENT */
1695     if (cmd_string) {
1696         execl("/bin/sh", "sh", "-c", cmd_string, (char*)NULL);
1697     } else {
1698         execv(cmd_array[0], cmd_array);
1699     }
1700 #endif  /* SK_COPY_ENVIRONMENT */
1701 
1702     /* only get here when an error occurs */
1703     if (cmd_string)  {
1704         skAppPrintSyserror("Error invoking /bin/sh");
1705     } else {
1706         skAppPrintSyserror("Error invoking %s", cmd_array[0]);
1707     }
1708     _exit(EXIT_FAILURE);
1709 }
1710 
1711 
1712 long int
skSubcommandExecute(char * const cmd_array[])1713 skSubcommandExecute(
1714     char * const        cmd_array[])
1715 {
1716     return skSubcommandExecuteHelper(NULL, cmd_array);
1717 }
1718 
1719 
1720 long int
skSubcommandExecuteShell(const char * cmd_string)1721 skSubcommandExecuteShell(
1722     const char         *cmd_string)
1723 {
1724     return skSubcommandExecuteHelper(cmd_string, NULL);
1725 }
1726 
1727 
1728 /*
1729 ** Local Variables:
1730 ** mode:c
1731 ** indent-tabs-mode:nil
1732 ** c-basic-offset:4
1733 ** End:
1734 */
1735