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