blob: 56f177701110e3d767ef3d4c1d59f7e5e2c3cb18 [file] [log] [blame] [edit]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2013 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 bool global_offlinemode;
struct connman_rfkill {
unsigned int index;
enum connman_service_type type;
bool softblock;
bool hardblock;
};
struct connman_technology {
int refcount;
enum connman_service_type type;
char *path;
GSList *device_list;
bool enabled;
char *regdom;
bool connected;
bool tethering;
bool tethering_persistent; /* Tells the save status, needed
* as offline mode might set
* tethering OFF.
*/
char *tethering_ident;
char *tethering_passphrase;
bool enable_persistent; /* Save the tech state */
GSList *driver_list;
DBusMessage *pending_reply;
guint pending_timeout;
GSList *scan_pending;
bool rfkill_driven;
bool softblocked;
bool hardblocked;
bool dbus_registered;
};
static GSList *driver_list = NULL;
static int technology_enabled(struct connman_technology *technology);
static int technology_disabled(struct connman_technology *technology);
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);
}
bool
connman_technology_is_tethering_allowed(enum connman_service_type type)
{
static char *allowed_default[] = { "wifi", "bluetooth", "gadget",
NULL };
const char *type_str = __connman_service_type2string(type);
char **allowed;
int i;
if (!type_str)
return false;
allowed = connman_setting_get_string_list("TetheringTechnologies");
if (!allowed)
allowed = allowed_default;
for (i = 0; allowed[i]; i++) {
if (g_strcmp0(allowed[i], type_str) == 0)
return true;
}
return false;
}
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:
break;
case CONNMAN_SERVICE_TYPE_GADGET:
return "Gadget";
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_P2P:
return "P2P";
case CONNMAN_SERVICE_TYPE_LOWPAN:
return "Thread";
}
return NULL;
}
static void technology_save(struct connman_technology *technology)
{
GKeyFile *keyfile;
gchar *identifier;
const char *name = get_name(technology->type);
DBG("technology %p type %d name %s", technology, technology->type,
name);
if (!name)
return;
keyfile = __connman_storage_load_global();
if (!keyfile)
keyfile = g_key_file_new();
identifier = g_strdup_printf("%s", name);
if (!identifier)
goto done;
g_key_file_set_boolean(keyfile, identifier, "Enable",
technology->enable_persistent);
g_key_file_set_boolean(keyfile, identifier, "Tethering",
technology->tethering_persistent);
if (technology->tethering_ident)
g_key_file_set_string(keyfile, identifier,
"Tethering.Identifier",
technology->tethering_ident);
if (technology->tethering_passphrase)
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 tethering_changed(struct connman_technology *technology)
{
dbus_bool_t tethering = technology->tethering;
connman_dbus_property_changed_basic(technology->path,
CONNMAN_TECHNOLOGY_INTERFACE, "Tethering",
DBUS_TYPE_BOOLEAN, &tethering);
technology_save(technology);
}
void connman_technology_tethering_notify(struct connman_technology *technology,
bool enabled)
{
DBG("technology %p enabled %u", technology, enabled);
if (technology->tethering == enabled)
return;
technology->tethering = enabled;
tethering_changed(technology);
if (enabled)
__connman_tethering_set_enabled();
else
__connman_tethering_set_disabled();
}
static int set_tethering(struct connman_technology *technology,
bool 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)
return -EACCES;
bridge = __connman_tethering_get_bridge();
if (!bridge)
return -EOPNOTSUPP;
if (technology->type == CONNMAN_SERVICE_TYPE_WIFI &&
(!ident || !passphrase))
return -EINVAL;
for (tech_drivers = technology->driver_list; tech_drivers;
tech_drivers = g_slist_next(tech_drivers)) {
struct connman_technology_driver *driver = tech_drivers->data;
if (!driver || !driver->set_tethering)
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)
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;
tech_drivers = g_slist_next(tech_drivers)) {
struct connman_technology_driver *driver =
tech_drivers->data;
if (driver->set_regdom)
driver->set_regdom(technology, alpha2);
}
}
}
return 0;
}
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;
}
bool connman_technology_get_wifi_tethering(const char **ssid,
const char **psk)
{
struct connman_technology *technology;
if (!ssid || !psk)
return false;
*ssid = *psk = NULL;
technology = technology_find(CONNMAN_SERVICE_TYPE_WIFI);
if (!technology)
return false;
if (!technology->tethering)
return false;
*ssid = technology->tethering_ident;
*psk = technology->tethering_passphrase;
return true;
}
static void free_rfkill(gpointer data)
{
struct connman_rfkill *rfkill = data;
g_free(rfkill);
}
static void technology_load(struct connman_technology *technology)
{
GKeyFile *keyfile;
gchar *identifier;
GError *error = NULL;
bool enable, need_saving = false;
DBG("technology %p", technology);
keyfile = __connman_storage_load_global();
/* Fallback on disabling technology if file not found. */
if (!keyfile) {
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)
goto done;
enable = g_key_file_get_boolean(keyfile, identifier, "Enable", &error);
if (!error)
technology->enable_persistent = enable;
else {
if (technology->type == CONNMAN_SERVICE_TYPE_ETHERNET)
technology->enable_persistent = true;
else
technology->enable_persistent = false;
need_saving = true;
g_clear_error(&error);
}
enable = g_key_file_get_boolean(keyfile, identifier,
"Tethering", &error);
if (!error)
technology->tethering_persistent = enable;
else {
need_saving = true;
g_clear_error(&error);
}
if (need_saving)
technology_save(technology);
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;
}
bool __connman_technology_get_offlinemode(void)
{
return global_offlinemode;
}
static void connman_technology_save_offlinemode(void)
{
GKeyFile *keyfile;
keyfile = __connman_storage_load_global();
if (!keyfile)
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 bool connman_technology_load_offlinemode(void)
{
GKeyFile *keyfile;
GError *error = NULL;
bool offlinemode;
/* If there is a error, we enable offlinemode */
keyfile = __connman_storage_load_global();
if (!keyfile)
return false;
offlinemode = g_key_file_get_boolean(keyfile, "global",
"OfflineMode", &error);
if (error) {
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;
dbus_bool_t val;
const char *str;
connman_dbus_dict_open(iter, &dict);
str = get_name(technology->type);
if (str)
connman_dbus_dict_append_basic(&dict, "Name",
DBUS_TYPE_STRING, &str);
str = __connman_service_type2string(technology->type);
if (str)
connman_dbus_dict_append_basic(&dict, "Type",
DBUS_TYPE_STRING, &str);
__sync_synchronize();
val = technology->enabled;
connman_dbus_dict_append_basic(&dict, "Powered",
DBUS_TYPE_BOOLEAN,
&val);
val = technology->connected;
connman_dbus_dict_append_basic(&dict, "Connected",
DBUS_TYPE_BOOLEAN,
&val);
val = technology->tethering;
connman_dbus_dict_append_basic(&dict, "Tethering",
DBUS_TYPE_BOOLEAN,
&val);
if (technology->tethering_ident)
connman_dbus_dict_append_basic(&dict, "TetheringIdentifier",
DBUS_TYPE_STRING,
&technology->tethering_ident);
if (technology->tethering_passphrase)
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)
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)
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 ||
(technology->rfkill_driven &&
technology->hardblocked))
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) {
reply = __connman_error_failed(technology->pending_reply, ETIMEDOUT);
if (reply)
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,
bool enable_device)
{
int err = 0, err_dev;
GSList *list;
if (technology->type == CONNMAN_SERVICE_TYPE_P2P) {
if (enable_device)
__connman_technology_enabled(technology->type);
else
__connman_technology_disabled(technology->type);
return 0;
}
for (list = technology->device_list; list; list = list->next) {
struct connman_device *device = list->data;
if (enable_device)
err_dev = __connman_device_enable(device);
else
err_dev = __connman_device_disable(device);
if (err_dev < 0 && err_dev != -EALREADY)
err = err_dev;
}
return err;
}
static void powered_changed(struct connman_technology *technology)
{
dbus_bool_t enabled;
if (!technology->dbus_registered)
return;
if (technology->pending_reply) {
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();
enabled = technology->enabled;
connman_dbus_property_changed_basic(technology->path,
CONNMAN_TECHNOLOGY_INTERFACE, "Powered",
DBUS_TYPE_BOOLEAN, &enabled);
}
static void enable_tethering(struct connman_technology *technology)
{
int ret;
if (!connman_setting_get_bool("PersistentTetheringMode"))
return;
ret = set_tethering(technology, true);
if (ret < 0 && ret != -EALREADY)
DBG("Cannot enable tethering yet for %s (%d/%s)",
get_name(technology->type),
-ret, strerror(-ret));
}
static int technology_enabled(struct connman_technology *technology)
{
__sync_synchronize();
if (technology->enabled)
return -EALREADY;
technology->enabled = true;
if (technology->type == CONNMAN_SERVICE_TYPE_WIFI) {
struct connman_technology *p2p;
p2p = technology_find(CONNMAN_SERVICE_TYPE_P2P);
if (p2p && !p2p->enabled && p2p->enable_persistent)
technology_enabled(p2p);
}
if (technology->tethering_persistent)
enable_tethering(technology);
powered_changed(technology);
return 0;
}
static int technology_enable(struct connman_technology *technology)
{
int err = 0;
int err_dev;
DBG("technology %p enable", technology);
__sync_synchronize();
if (technology->type == CONNMAN_SERVICE_TYPE_P2P) {
struct connman_technology *wifi;
wifi = technology_find(CONNMAN_SERVICE_TYPE_WIFI);
if (wifi && wifi->enabled)
return technology_enabled(technology);
return 0;
}
if (technology->enabled)
return -EALREADY;
if (technology->pending_reply)
return -EBUSY;
if (connman_setting_get_bool("PersistentTetheringMode") &&
technology->tethering)
set_tethering(technology, true);
if (technology->rfkill_driven)
err = __connman_rfkill_block(technology->type, false);
err_dev = technology_affect_devices(technology, true);
if (!technology->rfkill_driven)
err = err_dev;
return err;
}
static int technology_disabled(struct connman_technology *technology)
{
__sync_synchronize();
if (!technology->enabled)
return -EALREADY;
technology->enabled = false;
powered_changed(technology);
return 0;
}
static int technology_disable(struct connman_technology *technology)
{
int err;
DBG("technology %p disable", technology);
__sync_synchronize();
if (technology->type == CONNMAN_SERVICE_TYPE_P2P) {
technology->enable_persistent = false;
return technology_disabled(technology);
} else if (technology->type == CONNMAN_SERVICE_TYPE_WIFI) {
struct connman_technology *p2p;
p2p = technology_find(CONNMAN_SERVICE_TYPE_P2P);
if (p2p && p2p->enabled) {
p2p->enable_persistent = true;
technology_disabled(p2p);
}
}
if (!technology->enabled)
return -EALREADY;
if (technology->pending_reply)
return -EBUSY;
if (technology->tethering)
set_tethering(technology, false);
err = technology_affect_devices(technology, false);
if (technology->rfkill_driven)
err = __connman_rfkill_block(technology->type, true);
return err;
}
static DBusMessage *set_powered(struct connman_technology *technology,
DBusMessage *msg, bool powered)
{
DBusMessage *reply = NULL;
int err = 0;
if (technology->rfkill_driven && technology->hardblocked) {
err = -EACCES;
goto make_reply;
}
if (powered)
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)
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))
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")) {
dbus_bool_t tethering;
int err;
if (type != DBUS_TYPE_BOOLEAN)
return __connman_error_invalid_arguments(msg);
if (!connman_technology_is_tethering_allowed(technology->type)) {
DBG("%s tethering not allowed by config file",
__connman_service_type2string(technology->type));
return __connman_error_not_supported(msg);
}
dbus_message_iter_get_basic(&value, &tethering);
if (technology->tethering == tethering) {
if (!tethering)
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);
technology->tethering_persistent = tethering;
technology_save(technology);
} else if (g_str_equal(name, "TetheringIdentifier")) {
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")) {
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")) {
dbus_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 void reply_scan_pending(struct connman_technology *technology, int err)
{
DBusMessage *reply;
DBG("technology %p err %d", technology, err);
while (technology->scan_pending) {
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,
enum connman_service_type type)
{
int count = 0;
struct connman_technology *technology;
GSList *list;
technology = technology_find(type);
DBG("technology %p device %p", technology, device);
if (!technology)
return;
for (list = technology->device_list; list; 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))
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)
{
bool 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)
return;
if (result < 0) {
for (tech_drivers = technology->driver_list;
tech_drivers;
tech_drivers = g_slist_next(tech_drivers)) {
struct connman_technology_driver *driver =
tech_drivers->data;
if (driver->set_regdom) {
driver->set_regdom(technology, alpha2);
regdom_set = true;
}
}
if (!regdom_set)
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 bool technology_dbus_register(struct connman_technology *technology)
{
if (technology->dbus_registered ||
(technology->rfkill_driven &&
technology->hardblocked))
return true;
if (!g_dbus_register_interface(connection, technology->path,
CONNMAN_TECHNOLOGY_INTERFACE,
technology_methods, technology_signals,
NULL, technology, NULL)) {
connman_error("Failed to register %s", technology->path);
return false;
}
technology_added_signal(technology);
technology->dbus_registered = true;
return true;
}
static void technology_dbus_unregister(struct connman_technology *technology)
{
if (!technology->dbus_registered)
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) {
struct connman_technology_driver *driver;
driver = technology->driver_list->data;
if (driver->remove)
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);
/*
* Once set_powered is called, technology is not powered
* immediately. The device driver enable function returns
* -EINPROGRESS and a timer of 10 sec is started. If the
* technology is powered within 10 sec, this timer is cancelled.
* In case technology is not powered, from timeout handler
* -ETIMEDOUT is return to the application. But if technology
* is disabled within that 10 sec, technology pointer is freed
* but timer is left running. On expiry of timer, it would try
* to access the technology pointer which is already freed and
* would cause crash.
*/
if (technology->pending_reply) {
dbus_message_unref(technology->pending_reply);
technology->pending_reply = NULL;
g_source_remove(technology->pending_timeout);
technology->pending_timeout = 0;
}
g_free(technology->path);
g_free(technology->regdom);
g_free(technology->tethering_ident);
g_free(technology->tethering_passphrase);
g_free(technology);
}
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)
return NULL;
technology = technology_find(type);
if (technology) {
if (type != CONNMAN_SERVICE_TYPE_P2P)
__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) {
DBG("No matching drivers found for %s.",
__connman_service_type2string(type));
return NULL;
}
technology = g_try_new0(struct connman_technology, 1);
if (!technology)
return NULL;
technology->refcount = 1;
technology->type = type;
technology->path = g_strdup_printf("%s/technology/%s",
CONNMAN_PATH, str);
technology_load(technology);
technology_list = g_slist_prepend(technology_list, technology);
technology->driver_list = tech_drivers;
for (list = tech_drivers; list; list = list->next) {
driver = list->data;
if (driver->probe && driver->probe(technology) < 0)
DBG("Driver probe failed for technology %p",
technology);
}
if (!technology_dbus_register(technology)) {
technology_put(technology);
return NULL;
}
if (type == CONNMAN_SERVICE_TYPE_P2P) {
struct connman_technology *wifi;
bool enable;
enable = technology->enable_persistent;
wifi = technology_find(CONNMAN_SERVICE_TYPE_WIFI);
if (enable && wifi)
enable = wifi->enabled;
technology_affect_devices(technology, enable);
}
DBG("technology %p %s", technology, get_name(technology->type));
return technology;
}
int connman_technology_driver_register(struct connman_technology_driver *driver)
{
GSList *list;
struct connman_device *device;
enum connman_service_type type;
for (list = driver_list; list; list = list->next) {
if (list->data == driver)
goto exist;
}
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; 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));
exist:
if (driver->type == CONNMAN_SERVICE_TYPE_P2P) {
if (!technology_get(CONNMAN_SERVICE_TYPE_P2P))
return -ENOMEM;
}
return 0;
}
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;
tech_drivers = g_slist_next(tech_drivers)) {
current = tech_drivers->data;
if (driver != current)
continue;
if (driver->remove)
driver->remove(technology);
technology->driver_list =
g_slist_remove(technology->driver_list,
driver);
break;
}
}
driver_list = g_slist_remove(driver_list, driver);
if (driver->type == CONNMAN_SERVICE_TYPE_P2P) {
technology = technology_find(CONNMAN_SERVICE_TYPE_P2P);
if (technology)
technology_put(technology);
}
}
void __connman_technology_add_interface(enum connman_service_type type,
int index, const char *ident)
{
struct connman_technology *technology;
GSList *tech_drivers;
struct connman_technology_driver *driver;
char *name;
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:
case CONNMAN_SERVICE_TYPE_P2P:
break;
}
name = connman_inet_ifname(index);
connman_info("Adding interface %s [ %s ]", name,
__connman_service_type2string(type));
technology = technology_find(type);
if (!technology)
goto out;
for (tech_drivers = technology->driver_list; tech_drivers;
tech_drivers = g_slist_next(tech_drivers)) {
driver = tech_drivers->data;
if (driver->add_interface)
driver->add_interface(technology, index, name, ident);
}
/*
* At this point we can try to enable tethering automatically as
* now the interfaces are set properly.
*/
if (technology->tethering_persistent)
enable_tethering(technology);
out:
g_free(name);
}
void __connman_technology_remove_interface(enum connman_service_type type,
int index, const char *ident)
{
struct connman_technology *technology;
GSList *tech_drivers;
struct connman_technology_driver *driver;
char *name;
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:
case CONNMAN_SERVICE_TYPE_P2P:
break;
}
name = connman_inet_ifname(index);
connman_info("Remove interface %s [ %s ]", name,
__connman_service_type2string(type));
g_free(name);
technology = technology_find(type);
if (!technology)
return;
for (tech_drivers = technology->driver_list; tech_drivers;
tech_drivers = g_slist_next(tech_drivers)) {
driver = tech_drivers->data;
if (driver->remove_interface)
driver->remove_interface(technology, index);
}
}
int __connman_technology_add_device(struct connman_device *device)
{
struct connman_technology *technology;
enum connman_service_type type;
type = __connman_device_get_service_type(device);
DBG("device %p type %s", device, get_name(type));
technology = technology_get(type);
if (!technology) {
/*
* 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();
DBG("rf kill %d enable %d persistent %d global %d", technology->rfkill_driven, technology->enabled, technology->enable_persistent, global_offlinemode);
if (technology->rfkill_driven) {
if (technology->enabled)
__connman_device_enable(device);
else
__connman_device_disable(device);
goto done;
}
if (technology->enable_persistent &&
!global_offlinemode) {
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)
__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) {
techless_device_list = g_slist_remove(techless_device_list,
device);
return -ENXIO;
}
technology->device_list = g_slist_remove(technology->device_list,
device);
if (technology->tethering)
set_tethering(technology, false);
technology_put(technology);
return 0;
}
int __connman_technology_enabled(enum connman_service_type type)
{
struct connman_technology *technology;
technology = technology_find(type);
if (!technology)
return -ENXIO;
DBG("technology %p type %s rfkill %d enabled %d", technology,
get_name(type), technology->rfkill_driven,
technology->enabled);
if (technology->rfkill_driven) {
if (technology->tethering_persistent)
enable_tethering(technology);
return 0;
}
return technology_enabled(technology);
}
int __connman_technology_disabled(enum connman_service_type type)
{
struct connman_technology *technology;
GSList *list;
technology = technology_find(type);
if (!technology)
return -ENXIO;
if (technology->rfkill_driven)
return 0;
for (list = technology->device_list; list; list = list->next) {
struct connman_device *device = list->data;
if (connman_device_get_powered(device))
return 0;
}
return technology_disabled(technology);
}
int __connman_technology_set_offlinemode(bool offlinemode)
{
GSList *list;
int err = -EINVAL, enabled_tech_count = 0;
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);
else {
if (technology->hardblocked)
continue;
if (technology->enable_persistent) {
err = technology_enable(technology);
enabled_tech_count++;
}
}
}
if (err == 0 || err == -EINPROGRESS || err == -EALREADY ||
(err == -EINVAL && enabled_tech_count == 0)) {
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,
bool connected)
{
struct connman_technology *technology;
dbus_bool_t val;
technology = technology_find(type);
if (!technology)
return;
DBG("technology %p connected %d", technology, connected);
technology->connected = connected;
val = connected;
connman_dbus_property_changed_basic(technology->path,
CONNMAN_TECHNOLOGY_INTERFACE, "Connected",
DBUS_TYPE_BOOLEAN, &val);
}
static bool technology_apply_rfkill_change(struct connman_technology *technology,
bool softblock,
bool hardblock,
bool new_rfkill)
{
bool hardblock_changed = false;
bool 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 && !hardblock)) {
start = g_hash_table_get_values(rfkill_list);
for (list = start; list; 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)
goto softblock_change;
technology->hardblocked = hardblock;
hardblock_changed = true;
softblock_change:
if (!apply && technology->softblocked != softblock)
apply = true;
if (!apply)
return technology->hardblocked;
technology->softblocked = softblock;
if (technology->hardblocked ||
technology->softblocked) {
if (technology_disabled(technology) != -EALREADY)
technology_affect_devices(technology, false);
} else if (!technology->hardblocked &&
!technology->softblocked) {
if (technology_enabled(technology) != -EALREADY)
technology_affect_devices(technology, true);
}
if (hardblock_changed) {
if (technology->hardblocked) {
DBG("%s is switched off.", get_name(technology->type));
technology_dbus_unregister(technology);
} else {
DBG("%s is switched on.", get_name(technology->type));
technology_dbus_register(technology);
if (global_offlinemode)
__connman_rfkill_block(technology->type, true);
}
}
return technology->hardblocked;
}
int __connman_technology_add_rfkill(unsigned int index,
enum connman_service_type type,
bool softblock,
bool 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)
goto done;
rfkill = g_try_new0(struct connman_rfkill, 1);
if (!rfkill)
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)
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))
return 0;
if (global_offlinemode)
return 0;
/*
* Depending on softblocked state we unblock/block according to
* offlinemode and persistente state.
*/
if (technology->softblocked &&
technology->enable_persistent)
return __connman_rfkill_block(type, false);
else if (!technology->softblocked &&
!technology->enable_persistent)
return __connman_rfkill_block(type, true);
return 0;
}
int __connman_technology_update_rfkill(unsigned int index,
enum connman_service_type type,
bool softblock,
bool 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)
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)
return -ENXIO;
technology_apply_rfkill_change(technology, softblock, hardblock,
false);
if (technology->hardblocked)
DBG("%s hardblocked", get_name(technology->type));
else
DBG("%s is%s softblocked", get_name(technology->type),
technology->softblocked ? "" : " not");
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)
return -ENXIO;
g_hash_table_remove(rfkill_list, GINT_TO_POINTER(index));
technology = technology_find(type);
if (!technology)
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("");
while (technology_list) {
struct connman_technology *technology = technology_list->data;
technology_list = g_slist_remove(technology_list, technology);
technology_put(technology);
}
g_hash_table_destroy(rfkill_list);
dbus_connection_unref(connection);
}