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