535 lines
13 KiB
C
535 lines
13 KiB
C
#define MODULE_LOG_PREFIX "config"
|
|
|
|
#include "globals.h"
|
|
#include "oscam-conf.h"
|
|
#include "oscam-config.h"
|
|
#include "oscam-files.h"
|
|
#include "oscam-garbage.h"
|
|
#include "oscam-string.h"
|
|
|
|
#define CONFVARWIDTH 30
|
|
|
|
/* Returns the default value if string length is zero, otherwise atoi is called*/
|
|
int32_t strToIntVal(char *value, int32_t defaultvalue)
|
|
{
|
|
if(cs_strlen(value) == 0) { return defaultvalue; }
|
|
errno = 0; // errno should be set to 0 before calling strtol
|
|
int32_t i = strtol(value, NULL, 10);
|
|
return (errno == 0) ? i : defaultvalue;
|
|
}
|
|
|
|
/* Returns the default value if string length is zero, otherwise strtoul is called*/
|
|
uint32_t strToUIntVal(char *value, uint32_t defaultvalue)
|
|
{
|
|
if(cs_strlen(value) == 0) { return defaultvalue; }
|
|
errno = 0; // errno should be set to 0 before calling strtoul
|
|
uint32_t i = strtoul(value, NULL, 10);
|
|
return (errno == 0) ? i : defaultvalue;
|
|
}
|
|
|
|
/* Replacement of fprintf which adds necessary whitespace to fill up the varname to a fixed width.
|
|
If varname is longer than CONFVARWIDTH, no whitespace is added*/
|
|
void fprintf_conf(FILE *f, const char *varname, const char *fmtstring, ...)
|
|
{
|
|
int32_t varlen = cs_strlen(varname);
|
|
int32_t max = (varlen > CONFVARWIDTH) ? varlen : CONFVARWIDTH;
|
|
char varnamebuf[max + 3];
|
|
char *ptr = varnamebuf + varlen;
|
|
va_list argptr;
|
|
|
|
cs_strncpy(varnamebuf, varname, sizeof(varnamebuf));
|
|
while(varlen < CONFVARWIDTH)
|
|
{
|
|
ptr[0] = ' ';
|
|
++ptr;
|
|
++varlen;
|
|
}
|
|
cs_strncpy(ptr, "= ", sizeof(varnamebuf) - (ptr - varnamebuf));
|
|
if(fwrite(varnamebuf, sizeof(char), cs_strlen(varnamebuf), f))
|
|
{
|
|
if(cs_strlen(fmtstring) > 0)
|
|
{
|
|
va_start(argptr, fmtstring);
|
|
vfprintf(f, fmtstring, argptr);
|
|
va_end(argptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
int config_list_parse(const struct config_list *clist, const char *token, char *value, void *config_data)
|
|
{
|
|
const struct config_list *c;
|
|
for(c = clist; c->opt_type != OPT_UNKNOWN; c++)
|
|
{
|
|
if(c->opt_type == OPT_SAVE_FUNC || c->opt_type == OPT_FIXUP_FUNC)
|
|
{ continue; }
|
|
if(strcasecmp(token, c->config_name) != 0)
|
|
{ continue; }
|
|
void *var = config_data + c->var_offset;
|
|
switch(c->opt_type)
|
|
{
|
|
case OPT_INT8:
|
|
{
|
|
*(int8_t *)var = (int8_t)strToIntVal(value, c->def.d_int8);
|
|
return 1;
|
|
}
|
|
case OPT_UINT8:
|
|
{
|
|
uint32_t tmp = strToUIntVal(value, c->def.d_uint8);
|
|
*(uint8_t *)var = (uint8_t)(tmp <= 0xff ? tmp : 0xff);
|
|
return 1;
|
|
}
|
|
case OPT_INT32:
|
|
{
|
|
int32_t tmp = strToIntVal(value, c->def.d_int32);
|
|
memcpy(var, &tmp, sizeof(int32_t));
|
|
return 1;
|
|
}
|
|
case OPT_UINT32:
|
|
{
|
|
uint32_t tmp = strToUIntVal(value, c->def.d_uint32);
|
|
memcpy(var, &tmp, sizeof(uint32_t));
|
|
return 1;
|
|
}
|
|
case OPT_STRING:
|
|
{
|
|
char **scfg = var;
|
|
if(c->def.d_char && cs_strlen(value) == 0) // Set default
|
|
{ value = c->def.d_char; }
|
|
NULLFREE(*scfg);
|
|
if(cs_strlen(value))
|
|
{ *scfg = cs_strdup(value); }
|
|
return 1;
|
|
}
|
|
case OPT_SSTRING:
|
|
{
|
|
char *scfg = var;
|
|
if(c->def.d_char && cs_strlen(value) == 0) // Set default
|
|
{ value = c->def.d_char; }
|
|
scfg[0] = '\0';
|
|
unsigned int len = cs_strlen(value);
|
|
if(len)
|
|
{
|
|
cs_strncpy(scfg, value, c->str_size);
|
|
if(len > c->str_size)
|
|
{
|
|
fprintf(stderr, "WARNING: Config value for '%s' (%s, len=%u) exceeds max length: %d (%s)\n",
|
|
token, value, len, c->str_size - 1, scfg);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
case OPT_HEX_ARRAY:
|
|
{
|
|
uint8_t *hex_array = var;
|
|
if(!cs_strlen(value))
|
|
{ memset(hex_array, 0, c->def.array_size); }
|
|
else if(key_atob_l(value, hex_array, c->def.array_size * 2))
|
|
{
|
|
memset(hex_array, 0, c->def.array_size);
|
|
fprintf(stderr, "WARNING: Config value for '%s' (%s, len=%zu) requires %d chars.\n",
|
|
token, value, cs_strlen(value), c->def.array_size * 2);
|
|
}
|
|
return 1;
|
|
}
|
|
case OPT_FUNC:
|
|
{
|
|
c->ops.process_fn(token, value, var, NULL);
|
|
return 1;
|
|
}
|
|
case OPT_FUNC_EXTRA:
|
|
{
|
|
c->ops.process_fn_extra(token, value, var, c->def.d_extra, NULL);
|
|
return 1;
|
|
}
|
|
case OPT_FIXUP_FUNC:
|
|
case OPT_SAVE_FUNC:
|
|
return 1;
|
|
case OPT_UNKNOWN:
|
|
{
|
|
fprintf(stderr, "Unknown config type (%s = %s).", token, value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void config_list_save_ex(FILE *f, const struct config_list *clist, void *config_data, int save_all,
|
|
bool (*check_func)(const struct config_list *clist, void *config_data, const char *setting))
|
|
{
|
|
const struct config_list *c;
|
|
for(c = clist; c->opt_type != OPT_UNKNOWN; c++)
|
|
{
|
|
void *var = config_data + c->var_offset;
|
|
if(check_func && c->opt_type != OPT_UNKNOWN)
|
|
{
|
|
if(!check_func(clist, config_data, c->config_name))
|
|
{ continue; }
|
|
}
|
|
switch(c->opt_type)
|
|
{
|
|
case OPT_INT8:
|
|
{
|
|
int8_t val = *(int8_t *)var;
|
|
|
|
// always save pmt_mode, because external tools parse it
|
|
if(save_all || val != c->def.d_int8 || !strcmp(c->config_name, "pmt_mode"))
|
|
{ fprintf_conf(f, c->config_name, "%d\n", val); }
|
|
continue;
|
|
}
|
|
case OPT_UINT8:
|
|
{
|
|
uint8_t val = *(uint8_t *)var;
|
|
if(save_all || val != c->def.d_uint8)
|
|
{ fprintf_conf(f, c->config_name, "%u\n", val); }
|
|
continue;
|
|
}
|
|
case OPT_INT32:
|
|
{
|
|
int32_t val;
|
|
memcpy(&val, var, sizeof(int32_t));
|
|
if(save_all || val != c->def.d_int32)
|
|
{ fprintf_conf(f, c->config_name, "%d\n", val); }
|
|
continue;
|
|
}
|
|
case OPT_UINT32:
|
|
{
|
|
uint32_t val;
|
|
memcpy(&val, var, sizeof(uint32_t));
|
|
if(save_all || val != c->def.d_uint32)
|
|
{ fprintf_conf(f, c->config_name, "%u\n", val); }
|
|
continue;
|
|
}
|
|
case OPT_STRING:
|
|
{
|
|
char **val = var;
|
|
if(save_all || !streq(*val, c->def.d_char))
|
|
{
|
|
fprintf_conf(f, c->config_name, "%s\n", *val ? *val : "");
|
|
}
|
|
continue;
|
|
}
|
|
case OPT_SSTRING:
|
|
{
|
|
char *val = var;
|
|
if(save_all || !streq(val, c->def.d_char))
|
|
{
|
|
fprintf_conf(f, c->config_name, "%s\n", val[0] ? val : "");
|
|
}
|
|
continue;
|
|
}
|
|
case OPT_HEX_ARRAY:
|
|
{
|
|
uint8_t *hex_array = var;
|
|
uint32_t ok = array_has_nonzero_byte(hex_array, c->def.array_size);
|
|
if(save_all || ok)
|
|
{
|
|
fprintf_conf(f, c->config_name, "%s", ""); // it should not have \n at the end
|
|
if(ok)
|
|
{
|
|
for(ok = 0; ok < c->def.array_size; ok++)
|
|
{
|
|
fprintf(f, "%02X", hex_array[ok]);
|
|
}
|
|
}
|
|
fprintf(f, "\n");
|
|
}
|
|
continue;
|
|
}
|
|
case OPT_FUNC:
|
|
{
|
|
c->ops.process_fn((const char *)c->config_name, NULL, var, f);
|
|
continue;
|
|
}
|
|
case OPT_FUNC_EXTRA:
|
|
{
|
|
c->ops.process_fn_extra((const char *)c->config_name, NULL, var, c->def.d_extra, f);
|
|
continue;
|
|
}
|
|
case OPT_FIXUP_FUNC:
|
|
case OPT_SAVE_FUNC:
|
|
continue;
|
|
case OPT_UNKNOWN:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool config_list_should_be_saved(const struct config_list *clist, void *var)
|
|
{
|
|
const struct config_list *c;
|
|
for(c = clist; c->opt_type != OPT_UNKNOWN; c++)
|
|
{
|
|
if(c->opt_type == OPT_SAVE_FUNC)
|
|
{
|
|
return c->ops.should_save_fn(var);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void config_list_apply_fixups(const struct config_list *clist, void *var)
|
|
{
|
|
const struct config_list *c;
|
|
for(c = clist; c->opt_type != OPT_UNKNOWN; c++)
|
|
{
|
|
if(c->opt_type == OPT_FIXUP_FUNC)
|
|
{
|
|
c->ops.fixup_fn(var);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void config_list_set_defaults(const struct config_list *clist, void *config_data)
|
|
{
|
|
const struct config_list *c;
|
|
for(c = clist; c->opt_type != OPT_UNKNOWN; c++)
|
|
{
|
|
void *var = config_data + c->var_offset;
|
|
switch(c->opt_type)
|
|
{
|
|
case OPT_INT8:
|
|
{
|
|
*(int8_t *)var = c->def.d_int8;
|
|
break;
|
|
}
|
|
case OPT_UINT8:
|
|
{
|
|
*(uint8_t *)var = c->def.d_uint8;
|
|
break;
|
|
}
|
|
case OPT_INT32:
|
|
{
|
|
memcpy(var, &c->def.d_int32, sizeof(int32_t));
|
|
break;
|
|
}
|
|
case OPT_UINT32:
|
|
{
|
|
memcpy(var, &c->def.d_uint32, sizeof(uint32_t));
|
|
break;
|
|
}
|
|
case OPT_STRING:
|
|
{
|
|
char **scfg = var;
|
|
NULLFREE(*scfg);
|
|
if(c->def.d_char)
|
|
{ *scfg = cs_strdup(c->def.d_char); }
|
|
break;
|
|
}
|
|
case OPT_SSTRING:
|
|
{
|
|
char *scfg = var;
|
|
scfg[0] = '\0';
|
|
if(c->def.d_char && cs_strlen(c->def.d_char))
|
|
{ cs_strncpy(scfg, c->def.d_char, c->str_size); }
|
|
break;
|
|
}
|
|
case OPT_HEX_ARRAY:
|
|
{
|
|
uint8_t *hex_array = var;
|
|
memset(hex_array, 0, c->def.array_size);
|
|
break;
|
|
}
|
|
case OPT_FUNC:
|
|
{
|
|
c->ops.process_fn((const char *)c->config_name, "", var, NULL);
|
|
break;
|
|
}
|
|
case OPT_FUNC_EXTRA:
|
|
{
|
|
c->ops.process_fn_extra((const char *)c->config_name, "", var, c->def.d_extra, NULL);
|
|
break;
|
|
}
|
|
case OPT_SAVE_FUNC:
|
|
case OPT_FIXUP_FUNC:
|
|
case OPT_UNKNOWN:
|
|
continue;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void config_list_free_values(const struct config_list *clist, void *config_data)
|
|
{
|
|
const struct config_list *c;
|
|
for(c = clist; c->opt_type != OPT_UNKNOWN; c++)
|
|
{
|
|
void *var = config_data + c->var_offset;
|
|
if(c->opt_type == OPT_STRING)
|
|
{
|
|
char **scfg = var;
|
|
NULLFREE(*scfg);
|
|
}
|
|
if(c->free_value && (c->opt_type == OPT_FUNC || c->opt_type == OPT_FUNC_EXTRA))
|
|
{
|
|
c->free_value(var);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void config_list_gc_values(const struct config_list *clist, void *config_data)
|
|
{
|
|
const struct config_list *c;
|
|
for(c = clist; c->opt_type != OPT_UNKNOWN; c++)
|
|
{
|
|
void *var = config_data + c->var_offset;
|
|
if(c->opt_type == OPT_STRING)
|
|
{
|
|
char **scfg = var;
|
|
add_garbage(*scfg);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
int config_section_is_active(const struct config_sections *sec)
|
|
{
|
|
if(!sec)
|
|
{ return 0; }
|
|
if(sec->config[0].opt_type == OPT_UNKNOWN)
|
|
{ return 0; }
|
|
return 1;
|
|
}
|
|
|
|
const struct config_sections *config_find_section(const struct config_sections *conf, char *section_name)
|
|
{
|
|
const struct config_sections *sec;
|
|
for(sec = conf; sec && sec->section; sec++)
|
|
{
|
|
if(streq(section_name, sec->section))
|
|
{
|
|
return sec;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void config_sections_save(const struct config_sections *conf, FILE *f, void *var)
|
|
{
|
|
const struct config_sections *sec;
|
|
for(sec = conf; sec && sec->section; sec++)
|
|
{
|
|
if(config_section_is_active(sec) && config_list_should_be_saved(sec->config, var))
|
|
{
|
|
fprintf(f, "[%s]\n", sec->section);
|
|
config_list_apply_fixups(sec->config, var);
|
|
config_list_save(f, sec->config, var, cfg.http_full_cfg);
|
|
fprintf(f, "\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void config_sections_set_defaults(const struct config_sections *conf, void *var)
|
|
{
|
|
const struct config_sections *sec;
|
|
for(sec = conf; sec && sec->section; sec++)
|
|
{
|
|
if(config_section_is_active(sec))
|
|
{ config_list_set_defaults(sec->config, var); }
|
|
}
|
|
}
|
|
|
|
void config_sections_free(const struct config_sections *conf, void *var)
|
|
{
|
|
const struct config_sections *sec;
|
|
for(sec = conf; sec && sec->section; sec++)
|
|
{
|
|
if(config_section_is_active(sec))
|
|
{
|
|
config_list_free_values(sec->config, var);
|
|
}
|
|
}
|
|
}
|
|
|
|
void config_set_value(const struct config_sections *conf, char *section, const char *token, char *value, void *var)
|
|
{
|
|
const struct config_sections *sec = config_find_section(conf, section);
|
|
if(!sec)
|
|
{
|
|
fprintf(stderr, "WARNING: Unknown section '%s'.\n", section);
|
|
return;
|
|
}
|
|
if(config_section_is_active(sec))
|
|
{
|
|
if(!config_list_parse(sec->config, token, value, var))
|
|
{
|
|
fprintf(stderr, "WARNING: In section [%s] unknown setting '%s=%s' tried.\n",
|
|
section, token, value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "WARNING: Section is not active '%s'.\n", section);
|
|
}
|
|
}
|
|
|
|
static FILE *__open_config_file(const char *conf_filename, bool die_on_err)
|
|
{
|
|
char filename[256];
|
|
FILE *f = fopen(get_config_filename(filename, sizeof(filename), conf_filename), "r");
|
|
if(!f)
|
|
{
|
|
if(die_on_err)
|
|
{
|
|
fprintf(stderr, "ERROR: Cannot open file \"%s\" (errno=%d %s)", filename, errno, strerror(errno));
|
|
fprintf(stderr, "\n");
|
|
exit(1);
|
|
}
|
|
else
|
|
{
|
|
cs_log_dbg(D_TRACE, "INFO: Cannot open file \"%s\" (errno=%d %s)", filename, errno, strerror(errno));
|
|
}
|
|
return NULL;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
FILE *open_config_file(const char *conf_filename)
|
|
{
|
|
return __open_config_file(conf_filename, false);
|
|
}
|
|
|
|
FILE *open_config_file_or_die(const char *conf_filename)
|
|
{
|
|
return __open_config_file(conf_filename, true);
|
|
}
|
|
|
|
|
|
FILE *create_config_file(const char *conf_filename)
|
|
{
|
|
char temp_file[256];
|
|
get_config_filename(temp_file, sizeof(temp_file), conf_filename);
|
|
if (!cs_strncat(temp_file, ".tmp", sizeof(temp_file))) {
|
|
return NULL;
|
|
}
|
|
FILE *f = fopen(temp_file, "w");
|
|
if(!f)
|
|
{
|
|
cs_log("ERROR: Cannot create file \"%s\" (errno=%d %s)", temp_file, errno, strerror(errno));
|
|
return NULL;
|
|
}
|
|
fprintf(f, "# %s generated automatically by Streamboard OSCam %s\n",
|
|
conf_filename, CS_VERSION);
|
|
fprintf(f, "# Read more: %s/blob/master/Distribution/doc/txt/%s.txt\n\n",
|
|
SCM_URL, conf_filename);
|
|
return f;
|
|
}
|
|
|
|
bool flush_config_file(FILE *f, const char *conf_filename)
|
|
{
|
|
char dst_file[256], tmp_file[256], bak_file[256];
|
|
get_config_filename(dst_file, sizeof(dst_file), conf_filename);
|
|
memcpy(tmp_file, dst_file, sizeof(tmp_file));
|
|
memcpy(bak_file, dst_file, sizeof(bak_file));
|
|
strncat(tmp_file, ".tmp", sizeof(tmp_file) - cs_strlen(tmp_file) - 1);
|
|
strncat(bak_file, ".bak", sizeof(bak_file) - cs_strlen(bak_file) - 1);
|
|
if(f)
|
|
{
|
|
fclose(f);
|
|
}
|
|
return safe_overwrite_with_bak(dst_file, tmp_file, bak_file, cfg.http_overwrite_bak_file);
|
|
}
|