/* * Copyright (C) 2000-2004 the xine project * * This file is part of xine, a free video player. * * xine is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * xine 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * $Id: configfile.c,v 1.72 2004/12/13 11:11:27 mlampard Exp $ * * config object (was: file) management - implementation * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include "configfile.h" #define LOG_MODULE "configfile" #define LOG_VERBOSE /* #define LOG */ #include "xineutils.h" #include "xine_internal.h" typedef struct { const char *old; const char *new; } config_entry_translation_t; static config_entry_translation_t config_entry_translation[] = { { "audio.a52_pass_through", "" }, { "audio.alsa_a52_device", "audio.device.alsa_passthrough_device" }, { "audio.alsa_default_device", "audio.device.alsa_default_device" }, { "audio.alsa_front_device", "audio.device.alsa_front_device" }, { "audio.alsa_mixer_name", "audio.device.alsa_mixer_name" }, { "audio.alsa_mmap_enable", "audio.device.alsa_mmap_enable" }, { "audio.alsa_surround40_device", "audio.device.alsa_surround40_device" }, { "audio.alsa_surround51_device", "audio.device.alsa_surround51_device" }, { "audio.av_sync_method", "audio.synchronization.av_sync_method" }, { "audio.directx_device", "" }, { "audio.esd_latency", "audio.device.esd_latency" }, { "audio.five_channel", "" }, { "audio.five_lfe_channel", "" }, { "audio.force_rate", "audio.synchronization.force_rate" }, { "audio.four_channel", "" }, { "audio.four_lfe_channel", "" }, { "audio.irixal_gap_tolerance", "audio.device.irixal_gap_tolerance" }, { "audio.mixer_name", "" }, { "audio.mixer_number", "audio.device.oss_mixer_number" }, { "audio.mixer_volume", "audio.volume.mixer_volume" }, { "audio.num_buffers", "engine.buffers.audio_num_buffers" }, { "audio.oss_device_name", "audio.device.oss_device_name" }, { "audio.oss_device_num", "" }, { "audio.oss_device_number", "audio.device.oss_device_number" }, { "audio.oss_pass_through_bug", "" }, { "audio.passthrough_offset", "audio.synchronization.passthrough_offset" }, { "audio.remember_volume", "audio.volume.remember_volume" }, { "audio.resample_mode", "audio.synchronization.resample_mode" }, { "audio.speaker_arrangement", "audio.output.speaker_arrangement" }, { "audio.sun_audio_device", "audio.device.sun_audio_device" }, { "codec.a52_dynrng", "audio.a52.dynamic_range" }, { "codec.a52_level", "audio.a52.level" }, { "codec.a52_surround_downmix", "audio.a52.surround_downmix" }, { "codec.ffmpeg_pp_quality", "video.processing.ffmpeg_pp_quality" }, { "codec.real_codecs_path", "decoder.external.real_codecs_path" }, { "codec.win32_path", "decoder.external.win32_codecs_path" }, { "dxr3.alt_play_mode", "dxr3.playback.alt_play_mode" }, { "dxr3.color_interval", "dxr3.output.keycolor_interval" }, { "dxr3.correct_durations", "dxr3.playback.correct_durations" }, { "dxr3.devicename", "" }, { "dxr3.enc_add_bars", "dxr3.encoding.add_bars" }, { "dxr3.enc_alt_play_mode", "dxr3.encoding.alt_play_mode" }, { "dxr3.enc_swap_fields", "dxr3.encoding.swap_fields" }, { "dxr3.encoder", "dxr3.encoding.encoder" }, { "dxr3.fame_quality", "dxr3.encoding.fame_quality" }, { "dxr3.keycolor", "dxr3.output.keycolor" }, { "dxr3.lavc_bitrate", "dxr3.encoding.lavc_bitrate" }, { "dxr3.lavc_qmax", "dxr3.encoding.lavc_qmax" }, { "dxr3.lavc_qmin", "dxr3.encoding.lavc_qmin" }, { "dxr3.lavc_quantizer", "dxr3.encoding.lavc_quantizer" }, { "dxr3.preferred_tvmode", "dxr3.output.tvmode" }, { "dxr3.rte_bitrate", "dxr3.encoding.rte_bitrate" }, { "dxr3.shrink_overlay_area", "dxr3.output.shrink_overlay_area" }, { "dxr3.sync_every_frame", "dxr3.playback.sync_every_frame" }, { "dxr3.videoout_mode", "dxr3.output.mode" }, { "input.cdda_cddb_cachedir", "media.audio_cd.cddb_cachedir" }, { "input.cdda_cddb_port", "media.audio_cd.cddb_port" }, { "input.cdda_cddb_server", "media.audio_cd.cddb_server" }, { "input.cdda_device", "media.audio_cd.device" }, { "input.cdda_use_cddb", "media.audio_cd.use_cddb" }, { "input.css_cache_path", "media.dvd.css_cache_path" }, { "input.css_decryption_method", "media.dvd.css_decryption_method" }, { "input.drive_slowdown", "media.audio_cd.drive_slowdown" }, { "input.dvb_last_channel_enable", "media.dvb.remember_channel" }, { "input.dvb_last_channel_watched", "media.dvb.last_channel" }, { "input.dvbdisplaychan", "media.dvb.display_channel" }, { "input.dvbzoom", "media.dvb.zoom" }, { "input.dvb_adapternum", "media.dvb.adapter"}, { "input.dvd_device", "media.dvd.device" }, { "input.dvd_language", "media.dvd.language" }, { "input.dvd_raw_device", "media.dvd.raw_device" }, { "input.dvd_region", "media.dvd.region" }, { "input.dvd_seek_behaviour", "media.dvd.seek_behaviour" }, { "input.dvd_skip_behaviour", "media.dvd.skip_behaviour" }, { "input.dvd_use_readahead", "media.dvd.readahead" }, { "input.file_hidden_files", "media.files.show_hidden_files" }, { "input.file_origin_path", "media.files.origin_path" }, { "input.http_proxy_host", "media.network.http_proxy_host" }, { "input.http_proxy_password", "media.network.http_proxy_password" }, { "input.http_proxy_port", "media.network.http_proxy_port" }, { "input.http_proxy_user", "media.network.http_proxy_user" }, { "input.mms_network_bandwidth", "media.network.bandwidth" }, { "input.mms_protocol", "media.network.mms_protocol" }, { "input.pvr_device", "media.wintv_pvr.device" }, { "input.v4l_radio_device_path", "media.video4linux.radio_device" }, { "input.v4l_video_device_path", "media.video4linux.video_device" }, { "input.vcd_device", "media.vcd.device" }, { "misc.cc_center", "subtitles.closedcaption.center" }, { "misc.cc_enabled", "subtitles.closedcaption.enabled" }, { "misc.cc_font", "subtitles.closedcaption.font" }, { "misc.cc_font_size", "subtitles.closedcaption.font_size" }, { "misc.cc_italic_font", "subtitles.closedcaption.italic_font" }, { "misc.cc_scheme", "subtitles.closedcaption.scheme" }, { "misc.demux_strategy", "engine.demux.strategy" }, { "misc.memcpy_method", "engine.performance.memcpy_method" }, { "misc.osd_text_palette", "ui.osd.text_palette" }, { "misc.save_dir", "media.capture.save_dir" }, { "misc.spu_font", "subtitles.separate.font" }, { "misc.spu_src_encoding", "subtitles.separate.src_encoding" }, { "misc.spu_subtitle_size", "subtitles.separate.subtitle_size" }, { "misc.spu_use_unscaled_osd", "subtitles.separate.use_unscaled_osd" }, { "misc.spu_vertical_offset", "subtitles.separate.vertical_offset" }, { "misc.sub_timeout", "subtitles.separate.timeout" }, { "post.goom_csc_method", "effects.goom.csc_method" }, { "post.goom_fps", "effects.goom.fps" }, { "post.goom_height", "effects.goom.height" }, { "post.goom_width", "effects.goom.width" }, { "vcd.autoadvance", "media.vcd.autoadvance" }, { "vcd.autoplay", "media.vcd.autoplay" }, { "vcd.comment_format", "media.vcd.comment_format" }, { "vcd.debug", "media.vcd.debug" }, { "vcd.default_device", "media.vcd.device" }, { "vcd.length_reporting", "media.vcd.length_reporting" }, { "vcd.show_rejected", "media.vcd.show_rejected" }, { "vcd.title_format", "media.vcd.title_format" }, { "video.XV_DOUBLE_BUFFER", "video.device.xv_double_buffer" }, { "video.XV_FILTER", "video.device.xv_filter" }, { "video.deinterlace_method", "video.output.xv_deinterlace_method" }, { "video.disable_exact_osd_alpha_blending", "video.output.disable_exact_alphablend" }, { "video.disable_scaling", "video.output.disable_scaling" }, { "video.fb_device", "video.device.fb_device" }, { "video.fb_gamma", "video.output.fb_gamma" }, { "video.horizontal_position", "video.output.horizontal_position" }, { "video.num_buffers", "engine.buffers.video_num_buffers" }, { "video.opengl_double_buffer", "video.device.opengl_double_buffer" }, { "video.opengl_gamma", "video.output.opengl_gamma" }, { "video.opengl_min_fps", "video.output.opengl_min_fps" }, { "video.opengl_renderer", "video.output.opengl_renderer" }, { "video.pgx32_device", "video.device.pgx32_device" }, { "video.pgx64_brightness", "video.output.pgx64_brightness" }, { "video.pgx64_chromakey_en", "video.device.pgx64_chromakey_en" }, { "video.pgx64_colour_key", "video.device.pgx64_colour_key" }, { "video.pgx64_device", "" }, { "video.pgx64_multibuf_en", "video.device.pgx64_multibuf_en" }, { "video.pgx64_overlay_mode", "" }, { "video.pgx64_saturation", "video.output.pgx64_saturation" }, { "video.sdl_hw_accel", "video.device.sdl_hw_accel" }, { "video.syncfb_default_repeat", "video.device.syncfb_default_repeat" }, { "video.syncfb_device", "video.device.syncfb_device" }, { "video.unichrome_cpu_save", "video.device.unichrome_cpu_save" }, { "video.vertical_position", "video.output.vertical_position" }, { "video.vidix_blue_intensity", "video.output.vidix_blue_intensity" }, { "video.vidix_colour_key_blue", "video.device.vidix_colour_key_blue" }, { "video.vidix_colour_key_green", "video.device.vidix_colour_key_green" }, { "video.vidix_colour_key_red", "video.device.vidix_colour_key_red" }, { "video.vidix_green_intensity", "video.output.vidix_green_intensity" }, { "video.vidix_red_intensity", "video.output.vidix_red_intensity" }, { "video.vidix_use_double_buffer", "video.device.vidix_double_buffer" }, { "video.vidixfb_device", "video.device.vidixfb_device" }, { "video.warn_discarded_threshold", "engine.performance.warn_discarded_threshold" }, { "video.warn_skipped_threshold", "engine.performance.warn_skipped_threshold" }, { "video.xshm_gamma", "video.output.xshm_gamma" }, { "video.xv_autopaint_colorkey", "video.device.xv_autopaint_colorkey" }, { "video.xv_colorkey", "video.device.xv_colorkey" }, { "video.xv_pitch_alignment", "video.device.xv_pitch_alignment" }, { "video.xvmc_more_frames", "video.device.xvmc_more_frames" }, { "video.xvmc_nvidia_color_fix", "video.device.xvmc_nvidia_color_fix" } }; static int config_section_enum(const char *sect) { static char *known_section[] = { "gui", "ui", "audio", "video", "dxr3", "input", "media", "codec", "decoder", "subtitles", "post", "effects", "engine", "misc", NULL }; int i = 0; while (known_section[i]) if (strcmp(sect, known_section[i++]) == 0) return i; return i + 1; } static void config_key_split(const char *key, char **base, char **section, char **subsect, char **name) { char *parse; *base = strdup(key); if ((parse = strchr(*base, '.'))) { *section = *base; *parse = '\0'; parse++; if ((*name = strchr(parse, '.'))) { *subsect = parse; **name = '\0'; (*name)++; } else { *subsect = NULL; *name = parse; } } else { *section = NULL; *subsect = NULL; *name = parse; } } static void config_insert(config_values_t *this, cfg_entry_t *new_entry) { cfg_entry_t *cur, *prev; char *new_base, *new_section, *new_subsect, *new_name; char *cur_base, *cur_section, *cur_subsect, *cur_name; /* extract parts of the new key */ config_key_split(new_entry->key, &new_base, &new_section, &new_subsect, &new_name); /* search right position */ cur_base = NULL; for (cur = this->first, prev = NULL; cur; prev = cur, cur = cur->next) { /* extract parts of the cur key */ free(cur_base); config_key_split(cur->key, &cur_base, &cur_section, &cur_subsect, &cur_name); /* sort by section name */ if (!new_section && cur_section) break; if ( new_section && !cur_section) continue; if ( new_section && cur_section) { int new_sec_num = config_section_enum(new_section); int cur_sec_num = config_section_enum(cur_section); int cmp = strcmp(new_section, cur_section); if (new_sec_num < cur_sec_num) break; if (new_sec_num > cur_sec_num) continue; if (cmp < 0) break; if (cmp > 0) continue; } /* sort by subsection name */ if (!new_subsect && cur_subsect) break; if ( new_subsect && !cur_subsect) continue; if ( new_subsect && cur_subsect) { int cmp = strcmp(new_subsect, cur_subsect); if (cmp < 0) break; if (cmp > 0) continue; } /* sort by experience level */ if (new_entry->exp_level < cur->exp_level) break; if (new_entry->exp_level > cur->exp_level) continue; /* sort by entry name */ if (!new_name && cur_name) break; if ( new_name && !cur_name) continue; if ( new_name && cur_name) { int cmp = strcmp(new_name, cur_name); if (cmp < 0) break; if (cmp > 0) continue; } break; } free(new_base); free(cur_base); new_entry->next = cur; if (!cur) this->last = new_entry; if (prev) prev->next = new_entry; else this->first = new_entry; } static cfg_entry_t *__config_add (config_values_t *this, const char *key, int exp_level) { cfg_entry_t *entry; entry = (cfg_entry_t *) xine_xmalloc (sizeof (cfg_entry_t)); entry->config = this; entry->key = strdup(key); entry->type = XINE_CONFIG_TYPE_UNKNOWN; entry->unknown_value = NULL; entry->str_value = NULL; entry->exp_level = exp_level; config_insert(this, entry); lprintf ("add entry key=%s\n", key); return entry; } static const char *__config_translate_key (const char *key) { /* Returns translated key or, if no translation found, NULL. * Translated key may be in a static buffer allocated within this function. * NOT re-entrant; assumes that config_lock is held. */ unsigned trans; static char *newkey = NULL; /* first, special-case the decoder entries (so that new ones can be added * without requiring modification of the translation table) */ if (!strncmp (key, "decoder.", 8) && !strcmp (key + (trans = strlen (key)) - 9, "_priority")) { newkey = realloc (newkey, trans + 27 - 17); /* diff. in string lengths */ sprintf (newkey, "engine.decoder_priorities.%.*s", trans - 17, key + 8); return newkey; } /* search the translation table... */ for (trans = 0; trans < sizeof(config_entry_translation) / sizeof(config_entry_translation[0]); trans++) if (config_entry_translation[trans].new[0] && strcmp(key, config_entry_translation[trans].old) == 0) return config_entry_translation[trans].new; return NULL; } static void __config_lookup_entry_int (config_values_t *this, const char *key, cfg_entry_t **entry, cfg_entry_t **prev) { int trans; /* try twice at most (second time with translation from old key name) */ for (trans = 2; trans; --trans) { *entry = this->first; *prev = NULL; while (*entry && strcmp((*entry)->key, key)) { *prev = *entry; *entry = (*entry)->next; } if (*entry) return; /* we did not find a match, maybe this is an old config entry name * trying to translate */ key = __config_translate_key(key); if (!key) return; } } /* * external interface */ static cfg_entry_t *__config_lookup_entry(config_values_t *this, const char *key) { cfg_entry_t *entry, *prev; pthread_mutex_lock(&this->config_lock); __config_lookup_entry_int(this, key, &entry, &prev); pthread_mutex_unlock(&this->config_lock); return entry; } static char *__config_register_string (config_values_t *this, const char *key, const char *def_value, const char *description, const char *help, int exp_level, xine_config_cb_t changed_cb, void *cb_data) { cfg_entry_t *entry, *prev; _x_assert(key); _x_assert(def_value); lprintf ("registering %s\n", key); /* make sure this entry exists, create it if not */ pthread_mutex_lock(&this->config_lock); __config_lookup_entry_int(this, key, &entry, &prev); if (!entry) { entry = __config_add (this, key, exp_level); entry->unknown_value = strdup(def_value); } else { if (!entry->next) this->last = prev; if (!prev) this->first = entry->next; else prev->next = entry->next; entry->exp_level = exp_level; config_insert(this, entry); } /* convert entry to string type if necessary */ if (entry->type != XINE_CONFIG_TYPE_STRING) { entry->type = XINE_CONFIG_TYPE_STRING; /* * if there is no unknown_value (made with register_empty) set * it to default value */ if(!entry->unknown_value) entry->unknown_value = strdup(def_value); entry->str_value = strdup(entry->unknown_value); } else free (entry->str_default); /* fill out rest of struct */ entry->str_default = strdup(def_value); entry->description = description; entry->help = help; entry->callback = changed_cb; entry->callback_data = cb_data; pthread_mutex_unlock(&this->config_lock); return entry->str_value; } static int __config_register_num (config_values_t *this, const char *key, int def_value, const char *description, const char *help, int exp_level, xine_config_cb_t changed_cb, void *cb_data) { cfg_entry_t *entry, *prev; _x_assert(key); lprintf ("registering %s\n", key); /* make sure this entry exists, create it if not */ pthread_mutex_lock(&this->config_lock); __config_lookup_entry_int(this, key, &entry, &prev); if (!entry) { entry = __config_add (this, key, exp_level); entry->unknown_value = NULL; } else { if (!entry->next) this->last = prev; if (!prev) this->first = entry->next; else prev->next = entry->next; entry->exp_level = exp_level; config_insert(this, entry); } /* convert entry to num type if necessary */ if (entry->type != XINE_CONFIG_TYPE_NUM) { if (entry->type == XINE_CONFIG_TYPE_STRING) { free (entry->str_value); free (entry->str_default); } entry->type = XINE_CONFIG_TYPE_NUM; if (entry->unknown_value) sscanf (entry->unknown_value, "%d", &entry->num_value); else entry->num_value = def_value; } /* fill out rest of struct */ entry->num_default = def_value; entry->description = description; entry->help = help; entry->callback = changed_cb; entry->callback_data = cb_data; pthread_mutex_unlock(&this->config_lock); return entry->num_value; } static int __config_register_bool (config_values_t *this, const char *key, int def_value, const char *description, const char *help, int exp_level, xine_config_cb_t changed_cb, void *cb_data) { cfg_entry_t *entry, *prev; _x_assert(key); lprintf ("registering %s\n", key); /* make sure this entry exists, create it if not */ pthread_mutex_lock(&this->config_lock); __config_lookup_entry_int(this, key, &entry, &prev); if (!entry) { entry = __config_add (this, key, exp_level); entry->unknown_value = NULL; } else { if (!entry->next) this->last = prev; if (!prev) this->first = entry->next; else prev->next = entry->next; entry->exp_level = exp_level; config_insert(this, entry); } /* convert entry to bool type if necessary */ if (entry->type != XINE_CONFIG_TYPE_BOOL) { if (entry->type == XINE_CONFIG_TYPE_STRING) { free (entry->str_value); free (entry->str_default); } entry->type = XINE_CONFIG_TYPE_BOOL; if (entry->unknown_value) sscanf (entry->unknown_value, "%d", &entry->num_value); else entry->num_value = def_value; } /* fill out rest of struct */ entry->num_default = def_value; entry->description = description; entry->help = help; entry->callback = changed_cb; entry->callback_data = cb_data; pthread_mutex_unlock(&this->config_lock); return entry->num_value; } static int __config_register_range (config_values_t *this, const char *key, int def_value, int min, int max, const char *description, const char *help, int exp_level, xine_config_cb_t changed_cb, void *cb_data) { cfg_entry_t *entry, *prev; _x_assert(key); lprintf ("registering range %s\n", key); /* make sure this entry exists, create it if not */ pthread_mutex_lock(&this->config_lock); __config_lookup_entry_int(this, key, &entry, &prev); if (!entry) { entry = __config_add (this, key, exp_level); entry->unknown_value = NULL; } else { if (!entry->next) this->last = prev; if (!prev) this->first = entry->next; else prev->next = entry->next; entry->exp_level = exp_level; config_insert(this, entry); } /* convert entry to range type if necessary */ if (entry->type != XINE_CONFIG_TYPE_RANGE) { if (entry->type == XINE_CONFIG_TYPE_STRING) { free (entry->str_value); free (entry->str_default); } entry->type = XINE_CONFIG_TYPE_RANGE; if (entry->unknown_value) sscanf (entry->unknown_value, "%d", &entry->num_value); else entry->num_value = def_value; } /* fill out rest of struct */ entry->num_default = def_value; entry->range_min = min; entry->range_max = max; entry->description = description; entry->help = help; entry->callback = changed_cb; entry->callback_data = cb_data; pthread_mutex_unlock(&this->config_lock); return entry->num_value; } static int __config_parse_enum (const char *str, char **values) { char **value; int i; value = values; i = 0; while (*value) { lprintf ("parse enum, >%s< ?= >%s<\n", *value, str); if (!strcmp (*value, str)) return i; value++; i++; } lprintf ("warning, >%s< is not a valid enum here, using 0\n", str); return 0; } static int __config_register_enum (config_values_t *this, const char *key, int def_value, char **values, const char *description, const char *help, int exp_level, xine_config_cb_t changed_cb, void *cb_data) { cfg_entry_t *entry, *prev; _x_assert(key); _x_assert(values); lprintf ("registering enum %s\n", key); /* make sure this entry exists, create it if not */ pthread_mutex_lock(&this->config_lock); __config_lookup_entry_int(this, key, &entry, &prev); if (!entry) { entry = __config_add (this, key, exp_level); entry->unknown_value = NULL; } else { if (!entry->next) this->last = prev; if (!prev) this->first = entry->next; else prev->next = entry->next; entry->exp_level = exp_level; config_insert(this, entry); } /* convert entry to enum type if necessary */ if (entry->type != XINE_CONFIG_TYPE_ENUM) { if (entry->type == XINE_CONFIG_TYPE_STRING) { free (entry->str_value); free (entry->str_default); } entry->type = XINE_CONFIG_TYPE_ENUM; if (entry->unknown_value) entry->num_value = __config_parse_enum (entry->unknown_value, values); else entry->num_value = def_value; } /* fill out rest of struct */ entry->num_default = def_value; entry->enum_values = values; entry->description = description; entry->help = help; entry->callback = changed_cb; entry->callback_data = cb_data; pthread_mutex_unlock(&this->config_lock); return entry->num_value; } static void __config_shallow_copy(xine_cfg_entry_t *dest, cfg_entry_t *src) { dest->key = src->key; dest->type = src->type; dest->unknown_value = src->unknown_value; dest->str_value = src->str_value; dest->str_default = src->str_default; dest->num_value = src->num_value; dest->num_default = src->num_default; dest->range_min = src->range_min; dest->range_max = src->range_max; dest->enum_values = src->enum_values; dest->description = src->description; dest->help = src->help; dest->exp_level = src->exp_level; dest->callback = src->callback; dest->callback_data = src->callback_data; } static void __config_update_num (config_values_t *this, const char *key, int value) { cfg_entry_t *entry; entry = this->lookup_entry (this, key); lprintf ("updating %s to %d\n", key, value); if (!entry) { lprintf ("WARNING! tried to update unknown key %s (to %d)\n", key, value); return; } if ((entry->type == XINE_CONFIG_TYPE_UNKNOWN) || (entry->type == XINE_CONFIG_TYPE_STRING)) { printf ("configfile: error - tried to update non-num type %d (key %s, value %d)\n", entry->type, entry->key, value); return; } pthread_mutex_lock(&this->config_lock); entry->num_value = value; if (entry->callback) { xine_cfg_entry_t cb_entry; __config_shallow_copy(&cb_entry, entry); /* do not enter the callback from within a locked context */ pthread_mutex_unlock(&this->config_lock); entry->callback (entry->callback_data, &cb_entry); } else pthread_mutex_unlock(&this->config_lock); } static void __config_update_string (config_values_t *this, const char *key, const char *value) { cfg_entry_t *entry; char *str_free = NULL; lprintf ("updating %s to %s\n", key, value); entry = this->lookup_entry (this, key); if (!entry) { printf ("configfile: error - tried to update unknown key %s (to %s)\n", key, value); return; } /* if an enum is updated with a string, we convert the string to * its index and use update number */ if (entry->type == XINE_CONFIG_TYPE_ENUM) { __config_update_num(this, key, __config_parse_enum(value, entry->enum_values)); return; } if (entry->type != XINE_CONFIG_TYPE_STRING) { printf ("configfile: error - tried to update non-string type %d (key %s, value %s)\n", entry->type, entry->key, value); return; } pthread_mutex_lock(&this->config_lock); if (value != entry->str_value) { str_free = entry->str_value; entry->str_value = strdup(value); } if (entry->callback) { xine_cfg_entry_t cb_entry; __config_shallow_copy(&cb_entry, entry); /* FIXME: find a solution which does not enter the callback with the lock acquired, * but does also handle the char* leak- and race-free without unnecessary string copying */ entry->callback (entry->callback_data, &cb_entry); } if (str_free) free(str_free); pthread_mutex_unlock(&this->config_lock); } /* * load/save config data from/to afile (e.g. $HOME/.xine/config) */ void xine_config_load (xine_t *xine, const char *filename) { config_values_t *this = xine->config; FILE *f_config; lprintf ("reading from file '%s'\n", filename); f_config = fopen (filename, "r"); if (f_config) { char line[1024]; char *value; while (fgets (line, 1023, f_config)) { line[strlen(line)-1]= (char) 0; /* eliminate lf */ if (line[0] == '#') continue; if (line[0] == '.') { if (strncmp(line, ".version:", 9) == 0) { sscanf(line + 9, "%d", &this->current_version); if (this->current_version > CONFIG_FILE_VERSION) xine_log(xine, XINE_LOG_MSG, _("The current config file has been modified by a newer version of xine.")); } continue; } if ((value = strchr (line, ':'))) { cfg_entry_t *entry; *value = (char) 0; value++; if (!(entry = __config_lookup_entry(this, line))) { const char *key = line; pthread_mutex_lock(&this->config_lock); if (this->current_version < CONFIG_FILE_VERSION) { /* old config file -> let's see if we have to rename this one */ key = __config_translate_key(key); if (!key) key = line; /* no translation? fall back on untranslated key */ } entry = __config_add (this, key, 50); entry->unknown_value = strdup(value); pthread_mutex_unlock(&this->config_lock); } else { switch (entry->type) { case XINE_CONFIG_TYPE_RANGE: case XINE_CONFIG_TYPE_NUM: case XINE_CONFIG_TYPE_BOOL: __config_update_num (this, entry->key, atoi(value)); break; case XINE_CONFIG_TYPE_ENUM: case XINE_CONFIG_TYPE_STRING: __config_update_string (this, entry->key, value); break; case XINE_CONFIG_TYPE_UNKNOWN: pthread_mutex_lock(&this->config_lock); free(entry->unknown_value); entry->unknown_value = strdup(value); pthread_mutex_unlock(&this->config_lock); break; default: printf ("xine_interface: error, unknown config entry type %d\n", entry->type); _x_abort(); } } } } fclose (f_config); } } void xine_config_save (xine_t *xine, const char *filename) { config_values_t *this = xine->config; char temp[XINE_PATH_MAX]; int backup = 0; struct stat backup_stat, config_stat; FILE *f_config, *f_backup; snprintf(temp, XINE_PATH_MAX, "%s~", filename); unlink (temp); if (stat(temp, &backup_stat) != 0) { lprintf("backing up configfile to %s\n", temp); f_backup = fopen(temp, "w"); f_config = fopen(filename, "r"); if (f_config && f_backup && (stat(filename, &config_stat) == 0) && (config_stat.st_size > 0)) { char *buf = NULL; size_t rlen; buf = (char *) xine_xmalloc(config_stat.st_size + 1); if((rlen = fread(buf, 1, config_stat.st_size, f_config)) && (rlen == config_stat.st_size)) { (void) fwrite(buf, 1, rlen, f_backup); } free(buf); fclose(f_config); fclose(f_backup); stat(temp, &backup_stat); if (config_stat.st_size == backup_stat.st_size) backup = 1; else unlink(temp); } else { if (f_config) fclose(f_config); else backup = 1; if (f_backup) fclose(f_backup); } } if (!backup && (stat(filename, &config_stat) == 0)) { xprintf(xine, XINE_VERBOSITY_LOG, _("configfile: WARNING: backing up configfile to %s failed\n"), temp); xprintf(xine, XINE_VERBOSITY_LOG, _("configfile: WARNING: your configuration will not be saved\n")); return; } lprintf ("writing config file to %s\n", filename); f_config = fopen(filename, "w"); if (f_config) { cfg_entry_t *entry; fprintf (f_config, "#\n# xine config file\n#\n"); fprintf (f_config, ".version:%d\n\n", CONFIG_FILE_VERSION); fprintf (f_config, "# Entries which are still set to their default values are commented out.\n"); fprintf (f_config, "# Remove the \'#\' at the beginning of the line, if you want to change them.\n\n"); pthread_mutex_lock(&this->config_lock); entry = this->first; while (entry) { if (!entry->key[0]) /* deleted key */ continue; lprintf ("saving key '%s'\n", entry->key); if (entry->description) fprintf (f_config, "# %s\n", entry->description); switch (entry->type) { case XINE_CONFIG_TYPE_UNKNOWN: /*#if 0*/ /* discard unclaimed values */ fprintf (f_config, "%s:%s\n", entry->key, entry->unknown_value); fprintf (f_config, "\n"); /*#endif*/ break; case XINE_CONFIG_TYPE_RANGE: fprintf (f_config, "# [%d..%d], default: %d\n", entry->range_min, entry->range_max, entry->num_default); if (entry->num_value == entry->num_default) fprintf (f_config, "#"); fprintf (f_config, "%s:%d\n", entry->key, entry->num_value); fprintf (f_config, "\n"); break; case XINE_CONFIG_TYPE_STRING: fprintf (f_config, "# string, default: %s\n", entry->str_default); if (strcmp(entry->str_value, entry->str_default) == 0) fprintf (f_config, "#"); fprintf (f_config, "%s:%s\n", entry->key, entry->str_value); fprintf (f_config, "\n"); break; case XINE_CONFIG_TYPE_ENUM: { char **value; fprintf (f_config, "# {"); value = entry->enum_values; while (*value) { fprintf (f_config, " %s ", *value); value++; } fprintf (f_config, "}, default: %d\n", entry->num_default); if (entry->enum_values[entry->num_value] != NULL) { if (entry->num_value == entry->num_default) fprintf (f_config, "#"); fprintf (f_config, "%s:", entry->key); fprintf (f_config, "%s\n", entry->enum_values[entry->num_value]); } fprintf (f_config, "\n"); break; } case XINE_CONFIG_TYPE_NUM: fprintf (f_config, "# numeric, default: %d\n", entry->num_default); if (entry->num_value == entry->num_default) fprintf (f_config, "#"); fprintf (f_config, "%s:%d\n", entry->key, entry->num_value); fprintf (f_config, "\n"); break; case XINE_CONFIG_TYPE_BOOL: fprintf (f_config, "# bool, default: %d\n", entry->num_default); if (entry->num_value == entry->num_default) fprintf (f_config, "#"); fprintf (f_config, "%s:%d\n", entry->key, entry->num_value); fprintf (f_config, "\n"); break; } entry = entry->next; } pthread_mutex_unlock(&this->config_lock); if (fclose(f_config) != 0) { xprintf(xine, XINE_VERBOSITY_LOG, _("configfile: WARNING: writing configuration to %s failed\n"), filename); xprintf(xine, XINE_VERBOSITY_LOG, _("configfile: WARNING: removing possibly broken config file %s\n"), filename); xprintf(xine, XINE_VERBOSITY_LOG, _("configfile: WARNING: you should check the backup file %s\n"), temp); /* writing config failed -> remove file, it might be broken ... */ unlink(filename); /* ... but keep the backup */ backup = 0; } } if (backup) unlink(temp); } static void __config_dispose (config_values_t *this) { cfg_entry_t *entry, *last; pthread_mutex_lock(&this->config_lock); entry = this->first; lprintf ("dispose\n"); while (entry) { last = entry; entry = entry->next; if (last->key) free (last->key); if (last->unknown_value) free (last->unknown_value); if (last->type == XINE_CONFIG_TYPE_STRING) { free (last->str_value); free (last->str_default); } free (last); } pthread_mutex_unlock(&this->config_lock); pthread_mutex_destroy(&this->config_lock); free (this); } static void __config_unregister_cb (config_values_t *this, const char *key) { cfg_entry_t *entry; _x_assert(key); _x_assert(this); entry = __config_lookup_entry (this, key); if (entry) { pthread_mutex_lock(&this->config_lock); entry->callback = NULL; entry->callback_data = NULL; pthread_mutex_unlock(&this->config_lock); } } config_values_t *_x_config_init (void) { #ifdef HAVE_IRIXAL volatile /* is this a (old, 2.91.66) irix gcc bug?!? */ #endif config_values_t *this; if (!(this = xine_xmalloc(sizeof(config_values_t)))) { printf ("configfile: could not allocate config object\n"); _x_abort(); } this->first = NULL; this->last = NULL; this->current_version = 0; pthread_mutex_init(&this->config_lock, NULL); this->register_string = __config_register_string; this->register_range = __config_register_range; this->register_enum = __config_register_enum; this->register_num = __config_register_num; this->register_bool = __config_register_bool; this->update_num = __config_update_num; this->update_string = __config_update_string; this->parse_enum = __config_parse_enum; this->lookup_entry = __config_lookup_entry; this->unregister_callback = __config_unregister_cb; this->dispose = __config_dispose; return this; } int _x_config_change_opt(config_values_t *config, const char *opt) { cfg_entry_t *entry; int handled = 0; lprintf ("change_opt '%s'\n", opt); if ((entry = config->lookup_entry(config, "misc.implicit_config")) && entry->type == XINE_CONFIG_TYPE_BOOL) { if (!entry->num_value) /* changing config entries implicitly is denied */ return -1; } else /* someone messed with the config entry */ return -1; if(config && opt) { char *key, *value; key = strdup(opt); value = strrchr(key, ':'); if(key && strlen(key) && value && strlen(value)) { *value++ = '\0'; entry = config->lookup_entry(config, key); if(entry->exp_level >= XINE_CONFIG_SECURITY) { printf(_("configfile: entry '%s' mustn't be modified from MRL\n"), key); free(key); return -1; } if(entry) { switch(entry->type) { case XINE_CONFIG_TYPE_STRING: config->update_string(config, key, value); handled = 1; break; case XINE_CONFIG_TYPE_RANGE: case XINE_CONFIG_TYPE_ENUM: case XINE_CONFIG_TYPE_NUM: case XINE_CONFIG_TYPE_BOOL: config->update_num(config, key, (atoi(value))); handled = 1; break; case XINE_CONFIG_TYPE_UNKNOWN: entry->unknown_value = strdup(value); handled = 1; break; } } } free(key); } return handled; }