| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2026 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / common tools sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <gpac/config_file.h> | |
| #include <gpac/list.h> | |
| #define MAX_INI_LINE 2046 | |
| typedef struct | |
| { | |
| Bool do_restrict; | |
| char *name; | |
| char *value; | |
| } IniKey; | |
| typedef struct | |
| { | |
| char *section_name; | |
| GF_List *keys; | |
| } IniSection; | |
| struct __tag_config | |
| { | |
| char *fileName; | |
| GF_List *sections; | |
| Bool hasChanged, skip_changes; | |
| }; | |
| static void DelSection(IniSection *ptr) | |
| { | |
| if (!ptr) return; | |
| if (ptr->keys) { | |
| u32 i, count=gf_list_count(ptr->keys); | |
| for (i=0;i<count;i++) { | |
| IniKey *k = (IniKey *) gf_list_get(ptr->keys, i); | |
| if (k->value) gf_free(k->value); | |
| if (k->name) gf_free(k->name); | |
| gf_free(k); | |
| } | |
| gf_list_del(ptr->keys); | |
| } | |
| if (ptr->section_name) gf_free(ptr->section_name); | |
| gf_free(ptr); | |
| } | |
| /*! | |
| * \brief Clear the structure | |
| \param iniFile The structure to clear | |
| */ | |
| static void gf_cfg_clear(GF_Config * iniFile) | |
| { | |
| if (!iniFile) return; | |
| if (iniFile->sections) { | |
| u32 i, count=gf_list_count(iniFile->sections); | |
| for (i=0; i<count; i++) { | |
| IniSection *p = (IniSection *) gf_list_get(iniFile->sections, i); | |
| DelSection(p); | |
| } | |
| gf_list_del(iniFile->sections); | |
| } | |
| if (iniFile->fileName) | |
| gf_free(iniFile->fileName); | |
| memset((void *)iniFile, 0, sizeof(GF_Config)); | |
| } | |
| /*! | |
| * \brief Parses the config file if any and clears the existing structure | |
| */ | |
| GF_Err gf_cfg_parse_config_file(GF_Config * tmp, const char * filePath, const char * file_name) | |
| { | |
| IniSection *p; | |
| IniKey *k=NULL; | |
| FILE *file; | |
| char *line; | |
| u32 line_alloc = MAX_INI_LINE; | |
| char fileName[GF_MAX_PATH]; | |
| Bool line_done = GF_FALSE; | |
| u32 nb_empty_lines=0; | |
| Bool is_opts_probe = GF_FALSE; | |
| Bool in_multiline = GF_FALSE; | |
| gf_cfg_clear(tmp); | |
| if (strstr(file_name, "/all_opts.txt")) { | |
| if (filePath && !strcmp(filePath, "probe")) { | |
| filePath = NULL; | |
| is_opts_probe = GF_TRUE; | |
| } | |
| } | |
| size_t out_size = strlen(file_name); | |
| if (filePath) | |
| out_size += strlen(filePath) + 1; | |
| if (out_size >= sizeof(fileName)) { | |
| return GF_BAD_PARAM; | |
| } | |
| if (filePath && strlen(filePath) && ((filePath[strlen(filePath)-1] == '/') || (filePath[strlen(filePath)-1] == '\\')) ) { | |
| gf_strcpy(fileName, filePath); | |
| gf_strcat(fileName, file_name); | |
| } else if (filePath && strlen(filePath)) { | |
| sprintf(fileName, "%s%c%s", filePath, GF_PATH_SEPARATOR, file_name); | |
| } else { | |
| gf_strcpy(fileName, file_name); | |
| } | |
| tmp->fileName = gf_strdup(fileName); | |
| tmp->sections = gf_list_new(); | |
| file = gf_fopen(fileName, "rt"); | |
| if (!file) | |
| return GF_IO_ERR; | |
| /* load the file */ | |
| p = NULL; | |
| line = (char*)gf_malloc(sizeof(char)*line_alloc); | |
| memset(line, 0, sizeof(char)*line_alloc); | |
| #define FLUSH_EMPTY_LINES \ | |
| if (k&& nb_empty_lines) {\ | |
| u32 klen = (u32) strlen(k->value)+1+nb_empty_lines;\ | |
| k->value = gf_realloc(k->value, sizeof(char)*klen);\ | |
| while (nb_empty_lines) {\ | |
| nb_empty_lines--;\ | |
| gf_strlcat(k->value, "\n", klen);\ | |
| }\ | |
| k=NULL;\ | |
| }\ | |
| nb_empty_lines=0;\ | |
| while (!gf_feof(file)) { | |
| char *ret; | |
| u32 read, nb_pass; | |
| char *fsep; | |
| ret = gf_fgets(line, line_alloc, file); | |
| read = (u32) strlen(line); | |
| nb_pass = 1; | |
| while (read + nb_pass == line_alloc) { | |
| line_alloc += MAX_INI_LINE; | |
| line = (char*)gf_realloc(line, sizeof(char)*line_alloc); | |
| ret = gf_fgets(line+read, MAX_INI_LINE, file); | |
| read = (u32) strlen(line); | |
| nb_pass++; | |
| } | |
| if (!ret) continue; | |
| /* get rid of the end of line stuff */ | |
| fsep = strchr(line, '='); | |
| if (!fsep || (fsep[1]!='@')) { | |
| while (!in_multiline) { | |
| u32 len = (u32) strlen(line); | |
| if (!len) break; | |
| if ((line[len-1] != '\n') && (line[len-1] != '\r')) break; | |
| line[len-1] = 0; | |
| } | |
| } | |
| if (!strlen(line)) { | |
| if (k) nb_empty_lines++; | |
| continue; | |
| } | |
| if (line[0] == '#') { | |
| FLUSH_EMPTY_LINES | |
| continue; | |
| } | |
| /* new section */ | |
| if (line[0] == '[') { | |
| //compat with old arch | |
| if (nb_empty_lines) nb_empty_lines--; | |
| FLUSH_EMPTY_LINES | |
| p = (IniSection *) gf_malloc(sizeof(IniSection)); | |
| p->keys = gf_list_new(); | |
| p->section_name = gf_strdup(line + 1); | |
| p->section_name[strlen(line) - 2] = 0; | |
| while (p->section_name[strlen(p->section_name) - 1] == ']' || p->section_name[strlen(p->section_name) - 1] == ' ') p->section_name[strlen(p->section_name) - 1] = 0; | |
| gf_list_add(tmp->sections, p); | |
| } | |
| else if (strlen(line) && !in_multiline && (strchr(line, '=') != NULL) ) { | |
| if (!p) { | |
| gf_fclose(file); | |
| gf_free(line); | |
| gf_cfg_clear(tmp); | |
| return GF_IO_ERR; | |
| } | |
| FLUSH_EMPTY_LINES | |
| k = (IniKey *) gf_malloc(sizeof(IniKey)); | |
| memset((void *)k, 0, sizeof(IniKey)); | |
| ret = strchr(line, '='); | |
| if (ret) { | |
| ret[0] = 0; | |
| k->name = gf_strdup(line); | |
| while (k->name[0] && (k->name[strlen(k->name) - 1] == ' ')) | |
| k->name[strlen(k->name) - 1] = 0; | |
| ret[0] = '='; | |
| ret += 1; | |
| while (ret[0] == ' ') ret++; | |
| if (ret[0]=='@') { | |
| ret++; | |
| in_multiline = GF_TRUE; | |
| } | |
| if ( ret[0] != 0) { | |
| k->value = gf_strdup(ret); | |
| while (k->value[strlen(k->value) - 1] == ' ') k->value[strlen(k->value) - 1] = 0; | |
| } else { | |
| k->value = gf_strdup(""); | |
| } | |
| line_done = GF_FALSE; | |
| } | |
| gf_list_add(p->keys, k); | |
| if (line_done) k=NULL; | |
| line_done=GF_FALSE; | |
| if (is_opts_probe) { | |
| if (!strcmp(p->section_name, "version") && !strcmp(k->name, "version")) { | |
| break; | |
| } | |
| } | |
| } else if (k) { | |
| u32 l1 = (u32) strlen(k->value); | |
| u32 l2 = (u32) strlen(line); | |
| u32 asize = (l1 + l2 + 1 + nb_empty_lines) ; | |
| k->value = gf_realloc(k->value, sizeof(char) * asize); | |
| l2 += l1 + nb_empty_lines; | |
| while (nb_empty_lines) { | |
| gf_strlcat(k->value, "\n", asize); | |
| nb_empty_lines--; | |
| } | |
| gf_strlcat(k->value, line, asize); | |
| l2 -= 2; //@ and \n | |
| if (k->value[l2] == '@') { | |
| k->value[l2] = 0; | |
| k=NULL; | |
| in_multiline = GF_FALSE; | |
| } | |
| } else { | |
| k = NULL; | |
| } | |
| } | |
| gf_free(line); | |
| gf_fclose(file); | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Config *gf_cfg_force_new(const char *filePath, const char* file_name) { | |
| GF_Config *tmp = (GF_Config *)gf_malloc(sizeof(GF_Config)); | |
| memset((void *)tmp, 0, sizeof(GF_Config)); | |
| gf_cfg_parse_config_file(tmp, filePath, file_name); | |
| return tmp; | |
| } | |
| GF_EXPORT | |
| GF_Config *gf_cfg_new(const char *filePath, const char* file_name) | |
| { | |
| GF_Config *tmp = (GF_Config *)gf_malloc(sizeof(GF_Config)); | |
| memset((void *)tmp, 0, sizeof(GF_Config)); | |
| if (!filePath && !file_name) { | |
| tmp->sections = gf_list_new(); | |
| return tmp; | |
| } | |
| if (gf_cfg_parse_config_file(tmp, filePath, file_name)) { | |
| gf_cfg_clear(tmp); | |
| gf_free(tmp); | |
| tmp = NULL; | |
| } | |
| return tmp; | |
| } | |
| GF_EXPORT | |
| const char * gf_cfg_get_filename(GF_Config *iniFile) | |
| { | |
| if (!iniFile) | |
| return NULL; | |
| return iniFile->fileName; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_cfg_save(GF_Config *iniFile) | |
| { | |
| u32 i, j; | |
| IniSection *sec; | |
| IniKey *key; | |
| FILE *file; | |
| if (!iniFile->hasChanged) return GF_OK; | |
| if (iniFile->skip_changes) return GF_OK; | |
| if (!iniFile->fileName) return GF_OK; | |
| file = gf_fopen(iniFile->fileName, "wt"); | |
| if (!file) return GF_IO_ERR; | |
| i=0; | |
| while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) { | |
| /*Temporary sections are not saved*/ | |
| if (!strnicmp(sec->section_name, "temp", 4)) continue; | |
| gf_fprintf(file, "[%s]\n", sec->section_name); | |
| j=0; | |
| while ( (key = (IniKey *) gf_list_enum(sec->keys, &j)) ) { | |
| if (strchr(key->value, '\n')) { | |
| gf_fprintf(file, "%s=@%s@\n", key->name, key->value); | |
| } else { | |
| gf_fprintf(file, "%s=%s\n", key->name, key->value); | |
| } | |
| } | |
| } | |
| gf_fclose(file); | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_cfg_discard_changes(GF_Config *iniFile) | |
| { | |
| if (!iniFile) return GF_BAD_PARAM; | |
| iniFile->skip_changes = GF_TRUE; | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| void gf_cfg_del(GF_Config *iniFile) | |
| { | |
| if (!iniFile) return; | |
| gf_cfg_save(iniFile); | |
| gf_cfg_clear(iniFile); | |
| gf_free(iniFile); | |
| } | |
| #if 0 //unused | |
| void gf_cfg_remove(GF_Config *iniFile) | |
| { | |
| if (!iniFile) return; | |
| gf_file_delete(iniFile->fileName); | |
| gf_cfg_clear(iniFile); | |
| gf_free(iniFile); | |
| } | |
| #endif | |
| const char *gf_cfg_get_key_internal(GF_Config *iniFile, const char *secName, const char *keyName, Bool restricted_only) | |
| { | |
| u32 i; | |
| IniSection *sec; | |
| IniKey *key; | |
| if (!iniFile || !iniFile->sections) | |
| return NULL; | |
| i=0; | |
| while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) { | |
| if (!strcmp(secName, sec->section_name)) goto get_key; | |
| } | |
| return NULL; | |
| get_key: | |
| i=0; | |
| while ( (key = (IniKey *) gf_list_enum(sec->keys, &i)) ) { | |
| if (!strcmp(key->name, keyName)) { | |
| if (!restricted_only || key->do_restrict) | |
| return key->value; | |
| return NULL; | |
| } | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| const char *gf_cfg_get_key(GF_Config *iniFile, const char *secName, const char *keyName) | |
| { | |
| return gf_cfg_get_key_internal(iniFile, secName, keyName, GF_FALSE); | |
| } | |
| #if 0 //unused | |
| const char *gf_cfg_get_ikey(GF_Config *iniFile, const char *secName, const char *keyName) | |
| { | |
| u32 i; | |
| IniSection *sec; | |
| IniKey *key; | |
| i=0; | |
| while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) { | |
| if (!stricmp(secName, sec->section_name)) goto get_key; | |
| } | |
| return NULL; | |
| get_key: | |
| i=0; | |
| while ( (key = (IniKey *) gf_list_enum(sec->keys, &i)) ) { | |
| if (!stricmp(key->name, keyName)) return key->value; | |
| } | |
| return NULL; | |
| } | |
| #endif | |
| GF_Err gf_cfg_set_key_internal(GF_Config *iniFile, const char *secName, const char *keyName, const char *keyValue, Bool is_restrict) | |
| { | |
| u32 i; | |
| Bool has_changed = GF_TRUE; | |
| IniSection *sec; | |
| IniKey *key; | |
| if (!iniFile || !secName || !keyName) return GF_BAD_PARAM; | |
| if (!strnicmp(secName, "temp", 4)) has_changed = GF_FALSE; | |
| i=0; | |
| while ((sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) { | |
| if (!strcmp(secName, sec->section_name)) goto get_key; | |
| } | |
| /* need a new key */ | |
| sec = (IniSection *) gf_malloc(sizeof(IniSection)); | |
| sec->section_name = gf_strdup(secName); | |
| sec->keys = gf_list_new(); | |
| if (has_changed) | |
| iniFile->hasChanged = GF_TRUE; | |
| gf_list_add(iniFile->sections, sec); | |
| get_key: | |
| i=0; | |
| while ( (key = (IniKey *) gf_list_enum(sec->keys, &i) ) ) { | |
| if (!strcmp(key->name, keyName)) goto set_value; | |
| } | |
| if (!keyValue) return GF_OK; | |
| /* need a new key */ | |
| key = (IniKey *) gf_malloc(sizeof(IniKey)); | |
| key->name = gf_strdup(keyName); | |
| key->value = gf_strdup(keyValue); | |
| if (has_changed) | |
| iniFile->hasChanged = GF_TRUE; | |
| gf_list_add(sec->keys, key); | |
| key->do_restrict = is_restrict; | |
| return GF_OK; | |
| set_value: | |
| if (!keyValue) { | |
| gf_list_del_item(sec->keys, key); | |
| if (key->name) gf_free(key->name); | |
| if (key->value) gf_free(key->value); | |
| gf_free(key); | |
| if (has_changed) | |
| iniFile->hasChanged = GF_TRUE; | |
| return GF_OK; | |
| } | |
| key->do_restrict = is_restrict; | |
| /* same value, don't update */ | |
| if (!strcmp(key->value, keyValue)) return GF_OK; | |
| if (key->value) gf_free(key->value); | |
| key->value = gf_strdup(keyValue); | |
| if (has_changed) | |
| iniFile->hasChanged = GF_TRUE; | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_cfg_set_key(GF_Config *iniFile, const char *secName, const char *keyName, const char *keyValue) | |
| { | |
| return gf_cfg_set_key_internal(iniFile, secName, keyName, keyValue, GF_FALSE); | |
| } | |
| GF_EXPORT | |
| u32 gf_cfg_get_section_count(GF_Config *iniFile) | |
| { | |
| return gf_list_count(iniFile->sections); | |
| } | |
| GF_EXPORT | |
| const char *gf_cfg_get_section_name(GF_Config *iniFile, u32 secIndex) | |
| { | |
| IniSection *is = (IniSection *) gf_list_get(iniFile->sections, secIndex); | |
| if (!is) return NULL; | |
| return is->section_name; | |
| } | |
| GF_EXPORT | |
| u32 gf_cfg_get_key_count(GF_Config *iniFile, const char *secName) | |
| { | |
| u32 i = 0; | |
| IniSection *sec; | |
| while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i)) ) { | |
| if (!strcmp(secName, sec->section_name)) return gf_list_count(sec->keys); | |
| } | |
| return 0; | |
| } | |
| GF_EXPORT | |
| const char *gf_cfg_get_key_name(GF_Config *iniFile, const char *secName, u32 keyIndex) | |
| { | |
| u32 i = 0; | |
| IniSection *sec; | |
| while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i) ) ) { | |
| if (!strcmp(secName, sec->section_name)) { | |
| IniKey *key = (IniKey *) gf_list_get(sec->keys, keyIndex); | |
| return key ? key->name : NULL; | |
| } | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| void gf_cfg_del_section(GF_Config *iniFile, const char *secName) | |
| { | |
| u32 i; | |
| IniSection *p; | |
| if (!iniFile) return; | |
| i = 0; | |
| while ((p = (IniSection*)gf_list_enum(iniFile->sections, &i))) { | |
| if (!strcmp(secName, p->section_name)) { | |
| DelSection(p); | |
| gf_list_rem(iniFile->sections, i-1); | |
| iniFile->hasChanged = GF_TRUE; | |
| return; | |
| } | |
| } | |
| } | |
| #if 0 //unused | |
| GF_Err gf_cfg_insert_key(GF_Config *iniFile, const char *secName, const char *keyName, const char *keyValue, u32 index) | |
| { | |
| u32 i; | |
| IniSection *sec; | |
| IniKey *key; | |
| if (!iniFile || !secName || !keyName|| !keyValue) return GF_BAD_PARAM; | |
| i=0; | |
| while ( (sec = (IniSection *) gf_list_enum(iniFile->sections, &i) ) ) { | |
| if (!strcmp(secName, sec->section_name)) break; | |
| } | |
| if (!sec) return GF_BAD_PARAM; | |
| i=0; | |
| while ( (key = (IniKey *) gf_list_enum(sec->keys, &i) ) ) { | |
| if (!strcmp(key->name, keyName)) return GF_BAD_PARAM; | |
| } | |
| key = (IniKey *) gf_malloc(sizeof(IniKey)); | |
| key->name = gf_strdup(keyName); | |
| key->value = gf_strdup(keyValue); | |
| gf_list_insert(sec->keys, key, index); | |
| iniFile->hasChanged = GF_TRUE; | |
| return GF_OK; | |
| } | |
| #endif | |
| #if 0 //unused | |
| const char *gf_cfg_get_sub_key(GF_Config *iniFile, const char *secName, const char *keyName, u32 sub_index) | |
| { | |
| u32 j; | |
| char *subKeyValue, *returnKey; | |
| char *keyValue; | |
| keyValue = gf_strdup(gf_cfg_get_key(iniFile, secName, keyName)); | |
| if (!keyValue) { | |
| return NULL; | |
| } | |
| j = 0; | |
| subKeyValue = strtok((char*)keyValue,";"); | |
| while (subKeyValue!=NULL) { | |
| if (j==sub_index) { | |
| returnKey = gf_strdup(subKeyValue); | |
| gf_free(keyValue); | |
| return returnKey; | |
| } | |
| j++; | |
| subKeyValue = strtok (NULL, ";"); | |
| } | |
| gf_free(keyValue); | |
| return NULL; | |
| } | |
| #endif | |
| GF_Err gf_cfg_set_filename(GF_Config *iniFile, const char * fileName) | |
| { | |
| if (!fileName) return GF_OK; | |
| if (iniFile->fileName) gf_free(iniFile->fileName); | |
| iniFile->fileName = gf_strdup(fileName); | |
| return iniFile->fileName ? GF_OK : GF_OUT_OF_MEM; | |
| } |