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