blob: d8acd9edb145a1742091c570a0b9c2050fe6021b [file] [log] [blame]
// Copyright 2016 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 "container_utils/device_jail_control.h"
#include <fcntl.h>
#include <linux/device_jail.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <base/logging.h>
namespace {
const char kJailControlPath[] = "/dev/jail-control";
struct UdevDeviceUnref {
inline void operator()(struct udev_device* ptr) {
if (ptr)
udev_device_unref(ptr);
}
};
using UdevDevice = std::unique_ptr<struct udev_device, UdevDeviceUnref>;
} // namespace
namespace device_jail {
// static
std::unique_ptr<DeviceJailControl> DeviceJailControl::Create() {
base::ScopedFD control_fd(open(kJailControlPath, O_RDWR));
if (!control_fd.is_valid()) {
PLOG(ERROR) << "unable to open control device";
return std::unique_ptr<DeviceJailControl>();
}
struct udev* udev = udev_new();
if (!udev) {
LOG(ERROR) << "unable to get udev context";
return std::unique_ptr<DeviceJailControl>();
}
return std::unique_ptr<DeviceJailControl>(
new DeviceJailControl(std::move(control_fd), udev));
}
DeviceJailControl::~DeviceJailControl() {
if (udev_)
udev_unref(udev_);
}
DeviceJailControl::AddResult DeviceJailControl::AddDevice(
const std::string& path, std::string* jail_path) {
if (!jail_path)
return AddResult::ERROR;
struct jail_control_add_dev arg;
arg.path = path.c_str();
bool exists = false;
int ret = ioctl(control_fd_.get(), JAIL_CONTROL_ADD_DEVICE, &arg);
if (ret < 0) {
if (errno == EEXIST) {
exists = true;
} else {
PLOG(INFO) << "failed to create device";
return AddResult::ERROR;
}
}
// The udev library doesn't implement its own locking, so we have
// to make sure nobody else is using the struct udev.
base::AutoLock _l(udev_lock_);
UdevDevice device(udev_device_new_from_devnum(udev_, 'c', arg.devnum));
if (!device) {
PLOG(ERROR) << "udev doesn't recognize the jail device";
return AddResult::ERROR;
}
// Wait a few ms for udev to run rules on the device. Shouldn't take
// longer than 3ms, but poll for a little longer to be sure.
// TODO(ejcaruso): replace this with an async call that uses udev_monitor
// to determine when the device is initialized.
for (int i = 0; i < 10; i++) {
if (udev_device_get_is_initialized(device.get()))
break;
usleep(1000);
}
*jail_path = udev_device_get_devnode(device.get());
if (!udev_device_get_is_initialized(device.get()))
LOG(WARNING) << "udev is taking a while to initialize " << *jail_path;
return exists ? AddResult::ALREADY_EXISTS : AddResult::CREATED;
}
bool DeviceJailControl::RemoveDevice(const std::string& path) {
struct stat buf;
int ret = stat(path.c_str(), &buf);
if (ret < 0) {
PLOG(ERROR) << "could not stat " << path;
return false;
}
if (!S_ISCHR(buf.st_mode)) {
LOG(ERROR) << path << " is not a character device";
return false;
}
ret = ioctl(control_fd_.get(), JAIL_CONTROL_REMOVE_DEVICE, &buf.st_rdev);
if (ret < 0) {
PLOG(ERROR) << "error removing device";
return false;
}
return true;
}
} // namespace device_jail