blob: 761f7ab7bf8512f798a34e7190e4a8de32c80c97 [file] [log] [blame]
// Copyright (c) 2009 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 "chromeos_mount.h" // NOLINT
#include <base/logging.h>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <string>
#include <vector>
#include "chromeos/dbus/dbus.h"
#include "chromeos/glib/object.h"
#include "chromeos/string.h"
#define G_UDEV_API_IS_SUBJECT_TO_CHANGE
#include <gudev/gudev.h>
#include <rootdev/rootdev.h>
namespace chromeos { // NOLINT
const char* kDeviceKitDisksInterface =
"org.freedesktop.DeviceKit.Disks";
const char* kDeviceKitDeviceInterface =
"org.freedesktop.DeviceKit.Disks.Device";
const char* kDeviceKitPropertiesInterface =
"org.freedesktop.DBus.Properties";
const char* kDefaultMountOptions[] = {
"rw",
"nodev",
"noexec",
"nosuid",
NULL
};
extern "C"
MountStatus* ChromeOSRetrieveMountInformation();
namespace { // NOLINT
bool DeviceIsParent(const dbus::BusConnection& bus,
dbus::Proxy& proxy) {
bool parent = false;
if (!dbus::RetrieveProperty(proxy,
kDeviceKitDeviceInterface,
"device-is-drive",
&parent)) {
// Since we should always be able to get this property, if we can't,
// there is some problem, so we should return false.
DLOG(ERROR) << "unable to determine if device is a drive";
return false;
}
return parent;
}
bool DeviceIsHidden(const dbus::BusConnection& bus,
dbus::Proxy& proxy) {
// Assume that all devices which are not on sda are removeable
bool hidden;
if (!dbus::RetrieveProperty(proxy,
kDeviceKitDeviceInterface,
"device-presentation-hide",
&hidden)) {
// We should always be able to get this property. If we can't, there is
// some problem, so we should return true to have Chrome ignore this disk.
DLOG(ERROR) << "unable to determine if device is hidden";
return true;
}
return hidden;
}
// Creates a new MountStatus instance populated with the contents from
// a vector of DiskStatus.
MountStatus* CopyFromVector(const std::vector<DiskStatus>& services) {
MountStatus* result = new MountStatus();
if (services.size() == 0) {
result->disks = NULL;
} else {
result->disks = new DiskStatus[services.size()];
}
result->size = services.size();
std::copy(services.begin(), services.end(), result->disks);
return result;
}
bool DeviceIsMounted(const dbus::BusConnection& bus,
const dbus::Proxy& proxy,
std::string& path,
bool* ismounted) {
bool mounted = false;
if (!dbus::RetrieveProperty(proxy,
kDeviceKitDeviceInterface,
"device-is-mounted",
&mounted)) {
DLOG(WARNING) << "unable to determine if device is mounted, bailing";
return false;
}
*ismounted = mounted;
if (mounted) {
glib::Value value;
if (!dbus::RetrieveProperty(proxy,
kDeviceKitDeviceInterface,
"device-mount-paths",
&value)) {
return false;
}
char** paths = static_cast<char**>(g_value_get_boxed(&value));
// TODO(dhg): This is an array for a reason, try to use it.
if (paths[0])
path = paths[0];
}
return true;
}
bool MountRemoveableDevice(const dbus::BusConnection& bus, const char* path) {
dbus::Proxy proxy(bus,
kDeviceKitDisksInterface,
path,
kDeviceKitDeviceInterface);
glib::ScopedError error;
char* val;
if (!::dbus_g_proxy_call(proxy.gproxy(),
"FilesystemMount",
&Resetter(&error).lvalue(),
G_TYPE_STRING, NULL,
G_TYPE_STRV, kDefaultMountOptions,
G_TYPE_INVALID,
G_TYPE_STRING,
&val, G_TYPE_INVALID)) {
LOG(WARNING) << "Filesystem Mount failed: "
<< (error->message ? error->message : "Unknown Error.");
return false;
}
g_free(val);
return true;
}
bool UnmountRemoveableDevice(const dbus::BusConnection& bus, const char* path) {
dbus::Proxy proxy(bus,
kDeviceKitDisksInterface,
path,
kDeviceKitDeviceInterface);
glib::ScopedError error;
if (!::dbus_g_proxy_call(proxy.gproxy(),
"FilesystemUnmount",
&Resetter(&error).lvalue(),
G_TYPE_STRV, NULL,
G_TYPE_INVALID,
G_TYPE_INVALID)) {
LOG(WARNING) << "Filesystem Unmount failed: "
<< (error->message ? error->message : "Unknown Error.");
return false;
}
return true;
}
} // namespace
void DeleteDiskStatusProperties(DiskStatus status) {
delete status.path;
delete status.mountpath;
}
extern "C"
void ChromeOSFreeMountStatus(MountStatus* status) {
if (status == NULL)
return;
std::for_each(status->disks,
status->disks + status->size,
&DeleteDiskStatusProperties);
delete [] status->disks;
delete status;
}
class OpaqueMountStatusConnection {
public:
typedef dbus::MonitorConnection<void (const char*)>* ConnectionType;
OpaqueMountStatusConnection(const MountMonitor& monitor,
const dbus::Proxy& mount,
void* object)
: gudev_client(NULL),
monitor_(monitor),
object_(object),
mount_(mount),
addconnection_(NULL),
removeconnection_(NULL) {
}
void FireEvent(MountEventType evt, const char* path) {
MountStatus* info;
if ((info = ChromeOSRetrieveMountInformation()) != NULL) {
if (evt == DEVICE_REMOVED ||
evt == DEVICE_ADDED ||
evt == DEVICE_SCANNED ||
evt == DISK_REMOVED) {
monitor_(object_, *info, evt, path);
} else {
for (int x = 0; x < info->size; x++) {
// This ensures we only event on disk adds/changes
// for things which are in the disk array
if (strcmp(path, info->disks[x].path) == 0) {
monitor_(object_, *info, evt, path);
break;
}
}
}
ChromeOSFreeMountStatus(info);
}
}
std::vector<std::string>::iterator FindDevicePath(std::string path) {
for (std::vector<std::string>::iterator i = paths_.begin();
i != paths_.end();
++i) {
if (path.find(*i) != std::string::npos) {
return i;
}
}
return paths_.end();
}
static void Added(void* object, const char* device) {
MountStatusConnection self = static_cast<MountStatusConnection>(object);
self->FireEvent(DISK_ADDED, device);
}
static void Removed(void* object, const char* device) {
MountStatusConnection self = static_cast<MountStatusConnection>(object);
self->FireEvent(DISK_REMOVED, device);
}
static void Changed(void* object, const char* device) {
MountStatusConnection self = static_cast<MountStatusConnection>(object);
self->FireEvent(DISK_CHANGED, device);
}
static void OnUDevEvent(GUdevClient* client,
const char* action,
GUdevDevice* device,
gpointer object) {
MountStatusConnection self = static_cast<MountStatusConnection>(object);
std::vector<std::string>::iterator iter;
// can be scsi or block
const char* subsystem = g_udev_device_get_subsystem(device);
if (subsystem == NULL || strcmp(subsystem, "scsi") != 0) {
return;
}
if (strcmp(action, "add") == 0) {
const char* device_path = g_udev_device_get_sysfs_path(device);
iter = self->FindDevicePath(device_path);
if (iter != self->paths_.end()) {
self->FireEvent(DEVICE_SCANNED, iter->c_str());
} else {
std::string path = device_path;
self->paths_.push_back(path);
self->FireEvent(DEVICE_ADDED, device_path);
}
} else if (strcmp(action, "remove") == 0) {
const char* device_path = g_udev_device_get_sysfs_path(device);
iter = self->FindDevicePath(device_path);
if (iter != self->paths_.end()) {
self->paths_.erase(iter);
self->FireEvent(DEVICE_REMOVED, device_path);
}
}
}
ConnectionType& addedconnection() {
return addconnection_;
}
ConnectionType& removedconnection() {
return removeconnection_;
}
ConnectionType& changedconnection() {
return changedconnection_;
}
GUdevClient* gudev_client;
private:
MountMonitor monitor_;
void* object_;
std::vector<std::string> paths_;
dbus::Proxy mount_;
ConnectionType addconnection_;
ConnectionType removeconnection_;
ConnectionType changedconnection_;
};
extern "C"
MountStatusConnection ChromeOSMonitorMountStatus(MountMonitor monitor,
void* object) {
dbus::BusConnection bus = dbus::GetSystemBusConnection();
dbus::Proxy mount(bus,
kDeviceKitDisksInterface,
"/org/freedesktop/DeviceKit/Disks",
kDeviceKitDisksInterface);
MountStatusConnection result =
new OpaqueMountStatusConnection(monitor, mount, object);
::dbus_g_proxy_add_signal(mount.gproxy(),
"DeviceAdded",
DBUS_TYPE_G_OBJECT_PATH,
G_TYPE_INVALID);
::dbus_g_proxy_add_signal(mount.gproxy(),
"DeviceRemoved",
DBUS_TYPE_G_OBJECT_PATH,
G_TYPE_INVALID);
::dbus_g_proxy_add_signal(mount.gproxy(),
"DeviceChanged",
DBUS_TYPE_G_OBJECT_PATH,
G_TYPE_INVALID);
typedef dbus::MonitorConnection<void (const char*)> ConnectionType;
ConnectionType* added = new ConnectionType(mount, "DeviceAdded",
&OpaqueMountStatusConnection::Added, result);
::dbus_g_proxy_connect_signal(mount.gproxy(), "DeviceAdded",
G_CALLBACK(&ConnectionType::Run),
added, NULL);
result->addedconnection() = added;
ConnectionType* removed = new ConnectionType(mount, "DeviceRemoved",
&OpaqueMountStatusConnection::Removed, result);
::dbus_g_proxy_connect_signal(mount.gproxy(), "DeviceRemoved",
G_CALLBACK(&ConnectionType::Run),
removed, NULL);
result->removedconnection() = removed;
ConnectionType* changed = new ConnectionType(mount, "DeviceChanged",
&OpaqueMountStatusConnection::Changed, result);
::dbus_g_proxy_connect_signal(mount.gproxy(), "DeviceChanged",
G_CALLBACK(&ConnectionType::Run),
changed, NULL);
result->changedconnection() = changed;
// Listen to udev events
const char *subsystems[] = {"scsi", "block", NULL};
result->gudev_client = g_udev_client_new(subsystems);
g_signal_connect(result->gudev_client,
"uevent",
G_CALLBACK(&OpaqueMountStatusConnection::OnUDevEvent),
result);
return result;
}
extern "C"
bool ChromeOSMountDevicePath(const char* device_path) {
dbus::BusConnection bus = dbus::GetSystemBusConnection();
return MountRemoveableDevice(bus, device_path);
}
extern "C"
bool ChromeOSUnmountDevicePath(const char* device_path) {
dbus::BusConnection bus = dbus::GetSystemBusConnection();
return UnmountRemoveableDevice(bus, device_path);
}
extern "C"
void ChromeOSDisconnectMountStatus(MountStatusConnection connection) {
dbus::Disconnect(connection->addedconnection());
dbus::Disconnect(connection->removedconnection());
dbus::Disconnect(connection->changedconnection());
delete connection;
}
extern "C"
MountStatus* ChromeOSRetrieveMountInformation() {
typedef glib::ScopedPtrArray<const char*> ScopedPtrArray;
typedef ScopedPtrArray::iterator iterator;
ScopedPtrArray devices;
dbus::BusConnection bus = dbus::GetSystemBusConnection();
dbus::Proxy mount(bus,
kDeviceKitDisksInterface,
"/org/freedesktop/DeviceKit/Disks",
kDeviceKitDisksInterface);
if (!dbus::CallPtrArray(mount, "EnumerateDevices", &devices)) {
DLOG(WARNING) << "Could not enumerate disk devices.";
return NULL;
}
std::vector<DiskStatus> buffer;
for (iterator currentpath = devices.begin();
currentpath < devices.end();
++currentpath) {
dbus::Proxy proxy(bus,
kDeviceKitDisksInterface,
*currentpath,
kDeviceKitPropertiesInterface);
const bool hidden = DeviceIsHidden(bus, proxy);
if (!hidden) {
DiskStatus info = {};
bool ismounted = false;
std::string path;
info.isparent = DeviceIsParent(bus, proxy);
info.path = NewStringCopy(*currentpath);
if (DeviceIsMounted(bus, proxy, path, &ismounted)) {
if (ismounted) {
info.mountpath = NewStringCopy(path.c_str());
}
}
info.hasmedia = false;
dbus::RetrieveProperty(proxy,
kDeviceKitDeviceInterface,
"device-is-media-available",
&info.hasmedia);
if (dbus::RetrieveProperty(proxy,
kDeviceKitDeviceInterface,
"native-path",
&path)) {
info.systempath = NewStringCopy(path.c_str());
}
buffer.push_back(info);
}
}
return CopyFromVector(buffer);
}
extern "C"
bool ChromeOSIsBootDevicePath(const char* device_path) {
char boot_path[PATH_MAX];
if (rootdev(boot_path, sizeof(boot_path), true, true)) {
LOG(ERROR) << "IsBootDevicePath: rootdev failed to find the root device";
return true;
}
return (strncmp(device_path, boot_path, strlen(boot_path)) == 0);
}
} // namespace chromeos