blob: 63fb7cf831679b2710258d5ebea1b1405ea0d67b [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2012 Intel Corporation. All rights reserved.
*
* 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.
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <string.h>
#include <gdbus.h>
#include "connman.h"
static DBusConnection *connection;
static GSList *technology_list = NULL;
/*
* List of devices with no technology associated with them either because of
* no compiled in support or the driver is not yet loaded.
*/
static GSList *techless_device_list = NULL;
static GHashTable *rfkill_list;
static connman_bool_t global_offlinemode;
struct connman_rfkill {
unsigned int index;
enum connman_service_type type;
connman_bool_t softblock;
connman_bool_t hardblock;
};
struct connman_technology {
int refcount;
enum connman_service_type type;
char *path;
GSList *device_list;
connman_bool_t enabled;
char *regdom;
connman_bool_t connected;
connman_bool_t tethering;
char *tethering_ident;
char *tethering_passphrase;
connman_bool_t enable_persistent; /* Save the tech state */
GSList *driver_list;
DBusMessage *pending_reply;
guint pending_timeout;
GSList *scan_pending;
connman_bool_t rfkill_driven;
connman_bool_t softblocked;
connman_bool_t hardblocked;
connman_bool_t dbus_registered;
};
static GSList *driver_list = NULL;
static gint compare_priority(gconstpointer a, gconstpointer b)
{
const struct connman_technology_driver *driver1 = a;
const struct connman_technology_driver *driver2 = b;
return driver2->priority - driver1->priority;
}
static void rfkill_check(gpointer key, gpointer value, gpointer user_data)
{
struct connman_rfkill *rfkill = value;
enum connman_service_type type = GPOINTER_TO_INT(user_data);
/* Calling _technology_rfkill_add will update the tech. */
if (rfkill->type == type)
__connman_technology_add_rfkill(rfkill->index, type,
rfkill->softblock, rfkill->hardblock);
}
/**
* connman_technology_driver_register:
* @driver: Technology driver definition
*
* Register a new technology driver
*
* Returns: %0 on success
*/
int connman_technology_driver_register(struct connman_technology_driver *driver)
{
GSList *list;
struct connman_device *device;
enum connman_service_type type;
DBG("Registering %s driver", driver->name);
driver_list = g_slist_insert_sorted(driver_list, driver,
compare_priority);
/*
* Check for technology less devices if this driver
* can service any of them.
*/
for (list = techless_device_list; list != NULL; list = list->next) {
device = list->data;
type = __connman_device_get_service_type(device);
if (type != driver->type)
continue;
techless_device_list = g_slist_remove(techless_device_list,
device);
__connman_technology_add_device(device);
}
/* Check for orphaned rfkill switches. */
g_hash_table_foreach(rfkill_list, rfkill_check,
GINT_TO_POINTER(driver->type));
return 0;
}
/**
* connman_technology_driver_unregister:
* @driver: Technology driver definition
*
* Remove a previously registered technology driver
*/
void connman_technology_driver_unregister(struct connman_technology_driver *driver)
{
GSList *list, *tech_drivers;
struct connman_technology *technology;
struct connman_technology_driver *current;
DBG("Unregistering driver %p name %s", driver, driver->name);
for (list = technology_list; list; list = list->next) {
technology = list->data;
for (tech_drivers = technology->driver_list;
tech_drivers != NULL;
tech_drivers = g_slist_next(tech_drivers)) {
current = tech_drivers->data;
if (driver != current)
continue;
if (driver->remove != NULL)
driver->remove(technology);
technology->driver_list =
g_slist_remove(technology->driver_list, driver);
break;
}
}
driver_list = g_slist_remove(driver_list, driver);
}
static void tethering_changed(struct connman_technology *technology)
{
connman_bool_t tethering = technology->tethering;
connman_dbus_property_changed_basic(technology->path,
CONNMAN_TECHNOLOGY_INTERFACE, "Tethering",
DBUS_TYPE_BOOLEAN, &tethering);
}
void connman_technology_tethering_notify(struct connman_technology *technology,
connman_bool_t enabled)
{
GSList *list;
DBG("technology %p enabled %u", technology, enabled);
if (technology->tethering == enabled)
return;
technology->tethering = enabled;
tethering_changed(technology);
if (enabled == TRUE)
__connman_tethering_set_enabled();
else {
for (list = technology_list; list; list = list->next) {
struct connman_technology *other_tech = list->data;
if (other_tech->tethering == TRUE)
break;
}
if (list == NULL)
__connman_tethering_set_disabled();
}
}
static int set_tethering(struct connman_technology *technology,
connman_bool_t enabled)
{
int result = -EOPNOTSUPP;
int err;
const char *ident, *passphrase, *bridge;
GSList *tech_drivers;
ident = technology->tethering_ident;
passphrase = technology->tethering_passphrase;
__sync_synchronize();
if (technology->enabled == FALSE)
return -EACCES;
bridge = __connman_tethering_get_bridge();
if (bridge == NULL)
return -EOPNOTSUPP;
if (technology->type == CONNMAN_SERVICE_TYPE_WIFI &&
(ident == NULL || passphrase == NULL))
return -EINVAL;
for (tech_drivers = technology->driver_list; tech_drivers != NULL;
tech_drivers = g_slist_next(tech_drivers)) {
struct connman_technology_driver *driver = tech_drivers->data;
if (driver == NULL || driver->set_tethering == NULL)
continue;
err = driver->set_tethering(technology, ident, passphrase,
bridge, enabled);
if (result == -EINPROGRESS)
continue;
if (err == -EINPROGRESS || err == 0) {
result = err;
continue;
}
}
return result;
}
void connman_technology_regdom_notify(struct connman_technology *technology,
const char *alpha2)
{
DBG("");
if (alpha2 == NULL)
connman_error("Failed to set regulatory domain");
else
DBG("Regulatory domain set to %s", alpha2);
g_free(technology->regdom);
technology->regdom = g_strdup(alpha2);
}
static int set_regdom_by_device(struct connman_technology *technology,
const char *alpha2)
{
GSList *list;
for (list = technology->device_list; list; list = list->next) {
struct connman_device *device = list->data;
if (connman_device_set_regdom(device, alpha2) != 0)
return -ENOTSUP;
}
return 0;
}
int connman_technology_set_regdom(const char *alpha2)
{
GSList *list, *tech_drivers;
for (list = technology_list; list; list = list->next) {
struct connman_technology *technology = list->data;
if (set_regdom_by_device(technology, alpha2) != 0) {
for (tech_drivers = technology->driver_list;
tech_drivers != NULL;
tech_drivers = g_slist_next(tech_drivers)) {
struct connman_technology_driver *driver =
tech_drivers->data;
if (driver->set_regdom != NULL)
driver->set_regdom(technology, alpha2);
}
}
}
return 0;
}
static void free_rfkill(gpointer data)
{
struct connman_rfkill *rfkill = data;
g_free(rfkill);
}
static const char *get_name(enum connman_service_type type)
{
switch (type) {
case CONNMAN_SERVICE_TYPE_UNKNOWN:
case CONNMAN_SERVICE_TYPE_SYSTEM:
case CONNMAN_SERVICE_TYPE_GPS:
case CONNMAN_SERVICE_TYPE_VPN:
case CONNMAN_SERVICE_TYPE_GADGET:
break;
case CONNMAN_SERVICE_TYPE_ETHERNET:
return "Wired";
case CONNMAN_SERVICE_TYPE_WIFI:
return "WiFi";
case CONNMAN_SERVICE_TYPE_BLUETOOTH:
return "Bluetooth";
case CONNMAN_SERVICE_TYPE_CELLULAR:
return "Cellular";
case CONNMAN_SERVICE_TYPE_LOWPAN:
return "Thread";
}
return NULL;
}
static void technology_save(struct connman_technology *technology)
{
GKeyFile *keyfile;
gchar *identifier;
DBG("technology %p", technology);
keyfile = __connman_storage_load_global();
if (keyfile == NULL)
keyfile = g_key_file_new();
identifier = g_strdup_printf("%s", get_name(technology->type));
if (identifier == NULL)
goto done;
g_key_file_set_boolean(keyfile, identifier, "Enable",
technology->enable_persistent);
if (technology->tethering_ident != NULL)
g_key_file_set_string(keyfile, identifier,
"Tethering.Identifier",
technology->tethering_ident);
if (technology->tethering_passphrase != NULL)
g_key_file_set_string(keyfile, identifier,
"Tethering.Passphrase",
technology->tethering_passphrase);
done:
g_free(identifier);
__connman_storage_save_global(keyfile);
g_key_file_free(keyfile);
return;
}
static void technology_load(struct connman_technology *technology)
{
GKeyFile *keyfile;
gchar *identifier;
GError *error = NULL;
connman_bool_t enable;
DBG("technology %p", technology);
keyfile = __connman_storage_load_global();
/* Fallback on disabling technology if file not found. */
if (keyfile == NULL) {
if (technology->type == CONNMAN_SERVICE_TYPE_ETHERNET)
/* We enable ethernet by default */
technology->enable_persistent = TRUE;
else
technology->enable_persistent = FALSE;
return;
}
identifier = g_strdup_printf("%s", get_name(technology->type));
if (identifier == NULL)
goto done;
enable = g_key_file_get_boolean(keyfile, identifier, "Enable", &error);
if (error == NULL)
technology->enable_persistent = enable;
else {
if (technology->type == CONNMAN_SERVICE_TYPE_ETHERNET)
technology->enable_persistent = TRUE;
else
technology->enable_persistent = FALSE;
technology_save(technology);
g_clear_error(&error);
}
technology->tethering_ident = g_key_file_get_string(keyfile,
identifier, "Tethering.Identifier", NULL);
technology->tethering_passphrase = g_key_file_get_string(keyfile,
identifier, "Tethering.Passphrase", NULL);
done:
g_free(identifier);
g_key_file_free(keyfile);
return;
}
connman_bool_t __connman_technology_get_offlinemode(void)
{
return global_offlinemode;
}
static void connman_technology_save_offlinemode()
{
GKeyFile *keyfile;
keyfile = __connman_storage_load_global();
if (keyfile == NULL)
keyfile = g_key_file_new();
g_key_file_set_boolean(keyfile, "global",
"OfflineMode", global_offlinemode);
__connman_storage_save_global(keyfile);
g_key_file_free(keyfile);
return;
}
static connman_bool_t connman_technology_load_offlinemode()
{
GKeyFile *keyfile;
GError *error = NULL;
connman_bool_t offlinemode;
/* If there is a error, we enable offlinemode */
keyfile = __connman_storage_load_global();
if (keyfile == NULL)
return FALSE;
offlinemode = g_key_file_get_boolean(keyfile, "global",
"OfflineMode", &error);
if (error != NULL) {
offlinemode = FALSE;
g_clear_error(&error);
}
g_key_file_free(keyfile);
return offlinemode;
}
static void append_properties(DBusMessageIter *iter,
struct connman_technology *technology)
{
DBusMessageIter dict;
const char *str;
connman_dbus_dict_open(iter, &dict);
str = get_name(technology->type);
if (str != NULL)
connman_dbus_dict_append_basic(&dict, "Name",
DBUS_TYPE_STRING, &str);
str = __connman_service_type2string(technology->type);
if (str != NULL)
connman_dbus_dict_append_basic(&dict, "Type",
DBUS_TYPE_STRING, &str);
__sync_synchronize();
connman_dbus_dict_append_basic(&dict, "Powered",
DBUS_TYPE_BOOLEAN,
&technology->enabled);
connman_dbus_dict_append_basic(&dict, "Connected",
DBUS_TYPE_BOOLEAN,
&technology->connected);
connman_dbus_dict_append_basic(&dict, "Tethering",
DBUS_TYPE_BOOLEAN,
&technology->tethering);
if (technology->tethering_ident != NULL)
connman_dbus_dict_append_basic(&dict, "TetheringIdentifier",
DBUS_TYPE_STRING,
&technology->tethering_ident);
if (technology->tethering_passphrase != NULL)
connman_dbus_dict_append_basic(&dict, "TetheringPassphrase",
DBUS_TYPE_STRING,
&technology->tethering_passphrase);
connman_dbus_dict_close(iter, &dict);
}
static void technology_added_signal(struct connman_technology *technology)
{
DBusMessage *signal;
DBusMessageIter iter;
signal = dbus_message_new_signal(CONNMAN_MANAGER_PATH,
CONNMAN_MANAGER_INTERFACE, "TechnologyAdded");
if (signal == NULL)
return;
dbus_message_iter_init_append(signal, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
&technology->path);
append_properties(&iter, technology);
dbus_connection_send(connection, signal, NULL);
dbus_message_unref(signal);
}
static void technology_removed_signal(struct connman_technology *technology)
{
g_dbus_emit_signal(connection, CONNMAN_MANAGER_PATH,
CONNMAN_MANAGER_INTERFACE, "TechnologyRemoved",
DBUS_TYPE_OBJECT_PATH, &technology->path,
DBUS_TYPE_INVALID);
}
static DBusMessage *get_properties(DBusConnection *conn,
DBusMessage *message, void *user_data)
{
struct connman_technology *technology = user_data;
DBusMessage *reply;
DBusMessageIter iter;
reply = dbus_message_new_method_return(message);
if (reply == NULL)
return NULL;
dbus_message_iter_init_append(reply, &iter);
append_properties(&iter, technology);
return reply;
}
void __connman_technology_list_struct(DBusMessageIter *array)
{
GSList *list;
DBusMessageIter entry;
for (list = technology_list; list; list = list->next) {
struct connman_technology *technology = list->data;
if (technology->path == NULL ||
(technology->rfkill_driven == TRUE &&
technology->hardblocked == TRUE))
continue;
dbus_message_iter_open_container(array, DBUS_TYPE_STRUCT,
NULL, &entry);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH,
&technology->path);
append_properties(&entry, technology);
dbus_message_iter_close_container(array, &entry);
}
}
static gboolean technology_pending_reply(gpointer user_data)
{
struct connman_technology *technology = user_data;
DBusMessage *reply;
/* Power request timedout, send ETIMEDOUT. */
if (technology->pending_reply != NULL) {
reply = __connman_error_failed(technology->pending_reply, ETIMEDOUT);
if (reply != NULL)
g_dbus_send_message(connection, reply);
dbus_message_unref(technology->pending_reply);
technology->pending_reply = NULL;
technology->pending_timeout = 0;
}
return FALSE;
}
static int technology_affect_devices(struct connman_technology *technology,
connman_bool_t enable_device)
{
GSList *list;
int err = 0;
for (list = technology->device_list; list; list = list->next) {
struct connman_device *device = list->data;
if (enable_device == TRUE)
err = __connman_device_enable(device);
else
err = __connman_device_disable(device);
}
return err;
}
static int technology_enable(struct connman_technology *technology)
{
int err = 0;
int err_dev;
DBG("technology %p enable", technology);
__sync_synchronize();
if (technology->enabled == TRUE)
return -EALREADY;
if (technology->pending_reply != NULL)
return -EBUSY;
if (technology->rfkill_driven == TRUE)
err = __connman_rfkill_block(technology->type, FALSE);
err_dev = technology_affect_devices(technology, TRUE);
if (technology->rfkill_driven == FALSE)
err = err_dev;
return err;
}
static int technology_disable(struct connman_technology *technology)
{
int err;
DBG("technology %p disable", technology);
__sync_synchronize();
if (technology->enabled == FALSE)
return -EALREADY;
if (technology->pending_reply != NULL)
return -EBUSY;
if (technology->tethering == TRUE)
set_tethering(technology, FALSE);
err = technology_affect_devices(technology, FALSE);
if (technology->rfkill_driven == TRUE)
err = __connman_rfkill_block(technology->type, TRUE);
return err;
}
static DBusMessage *set_powered(struct connman_technology *technology,
DBusMessage *msg, connman_bool_t powered)
{
DBusMessage *reply = NULL;
int err = 0;
if (technology->rfkill_driven && technology->hardblocked == TRUE) {
err = -EACCES;
goto make_reply;
}
if (powered == TRUE)
err = technology_enable(technology);
else
err = technology_disable(technology);
if (err != -EBUSY) {
technology->enable_persistent = powered;
technology_save(technology);
}
make_reply:
if (err == -EINPROGRESS) {
technology->pending_reply = dbus_message_ref(msg);
technology->pending_timeout = g_timeout_add_seconds(10,
technology_pending_reply, technology);
} else if (err == -EALREADY) {
if (powered == TRUE)
reply = __connman_error_already_enabled(msg);
else
reply = __connman_error_already_disabled(msg);
} else if (err < 0)
reply = __connman_error_failed(msg, -err);
else
reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
return reply;
}
static DBusMessage *set_property(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct connman_technology *technology = data;
DBusMessageIter iter, value;
const char *name;
int type;
DBG("conn %p", conn);
if (dbus_message_iter_init(msg, &iter) == FALSE)
return __connman_error_invalid_arguments(msg);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&iter, &name);
dbus_message_iter_next(&iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_recurse(&iter, &value);
type = dbus_message_iter_get_arg_type(&value);
DBG("property %s", name);
if (g_str_equal(name, "Tethering") == TRUE) {
int err;
connman_bool_t tethering;
if (type != DBUS_TYPE_BOOLEAN)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&value, &tethering);
if (technology->tethering == tethering) {
if (tethering == FALSE)
return __connman_error_already_disabled(msg);
else
return __connman_error_already_enabled(msg);
}
err = set_tethering(technology, tethering);
if (err < 0)
return __connman_error_failed(msg, -err);
} else if (g_str_equal(name, "TetheringIdentifier") == TRUE) {
const char *str;
dbus_message_iter_get_basic(&value, &str);
if (technology->type != CONNMAN_SERVICE_TYPE_WIFI)
return __connman_error_not_supported(msg);
if (strlen(str) < 1 || strlen(str) > 32)
return __connman_error_invalid_arguments(msg);
if (g_strcmp0(technology->tethering_ident, str) != 0) {
g_free(technology->tethering_ident);
technology->tethering_ident = g_strdup(str);
technology_save(technology);
connman_dbus_property_changed_basic(technology->path,
CONNMAN_TECHNOLOGY_INTERFACE,
"TetheringIdentifier",
DBUS_TYPE_STRING,
&technology->tethering_ident);
}
} else if (g_str_equal(name, "TetheringPassphrase") == TRUE) {
const char *str;
dbus_message_iter_get_basic(&value, &str);
if (technology->type != CONNMAN_SERVICE_TYPE_WIFI)
return __connman_error_not_supported(msg);
if (strlen(str) < 8 || strlen(str) > 63)
return __connman_error_passphrase_required(msg);
if (g_strcmp0(technology->tethering_passphrase, str) != 0) {
g_free(technology->tethering_passphrase);
technology->tethering_passphrase = g_strdup(str);
technology_save(technology);
connman_dbus_property_changed_basic(technology->path,
CONNMAN_TECHNOLOGY_INTERFACE,
"TetheringPassphrase",
DBUS_TYPE_STRING,
&technology->tethering_passphrase);
}
} else if (g_str_equal(name, "Powered") == TRUE) {
connman_bool_t enable;
if (type != DBUS_TYPE_BOOLEAN)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&value, &enable);
return set_powered(technology, msg, enable);
} else
return __connman_error_invalid_property(msg);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static struct connman_technology *technology_find(enum connman_service_type type)
{
GSList *list;
DBG("type %d", type);
for (list = technology_list; list; list = list->next) {
struct connman_technology *technology = list->data;
if (technology->type == type)
return technology;
}
return NULL;
}
static void reply_scan_pending(struct connman_technology *technology, int err)
{
DBusMessage *reply;
DBG("technology %p err %d", technology, err);
while (technology->scan_pending != NULL) {
DBusMessage *msg = technology->scan_pending->data;
DBG("reply to %s", dbus_message_get_sender(msg));
if (err == 0)
reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
else
reply = __connman_error_failed(msg, -err);
g_dbus_send_message(connection, reply);
dbus_message_unref(msg);
technology->scan_pending =
g_slist_delete_link(technology->scan_pending,
technology->scan_pending);
}
}
void __connman_technology_scan_started(struct connman_device *device)
{
DBG("device %p", device);
}
void __connman_technology_scan_stopped(struct connman_device *device)
{
int count = 0;
struct connman_technology *technology;
enum connman_service_type type;
GSList *list;
type = __connman_device_get_service_type(device);
technology = technology_find(type);
DBG("technology %p device %p", technology, device);
if (technology == NULL)
return;
for (list = technology->device_list; list != NULL; list = list->next) {
struct connman_device *other_device = list->data;
if (device == other_device)
continue;
if (__connman_device_get_service_type(other_device) != type)
continue;
if (connman_device_get_scanning(other_device) == TRUE)
count += 1;
}
if (count == 0)
reply_scan_pending(technology, 0);
}
void __connman_technology_notify_regdom_by_device(struct connman_device *device,
int result, const char *alpha2)
{
connman_bool_t regdom_set = FALSE;
struct connman_technology *technology;
enum connman_service_type type;
GSList *tech_drivers;
type = __connman_device_get_service_type(device);
technology = technology_find(type);
if (technology == NULL)
return;
if (result < 0) {
for (tech_drivers = technology->driver_list;
tech_drivers != NULL;
tech_drivers = g_slist_next(tech_drivers)) {
struct connman_technology_driver *driver =
tech_drivers->data;
if (driver->set_regdom != NULL) {
driver->set_regdom(technology, alpha2);
regdom_set = TRUE;
}
}
if (regdom_set == FALSE)
alpha2 = NULL;
}
connman_technology_regdom_notify(technology, alpha2);
}
static DBusMessage *scan(DBusConnection *conn, DBusMessage *msg, void *data)
{
struct connman_technology *technology = data;
int err;
DBG ("technology %p request from %s", technology,
dbus_message_get_sender(msg));
dbus_message_ref(msg);
technology->scan_pending =
g_slist_prepend(technology->scan_pending, msg);
err = __connman_device_request_scan(technology->type);
if (err < 0)
reply_scan_pending(technology, err);
return NULL;
}
static const GDBusMethodTable technology_methods[] = {
{ GDBUS_DEPRECATED_METHOD("GetProperties",
NULL, GDBUS_ARGS({ "properties", "a{sv}" }),
get_properties) },
{ GDBUS_ASYNC_METHOD("SetProperty",
GDBUS_ARGS({ "name", "s" }, { "value", "v" }),
NULL, set_property) },
{ GDBUS_ASYNC_METHOD("Scan", NULL, NULL, scan) },
{ },
};
static const GDBusSignalTable technology_signals[] = {
{ GDBUS_SIGNAL("PropertyChanged",
GDBUS_ARGS({ "name", "s" }, { "value", "v" })) },
{ },
};
static gboolean technology_dbus_register(struct connman_technology *technology)
{
if (technology->dbus_registered == TRUE ||
(technology->rfkill_driven == TRUE &&
technology->hardblocked == TRUE))
return TRUE;
if (g_dbus_register_interface(connection, technology->path,
CONNMAN_TECHNOLOGY_INTERFACE,
technology_methods, technology_signals,
NULL, technology, NULL) == FALSE) {
connman_error("Failed to register %s", technology->path);
return FALSE;
}
technology_added_signal(technology);
technology->dbus_registered = TRUE;
return TRUE;
}
static struct connman_technology *technology_get(enum connman_service_type type)
{
GSList *tech_drivers = NULL;
struct connman_technology_driver *driver;
struct connman_technology *technology;
const char *str;
GSList *list;
DBG("type %d", type);
str = __connman_service_type2string(type);
if (str == NULL)
return NULL;
technology = technology_find(type);
if (technology != NULL) {
__sync_fetch_and_add(&technology->refcount, 1);
return technology;
}
/* First check if we have a driver for this technology type */
for (list = driver_list; list; list = list->next) {
driver = list->data;
if (driver->type == type) {
DBG("technology %p driver %p", technology, driver);
tech_drivers = g_slist_append(tech_drivers, driver);
}
}
if (tech_drivers == NULL) {
DBG("No matching drivers found for %s.",
__connman_service_type2string(type));
return NULL;
}
technology = g_try_new0(struct connman_technology, 1);
if (technology == NULL)
return NULL;
technology->refcount = 1;
technology->rfkill_driven = FALSE;
technology->softblocked = FALSE;
technology->hardblocked = FALSE;
technology->type = type;
technology->path = g_strdup_printf("%s/technology/%s",
CONNMAN_PATH, str);
technology->device_list = NULL;
technology->pending_reply = NULL;
technology_load(technology);
if (technology_dbus_register(technology) == FALSE) {
g_free(technology);
return NULL;
}
technology_list = g_slist_prepend(technology_list, technology);
technology->driver_list = tech_drivers;
for (list = tech_drivers; list != NULL; list = g_slist_next(list)) {
driver = list->data;
if (driver->probe != NULL && driver->probe(technology) < 0)
DBG("Driver probe failed for technology %p",
technology);
}
DBG("technology %p", technology);
return technology;
}
static void technology_dbus_unregister(struct connman_technology *technology)
{
if (technology->dbus_registered == FALSE)
return;
technology_removed_signal(technology);
g_dbus_unregister_interface(connection, technology->path,
CONNMAN_TECHNOLOGY_INTERFACE);
technology->dbus_registered = FALSE;
}
static void technology_put(struct connman_technology *technology)
{
DBG("technology %p", technology);
if (__sync_sub_and_fetch(&technology->refcount, 1) > 0)
return;
reply_scan_pending(technology, -EINTR);
while (technology->driver_list != NULL) {
struct connman_technology_driver *driver;
driver = technology->driver_list->data;
if (driver->remove != NULL)
driver->remove(technology);
technology->driver_list =
g_slist_delete_link(technology->driver_list,
technology->driver_list);
}
technology_list = g_slist_remove(technology_list, technology);
technology_dbus_unregister(technology);
g_slist_free(technology->device_list);
g_free(technology->path);
g_free(technology->regdom);
g_free(technology->tethering_ident);
g_free(technology->tethering_passphrase);
g_free(technology);
}
void __connman_technology_add_interface(enum connman_service_type type,
int index, const char *name, const char *ident)
{
struct connman_technology *technology;
GSList *tech_drivers;
struct connman_technology_driver *driver;
switch (type) {
case CONNMAN_SERVICE_TYPE_UNKNOWN:
case CONNMAN_SERVICE_TYPE_SYSTEM:
return;
case CONNMAN_SERVICE_TYPE_ETHERNET:
case CONNMAN_SERVICE_TYPE_WIFI:
case CONNMAN_SERVICE_TYPE_LOWPAN:
case CONNMAN_SERVICE_TYPE_BLUETOOTH:
case CONNMAN_SERVICE_TYPE_CELLULAR:
case CONNMAN_SERVICE_TYPE_GPS:
case CONNMAN_SERVICE_TYPE_VPN:
case CONNMAN_SERVICE_TYPE_GADGET:
break;
}
connman_info("Adding interface %s [ %s ]", name,
__connman_service_type2string(type));
technology = technology_find(type);
if (technology == NULL)
return;
for (tech_drivers = technology->driver_list; tech_drivers != NULL;
tech_drivers = g_slist_next(tech_drivers)) {
driver = tech_drivers->data;
if(driver->add_interface != NULL)
driver->add_interface(technology, index, name, ident);
}
}
void __connman_technology_remove_interface(enum connman_service_type type,
int index, const char *name, const char *ident)
{
struct connman_technology *technology;
GSList *tech_drivers;
struct connman_technology_driver *driver;
switch (type) {
case CONNMAN_SERVICE_TYPE_UNKNOWN:
case CONNMAN_SERVICE_TYPE_SYSTEM:
return;
case CONNMAN_SERVICE_TYPE_ETHERNET:
case CONNMAN_SERVICE_TYPE_WIFI:
case CONNMAN_SERVICE_TYPE_LOWPAN:
case CONNMAN_SERVICE_TYPE_BLUETOOTH:
case CONNMAN_SERVICE_TYPE_CELLULAR:
case CONNMAN_SERVICE_TYPE_GPS:
case CONNMAN_SERVICE_TYPE_VPN:
case CONNMAN_SERVICE_TYPE_GADGET:
break;
}
connman_info("Remove interface %s [ %s ]", name,
__connman_service_type2string(type));
technology = technology_find(type);
if (technology == NULL)
return;
for (tech_drivers = technology->driver_list; tech_drivers != NULL;
tech_drivers = g_slist_next(tech_drivers)) {
driver = tech_drivers->data;
if(driver->remove_interface != NULL)
driver->remove_interface(technology, index);
}
}
int __connman_technology_add_device(struct connman_device *device)
{
struct connman_technology *technology;
enum connman_service_type type;
DBG("device %p", device);
type = __connman_device_get_service_type(device);
technology = technology_get(type);
if (technology == NULL) {
/*
* Since no driver can be found for this device at the moment we
* add it to the techless device list.
*/
techless_device_list = g_slist_prepend(techless_device_list,
device);
return -ENXIO;
}
__sync_synchronize();
if (technology->rfkill_driven == TRUE) {
if (technology->enabled == TRUE)
__connman_device_enable(device);
else
__connman_device_disable(device);
goto done;
}
if (technology->enable_persistent == TRUE &&
global_offlinemode == FALSE) {
int err = __connman_device_enable(device);
/*
* connman_technology_add_device() calls __connman_device_enable()
* but since the device is already enabled, the calls does not
* propagate through to connman_technology_enabled via
* connman_device_set_powered.
*/
if (err == -EALREADY)
__connman_technology_enabled(type);
}
/* if technology persistent state is offline */
if (technology->enable_persistent == FALSE)
__connman_device_disable(device);
done:
technology->device_list = g_slist_prepend(technology->device_list,
device);
return 0;
}
int __connman_technology_remove_device(struct connman_device *device)
{
struct connman_technology *technology;
enum connman_service_type type;
DBG("device %p", device);
type = __connman_device_get_service_type(device);
technology = technology_find(type);
if (technology == NULL) {
techless_device_list = g_slist_remove(techless_device_list,
device);
return -ENXIO;
}
technology->device_list = g_slist_remove(technology->device_list,
device);
technology_put(technology);
return 0;
}
static void powered_changed(struct connman_technology *technology)
{
if (technology->dbus_registered == FALSE)
return;
if (technology->pending_reply != NULL) {
g_dbus_send_reply(connection,
technology->pending_reply, DBUS_TYPE_INVALID);
dbus_message_unref(technology->pending_reply);
technology->pending_reply = NULL;
g_source_remove(technology->pending_timeout);
technology->pending_timeout = 0;
}
__sync_synchronize();
connman_dbus_property_changed_basic(technology->path,
CONNMAN_TECHNOLOGY_INTERFACE, "Powered",
DBUS_TYPE_BOOLEAN, &technology->enabled);
}
static int technology_enabled(struct connman_technology *technology)
{
__sync_synchronize();
if (technology->enabled == TRUE)
return -EALREADY;
technology->enabled = TRUE;
powered_changed(technology);
return 0;
}
int __connman_technology_enabled(enum connman_service_type type)
{
struct connman_technology *technology;
technology = technology_find(type);
if (technology == NULL)
return -ENXIO;
if (technology->rfkill_driven == TRUE)
return 0;
return technology_enabled(technology);
}
static int technology_disabled(struct connman_technology *technology)
{
__sync_synchronize();
if (technology->enabled == FALSE)
return -EALREADY;
technology->enabled = FALSE;
powered_changed(technology);
return 0;
}
int __connman_technology_disabled(enum connman_service_type type)
{
struct connman_technology *technology;
GSList *list;
technology = technology_find(type);
if (technology == NULL)
return -ENXIO;
if (technology->rfkill_driven == TRUE)
return 0;
for (list = technology->device_list; list != NULL; list = list->next) {
struct connman_device *device = list->data;
if (connman_device_get_powered(device) == TRUE)
return 0;
}
return technology_disabled(technology);
}
int __connman_technology_set_offlinemode(connman_bool_t offlinemode)
{
GSList *list;
int err = -EINVAL;
if (global_offlinemode == offlinemode)
return 0;
DBG("offlinemode %s", offlinemode ? "On" : "Off");
/*
* This is a bit tricky. When you set offlinemode, there is no
* way to differentiate between attempting offline mode and
* resuming offlinemode from last saved profile. We need that
* information in rfkill_update, otherwise it falls back on the
* technology's persistent state. Hence we set the offline mode here
* but save it & call the notifier only if its successful.
*/
global_offlinemode = offlinemode;
/* Traverse technology list, enable/disable each technology. */
for (list = technology_list; list; list = list->next) {
struct connman_technology *technology = list->data;
if (offlinemode)
err = technology_disable(technology);
if (!offlinemode && technology->enable_persistent)
err = technology_enable(technology);
}
if (err == 0 || err == -EINPROGRESS || err == -EALREADY) {
connman_technology_save_offlinemode();
__connman_notifier_offlinemode(offlinemode);
} else
global_offlinemode = connman_technology_load_offlinemode();
return err;
}
void __connman_technology_set_connected(enum connman_service_type type,
connman_bool_t connected)
{
struct connman_technology *technology;
technology = technology_find(type);
if (technology == NULL)
return;
DBG("technology %p connected %d", technology, connected);
technology->connected = connected;
connman_dbus_property_changed_basic(technology->path,
CONNMAN_TECHNOLOGY_INTERFACE, "Connected",
DBUS_TYPE_BOOLEAN, &connected);
}
static connman_bool_t technology_apply_rfkill_change(struct connman_technology *technology,
connman_bool_t softblock,
connman_bool_t hardblock,
connman_bool_t new_rfkill)
{
gboolean hardblock_changed = FALSE;
gboolean apply = TRUE;
GList *start, *list;
DBG("technology %p --> %d/%d vs %d/%d",
technology, softblock, hardblock,
technology->softblocked, technology->hardblocked);
if (technology->hardblocked == hardblock)
goto softblock_change;
if (!(new_rfkill == TRUE && hardblock == FALSE)) {
start = g_hash_table_get_values(rfkill_list);
for (list = start; list != NULL; list = list->next) {
struct connman_rfkill *rfkill = list->data;
if (rfkill->type != technology->type)
continue;
if (rfkill->hardblock != hardblock)
apply = FALSE;
}
g_list_free(start);
}
if (apply == FALSE)
goto softblock_change;
technology->hardblocked = hardblock;
hardblock_changed = TRUE;
softblock_change:
if (apply == FALSE && technology->softblocked != softblock)
apply = TRUE;
if (apply == FALSE)
return technology->hardblocked;
technology->softblocked = softblock;
if (technology->hardblocked == TRUE ||
technology->softblocked == TRUE) {
if (technology_disabled(technology) != -EALREADY)
technology_affect_devices(technology, FALSE);
} else if (technology->hardblocked == FALSE &&
technology->softblocked == FALSE) {
if (technology_enabled(technology) != -EALREADY)
technology_affect_devices(technology, TRUE);
}
if (hardblock_changed == TRUE) {
if (technology->hardblocked == TRUE) {
DBG("%s is switched off.", get_name(technology->type));
technology_dbus_unregister(technology);
} else
technology_dbus_register(technology);
}
return technology->hardblocked;
}
int __connman_technology_add_rfkill(unsigned int index,
enum connman_service_type type,
connman_bool_t softblock,
connman_bool_t hardblock)
{
struct connman_technology *technology;
struct connman_rfkill *rfkill;
DBG("index %u type %d soft %u hard %u", index, type,
softblock, hardblock);
rfkill = g_hash_table_lookup(rfkill_list, GINT_TO_POINTER(index));
if (rfkill != NULL)
goto done;
rfkill = g_try_new0(struct connman_rfkill, 1);
if (rfkill == NULL)
return -ENOMEM;
rfkill->index = index;
rfkill->type = type;
rfkill->softblock = softblock;
rfkill->hardblock = hardblock;
g_hash_table_insert(rfkill_list, GINT_TO_POINTER(index), rfkill);
done:
technology = technology_get(type);
/* If there is no driver for this type, ignore it. */
if (technology == NULL)
return -ENXIO;
technology->rfkill_driven = TRUE;
/* If hardblocked, there is no need to handle softblocked state */
if (technology_apply_rfkill_change(technology,
softblock, hardblock, TRUE) == TRUE)
return 0;
/*
* Depending on softblocked state we unblock/block according to
* offlinemode and persistente state.
*/
if (technology->softblocked == TRUE &&
global_offlinemode == FALSE &&
technology->enable_persistent == TRUE)
return __connman_rfkill_block(type, FALSE);
else if (technology->softblocked == FALSE &&
(global_offlinemode == TRUE ||
technology->enable_persistent == FALSE))
return __connman_rfkill_block(type, TRUE);
return 0;
}
int __connman_technology_update_rfkill(unsigned int index,
enum connman_service_type type,
connman_bool_t softblock,
connman_bool_t hardblock)
{
struct connman_technology *technology;
struct connman_rfkill *rfkill;
DBG("index %u soft %u hard %u", index, softblock, hardblock);
rfkill = g_hash_table_lookup(rfkill_list, GINT_TO_POINTER(index));
if (rfkill == NULL)
return -ENXIO;
if (rfkill->softblock == softblock &&
rfkill->hardblock == hardblock)
return 0;
rfkill->softblock = softblock;
rfkill->hardblock = hardblock;
technology = technology_find(type);
/* If there is no driver for this type, ignore it. */
if (technology == NULL)
return -ENXIO;
/* If hardblocked, there is no need to handle softblocked state */
if (technology_apply_rfkill_change(technology,
softblock, hardblock, FALSE) == TRUE)
return 0;
if (global_offlinemode == TRUE)
return 0;
/*
* Depending on softblocked state we unblock/block according to
* persistent state.
*/
if (technology->softblocked == TRUE &&
technology->enable_persistent == TRUE)
return __connman_rfkill_block(type, FALSE);
else if (technology->softblocked == FALSE &&
technology->enable_persistent == FALSE)
return __connman_rfkill_block(type, TRUE);
return 0;
}
int __connman_technology_remove_rfkill(unsigned int index,
enum connman_service_type type)
{
struct connman_technology *technology;
struct connman_rfkill *rfkill;
DBG("index %u", index);
rfkill = g_hash_table_lookup(rfkill_list, GINT_TO_POINTER(index));
if (rfkill == NULL)
return -ENXIO;
g_hash_table_remove(rfkill_list, GINT_TO_POINTER(index));
technology = technology_find(type);
if (technology == NULL)
return -ENXIO;
technology_apply_rfkill_change(technology,
technology->softblocked, !technology->hardblocked, FALSE);
technology_put(technology);
return 0;
}
int __connman_technology_init(void)
{
DBG("");
connection = connman_dbus_get_connection();
rfkill_list = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, free_rfkill);
global_offlinemode = connman_technology_load_offlinemode();
/* This will create settings file if it is missing */
connman_technology_save_offlinemode();
return 0;
}
void __connman_technology_cleanup(void)
{
DBG("");
g_hash_table_destroy(rfkill_list);
dbus_connection_unref(connection);
}