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