]> git.sesse.net Git - vlc/blob - src/interface/intf_cmd.c
Initial revision
[vlc] / src / interface / intf_cmd.c
1 /*******************************************************************************
2  * intf_cmd.c: interface commands parsing and executions functions
3  * (c)1998 VideoLAN
4  *******************************************************************************
5  * This file implements the interface commands execution functions. It is used
6  * by command-line oriented interfaces and scripts. The commands themselves are
7  * implemented in intf_ctrl.
8  *******************************************************************************/
9
10 /*******************************************************************************
11  * Preamble
12  *******************************************************************************/
13 #include <errno.h>
14 #include <pthread.h>
15 #include <netinet/in.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/soundcard.h>
20 #include <sys/uio.h>
21 #include <X11/Xlib.h>
22 #include <X11/extensions/XShm.h>
23
24 #include "config.h"
25 #include "common.h"
26 #include "mtime.h"
27
28 #include "input.h"
29 #include "input_vlan.h"
30
31 #include "audio_output.h"
32
33 #include "video.h"
34 #include "video_output.h"
35
36 #include "xconsole.h"
37 #include "interface.h"
38 #include "intf_msg.h"
39 #include "intf_cmd.h"
40 #include "intf_ctrl.h"
41
42 #include "pgm_data.h"
43
44 /*
45  * Local prototypes
46  */
47 static int  ParseCommandArguments   ( char *psz_argv[INTF_MAX_ARGS], char *psz_cmd );
48 static int  CheckCommandArguments   ( intf_arg_t argv[INTF_MAX_ARGS], int i_argc, 
49                                       char *psz_argv[INTF_MAX_ARGS], char *psz_format );
50 static void ParseFormatString       ( intf_arg_t format[INTF_MAX_ARGS], char *psz_format );
51 static int  ConvertArgument         ( intf_arg_t *p_arg, int i_flags, char *psz_str );
52
53 /*******************************************************************************
54  * intf_ExecCommand: parse and execute a command               
55  *******************************************************************************
56  * This function is called when a command needs to be executed. It parse the
57  * command line, build an argument array, find the command in control commands
58  * array and run the command. It returns the return value of the command, or
59  * EINVAL if no command could be executed. Command line is modified by this 
60  * function.
61  * Note that this function may terminate abruptly the program or signify it's
62  * end to the interface thread.
63  *******************************************************************************/
64 int intf_ExecCommand( char *psz_cmd )
65 {
66     char *          psz_argv[INTF_MAX_ARGS];             /* arguments pointers */
67     intf_arg_t      argv[INTF_MAX_ARGS];                /* converted arguments */
68     int             i_argc;                             /* number of arguments */
69     int             i_index;                           /* multi-purposes index */
70     int             i_return;                          /* command return value */
71
72     intf_DbgMsg("intf debug: command `%s'\n", psz_cmd);
73
74     /* Parse command line (separate arguments). If nothing has been found, 
75      * the function returns without error */
76     i_argc = ParseCommandArguments( psz_argv, psz_cmd );
77     if( !i_argc )
78     {
79         return( 0 );
80     }
81
82     /* Find command. Command is always the first token on the line */
83     for( i_index = 0; 
84          control_command[i_index].psz_name && strcmp( psz_argv[0], control_command[i_index].psz_name ); 
85          i_index++ )
86     {
87         ;
88     }
89     if( !control_command[i_index].psz_name )                /* unknown command */
90     {
91         /* Print error message */
92         intf_IntfMsg( "error: unknown command `%s'. Try `help'", psz_argv[0] );
93         return( INTF_USAGE_ERROR );
94     }
95
96     /* Check arguments validity */
97     if( CheckCommandArguments( argv, i_argc, psz_argv, control_command[i_index].psz_format ) )
98     {
99         /* The given arguments does not match the format string. An error message has 
100          * already been displayed, so only the usage string is printed */
101         intf_IntfMsg( "usage: %s", control_command[i_index].psz_usage );
102         return( INTF_USAGE_ERROR );
103     }
104
105     /* Execute command */    
106     i_return = control_command[i_index].function( i_argc, argv );
107
108     /* Manage special error codes */
109     switch( i_return )
110     {
111     case INTF_FATAL_ERROR:                                      /* fatal error */
112         /* Print message and terminates the interface thread */
113         intf_ErrMsg( "intf fatal: in command `%s'\n", psz_argv[0] );
114         p_program_data->intf_thread.b_die = 1;
115         break;
116
117     case INTF_CRITICAL_ERROR:                                /* critical error */
118         /* Print message, flush messages queue and exit. Note that this
119          * error should be very rare since it does not even try to cancel other
120          * threads... */
121         intf_ErrMsg("intf critical: in command `%s'. Please report this error !\n", psz_argv[0] );
122         intf_FlushMsg();
123         exit( INTF_CRITICAL_ERROR );
124         break;
125
126     case INTF_USAGE_ERROR:                                      /* usage error */
127         /* Print error message and usage */
128         intf_IntfMsg( "usage: %s", control_command[i_index].psz_usage );
129         break;
130     }
131
132     /* Return error code */
133     return( i_return );
134 }
135
136 /*******************************************************************************
137  * intf_ExecScript: parse and execute a command script
138  *******************************************************************************
139  * This function, based on ExecCommand read a file and tries to execute each
140  * of its line as a command. It returns 0 if everything succeeded, a negative
141  * number if the script could not be executed and a positive one if an error
142  * occured during execution.
143  *******************************************************************************/
144 int intf_ExecScript( char *psz_filename )
145 {
146     FILE *  p_file;                                                    /* file */
147     char    psz_line[INTF_MAX_CMD_SIZE];                               /* line */
148     char *  psz_index;                                      /* index in string */
149     int     i_err;                                          /* error indicator */    
150     
151     /* Open file */
152     i_err = 0;    
153     p_file = fopen( psz_filename, "r" );
154     if( p_file == NULL )
155     {
156         intf_ErrMsg("intf error: %s: %s\n", psz_filename, strerror(errno));
157         return( -1 );        
158     }
159     
160     /* For each line: read and execute */    
161     while( fgets( psz_line, INTF_MAX_CMD_SIZE, p_file ) != NULL )
162     {
163         /* If line begins with a '#', it is a comment and shoule be ignored,
164          * else, execute it */
165         if( psz_line[0] != '#' )
166         {            
167             /* The final '\n' needs to be removed before execution */
168             for( psz_index = psz_line; *psz_index && (*psz_index != '\n'); psz_index++ )
169             {                
170                 ;                
171             }
172             if( *psz_index == '\n' )
173             {
174                 *psz_index = '\0';                
175             }            
176
177             /* Execute command */
178             i_err |= intf_ExecCommand( psz_line );    
179         }                
180     }
181     if( !feof( p_file ) )
182     {
183         intf_ErrMsg("intf error: %s: %s\n", psz_filename, strerror(errno));
184         return( -1 );        
185     }
186     
187     /* Close file */
188     fclose( p_file );
189     return( i_err != 0 );
190 }
191
192 /* following functions are local */
193
194 /*******************************************************************************
195  * ParseCommandArguments: isolate arguments in a command line
196  *******************************************************************************
197  * This function modify the original command line, adding '\0' and completes
198  * an array of pointers to beginning of arguments. It return the number of
199  * arguments.
200  *******************************************************************************/
201 static int ParseCommandArguments( char *psz_argv[INTF_MAX_ARGS], char *psz_cmd )
202 {
203     int         i_argc;                                 /* number of arguments */
204     char *      psz_index;                                            /* index */
205     boolean_t   b_block;                         /* block (argument) indicator */
206
207     /* Initialize parser state */
208     b_block = 0;     /* we start outside a block to remove spaces at beginning */
209     i_argc = 0;
210
211     /* Go through command until end has been reached or maximal number of
212      * arguments has been reached */
213     for( psz_index = psz_cmd; *psz_index && (i_argc < INTF_MAX_ARGS); psz_index++ )
214     {
215         /* Inside a block, end of blocks are marked by spaces */
216         if( b_block )
217         {
218             if( *psz_index == ' ' )
219             {
220                 *psz_index = '\0';                 /* mark the end of argument */
221                 b_block = 0;                                 /* exit the block */
222             }
223             
224         }
225         /* Outside a block, beginning of blocks are marked by any character
226          * different from space */
227         else
228         {
229             if( *psz_index != ' ' )
230             {
231                 psz_argv[i_argc++] = psz_index;              /* store argument */
232                 b_block = 1;                                /* enter the block */            
233             }
234         }
235     }
236
237     /* Return number of arguments found */
238     return( i_argc );
239 }
240
241 /*******************************************************************************
242  * CheckCommandArguments: check arguments agains format
243  *******************************************************************************
244  * This function parse each argument and tries to find a match in the format
245  * string. It fills the argv array.
246  * If all arguments have been sucessfuly identified and converted, it returns
247  * 0, else, an error message is issued and non 0 is returned.
248  * Note that no memory is allocated by this function, but that the arguments
249  * can be modified.
250  *******************************************************************************/
251 static int CheckCommandArguments( intf_arg_t argv[INTF_MAX_ARGS], int i_argc, 
252                                   char *psz_argv[INTF_MAX_ARGS], char *psz_format )
253 {
254     intf_arg_t  format[INTF_MAX_ARGS];             /* parsed format indicators */
255     int         i_arg;                                       /* argument index */
256     int         i_format;                                      /* format index */    
257     char *      psz_index;                                     /* string index */
258     char *      psz_cmp_index;                     /* string comparaison index */
259     int         i_index;                                      /* generic index */    
260     boolean_t   b_found;                              /* `argument found' flag */
261
262
263     /* Build format array */
264     ParseFormatString( format, psz_format );
265
266     /* Initialize parser: i_format must be the first non named formatter */
267     for( i_format = 0; ( i_format < INTF_MAX_ARGS ) 
268              && (format[i_format].i_flags & INTF_NAMED_ARG);
269          i_format++ )
270     {
271         ;
272     }
273
274     /* Scan all arguments */
275     for( i_arg = 1; i_arg < i_argc; i_arg++ )
276     {
277         b_found = 0;
278
279         /* Test if argument can be taken as a named argument: try to find a
280          * '=' in the string */
281         for( psz_index = psz_argv[i_arg]; *psz_index && ( *psz_index != '=' ); psz_index++ ) 
282         {
283             ;
284         }
285         if( *psz_index == '=' )                                   /* '=' found */
286         {
287             /* Browse all named arguments to check if there is one matching */
288             for( i_index = 0; (i_index < INTF_MAX_ARGS) 
289                      && ( format[i_index].i_flags & INTF_NAMED_ARG )
290                      && !b_found;
291                  i_index++ )
292             {
293                 /* Current format string is named... compare start of two 
294                  * names. A local inline ntation of a strcmp is used since
295                  * string isn't ended by '\0' but by '=' */
296                 for( psz_index = psz_argv[i_arg], psz_cmp_index = format[i_index].ps_name; 
297                      (*psz_index == *psz_cmp_index) && (*psz_index != '=') && (*psz_cmp_index != '=');
298                      psz_index++, psz_cmp_index++ )
299                 {
300                     ;
301                 }
302                 if( *psz_index == *psz_cmp_index )          /* the names match */
303                 {
304                     /* The argument is a named argument which name match the
305                      * named argument i_index. To be valid, the argument should
306                      * not have been already encountered and the type must 
307                      * match. Before going further, the '=' is replaced by
308                      * a '\0'. */
309                     *psz_index = '\0';
310
311                     /* Check unicity. If the argument has already been encountered,
312                      * print an error message and return. */
313                     if( format[i_index].i_flags & INTF_PRESENT_ARG )/* present */
314                     {
315                         intf_IntfMsg("error: `%s' has already been encountered", psz_argv[i_arg] );
316                         return( 1 );
317                     }
318
319                      /* Register argument and prepare exit */
320                     b_found = 1;
321                     format[i_index].i_flags |= INTF_PRESENT_ARG;
322                     argv[i_arg].i_flags = INTF_NAMED_ARG;
323                     argv[i_arg].i_index = i_index;
324                     argv[i_arg].ps_name = psz_argv[i_arg];                    
325
326                     /* Check type and store value */
327                     psz_index++;
328                     if( ConvertArgument( &argv[i_arg], format[i_index].i_flags, psz_index ) )
329                     {
330                         /* An error occured during conversion */
331                         intf_IntfMsg( "error: invalid type for `%s'", psz_index );
332                     }
333                 }
334             }
335         }  
336
337         /* If argument is not a named argument, the format string will
338          * be browsed starting from last position until the argument is
339          * found or an error occurs. */
340         if( !b_found )
341         {
342             /* Reset type indicator */
343             argv[i_arg].i_flags = 0;
344
345             /* If argument is not a named argument, the format string will
346              * be browsed starting from last position until the argument is
347              * found, an error occurs or the last format argument is 
348              * reached */
349             while( !b_found && (i_format < INTF_MAX_ARGS) && format[i_format].i_flags )
350             {
351                 /* Try to convert argument */
352                 if( !ConvertArgument( &argv[i_arg], format[i_format].i_flags, psz_argv[i_arg] ) ) 
353                 {
354                     /* Matching format has been found */
355                     b_found = 1;
356                     format[i_format].i_flags |= INTF_PRESENT_ARG;
357                     argv[i_arg].i_index = i_format;
358
359                     /* If argument is repeatable, dot not increase format counter */
360                     if( !(format[i_format].i_flags & INTF_REP_ARG) )
361                     {
362                         i_format++;
363                     }
364                 }
365                 else
366                 {
367                     /* Argument does not match format. This can be an error, or
368                      * just a missing optionnal parameter, or the end of a 
369                      * repeated argument */                       
370                     if( (format[i_format].i_flags & INTF_OPT_ARG) 
371                         || (format[i_format].i_flags & INTF_PRESENT_ARG) )
372                     {
373                         /* This is not an error */
374                         i_format++;
375                     }
376                     else
377                     {
378                         /* The present format argument is mandatory and does
379                          * not match the argument */
380                         intf_IntfMsg("error: missing argument before `%s'", psz_argv[i_arg] );
381                         return( 1 );
382                     }
383                 }
384             }
385         }
386
387         /* If argument is not a named argument and hasn't been found in
388          * format string, then it is an usage error and the function can
389          * return */
390         if( !b_found )
391         {
392             intf_IntfMsg("error: `%s' does not match any argument", psz_argv[i_arg] );
393             return( 1 );
394         }
395
396         intf_DbgMsg("intf debug: argument flags=0x%x (index=%d) name=%s str=%s int=%d float=%f\n",
397                     argv[i_arg].i_flags,
398                     argv[i_arg].i_index,
399                     (argv[i_arg].i_flags & INTF_NAMED_ARG) ? argv[i_arg].ps_name : "NA",
400                     (argv[i_arg].i_flags & INTF_STR_ARG) ? argv[i_arg].psz_str : "NA",
401                     (argv[i_arg].i_flags & INTF_INT_ARG) ? argv[i_arg].i_num : 0,
402                     (argv[i_arg].i_flags & INTF_FLOAT_ARG) ? argv[i_arg].f_num : 0);
403     }
404
405     /* Parse all remaining format specifier to verify they are all optionnal */
406     for( ;  (i_format < INTF_MAX_ARGS) && format[i_format].i_flags ; i_format++ )
407     {
408         if( !(( format[i_format].i_flags & INTF_OPT_ARG)
409               || ( format[i_format].i_flags & INTF_PRESENT_ARG)) )
410         {
411             /* Format has not been used and is neither optionnal nor multiple
412              * and present */
413             intf_IntfMsg("error: missing argument(s)\n");
414             return( 1 );
415         }
416     }
417     
418     /* If an error occured, the function already exited, so if this point is
419      * reached, everything is fine */
420     return( 0 );
421 }
422
423 /*******************************************************************************
424  * ConvertArgument: try to convert an argument to a given type
425  *******************************************************************************
426  * This function tries to convert the string argument given in psz_str to
427  * a type specified in i_flags. It updates p_arg and returns O on success,
428  * or 1 on error. No error message is issued.
429  *******************************************************************************/
430 static int ConvertArgument( intf_arg_t *p_arg, int i_flags, char *psz_str )
431 {
432     char *psz_end;                     /* end pointer for conversion functions */
433
434     if( i_flags & INTF_STR_ARG )                                     /* string */
435     {
436         /* A conversion from a string to a string will always succeed... */
437         p_arg->psz_str = psz_str;
438         p_arg->i_flags |= INTF_STR_ARG;
439     }
440     else if( i_flags & INTF_INT_ARG )                               /* integer */
441     {        
442         p_arg->i_num = strtol( psz_str, &psz_end, 0 );       /* convert string */
443         /* If the conversion failed, return 1 and do not modify argument
444          * flags. Else, add 'int' flag and continue. */
445         if( !*psz_str || *psz_end )
446         {
447             return( 1 );
448         }
449         p_arg->i_flags |= INTF_INT_ARG;
450     }
451     else if( i_flags & INTF_FLOAT_ARG )                               /* float */
452     {
453         p_arg->f_num = strtod( psz_str, &psz_end );          /* convert string */
454         /* If the conversion failed, return 1 and do not modify argument
455          * flags. Else, add 'float' flag and continue. */
456         if( !*psz_str || *psz_end )
457         {
458             return( 1 );
459         }
460         p_arg->i_flags |= INTF_FLOAT_ARG;
461     }
462 #ifdef DEBUG
463     else                                      /* error: missing type specifier */
464     {
465         intf_ErrMsg("intf error: missing type specifier for `%s' (0x%x)\n", psz_str, i_flags);
466         return( 1 );
467     }
468 #endif
469
470     return( 0 );
471 }
472
473 /*******************************************************************************
474  * ParseFormatString: parse a format string                              (ok ?)
475  *******************************************************************************
476  * This function read a format string, as specified in the control_command
477  * array, and fill a format array, to allow easier argument identification.
478  * Note that no memory is allocated by this function, but that, in a named
479  * argument, the name field does not end with a '\0' but with an '='.
480  * See command.h for format string specifications.
481  * Note that this function is designed to be efficient, not to check everything
482  * in a format string, which should be entered by a developper and therefore
483  * should be correct (TRUST !).
484  *******************************************************************************/
485 static void ParseFormatString( intf_arg_t format[INTF_MAX_ARGS], char *psz_format )
486 {
487     char *  psz_index;                                  /* format string index */
488     char *  psz_start;                                /* argument format start */
489     char *  psz_item;                                            /* item index */
490     int     i_index;                                           /* format index */
491
492     /* Initialize parser */
493     i_index = 0;
494     psz_start = psz_format;      
495
496     /* Reset first format indicator */
497     format[ 0 ].i_flags = 0;
498
499     /* Parse format string */
500     for( psz_index = psz_format; *psz_index && (i_index < INTF_MAX_ARGS) ; psz_index++ )
501     {
502         /* A space is always an item terminator */
503         if( *psz_index == ' ' )
504         {        
505             /* Parse format item. Items are parsed from end to beginning or to
506              * first '=' */
507             for( psz_item = psz_index - 1; 
508                  (psz_item >= psz_start) && !( format[i_index].i_flags & INTF_NAMED_ARG); 
509                  psz_item-- )
510             {
511                 switch( *psz_item )
512                 {
513                 case 's':                                            /* string */
514                     format[i_index].i_flags |= INTF_STR_ARG;
515                     break;
516                 case 'i':                                           /* integer */
517                     format[i_index].i_flags |= INTF_INT_ARG;
518                     break;
519                 case 'f':                                             /* float */
520                     format[i_index].i_flags |= INTF_FLOAT_ARG;
521                     break;
522                 case '*':                                   /* can be repeated */
523                     format[i_index].i_flags |= INTF_REP_ARG;
524                     break;
525                 case '?':                                /* optionnal argument */
526                     format[i_index].i_flags |= INTF_OPT_ARG;
527                     break;
528                 case '=':                                     /* name argument */
529                     format[i_index].i_flags |= INTF_NAMED_ARG;
530                     format[i_index].ps_name = psz_start;    
531                     break;
532 #ifdef DEBUG
533                 default:  /* error which should never happen: incorrect format */
534                     intf_DbgMsg("intf error: incorrect format string `%s'\n", psz_format);
535                     break;
536 #endif
537                 }
538             }
539
540             /* Mark next item start, increase items counter and reset next 
541              * format indicator, if it wasn't the last one. */
542             i_index++;
543             psz_start = psz_index + 1;    
544             if( i_index != INTF_MAX_ARGS )         /* end of array not reached */
545             {
546                 format[ i_index ].i_flags = 0;
547             }
548         }
549     }
550 }