/* * command line and config file parser * by Szabolcs Berecz <szabi@inf.elte.hu> * (C) 2001 * * subconfig support by alex */ //#define DEBUG #include <stdlib.h> #include <stdio.h> #include <ctype.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <errno.h> #include <math.h> #include "config.h" #ifndef NEW_CONFIG #ifdef USE_SETLOCALE #include <locale.h> #endif #include "mp_msg.h" #define COMMAND_LINE 0 #define CONFIG_FILE 1 #define LIST_SEPARATOR ',' #define CONFIG_GLOBAL (1<<0) #define CONFIG_RUNNING (1<<1) #define SET_GLOBAL(c) (c->flags |= CONFIG_GLOBAL) #ifdef GLOBAL_OPTIONS_ONLY #define UNSET_GLOBAL(c) #else #define UNSET_GLOBAL(c) (c->flags &= (!CONFIG_GLOBAL)) #endif #define IS_GLOBAL(c) (c->flags & CONFIG_GLOBAL) #define SET_RUNNING(c) (c->flags |= CONFIG_RUNNING) #define IS_RUNNING(c) (c->flags & CONFIG_RUNNING) #define MAX_RECURSION_DEPTH 8 #ifdef MP_DEBUG #include <assert.h> #endif #include "cfgparser.h" #include "playtree.h" static void m_config_list_options(m_config_t *config); static void m_config_error(int err,char* opt,char* val); static void m_config_save_option(m_config_t* config, config_t* conf,char* opt, char *param) { config_save_t* save; int sl=0; #ifdef MP_DEBUG assert(config != NULL); assert(config->cs_level >= 0); assert(conf != NULL); assert(opt != NULL); assert( ! (conf->flags & CONF_NOSAVE)); #endif switch(conf->type) { case CONF_TYPE_PRINT : case CONF_TYPE_SUBCONFIG : return; default : ; } mp_msg(MSGT_CFGPARSER, MSGL_DBG2,"Saving option %s\n",opt); save = config->config_stack[config->cs_level]; if(save) { for(sl = 0; save[sl].opt != NULL; sl++){ // Check to not save the same arg two times if(save[sl].opt == conf && (save[sl].opt_name == NULL || strcasecmp(save[sl].opt_name,opt) == 0)) break; } if(save[sl].opt) return; } save = (config_save_t*)realloc(save,(sl+2)*sizeof(config_save_t)); if(save == NULL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Can't allocate %d bytes of memory : %s\n",(sl+2)*sizeof(config_save_t),strerror(errno)); return; } memset(&save[sl],0,2*sizeof(config_save_t)); save[sl].opt = conf; switch(conf->type) { case CONF_TYPE_FLAG : case CONF_TYPE_INT : save[sl].param.as_int = *((int*)conf->p); break; case CONF_TYPE_FLOAT : save[sl].param.as_float = *((float*)conf->p); break; case CONF_TYPE_STRING : save[sl].param.as_pointer = *((char**)conf->p); break; case CONF_TYPE_FUNC_FULL : if(strcasecmp(conf->name,opt) != 0) save->opt_name = strdup(opt); case CONF_TYPE_FUNC_PARAM : if(param) save->param.as_pointer = strdup(param); case CONF_TYPE_FUNC : break; case CONF_TYPE_STRING_LIST : save[sl].param.as_pointer = *((char***)conf->p); break; case CONF_TYPE_POSITION : save[sl].param.as_off_t = *((off_t*)conf->p); break; default : mp_msg(MSGT_CFGPARSER,MSGL_ERR,"Should never append in m_config_save_option : conf->type=%d\n",conf->type); } config->config_stack[config->cs_level] = save; } static int m_config_revert_option(m_config_t* config, config_save_t* save) { char* arg = NULL; config_save_t* iter=NULL; int i=-1; #ifdef MP_DEBUG assert(config != NULL); assert(config->cs_level >= 0); assert(save != NULL); #endif arg = save->opt_name ? save->opt_name : save->opt->name; mp_msg(MSGT_CFGPARSER, MSGL_DBG2,"Reverting option %s\n",arg); if(save->opt->default_func) save->opt->default_func(save->opt,arg); switch(save->opt->type) { case CONF_TYPE_FLAG : case CONF_TYPE_INT : *((int*)save->opt->p) = save->param.as_int; break; case CONF_TYPE_FLOAT : *((float*)save->opt->p) = save->param.as_float; break; case CONF_TYPE_STRING : *((char**)save->opt->p) = save->param.as_pointer; break; case CONF_TYPE_STRING_LIST : *((char***)save->opt->p) = save->param.as_pointer; break; case CONF_TYPE_FUNC_PARAM : case CONF_TYPE_FUNC_FULL : case CONF_TYPE_FUNC : if(config->cs_level > 0) { for(i = config->cs_level - 1 ; i >= 0 ; i--){ if(config->config_stack[i] == NULL) continue; for(iter = config->config_stack[i]; iter != NULL && iter->opt != NULL ; iter++) { if(iter->opt == save->opt && ((save->param.as_pointer == NULL || iter->param.as_pointer == NULL) || strcasecmp(save->param.as_pointer,iter->param.as_pointer) == 0) && (save->opt_name == NULL || (iter->opt_name && strcasecmp(save->opt_name,iter->opt_name)))) break; } } } free(save->param.as_pointer); if(save->opt_name) free(save->opt_name); save->opt_name = save->param.as_pointer = NULL; if(i < 0) break; arg = iter->opt_name ? iter->opt_name : iter->opt->name; switch(iter->opt->type) { case CONF_TYPE_FUNC : if ((((cfg_func_t) iter->opt->p)(iter->opt)) < 0) return -1; break; case CONF_TYPE_FUNC_PARAM : if (iter->param.as_pointer == NULL) { mp_msg(MSGT_CFGPARSER,MSGL_ERR,"We lost param for option %s?\n",iter->opt->name); return -1; } if ((((cfg_func_param_t) iter->opt->p)(iter->opt, (char*)iter->param.as_pointer)) < 0) return -1; break; case CONF_TYPE_FUNC_FULL : if (iter->param.as_pointer != NULL && ((char*)iter->param.as_pointer)[0]=='-'){ if( ((cfg_func_arg_param_t) iter->opt->p)(iter->opt, arg, NULL) < 0) return -1; }else { if (((cfg_func_arg_param_t) save->opt->p)(iter->opt, arg, (char*)iter->param.as_pointer) < 0) return -1; } break; } break; case CONF_TYPE_POSITION : *((off_t*)save->opt->p) = save->param.as_off_t; break; default : mp_msg(MSGT_CFGPARSER,MSGL_WARN,"Why do we reverse this : name=%s type=%d ?\n",save->opt->name,save->opt->type); } return 1; } void m_config_push(m_config_t* config) { #ifdef MP_DEBUG assert(config != NULL); assert(config->cs_level >= 0); #endif config->cs_level++; config->config_stack = (config_save_t**)realloc(config->config_stack ,sizeof(config_save_t*)*(config->cs_level+1)); if(config->config_stack == NULL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Can't allocate %d bytes of memory : %s\n",sizeof(config_save_t*)*(config->cs_level+1),strerror(errno)); config->cs_level = -1; return; } config->config_stack[config->cs_level] = NULL; mp_msg(MSGT_CFGPARSER, MSGL_DBG2,"Config pushed level=%d\n",config->cs_level); } int m_config_pop(m_config_t* config) { int i,ret= 1; config_save_t* cs; #ifdef MP_DEBUG assert(config != NULL); //assert(config->cs_level > 0); #endif if(config->config_stack[config->cs_level] != NULL) { cs = config->config_stack[config->cs_level]; for(i=0; cs[i].opt != NULL ; i++ ) { if (m_config_revert_option(config,&cs[i]) < 0) ret = -1; } free(config->config_stack[config->cs_level]); } config->config_stack = (config_save_t**)realloc(config->config_stack ,sizeof(config_save_t*)*config->cs_level); config->cs_level--; if(config->cs_level > 0 && config->config_stack == NULL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Can't allocate %d bytes of memory : %s\n",sizeof(config_save_t*)*config->cs_level,strerror(errno)); config->cs_level = -1; return -1; } mp_msg(MSGT_CFGPARSER, MSGL_DBG2,"Config poped level=%d\n",config->cs_level); return ret; } m_config_t* m_config_new(play_tree_t* pt) { m_config_t* config; #ifdef MP_DEBUG assert(pt != NULL); #endif config = (m_config_t*)calloc(1,sizeof(m_config_t)); if(config == NULL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Can't allocate %d bytes of memory : %s\n",sizeof(m_config_t),strerror(errno)); return NULL; } config->config_stack = (config_save_t**)calloc(1,sizeof(config_save_t*)); if(config->config_stack == NULL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Can't allocate %d bytes of memory : %s\n",sizeof(config_save_t*),strerror(errno)); free(config); return NULL; } SET_GLOBAL(config); // We always start with global options config->pt = pt; return config; } void m_config_free(m_config_t* config) { #ifdef MP_DEBUG assert(config != NULL); #endif free(config->opt_list); free(config->config_stack); free(config); } static int init_conf(m_config_t *config, int mode) { #ifdef MP_DEBUG assert(config != NULL); assert(config->pt != NULL); assert(config->last_entry == NULL || config->last_entry->parent == config->pt); if (mode != COMMAND_LINE && mode != CONFIG_FILE) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "init_conf: wrong mode!\n"); return -1; } #endif config->parser_mode = mode; return 1; } static int config_is_entry_option(m_config_t *config, char *opt, char *param) { play_tree_t* entry = NULL; #ifdef MP_DEBUG assert(config->pt != NULL); #endif if(strcasecmp(opt,"playlist") == 0) { // We handle playlist here if(!param) return ERR_MISSING_PARAM; entry = parse_playlist_file(param); if(!entry) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Playlist parsing failed: %s\n",param); return 1; } } if(! IS_RUNNING(config)) { if(strcasecmp(opt,"vcd") == 0) { char* s; if(!param) return ERR_MISSING_PARAM; s = (char*)malloc((strlen(param) + 6 + 1)*sizeof(char)); sprintf(s,"vcd://%s",param); entry = play_tree_new(); play_tree_add_file(entry,s); free(s); } else if(strcasecmp(opt,"dvd") == 0) { char* s; if(!param) return ERR_MISSING_PARAM; s = (char*)malloc((strlen(param) + 6 + 1)*sizeof(char)); sprintf(s,"dvd://%s",param); entry = play_tree_new(); play_tree_add_file(entry,s); free(s); } else if(strcasecmp(opt,"tv") == 0) { char *s,*pr,*prs; char *ps,*pe,*channel=NULL; char *as; int on=0; if(!param) return ERR_MISSING_PARAM; ps = param; pe = strchr(param,':'); pr = prs = (char*)malloc((strlen(param)+1)*sizeof(char)); pr[0] = '\0'; while(ps) { if(!pe) pe = ps + strlen(ps); as = strchr(ps,'='); if(as && as[1] != '\0' && pe-as > 0) as++; else as = NULL; if( !as && pe-ps == 2 && strncasecmp("on",ps,2) == 0 ) on = 1; else if(as && as-ps == 8 && strncasecmp("channel",ps,6) == 0 && pe-as > 0) { channel = (char*)realloc(channel,(pe-as+1)*sizeof(char)); strncpy(channel,as,pe-as); channel[pe-as] = '\0'; } else if(pe-ps > 0) { if(prs != pr) { prs[0] = ':'; prs++; } strncpy(prs,ps,pe-ps); prs += pe-ps; prs[0] = '\0'; } if(pe[0] != '\0') { ps = pe+1; pe = strchr(ps,':'); } else ps = NULL; } if(on) { int l=5; if(channel) l += strlen(channel); s = (char*) malloc((l+1)*sizeof(char)); if(channel) sprintf(s,"tv://%s",channel); else sprintf(s,"tv://"); entry = play_tree_new(); play_tree_add_file(entry,s); if(strlen(pr) > 0) play_tree_set_param(entry,"tv",pr); free(s); } free(pr); if(channel) free(channel); } } if(entry) { if(config->last_entry) play_tree_append_entry(config->last_entry,entry); else play_tree_set_child(config->pt,entry); config->last_entry = entry; if(config->parser_mode == COMMAND_LINE) UNSET_GLOBAL(config); return 1; } else return 0; } static int config_read_option(m_config_t *config,config_t** conf_list, char *opt, char *param) { int i=0,nconf = 0; long tmp_int; off_t tmp_off; double tmp_float; int dummy; int ret = -1; char *endptr; config_t* conf=NULL; #ifdef MP_DEBUG assert(config != NULL); assert(conf_list != NULL); assert(opt != NULL); #endif mp_msg(MSGT_CFGPARSER, MSGL_DBG3, "read_option: conf=%p opt='%s' param='%s'\n", conf, opt, param); for(nconf = 0 ; conf_list[nconf] != NULL; nconf++) { conf = conf_list[nconf]; for (i = 0; conf[i].name != NULL; i++) { int namelength; /* allow 'aa*' in config.name */ namelength=strlen(conf[i].name); if ( (conf[i].name[namelength-1]=='*') && !memcmp(opt, conf[i].name, namelength-1)) goto option_found; if (!strcasecmp(opt, conf[i].name)) goto option_found; } } if (config->parser_mode == CONFIG_FILE) mp_msg(MSGT_CFGPARSER, MSGL_ERR, "invalid option: %s\n",opt); ret = ERR_NOT_AN_OPTION; goto out; option_found : mp_msg(MSGT_CFGPARSER, MSGL_DBG3, "read_option: name='%s' p=%p type=%d\n", conf[i].name, conf[i].p, conf[i].type); if (conf[i].flags & CONF_NOCFG && config->parser_mode == CONFIG_FILE) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "this option can only be used on command line:\n", opt); ret = ERR_NOT_AN_OPTION; goto out; } if (conf[i].flags & CONF_NOCMD && config->parser_mode == COMMAND_LINE) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "this option can only be used in config file:\n", opt); ret = ERR_NOT_AN_OPTION; goto out; } ret = config_is_entry_option(config,opt,param); if(ret != 0) return ret; else ret = -1; if(! IS_RUNNING(config) && ! IS_GLOBAL(config) && ! (conf[i].flags & CONF_GLOBAL) && conf[i].type != CONF_TYPE_SUBCONFIG ) m_config_push(config); if( !(conf[i].flags & CONF_NOSAVE) && ! (conf[i].flags & CONF_GLOBAL) ) m_config_save_option(config,&conf[i],opt,param); switch (conf[i].type) { case CONF_TYPE_FLAG: /* flags need a parameter in config file */ if (config->parser_mode == CONFIG_FILE) { if (!strcasecmp(param, "yes") || /* any other language? */ !strcasecmp(param, "ja") || !strcasecmp(param, "si") || !strcasecmp(param, "igen") || !strcasecmp(param, "y") || !strcasecmp(param, "j") || !strcasecmp(param, "i") || !strcmp(param, "1")) *((int *) conf[i].p) = conf[i].max; else if (!strcasecmp(param, "no") || !strcasecmp(param, "nein") || !strcasecmp(param, "nicht") || !strcasecmp(param, "nem") || !strcasecmp(param, "n") || !strcmp(param, "0")) *((int *) conf[i].p) = conf[i].min; else { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "invalid parameter for flag: %s\n", param); ret = ERR_OUT_OF_RANGE; goto out; } ret = 1; } else { /* parser_mode == COMMAND_LINE */ *((int *) conf[i].p) = conf[i].max; ret = 0; } break; case CONF_TYPE_INT: if (param == NULL) goto err_missing_param; tmp_int = strtol(param, &endptr, 0); if (*endptr) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parameter must be an integer: %s\n", param); ret = ERR_OUT_OF_RANGE; goto out; } if (conf[i].flags & CONF_MIN) if (tmp_int < conf[i].min) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parameter must be >= %d: %s\n", (int) conf[i].min, param); ret = ERR_OUT_OF_RANGE; goto out; } if (conf[i].flags & CONF_MAX) if (tmp_int > conf[i].max) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parameter must be <= %d: %s\n", (int) conf[i].max, param); ret = ERR_OUT_OF_RANGE; goto out; } *((int *) conf[i].p) = tmp_int; ret = 1; break; case CONF_TYPE_FLOAT: if (param == NULL) goto err_missing_param; /* <olo@altkom.com.pl> Use portable C locale for parsing floats: */ #ifdef USE_SETLOCALE setlocale(LC_NUMERIC, "C"); #endif tmp_float = strtod(param, &endptr); switch(*endptr) { case ':': case '/': tmp_float /= strtod(endptr+1, &endptr); default: break; } #ifdef USE_SETLOCALE setlocale(LC_NUMERIC, ""); #endif if (*endptr) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parameter must be a floating point number" " or a ratio (numerator[:/]denominator): %s\n", param); ret = ERR_MISSING_PARAM; goto out; } if (conf[i].flags & CONF_MIN) if (tmp_float < conf[i].min) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parameter must be >= %f: %s\n", conf[i].min, param); ret = ERR_OUT_OF_RANGE; goto out; } if (conf[i].flags & CONF_MAX) if (tmp_float > conf[i].max) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parameter must be <= %f: %s\n", conf[i].max, param); ret = ERR_OUT_OF_RANGE; goto out; } *((float *) conf[i].p) = tmp_float; ret = 1; break; case CONF_TYPE_STRING: if (param == NULL) goto err_missing_param; if (conf[i].flags & CONF_MIN) if (strlen(param) < conf[i].min) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parameter must be >= %d chars: %s\n", (int) conf[i].min, param); ret = ERR_OUT_OF_RANGE; goto out; } if (conf[i].flags & CONF_MAX) if (strlen(param) > conf[i].max) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parameter must be <= %d chars: %s\n", (int) conf[i].max, param); ret = ERR_OUT_OF_RANGE; goto out; } *((char **) conf[i].p) = strdup(param); ret = 1; break; case CONF_TYPE_STRING_LIST: if (param == NULL) goto err_missing_param; else { int n = 0,len; char *ptr = param, *last_ptr, **res; while(ptr[0] != '\0') { last_ptr = ptr; ptr = strchr(ptr,LIST_SEPARATOR); if(!ptr) { // if(strlen(last_ptr) > 0) n++; break; } ptr++; n++; } if(n == 0) goto err_missing_param; else if( (conf[i].flags & CONF_MIN && n < conf[i].min) || (conf[i].flags & CONF_MAX && n > conf[i].max) ) { ret = ERR_OUT_OF_RANGE; goto out; } ret = 1; res = malloc((n+2)*sizeof(char*)); ptr = param; n = 0; // while(ptr[0] != '\0') { while(1) { last_ptr = ptr; ptr = strchr(ptr,LIST_SEPARATOR); if(!ptr) { //if(strlen(last_ptr) > 0) { res[n] = strdup(last_ptr); n++; } break; } len = ptr - last_ptr; res[n] = (char*)malloc(len + 1); if(len) strncpy(res[n],last_ptr,len); res[n][len] = '\0'; ptr++; n++; } res[n] = NULL; *((char ***) conf[i].p) = res; } break; case CONF_TYPE_FUNC_PARAM: if (param == NULL) goto err_missing_param; if ((((cfg_func_param_t) conf[i].p)(conf + i, param)) < 0) { ret = ERR_FUNC_ERR; goto out; } ret = 1; break; case CONF_TYPE_FUNC_FULL: if (param!=NULL && param[0]=='-'){ ret=((cfg_func_arg_param_t) conf[i].p)(conf + i, opt, NULL); if (ret>=0) ret=0; /* if we return >=0: param is processed again (if there is any) */ }else{ ret=((cfg_func_arg_param_t) conf[i].p)(conf + i, opt, param); /* if we return 0: need no param, precess it again */ /* if we return 1: accepted param */ } break; case CONF_TYPE_FUNC: if ((((cfg_func_t) conf[i].p)(conf + i)) < 0) { ret = ERR_FUNC_ERR; goto out; } ret = 0; break; case CONF_TYPE_SUBCONFIG: { char *subparam; char *subopt; int subconf_optnr; config_t *subconf; config_t *sublist[] = { NULL , NULL }; char *token; char *p; if (param == NULL) goto err_missing_param; subparam = malloc(strlen(param)+1); subopt = malloc(strlen(param)+1); p = strdup(param); // In case that param is a static string (cf man strtok) subconf = conf[i].p; sublist[0] = subconf; for (subconf_optnr = 0; subconf[subconf_optnr].name != NULL; subconf_optnr++) /* NOTHING */; config->sub_conf = opt; token = strtok(p, (char *)&(":")); while(token) { int sscanf_ret; /* clear out */ subopt[0] = subparam[0] = 0; sscanf_ret = sscanf(token, "%[^=]=%[^:]", subopt, subparam); mp_msg(MSGT_CFGPARSER, MSGL_DBG3, "token: '%s', i=%d, subopt='%s', subparam='%s' (ret: %d)\n", token, i, subopt, subparam, sscanf_ret); switch(sscanf_ret) { case 1: subparam[0] = 0; case 2: if ((ret = config_read_option(config,sublist, subopt, subparam)) < 0) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Subconfig parsing returned error: %d in token: %s\n", ret, token); goto out; } break; default: mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Invalid subconfig argument! ('%s')\n", token); ret = ERR_NOT_AN_OPTION; goto out; } token = strtok(NULL, (char *)&(":")); } config->sub_conf = NULL; free(subparam); free(subopt); free(p); ret = 1; break; } case CONF_TYPE_PRINT: mp_msg(MSGT_CFGPARSER, MSGL_INFO, "%s", (char *) conf[i].p); exit(1); case CONF_TYPE_POSITION: if (param == NULL) goto err_missing_param; if (sscanf(param, sizeof(off_t) == sizeof(int) ? "%d%c" : "%lld%c", &tmp_off, (char *)&dummy) != 1) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parameter must be an integer: %s\n", param); ret = ERR_OUT_OF_RANGE; goto out; } if (conf[i].flags & CONF_MIN) if (tmp_off < conf[i].min) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, (sizeof(off_t) == sizeof(int) ? "parameter must be >= %d: %s\n" : "parameter must be >= %lld: %s\n"), (off_t) conf[i].min, param); ret = ERR_OUT_OF_RANGE; goto out; } if (conf[i].flags & CONF_MAX) if (tmp_off > conf[i].max) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, (sizeof(off_t) == sizeof(int) ? "parameter must be <= %d: %s\n" : "parameter must be <= %lld: %s\n"), (off_t) conf[i].max, param); ret = ERR_OUT_OF_RANGE; goto out; } *((off_t *) conf[i].p) = tmp_off; ret = 1; break; default: mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Unknown config type specified in conf-mplayer.h!\n"); break; } out: if(ret >= 0 && ! IS_RUNNING(config) && ! IS_GLOBAL(config) && ! (conf[i].flags & CONF_GLOBAL) && conf[i].type != CONF_TYPE_SUBCONFIG ) { play_tree_t* dest = config->last_entry ? config->last_entry : config->last_parent; char* o; #ifdef MP_DEBUG assert(dest != NULL); #endif if(config->sub_conf) { o = (char*)malloc((strlen(config->sub_conf) + 1 + strlen(opt) + 1)*sizeof(char)); sprintf(o,"%s:%s",config->sub_conf,opt); } else o =strdup(opt); if(ret == 0) play_tree_set_param(dest,o,NULL); else if(ret > 0) play_tree_set_param(dest,o,param); free(o); m_config_pop(config); } return ret; err_missing_param: mp_msg(MSGT_CFGPARSER, MSGL_ERR, "missing parameter for option: %s\n", opt); ret = ERR_MISSING_PARAM; goto out; } int m_config_set_option(m_config_t *config,char *opt, char *param) { char *e; #ifdef MP_DEBUG assert(config != NULL); assert(config->opt_list != NULL); assert(opt != NULL); #endif mp_msg(MSGT_CFGPARSER, MSGL_DBG2, "Setting option %s=%s\n",opt,param); e = strchr(opt,':'); if(e && e[1] != '\0') { int ret; config_t* opt_list[] = { NULL, NULL }; char* s = (char*)malloc((e-opt+1)*sizeof(char)); strncpy(s,opt,e-opt); s[e-opt] = '\0'; opt_list[0] = m_config_get_option_ptr(config,s); if(!opt_list[0]) { mp_msg(MSGT_CFGPARSER, MSGL_ERR,"m_config_set_option %s=%s : no %s subconfig\n",opt,param,s); free(s); return ERR_NOT_AN_OPTION; } e++; s = (char*)realloc(s,strlen(e) + 1); strcpy(s,e); ret = config_read_option(config,opt_list,s,param); free(s); return ret; } return config_read_option(config,config->opt_list,opt,param); } int m_config_parse_config_file(m_config_t *config, char *conffile) { #define PRINT_LINENUM mp_msg(MSGT_CFGPARSER,MSGL_INFO,"%s(%d): ", conffile, line_num) #define MAX_LINE_LEN 1000 #define MAX_OPT_LEN 100 #define MAX_PARAM_LEN 100 FILE *fp; char *line; char opt[MAX_OPT_LEN + 1]; char param[MAX_PARAM_LEN + 1]; char c; /* for the "" and '' check */ int tmp; int line_num = 0; int line_pos; /* line pos */ int opt_pos; /* opt pos */ int param_pos; /* param pos */ int ret = 1; int errors = 0; #ifdef MP_DEBUG assert(config != NULL); // assert(conf_list != NULL); #endif if (++config->recursion_depth > 1) mp_msg(MSGT_CFGPARSER,MSGL_INFO,"Reading config file: %s", conffile); if (config->recursion_depth > MAX_RECURSION_DEPTH) { mp_msg(MSGT_CFGPARSER,MSGL_ERR,": too deep 'include'. check your configfiles\n"); ret = -1; goto out; } if (init_conf(config, CONFIG_FILE) == -1) { ret = -1; goto out; } if ((line = (char *) malloc(MAX_LINE_LEN + 1)) == NULL) { mp_msg(MSGT_CFGPARSER,MSGL_FATAL,"\ncan't get memory for 'line': %s", strerror(errno)); ret = -1; goto out; } if ((fp = fopen(conffile, "r")) == NULL) { if (config->recursion_depth > 1) mp_msg(MSGT_CFGPARSER,MSGL_ERR,": %s\n", strerror(errno)); free(line); ret = 0; goto out; } if (config->recursion_depth > 1) mp_msg(MSGT_CFGPARSER,MSGL_INFO,"\n"); while (fgets(line, MAX_LINE_LEN, fp)) { if (errors >= 16) { mp_msg(MSGT_CFGPARSER,MSGL_FATAL,"too many errors\n"); goto out; } line_num++; line_pos = 0; /* skip whitespaces */ while (isspace(line[line_pos])) ++line_pos; /* EOL / comment */ if (line[line_pos] == '\0' || line[line_pos] == '#') continue; /* read option. */ for (opt_pos = 0; isprint(line[line_pos]) && line[line_pos] != ' ' && line[line_pos] != '#' && line[line_pos] != '='; /* NOTHING */) { opt[opt_pos++] = line[line_pos++]; if (opt_pos >= MAX_OPT_LEN) { PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_ERR,"too long option\n"); errors++; ret = -1; goto nextline; } } if (opt_pos == 0) { PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_ERR,"parse error\n"); ret = -1; errors++; continue; } opt[opt_pos] = '\0'; #ifdef MP_DEBUG PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_INFO,"option: %s\n", opt); #endif /* skip whitespaces */ while (isspace(line[line_pos])) ++line_pos; /* check '=' */ if (line[line_pos++] != '=') { PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_ERR,"option without parameter\n"); ret = -1; errors++; continue; } /* whitespaces... */ while (isspace(line[line_pos])) ++line_pos; /* read the parameter */ if (line[line_pos] == '"' || line[line_pos] == '\'') { c = line[line_pos]; ++line_pos; for (param_pos = 0; line[line_pos] != c; /* NOTHING */) { param[param_pos++] = line[line_pos++]; if (param_pos >= MAX_PARAM_LEN) { PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_ERR,"too long parameter\n"); ret = -1; errors++; goto nextline; } } line_pos++; /* skip the closing " or ' */ } else { for (param_pos = 0; isprint(line[line_pos]) && !isspace(line[line_pos]) && line[line_pos] != '#'; /* NOTHING */) { param[param_pos++] = line[line_pos++]; if (param_pos >= MAX_PARAM_LEN) { PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_ERR,"too long parameter\n"); ret = -1; errors++; goto nextline; } } } param[param_pos] = '\0'; /* did we read a parameter? */ if (param_pos == 0) { PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_ERR,"option without parameter\n"); ret = -1; errors++; continue; } #ifdef MP_DEBUG PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_INFO,"parameter: %s\n", param); #endif /* now, check if we have some more chars on the line */ /* whitespace... */ while (isspace(line[line_pos])) ++line_pos; /* EOL / comment */ if (line[line_pos] != '\0' && line[line_pos] != '#') { PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_WARN,"extra characters on line: %s\n", line+line_pos); ret = -1; } tmp = m_config_set_option(config, opt, param); switch (tmp) { case ERR_NOT_AN_OPTION: case ERR_MISSING_PARAM: case ERR_OUT_OF_RANGE: case ERR_FUNC_ERR: PRINT_LINENUM; mp_msg(MSGT_CFGPARSER,MSGL_INFO,"%s\n", opt); ret = -1; errors++; continue; /* break */ } nextline: ; } free(line); fclose(fp); out: --config->recursion_depth; return ret; } int m_config_parse_command_line(m_config_t *config, int argc, char **argv) { int i; int tmp; char *opt; int no_more_opts = 0; #ifdef MP_DEBUG assert(config != NULL); assert(config->pt != NULL); assert(argv != NULL); assert(argc >= 1); #endif if (init_conf(config, COMMAND_LINE) == -1) return -1; if(config->last_parent == NULL) config->last_parent = config->pt; /* in order to work recursion detection properly in parse_config_file */ ++config->recursion_depth; for (i = 1; i < argc; i++) { //next: opt = argv[i]; /* check for -- (no more options id.) except --help! */ if ((*opt == '-') && (*(opt+1) == '-') && (*(opt+2) != 'h')) { no_more_opts = 1; if (i+1 >= argc) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "You added '--' but no filenames presented!\n"); goto err_out; } continue; } if((opt[0] == '{') && (opt[1] == '\0')) { play_tree_t* entry = play_tree_new(); UNSET_GLOBAL(config); if(config->last_entry == NULL) { play_tree_set_child(config->last_parent,entry); } else { play_tree_append_entry(config->last_entry,entry); config->last_entry = NULL; } config->last_parent = entry; continue; } if((opt[0] == '}') && (opt[1] == '\0')) { if( ! config->last_parent || ! config->last_parent->parent) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too much }-\n"); goto err_out; } config->last_entry = config->last_parent; config->last_parent = config->last_entry->parent; continue; } if ((no_more_opts == 0) && (*opt == '-') && (*(opt+1) != 0)) /* option */ { /* remove trailing '-' */ opt++; mp_msg(MSGT_CFGPARSER, MSGL_DBG3, "this_opt = option: %s\n", opt); // We handle here some specific option if(strcasecmp(opt,"list-options") == 0) { m_config_list_options(config); exit(1); // Loop option when it apply to a group } else if(strcasecmp(opt,"loop") == 0 && (! config->last_entry || config->last_entry->child) ) { int l; char* end; l = strtol(argv[i+1],&end,0); if(!end) tmp = ERR_OUT_OF_RANGE; else { play_tree_t* pt = config->last_entry ? config->last_entry : config->last_parent; l = l <= 0 ? -1 : l; pt->loop = l; tmp = 1; } } else // All normal options tmp = m_config_set_option(config, opt, argv[i + 1]); switch (tmp) { case ERR_NOT_AN_OPTION: case ERR_MISSING_PARAM: case ERR_OUT_OF_RANGE: case ERR_FUNC_ERR: mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Error: "); m_config_error(tmp,opt,argv[i+1]); goto err_out; default: i += tmp; break; } } else /* filename */ { play_tree_t* entry = play_tree_new(); mp_msg(MSGT_CFGPARSER, MSGL_DBG2,"Adding file %s\n",argv[i]); play_tree_add_file(entry,argv[i]); if(strcasecmp(argv[i],"-") == 0) m_config_set_option(config,"use-stdin",NULL); /* opt is not an option -> treat it as a filename */ UNSET_GLOBAL(config); // We start entry specific options if(config->last_entry == NULL) play_tree_set_child(config->last_parent,entry); else play_tree_append_entry(config->last_entry,entry); config->last_entry = entry; } } --config->recursion_depth; if(config->last_parent != config->pt) mp_msg(MSGT_CFGPARSER, MSGL_ERR,"Missing }- ?\n"); config->flags &= (!CONFIG_GLOBAL); SET_RUNNING(config); return 1; #if 0 err_out_mem: mp_msg(MSGT_CFGPARSER, MSGL_ERR, "can't allocate memory for filenames (%s)\n", strerror(errno)); #endif err_out: --config->recursion_depth; mp_msg(MSGT_CFGPARSER, MSGL_ERR, "command line: %s\n", argv[i]); return -1; } int m_config_register_options(m_config_t *config,config_t *args) { int list_len = 0; config_t** conf_list = config->opt_list; #ifdef MP_DEBUG assert(config != NULL); assert(args != NULL); #endif if(conf_list) { for ( ; conf_list[list_len] != NULL; list_len++) /* NOTHING */; } conf_list = (config_t**)realloc(conf_list,sizeof(struct conf*)*(list_len+2)); if(conf_list == NULL) { mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Can't allocate %d bytes of memory : %s\n",sizeof(struct conf*)*(list_len+2),strerror(errno)); return 0; } conf_list[list_len] = args; conf_list[list_len+1] = NULL; config->opt_list = conf_list; return 1; } config_t* m_config_get_option(m_config_t *config, char* arg) { int i,j; char *e; config_t *conf; config_t **conf_list; config_t* cl[] = { NULL, NULL }; #ifdef MP_DEBUG assert(config != NULL); assert(arg != NULL); #endif e = strchr(arg,':'); if(e) { char *s; s = (char*)malloc((e-arg+1)*sizeof(char)); strncpy(s,arg,e-arg); s[e-arg] = '\0'; cl[0] = m_config_get_option(config,s); conf_list = cl; free(s); } else conf_list = config->opt_list; if(conf_list) { for(j = 0 ; conf_list[j] != NULL ; j++) { conf = conf_list[j]; for(i=0; conf[i].name != NULL; i++) { if(strcasecmp(conf[i].name,arg) == 0) return &conf[i]; } } } return NULL; } void* m_config_get_option_ptr(m_config_t *config, char* arg) { config_t* conf; #ifdef MP_DEBUG assert(config != NULL); assert(arg != NULL); #endif conf = m_config_get_option(config,arg); if(!conf) return NULL; return conf->p; } int m_config_get_int (m_config_t *config, char* arg,int* err_ret) { int *ret; #ifdef MP_DEBUG assert(config != NULL); assert(arg != NULL); #endif ret = m_config_get_option_ptr(config,arg); if(err_ret) *err_ret = 0; if(!ret) { if(err_ret) *err_ret = 1; return -1; } else return (*ret); } float m_config_get_float (m_config_t *config, char* arg,int* err_ret) { float *ret; #ifdef MP_DEBUG assert(config != NULL); assert(arg != NULL); #endif ret = m_config_get_option_ptr(config,arg); if(err_ret) *err_ret = 0; if(!ret) { if(err_ret) *err_ret = 1; return -1; } else return (*ret); } #define AS_INT(c) (*((int*)c->p)) int m_config_set_int(m_config_t *config, char* arg,int val) { config_t* opt; #ifdef MP_DEBUG assert(config != NULL); assert(arg != NULL); #endif opt = m_config_get_option(config,arg); if(!opt || opt->type != CONF_TYPE_INT) return ERR_NOT_AN_OPTION; if(opt->flags & CONF_MIN && val < opt->min) return ERR_OUT_OF_RANGE; if(opt->flags & CONF_MAX && val > opt->max) return ERR_OUT_OF_RANGE; m_config_save_option(config,opt,arg,NULL); AS_INT(opt) = val; return 1; } int m_config_set_float(m_config_t *config, char* arg,float val) { config_t* opt; #ifdef MP_DEBUG assert(config != NULL); assert(arg != NULL); #endif opt = m_config_get_option(config,arg); if(!opt || opt->type != CONF_TYPE_FLOAT) return ERR_NOT_AN_OPTION; if(opt->flags & CONF_MIN && val < opt->min) return ERR_OUT_OF_RANGE; if(opt->flags & CONF_MAX && val > opt->max) return ERR_OUT_OF_RANGE; m_config_save_option(config,opt,arg,NULL); *((float*)opt->p) = val; return 1; } int m_config_switch_flag(m_config_t *config, char* opt) { config_t *conf; #ifdef MP_DEBUG assert(config != NULL); assert(opt != NULL); #endif conf = m_config_get_option(config,opt); if(!conf || conf->type != CONF_TYPE_FLAG) return 0; if( AS_INT(conf) == conf->min) AS_INT(conf) = conf->max; else if(AS_INT(conf) == conf->max) AS_INT(conf) = conf->min; else return 0; return 1; } int m_config_set_flag(m_config_t *config, char* opt, int state) { config_t *conf; #ifdef MP_DEBUG assert(config != NULL); assert(opt != NULL); #endif conf = m_config_get_option(config,opt); if(!conf || conf->type != CONF_TYPE_FLAG) return 0; if(state) AS_INT(conf) = conf->max; else AS_INT(conf) = conf->min; return 1; } int m_config_get_flag(m_config_t *config, char* opt) { config_t *conf; #ifdef MP_DEBUG assert(config != NULL); assert(opt != NULL); #endif conf = m_config_get_option(config,opt); if(!conf || conf->type != CONF_TYPE_FLAG) return -1; if(AS_INT(conf) == conf->max) return 1; else if(AS_INT(conf) == conf->min) return 0; else return -1; } int m_config_is_option_set(m_config_t *config, char* arg) { config_t* opt; config_save_t* save; int l,i; #ifdef MP_DEBUG assert(config != NULL); assert(arg != NULL); #endif opt = m_config_get_option(config,arg); if(!opt) return -1; for(l = config->cs_level ; l >= 0 ; l--) { save = config->config_stack[l]; if(!save) continue; for(i = 0 ; save[i].opt != NULL ; i++) { if(save[i].opt == opt) return 1; } } return 0; } #undef AS_INT static void m_config_print_option_list(char* prefix, config_t* opt_list) { char* pf = NULL; config_t* opt; char min[50],max[50],*type; for(opt = opt_list ; opt->name != NULL ; opt++) { if(opt->type == CONF_TYPE_SUBCONFIG) { if(prefix) { pf = (char*)malloc(strlen(prefix) + strlen(opt->name) + 1); sprintf(pf,"%s:%s",prefix,opt->name); } else pf = strdup(opt->name); m_config_print_option_list(pf,(config_t*)opt->p); free(pf); continue; } if(prefix) printf("%1.15s:",prefix); if(opt->flags & CONF_MIN) sprintf(min,"%-8.0f",opt->min); else strcpy(min,"No"); if(opt->flags & CONF_MAX) sprintf(max,"%-8.0f",opt->max); else strcpy(max,"No"); switch(opt->type) { case CONF_TYPE_FLAG: type = "Flag"; break; case CONF_TYPE_INT: type = "Integer"; break; case CONF_TYPE_FLOAT: type = "Float"; break; case CONF_TYPE_STRING: type = "String"; break; case CONF_TYPE_FUNC: case CONF_TYPE_FUNC_PARAM: case CONF_TYPE_FUNC_FULL: type = "Function"; break; case CONF_TYPE_PRINT: type = "Print"; break; case CONF_TYPE_STRING_LIST: type = "String list"; break; default: type = ""; break; } printf("%-*.15s %-13.13s %-10.10s %-10.10s %-3.3s %-3.3s %-3.3s\n", 30 - (prefix ? strlen(prefix) + 1 : 0), opt->name, type, min, max, opt->flags & CONF_GLOBAL ? "Yes" : "No", opt->flags & CONF_NOCMD ? "No" : "Yes", opt->flags & CONF_NOCFG ? "No" : "Yes"); } } static void m_config_list_options(m_config_t *config) { int i; printf("\nName Type Min Max Glob CL Cfg\n\n"); for(i = 0; config->opt_list[i] ; i++) m_config_print_option_list(NULL,config->opt_list[i]); } static void m_config_error(int err,char* opt,char* val) { switch(err) { case ERR_NOT_AN_OPTION: mp_msg(MSGT_CFGPARSER, MSGL_ERR,"'%s' is not a mplayer/mencoder option\n",opt); break; case ERR_MISSING_PARAM: mp_msg(MSGT_CFGPARSER, MSGL_ERR,"option '%s' need a parameter\n",opt); break; case ERR_OUT_OF_RANGE: mp_msg(MSGT_CFGPARSER, MSGL_ERR,"value '%s' of option '%s' is out of range\n",val,opt); break; case ERR_FUNC_ERR: mp_msg(MSGT_CFGPARSER, MSGL_ERR,"while parsing option '%s'\n",opt); break; } } #endif