| Index: src/profile.c
|
| diff --git a/src/profile.c b/src/profile.c
|
| index 2bc14c781dd3f73ee03caf0dbf0c613d08927480..30fb5cec27866ff5844c5f24e2094c3664dc31f3 100644
|
| --- a/src/profile.c
|
| +++ b/src/profile.c
|
| @@ -28,24 +28,41 @@
|
| #include <glib.h>
|
| #include <gdbus.h>
|
|
|
| +#include <connman/assert.h>
|
| +
|
| #include "connman.h"
|
|
|
| #define PROFILE_DEFAULT_IDENT "default"
|
| +#define PROFILE_MAX 3 /* 2 is probably sufficient */
|
|
|
| #define _DBG_PROFILE(fmt, arg...) DBG(DBG_PROFILE, fmt, ## arg)
|
|
|
| struct connman_profile {
|
| - char *ident;
|
| + struct connman_storage_ident ident;
|
| char *path;
|
| char *name;
|
| connman_bool_t offlinemode;
|
| };
|
|
|
| static GHashTable *profile_hash = NULL;
|
| +
|
| +static struct connman_profile *profile_stack[PROFILE_MAX];
|
| +static int cur_profile = -1;
|
| +
|
| +static struct connman_storage_ident default_ident = {
|
| + .ident = PROFILE_DEFAULT_IDENT
|
| +};
|
| static struct connman_profile *default_profile = NULL;
|
|
|
| static DBusConnection *connection = NULL;
|
|
|
| +static int ident_equal(const struct connman_storage_ident *a,
|
| + const struct connman_storage_ident *b)
|
| +{
|
| + return (g_strcmp0(a->user, b->user) == 0 &&
|
| + g_strcmp0(a->ident, b->ident) == 0);
|
| +}
|
| +
|
| static void append_path(gpointer key, gpointer value, gpointer user_data)
|
| {
|
| struct connman_profile *profile = value;
|
| @@ -57,136 +74,256 @@ static void append_path(gpointer key, gpointer value, gpointer user_data)
|
|
|
| void __connman_profile_list(DBusMessageIter *iter, void *arg)
|
| {
|
| - _DBG_PROFILE("");
|
| -
|
| g_hash_table_foreach(profile_hash, append_path, iter);
|
| }
|
|
|
| static void profiles_changed(void)
|
| {
|
| - _DBG_PROFILE("");
|
| -
|
| connman_dbus_send_property_changed_array(CONNMAN_MANAGER_PATH,
|
| CONNMAN_MANAGER_INTERFACE, "Profiles",
|
| DBUS_TYPE_OBJECT_PATH, __connman_profile_list, NULL);
|
| }
|
|
|
| -static void name_changed(struct connman_profile *profile)
|
| +connman_bool_t __connman_profile_get_offlinemode(void)
|
| {
|
| - _DBG_PROFILE("profile %p", profile);
|
| + return (default_profile == NULL) ? FALSE : default_profile->offlinemode;
|
| +}
|
|
|
| - connman_dbus_send_property_changed_variant(profile->path,
|
| - CONNMAN_PROFILE_INTERFACE, "Name",
|
| - DBUS_TYPE_STRING, &profile->name);
|
| +int __connman_profile_set_offlinemode(connman_bool_t offlinemode)
|
| +{
|
| + _DBG_PROFILE("offlinemode %d", offlinemode);
|
| +
|
| + /* TODO(sleffler) disallow if no default profile? */
|
| + if (default_profile != NULL) {
|
| + /*
|
| + * OfflineMode is only saved to the default profile;
|
| + * this ensures it is preserved across user changes.
|
| + */
|
| + if (default_profile->offlinemode == offlinemode)
|
| + return -EALREADY;
|
| +
|
| + default_profile->offlinemode = offlinemode;
|
| +
|
| + connman_dbus_send_property_changed_variant(
|
| + default_profile->path,
|
| + CONNMAN_PROFILE_INTERFACE, "OfflineMode",
|
| + DBUS_TYPE_BOOLEAN, &offlinemode);
|
| + }
|
| + return __connman_device_set_offlinemode(offlinemode);
|
| }
|
|
|
| -static void offlinemode_changed(struct connman_profile *profile)
|
| +/*
|
| + * Return the active profile; it's on the top of the stack.
|
| + */
|
| +static inline struct connman_profile *active_profile(void)
|
| {
|
| - _DBG_PROFILE("profile %p", profile);
|
| + return cur_profile >= 0 ? profile_stack[cur_profile] : NULL;
|
| +}
|
| +
|
| +/*
|
| + * Load/Save objects. When loading we walk the stack of profiles
|
| + * until we find a successful load. Saving always happens to the
|
| + * top-most/active profile.
|
| + */
|
|
|
| - connman_dbus_send_property_changed_variant(profile->path,
|
| - CONNMAN_PROFILE_INTERFACE, "OfflineMode",
|
| - DBUS_TYPE_BOOLEAN, &profile->offlinemode);
|
| +static inline int load_continue(int err)
|
| +{
|
| + /* NB: ENXIO for no file, ESRCH for no group/key */
|
| + return (err == -ENXIO || err == -ESRCH);
|
| }
|
|
|
| -connman_bool_t __connman_profile_get_offlinemode(void)
|
| +int __connman_profile_load_service(struct connman_service *service)
|
| {
|
| - if (default_profile == NULL)
|
| - return FALSE;
|
| + int i, err;
|
|
|
| - _DBG_PROFILE("offlinemode %d", default_profile->offlinemode);
|
| + _DBG_PROFILE("service %p", service);
|
|
|
| - return default_profile->offlinemode;
|
| + err = 0;
|
| + for (i = cur_profile; i >= 0; i--) {
|
| + const struct connman_profile *profile = profile_stack[i];
|
| +
|
| + err = __connman_storage_load_service(service, &profile->ident);
|
| + if (!load_continue(err))
|
| + break;
|
| + }
|
| + return err;
|
| }
|
|
|
| -int __connman_profile_set_offlinemode(connman_bool_t offlinemode)
|
| +int __connman_profile_save_service(struct connman_service *service)
|
| {
|
| - _DBG_PROFILE("offlinemode %d", offlinemode);
|
| + struct connman_profile *profile = active_profile();
|
|
|
| - if (default_profile == NULL)
|
| - return -EINVAL;
|
| + _DBG_PROFILE("service %p profile %p", service, profile);
|
|
|
| - if (default_profile->offlinemode == offlinemode)
|
| - return -EALREADY;
|
| + return (profile == NULL) ? 0 :
|
| + __connman_storage_save_service(service, &profile->ident);
|
| +}
|
|
|
| - default_profile->offlinemode = offlinemode;
|
| - offlinemode_changed(default_profile);
|
| +int __connman_profile_load_device(struct connman_device *device)
|
| +{
|
| + int i, err;
|
|
|
| - __connman_device_set_offlinemode(offlinemode);
|
| + _DBG_PROFILE("device %p", device);
|
|
|
| - return 0;
|
| + err = 0;
|
| + for (i = cur_profile; i >= 0; i--) {
|
| + const struct connman_profile *profile = profile_stack[i];
|
| +
|
| + err = __connman_storage_load_device(device, &profile->ident);
|
| + if (!load_continue(err))
|
| + break;
|
| + }
|
| + return err;
|
| }
|
|
|
| -int __connman_profile_save_default(void)
|
| +int __connman_profile_save_device(struct connman_device *device)
|
| {
|
| + struct connman_profile *profile = active_profile();
|
| +
|
| + _DBG_PROFILE("device %p profile %p", device, profile);
|
| +
|
| + return (profile == NULL) ? 0:
|
| + __connman_storage_save_device(device, &profile->ident);
|
| +}
|
| +
|
| +int __connman_profile_load_ipconfig(struct connman_ipconfig *ipconfig)
|
| +{
|
| + int i, err;
|
| +
|
| + _DBG_PROFILE("ipconfig %p", ipconfig);
|
| +
|
| + err = 0;
|
| + for (i = cur_profile; i >= 0; i--) {
|
| + const struct connman_profile *profile = profile_stack[i];
|
| +
|
| + err = __connman_storage_load_ipconfig(ipconfig,
|
| + &profile->ident);
|
| + if (!load_continue(err))
|
| + break;
|
| + }
|
| + return err;
|
| +}
|
| +
|
| +int __connman_profile_save_ipconfig(const struct connman_ipconfig *ipconfig)
|
| +{
|
| + struct connman_profile *profile = active_profile();
|
| +
|
| + _DBG_PROFILE("ipconfig %p profile %p", ipconfig, profile);
|
| +
|
| + return (profile == NULL) ? 0 :
|
| + __connman_storage_save_ipconfig(ipconfig, &profile->ident);
|
| +}
|
| +
|
| +int __connman_profile_append_hidden_ssids(GSList **hidden_ssids,
|
| + void (*append_hidden_ssids)(GKeyFile *keyfile, GSList **hidden_ssids))
|
| +{
|
| + int i;
|
| +
|
| _DBG_PROFILE("");
|
|
|
| + for (i = cur_profile; i >= 0; i--) {
|
| + const struct connman_profile *profile = profile_stack[i];
|
| + GKeyFile *keyfile;
|
| +
|
| + keyfile = __connman_storage_open(&profile->ident);
|
| + if (keyfile != NULL) {
|
| + append_hidden_ssids(keyfile, hidden_ssids);
|
| + __connman_storage_close(&profile->ident, keyfile,
|
| + FALSE);
|
| + }
|
| + }
|
| + return 0;
|
| +}
|
| +
|
| +/*
|
| + * Save the default profile if registered.
|
| + */
|
| +int __connman_profile_save_default(void)
|
| +{
|
| if (default_profile != NULL)
|
| __connman_storage_save_profile(default_profile);
|
| -
|
| return 0;
|
| }
|
|
|
| -const char *__connman_profile_active_ident(void)
|
| +/*
|
| + * Save the active profile (if any).
|
| + */
|
| +int __connman_profile_save_active(void)
|
| {
|
| - _DBG_PROFILE("");
|
| + struct connman_profile *profile = active_profile();
|
| + return (profile == NULL) ? -EINVAL :
|
| + __connman_storage_save_profile(profile);
|
| +}
|
|
|
| - return PROFILE_DEFAULT_IDENT;
|
| +/*
|
| + * Return the identifier for the active profile or NULL
|
| + * if there is none.
|
| + */
|
| +const struct connman_storage_ident *__connman_profile_active_ident(void)
|
| +{
|
| + struct connman_profile *profile = active_profile();
|
| + return profile != NULL ? &profile->ident : NULL;
|
| }
|
|
|
| +/*
|
| + * Return the object path for the active profile or NULL
|
| + * if there is none.
|
| + */
|
| const char *__connman_profile_active_path(void)
|
| {
|
| - _DBG_PROFILE("");
|
| -
|
| - if (default_profile == NULL)
|
| - return NULL;
|
| -
|
| - return default_profile->path;
|
| + struct connman_profile *profile = active_profile();
|
| + return profile != NULL ? profile->path : NULL;
|
| }
|
|
|
| static guint changed_timeout = 0;
|
|
|
| +static void clear_timeout(void)
|
| +{
|
| + if (changed_timeout > 0) {
|
| + g_source_remove(changed_timeout);
|
| + changed_timeout = 0;
|
| + }
|
| +}
|
| +
|
| static void append_services(DBusMessageIter *iter, void *arg)
|
| {
|
| - const struct connman_profile *profile = arg;
|
| - if (g_strcmp0(profile->ident, PROFILE_DEFAULT_IDENT) == 0)
|
| - __connman_service_list(iter, arg);
|
| + __connman_service_list(iter, arg);
|
| }
|
| static gboolean services_changed(gpointer user_data)
|
| {
|
| - struct connman_profile *profile = default_profile;
|
| + struct connman_profile *profile = active_profile();
|
|
|
| changed_timeout = 0;
|
|
|
| if (profile != NULL) {
|
| connman_dbus_send_property_changed_array(profile->path,
|
| CONNMAN_PROFILE_INTERFACE, "Services",
|
| - DBUS_TYPE_OBJECT_PATH, append_services, profile);
|
| -
|
| - if (g_strcmp0(profile->ident, PROFILE_DEFAULT_IDENT) == 0)
|
| - connman_dbus_send_property_changed_array(
|
| - CONNMAN_MANAGER_PATH, CONNMAN_MANAGER_INTERFACE,
|
| - "Services",
|
| - DBUS_TYPE_OBJECT_PATH, append_services, profile);
|
| + DBUS_TYPE_OBJECT_PATH, append_services, NULL);
|
| }
|
| +
|
| + connman_dbus_send_property_changed_array(CONNMAN_MANAGER_PATH,
|
| + CONNMAN_MANAGER_INTERFACE, "Services",
|
| + DBUS_TYPE_OBJECT_PATH, append_services, NULL);
|
| return FALSE;
|
| }
|
|
|
| +/*
|
| + * Handle changes to the profile. Generate PropertyChanged signals
|
| + * on Manager.Services and Profile.Services for the currently active
|
| + * profile. To minimize overhead requests may be coalesced using a
|
| + * 1 second delay on the signals.
|
| + */
|
| void __connman_profile_changed(gboolean delayed)
|
| {
|
| - _DBG_PROFILE("");
|
| + _DBG_PROFILE("delayed %d changed_timeout %d", delayed, changed_timeout);
|
|
|
| - if (changed_timeout > 0) {
|
| - g_source_remove(changed_timeout);
|
| - changed_timeout = 0;
|
| - }
|
| + clear_timeout();
|
|
|
| - if (delayed == FALSE) {
|
| + if (delayed == TRUE) {
|
| + changed_timeout = g_timeout_add_seconds(1, services_changed,
|
| + NULL);
|
| + } else
|
| services_changed(NULL);
|
| - return;
|
| - }
|
| -
|
| - changed_timeout = g_timeout_add_seconds(1, services_changed, NULL);
|
| }
|
|
|
| int __connman_profile_add_device(struct connman_device *device)
|
| @@ -274,7 +411,7 @@ static void __profile_entry_list(DBusMessageIter *iter, void *arg)
|
|
|
| _DBG_PROFILE("profile %p", profile);
|
|
|
| - keyfile = __connman_storage_open(profile->ident);
|
| + keyfile = __connman_storage_open(&profile->ident);
|
| if (keyfile == NULL)
|
| return;
|
|
|
| @@ -297,7 +434,7 @@ static void __profile_entry_list(DBusMessageIter *iter, void *arg)
|
| }
|
| }
|
| g_strfreev(groups);
|
| - __connman_storage_close(profile->ident, keyfile, FALSE);
|
| + __connman_storage_close(&profile->ident, keyfile, FALSE);
|
| }
|
|
|
| static DBusMessage *get_properties(DBusConnection *conn,
|
| @@ -324,11 +461,13 @@ static DBusMessage *get_properties(DBusConnection *conn,
|
| connman_dbus_dict_append_variant(&dict, "Name",
|
| DBUS_TYPE_STRING, &profile->name);
|
|
|
| - connman_dbus_dict_append_variant(&dict, "OfflineMode",
|
| - DBUS_TYPE_BOOLEAN, &profile->offlinemode);
|
| + if (profile == default_profile)
|
| + connman_dbus_dict_append_variant(&dict, "OfflineMode",
|
| + DBUS_TYPE_BOOLEAN, &profile->offlinemode);
|
|
|
| - connman_dbus_dict_append_variant_array(&dict, "Services",
|
| - DBUS_TYPE_OBJECT_PATH, __connman_service_list, NULL);
|
| + if (profile == active_profile())
|
| + connman_dbus_dict_append_variant_array(&dict, "Services",
|
| + DBUS_TYPE_OBJECT_PATH, __connman_service_list, NULL);
|
|
|
| connman_dbus_dict_append_variant_array(&dict, "Entries",
|
| DBUS_TYPE_STRING, __profile_entry_list, profile);
|
| @@ -372,23 +511,11 @@ static DBusMessage *set_property(DBusConnection *conn,
|
| g_free(profile->name);
|
| profile->name = g_strdup(name);
|
|
|
| - if (profile->name != NULL)
|
| - name_changed(profile);
|
| -
|
| - __connman_storage_save_profile(profile);
|
| - } else if (g_str_equal(name, "OfflineMode") == TRUE) {
|
| - connman_bool_t offlinemode;
|
| -
|
| - if (type != DBUS_TYPE_BOOLEAN)
|
| - return __connman_error_invalid_arguments(msg);
|
| -
|
| - dbus_message_iter_get_basic(&value, &offlinemode);
|
| -
|
| - if (profile->offlinemode == offlinemode)
|
| - return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
|
| -
|
| - profile->offlinemode = offlinemode;
|
| - offlinemode_changed(profile);
|
| + if (profile->name != NULL) {
|
| + connman_dbus_send_property_changed_variant(
|
| + profile->path, CONNMAN_PROFILE_INTERFACE, "Name",
|
| + DBUS_TYPE_STRING, &profile->name);
|
| + }
|
|
|
| __connman_storage_save_profile(profile);
|
| } else
|
| @@ -415,6 +542,7 @@ static DBusMessage *get_entry(DBusConnection *conn,
|
| DBusMessageIter iter, array, dict;
|
| DBusMessage *reply;
|
| connman_bool_t autoconnect, hidden_ssid;
|
| + int len;
|
|
|
| _DBG_PROFILE("profile %p", profile);
|
|
|
| @@ -423,15 +551,16 @@ static DBusMessage *get_entry(DBusConnection *conn,
|
|
|
| dbus_message_iter_get_basic(&iter, &ident);
|
|
|
| - keyfile = __connman_storage_open(profile->ident);
|
| + keyfile = __connman_storage_open(&profile->ident);
|
| if (keyfile == NULL) {
|
| - _DBG_PROFILE("cannot open keyfile %s", profile->ident);
|
| + _DBG_PROFILE("cannot open keyfile %s:%s",
|
| + profile->ident.user, profile->ident.ident);
|
| return __connman_error_not_found(msg); /* XXX */
|
| }
|
|
|
| if (g_key_file_has_group(keyfile, ident) == FALSE) {
|
| _DBG_PROFILE("ident %s not found", ident);
|
| - __connman_storage_close(profile->ident, keyfile, FALSE);
|
| + __connman_storage_close(&profile->ident, keyfile, FALSE);
|
| return __connman_error_not_found(msg);
|
| }
|
|
|
| @@ -440,10 +569,11 @@ static DBusMessage *get_entry(DBusConnection *conn,
|
| * <type>_<device>_<ssid>_<mode>_<security> for wifi
|
| */
|
| tokens = g_strsplit(ident, "_", 0);
|
| - /* NB: tokens[0] is NULL if ident is "" */
|
| - if (tokens == NULL || tokens[0] == NULL) {
|
| - _DBG_PROFILE("ident %s malformed", ident);
|
| - __connman_storage_close(profile->ident, keyfile, FALSE);
|
| + len = g_strv_length(tokens);
|
| + if (len < 2) {
|
| + _DBG_PROFILE("ident %s malformed, len %d", ident, len);
|
| + g_strfreev(tokens);
|
| + __connman_storage_close(&profile->ident, keyfile, FALSE);
|
| return __connman_error_invalid_arguments(msg);
|
| }
|
|
|
| @@ -451,7 +581,7 @@ static DBusMessage *get_entry(DBusConnection *conn,
|
| if (reply == NULL) {
|
| _DBG_PROFILE("cannot allocate reply");
|
| g_strfreev(tokens);
|
| - __connman_storage_close(profile->ident, keyfile, FALSE);
|
| + __connman_storage_close(&profile->ident, keyfile, FALSE);
|
| return NULL;
|
| }
|
|
|
| @@ -467,14 +597,17 @@ static DBusMessage *get_entry(DBusConnection *conn,
|
|
|
| switch (__connman_service_string2type(val)) {
|
| case CONNMAN_SERVICE_TYPE_WIFI:
|
| - if (g_strv_length(tokens) != 5) {
|
| - /* TODO(sleffler) accept > 5? */
|
| - _DBG_PROFILE("bad token cnt %d", g_strv_length(tokens));
|
| + if (len != 5 && len != 6) {
|
| + _DBG_PROFILE("bad token cnt %d for ident %s",
|
| + len, ident);
|
| break;
|
| }
|
| val = tokens[3];
|
| ADD_STR("Mode", &val);
|
| val = tokens[4];
|
| + /* NB: g_strsplit breaks 802_1x into 802+1x; restore */
|
| + if (g_strcmp0(val, "802") == 0)
|
| + val = "802_1x";
|
| ADD_STR("Security", &val);
|
| break;
|
| case CONNMAN_SERVICE_TYPE_CELLULAR:
|
| @@ -503,7 +636,7 @@ static DBusMessage *get_entry(DBusConnection *conn,
|
| dbus_message_iter_close_container(&array, &dict);
|
|
|
| g_strfreev(tokens);
|
| - __connman_storage_close(profile->ident, keyfile, FALSE);
|
| + __connman_storage_close(&profile->ident, keyfile, FALSE);
|
|
|
| return reply;
|
| #undef VAL_STR
|
| @@ -520,7 +653,8 @@ static DBusMessage *delete_entry(DBusConnection *conn,
|
| GKeyFile *keyfile;
|
| gboolean status;
|
|
|
| - _DBG_PROFILE("profile %s", profile->ident);
|
| + _DBG_PROFILE("profile %s:%s", profile->ident.user,
|
| + profile->ident.ident);
|
|
|
| if (dbus_message_iter_init(msg, &iter) == FALSE)
|
| return __connman_error_invalid_arguments(msg);
|
| @@ -538,14 +672,14 @@ static DBusMessage *delete_entry(DBusConnection *conn,
|
| }
|
|
|
| /* Remove directly from profile */
|
| - keyfile = __connman_storage_open(profile->ident);
|
| + keyfile = __connman_storage_open(&profile->ident);
|
| if (keyfile == NULL) {
|
| _DBG_PROFILE("cannot open key file");
|
| return __connman_error_invalid_arguments(msg);
|
| }
|
|
|
| status = g_key_file_remove_group(keyfile, identifier, NULL);
|
| - __connman_storage_close(profile->ident, keyfile, status);
|
| + __connman_storage_close(&profile->ident, keyfile, status);
|
|
|
| if (status == FALSE) {
|
| _DBG_PROFILE("cannot remove %s", identifier);
|
| @@ -568,11 +702,20 @@ static GDBusSignalTable profile_signals[] = {
|
| { },
|
| };
|
|
|
| +static void free_ident(struct connman_storage_ident *ident)
|
| +{
|
| + /* NB: blech, don't reclaim, it's statically allocated */
|
| + if (!ident_equal(ident, &default_ident)) {
|
| + g_free(ident->user);
|
| + g_free(ident->ident);
|
| + }
|
| +}
|
| +
|
| static void free_profile(struct connman_profile *profile)
|
| {
|
| - g_free(profile->name);
|
| + free_ident(&profile->ident);
|
| g_free(profile->path);
|
| - g_free(profile->ident);
|
| + g_free(profile->name);
|
| g_free(profile);
|
| }
|
|
|
| @@ -582,32 +725,50 @@ static void unregister_profile(gpointer data)
|
|
|
| _DBG_PROFILE("profile %p", profile);
|
|
|
| - connman_info("Removing profile %s", profile->ident);
|
| + connman_info("Remove profile %s:%s", profile->ident.user,
|
| + profile->ident.ident);
|
|
|
| g_dbus_unregister_interface(connection, profile->path,
|
| CONNMAN_PROFILE_INTERFACE);
|
|
|
| - if (g_strcmp0(profile->ident, PROFILE_DEFAULT_IDENT) == 0)
|
| + if (profile == default_profile)
|
| default_profile = NULL;
|
|
|
| free_profile(profile);
|
| }
|
|
|
| -static int create_profile(const char *ident, const char *name,
|
| - const char **path)
|
| +static char *getpath(const struct connman_storage_ident *ident)
|
| +{
|
| + if (ident->user != NULL) {
|
| + /* NB: check for two tokens done in validate_ident */
|
| + return g_strdup_printf("/profile/%s/%s",
|
| + ident->user, ident->ident);
|
| + } else
|
| + return g_strdup_printf("/profile/%s", ident->ident);
|
| +}
|
| +
|
| +/*
|
| + * Create an in-memory profile using the specified identifier
|
| + * and name. The object path and a reference to the profile
|
| + * data structure are returned on success.
|
| + */
|
| +static int create_profile(struct connman_storage_ident *ident,
|
| + const char *name, const char **path, struct connman_profile **pprofile)
|
| {
|
| struct connman_profile *profile;
|
|
|
| - _DBG_PROFILE("ident %s name %s", ident, name);
|
| + _DBG_PROFILE("ident %s:%s name %s", ident->user, ident->ident, name);
|
|
|
| profile = g_try_new0(struct connman_profile, 1);
|
| - if (profile == NULL)
|
| + if (profile == NULL) {
|
| + free_ident(ident);
|
| return -ENOMEM;
|
| + }
|
|
|
| - profile->ident = g_strdup(ident);
|
| - profile->path = g_strdup_printf("/profile/%s", ident);
|
| -
|
| - if (profile->ident == NULL || profile->path == NULL) {
|
| + profile->ident = *ident;
|
| + profile->path = getpath(ident);
|
| + /* TODO(sleffler) check ident.user */
|
| + if (profile->ident.ident == NULL || profile->path == NULL) {
|
| free_profile(profile);
|
| return -ENOMEM;
|
| }
|
| @@ -623,9 +784,9 @@ static int create_profile(const char *ident, const char *name,
|
|
|
| g_hash_table_insert(profile_hash, g_strdup(profile->path), profile);
|
|
|
| - connman_info("Adding profile %s", ident);
|
| + connman_info("Add profile %s:%s", ident->user, ident->ident);
|
|
|
| - if (g_strcmp0(ident, PROFILE_DEFAULT_IDENT) == 0)
|
| + if (ident_equal(ident, &default_ident))
|
| default_profile = profile;
|
|
|
| g_dbus_register_interface(connection, profile->path,
|
| @@ -638,10 +799,16 @@ static int create_profile(const char *ident, const char *name,
|
|
|
| _DBG_PROFILE("profile %p path %s", profile, profile->path);
|
|
|
| + *pprofile = profile;
|
| return 0;
|
| }
|
|
|
| -static gboolean validate_ident(const char *ident)
|
| +/*
|
| + * Check a profile identifier token. This must be non-null and
|
| + * made up of alpha-numeric chars suitable for use in a D-bus
|
| + * object path.
|
| + */
|
| +static gboolean validate_token(const char *ident)
|
| {
|
| unsigned int i;
|
| unsigned int len = strlen(ident);
|
| @@ -662,95 +829,292 @@ static gboolean validate_ident(const char *ident)
|
| return TRUE;
|
| }
|
|
|
| -int __connman_profile_create(const char *name, const char **path)
|
| +/*
|
| + * Validate the profile identifier. It must be suitable
|
| + * for use in a D-Bus object path and, optionally, be of
|
| + * the form ~user/ident to signify a per-user profile.
|
| + */
|
| +static gboolean parse_ident(const char *str,
|
| + struct connman_storage_ident *ident)
|
| +{
|
| + gboolean is_valid;
|
| +
|
| + if (str[0] == '~') {
|
| + /* syntax is ~user/name for profile in cryptohome */
|
| + gchar **tokens = g_strsplit_set(str, "~/", 4);
|
| + if (g_strv_length(tokens) == 3) {
|
| + is_valid = (validate_token(tokens[1]) == TRUE &&
|
| + validate_token(tokens[2]) == TRUE);
|
| + if (is_valid) {
|
| + ident->user = g_strdup(tokens[1]);
|
| + ident->ident = g_strdup(tokens[2]);
|
| + }
|
| + } else
|
| + is_valid = FALSE;
|
| + g_strfreev(tokens);
|
| + } else {
|
| + is_valid = validate_token(str);
|
| + if (is_valid) {
|
| + ident->user = NULL;
|
| + ident->ident = g_strdup(str);
|
| + }
|
| + }
|
| + return is_valid;
|
| +}
|
| +
|
| +/*
|
| + * Lookup a profile on the stack by object path.
|
| + */
|
| +static struct connman_profile *lookup_stack_by_path(const char *path)
|
| +{
|
| + int i;
|
| +
|
| + for (i = cur_profile; i >= 0; i--)
|
| + if (g_strcmp0(profile_stack[i]->path, path) == 0)
|
| + return profile_stack[i];
|
| + return NULL;
|
| +}
|
| +
|
| +/*
|
| + * Push a profile on the stack and make it the ``active profile''.
|
| + * The profile may be currently registered in memory or previously
|
| + * created by CreateProfile. The profile may not already be on
|
| + * the stack.
|
| + */
|
| +int __connman_profile_push(const char *ident, const char *name,
|
| + const char **path)
|
| {
|
| struct connman_profile *profile;
|
| + struct connman_storage_ident sid;
|
| + char *tmp_path;
|
| int err;
|
|
|
| - _DBG_PROFILE("name %s", name);
|
| + _DBG_PROFILE("ident %s name %s", ident, name);
|
|
|
| - if (validate_ident(name) == FALSE)
|
| + if (parse_ident(ident, &sid) == FALSE) {
|
| + connman_error("%s: invalid profile name %s", __func__, ident);
|
| return -EINVAL;
|
| + }
|
|
|
| - err = create_profile(name, NULL, path);
|
| - if (err < 0)
|
| - return err;
|
| + if (cur_profile+1 >= PROFILE_MAX) {
|
| + connman_error("%s: too many profiles (max %d)", __func__,
|
| + PROFILE_MAX);
|
| + free_ident(&sid);
|
| + return -EMFILE;
|
| + }
|
|
|
| - profile = g_hash_table_lookup(profile_hash, *path);
|
| - if (profile == NULL)
|
| - return -EIO;
|
| + /*
|
| + * Check for in-memory profile by way of a CreateProfile request.
|
| + */
|
| + tmp_path = getpath(&sid);
|
| + if (tmp_path == NULL) {
|
| + connman_error("%s: no memory for %s", __func__, ident);
|
| + free_ident(&sid);
|
| + return -ENOMEM;
|
| + }
|
|
|
| - __connman_storage_save_profile(profile);
|
| + profile = g_hash_table_lookup(profile_hash, tmp_path);
|
| + g_free(tmp_path);
|
| + if (profile == NULL) {
|
| + /*
|
| + * Not in memory; accept an existing file.
|
| + */
|
| + if (__connman_storage_exists(&sid) == FALSE) {
|
| + connman_error("%s: profile %s does not exist",
|
| + __func__, ident);
|
| + free_ident(&sid);
|
| + return -ENOENT;
|
| + }
|
| +
|
| + err = create_profile(&sid, name, path, &profile);
|
| + if (err < 0) {
|
| + connman_error("%s: cannot open, error %d",
|
| + __func__, err);
|
| + /* NB: create_profile reclaims sid */
|
| + return err;
|
| + }
|
| + } else {
|
| + free_ident(&sid); /* NB: not needed below */
|
| + /*
|
| + * Check this profile is not already on the stack.
|
| + */
|
| + if (lookup_stack_by_path(profile->path) != NULL) {
|
| + connman_error("%s: already pushed", __func__);
|
| + return -EEXIST;
|
| + }
|
| + if (path != NULL)
|
| + *path = profile->path;
|
| + }
|
| +
|
| + profile_stack[++cur_profile] = profile;
|
|
|
| profiles_changed();
|
|
|
| + /* NB: this is a noop if we are online (or trying to get online) */
|
| + __connman_service_auto_connect_any();
|
| +
|
| return 0;
|
| }
|
|
|
| -int __connman_profile_remove(const char *path)
|
| +/*
|
| + * Pop the profile from the top of the stack and remove it from
|
| + * the in-memory table. Any associated services are invalidated
|
| + * (credentials revoked and connections closed). After a pop we
|
| + * generate a PropertyChanged signal for Manager.Profiles.
|
| + *
|
| + * An optional identifier may be specified. If specified it is
|
| + * checked against the active profile and if not the same then
|
| + * the request is rejected. This is useful to ensure the right
|
| + * profile is pop'd (as might happen on logout if an associated
|
| + * push failed for some reason).
|
| + */
|
| +int __connman_profile_pop(const char *ident)
|
| {
|
| struct connman_profile *profile;
|
|
|
| - _DBG_PROFILE("path %s", path);
|
| + _DBG_PROFILE("ident %s", ident);
|
|
|
| - if (default_profile != NULL &&
|
| - g_strcmp0(path, default_profile->path) == 0)
|
| + if (cur_profile < 0) {
|
| + connman_error("%s: profile stack empty", __func__);
|
| return -EINVAL;
|
| + }
|
| +
|
| + profile = profile_stack[cur_profile];
|
|
|
| - profile = g_hash_table_lookup(profile_hash, path);
|
| - if (profile == NULL)
|
| - return -ENXIO;
|
| + if (ident != NULL) {
|
| + struct connman_storage_ident sid;
|
| +
|
| + if (parse_ident(ident, &sid) == FALSE) {
|
| + connman_error("%s: invalid profile name %s",
|
| + __func__, ident);
|
| + return -EINVAL;
|
| + }
|
| + if (ident_equal(&profile->ident, &sid) == FALSE) {
|
| + connman_error("%s: %s is not the active profile",
|
| + __func__, ident);
|
| + free_ident(&sid);
|
| + return -ENXIO;
|
| + }
|
| + free_ident(&sid);
|
| + }
|
|
|
| - __connman_storage_delete(profile->ident);
|
| + __connman_service_invalidate_profile(&profile->ident);
|
|
|
| - g_hash_table_remove(profile_hash, path);
|
| + g_hash_table_remove(profile_hash, profile->path);
|
| + cur_profile--;
|
|
|
| profiles_changed();
|
|
|
| + /* NB: no need to kick auto-connect; it will happen due to invalidate */
|
| +
|
| return 0;
|
| }
|
|
|
| -static int profile_init(void)
|
| +/*
|
| + * Create a profile file and register it in memory. The
|
| + * file is created with minimal contents.
|
| + * TODO(sleffler) disallow overwriting an existing file?
|
| + */
|
| +int __connman_profile_create(const char *name, const char **path)
|
| {
|
| - GDir *dir;
|
| - const gchar *file;
|
| + struct connman_profile *profile;
|
| + struct connman_storage_ident sid;
|
| + int err;
|
|
|
| - _DBG_PROFILE("");
|
| + _DBG_PROFILE("name %s", name);
|
|
|
| - dir = g_dir_open(STORAGEDIR, 0, NULL);
|
| - if (dir != NULL) {
|
| - while ((file = g_dir_read_name(dir)) != NULL) {
|
| - GString *str;
|
| - gchar *ident;
|
| + if (parse_ident(name, &sid) == FALSE) {
|
| + connman_error("%s: invalid profile name %s)", __func__, name);
|
| + return -EINVAL;
|
| + }
|
|
|
| - if (g_str_has_suffix(file, ".profile") == FALSE)
|
| - continue;
|
| + err = create_profile(&sid, NULL, path, &profile);
|
| + if (err < 0) {
|
| + connman_error("%s: cannot open, error %d", __func__, err);
|
| + /* NB: create_profile reclaims sid */
|
| + return err;
|
| + }
|
|
|
| - ident = g_strrstr(file, ".profile");
|
| - if (ident == NULL)
|
| - continue;
|
| + __connman_storage_save_profile(profile);
|
| +
|
| + profiles_changed();
|
|
|
| - str = g_string_new_len(file, ident - file);
|
| - if (str == NULL)
|
| - continue;
|
| + return 0;
|
| +}
|
|
|
| - ident = g_string_free(str, FALSE);
|
| +/*
|
| + * Delete a profile and remove from memory. The default
|
| + * profile may not be removed/deleted. Likewise, one
|
| + * cannot remove a profile pushed on the stack.
|
| + */
|
| +int __connman_profile_remove(const char *ident)
|
| +{
|
| + struct connman_profile *profile;
|
| + struct connman_storage_ident sid;
|
| + char *tmp_path;
|
| + int err = 0;
|
|
|
| - if (validate_ident(ident) == TRUE)
|
| - create_profile(ident, NULL, NULL);
|
| + _DBG_PROFILE("ident %s", ident);
|
|
|
| - g_free(ident);
|
| + if (parse_ident(ident, &sid) == FALSE) {
|
| + connman_error("%s: invalid profile name %s)", __func__, ident);
|
| + return -EINVAL;
|
| + }
|
| +
|
| + if (ident_equal(&sid, &default_ident)) {
|
| + /* TODO(sleffler) should this be permitted? */
|
| + connman_error("%s: cannot delete default profile", __func__);
|
| + err = -EINVAL;
|
| + goto done;
|
| + }
|
| +
|
| + /*
|
| + * Check for in-memory profile by way of a CreateProfile request.
|
| + */
|
| + tmp_path = getpath(&sid);
|
| + if (tmp_path == NULL) {
|
| + connman_error("%s: no memory for %s", __func__, ident);
|
| + err = -ENOMEM;
|
| + goto done;
|
| + }
|
| + profile = g_hash_table_lookup(profile_hash, tmp_path);
|
| + g_free(tmp_path);
|
| +
|
| + if (profile != NULL) {
|
| + /*
|
| + * Check this profile is not on the stack.
|
| + */
|
| + if (lookup_stack_by_path(profile->path) != NULL) {
|
| + connman_error("%s: cannot delete (on the stack)",
|
| + __func__);
|
| + err = -EEXIST;
|
| + goto done;
|
| }
|
| + g_hash_table_remove(profile_hash, profile->path);
|
|
|
| - g_dir_close(dir);
|
| + profiles_changed();
|
| }
|
|
|
| - if (g_hash_table_size(profile_hash) == 0)
|
| - create_profile(PROFILE_DEFAULT_IDENT, "Default", NULL);
|
| + __connman_storage_delete(&sid);
|
| +done:
|
| + free_ident(&sid);
|
| + return err;
|
| +}
|
|
|
| - profiles_changed();
|
| +/*
|
| + * Initialize the profile stack by pushing the default
|
| + * (global) profile. Unlike connman we do not read in
|
| + * all available .profile's in the global dir.
|
| + */
|
| +static int profile_init(void)
|
| +{
|
| + struct connman_profile *profile;
|
| + int err;
|
|
|
| - return 0;
|
| + err = create_profile(&default_ident, "Default", NULL, &profile);
|
| + if (err == 0)
|
| + profile_stack[++cur_profile] = profile;
|
| + return err;
|
| }
|
|
|
| static int profile_load(struct connman_profile *profile)
|
| @@ -762,7 +1126,7 @@ static int profile_load(struct connman_profile *profile)
|
|
|
| _DBG_PROFILE("profile %p", profile);
|
|
|
| - keyfile = __connman_storage_open(profile->ident);
|
| + keyfile = __connman_storage_open(&profile->ident);
|
| if (keyfile == NULL)
|
| return -EIO;
|
|
|
| @@ -778,7 +1142,7 @@ static int profile_load(struct connman_profile *profile)
|
| profile->offlinemode = offlinemode;
|
| g_clear_error(&error);
|
|
|
| - __connman_storage_close(profile->ident, keyfile, FALSE);
|
| + __connman_storage_close(&profile->ident, keyfile, FALSE);
|
|
|
| return 0;
|
| }
|
| @@ -789,7 +1153,7 @@ static int profile_save(struct connman_profile *profile)
|
|
|
| _DBG_PROFILE("profile %p", profile);
|
|
|
| - keyfile = __connman_storage_open(profile->ident);
|
| + keyfile = __connman_storage_open(&profile->ident);
|
| if (keyfile == NULL)
|
| return -EIO;
|
|
|
| @@ -800,7 +1164,7 @@ static int profile_save(struct connman_profile *profile)
|
| g_key_file_set_boolean(keyfile, "global",
|
| "OfflineMode", profile->offlinemode);
|
|
|
| - __connman_storage_close(profile->ident, keyfile, TRUE);
|
| + __connman_storage_close(&profile->ident, keyfile, TRUE);
|
|
|
| return 0;
|
| }
|
| @@ -830,6 +1194,21 @@ int __connman_profile_init(void)
|
| return 0;
|
| }
|
|
|
| +int __connman_profile_push_batch(char **profiles)
|
| +{
|
| + int i;
|
| +
|
| + if (profiles == NULL)
|
| + return 0;
|
| +
|
| + for (i = 0; profiles[i] != NULL; i++) {
|
| + int err = __connman_profile_push(profiles[i], NULL, NULL);
|
| + if (err != 0)
|
| + return err;
|
| + }
|
| + return 0;
|
| +}
|
| +
|
| void __connman_profile_cleanup(void)
|
| {
|
| _DBG_PROFILE("");
|
| @@ -837,9 +1216,14 @@ void __connman_profile_cleanup(void)
|
| if (connection == NULL)
|
| return;
|
|
|
| + while (cur_profile >= 0)
|
| + __connman_profile_pop(NULL);
|
| +
|
| g_hash_table_destroy(profile_hash);
|
| profile_hash = NULL;
|
|
|
| + clear_timeout(); /* cancel any pending timer */
|
| +
|
| connman_storage_unregister(&profile_storage);
|
|
|
| dbus_connection_unref(connection);
|
|
|