1 /*
2  * tvutil.c
3  *
4  * Misc (non-X) utility routines for FXTV.
5  *
6  * (C) 1997 Randall Hopper
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are
10  * met: 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer. 2.
12  * Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
20  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  */
29 
30 /*      ******************** Include Files                ************** */
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <ctype.h>
35 #include <fcntl.h>
36 #include <signal.h>
37 #include <sys/types.h>
38 #if defined(__FreeBSD__)
39 #  include <sys/sysctl.h>
40 #elif defined(__bsdi__) || defined(__NetBSD__) || defined(__OpenBSD__)
41 #  include <sys/param.h>
42 #  include <sys/sysctl.h>
43 #elif defined(linux)
44 #  include <linux/sysctl.h>
45 #endif
46 #include <sys/wait.h>
47 #include <unistd.h>
48 #include "tvdefines.h"
49 #include "tvutil.h"
50 #include "glob.h"
51 
52 /*      ******************** Local defines                ************** */
53 
54 #define CHILD_PREPFAIL_STATUS 0x99
55 
56 /*      ******************** Forward declarations         ************** */
57 /*      ******************** Private variables            ************** */
58 /*      ******************** Function Definitions         ************** */
59 
60 
61 
62 /**@BEGINFUNC**************************************************************
63 
64     Prototype  : void TVUTILOutOfMemory()
65 
66     Purpose    : Convenience rtn for handling out of memory situations.
67 
68     Programmer : 20-Jul-97  Randall Hopper
69 
70     Parameters : None.
71 
72     Returns    : NEVER RETURNS - exits the program
73 
74     Globals    : None.
75 
76  **@ENDFUNC*****************************************************************/
77 
TVUTILOutOfMemory()78 void TVUTILOutOfMemory()
79 {
80     if ( getenv( "FXTV_DEBUG_MEM" ) == NULL ) {
81         fprintf( stderr, "Out of memory\n" );
82         exit(1);
83     }
84     else
85         abort();
86 }
87 
88 /**@BEGINFUNC**************************************************************
89 
90     Prototype  : void CleanupChildFileDesc()
91 
92     Purpose    : Close all file descriptors except stdin/out/err.
93 
94     Programmer : 29-May-97  Randall Hopper
95 
96     Parameters : None.
97 
98     Returns    : None.
99 
100     Globals    : None.
101 
102  **@ENDFUNC*****************************************************************/
103 
CleanupChildFileDesc()104 void CleanupChildFileDesc()
105 {
106     static int Max_files_per_proc = -1;
107 
108 #if defined(__FreeBSD__)
109     int    mib[2] = { CTL_KERN, KERN_MAXFILESPERPROC };
110 #elif defined(linux)
111     int    mib[2] = { CTL_KERN, FOPEN_MAX };
112 #elif defined(__bsdi__)
113     int    mib[2] = { CTL_KERN, KERN_MAXFILES };
114 #elif defined(__NetBSD__)
115     int    mib[2] = { CTL_KERN, OPEN_MAX };
116 #elif defined(__OpenBSD__)
117     int    mib[2] = { CTL_KERN, OPEN_MAX };
118 #endif
119     int    i;
120     size_t len;
121 
122     /*  Close all file descriptors but stdin/out/err  */
123     if ( Max_files_per_proc < 0 ) {
124         len = sizeof( Max_files_per_proc );
125         if ( sysctl( mib, 2, &Max_files_per_proc, &len, NULL, 0 ) < 0 ) {
126             perror( "sysctl() failed" );
127             _exit( CHILD_PREPFAIL_STATUS );        /*  Skip atexit() functs  */
128         }
129     }
130     for ( i = 3; i < Max_files_per_proc; i++ )
131         close( i );
132 }
133 
134 
135 /**@BEGINFUNC**************************************************************
136 
137     Prototype  : void TVUTILCmdStrToArgList(
138                       char    shell_cmd[],
139                       char ***cmd,
140                       char  **argbuf )
141 
142     Purpose    : Given a shell command, does simple shell-like parsing
143                  of the list into an arg string-array suitable for
144                  passing to execvp.
145 
146                  NOTE:  Other than straight blank-space splitting, only
147                  simple single-level quoting ['"] is recognized.  That's all.
148                  I.e. no cool shell expansion of any kind (e.g. variable,
149                  backquote, alias, etc. etc.).
150 
151     Programmer : 20-Jul-97  Randall Hopper
152 
153     Parameters : shell_cmd - I: shell cmd         (e.g. "mpg123 -")
154                  cmd       - O: malloced arg list (e.g. {"mpg123","-"}
155                  argbuf    - O: malloced arg buf (cmd elements point into)
156 
157                  NOTE:  "cmd" and "argbuf" are allocated by this routine and
158                         must be freed by the caller
159 
160     Returns    : None.
161 
162     Globals    : None.
163 
164  **@ENDFUNC*****************************************************************/
165 
TVUTILCmdStrToArgList(char shell_cmd[],char *** cmd,char ** argbuf)166 void TVUTILCmdStrToArgList(
167          char    shell_cmd[],
168          char ***cmd,
169          char  **argbuf )
170 {
171     char      *s  = shell_cmd,
172               *p,
173                quote_char = -1,
174                arg       [ MAXPATHLEN ];
175     TV_INT32   cmd_cnt      = 0,
176                cmd_alloc    = 0,
177                argbuf_len   = 0,
178                argbuf_alloc = 0;
179     TV_BOOL    in_quote,
180                ignore;
181 
182     /*  Get ready  */
183     *cmd    = NULL;
184     *argbuf = NULL;
185 
186     while ( *s != '\0' ) {                      /*  For all args    */
187         while ( isspace( *s ) )                 /*    Skip spaces   */
188             s++;
189         if ( *s == '\0' )
190             continue;
191 
192         in_quote = FALSE;                       /*    Extract an arg  */
193         p        = arg;
194         while ( (in_quote || !isspace(*s)) && (*s != '\0') ) {
195             ignore = FALSE;
196 
197             if (( *s == '\'' ) || ( *s == '\"' ))
198                 if ( !in_quote )
199                     in_quote = TRUE , ignore = TRUE, quote_char = *s;
200                 else if ( quote_char == *s )
201                     in_quote = FALSE, ignore = TRUE;
202 
203             if ( !ignore && ( p-arg < sizeof(arg)-1 ) )
204                 *(p++) = *s;
205             s++;
206         }
207         *p = '\0';
208 
209         if ( in_quote )
210             fprintf( stderr,
211                    "TVUTILCmdStrToArgList: Unbalanced quotes in command\n"
212                    "\tWe're going to pretend we saw a quote at the end.\n" );
213 
214         if ( cmd_cnt >= cmd_alloc ) {           /*    Extend arrays  */
215             cmd_alloc += 40;
216             *cmd = realloc( *cmd, cmd_alloc * sizeof((*cmd)[0]) );
217             if ( *cmd == NULL )
218                 TVUTILOutOfMemory();
219         }
220         if ( argbuf_len + strlen(arg)+1 > argbuf_alloc ) {
221             argbuf_alloc += MAXPATHLEN;
222             *argbuf = realloc( *argbuf, argbuf_alloc * sizeof((*argbuf)[0]) );
223             if ( *argbuf == NULL )
224                 TVUTILOutOfMemory();
225         }
226 
227         p = &(*argbuf)[ argbuf_len ];           /*    Store arg string  */
228         strcpy( p, arg );
229         argbuf_len += strlen(arg)+1;
230 
231         (*cmd)[cmd_cnt++] = p;                  /*    Store arg str pointer */
232     }
233 
234     /*  Tack NULL arg onto end of list  */
235     if ( cmd_cnt >= cmd_alloc ) {
236         cmd_alloc += 1;
237         *cmd = realloc( *cmd, cmd_alloc * sizeof((*cmd)[0]) );
238         if ( *cmd == NULL )
239             TVUTILOutOfMemory();
240     }
241     (*cmd)[cmd_cnt++] = NULL;
242 }
243 
244 
245 
246 /**@BEGINFUNC**************************************************************
247 
248     Prototype  : void TVUTILPipeSetup(
249                       char            *shell_cmd,
250                       char            *shell_cmd2[],
251                       TVUTIL_PIPE_END  end[3],
252                       pid_t           *child_pid )
253 
254     Purpose    : Routines to set up a pipe to and execute a shell command,
255                  and to cleanup after the pipe completes.
256 
257                  The command to execute may be specified via either
258                  shell_cmd or shell_cmd2.  If shell_cmd is used, execl is
259                  used to run "sh -c <shell_cmd>", so redirection, wildcard,
260                  and quoting characters may be used.  If instead shell_cmd2
261                  is used, then the command is executed directly (with no
262                  intervening shell) using execvp.
263 
264                  end[] is a array of three structures representing the source
265                  for the child's stdin and the sinks for the child's stdout
266                  and stderr, respectively
267 
268                  If end[0/1/2].fd == -1, don't mess with the child's stdin/out/
269                  err -- leave stdin/out/err connected to the parents stdin/out/
270                  err
271 
272                  ...But if end[0/1/2].fd != -1, read on:
273 
274                  If end[0/1/2].is_pipe is TRUE, end[0/1/2].fd is wired to a
275                  pipe connected to the child process's stdin/out/err.
276 
277                  If end[0/1/2].is_pipe is FALSE, end[0/1/2].fd is presumed
278                  to be an already-open file descriptor to be wired directly
279                  to the child process's stdin/stdout.
280 
281                  end[0/1].fd_saved is used to store a copy of the old
282                  end[0/1].fd when end[0/1].is_pipe is TRUE (pipe created
283                  and replaces end[0/1].fd.
284 
285                  NOTE:  Only stdout or stderr, not both, may be connected
286                  to a pipe.
287 
288                  NOTE:  If the caller is reconnecting buffered stream(s)
289                  to a child process, they should fflush the file handle(s)
290                  before calling this function.
291 
292     Programmer : 28-May-97  Randall Hopper
293 
294     Parameters : shell_cmd  - I  : cmd to execute; sh -c "<cmd>" is run
295                  shlll_cmd2 - I  : if shell_cmd == NULL, specifies cmd;
296                                    execvp applied directly to <shell_cmd2>
297                  end        - I/O: child stdin/out/err connection info
298                  child_pid  - O  : the child pid
299 
300     Returns    : None.
301 
302     Globals    : None.
303 
304  **@ENDFUNC*****************************************************************/
305 
TVUTILPipeSetup(char * shell_cmd,char * shell_cmd2[],TVUTIL_PIPE_END end[3],pid_t * child_pid)306 void TVUTILPipeSetup( char            *shell_cmd,
307                       char            *shell_cmd2[],
308                       TVUTIL_PIPE_END  end[3],
309                       pid_t           *child_pid )
310 {
311     int   p_fd[2][2],
312           null_fd = -1,
313           debug;
314     pid_t pid;
315 
316     /*  Print command if in debug mode  */
317     debug = (G_debug & DEBUG_SUBPROC) != 0;
318     if ( debug ) {
319         printf( "\nCMD: " );
320         if ( shell_cmd )
321             printf( "%s\n", shell_cmd );
322         else {
323             int i;
324             for ( i = 0; shell_cmd2[i] != NULL; i++ )
325                 printf( "%s ", shell_cmd2[i] );
326             printf( "\n\n" );
327         }
328     }
329 
330     if ((( end[1].fd >= 0 ) && end[1].is_pipe ) &&
331         (( end[2].fd >= 0 ) && end[2].is_pipe )) {
332         fprintf( stderr, "PipeSetup: pipes on both stdout & stderr "
333                          "not supported\n" );
334         exit(1);
335     }
336 
337     if ( child_pid )
338         *child_pid  = -1;
339 
340     end[0].fd_saved = end[1].fd_saved = end[2].fd_saved = -1;
341 
342     /*  Create any needed pipes, first; then fork  */
343     if ((( end[0].fd >= 0 ) && end[0].is_pipe && ( pipe( p_fd[0] ) < 0 )) ||
344         (( end[1].fd >= 0 ) && end[1].is_pipe && ( pipe( p_fd[1] ) < 0 )))
345         { perror( "PipeSetup: pipe failed" ); exit(1); }
346 
347     if ( ( pid = fork()) < 0 )
348         { perror( "PipeSetup: fork failed" ); exit(1); }
349 
350     /*  In child, connect pipes or desired filedescs to stdin/stdout  */
351     if ( pid == 0 ) {
352 
353         /*  If not in debug mode, and stdin/out/err left unbound, bind to  */
354         /*    /dev/null                                                    */
355         if ( !debug && (( end[1].fd < 0 ) || ( end[2].fd < 0 )) )
356             if ( (null_fd = open( "/dev/null", O_WRONLY )) < 0 )
357                 { perror( "PipeSetup: can't open /dev/null" ); exit(1); }
358 
359         /*  Reset sig handlers first  */
360         signal( SIGINT , SIG_DFL );
361         signal( SIGTERM, SIG_DFL );
362         signal( SIGTSTP, SIG_DFL );
363 
364         if ( (( end[0].fd >= 0 ) && end[0].is_pipe &&   /* Pipe connections */
365               ((  dup2( p_fd[0][0], 0 ) < 0 )   ||
366                ( close( p_fd[0][0] )    < 0 )   ||
367                ( close( p_fd[0][1] )    < 0 ))) ||
368 
369              (( end[1].fd >= 0 ) && end[1].is_pipe &&
370               ((  dup2( p_fd[1][1], 1 ) < 0 )   ||
371                ( close( p_fd[1][0] )    < 0 )   ||
372                ( close( p_fd[1][1] )    < 0 ))) ||
373 
374              (( end[2].fd >= 0 ) && end[2].is_pipe &&
375               ((  dup2( p_fd[1][1], 2 ) < 0 )   ||
376                ( close( p_fd[1][0] )    < 0 )   ||
377                ( close( p_fd[1][1] )    < 0 ))) ||
378 
379              (( end[0].fd >= 0 ) && !end[0].is_pipe &&  /* Non-pipe connect */
380               (  dup2( end[0].fd, 0 )   < 0 ))  ||
381 
382              (( end[1].fd >= 0 ) && !end[1].is_pipe &&
383               (  dup2( end[1].fd, 1 )   < 0 ))  ||
384 
385              (( end[2].fd >= 0 ) && !end[2].is_pipe &&
386               (  dup2( end[2].fd, 2 )   < 0 ))  ||
387 
388              (( end[1].fd < 0 ) && ( null_fd >= 0 ) &&  /* /dev/null connect */
389               (  dup2( null_fd, 1 )     < 0 ))   ||
390 
391              (( end[2].fd < 0 ) && ( null_fd >= 0 ) &&
392               (  dup2( null_fd, 2 )     < 0 )) ) {
393 
394             perror( "PipeSetup: child stdin/out/err setup failed" );
395             _exit( CHILD_PREPFAIL_STATUS );       /*  Skip atexit() functs  */
396         }
397         if ( null_fd >= 0 )
398             close( null_fd );
399 
400         CleanupChildFileDesc();
401         if ( shell_cmd != NULL )
402             execl( "/bin/sh", "sh", "-c", shell_cmd, NULL );
403         else
404             execvp( shell_cmd2[0], shell_cmd2 );
405         perror( "PipeSetup: exec failed" );
406         _exit( CHILD_PREPFAIL_STATUS );
407     }
408 
409     /*  In parent, connect pipes to desired file descs, saving old  */
410     if ( (( end[0].fd >= 0 ) && end[0].is_pipe &&
411           (( (end[0].fd_saved = dup( end[0].fd )) < 0 )   ||
412            ( dup2( p_fd[0][1], end[0].fd )        < 0 )   ||
413            ( close( p_fd[0][0] )                  < 0 )   ||
414            ( close( p_fd[0][1] )                  < 0 ))) ||
415 
416          (( end[1].fd >= 0 ) && end[1].is_pipe &&
417           (( (end[1].fd_saved = dup( end[1].fd )) < 0 )   ||
418            ( dup2( p_fd[1][0], end[1].fd )        < 0 )   ||
419            ( close( p_fd[1][0] )                  < 0 )   ||
420            ( close( p_fd[1][1] )                  < 0 ))) ||
421 
422          (( end[2].fd >= 0 ) && end[2].is_pipe &&
423           (( (end[2].fd_saved = dup( end[2].fd )) < 0 )   ||
424            ( dup2( p_fd[1][0], end[2].fd )        < 0 )   ||
425            ( close( p_fd[1][0] )                  < 0 )   ||
426            ( close( p_fd[1][1] )                  < 0 ))) ) {
427 
428         perror( "PipeSetup: parent pipe fd setup failed" );
429         exit(1);
430     }
431     if ( child_pid )
432         *child_pid  = pid;
433 }
434 
435 
436 /*  TVUTILPipeCleanup - Cleanup a child proc & any pipes to it that  */
437 /*    were initiated by TVUTILPipeSetup.                             */
TVUTILPipeCleanup(pid_t child_pid,TVUTIL_PIPE_END end[3],int * ex_status)438 void TVUTILPipeCleanup( pid_t            child_pid,
439                         TVUTIL_PIPE_END  end[3],
440                         int             *ex_status )
441 {
442     int status;
443 
444     if ( (( end[0].fd >= 0 ) && end[0].is_pipe &&
445           ( close( end[0].fd ) < 0 )) ||
446 
447          (( end[1].fd >= 0 ) && end[1].is_pipe &&
448           ( close( end[1].fd ) < 0 )) ||
449 
450          (( end[2].fd >= 0 ) && end[2].is_pipe &&
451           ( close( end[2].fd ) < 0 )) ||
452 
453          ( waitpid( child_pid, &status, NULL ) < 0 )  ||
454 
455          (( end[0].fd >= 0 ) && end[1].is_pipe &&
456           ((  dup2( end[0].fd_saved, end[0].fd ) < 0 )   ||
457            ( close( end[0].fd_saved )            < 0 ))) ||
458 
459          (( end[1].fd >= 0 ) && end[1].is_pipe &&
460           ((  dup2( end[1].fd_saved, end[1].fd ) < 0 )   ||
461            ( close( end[1].fd_saved )            < 0 ))) ||
462 
463          (( end[2].fd >= 0 ) && end[2].is_pipe &&
464           ((  dup2( end[2].fd_saved, end[2].fd ) < 0 )   ||
465            ( close( end[2].fd_saved )            < 0 ))) ) {
466 
467         perror( "PipeCleanup: failed" );
468         exit(1);
469     }
470     if ( ex_status )
471         *ex_status = status;
472 }
473 
474 
475 /*  TVUTILstrupr - Convert a string to upper case  */
TVUTILstrupr(char * str)476 void TVUTILstrupr( char *str )
477 {
478     while ( *str != '\0' )
479         *(str++) = toupper( *str );
480 }
481 
482 /*  TVUTILstrlwr - Convert a string to lower case  */
TVUTILstrlwr(char * str)483 void TVUTILstrlwr( char *str )
484 {
485     while ( *str != '\0' )
486         *(str++) = tolower( *str );
487 }
488 
489 /*  TVUTILStrStrip - Strip selected characters out of a string  */
TVUTILStrStrip(char * str,char * strip_chars,TV_BOOL leading,TV_BOOL imbedded,TV_BOOL trailing)490 char *TVUTILStrStrip(
491           char    *str,
492           char    *strip_chars,
493           TV_BOOL  leading,
494           TV_BOOL  imbedded,
495           TV_BOOL  trailing )
496 {
497     char *src     = str,
498          *src_end,
499          *dest    = str;
500 
501     if ( strip_chars == NULL )
502         strip_chars = " \t\r\n\v\f";
503 
504     if ( leading )
505         src += strspn( src, strip_chars );
506 
507     src_end = src + strlen(src);
508     if ( trailing )
509         while ( src_end > src ) {
510             if ( strchr( strip_chars, *(src_end-1) ) == NULL )
511 
512                 break;
513             src_end--;
514         }
515 
516     while ( src < src_end ) {
517         if ( !imbedded || ( strchr( strip_chars, *src ) == NULL ) )
518             *(dest++) = *src;
519         src++;
520     }
521     *dest = '\0';
522 
523     return( str );
524 }
525 
526