/* * tvutil.c * * Misc (non-X) utility routines for FXTV. * * (C) 1997 Randall Hopper * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. 2. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* ******************** Include Files ************** */ #include #include #include #include #include #include #if defined(__FreeBSD__) # include #elif defined(__bsdi__) || defined(__NetBSD__) || defined(__OpenBSD__) # include # include #elif defined(linux) # include #endif #include #include #include "tvdefines.h" #include "tvutil.h" #include "glob.h" /* ******************** Local defines ************** */ #define CHILD_PREPFAIL_STATUS 0x99 /* ******************** Forward declarations ************** */ /* ******************** Private variables ************** */ /* ******************** Function Definitions ************** */ /**@BEGINFUNC************************************************************** Prototype : void TVUTILOutOfMemory() Purpose : Convenience rtn for handling out of memory situations. Programmer : 20-Jul-97 Randall Hopper Parameters : None. Returns : NEVER RETURNS - exits the program Globals : None. **@ENDFUNC*****************************************************************/ void TVUTILOutOfMemory() { if ( getenv( "FXTV_DEBUG_MEM" ) == NULL ) { fprintf( stderr, "Out of memory\n" ); exit(1); } else abort(); } /**@BEGINFUNC************************************************************** Prototype : void CleanupChildFileDesc() Purpose : Close all file descriptors except stdin/out/err. Programmer : 29-May-97 Randall Hopper Parameters : None. Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ void CleanupChildFileDesc() { static int Max_files_per_proc = -1; #if defined(__FreeBSD__) int mib[2] = { CTL_KERN, KERN_MAXFILESPERPROC }; #elif defined(linux) int mib[2] = { CTL_KERN, FOPEN_MAX }; #elif defined(__bsdi__) int mib[2] = { CTL_KERN, KERN_MAXFILES }; #elif defined(__NetBSD__) int mib[2] = { CTL_KERN, OPEN_MAX }; #elif defined(__OpenBSD__) int mib[2] = { CTL_KERN, OPEN_MAX }; #endif int i; size_t len; /* Close all file descriptors but stdin/out/err */ if ( Max_files_per_proc < 0 ) { len = sizeof( Max_files_per_proc ); if ( sysctl( mib, 2, &Max_files_per_proc, &len, NULL, 0 ) < 0 ) { perror( "sysctl() failed" ); _exit( CHILD_PREPFAIL_STATUS ); /* Skip atexit() functs */ } } for ( i = 3; i < Max_files_per_proc; i++ ) close( i ); } /**@BEGINFUNC************************************************************** Prototype : void TVUTILCmdStrToArgList( char shell_cmd[], char ***cmd, char **argbuf ) Purpose : Given a shell command, does simple shell-like parsing of the list into an arg string-array suitable for passing to execvp. NOTE: Other than straight blank-space splitting, only simple single-level quoting ['"] is recognized. That's all. I.e. no cool shell expansion of any kind (e.g. variable, backquote, alias, etc. etc.). Programmer : 20-Jul-97 Randall Hopper Parameters : shell_cmd - I: shell cmd (e.g. "mpg123 -") cmd - O: malloced arg list (e.g. {"mpg123","-"} argbuf - O: malloced arg buf (cmd elements point into) NOTE: "cmd" and "argbuf" are allocated by this routine and must be freed by the caller Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ void TVUTILCmdStrToArgList( char shell_cmd[], char ***cmd, char **argbuf ) { char *s = shell_cmd, *p, quote_char = -1, arg [ MAXPATHLEN ]; TV_INT32 cmd_cnt = 0, cmd_alloc = 0, argbuf_len = 0, argbuf_alloc = 0; TV_BOOL in_quote, ignore; /* Get ready */ *cmd = NULL; *argbuf = NULL; while ( *s != '\0' ) { /* For all args */ while ( isspace( *s ) ) /* Skip spaces */ s++; if ( *s == '\0' ) continue; in_quote = FALSE; /* Extract an arg */ p = arg; while ( (in_quote || !isspace(*s)) && (*s != '\0') ) { ignore = FALSE; if (( *s == '\'' ) || ( *s == '\"' )) if ( !in_quote ) in_quote = TRUE , ignore = TRUE, quote_char = *s; else if ( quote_char == *s ) in_quote = FALSE, ignore = TRUE; if ( !ignore && ( p-arg < sizeof(arg)-1 ) ) *(p++) = *s; s++; } *p = '\0'; if ( in_quote ) fprintf( stderr, "TVUTILCmdStrToArgList: Unbalanced quotes in command\n" "\tWe're going to pretend we saw a quote at the end.\n" ); if ( cmd_cnt >= cmd_alloc ) { /* Extend arrays */ cmd_alloc += 40; *cmd = realloc( *cmd, cmd_alloc * sizeof((*cmd)[0]) ); if ( *cmd == NULL ) TVUTILOutOfMemory(); } if ( argbuf_len + strlen(arg)+1 > argbuf_alloc ) { argbuf_alloc += MAXPATHLEN; *argbuf = realloc( *argbuf, argbuf_alloc * sizeof((*argbuf)[0]) ); if ( *argbuf == NULL ) TVUTILOutOfMemory(); } p = &(*argbuf)[ argbuf_len ]; /* Store arg string */ strcpy( p, arg ); argbuf_len += strlen(arg)+1; (*cmd)[cmd_cnt++] = p; /* Store arg str pointer */ } /* Tack NULL arg onto end of list */ if ( cmd_cnt >= cmd_alloc ) { cmd_alloc += 1; *cmd = realloc( *cmd, cmd_alloc * sizeof((*cmd)[0]) ); if ( *cmd == NULL ) TVUTILOutOfMemory(); } (*cmd)[cmd_cnt++] = NULL; } /**@BEGINFUNC************************************************************** Prototype : void TVUTILPipeSetup( char *shell_cmd, char *shell_cmd2[], TVUTIL_PIPE_END end[3], pid_t *child_pid ) Purpose : Routines to set up a pipe to and execute a shell command, and to cleanup after the pipe completes. The command to execute may be specified via either shell_cmd or shell_cmd2. If shell_cmd is used, execl is used to run "sh -c ", so redirection, wildcard, and quoting characters may be used. If instead shell_cmd2 is used, then the command is executed directly (with no intervening shell) using execvp. end[] is a array of three structures representing the source for the child's stdin and the sinks for the child's stdout and stderr, respectively If end[0/1/2].fd == -1, don't mess with the child's stdin/out/ err -- leave stdin/out/err connected to the parents stdin/out/ err ...But if end[0/1/2].fd != -1, read on: If end[0/1/2].is_pipe is TRUE, end[0/1/2].fd is wired to a pipe connected to the child process's stdin/out/err. If end[0/1/2].is_pipe is FALSE, end[0/1/2].fd is presumed to be an already-open file descriptor to be wired directly to the child process's stdin/stdout. end[0/1].fd_saved is used to store a copy of the old end[0/1].fd when end[0/1].is_pipe is TRUE (pipe created and replaces end[0/1].fd. NOTE: Only stdout or stderr, not both, may be connected to a pipe. NOTE: If the caller is reconnecting buffered stream(s) to a child process, they should fflush the file handle(s) before calling this function. Programmer : 28-May-97 Randall Hopper Parameters : shell_cmd - I : cmd to execute; sh -c "" is run shlll_cmd2 - I : if shell_cmd == NULL, specifies cmd; execvp applied directly to end - I/O: child stdin/out/err connection info child_pid - O : the child pid Returns : None. Globals : None. **@ENDFUNC*****************************************************************/ void TVUTILPipeSetup( char *shell_cmd, char *shell_cmd2[], TVUTIL_PIPE_END end[3], pid_t *child_pid ) { int p_fd[2][2], null_fd = -1, debug; pid_t pid; /* Print command if in debug mode */ debug = (G_debug & DEBUG_SUBPROC) != 0; if ( debug ) { printf( "\nCMD: " ); if ( shell_cmd ) printf( "%s\n", shell_cmd ); else { int i; for ( i = 0; shell_cmd2[i] != NULL; i++ ) printf( "%s ", shell_cmd2[i] ); printf( "\n\n" ); } } if ((( end[1].fd >= 0 ) && end[1].is_pipe ) && (( end[2].fd >= 0 ) && end[2].is_pipe )) { fprintf( stderr, "PipeSetup: pipes on both stdout & stderr " "not supported\n" ); exit(1); } if ( child_pid ) *child_pid = -1; end[0].fd_saved = end[1].fd_saved = end[2].fd_saved = -1; /* Create any needed pipes, first; then fork */ if ((( end[0].fd >= 0 ) && end[0].is_pipe && ( pipe( p_fd[0] ) < 0 )) || (( end[1].fd >= 0 ) && end[1].is_pipe && ( pipe( p_fd[1] ) < 0 ))) { perror( "PipeSetup: pipe failed" ); exit(1); } if ( ( pid = fork()) < 0 ) { perror( "PipeSetup: fork failed" ); exit(1); } /* In child, connect pipes or desired filedescs to stdin/stdout */ if ( pid == 0 ) { /* If not in debug mode, and stdin/out/err left unbound, bind to */ /* /dev/null */ if ( !debug && (( end[1].fd < 0 ) || ( end[2].fd < 0 )) ) if ( (null_fd = open( "/dev/null", O_WRONLY )) < 0 ) { perror( "PipeSetup: can't open /dev/null" ); exit(1); } /* Reset sig handlers first */ signal( SIGINT , SIG_DFL ); signal( SIGTERM, SIG_DFL ); signal( SIGTSTP, SIG_DFL ); if ( (( end[0].fd >= 0 ) && end[0].is_pipe && /* Pipe connections */ (( dup2( p_fd[0][0], 0 ) < 0 ) || ( close( p_fd[0][0] ) < 0 ) || ( close( p_fd[0][1] ) < 0 ))) || (( end[1].fd >= 0 ) && end[1].is_pipe && (( dup2( p_fd[1][1], 1 ) < 0 ) || ( close( p_fd[1][0] ) < 0 ) || ( close( p_fd[1][1] ) < 0 ))) || (( end[2].fd >= 0 ) && end[2].is_pipe && (( dup2( p_fd[1][1], 2 ) < 0 ) || ( close( p_fd[1][0] ) < 0 ) || ( close( p_fd[1][1] ) < 0 ))) || (( end[0].fd >= 0 ) && !end[0].is_pipe && /* Non-pipe connect */ ( dup2( end[0].fd, 0 ) < 0 )) || (( end[1].fd >= 0 ) && !end[1].is_pipe && ( dup2( end[1].fd, 1 ) < 0 )) || (( end[2].fd >= 0 ) && !end[2].is_pipe && ( dup2( end[2].fd, 2 ) < 0 )) || (( end[1].fd < 0 ) && ( null_fd >= 0 ) && /* /dev/null connect */ ( dup2( null_fd, 1 ) < 0 )) || (( end[2].fd < 0 ) && ( null_fd >= 0 ) && ( dup2( null_fd, 2 ) < 0 )) ) { perror( "PipeSetup: child stdin/out/err setup failed" ); _exit( CHILD_PREPFAIL_STATUS ); /* Skip atexit() functs */ } if ( null_fd >= 0 ) close( null_fd ); CleanupChildFileDesc(); if ( shell_cmd != NULL ) execl( "/bin/sh", "sh", "-c", shell_cmd, NULL ); else execvp( shell_cmd2[0], shell_cmd2 ); perror( "PipeSetup: exec failed" ); _exit( CHILD_PREPFAIL_STATUS ); } /* In parent, connect pipes to desired file descs, saving old */ if ( (( end[0].fd >= 0 ) && end[0].is_pipe && (( (end[0].fd_saved = dup( end[0].fd )) < 0 ) || ( dup2( p_fd[0][1], end[0].fd ) < 0 ) || ( close( p_fd[0][0] ) < 0 ) || ( close( p_fd[0][1] ) < 0 ))) || (( end[1].fd >= 0 ) && end[1].is_pipe && (( (end[1].fd_saved = dup( end[1].fd )) < 0 ) || ( dup2( p_fd[1][0], end[1].fd ) < 0 ) || ( close( p_fd[1][0] ) < 0 ) || ( close( p_fd[1][1] ) < 0 ))) || (( end[2].fd >= 0 ) && end[2].is_pipe && (( (end[2].fd_saved = dup( end[2].fd )) < 0 ) || ( dup2( p_fd[1][0], end[2].fd ) < 0 ) || ( close( p_fd[1][0] ) < 0 ) || ( close( p_fd[1][1] ) < 0 ))) ) { perror( "PipeSetup: parent pipe fd setup failed" ); exit(1); } if ( child_pid ) *child_pid = pid; } /* TVUTILPipeCleanup - Cleanup a child proc & any pipes to it that */ /* were initiated by TVUTILPipeSetup. */ void TVUTILPipeCleanup( pid_t child_pid, TVUTIL_PIPE_END end[3], int *ex_status ) { int status; if ( (( end[0].fd >= 0 ) && end[0].is_pipe && ( close( end[0].fd ) < 0 )) || (( end[1].fd >= 0 ) && end[1].is_pipe && ( close( end[1].fd ) < 0 )) || (( end[2].fd >= 0 ) && end[2].is_pipe && ( close( end[2].fd ) < 0 )) || ( waitpid( child_pid, &status, NULL ) < 0 ) || (( end[0].fd >= 0 ) && end[1].is_pipe && (( dup2( end[0].fd_saved, end[0].fd ) < 0 ) || ( close( end[0].fd_saved ) < 0 ))) || (( end[1].fd >= 0 ) && end[1].is_pipe && (( dup2( end[1].fd_saved, end[1].fd ) < 0 ) || ( close( end[1].fd_saved ) < 0 ))) || (( end[2].fd >= 0 ) && end[2].is_pipe && (( dup2( end[2].fd_saved, end[2].fd ) < 0 ) || ( close( end[2].fd_saved ) < 0 ))) ) { perror( "PipeCleanup: failed" ); exit(1); } if ( ex_status ) *ex_status = status; } /* TVUTILstrupr - Convert a string to upper case */ void TVUTILstrupr( char *str ) { while ( *str != '\0' ) *(str++) = toupper( *str ); } /* TVUTILstrlwr - Convert a string to lower case */ void TVUTILstrlwr( char *str ) { while ( *str != '\0' ) *(str++) = tolower( *str ); } /* TVUTILStrStrip - Strip selected characters out of a string */ char *TVUTILStrStrip( char *str, char *strip_chars, TV_BOOL leading, TV_BOOL imbedded, TV_BOOL trailing ) { char *src = str, *src_end, *dest = str; if ( strip_chars == NULL ) strip_chars = " \t\r\n\v\f"; if ( leading ) src += strspn( src, strip_chars ); src_end = src + strlen(src); if ( trailing ) while ( src_end > src ) { if ( strchr( strip_chars, *(src_end-1) ) == NULL ) break; src_end--; } while ( src < src_end ) { if ( !imbedded || ( strchr( strip_chars, *src ) == NULL ) ) *(dest++) = *src; src++; } *dest = '\0'; return( str ); }