diff --git a/ofono/.gitignore b/ofono/.gitignore index b51bbd497..5988f7c4f 100644 --- a/ofono/.gitignore +++ b/ofono/.gitignore @@ -62,6 +62,7 @@ unit/test-sailfish_cell_info_dbus unit/test-sailfish_manager unit/test-sailfish_sim_info unit/test-sailfish_sim_info_dbus +unit/test-config unit/test-watch unit/test-sms-filter unit/test-voicecall-filter diff --git a/ofono/Makefile.am b/ofono/Makefile.am index 07a7b99b5..0919c471c 100644 --- a/ofono/Makefile.am +++ b/ofono/Makefile.am @@ -772,7 +772,7 @@ src_ofonod_SOURCES = $(builtin_sources) $(gatchat_sources) src/ofono.ver \ src/handsfree-audio.c src/bluetooth.h \ src/sim-mnclength.c src/voicecallagent.c \ src/sms-filter.c src/gprs-filter.c \ - src/dbus-queue.c src/dbus-access.c \ + src/dbus-queue.c src/dbus-access.c src/config.c \ src/voicecall-filter.c src/ril-transport.c \ src/hfp.h src/siri.c src/watchlist.c \ src/netmon.c src/lte.c src/ims.c \ @@ -968,8 +968,7 @@ if SAILFISH_MANAGER unit_test_sailfish_cell_info_SOURCES = unit/test-sailfish_cell_info.c \ plugins/sailfish_manager/sailfish_cell_info.c -unit_test_sailfish_cell_info_CFLAGS = $(AM_CFLAGS) $(COVERAGE_OPT) \ - -Iplugins/sailfish_manager +unit_test_sailfish_cell_info_CFLAGS = $(AM_CFLAGS) $(COVERAGE_OPT) unit_test_sailfish_cell_info_LDADD = @GLIB_LIBS@ -ldl unit_objects += $(unit_test_sailfish_cell_info_OBJECTS) unit_tests += unit/test-sailfish_cell_info @@ -982,7 +981,7 @@ unit_test_sailfish_cell_info_dbus_SOURCES = unit/test-dbus.c \ gdbus/object.c \ src/dbus.c src/log.c unit_test_sailfish_cell_info_dbus_CFLAGS = $(AM_CFLAGS) $(COVERAGE_OPT) \ - @DBUS_GLIB_CFLAGS@ -Iplugins/sailfish_manager + @DBUS_GLIB_CFLAGS@ unit_test_sailfish_cell_info_dbus_LDADD = @DBUS_GLIB_LIBS@ @GLIB_LIBS@ -ldl unit_objects += $(unit_test_sailfish_cell_info_dbus_OBJECTS) unit_tests += unit/test-sailfish_cell_info_dbus @@ -992,7 +991,7 @@ unit_test_sailfish_sim_info_SOURCES = unit/test-sailfish_sim_info.c \ plugins/sailfish_manager/sailfish_sim_info.c \ src/storage.c src/watchlist.c src/log.c unit_test_sailfish_sim_info_CFLAGS = $(COVERAGE_OPT) $(AM_CFLAGS) \ - -DSTORAGEDIR='"/tmp/ofono"' -Iplugins/sailfish_manager + -DSTORAGEDIR='"/tmp/ofono"' unit_test_sailfish_sim_info_LDADD = @GLIB_LIBS@ -ldl unit_objects += $(unit_test_sailfish_sim_info_OBJECTS) unit_tests += unit/test-sailfish_sim_info @@ -1004,8 +1003,7 @@ unit_test_sailfish_sim_info_dbus_SOURCES = unit/test-sailfish_sim_info_dbus.c \ gdbus/object.c \ src/dbus.c src/storage.c src/watchlist.c src/log.c unit_test_sailfish_sim_info_dbus_CFLAGS = $(COVERAGE_OPT) $(AM_CFLAGS) \ - @DBUS_GLIB_CFLAGS@ -DSTORAGEDIR='"/tmp/ofono"' \ - -Iplugins/sailfish_manager + @DBUS_GLIB_CFLAGS@ -DSTORAGEDIR='"/tmp/ofono"' unit_test_sailfish_sim_info_dbus_LDADD = @DBUS_GLIB_LIBS@ @GLIB_LIBS@ -ldl unit_objects += $(unit_test_sailfish_sim_info_dbus_OBJECTS) unit_tests += unit/test-sailfish_sim_info_dbus @@ -1017,7 +1015,7 @@ unit_test_sailfish_manager_SOURCES = unit/test-sailfish_manager.c \ plugins/sailfish_manager/sailfish_sim_info.c \ src/storage.c src/log.c unit_test_sailfish_manager_CFLAGS = $(AM_CFLAGS) $(COVERAGE_OPT) \ - -DSTORAGEDIR='"/tmp/ofono"' -Iplugins/sailfish_manager + -DSTORAGEDIR='"/tmp/ofono"' unit_test_sailfish_manager_LDADD = @GLIB_LIBS@ -ldl unit_objects += $(unit_test_sailfish_manager_OBJECTS) unit_tests += unit/test-sailfish_manager @@ -1025,13 +1023,20 @@ unit_tests += unit/test-sailfish_manager unit_test_watch_SOURCES = unit/test-watch.c src/watch.c \ src/log.c src/watchlist.c unit_test_watch_CFLAGS = $(AM_CFLAGS) $(COVERAGE_OPT) \ - -DSTORAGEDIR='"/tmp/ofono"' -Iplugins/sailfish_manager + -DSTORAGEDIR='"/tmp/ofono"' unit_test_watch_LDADD = @GLIB_LIBS@ -ldl unit_objects += $(unit_test_watch_OBJECTS) unit_tests += unit/test-watch endif +unit_test_config_SOURCES = unit/test-config.c drivers/ril/ril_util.c \ + src/config.c src/log.c +unit_test_config_CFLAGS = $(COVERAGE_OPT) $(AM_CFLAGS) +unit_test_config_LDADD = @GLIB_LIBS@ -ldl +unit_objects += $(unit_test_config_OBJECTS) +unit_tests += unit/test-config + if SAILFISH_ACCESS unit_test_sailfish_access_SOURCES = unit/test-sailfish_access.c \ plugins/sailfish_access.c src/dbus-access.c src/log.c diff --git a/ofono/drivers/ril/ril_config.c b/ofono/drivers/ril/ril_config.c index c325be910..13e01b26f 100644 --- a/ofono/drivers/ril/ril_config.c +++ b/ofono/drivers/ril/ril_config.c @@ -1,7 +1,8 @@ /* * oFono - Open Source Telephony - RIL-based devices * - * Copyright (C) 2015-2018 Jolla Ltd. + * Copyright (C) 2015-2019 Jolla Ltd. + * Copyright (C) 2019 Open Mobile Platform LLC. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -237,337 +238,6 @@ char *ril_config_ints_to_string(GUtilInts *ints, char separator) return NULL; } -/** - * The ril_config_merge_files() function does the following: - * - * 1. Loads the specified key file (say, "/etc/foo.conf") - * 2. Scans the subdirectory named after the file (e.g. "/etc/foo.d/") - * for the files with the same suffix as the main file (e.g. "*.conf") - * 3. Sorts the files from the subdirectory (alphabetically) - * 4. Merges the contents of the additional files with the main file - * according to their sort order. - * - * When the entries are merged, keys and groups overwrite the exising - * ones by default. Keys can be suffixed with special characters to - * remove or modify the existing entries instead: - * - * ':' Sets the (default) value if the key is missing - * '+' Appends values to the string list - * '?' Appends only new (non-existent) values to the string list - * '-' Removes the values from the string list - * - * Both keys and groups can be prefixed with '!' to remove the entire key - * or group. - * - * For example if we merge these two files: - * - * /etc/foo.conf: - * - * [foo] - * a=1 - * b=2,3 - * c=4 - * d=5 - * [bar] - * e=5 - * - * /etc/foo.d/bar.conf: - * - * [foo] - * a+=2 - * b-=2 - * c=5 - * !d - * [!bar] - * - * we end up with this: - * - * [foo] - * a=1 - * b=2,3 - * c=5 - * - * Not that the list separator is assumed to be ',' (rather than default ';'). - * The keyfile passed to ril_config_merge_files() should use the same list - * separator, because the default values are copied from the config files - * as is. - */ - -static gint ril_config_sort_files(gconstpointer a, gconstpointer b) -{ - /* The comparison function for g_ptr_array_sort() doesn't take - * the pointers from the array as arguments, it takes pointers - * to the pointers in the array. */ - return strcmp(*(char**)a, *(char**)b); -} - -static char **ril_config_collect_files(const char *path, const char *suffix) -{ - /* Returns sorted list of regular files in the directory, - * optionally having the specified suffix (e.g. ".conf"). - * Returns NULL if nothing appropriate has been found. */ - char **files = NULL; - DIR *d = opendir(path); - - if (d) { - GPtrArray *list = g_ptr_array_new(); - const struct dirent *p; - - while ((p = readdir(d)) != NULL) { - /* No need to even stat . and .. */ - if (strcmp(p->d_name, ".") && - strcmp(p->d_name, "..") && (!suffix || - g_str_has_suffix(p->d_name, suffix))) { - struct stat st; - char *buf = g_strconcat(path, "/", p->d_name, - NULL); - - if (!stat(buf, &st) && S_ISREG(st.st_mode)) { - g_ptr_array_add(list, buf); - } else { - g_free(buf); - } - } - } - - if (list->len > 0) { - g_ptr_array_sort(list, ril_config_sort_files); - g_ptr_array_add(list, NULL); - files = (char**)g_ptr_array_free(list, FALSE); - } else { - g_ptr_array_free(list, TRUE); - } - - closedir(d); - } - return files; -} - -static int ril_config_list_find(char **list, gsize len, const char *value) -{ - guint i; - - for (i = 0; i < len; i++) { - if (!strcmp(list[i], value)) { - return i; - } - } - - return -1; -} - -static void ril_config_list_append(GKeyFile *conf, GKeyFile *k, - const char *group, const char *key, - char **values, gsize n, gboolean unique) -{ - /* Note: will steal strings from values */ - if (n > 0) { - int i; - gsize len = 0; - gchar **list = g_key_file_get_string_list(conf, group, key, - &len, NULL); - GPtrArray *newlist = g_ptr_array_new_full(0, g_free); - - for (i = 0; i < (int)len; i++) { - g_ptr_array_add(newlist, list[i]); - } - - for (i = 0; i < (int)n; i++) { - char *val = values[i]; - - if (!unique || ril_config_list_find((char**) - newlist->pdata, newlist->len, val) < 0) { - /* Move the string to the new list */ - g_ptr_array_add(newlist, val); - memmove(values + i, values + i + 1, - sizeof(char*) * (n - i)); - i--; - n--; - } - } - - if (newlist->len > len) { - g_key_file_set_string_list(conf, group, key, - (const gchar * const *) newlist->pdata, - newlist->len); - } - - /* Strings are deallocated by GPtrArray */ - g_ptr_array_free(newlist, TRUE); - g_free(list); - } -} - -static void ril_config_list_remove(GKeyFile *conf, GKeyFile *k, - const char *group, const char *key, char **values, gsize n) -{ - if (n > 0) { - gsize len = 0; - gchar **list = g_key_file_get_string_list(conf, group, key, - &len, NULL); - - if (len > 0) { - gsize i; - const gsize oldlen = len; - - for (i = 0; i < n; i++) { - int pos; - - /* Remove all matching values */ - while ((pos = ril_config_list_find(list, len, - values[i])) >= 0) { - g_free(list[pos]); - memmove(list + pos, list + pos + 1, - sizeof(char*) * (len - pos)); - len--; - } - } - - if (len < oldlen) { - g_key_file_set_string_list(conf, group, key, - (const gchar * const *) list, len); - } - } - - g_strfreev(list); - } -} - -static void ril_config_merge_group(GKeyFile *conf, GKeyFile *k, - const char *group) -{ - gsize i, n = 0; - char **keys = g_key_file_get_keys(k, group, &n, NULL); - - for (i=0; i 0) ? key[len-1] : 0; - - if (last == '+' || last == '?') { - gsize count = 0; - gchar **values = g_key_file_get_string_list(k, - group, key, &count, NULL); - - key[len-1] = 0; - ril_config_list_append(conf, k, group, key, - values, count, last == '?'); - g_strfreev(values); - } else if (last == '-') { - gsize count = 0; - gchar **values = g_key_file_get_string_list(k, - group, key, &count, NULL); - - key[len-1] = 0; - ril_config_list_remove(conf, k, group, key, - values, count); - g_strfreev(values); - } else { - /* Overwrite the value (it must exist in k) */ - gchar *value = g_key_file_get_value(k, group, - key, NULL); - - if (last == ':') { - /* Default value */ - key[len-1] = 0; - if (!g_key_file_has_key(conf, - group, key, NULL)) { - g_key_file_set_value(conf, - group, key, value); - } - } else { - g_key_file_set_value(conf, group, key, - value); - } - g_free(value); - } - } - } - - g_strfreev(keys); -} - -static void ril_config_merge_keyfile(GKeyFile *conf, GKeyFile *k) -{ - gsize i, n = 0; - char **groups = g_key_file_get_groups(k, &n); - - for (i=0; i @@ -1884,7 +1885,7 @@ static GSList *ril_plugin_load_config(const char *path, GKeyFile *file = g_key_file_new(); gboolean empty = FALSE; - ril_config_merge_files(file, path); + config_merge_files(file, path); if (ril_config_get_boolean(file, RILCONF_SETTINGS_GROUP, RILCONF_SETTINGS_EMPTY, &empty) && empty) { DBG("Empty config"); diff --git a/ofono/plugins/sailfish_manager/sailfish_manager.c b/ofono/plugins/sailfish_manager/sailfish_manager.c index 2f73ad77f..2005ccfa7 100644 --- a/ofono/plugins/sailfish_manager/sailfish_manager.c +++ b/ofono/plugins/sailfish_manager/sailfish_manager.c @@ -1,7 +1,8 @@ /* * oFono - Open Source Telephony * - * Copyright (C) 2017-2019 Jolla Ltd. + * Copyright (C) 2017-2020 Jolla Ltd. + * Copyright (C) 2019-2020 Open Mobile Platform LLC. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -22,6 +23,7 @@ #include #include +#include #include #include "src/ofono.h" @@ -44,6 +46,12 @@ enum ofono_watch_events { WATCH_EVENT_COUNT }; +enum sim_auto_select { + SIM_AUTO_SELECT_OFF, + SIM_AUTO_SELECT_ON, + SIM_AUTO_SELECT_ONCE +}; + struct sailfish_manager_priv { struct sailfish_manager pub; /* Public part */ struct sailfish_slot_driver_reg *drivers; @@ -52,6 +60,8 @@ struct sailfish_manager_priv { struct sailfish_slot_priv *data_slot; struct sailfish_slot_priv *mms_slot; sailfish_slot_ptr *slots; + enum sim_auto_select auto_data_sim; + gboolean auto_data_sim_done; int slot_count; guint init_countdown; guint init_id; @@ -99,6 +109,11 @@ struct sailfish_slot_priv { int index; }; +/* Read-only config */ +#define SF_CONFIG_FILE "main.conf" +#define SF_CONFIG_GROUP "ModemManager" +#define SF_CONFIG_KEY_AUTO_DATA_SIM "AutoSelectDataSim" + /* "ril" is used for historical reasons */ #define SF_STORE "ril" #define SF_STORE_GROUP "Settings" @@ -106,6 +121,7 @@ struct sailfish_slot_priv { #define SF_STORE_DEFAULT_VOICE_SIM "DefaultVoiceSim" #define SF_STORE_DEFAULT_DATA_SIM "DefaultDataSim" #define SF_STORE_SLOTS_SEP "," +#define SF_STORE_AUTO_DATA_SIM_DONE "AutoSelectDataSimDone" /* The file where error statistics is stored. Again "rilerror" is historical */ #define SF_ERROR_STORAGE "rilerror" /* File name */ @@ -145,6 +161,50 @@ static inline void sailfish_slot_set_data_role(struct sailfish_slot_priv *s, } } +static gboolean sailfish_config_get_enum(GKeyFile *file, const char *group, + const char *key, int *result, + const char *name, int value, ...) +{ + char *str = g_key_file_get_string(file, group, key, NULL); + + if (str) { + /* + * Some people are thinking that # is a comment + * anywhere on the line, not just at the beginning + */ + char *comment = strchr(str, '#'); + + if (comment) *comment = 0; + g_strstrip(str); + if (strcasecmp(str, name)) { + va_list args; + va_start(args, value); + while ((name = va_arg(args, char*)) != NULL) { + value = va_arg(args, int); + if (!strcasecmp(str, name)) { + break; + } + } + va_end(args); + } + + if (!name) { + ofono_error("Invalid %s config value (%s)", key, str); + } + + g_free(str); + + if (name) { + if (result) { + *result = value; + } + return TRUE; + } + } + + return FALSE; +} + /* Update modem paths and emit D-Bus signal if necessary */ static void sailfish_manager_update_modem_paths_full (struct sailfish_manager_priv *p) @@ -588,6 +648,27 @@ static struct sailfish_slot_priv *sailfish_manager_find_slot_imsi } } +static gboolean sailfish_manager_all_sims_are_initialized_proc + (struct sailfish_slot_priv *s, void *user_data) +{ + if (s->pub.sim_present && s->pub.enabled && !s->watch->imsi) { + *((gboolean*)user_data) = FALSE; + return SF_LOOP_DONE; + } else { + return SF_LOOP_CONTINUE; + } +} + +static gboolean sailfish_manager_all_sims_are_initialized + (struct sailfish_manager_priv *p) +{ + gboolean result = TRUE; + + sailfish_manager_foreach_slot(p, + sailfish_manager_all_sims_are_initialized_proc, &result); + return result; +} + /* Returns the event mask to be passed to sailfish_manager_dbus_signal. * The caller has a chance to OR it with other bits */ static int sailfish_manager_update_modem_paths(struct sailfish_manager_priv *p) @@ -615,7 +696,7 @@ static int sailfish_manager_update_modem_paths(struct sailfish_manager_priv *p) * previously selected voice SIM is inserted, we will switch * back to it. * - * There is no such fallback for the data. + * A similar behavior can be configured for data SIM too. */ if (!slot) { slot = sailfish_manager_find_slot_imsi(p, NULL); @@ -648,11 +729,36 @@ static int sailfish_manager_update_modem_paths(struct sailfish_manager_priv *p) slot = sailfish_manager_find_slot_imsi(p, NULL); } } else { + slot = NULL; + } + + /* Check if we need to auto-select data SIM (always or once) */ + if (!slot && (p->auto_data_sim == SIM_AUTO_SELECT_ON || + (p->auto_data_sim == SIM_AUTO_SELECT_ONCE && + !p->auto_data_sim_done))) { /* - * Should we automatically select the default data sim - * on a multisim phone that has only one sim inserted? + * To actually make a selection we need all present SIMs + * to be initialized. Otherwise we may end up endlessly + * switching data SIMs back and forth. */ - slot = NULL; + if (sailfish_manager_all_sims_are_initialized(p)) { + slot = sailfish_manager_find_slot_imsi(p, NULL); + if (slot && slot->watch->online && + p->auto_data_sim == SIM_AUTO_SELECT_ONCE) { + /* + * Data SIM only needs to be auto-selected + * once and it's done. Write that down. + */ + p->auto_data_sim_done = TRUE; + g_key_file_set_boolean(p->storage, + SF_STORE_GROUP, + SF_STORE_AUTO_DATA_SIM_DONE, + p->auto_data_sim_done); + storage_sync(NULL, SF_STORE, p->storage); + } + } else { + DBG("Skipping auto-selection of data SIM"); + } } if (slot && !slot->watch->online) { @@ -1274,6 +1380,29 @@ static struct sailfish_manager_priv *sailfish_manager_priv_new() struct sailfish_manager_priv *p = g_slice_new0(struct sailfish_manager_priv); + GKeyFile *conf = g_key_file_new(); + char* fn = g_build_filename(ofono_config_dir(), SF_CONFIG_FILE, NULL); + + /* Load config */ + if (g_key_file_load_from_file(conf, fn, 0, NULL)) { + int ival; + + DBG("Loading configuration file %s", fn); + if (sailfish_config_get_enum(conf, SF_CONFIG_GROUP, + SF_CONFIG_KEY_AUTO_DATA_SIM, &ival, + "off", SIM_AUTO_SELECT_OFF, + "once", SIM_AUTO_SELECT_ONCE, + "always", SIM_AUTO_SELECT_ON, + "on", SIM_AUTO_SELECT_ON, NULL)) { + DBG("Automatic data SIM selection: %s", + ival == SIM_AUTO_SELECT_ONCE ? "once": + ival == SIM_AUTO_SELECT_ON ? "on": + "off"); + p->auto_data_sim = ival; + } + } + g_key_file_free(conf); + g_free(fn); /* Load settings */ p->storage = storage_open(NULL, SF_STORE); @@ -1283,6 +1412,8 @@ static struct sailfish_manager_priv *sailfish_manager_priv_new() p->pub.default_data_imsi = p->default_data_imsi = g_key_file_get_string(p->storage, SF_STORE_GROUP, SF_STORE_DEFAULT_DATA_SIM, NULL); + p->auto_data_sim_done = g_key_file_get_boolean(p->storage, + SF_STORE_GROUP, SF_STORE_AUTO_DATA_SIM_DONE, NULL); DBG("Default voice sim is %s", p->default_voice_imsi ? p->default_voice_imsi : "(auto)"); diff --git a/ofono/src/config.c b/ofono/src/config.c new file mode 100644 index 000000000..5828e7de6 --- /dev/null +++ b/ofono/src/config.c @@ -0,0 +1,365 @@ +/* + * oFono - Open Source Telephony + * + * Copyright (C) 2015-2019 Jolla Ltd. + * Copyright (C) 2019 Open Mobile Platform LLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "ofono.h" + +#include +#include +#include +#include + +/** + * The config_merge_files() function does the following: + * + * 1. Loads the specified key file (say, "/etc/foo.conf") + * 2. Scans the subdirectory named after the file (e.g. "/etc/foo.d/") + * for the files with the same suffix as the main file (e.g. "*.conf") + * 3. Sorts the files from the subdirectory (alphabetically) + * 4. Merges the contents of the additional files with the main file + * according to their sort order. + * + * When the entries are merged, keys and groups overwrite the exising + * ones by default. Keys can be suffixed with special characters to + * remove or modify the existing entries instead: + * + * ':' Sets the (default) value if the key is missing + * '+' Appends values to the string list + * '?' Appends only new (non-existent) values to the string list + * '-' Removes the values from the string list + * + * Both keys and groups can be prefixed with '!' to remove the entire key + * or group. + * + * For example if we merge these two files: + * + * /etc/foo.conf: + * + * [foo] + * a=1 + * b=2,3 + * c=4 + * d=5 + * [bar] + * e=5 + * + * /etc/foo.d/bar.conf: + * + * [foo] + * a+=2 + * b-=2 + * c=5 + * !d + * [!bar] + * + * we end up with this: + * + * [foo] + * a=1 + * b=2,3 + * c=5 + * + * Note that the list separator is assumed to be ',' (rather than default ';'). + * The keyfile passed to config_merge_files() should use the same list + * separator, because the default values are copied from the config files + * as is. + */ + +static gint config_sort_files(gconstpointer a, gconstpointer b) +{ + /* The comparison function for g_ptr_array_sort() doesn't take + * the pointers from the array as arguments, it takes pointers + * to the pointers in the array. */ + return strcmp(*(char**)a, *(char**)b); +} + +static char **config_collect_files(const char *path, const char *suffix) +{ + /* Returns sorted list of regular files in the directory, + * optionally having the specified suffix (e.g. ".conf"). + * Returns NULL if nothing appropriate has been found. */ + char **files = NULL; + DIR *d = opendir(path); + + if (d) { + GPtrArray *list = g_ptr_array_new(); + const struct dirent *p; + + while ((p = readdir(d)) != NULL) { + /* No need to even stat . and .. */ + if (strcmp(p->d_name, ".") && + strcmp(p->d_name, "..") && (!suffix || + g_str_has_suffix(p->d_name, suffix))) { + struct stat st; + char *buf = g_strconcat(path, "/", p->d_name, + NULL); + + if (!stat(buf, &st) && S_ISREG(st.st_mode)) { + g_ptr_array_add(list, buf); + } else { + g_free(buf); + } + } + } + + if (list->len > 0) { + g_ptr_array_sort(list, config_sort_files); + g_ptr_array_add(list, NULL); + files = (char**)g_ptr_array_free(list, FALSE); + } else { + g_ptr_array_free(list, TRUE); + } + + closedir(d); + } + return files; +} + +static int config_list_find(char **list, gsize len, const char *value) +{ + guint i; + + for (i = 0; i < len; i++) { + if (!strcmp(list[i], value)) { + return i; + } + } + + return -1; +} + +static void config_list_append(GKeyFile *conf, GKeyFile *k, + const char *group, const char *key, + char **values, gsize n, gboolean unique) +{ + /* Note: will steal strings from values */ + if (n > 0) { + int i; + gsize len = 0; + gchar **list = g_key_file_get_string_list(conf, group, key, + &len, NULL); + GPtrArray *newlist = g_ptr_array_new_full(0, g_free); + + for (i = 0; i < (int)len; i++) { + g_ptr_array_add(newlist, list[i]); + } + + for (i = 0; i < (int)n; i++) { + char *val = values[i]; + + if (!unique || config_list_find((char**) + newlist->pdata, newlist->len, val) < 0) { + /* Move the string to the new list */ + g_ptr_array_add(newlist, val); + memmove(values + i, values + i + 1, + sizeof(char*) * (n - i)); + i--; + n--; + } + } + + if (newlist->len > len) { + g_key_file_set_string_list(conf, group, key, + (const gchar * const *) newlist->pdata, + newlist->len); + } + + /* Strings are deallocated by GPtrArray */ + g_ptr_array_free(newlist, TRUE); + g_free(list); + } +} + +static void config_list_remove(GKeyFile *conf, GKeyFile *k, + const char *group, const char *key, char **values, gsize n) +{ + if (n > 0) { + gsize len = 0; + gchar **list = g_key_file_get_string_list(conf, group, key, + &len, NULL); + + if (len > 0) { + gsize i; + const gsize oldlen = len; + + for (i = 0; i < n; i++) { + int pos; + + /* Remove all matching values */ + while ((pos = config_list_find(list, len, + values[i])) >= 0) { + g_free(list[pos]); + memmove(list + pos, list + pos + 1, + sizeof(char*) * (len - pos)); + len--; + } + } + + if (len < oldlen) { + g_key_file_set_string_list(conf, group, key, + (const gchar * const *) list, len); + } + } + + g_strfreev(list); + } +} + +static void config_merge_group(GKeyFile *conf, GKeyFile *k, + const char *group) +{ + gsize i, n = 0; + char **keys = g_key_file_get_keys(k, group, &n, NULL); + + for (i=0; i 0) ? key[len-1] : 0; + + if (last == '+' || last == '?') { + gsize count = 0; + gchar **values = g_key_file_get_string_list(k, + group, key, &count, NULL); + + key[len-1] = 0; + config_list_append(conf, k, group, key, + values, count, last == '?'); + g_strfreev(values); + } else if (last == '-') { + gsize count = 0; + gchar **values = g_key_file_get_string_list(k, + group, key, &count, NULL); + + key[len-1] = 0; + config_list_remove(conf, k, group, key, + values, count); + g_strfreev(values); + } else { + /* Overwrite the value (it must exist in k) */ + gchar *value = g_key_file_get_value(k, group, + key, NULL); + + if (last == ':') { + /* Default value */ + key[len-1] = 0; + if (!g_key_file_has_key(conf, + group, key, NULL)) { + g_key_file_set_value(conf, + group, key, value); + } + } else { + g_key_file_set_value(conf, group, key, + value); + } + g_free(value); + } + } + } + + g_strfreev(keys); +} + +static void config_merge_keyfile(GKeyFile *conf, GKeyFile *k) +{ + gsize i, n = 0; + char **groups = g_key_file_get_groups(k, &n); + + for (i=0; i void __ofono_set_config_dir(const char *dir); +void config_merge_files(GKeyFile *conf, const char *file); diff --git a/ofono/unit/coverage b/ofono/unit/coverage index 3a0e4ddbf..074cd99c1 100755 --- a/ofono/unit/coverage +++ b/ofono/unit/coverage @@ -20,6 +20,7 @@ TESTS="\ test-dbus-access \ test-gprs-filter \ test-provision \ + test-config \ test-watch \ test-ril_util \ test-ril_config \ diff --git a/ofono/unit/test-config.c b/ofono/unit/test-config.c new file mode 100644 index 000000000..ffd08c9d0 --- /dev/null +++ b/ofono/unit/test-config.c @@ -0,0 +1,448 @@ +/* + * oFono - Open Source Telephony + * + * Copyright (C) 2018-2019 Jolla Ltd. + * Copyright (C) 2019 Open Mobile Platform LLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#include +#include "ofono.h" + +#include +#include + +#include +#include +#include + +#define TMP_DIR_TEMPLATE "test-config-XXXXXX" + +static gboolean test_keyfile_empty(GKeyFile *k) +{ + gsize n = 0; + char **groups = g_key_file_get_groups(k, &n); + + g_strfreev(groups); + return !n; +} + +static void test_merge_ignore(const char *filename, const char *contents, + const char *dirname, const char *filename1, const char *contents1) +{ + char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); + char *file = g_strconcat(dir, "/", filename, NULL); + char *subdir = g_strconcat(dir, "/", dirname, NULL); + char *file1 = g_strconcat(subdir, "/", filename1, NULL); + GKeyFile *k = g_key_file_new(); + char *data; + + g_assert(!mkdir(subdir, 0700)); + g_assert(g_file_set_contents(file, contents, -1, NULL)); + g_assert(g_file_set_contents(file1, contents1, -1, NULL)); + DBG("reading %s", file); + config_merge_files(k, file); + data = g_key_file_to_data(k, NULL, NULL); + DBG("\n%s", data); + g_assert(!g_strcmp0(data, contents)); + g_free(data); + g_key_file_unref(k); + + remove(file); + remove(file1); + remove(subdir); + remove(dir); + + g_free(file); + g_free(file1); + g_free(dir); + g_free(subdir); +} + +static void test_merge1(const char *conf, const char *conf1, const char *out) +{ + char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); + char *file = g_strconcat(dir, "/foo.conf", NULL); + char *subdir = g_strconcat(dir, "/foo.d", NULL); + char *file1 = g_strconcat(subdir, "/bar.conf", NULL); + GKeyFile *k = g_key_file_new(); + char *data; + + g_assert(!mkdir(subdir, 0700)); + g_assert(g_file_set_contents(file, conf, -1, NULL)); + g_assert(g_file_set_contents(file1, conf1, -1, NULL)); + + DBG("reading %s", file); + g_key_file_set_list_separator(k, ','); + config_merge_files(k, file); + data = g_key_file_to_data(k, NULL, NULL); + DBG("\n%s", data); + g_assert(!g_strcmp0(data, out)); + g_free(data); + g_key_file_unref(k); + + remove(file); + remove(file1); + remove(subdir); + remove(dir); + + g_free(file); + g_free(file1); + g_free(dir); + g_free(subdir); +} + +/* ==== merge_basic ==== */ + +static void test_merge_basic(void) +{ + GKeyFile *k = g_key_file_new(); + char *nonexistent = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); + + config_merge_files(NULL, NULL); + + remove(nonexistent); + config_merge_files(k, nonexistent); + g_assert(test_keyfile_empty(k)); + + config_merge_files(k, NULL); + g_assert(test_keyfile_empty(k)); + + config_merge_files(k, ""); + g_assert(test_keyfile_empty(k)); + + g_key_file_unref(k); + g_free(nonexistent); +} + +/* ==== merge_simple ==== */ + +static void test_merge_simple(void) +{ + static const char contents [] = "[foo]\na=1\nb=2\n"; + char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); + char *file = g_strconcat(dir, "/foo.conf", NULL); + char *data; + GKeyFile *k = g_key_file_new(); + + g_assert(g_file_set_contents(file, contents, -1, NULL)); + DBG("reading %s", file); + config_merge_files(k, file); + data = g_key_file_to_data(k, NULL, NULL); + DBG("\n%s", data); + g_assert(!g_strcmp0(data, contents)); + g_free(data); + g_key_file_unref(k); + + remove(file); + remove(dir); + + g_free(file); + g_free(dir); +} + +/* ==== merge_empty_dir ==== */ + +static void test_merge_empty_dir(void) +{ + static const char contents [] = "[foo]\na=1\nb=2\n"; + char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); + char *subdir = g_strconcat(dir, "/foo.d", NULL); + char *file = g_strconcat(dir, "/foo.conf", NULL); + GKeyFile *k = g_key_file_new(); + char *data; + + g_assert(!mkdir(subdir, 0700)); + g_assert(g_file_set_contents(file, contents, -1, NULL)); + DBG("reading %s", file); + config_merge_files(k, file); + data = g_key_file_to_data(k, NULL, NULL); + DBG("\n%s", data); + g_assert(!g_strcmp0(data, contents)); + g_free(data); + g_key_file_unref(k); + + remove(file); + remove(subdir); + remove(dir); + + g_free(file); + g_free(dir); + g_free(subdir); +} + +/* ==== merge_ignore ==== */ + +static void test_merge_ignore0(void) +{ + static const char contents [] = "[foo]\na=1\nb=2\n"; + char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); + char *subdir = g_strconcat(dir, "/foo.d", NULL); + char *subdir2 = g_strconcat(subdir, "/dir.conf", NULL); + char *file = g_strconcat(dir, "/foo.conf", NULL); + GKeyFile *k = g_key_file_new(); + char *data; + + /* Two empty subdirectories, one with matching name, one not */ + g_assert(!mkdir(subdir, 0700)); + g_assert(!mkdir(subdir2, 0700)); + g_assert(g_file_set_contents(file, contents, -1, NULL)); + DBG("reading %s", file); + config_merge_files(k, file); + data = g_key_file_to_data(k, NULL, NULL); + DBG("\n%s", data); + g_assert(!g_strcmp0(data, contents)); + g_free(data); + g_key_file_unref(k); + + remove(file); + remove(subdir2); + remove(subdir); + remove(dir); + + g_free(file); + g_free(dir); + g_free(subdir); + g_free(subdir2); +} + +static void test_merge_ignore1(void) +{ + static const char contents [] = "[foo]\na=1\nb=2\n"; + static const char contents1 [] = "[foo]\nb=3\n"; + + /* File has no suffix */ + test_merge_ignore("foo.conf", contents, "foo.d", "file", contents1); +} + +static void test_merge_ignore2(void) +{ + static const char contents [] = "[foo]\na=1\nb=2\n"; + static const char contents1 [] = "[[[[[[["; + + /* File is not a valid keyfile */ + test_merge_ignore("foo.conf", contents, "foo.d", "a.conf", contents1); +} + +/* ==== merge_sort ==== */ + +static void test_merge_sort(void) +{ + static const char contents [] = "[foo]\na=1\nb=2\n"; + static const char contents1 [] = "[foo]\nb=3\n"; + static const char contents2 [] = "[foo]\nb=4\n"; + static const char result [] = "[foo]\na=1\nb=4\n"; + + /* Test file sort order */ + char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); + char *file = g_strconcat(dir, "/foo.", NULL); + char *subdir = g_strconcat(dir, "/foo.d", NULL); + char *file1 = g_strconcat(subdir, "/1.conf", NULL); + char *file2 = g_strconcat(subdir, "/2.conf", NULL); + GKeyFile *k = g_key_file_new(); + char *data; + + g_assert(!mkdir(subdir, 0700)); + g_assert(g_file_set_contents(file, contents, -1, NULL)); + g_assert(g_file_set_contents(file1, contents1, -1, NULL)); + g_assert(g_file_set_contents(file2, contents2, -1, NULL)); + + DBG("reading %s", file); + config_merge_files(k, file); + data = g_key_file_to_data(k, NULL, NULL); + DBG("\n%s", data); + g_assert(!g_strcmp0(data, result)); + g_free(data); + g_key_file_unref(k); + + remove(file); + remove(file1); + remove(file2); + remove(subdir); + remove(dir); + + g_free(file); + g_free(file1); + g_free(file2); + g_free(dir); + g_free(subdir); +} + +/* ==== merge_remove_group ==== */ + +static void test_merge_remove_group(void) +{ + static const char contents [] = "[foo]\na=1\n\n[bar]\nb=1\n"; + static const char contents1 [] = "[!bar]\n"; + static const char result [] = "[foo]\na=1\n"; + + test_merge1(contents, contents1, result); +} + +/* ==== merge_remove_key ==== */ + +static void test_merge_remove_key(void) +{ + static const char contents [] = "[foo]\na=1\nb=2\n"; + static const char contents1 [] = "[foo]\n!b=\n\n!=\n"; + static const char result [] = "[foo]\na=1\n"; + + test_merge1(contents, contents1, result); +} + +/* ==== merge_default_value ==== */ + +static void test_merge_default_value(void) +{ + /* b is assigned the default value, a stays as is */ + static const char contents [] = "[foo]\na=1\n"; + static const char contents1 [] = "[foo]\na:=2\nb:=3\n"; + static const char result [] = "[foo]\na=1\nb=3\n"; + + test_merge1(contents, contents1, result); +} + +/* ==== merge_list_add ==== */ + +static void test_merge_list_add0(void) +{ + /* Adding empty list */ + static const char contents [] = "[foo]\na=1\nb=2\n"; + static const char contents1 [] = "[foo]\na+=\n"; + + test_merge1(contents, contents1, contents); +} + +static void test_merge_list_add1(void) +{ + /* a=1 turns into a=1,2, */ + static const char contents [] = "[foo]\na=1\nb=2\n"; + static const char contents1 [] = "[foo]\na+=2,\n"; + static const char result [] = "[foo]\na=1,2,\nb=2\n"; + + test_merge1(contents, contents1, result); +} + +static void test_merge_list_add2(void) +{ + /* 2 is already there */ + static const char contents [] = "[foo]\na=1,2,\nb=2\n"; + static const char contents1 [] = "[foo]\na?=2\n"; + + test_merge1(contents, contents1, contents); +} + +static void test_merge_list_add3(void) +{ + /* 2 is already there, 3 is not */ + static const char contents [] = "[foo]\na=1,2,\n"; + static const char contents1 [] = "[foo]\na?=2,3,\n"; + static const char result [] = "[foo]\na=1,2,3,\n"; + + test_merge1(contents, contents1, result); +} + +static void test_merge_list_add4(void) +{ + /* b=2,3, is created */ + static const char contents [] = "[foo]\na=1\n"; + static const char contents1 [] = "[foo]\nb?=2,3,\n"; + static const char result [] = "[foo]\na=1\nb=2,3,\n"; + + test_merge1(contents, contents1, result); +} + +static void test_merge_list_add5(void) +{ + /* Add a new group */ + static const char contents [] = "[foo]\na=1\n"; + static const char contents1 [] = "[bar]\nb=2\n"; + static const char result [] = "[foo]\na=1\n\n[bar]\nb=2\n"; + + test_merge1(contents, contents1, result); +} + +/* ==== merge_list_remove ==== */ + +static void test_merge_list_remove0(void) +{ + static const char contents [] = "[foo]\na=1,2,\n"; + static const char contents1 [] = "[foo]\na-=\n"; + + test_merge1(contents, contents1, contents); +} + +static void test_merge_list_remove1(void) +{ + static const char contents [] = "[foo]\na=1,2,\n"; + static const char contents1 [] = "[foo]\na-=2,\n"; + static const char result [] = "[foo]\na=1,\n"; + + test_merge1(contents, contents1, result); +} + +static void test_merge_list_remove2(void) +{ + static const char contents [] = "[foo]\na=1,2,\n"; + static const char contents1 [] = "[foo]\na-=3\n"; + + test_merge1(contents, contents1, contents); +} + +static void test_merge_list_remove3(void) +{ + static const char contents [] = "[foo]\na=1,2,\n"; + static const char contents1 [] = "[foo]\nb-=1\n"; + + test_merge1(contents, contents1, contents); +} + +#define TEST_(name) "/config/" name + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + __ofono_log_init("test-config", + g_test_verbose() ? "*" : NULL, + FALSE, FALSE); + + g_test_add_func(TEST_("merge_basic"), test_merge_basic); + g_test_add_func(TEST_("merge_simple"), test_merge_simple); + g_test_add_func(TEST_("merge_empty_dir"), test_merge_empty_dir); + g_test_add_func(TEST_("merge_ignore0"), test_merge_ignore0); + g_test_add_func(TEST_("merge_ignore1"), test_merge_ignore1); + g_test_add_func(TEST_("merge_ignore2"), test_merge_ignore2); + g_test_add_func(TEST_("merge_sort"), test_merge_sort); + g_test_add_func(TEST_("merge_remove_group"), test_merge_remove_group); + g_test_add_func(TEST_("merge_remove_key"), test_merge_remove_key); + g_test_add_func(TEST_("merge_default_value"), test_merge_default_value); + g_test_add_func(TEST_("merge_list_add0"), test_merge_list_add0); + g_test_add_func(TEST_("merge_list_add1"), test_merge_list_add1); + g_test_add_func(TEST_("merge_list_add2"), test_merge_list_add2); + g_test_add_func(TEST_("merge_list_add3"), test_merge_list_add3); + g_test_add_func(TEST_("merge_list_add4"), test_merge_list_add4); + g_test_add_func(TEST_("merge_list_add5"), test_merge_list_add5); + g_test_add_func(TEST_("merge_list_remove0"), test_merge_list_remove0); + g_test_add_func(TEST_("merge_list_remove1"), test_merge_list_remove1); + g_test_add_func(TEST_("merge_list_remove2"), test_merge_list_remove2); + g_test_add_func(TEST_("merge_list_remove3"), test_merge_list_remove3); + + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/unit/test-ril_config.c b/ofono/unit/test-ril_config.c index 87e6c1192..b9d058096 100644 --- a/ofono/unit/test-ril_config.c +++ b/ofono/unit/test-ril_config.c @@ -1,7 +1,8 @@ /* * oFono - Open Source Telephony * - * Copyright (C) 2018 Jolla Ltd. + * Copyright (C) 2018-2019 Jolla Ltd. + * Copyright (C) 2019 Open Mobile Platform LLC. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -47,80 +48,6 @@ static void test_get_value(const char *conf, void (*test)(GKeyFile *k)) g_free(dir); } -static gboolean test_keyfile_empty(GKeyFile *k) -{ - gsize n = 0; - char **groups = g_key_file_get_groups(k, &n); - - g_strfreev(groups); - return !n; -} - -static void test_merge_ignore(const char *filename, const char *contents, - const char *dirname, const char *filename1, const char *contents1) -{ - char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); - char *file = g_strconcat(dir, "/", filename, NULL); - char *subdir = g_strconcat(dir, "/", dirname, NULL); - char *file1 = g_strconcat(subdir, "/", filename1, NULL); - GKeyFile *k = g_key_file_new(); - char *data; - - g_assert(!mkdir(subdir, 0700)); - g_assert(g_file_set_contents(file, contents, -1, NULL)); - g_assert(g_file_set_contents(file1, contents1, -1, NULL)); - DBG("reading %s", file); - ril_config_merge_files(k, file); - data = g_key_file_to_data(k, NULL, NULL); - DBG("\n%s", data); - g_assert(!g_strcmp0(data, contents)); - g_free(data); - g_key_file_unref(k); - - remove(file); - remove(file1); - remove(subdir); - remove(dir); - - g_free(file); - g_free(file1); - g_free(dir); - g_free(subdir); -} - -static void test_merge1(const char *conf, const char *conf1, const char *out) -{ - char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); - char *file = g_strconcat(dir, "/foo.conf", NULL); - char *subdir = g_strconcat(dir, "/foo.d", NULL); - char *file1 = g_strconcat(subdir, "/bar.conf", NULL); - GKeyFile *k = g_key_file_new(); - char *data; - - g_assert(!mkdir(subdir, 0700)); - g_assert(g_file_set_contents(file, conf, -1, NULL)); - g_assert(g_file_set_contents(file1, conf1, -1, NULL)); - - DBG("reading %s", file); - g_key_file_set_list_separator(k, ','); - ril_config_merge_files(k, file); - data = g_key_file_to_data(k, NULL, NULL); - DBG("\n%s", data); - g_assert(!g_strcmp0(data, out)); - g_free(data); - g_key_file_unref(k); - - remove(file); - remove(file1); - remove(subdir); - remove(dir); - - g_free(file); - g_free(file1); - g_free(dir); - g_free(subdir); -} - /* ==== get_string ==== */ static void test_get_string0_cb(GKeyFile *k) @@ -500,311 +427,6 @@ static void test_ints_to_string(void) g_assert(!ril_config_ints_to_string(NULL, 0)); } -/* ==== merge_basic ==== */ - -static void test_merge_basic(void) -{ - GKeyFile *k = g_key_file_new(); - char *nonexistent = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); - - ril_config_merge_files(NULL, NULL); - - remove(nonexistent); - ril_config_merge_files(k, nonexistent); - g_assert(test_keyfile_empty(k)); - - ril_config_merge_files(k, NULL); - g_assert(test_keyfile_empty(k)); - - ril_config_merge_files(k, ""); - g_assert(test_keyfile_empty(k)); - - g_key_file_unref(k); - g_free(nonexistent); -} - -/* ==== merge_simple ==== */ - -static void test_merge_simple(void) -{ - static const char contents [] = "[foo]\na=1\nb=2\n"; - char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); - char *file = g_strconcat(dir, "/foo.conf", NULL); - char *data; - GKeyFile *k = g_key_file_new(); - - g_assert(g_file_set_contents(file, contents, -1, NULL)); - DBG("reading %s", file); - ril_config_merge_files(k, file); - data = g_key_file_to_data(k, NULL, NULL); - DBG("\n%s", data); - g_assert(!g_strcmp0(data, contents)); - g_free(data); - g_key_file_unref(k); - - remove(file); - remove(dir); - - g_free(file); - g_free(dir); -} - -/* ==== merge_empty_dir ==== */ - -static void test_merge_empty_dir(void) -{ - static const char contents [] = "[foo]\na=1\nb=2\n"; - char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); - char *subdir = g_strconcat(dir, "/foo.d", NULL); - char *file = g_strconcat(dir, "/foo.conf", NULL); - GKeyFile *k = g_key_file_new(); - char *data; - - g_assert(!mkdir(subdir, 0700)); - g_assert(g_file_set_contents(file, contents, -1, NULL)); - DBG("reading %s", file); - ril_config_merge_files(k, file); - data = g_key_file_to_data(k, NULL, NULL); - DBG("\n%s", data); - g_assert(!g_strcmp0(data, contents)); - g_free(data); - g_key_file_unref(k); - - remove(file); - remove(subdir); - remove(dir); - - g_free(file); - g_free(dir); - g_free(subdir); -} - -/* ==== merge_ignore ==== */ - -static void test_merge_ignore0(void) -{ - static const char contents [] = "[foo]\na=1\nb=2\n"; - char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); - char *subdir = g_strconcat(dir, "/foo.d", NULL); - char *subdir2 = g_strconcat(subdir, "/dir.conf", NULL); - char *file = g_strconcat(dir, "/foo.conf", NULL); - GKeyFile *k = g_key_file_new(); - char *data; - - /* Two empty subdirectories, one with matching name, one not */ - g_assert(!mkdir(subdir, 0700)); - g_assert(!mkdir(subdir2, 0700)); - g_assert(g_file_set_contents(file, contents, -1, NULL)); - DBG("reading %s", file); - ril_config_merge_files(k, file); - data = g_key_file_to_data(k, NULL, NULL); - DBG("\n%s", data); - g_assert(!g_strcmp0(data, contents)); - g_free(data); - g_key_file_unref(k); - - remove(file); - remove(subdir2); - remove(subdir); - remove(dir); - - g_free(file); - g_free(dir); - g_free(subdir); - g_free(subdir2); -} - -static void test_merge_ignore1(void) -{ - static const char contents [] = "[foo]\na=1\nb=2\n"; - static const char contents1 [] = "[foo]\nb=3\n"; - - /* File has no suffix */ - test_merge_ignore("foo.conf", contents, "foo.d", "file", contents1); -} - -static void test_merge_ignore2(void) -{ - static const char contents [] = "[foo]\na=1\nb=2\n"; - static const char contents1 [] = "[[[[[[["; - - /* File is not a valid keyfile */ - test_merge_ignore("foo.conf", contents, "foo.d", "a.conf", contents1); -} - -/* ==== merge_sort ==== */ - -static void test_merge_sort(void) -{ - static const char contents [] = "[foo]\na=1\nb=2\n"; - static const char contents1 [] = "[foo]\nb=3\n"; - static const char contents2 [] = "[foo]\nb=4\n"; - static const char result [] = "[foo]\na=1\nb=4\n"; - - /* Test file sort order */ - char *dir = g_dir_make_tmp(TMP_DIR_TEMPLATE, NULL); - char *file = g_strconcat(dir, "/foo.", NULL); - char *subdir = g_strconcat(dir, "/foo.d", NULL); - char *file1 = g_strconcat(subdir, "/1.conf", NULL); - char *file2 = g_strconcat(subdir, "/2.conf", NULL); - GKeyFile *k = g_key_file_new(); - char *data; - - g_assert(!mkdir(subdir, 0700)); - g_assert(g_file_set_contents(file, contents, -1, NULL)); - g_assert(g_file_set_contents(file1, contents1, -1, NULL)); - g_assert(g_file_set_contents(file2, contents2, -1, NULL)); - - DBG("reading %s", file); - ril_config_merge_files(k, file); - data = g_key_file_to_data(k, NULL, NULL); - DBG("\n%s", data); - g_assert(!g_strcmp0(data, result)); - g_free(data); - g_key_file_unref(k); - - remove(file); - remove(file1); - remove(file2); - remove(subdir); - remove(dir); - - g_free(file); - g_free(file1); - g_free(file2); - g_free(dir); - g_free(subdir); -} - -/* ==== merge_remove_group ==== */ - -static void test_merge_remove_group(void) -{ - static const char contents [] = "[foo]\na=1\n\n[bar]\nb=1\n"; - static const char contents1 [] = "[!bar]\n"; - static const char result [] = "[foo]\na=1\n"; - - test_merge1(contents, contents1, result); -} - -/* ==== merge_remove_key ==== */ - -static void test_merge_remove_key(void) -{ - static const char contents [] = "[foo]\na=1\nb=2\n"; - static const char contents1 [] = "[foo]\n!b=\n\n!=\n"; - static const char result [] = "[foo]\na=1\n"; - - test_merge1(contents, contents1, result); -} - -/* ==== merge_default_value ==== */ - -static void test_merge_default_value(void) -{ - /* b is assigned the default value, a stays as is */ - static const char contents [] = "[foo]\na=1\n"; - static const char contents1 [] = "[foo]\na:=2\nb:=3\n"; - static const char result [] = "[foo]\na=1\nb=3\n"; - - test_merge1(contents, contents1, result); -} - -/* ==== merge_list_add ==== */ - -static void test_merge_list_add0(void) -{ - /* Adding empty list */ - static const char contents [] = "[foo]\na=1\nb=2\n"; - static const char contents1 [] = "[foo]\na+=\n"; - - test_merge1(contents, contents1, contents); -} - -static void test_merge_list_add1(void) -{ - /* a=1 turns into a=1,2, */ - static const char contents [] = "[foo]\na=1\nb=2\n"; - static const char contents1 [] = "[foo]\na+=2,\n"; - static const char result [] = "[foo]\na=1,2,\nb=2\n"; - - test_merge1(contents, contents1, result); -} - -static void test_merge_list_add2(void) -{ - /* 2 is already there */ - static const char contents [] = "[foo]\na=1,2,\nb=2\n"; - static const char contents1 [] = "[foo]\na?=2\n"; - - test_merge1(contents, contents1, contents); -} - -static void test_merge_list_add3(void) -{ - /* 2 is already there, 3 is not */ - static const char contents [] = "[foo]\na=1,2,\n"; - static const char contents1 [] = "[foo]\na?=2,3,\n"; - static const char result [] = "[foo]\na=1,2,3,\n"; - - test_merge1(contents, contents1, result); -} - -static void test_merge_list_add4(void) -{ - /* b=2,3, is created */ - static const char contents [] = "[foo]\na=1\n"; - static const char contents1 [] = "[foo]\nb?=2,3,\n"; - static const char result [] = "[foo]\na=1\nb=2,3,\n"; - - test_merge1(contents, contents1, result); -} - -static void test_merge_list_add5(void) -{ - /* Add a new group */ - static const char contents [] = "[foo]\na=1\n"; - static const char contents1 [] = "[bar]\nb=2\n"; - static const char result [] = "[foo]\na=1\n\n[bar]\nb=2\n"; - - test_merge1(contents, contents1, result); -} - -/* ==== merge_list_remove ==== */ - -static void test_merge_list_remove0(void) -{ - static const char contents [] = "[foo]\na=1,2,\n"; - static const char contents1 [] = "[foo]\na-=\n"; - - test_merge1(contents, contents1, contents); -} - -static void test_merge_list_remove1(void) -{ - static const char contents [] = "[foo]\na=1,2,\n"; - static const char contents1 [] = "[foo]\na-=2,\n"; - static const char result [] = "[foo]\na=1,\n"; - - test_merge1(contents, contents1, result); -} - -static void test_merge_list_remove2(void) -{ - static const char contents [] = "[foo]\na=1,2,\n"; - static const char contents1 [] = "[foo]\na-=3\n"; - - test_merge1(contents, contents1, contents); -} - -static void test_merge_list_remove3(void) -{ - static const char contents [] = "[foo]\na=1,2,\n"; - static const char contents1 [] = "[foo]\nb-=1\n"; - - test_merge1(contents, contents1, contents); -} - #define TEST_(name) "/ril_config/" name int main(int argc, char *argv[]) @@ -831,26 +453,6 @@ int main(int argc, char *argv[]) g_test_add_func(TEST_("get_enum"), test_get_enum); g_test_add_func(TEST_("get_ints"), test_get_ints); g_test_add_func(TEST_("ints_to_string"), test_ints_to_string); - g_test_add_func(TEST_("merge_basic"), test_merge_basic); - g_test_add_func(TEST_("merge_simple"), test_merge_simple); - g_test_add_func(TEST_("merge_empty_dir"), test_merge_empty_dir); - g_test_add_func(TEST_("merge_ignore0"), test_merge_ignore0); - g_test_add_func(TEST_("merge_ignore1"), test_merge_ignore1); - g_test_add_func(TEST_("merge_ignore2"), test_merge_ignore2); - g_test_add_func(TEST_("merge_sort"), test_merge_sort); - g_test_add_func(TEST_("merge_remove_group"), test_merge_remove_group); - g_test_add_func(TEST_("merge_remove_key"), test_merge_remove_key); - g_test_add_func(TEST_("merge_default_value"), test_merge_default_value); - g_test_add_func(TEST_("merge_list_add0"), test_merge_list_add0); - g_test_add_func(TEST_("merge_list_add1"), test_merge_list_add1); - g_test_add_func(TEST_("merge_list_add2"), test_merge_list_add2); - g_test_add_func(TEST_("merge_list_add3"), test_merge_list_add3); - g_test_add_func(TEST_("merge_list_add4"), test_merge_list_add4); - g_test_add_func(TEST_("merge_list_add5"), test_merge_list_add5); - g_test_add_func(TEST_("merge_list_remove0"), test_merge_list_remove0); - g_test_add_func(TEST_("merge_list_remove1"), test_merge_list_remove1); - g_test_add_func(TEST_("merge_list_remove2"), test_merge_list_remove2); - g_test_add_func(TEST_("merge_list_remove3"), test_merge_list_remove3); return g_test_run(); } diff --git a/ofono/unit/test-sailfish_manager.c b/ofono/unit/test-sailfish_manager.c index 42edb8971..a81aced1a 100644 --- a/ofono/unit/test-sailfish_manager.c +++ b/ofono/unit/test-sailfish_manager.c @@ -1,7 +1,8 @@ /* * oFono - Open Source Telephony * - * Copyright (C) 2017-2019 Jolla Ltd. + * Copyright (C) 2017-2020 Jolla Ltd. + * Copyright (C) 2019-2020 Open Mobile Platform LLC. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -46,6 +47,7 @@ #define TEST_SPN "Test" #define TEST_ERROR_KEY "Error" #define TEST_SLOT_ERROR_KEY "SlotError" +#define TEST_CONFIG_DIR_TEMPLATE "test-saifish_manager-config-XXXXXX" extern struct ofono_plugin_desc __ofono_builtin_sailfish_manager; static GMainLoop *test_loop = NULL; @@ -970,6 +972,128 @@ static void test_voice_sim(void) test_common_deinit(); } +/* ==== auto_data_sim ==== */ + +static gboolean test_auto_data_sim_done(gpointer user_data) +{ + test_slot_manager *sm = user_data; + test_slot *s = sm->slot; + struct sailfish_manager *m = fake_sailfish_manager_dbus.m; + struct ofono_watch *w = ofono_watch_new(TEST_PATH); + struct ofono_watch *w2 = ofono_watch_new(TEST_PATH_1); + struct ofono_modem modem; + struct ofono_sim sim; + struct ofono_sim sim2; + + memset(&modem, 0, sizeof(modem)); + memset(&sim, 0, sizeof(sim)); + sim.mcc = TEST_MCC; + sim.mnc = TEST_MNC; + sim.state = OFONO_SIM_STATE_READY; + sim2 = sim; + + /* Assign IMSI to the SIMs */ + w->modem = &modem; + fake_watch_signal_queue(w, FAKE_WATCH_SIGNAL_MODEM_CHANGED); + fake_watch_set_ofono_sim(w, &sim); + fake_watch_set_ofono_iccid(w, TEST_ICCID); + fake_watch_set_ofono_imsi(w, TEST_IMSI); + fake_watch_emit_queued_signals(w); + + w2->modem = &modem; + fake_watch_signal_queue(w2, FAKE_WATCH_SIGNAL_MODEM_CHANGED); + fake_watch_set_ofono_sim(w2, &sim2); + fake_watch_set_ofono_iccid(w2, TEST_ICCID_1); + fake_watch_set_ofono_imsi(w2, TEST_IMSI_1); + fake_watch_emit_queued_signals(w2); + + /* No data SIM yet, only voice SIM is assigned */ + g_assert(s->data_role == SAILFISH_DATA_ROLE_NONE); + g_assert(!m->default_voice_imsi); + g_assert(!g_strcmp0(m->default_voice_path, TEST_PATH)); + g_assert(!m->default_data_imsi); + g_assert(!m->default_data_path); + + /* Set the first modem online */ + w->online = TRUE; + fake_watch_signal_queue(w, FAKE_WATCH_SIGNAL_ONLINE_CHANGED); + fake_watch_emit_queued_signals(w); + + /* Now data modem must point to the first slot */ + g_assert(!g_strcmp0(m->default_data_path, TEST_PATH)); + + ofono_watch_unref(w); + ofono_watch_unref(w2); + g_main_loop_quit(test_loop); + return G_SOURCE_REMOVE; +} + +static guint test_auto_data_sim_start(test_slot_manager *sm) +{ + struct sailfish_manager *m = fake_sailfish_manager_dbus.m; + test_slot *s = g_new0(test_slot, 1); + test_slot *s2 = g_new0(test_slot, 1); + + DBG(""); + + /* Create the slots */ + DBG(""); + s->handle = sailfish_manager_slot_add(sm->handle, s, TEST_PATH, + OFONO_RADIO_ACCESS_MODE_GSM, NULL, TEST_IMEISV, + SAILFISH_SIM_STATE_PRESENT); + s2->handle = sailfish_manager_slot_add(sm->handle, s2, TEST_PATH_1, + OFONO_RADIO_ACCESS_MODE_GSM, NULL, TEST_IMEISV, + SAILFISH_SIM_STATE_PRESENT); + sm->slot = s; + sm->slot2 = s2; + sailfish_slot_manager_started(sm->handle); + + g_assert(!m->ready); + sailfish_manager_imei_obtained(s->handle, TEST_IMEI); + g_assert(!m->ready); + sailfish_manager_imei_obtained(s2->handle, TEST_IMEI_1); + g_assert(m->ready); + + g_idle_add(test_auto_data_sim_done, sm); + return 0; +} + +static void test_auto_data_sim(gconstpointer option) +{ + static const struct sailfish_slot_driver test_auto_data_sim_driver = { + .name = "auto_data_sim", + .manager_create = test_slot_manager_create, + .manager_start = test_auto_data_sim_start, + .manager_free = test_slot_manager_free, + .slot_enabled_changed = test_slot_enabled_changed, + .slot_free = test_slot_free + }; + char *cfg_dir = g_dir_make_tmp(TEST_CONFIG_DIR_TEMPLATE, NULL); + char *cfg_file = g_build_filename(cfg_dir, "main.conf", NULL); + GKeyFile* cfg = g_key_file_new(); + struct sailfish_slot_driver_reg *reg; + + g_key_file_set_string(cfg, "ModemManager", "AutoSelectDataSim", option); + g_assert(g_key_file_save_to_file(cfg, cfg_file, NULL)); + g_key_file_unref(cfg); + + __ofono_set_config_dir(cfg_dir); + test_common_init(); + reg = sailfish_slot_driver_register(&test_auto_data_sim_driver); + g_assert(reg); + + g_main_loop_run(test_loop); + + sailfish_slot_driver_unregister(reg); + test_common_deinit(); + + __ofono_set_config_dir(NULL); + remove(cfg_file); + remove(cfg_dir); + g_free(cfg_file); + g_free(cfg_dir); +} + /* ==== data_sim ==== */ static gboolean test_data_sim_done(gpointer user_data) @@ -1066,8 +1190,17 @@ static void test_data_sim(void) .slot_enabled_changed = test_slot_enabled_changed, .slot_free = test_slot_free }; + char *cfg_dir = g_dir_make_tmp(TEST_CONFIG_DIR_TEMPLATE, NULL); + char *cfg_file = g_build_filename(cfg_dir, "main.conf", NULL); + GKeyFile* cfg = g_key_file_new(); struct sailfish_slot_driver_reg *reg; + /* Invalid AutoSelectDataSim option is treated as "off" */ + g_key_file_set_string(cfg, "ModemManager", "AutoSelectDataSim", "x"); + g_assert(g_key_file_save_to_file(cfg, cfg_file, NULL)); + g_key_file_unref(cfg); + + __ofono_set_config_dir(cfg_dir); test_common_init(); reg = sailfish_slot_driver_register(&test_data_sim_driver); g_assert(reg); @@ -1076,6 +1209,12 @@ static void test_data_sim(void) sailfish_slot_driver_unregister(reg); test_common_deinit(); + + __ofono_set_config_dir(NULL); + remove(cfg_file); + remove(cfg_dir); + g_free(cfg_file); + g_free(cfg_dir); } /* ==== mms_sim ==== */ @@ -1511,6 +1650,12 @@ int main(int argc, char *argv[]) g_test_add_func(TEST_("cancel_start"), test_cancel_start); g_test_add_func(TEST_("voice_sim"), test_voice_sim); g_test_add_func(TEST_("data_sim"), test_data_sim); + g_test_add_data_func(TEST_("auto_data_sim_on"), "on", + test_auto_data_sim); + g_test_add_data_func(TEST_("auto_data_sim_always"), "always", + test_auto_data_sim); + g_test_add_data_func(TEST_("auto_data_sim_once"), "once", + test_auto_data_sim); g_test_add_func(TEST_("mms_sim"), test_mms_sim); g_test_add_func(TEST_("multisim"), test_multisim); g_test_add_func(TEST_("storage"), test_storage);