blob: 90973e45ec10ffa56a85aa028c89e6a4d683761e [file] [log] [blame]
// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "update_engine/policy_manager/real_shill_provider.h"
#include <string>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include "update_engine/policy_manager/generic_variables.h"
#include "update_engine/utils.h"
using std::string;
namespace {
// Looks up a key in a hash table and returns the string inside of the returned
// GValue.
const char* GetStrProperty(GHashTable* hash_table, const char* key) {
auto gval = reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table, key));
return (gval ? g_value_get_string(gval) : NULL);
}
}; // namespace
namespace chromeos_policy_manager {
// ShillConnector methods.
const ShillConnector::ConnStrToType ShillConnector::shill_conn_str_to_type[] = {
{shill::kTypeEthernet, ConnectionType::kEthernet},
{shill::kTypeWifi, ConnectionType::kWifi},
{shill::kTypeWimax, ConnectionType::kWimax},
{shill::kTypeBluetooth, ConnectionType::kBluetooth},
{shill::kTypeCellular, ConnectionType::kCellular},
};
ShillConnector::~ShillConnector() {
if (!is_init_)
return;
// Detach signal handler, free manager proxy.
dbus_->ProxyDisconnectSignal(manager_proxy_, shill::kMonitorPropertyChanged,
G_CALLBACK(signal_handler_), signal_data_);
dbus_->ProxyUnref(manager_proxy_);
}
bool ShillConnector::Init() {
if (is_init_)
return true;
// Obtain a DBus connection.
GError* error = NULL;
connection_ = dbus_->BusGet(DBUS_BUS_SYSTEM, &error);
if (!connection_) {
LOG(ERROR) << "Failed to initialize DBus connection: "
<< chromeos_update_engine::utils::GetAndFreeGError(&error);
return false;
}
// Allocate a shill manager proxy.
manager_proxy_ = GetProxy(shill::kFlimflamServicePath,
shill::kFlimflamManagerInterface);
// Subscribe to the manager's PropertyChanged signal.
dbus_->ProxyAddSignal_2(manager_proxy_, shill::kMonitorPropertyChanged,
G_TYPE_STRING, G_TYPE_VALUE);
dbus_->ProxyConnectSignal(manager_proxy_, shill::kMonitorPropertyChanged,
G_CALLBACK(signal_handler_), signal_data_, NULL);
return is_init_ = true;
}
bool ShillConnector::GetConnectionType(const string& service_path,
ConnectionType* conn_type_p) {
// Obtain a proxy for the service path.
DBusGProxy* service_proxy = GetProxy(service_path.c_str(),
shill::kFlimflamServiceInterface);
GHashTable* hash_table = NULL;
bool success = false;
bool is_vpn = false;
if (GetProperties(service_proxy, &hash_table)) {
const char* type_str = GetStrProperty(hash_table, shill::kTypeProperty);
if (type_str && !strcmp(type_str, shill::kTypeVPN)) {
is_vpn = true;
type_str = GetStrProperty(hash_table, shill::kPhysicalTechnologyProperty);
}
if (type_str) {
success = true;
*conn_type_p = ParseConnType(type_str);
}
g_hash_table_unref(hash_table);
}
if (!success) {
LOG(ERROR) << "Could not find type of "
<< (is_vpn ? "physical connection underlying VPN " : "")
<< "connection (" << service_path << ")";
}
dbus_->ProxyUnref(service_proxy);
return success;
}
DBusGProxy* ShillConnector::GetProxy(const char* path, const char* interface) {
return dbus_->ProxyNewForName(connection_, shill::kFlimflamServiceName,
path, interface);
}
ConnectionType ShillConnector::ParseConnType(const char* str) {
for (unsigned i = 0; i < arraysize(shill_conn_str_to_type); i++)
if (!strcmp(str, shill_conn_str_to_type[i].str))
return shill_conn_str_to_type[i].type;
return ConnectionType::kUnknown;
}
bool ShillConnector::GetProperties(DBusGProxy* proxy, GHashTable** result_p) {
GError* error = NULL;
if (!dbus_->ProxyCall_0_1(proxy, shill::kGetPropertiesFunction, &error,
result_p)) {
LOG(ERROR) << "Calling shill via DBus proxy failed: "
<< chromeos_update_engine::utils::GetAndFreeGError(&error);
return false;
}
return true;
}
// A variable returning the curent connection type.
class ConnTypeVariable : public Variable<ConnectionType> {
public:
ConnTypeVariable(const string& name, ShillConnector* connector,
RealShillProvider* provider)
: Variable<ConnectionType>(name, kVariableModePoll),
connector_(connector), provider_(provider) {}
protected:
// TODO(garnold) Shift to a non-blocking version, respect the timeout.
virtual const ConnectionType* GetValue(base::TimeDelta /* timeout */,
string* errmsg) {
if (!(provider_->is_connected_)) {
if (errmsg)
*errmsg = "No connection detected";
return NULL;
}
ConnectionType conn_type;
if (provider_->is_conn_type_valid_) {
conn_type = provider_->conn_type_;
} else {
if (!connector_->GetConnectionType(provider_->default_service_path_,
&conn_type)) {
if (errmsg)
*errmsg = base::StringPrintf(
"Could not retrieve type of default connection (%s)",
provider_->default_service_path_.c_str());
return NULL;
}
provider_->is_conn_type_valid_ = true;
}
return new ConnectionType(conn_type);
}
private:
// The DBus connector.
ShillConnector* connector_;
// The shill provider object (we need to read/update some internal members).
RealShillProvider* provider_;
DISALLOW_COPY_AND_ASSIGN(ConnTypeVariable);
};
// RealShillProvider methods.
bool RealShillProvider::DoInit() {
// Initialize a DBus connection and obtain the shill manager proxy.
connector_.reset(new ShillConnector(dbus_, HandlePropertyChangedStatic,
this));
if (!connector_->Init())
return false;
// Read initial connection status.
GHashTable* hash_table = NULL;
if (!connector_->GetManagerProperties(&hash_table))
return false;
GValue* value = reinterpret_cast<GValue*>(
g_hash_table_lookup(hash_table, shill::kDefaultServiceProperty));
bool success = ProcessDefaultService(value);
g_hash_table_unref(hash_table);
if (!success)
return false;
// Initialize variables.
set_var_is_connected(
new CopyVariable<bool>("is_connected", kVariableModePoll, is_connected_));
set_var_conn_type(
new ConnTypeVariable("conn_type", connector_.get(), this));
set_var_conn_last_changed(
new CopyVariable<base::Time>("conn_last_changed", kVariableModePoll,
conn_last_changed_));
return true;
}
bool RealShillProvider::ProcessDefaultService(GValue* value) {
// Decode the string from the boxed value.
const char* default_service_path_str = NULL;
if (!(value && (default_service_path_str = g_value_get_string(value))))
return false;
// Update the connection status.
is_connected_ = strcmp(default_service_path_str, "/");
if (default_service_path_ != default_service_path_str)
conn_last_changed_ = clock_->GetWallclockTime();
default_service_path_ = default_service_path_str;
is_conn_type_valid_ = false;
return true;
}
void RealShillProvider::HandlePropertyChanged(DBusGProxy* proxy,
const char* name, GValue* value) {
if (!strcmp(name, shill::kDefaultServiceProperty))
ProcessDefaultService(value);
}
void RealShillProvider::HandlePropertyChangedStatic(DBusGProxy* proxy,
const char* name,
GValue* value,
void* data) {
auto obj = reinterpret_cast<RealShillProvider*>(data);
obj->HandlePropertyChanged(proxy, name, value);
}
} // namespace chromeos_policy_manager