/** * @file radiostates.c * Radio state module for the Mode Control Entity *

* Copyright © 2010-2011 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013-2019 Jolla Ltd. *

* @author David Weinehall * @author Santtu Lakkala * @author Simo Piiroinen * * mce is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * mce 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mce. If not, see . */ #include "radiostates.h" #include "../mce.h" #include "../mce-log.h" #include "../mce-io.h" #include "../mce-conf.h" #include "../mce-dbus.h" #include #include #include #include #include #include /* ========================================================================= * * Prototypes * ========================================================================= */ /* ------------------------------------------------------------------------- * * RADIO_STATES * ------------------------------------------------------------------------- */ static const char *radio_states_change_repr(guint prev, guint curr); static const char *radio_states_repr (guint state); /* ------------------------------------------------------------------------- * * MRS * ------------------------------------------------------------------------- */ static guint mrs_get_default_radio_states (void); static void mrs_restore_radio_states (void); static void mrs_save_radio_states (void); static void mrs_modify_radio_states (const guint states, const guint mask); static void mrs_sync_radio_state (void); static gboolean mrs_radio_state_sync_cb (gpointer aptr); static void mrs_cancel_radio_state_sync (void); static void mrs_schedule_radio_state_sync (void); static void mrs_datapipe_update_master_radio_enabled(void); static void mrs_datapipe_master_radio_enabled_cb (gconstpointer data); static void mrs_datapipe_init (void); static void mrs_datapipe_quit (void); /* ------------------------------------------------------------------------- * * MRS_DBUS * ------------------------------------------------------------------------- */ static gboolean mrs_dbus_send_radio_states (DBusMessage *const method_call); static gboolean mrs_dbus_get_radio_states_cb(DBusMessage *const msg); static gboolean mrs_dbus_set_radio_states_cb(DBusMessage *const msg); static void mrs_dbus_init (void); static void mrs_dbus_quit (void); /* ------------------------------------------------------------------------- * * XCONNMAN * ------------------------------------------------------------------------- */ static void xconnman_set_property_cb (DBusPendingCall *pc, void *user_data); static gboolean xconnman_set_property_bool (const char *key, gboolean val); static void xconnman_sync_offline_to_master (void); static void xconnman_sync_master_to_offline (void); static void xconnman_property_changed (const char *key, int type, const dbus_any_t *val); static void xconnman_handle_property_changed_signal(DBusMessage *msg); static void xconnman_get_properties_cb (DBusPendingCall *pc, void *user_data); static gboolean xconnman_get_properties (void); static void xconnman_set_runstate (gboolean running); static void xconnman_check_service_cb (DBusPendingCall *pc, void *user_data); static gboolean xconnman_check_service (void); static void xconnman_handle_name_owner_change (DBusMessage *msg); static DBusHandlerResult xconnman_dbus_filter_cb (DBusConnection *con, DBusMessage *msg, void *user_data); static void xconnman_quit (void); static gboolean xconnman_init (void); /* ------------------------------------------------------------------------- * * G_MODULE * ------------------------------------------------------------------------- */ const gchar *g_module_check_init(GModule *module); void g_module_unload (GModule *module); /* ========================================================================= * * Data * ========================================================================= */ /** Module name */ #define MODULE_NAME "radiostates" /** Number of radio states */ #define RADIO_STATES_COUNT 6 /** Names of radio state configuration keys */ static const gchar *const radio_state_names[RADIO_STATES_COUNT] = { MCE_CONF_MASTER_RADIO_STATE, MCE_CONF_CELLULAR_RADIO_STATE, MCE_CONF_WLAN_RADIO_STATE, MCE_CONF_BLUETOOTH_RADIO_STATE, MCE_CONF_NFC_RADIO_STATE, MCE_CONF_FMTX_RADIO_STATE }; /** Radio state default values */ static const gboolean radio_state_defaults[RADIO_STATES_COUNT] = { DEFAULT_MASTER_RADIO_STATE, DEFAULT_CELLULAR_RADIO_STATE, DEFAULT_WLAN_RADIO_STATE, DEFAULT_BLUETOOTH_RADIO_STATE, DEFAULT_NFC_RADIO_STATE, DEFAULT_FMTX_RADIO_STATE }; /** Short names - keep in sync with mcetool */ static const char * const radio_state_repr[RADIO_STATES_COUNT] = { "master", "cellular", "wlan", "bluetooth", "nfc", "fmtx", }; /** radio state flag */ static const guint radio_state_flags[RADIO_STATES_COUNT] = { MCE_RADIO_STATE_MASTER, MCE_RADIO_STATE_CELLULAR, MCE_RADIO_STATE_WLAN, MCE_RADIO_STATE_BLUETOOTH, MCE_RADIO_STATE_NFC, MCE_RADIO_STATE_FMTX }; /** Functionality provided by this module */ static const gchar *const provides[] = { MODULE_NAME, NULL }; /** Module information */ G_MODULE_EXPORT module_info_struct module_info = { /** Name of the module */ .name = MODULE_NAME, /** Module provides */ .provides = provides, /** Module priority */ .priority = 250 }; /** Copy of radio states from master disable time */ static guint saved_radio_states = 0; /** Active radio states (master switch disables all radios) */ static guint active_radio_states = 0; /* ========================================================================= * * RADIO_STATES * ========================================================================= */ /** Translate radio state bitmap change into human readable form * * @param prev previously active radio states * @param curr currently active radio states * * @return human readable bitmap change description */ static const char * radio_states_change_repr(guint prev, guint curr) { static char repr[128]; char *pos = repr; char *end = repr + sizeof repr - 1; auto void add(const char *str) { if( !str ) str = "(null)"; while( *str && pos < end ) *pos++ = *str++; } guint diff = prev ^ curr; for( int i = 0; i < RADIO_STATES_COUNT; ++i ) { guint mask = radio_state_flags[i]; if( (diff | curr) & mask ) { if( diff & mask ) add((curr & mask) ? "+" : "-"); add(radio_state_repr[i]); add(" "); } } if( pos > repr ) --pos; else add("(none)"); *pos = 0; return repr; } /** Translate radio state bitmap into human readable form * * @param state active radio states * * @return human readable bitmap description */ static const char * radio_states_repr(guint state) { return radio_states_change_repr(state, state); } /* ========================================================================= * * MRS * ========================================================================= */ /** Get default radio states from customisable settings * * @return radio states */ static guint mrs_get_default_radio_states(void) { guint default_radio_states = 0; for( size_t i = 0; i < RADIO_STATES_COUNT; ++i ) { gboolean flag = mce_conf_get_bool(MCE_CONF_RADIO_STATES_GROUP, radio_state_names[i], radio_state_defaults[i]); if( flag ) { default_radio_states |= radio_state_flags[i]; } } mce_log(LL_DEBUG, "default_radio_states = %s", radio_states_repr(default_radio_states)); return default_radio_states; } /** Restore the radio states from persistant storage */ static void mrs_restore_radio_states(void) { static const char online_file[] = MCE_ONLINE_RADIO_STATES_PATH; static const char offline_file[] = MCE_OFFLINE_RADIO_STATES_PATH; /* Apply configured defaults */ active_radio_states = saved_radio_states = mrs_get_default_radio_states(); /* FIXME: old maemo backup/restore handling - can be removed? */ if( mce_are_settings_locked() ) { if( mce_unlock_settings() ) mce_log(LL_INFO, "Removed stale settings lockfile"); else mce_log(LL_ERR, "Failed to remove settings lockfile; %m"); } /* The files get generated by mce on 1st bootup. Skip the * read attempt and associated diagnostic logging if the * files do not exist */ if( access(online_file, F_OK) == -1 && errno == ENOENT ) goto EXIT; gulong online_states = 0; gulong offline_states = 0; if( mce_read_number_string_from_file(online_file, &online_states, NULL, TRUE, TRUE) ) active_radio_states = (guint)online_states; if( mce_read_number_string_from_file(offline_file, &offline_states, NULL, TRUE, TRUE) ) saved_radio_states = (guint)offline_states; EXIT: mce_log(LL_DEBUG, "active_radio_states: %s", radio_states_repr(active_radio_states)); mce_log(LL_DEBUG, "saved_radio_states: %s", radio_states_repr(saved_radio_states)); return; } /** Save the radio states to persistant storage */ static void mrs_save_radio_states(void) { const guint online_states = active_radio_states; const guint offline_states = saved_radio_states; /* FIXME: old maemo backup/restore handling - can be removed? */ if (mce_are_settings_locked() == TRUE) { mce_log(LL_WARN, "Cannot save radio states; backup/restore " "or device clear/factory reset pending"); goto EXIT; } mce_write_number_string_to_file_atomic(MCE_ONLINE_RADIO_STATES_PATH, online_states); mce_write_number_string_to_file_atomic(MCE_OFFLINE_RADIO_STATES_PATH, offline_states); EXIT: return; } /** Set the radio states * * @param states The raw radio states * @param mask The raw radio states mask */ static void mrs_modify_radio_states(const guint states, const guint mask) { mce_log(LL_DEBUG, "states: %s", radio_states_change_repr(states ^ mask, states)); guint prev = active_radio_states; /* Deal with master bit changes first */ if( (mask & MCE_RADIO_STATE_MASTER) && ((active_radio_states ^ states) & MCE_RADIO_STATE_MASTER) ) { if( active_radio_states & MCE_RADIO_STATE_MASTER ) { /* Master disable: save & clear state */ saved_radio_states = active_radio_states; active_radio_states = 0; } else { /* Master enable: resture saved state */ active_radio_states = saved_radio_states; } } /* Then update active features bits */ active_radio_states = (active_radio_states & ~mask) | (states & mask); /* Immediate actions on state change */ if( prev != active_radio_states ) { mce_log(LL_DEBUG, "active_radio_states: %s", radio_states_change_repr(prev, active_radio_states)); /* Update persistent values */ mrs_save_radio_states(); /* Broadcast changes */ mrs_dbus_send_radio_states(NULL); } /* Do datapipe & connman sync from idle callback */ mrs_schedule_radio_state_sync(); } /** Immediately sync active radio state to datapipes and connman */ static void mrs_sync_radio_state(void) { /* Remove pending timer */ mrs_cancel_radio_state_sync(); /* Sync to master_radio_enabled_pipe */ mrs_datapipe_update_master_radio_enabled(); /* After datapipe execution the radio state should * be stable - sync connman offline property to it */ xconnman_sync_master_to_offline(); } /** Timer id for: delayed radio state sync */ static guint mrs_radio_state_sync_id = 0; /** Timer callback for: delayed radio state sync */ static gboolean mrs_radio_state_sync_cb(gpointer aptr) { (void)aptr; mrs_radio_state_sync_id = 0; mrs_sync_radio_state(); return G_SOURCE_REMOVE; } /** Cancel delayed radio state sync */ static void mrs_cancel_radio_state_sync(void) { if( mrs_radio_state_sync_id ) { g_source_remove(mrs_radio_state_sync_id), mrs_radio_state_sync_id = 0; } } /** Schedule delayed radio state sync */ static void mrs_schedule_radio_state_sync(void) { if( !mrs_radio_state_sync_id ) { mrs_radio_state_sync_id = g_idle_add(mrs_radio_state_sync_cb, 0); } } /* ========================================================================= * * MRS_DATAPIPES * ========================================================================= */ /** Sync active radio state -> master_radio_enabled_pipe */ static void mrs_datapipe_update_master_radio_enabled(void) { int prev = datapipe_get_gint(master_radio_enabled_pipe); int next = (active_radio_states & MCE_RADIO_STATE_MASTER) ? 1 : 0; if( prev != next ) datapipe_exec_full(&master_radio_enabled_pipe, GINT_TO_POINTER(next)); } /** Sync master_radio_enabled_pipe -> active radio state * * @param data master radio state as a pointer */ static void mrs_datapipe_master_radio_enabled_cb(gconstpointer data) { guint prev = (active_radio_states & MCE_RADIO_STATE_MASTER); guint next = data ? MCE_RADIO_STATE_MASTER : 0; if( prev != next ) mrs_modify_radio_states(next, MCE_RADIO_STATE_MASTER); } /** Array of datapipe handlers */ static datapipe_handler_t mrs_datapipe_handlers[] = { // output triggers { .datapipe = &master_radio_enabled_pipe, .output_cb = mrs_datapipe_master_radio_enabled_cb, }, // sentinel { .datapipe = 0, } }; /** Datapipe bindings for this module */ static datapipe_bindings_t mrs_datapipe_bindings = { .module = MODULE_NAME, .handlers = mrs_datapipe_handlers, }; /** Append triggers/filters to datapipes */ static void mrs_datapipe_init(void) { mce_datapipe_init_bindings(&mrs_datapipe_bindings); } /** Remove triggers/filters from datapipes */ static void mrs_datapipe_quit(void) { mce_datapipe_quit_bindings(&mrs_datapipe_bindings); } /* ========================================================================= * * MRS_DBUS * ========================================================================= */ /** Send the radio states * * @param method_call A DBusMessage to reply to; * pass NULL to send a signal instead * * @return TRUE on success, FALSE on failure */ static gboolean mrs_dbus_send_radio_states(DBusMessage *const method_call) { static dbus_uint32_t prev = ~0u; DBusMessage *msg = NULL; gboolean status = FALSE; dbus_uint32_t data = active_radio_states; if( method_call ) { /* Send reply to a method call */ msg = dbus_new_method_reply(method_call); } else if( prev == data ) { /* Skip duplicate signals */ goto EXIT; } else { /* Broadcast change signal */ prev = data; msg = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_RADIO_STATES_SIG); } mce_log(LL_DEBUG, "Sending radio states %s: %s", method_call ? "reply" : "signal", radio_states_repr(data)); /* Append the radio states */ if (dbus_message_append_args(msg, DBUS_TYPE_UINT32, &data, DBUS_TYPE_INVALID) == FALSE) { mce_log(LL_CRIT, "Failed to append %sargument to D-Bus message " "for %s.%s", method_call ? "reply " : "", method_call ? MCE_REQUEST_IF : MCE_SIGNAL_IF, method_call ? MCE_RADIO_STATES_GET : MCE_RADIO_STATES_SIG); dbus_message_unref(msg); goto EXIT; } /* Send the message */ status = dbus_send_message(msg); EXIT: return status; } /** D-Bus callback for the get radio states method call * * @param msg The D-Bus message * * @return TRUE on success, FALSE on failure */ static gboolean mrs_dbus_get_radio_states_cb(DBusMessage *const msg) { gboolean status = FALSE; mce_log(LL_DEVEL, "Received get radio states request from %s", mce_dbus_get_message_sender_ident(msg)); /* Try to send a reply that contains the current radio states */ if (mrs_dbus_send_radio_states(msg) == FALSE) goto EXIT; status = TRUE; EXIT: return status; } /** D-Bus callback for radio states change method call * * @todo Decide on error handling policy * * @param msg The D-Bus message * * @return TRUE on success, FALSE on failure */ static gboolean mrs_dbus_set_radio_states_cb(DBusMessage *const msg) { dbus_bool_t no_reply = dbus_message_get_no_reply(msg); gboolean status = FALSE; dbus_uint32_t states = 0; dbus_uint32_t mask = 0; DBusError error = DBUS_ERROR_INIT; mce_log(LL_DEVEL, "Received radio states change request from %s", mce_dbus_get_message_sender_ident(msg)); if (dbus_message_get_args(msg, &error, DBUS_TYPE_UINT32, &states, DBUS_TYPE_UINT32, &mask, DBUS_TYPE_INVALID) == FALSE) { // XXX: should we return an error instead? mce_log(LL_CRIT, "Failed to get argument from %s.%s: %s", MCE_REQUEST_IF, MCE_RADIO_STATES_CHANGE_REQ, error.message); goto EXIT; } mrs_modify_radio_states(states, mask); if (no_reply == FALSE) { DBusMessage *reply = dbus_new_method_reply(msg); status = dbus_send_message(reply); } else { status = TRUE; } EXIT: dbus_error_free(&error); return status; } /** Array of dbus message handlers */ static mce_dbus_handler_t radiostates_dbus_handlers[] = { /* signals - outbound (for Introspect purposes only) */ { .interface = MCE_SIGNAL_IF, .name = MCE_RADIO_STATES_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, /* method calls */ { .interface = MCE_REQUEST_IF, .name = MCE_RADIO_STATES_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mrs_dbus_get_radio_states_cb, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_RADIO_STATES_CHANGE_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mrs_dbus_set_radio_states_cb, .privileged = true, .args = " \n" " \n" }, /* sentinel */ { .interface = 0 } }; /** Add dbus handlers */ static void mrs_dbus_init(void) { mce_dbus_handler_register_array(radiostates_dbus_handlers); } /** Remove dbus handlers */ static void mrs_dbus_quit(void) { mce_dbus_handler_unregister_array(radiostates_dbus_handlers); } /* ------------------------------------------------------------------------- * * Functionality for keeping MCE Master radio state synchronized with * connman OfflineMode property. * * If OfflineMode property is changed via connman, the mce master radio * state is changed accordingly and legacy mce radio state change signals * are broadcast. * * If MCE master radio switch is toggled, the connman OfflineMode property * is changed accordingly. * * The mce master radio state is preserved over reboots and mce restarts * and will take priority over the OfflineMode setting kept by connman. * * If connman for any reason chooses not to obey OfflineMode property * change, mce will modify master radio state instead. * ------------------------------------------------------------------------- */ /** org.freedesktop.DBus.NameOwnerChanged D-Bus signal */ #define DBUS_NAME_OWNER_CHANGED_SIG "NameOwnerChanged" /** Connman D-Bus service name; mce is tracking ownership of this */ #define CONNMAN_SERVICE "net.connman" /** Connman D-Bus interface */ #define CONNMAN_INTERFACE "net.connman.Manager" /** Default connman D-Bus object path */ #define CONNMAN_OBJECT_PATH "/" /** net.connman.Manager.GetProperties D-Bus method call */ #define CONNMAN_GET_PROPERTIES_REQ "GetProperties" /** net.connman.Manager.SetProperty D-Bus method call */ #define CONNMAN_SET_PROPERTY_REQ "SetProperty" /** net.connman.Manager.PropertyChanged D-Bus signal */ #define CONNMAN_PROPERTY_CHANGED_SIG "PropertyChanged" /** Initializer for dbus_any_t; largest union member set to zero */ #define DBUS_ANY_INIT { .i64 = 0 } /** Rule for matching connman service name owner changes */ static const char xconnman_name_owner_rule[] = "type='signal'" ",sender='"DBUS_SERVICE_DBUS"'" ",interface='"DBUS_INTERFACE_DBUS"'" ",member='"DBUS_NAME_OWNER_CHANGED_SIG"'" ",path='"DBUS_PATH_DBUS"'" ",arg0='"CONNMAN_SERVICE"'" ; /** Rule for matching connman property value changes */ static const char xconnman_prop_change_rule[] = "type='signal'" ",sender='"CONNMAN_SERVICE"'" ",interface='"CONNMAN_INTERFACE"'" ",member='"CONNMAN_PROPERTY_CHANGED_SIG"'" ",path='"CONNMAN_OBJECT_PATH"'" ; /** D-Bus connection for doing ipc with connman */ static DBusConnection *connman_bus = 0; /** Availability of connman D-Bus service */ static gboolean connman_running = FALSE; /** Last MCE master radio state sent to connman; initialized to invalid value */ static guint connman_master = ~(guint)0; /** Flag: query connman properties if no change signal received * * FIXME/HACK: connman might ignore property setting without * complaining a bit -> set a flag when we are expecting a * property changed signal before getting reply to a set property * method call. Note that this will cease to work if connman ever * starts to send replies before signaling the changes */ static gboolean connman_verify_property_setting = FALSE; static gboolean xconnman_get_properties(void); /** Handle reply to asynchronous connman property change D-Bus method call * * @param pc State data for asynchronous D-Bus method call * @param user_data (not used) */ static void xconnman_set_property_cb(DBusPendingCall *pc, void *user_data) { (void)user_data; DBusMessage *rsp = 0; DBusError err = DBUS_ERROR_INIT; if( !(rsp = dbus_pending_call_steal_reply(pc)) ) goto EXIT; if( dbus_set_error_from_message(&err, rsp) ) { mce_log(LL_WARN, "%s: %s", err.name, err.message); goto EXIT; } /* NOTE: there is either empty or error reply message, we have * no clue whether connman actually modified the property */ mce_log(LL_DEBUG, "set property acked by connman"); /* Query properties if missing an expected property changed signal */ if( connman_verify_property_setting ) { connman_verify_property_setting = FALSE; mce_log(LL_DEBUG, "no change signal seen, querying props"); if( !xconnman_get_properties() ) mce_log(LL_WARN, "failed to query connman properties"); } EXIT: if( rsp ) dbus_message_unref(rsp); dbus_error_free(&err); } /** Initiate asynchronous connman property change D-Bus method call * * @param key property name * @param val value to set * * @return TRUE if the method call was initiated, or FALSE in case of errors */ static gboolean xconnman_set_property_bool(const char *key, gboolean val) { gboolean res = FALSE; DBusMessage *req = 0; DBusPendingCall *pc = 0; dbus_bool_t dta = val; DBusMessageIter miter, viter; mce_log(LL_DEBUG, "%s = %s", key, val ? "true" : "false"); if( !(req = dbus_message_new_method_call(CONNMAN_SERVICE, CONNMAN_OBJECT_PATH, CONNMAN_INTERFACE, CONNMAN_SET_PROPERTY_REQ)) ) goto EXIT; dbus_message_iter_init_append(req, &miter); dbus_message_iter_append_basic(&miter, DBUS_TYPE_STRING, &key); if( !dbus_message_iter_open_container(&miter, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &viter) ) { mce_log(LL_WARN, "container open failed"); goto EXIT; } dbus_message_iter_append_basic(&viter, DBUS_TYPE_BOOLEAN, &dta); if( !dbus_message_iter_close_container(&miter, &viter) ) { mce_log(LL_WARN, "container close failed"); goto EXIT; } if( !dbus_connection_send_with_reply(connman_bus, req, &pc, -1) ) goto EXIT; if( !pc ) goto EXIT; mce_dbus_pending_call_blocks_suspend(pc); if( !dbus_pending_call_set_notify(pc, xconnman_set_property_cb, 0, 0) ) goto EXIT; // success res = TRUE; EXIT: if( pc ) dbus_pending_call_unref(pc); if( req ) dbus_message_unref(req); return res; } /** Synchronize connman OfflineMode -> MCE master radio state */ static void xconnman_sync_offline_to_master(void) { if( (connman_master ^ active_radio_states) & MCE_RADIO_STATE_MASTER ) { mce_log(LL_DEBUG, "sync connman OfflineMode -> mce master"); mrs_modify_radio_states(connman_master, MCE_RADIO_STATE_MASTER); } } /** Synchronize MCE master radio state -> connman OfflineMode */ static void xconnman_sync_master_to_offline(void) { guint master; if( !connman_running ) return; master = active_radio_states & MCE_RADIO_STATE_MASTER; if( connman_master != master ) { connman_master = master; mce_log(LL_DEBUG, "sync mce master -> connman OfflineMode"); /* Expect property change signal ... */ connman_verify_property_setting = TRUE; /* ... before we get reply to set property */ xconnman_set_property_bool("OfflineMode", !connman_master); } } /** Process connman property value change * * @param key property name * @param type dbus type of the property (DBUS_TYPE_BOOLEAN, ...) * @param val value union; consult type for actual content */ static void xconnman_property_changed(const char *key, int type, const dbus_any_t *val) { switch( type ) { case DBUS_TYPE_STRING: mce_log(LL_DEBUG, "%s -> '%s'", key, val->s); break; case DBUS_TYPE_BOOLEAN: mce_log(LL_DEBUG, "%s -> %s", key, val->b ? "true" : "false"); break; default: mce_log(LL_DEBUG, "%s -> (unhandled)", key); break; } if( !strcmp(key, "OfflineMode") && type == DBUS_TYPE_BOOLEAN ) { /* Got it, no need for explicit query */ connman_verify_property_setting = FALSE; connman_master = val->b ? 0 : MCE_RADIO_STATE_MASTER; xconnman_sync_offline_to_master(); } } /** Handle connman property changed signals * * @param msg net.connman.Manager.PropertyChanged D-Bus signal */ static void xconnman_handle_property_changed_signal(DBusMessage *msg) { const char *key = 0; dbus_any_t val = DBUS_ANY_INIT; int vtype; DBusMessageIter miter, viter; if( !dbus_message_iter_init(msg, &miter) ) goto EXIT; if( dbus_message_iter_get_arg_type(&miter) != DBUS_TYPE_STRING ) goto EXIT; dbus_message_iter_get_basic(&miter, &key); dbus_message_iter_next(&miter); if( dbus_message_iter_get_arg_type(&miter) != DBUS_TYPE_VARIANT ) goto EXIT; dbus_message_iter_recurse(&miter, &viter); vtype = dbus_message_iter_get_arg_type(&viter); if( !dbus_type_is_basic(vtype) ) goto EXIT; dbus_message_iter_get_basic(&viter, &val); xconnman_property_changed(key, vtype, &val); EXIT: return; } /** Handle reply to asynchronous connman properties query * * @param pc State data for asynchronous D-Bus method call * @param user_data (not used) */ static void xconnman_get_properties_cb(DBusPendingCall *pc, void *user_data) { (void)user_data; DBusMessage *rsp = 0; DBusError err = DBUS_ERROR_INIT; const char *key = 0; dbus_any_t val = DBUS_ANY_INIT; int vtype; DBusMessageIter miter, aiter, diter, viter; if( !(rsp = dbus_pending_call_steal_reply(pc)) ) goto EXIT; if( dbus_set_error_from_message(&err, rsp) ) { mce_log(LL_WARN, "%s: %s", err.name, err.message); goto EXIT; } if( !dbus_message_iter_init(rsp, &miter) ) goto EXIT; if( dbus_message_iter_get_arg_type(&miter) != DBUS_TYPE_ARRAY ) goto EXIT; dbus_message_iter_recurse(&miter, &aiter); while( dbus_message_iter_get_arg_type(&aiter) == DBUS_TYPE_DICT_ENTRY ) { dbus_message_iter_recurse(&aiter, &diter); dbus_message_iter_next(&aiter); if( dbus_message_iter_get_arg_type(&diter) != DBUS_TYPE_STRING ) goto EXIT; dbus_message_iter_get_basic(&diter, &key); dbus_message_iter_next(&diter); if( dbus_message_iter_get_arg_type(&diter) != DBUS_TYPE_VARIANT ) goto EXIT; dbus_message_iter_recurse(&diter, &viter); vtype = dbus_message_iter_get_arg_type(&viter); if( !dbus_type_is_basic(vtype) ) continue; dbus_message_iter_get_basic(&viter, &val); xconnman_property_changed(key, vtype, &val); } EXIT: if( rsp ) dbus_message_unref(rsp); dbus_error_free(&err); } /** Initiate asynchronous connman properties query * * @return TRUE if the method call was initiated, or FALSE in case of errors */ static gboolean xconnman_get_properties(void) { gboolean res = FALSE; DBusMessage *req = 0; DBusPendingCall *pc = 0; if( !(req = dbus_message_new_method_call(CONNMAN_SERVICE, CONNMAN_OBJECT_PATH, CONNMAN_INTERFACE, CONNMAN_GET_PROPERTIES_REQ)) ) goto EXIT; if( !dbus_connection_send_with_reply(connman_bus, req, &pc, -1) ) goto EXIT; if( !pc ) goto EXIT; mce_dbus_pending_call_blocks_suspend(pc); if( !dbus_pending_call_set_notify(pc, xconnman_get_properties_cb, 0, 0) ) goto EXIT; // success res = TRUE; EXIT: if( pc ) dbus_pending_call_unref(pc); if( req ) dbus_message_unref(req); return res; } /** Process connman D-Bus service availability change * * @param running Reported connman availability state */ static void xconnman_set_runstate(gboolean running) { if( connman_running == running ) return; connman_running = running; mce_log(LL_NOTICE, "%s: %s", CONNMAN_SERVICE, connman_running ? "available" : "stopped"); if( connman_running ) { xconnman_sync_master_to_offline(); } else { /* force master -> offlinemode sync on connman restart */ connman_master = ~(guint)0; } } /** Handle reply to asynchronous connman service name ownership query * * @param pc State data for asynchronous D-Bus method call * @param user_data (not used) */ static void xconnman_check_service_cb(DBusPendingCall *pc, void *user_data) { (void)user_data; DBusMessage *rsp = 0; const char *owner = 0; DBusError err = DBUS_ERROR_INIT; if( !(rsp = dbus_pending_call_steal_reply(pc)) ) goto EXIT; if( dbus_set_error_from_message(&err, rsp) || !dbus_message_get_args(rsp, &err, DBUS_TYPE_STRING, &owner, DBUS_TYPE_INVALID) ) { if( strcmp(err.name, DBUS_ERROR_NAME_HAS_NO_OWNER) ) { mce_log(LL_WARN, "%s: %s", err.name, err.message); } goto EXIT; } xconnman_set_runstate(owner && *owner); EXIT: if( rsp ) dbus_message_unref(rsp); dbus_error_free(&err); } /** Initiate asynchronous connman service name ownership query * * @return TRUE if the method call was initiated, or FALSE in case of errors */ static gboolean xconnman_check_service(void) { gboolean res = FALSE; DBusMessage *req = 0; DBusPendingCall *pc = 0; const char *name = CONNMAN_SERVICE; if( !(req = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner")) ) goto EXIT; if( !dbus_message_append_args(req, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID) ) goto EXIT; if( !dbus_connection_send_with_reply(connman_bus, req, &pc, -1) ) goto EXIT; if( !pc ) goto EXIT; mce_dbus_pending_call_blocks_suspend(pc); if( !dbus_pending_call_set_notify(pc, xconnman_check_service_cb, 0, 0) ) goto EXIT; // success res = TRUE; EXIT: if( pc ) dbus_pending_call_unref(pc); if( req ) dbus_message_unref(req); return res; } /** Handle connman dbus service name ownership change signals * * @param msg org.freedesktop.DBus.NameOwnerChanged D-Bus signal */ static void xconnman_handle_name_owner_change(DBusMessage *msg) { const char *name = 0; const char *prev = 0; const char *curr = 0; DBusError err = DBUS_ERROR_INIT; if( !dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &prev, DBUS_TYPE_STRING, &curr, DBUS_TYPE_INVALID) ) { mce_log(LL_WARN, "%s: %s", err.name, err.message); goto EXIT; } if( strcmp(name, CONNMAN_SERVICE) ) goto EXIT; xconnman_set_runstate(curr && *curr); EXIT: dbus_error_free(&err); return; } /** D-Bus message filter for handling connman related signals * * @param con dbus connection * @param msg message to be acted upon * @param user_data (not used) * * @return DBUS_HANDLER_RESULT_NOT_YET_HANDLED (other filters see the msg too) */ static DBusHandlerResult xconnman_dbus_filter_cb(DBusConnection *con, DBusMessage *msg, void *user_data) { (void)user_data; DBusHandlerResult res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if( con != connman_bus ) goto EXIT; if( dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL ) goto EXIT; if( dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, DBUS_NAME_OWNER_CHANGED_SIG) ) { xconnman_handle_name_owner_change(msg); } else if( dbus_message_is_signal(msg, CONNMAN_INTERFACE, CONNMAN_PROPERTY_CHANGED_SIG) ) { xconnman_handle_property_changed_signal(msg); } EXIT: return res; } /** Stop connman OfflineMode property mirroring */ static void xconnman_quit(void) { if( connman_bus ) { dbus_connection_remove_filter(connman_bus, xconnman_dbus_filter_cb, 0); dbus_bus_remove_match(connman_bus, xconnman_prop_change_rule, 0); dbus_bus_remove_match(connman_bus, xconnman_name_owner_rule, 0); dbus_connection_unref(connman_bus), connman_bus = 0; } } /** Start mirroring connman OfflineMode property */ static gboolean xconnman_init(void) { gboolean ack = FALSE; if( !(connman_bus = dbus_connection_get()) ) { mce_log(LL_WARN, "mce has no dbus connection"); goto EXIT; } dbus_connection_add_filter(connman_bus, xconnman_dbus_filter_cb, 0, 0); dbus_bus_add_match(connman_bus, xconnman_prop_change_rule, 0); dbus_bus_add_match(connman_bus, xconnman_name_owner_rule, 0); ack = TRUE; if( !xconnman_check_service() ) ack = FALSE; EXIT: return ack; } /* ========================================================================= * * G_MODULE * ========================================================================= */ /** Init function for the radio states module * * @param module (Unused) * * @return NULL on success, a string with an error message on failure */ G_MODULE_EXPORT const gchar * g_module_check_init(GModule *module) { (void)module; /* Read persistent values */ mrs_restore_radio_states(); /* Append triggers/filters to datapipes */ mrs_datapipe_init(); /* Add dbus handlers */ mrs_dbus_init(); if( !xconnman_init() ) mce_log(LL_WARN, "failed to set up connman mirroring"); /* Process and broadcast initial state */ mrs_datapipe_update_master_radio_enabled(); mrs_dbus_send_radio_states(NULL); return NULL; } /** Exit function for the radio states module * * @param module (Unused) */ G_MODULE_EXPORT void g_module_unload(GModule *module) { (void)module; /* Remove dbus handlers */ mrs_dbus_quit(); xconnman_quit(); /* Remove triggers/filters from datapipes */ mrs_datapipe_quit(); mrs_cancel_radio_state_sync(); return; }