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