soma: Initial spec reading and parsing code
The SpecReader class has methods for reading a parsing a
container specification into a ContainerSpec object. Among
other data, this ContainerSpec holds lists of 'Filter'
objects that express which device and sysfs nodes should
be visible inside the specified container.
BUG=brillo:304
TEST=unit tests
Change-Id: If73669791ce402da03085550b9010550a38f6043
Reviewed-on: https://chromium-review.googlesource.com/254484
Reviewed-by: Chris Masone <cmasone@chromium.org>
Commit-Queue: Chris Masone <cmasone@chromium.org>
Tested-by: Chris Masone <cmasone@chromium.org>
diff --git a/soma/README b/soma/README
index b3498f3..b6f8c15 100644
--- a/soma/README
+++ b/soma/README
@@ -34,11 +34,12 @@
struct ContainerSpec {
String ServiceBundlePath;
- Int DesiredUser;
- Int DesiredGroup;
+ Int DesiredUserID;
+ Int DesiredGroupID;
List<Int> ListeningPorts;
- List<DeviceFilter> DeviceFilters;
+ List<DeviceNodeFilter> DeviceNodeFilters;
+ List<DevicePathFilter> DevicePathFilters;
List<SysfsPathFilter> SysfsPathFilters;
List<UsbDeviceFilter> UsbDeviceFilters;
};
@@ -50,7 +51,8 @@
Optional fields:
ListeningPorts: List of network ports on which the service can listen.
- DeviceFilters: Filters indicating which device nodes should be visible.
+ DeviceNodeFilters: Filters indicating which device nodes should be visible.
+ DevicePathFilters: Filters indicating which device paths should be visible.
SysfsPathFilters: Filters indicating which sysfs paths should be visible.
UsbDeviceFilters; Filters indicating which USB devices should be visible.
@@ -58,14 +60,12 @@
Filter types - Several kinds of filters used to control access to pieces of
hardware at runtime.
- struct DeviceFilter {};
-
- struct DeviceNodeFilter : DeviceFilter {
+ struct DeviceNodeFilter {
Int major; May be a wildcard
Int minor; May be a wildcard
};
- struct DevicePathFilter : DeviceFilter {
+ struct DevicePathFilter {
String filter; May contain globs (language TBD)
};
diff --git a/soma/container_spec.cc b/soma/container_spec.cc
new file mode 100644
index 0000000..1b02212
--- /dev/null
+++ b/soma/container_spec.cc
@@ -0,0 +1,70 @@
+// Copyright 2015 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 "soma/container_spec.h"
+
+#include <sys/types.h>
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/memory/scoped_vector.h>
+
+#include "soma/device_filter.h"
+#include "soma/sysfs_filter.h"
+#include "soma/usb_device_filter.h"
+
+namespace soma {
+
+ContainerSpec::ContainerSpec(const base::FilePath& service_bundle_path,
+ uid_t uid,
+ gid_t gid)
+ : service_bundle_path_(service_bundle_path),
+ uid_(uid),
+ gid_(gid) {
+}
+
+ContainerSpec::~ContainerSpec() {}
+
+void ContainerSpec::AddListenPort(int port) {
+ listen_ports_.insert(port);
+}
+
+void ContainerSpec::AddDevicePathFilter(const std::string& filter) {
+ device_path_filters_.push_back(new DevicePathFilter(base::FilePath(filter)));
+}
+
+void ContainerSpec::AddDeviceNodeFilter(int major, int minor) {
+ device_node_filters_.push_back(new DeviceNodeFilter(major, minor));
+}
+
+void ContainerSpec::AddSysfsFilter(const std::string& filter) {
+ sysfs_filters_.push_back(new SysfsFilter(base::FilePath(filter)));
+}
+
+void ContainerSpec::AddUSBDeviceFilter(int vid, int pid) {
+ usb_device_filters_.push_back(new USBDeviceFilter(vid, pid));
+}
+
+bool ContainerSpec::DevicePathIsAllowed(const base::FilePath& query) {
+ return
+ device_path_filters_.end() !=
+ std::find_if(device_path_filters_.begin(), device_path_filters_.end(),
+ [query](DevicePathFilter* to_check) {
+ return to_check->Allows(query);
+ });
+}
+
+bool ContainerSpec::DeviceNodeIsAllowed(int major, int minor) {
+ return
+ device_node_filters_.end() !=
+ std::find_if(device_node_filters_.begin(), device_node_filters_.end(),
+ [major, minor](DeviceNodeFilter* to_check) {
+ return to_check->Allows(major, minor);
+ });
+}
+
+} // namespace soma
diff --git a/soma/container_spec.h b/soma/container_spec.h
new file mode 100644
index 0000000..98bafc8
--- /dev/null
+++ b/soma/container_spec.h
@@ -0,0 +1,67 @@
+// Copyright 2015 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.
+
+#ifndef SOMA_CONTAINER_SPEC_H_
+#define SOMA_CONTAINER_SPEC_H_
+
+#include <sys/types.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/memory/scoped_vector.h>
+
+#include "soma/device_filter.h"
+#include "soma/sysfs_filter.h"
+#include "soma/usb_device_filter.h"
+
+namespace soma {
+
+// Holds intermediate representation of container specification.
+// TODO(cmasone): Serialization of this will need to be a thing.
+class ContainerSpec {
+ public:
+ ContainerSpec(const base::FilePath& service_bundle_path,
+ uid_t uid,
+ gid_t gid);
+ virtual ~ContainerSpec();
+
+ void AddListenPort(int port);
+
+ void AddDevicePathFilter(const std::string& filter);
+ void AddDeviceNodeFilter(int major, int minor);
+ void AddSysfsFilter(const std::string& filter);
+ void AddUSBDeviceFilter(int vid, int pid);
+
+ const base::FilePath& service_bundle_path() { return service_bundle_path_; }
+ uid_t uid() { return uid_; }
+ gid_t gid() { return gid_; }
+
+ // Returns true if there's a DevicePathFilter that matches query.
+ bool DevicePathIsAllowed(const base::FilePath& query);
+
+ // Returns true if there's a DeviceNodeFilter that matches major and minor.
+ bool DeviceNodeIsAllowed(int major, int minor);
+
+ private:
+ const base::FilePath service_bundle_path_;
+ const uid_t uid_;
+ const gid_t gid_;
+
+ std::set<int> listen_ports_;
+
+ // TODO(cmasone): As we gain more experience with these, investigate whether
+ // they should also be sets, or at leat have set semantics.
+ ScopedVector<DevicePathFilter> device_path_filters_;
+ ScopedVector<DeviceNodeFilter> device_node_filters_;
+ ScopedVector<SysfsFilter> sysfs_filters_;
+ ScopedVector<USBDeviceFilter> usb_device_filters_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContainerSpec);
+};
+
+} // namespace soma
+#endif // SOMA_CONTAINER_SPEC_H_
diff --git a/soma/container_spec_unittest.cc b/soma/container_spec_unittest.cc
new file mode 100644
index 0000000..0c2618a
--- /dev/null
+++ b/soma/container_spec_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright 2015 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 "soma/container_spec.h"
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+namespace soma {
+
+class ContainerSpecTest : public ::testing::Test {
+ public:
+ ContainerSpecTest() {}
+ virtual ~ContainerSpecTest() {}
+};
+
+TEST_F(ContainerSpecTest, DevicePathFilterTest) {
+ base::ScopedTempDir tmpdir;
+ base::FilePath scratch;
+ ASSERT_TRUE(tmpdir.CreateUniqueTempDir());
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(tmpdir.path(), &scratch));
+
+ ContainerSpec spec(base::FilePath("/foo/bar"), 0, 0);
+ std::string device_path("/dev/thing");
+ spec.AddDevicePathFilter(device_path);
+
+ EXPECT_TRUE(spec.DevicePathIsAllowed(base::FilePath(device_path)));
+ EXPECT_FALSE(spec.DevicePathIsAllowed(base::FilePath("/not/a/thing")));
+}
+
+TEST_F(ContainerSpecTest, DeviceNodeFilterTest) {
+ base::ScopedTempDir tmpdir;
+ base::FilePath scratch;
+ ASSERT_TRUE(tmpdir.CreateUniqueTempDir());
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(tmpdir.path(), &scratch));
+
+ ContainerSpec spec(base::FilePath("/foo/bar"), 0, 0);
+ spec.AddDeviceNodeFilter(1, 2);
+
+ EXPECT_TRUE(spec.DeviceNodeIsAllowed(1, 2));
+ EXPECT_FALSE(spec.DeviceNodeIsAllowed(0, 1));
+}
+
+} // namespace soma
diff --git a/soma/device_filter.cc b/soma/device_filter.cc
new file mode 100644
index 0000000..b848d06
--- /dev/null
+++ b/soma/device_filter.cc
@@ -0,0 +1,22 @@
+// Copyright 2015 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 "soma/device_filter.h"
+
+#include <base/files/file_path.h>
+
+namespace soma {
+
+DevicePathFilter::DevicePathFilter(const base::FilePath& path) : filter_(path) {
+}
+
+DevicePathFilter::~DevicePathFilter() {}
+
+DeviceNodeFilter::DeviceNodeFilter(int major, int minor)
+ : major_(major), minor_(minor) {
+}
+
+DeviceNodeFilter::~DeviceNodeFilter() {}
+
+} // namespace soma
diff --git a/soma/device_filter.h b/soma/device_filter.h
new file mode 100644
index 0000000..1d9e352
--- /dev/null
+++ b/soma/device_filter.h
@@ -0,0 +1,63 @@
+// Copyright 2015 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.
+
+#ifndef SOMA_DEVICE_FILTER_H_
+#define SOMA_DEVICE_FILTER_H_
+
+#include <base/files/file_path.h>
+
+namespace soma {
+
+class DevicePathFilter {
+ public:
+ // Will be useful if I put these in a std::set<>, which I might.
+ using Comparator = bool(*)(const DevicePathFilter&, const DevicePathFilter&);
+ static bool Comp(const DevicePathFilter& a, const DevicePathFilter& b) {
+ return a.Precedes(b);
+ }
+
+ explicit DevicePathFilter(const base::FilePath& path);
+ virtual ~DevicePathFilter();
+
+ bool Precedes(const DevicePathFilter& rhs) const {
+ return filter_.value() < rhs.filter_.value();
+ }
+
+ bool Allows(const base::FilePath& rhs) const {
+ return filter_.value() == rhs.value();
+ }
+
+ private:
+ const base::FilePath filter_;
+ DISALLOW_COPY_AND_ASSIGN(DevicePathFilter);
+};
+
+class DeviceNodeFilter{
+ public:
+ // Will be useful if I put these in a std::set<>, which I might.
+ using Comparator = bool(*)(const DeviceNodeFilter&, const DeviceNodeFilter&);
+ static bool Comp(const DeviceNodeFilter& a, const DeviceNodeFilter& b) {
+ return a.Precedes(b);
+ }
+
+ DeviceNodeFilter(int major, int minor);
+ virtual ~DeviceNodeFilter();
+
+ bool Precedes(const DeviceNodeFilter& rhs) const {
+ return major_ < rhs.major_ || (major_ == rhs.major_ && minor_ < rhs.minor_);
+ }
+
+ // TODO(cmasone): handle wildcarding in both major and minor.
+ bool Allows(int major, int minor) const {
+ return major_ == major && minor_ == minor;
+ }
+
+ private:
+ int major_;
+ int minor_;
+ DISALLOW_COPY_AND_ASSIGN(DeviceNodeFilter);
+};
+
+} // namespace soma
+#endif // SOMA_DEVICE_FILTER_H_
diff --git a/soma/soma.gyp b/soma/soma.gyp
index 0df8a14..41784b1 100644
--- a/soma/soma.gyp
+++ b/soma/soma.gyp
@@ -3,13 +3,23 @@
'defines': [
'__STDC_FORMAT_MACROS',
],
+ 'variables': {
+ 'deps': [
+ 'libchrome-<(libbase_ver)',
+ ],
+ },
},
'targets': [
{
'target_name': 'libsoma',
'type': 'static_library',
'sources': [
+ 'container_spec.cc',
+ 'device_filter.cc',
+ 'spec_reader.cc',
+ 'sysfs_filter.cc',
'soma.cc',
+ 'usb_device_filter.cc',
],
},
],
@@ -23,7 +33,9 @@
'defines': ['UNIT_TEST'],
'dependencies': ['libsoma'],
'sources': [
+ 'container_spec_unittest.cc',
'soma_testrunner.cc',
+ 'spec_reader_unittest.cc',
],
},
],
diff --git a/soma/spec_reader.cc b/soma/spec_reader.cc
new file mode 100644
index 0000000..82ee70a
--- /dev/null
+++ b/soma/spec_reader.cc
@@ -0,0 +1,116 @@
+// Copyright 2015 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 "soma/spec_reader.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/json/json_reader.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/values.h>
+
+#include "soma/container_spec.h"
+
+namespace soma {
+namespace {
+// Helper function that parses a list of integer pairs.
+std::vector<std::pair<int, int>> ParseIntegerPairs(base::ListValue* filters) {
+ std::vector<std::pair<int, int>> to_return;
+ for (base::Value* filter : *filters) {
+ base::ListValue* nested = nullptr;
+ if (!(filter->GetAsList(&nested) && nested->GetSize() == 2)) {
+ LOG(ERROR) << "Device node filter must be a list of 2 elements.";
+ continue;
+ }
+ int major, minor;
+ if (!nested->GetInteger(0, &major) || !nested->GetInteger(1, &minor)) {
+ LOG(ERROR) << "Device node filter must contain 2 ints.";
+ continue;
+ }
+ to_return.push_back(std::make_pair(major, minor));
+ }
+ return to_return;
+}
+} // anonymous namespace
+
+const char ContainerSpecReader::kServiceBundlePathKey[] = "service bundle path";
+const char ContainerSpecReader::kUidKey[] = "uid";
+const char ContainerSpecReader::kGidKey[] = "gid";
+
+const char ContainerSpecReader::kDevicePathFiltersKey[] = "device path filters";
+const char ContainerSpecReader::kDeviceNodeFiltersKey[] = "device node filters";
+
+ContainerSpecReader::ContainerSpecReader()
+ : reader_(base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS) {
+}
+
+ContainerSpecReader::~ContainerSpecReader() {
+}
+
+scoped_ptr<ContainerSpec> ContainerSpecReader::Read(
+ const base::FilePath& spec_file) {
+ LOG(INFO) << "Reading container spec at " << spec_file.value();
+ std::string spec_string;
+ if (!base::ReadFileToString(spec_file, &spec_string)) {
+ PLOG(ERROR) << "Can't read " << spec_file.value();
+ return nullptr;
+ }
+ return Parse(spec_string);
+}
+
+scoped_ptr<ContainerSpec> ContainerSpecReader::Parse(const std::string& json) {
+ scoped_ptr<base::Value> root = make_scoped_ptr(reader_.ReadToValue(json));
+ if (!root) {
+ LOG(ERROR) << "Failed to parse: " << reader_.GetErrorMessage();
+ return nullptr;
+ }
+ base::DictionaryValue* spec_dict = nullptr;
+ if (!root->GetAsDictionary(&spec_dict)) {
+ LOG(ERROR) << "Spec should have been a dictionary.";
+ return nullptr;
+ }
+
+ std::string service_bundle_path;
+ if (!spec_dict->GetString(kServiceBundlePathKey, &service_bundle_path)) {
+ LOG(ERROR) << "service bundle path is required.";
+ return nullptr;
+ }
+
+ int uid, gid;
+ if (!spec_dict->GetInteger(kUidKey, &uid) ||
+ !spec_dict->GetInteger(kGidKey, &gid)) {
+ LOG(ERROR) << "uid and gid are required.";
+ return nullptr;
+ }
+
+ scoped_ptr<ContainerSpec> spec(
+ new ContainerSpec(base::FilePath(service_bundle_path), uid, gid));
+
+ base::ListValue* device_path_filters = nullptr;
+ std::string temp_filter_string;
+ if (spec_dict->GetList(kDevicePathFiltersKey, &device_path_filters)) {
+ for (base::Value* filter : *device_path_filters) {
+ if (!filter->GetAsString(&temp_filter_string)) {
+ LOG(ERROR) << "Device path filters must be strings.";
+ continue;
+ }
+ spec->AddDevicePathFilter(temp_filter_string);
+ }
+ }
+
+ base::ListValue* device_node_filters = nullptr;
+ if (spec_dict->GetList(kDeviceNodeFiltersKey, &device_node_filters)) {
+ for (const auto& num_pair : ParseIntegerPairs(device_node_filters)) {
+ spec->AddDeviceNodeFilter(num_pair.first, num_pair.second);
+ }
+ }
+
+ return spec.Pass();
+}
+
+} // namespace soma
diff --git a/soma/spec_reader.h b/soma/spec_reader.h
new file mode 100644
index 0000000..1018570
--- /dev/null
+++ b/soma/spec_reader.h
@@ -0,0 +1,51 @@
+// Copyright 2015 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.
+
+#ifndef SOMA_SPEC_READER_H_
+#define SOMA_SPEC_READER_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/json/json_reader.h>
+#include <base/memory/scoped_ptr.h>
+
+namespace base {
+class ListValue;
+}
+
+namespace soma {
+class ContainerSpec;
+
+// A class that handles reading a container specification written in JSON
+// from disk and parsing it into a ContainerSpec object.
+class ContainerSpecReader {
+ public:
+ // Keys for required fields in a container specification.
+ static const char kServiceBundlePathKey[];
+ static const char kUidKey[];
+ static const char kGidKey[];
+
+ // Keys for optional fields in a container specification.
+ static const char kDevicePathFiltersKey[];
+ static const char kDeviceNodeFiltersKey[];
+
+ ContainerSpecReader();
+ virtual ~ContainerSpecReader();
+
+ // Read a container specification at spec_file and return a ContainerSpec
+ // object. Returns nullptr on failures and logs appropriate messages.
+ scoped_ptr<ContainerSpec> Read(const base::FilePath& spec_file);
+
+ private:
+ // Workhorse for doing the parsing of specific fields in the spec.
+ scoped_ptr<ContainerSpec> Parse(const std::string& json);
+
+ base::JSONReader reader_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContainerSpecReader);
+};
+} // namespace soma
+
+#endif // SOMA_SPEC_READER_H_
diff --git a/soma/spec_reader_unittest.cc b/soma/spec_reader_unittest.cc
new file mode 100644
index 0000000..4f42633
--- /dev/null
+++ b/soma/spec_reader_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright 2015 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 "soma/spec_reader.h"
+
+#include <string>
+#include <utility>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "soma/container_spec.h"
+
+namespace soma {
+
+class ContainerSpecReaderTest : public ::testing::Test {
+ public:
+ ContainerSpecReaderTest() {}
+ virtual ~ContainerSpecReaderTest() {}
+
+ void SetUp() override {
+ ASSERT_TRUE(tmpdir_.CreateUniqueTempDir());
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(tmpdir_.path(), &scratch_));
+ }
+
+ protected:
+ scoped_ptr<base::DictionaryValue> BuildBaselineValue() {
+ scoped_ptr<base::DictionaryValue> baseline_spec(new base::DictionaryValue);
+ baseline_spec->SetString(ContainerSpecReader::kServiceBundlePathKey,
+ kServiceBundlePath);
+ baseline_spec->SetInteger(ContainerSpecReader::kUidKey, kUid);
+ baseline_spec->SetInteger(ContainerSpecReader::kGidKey, kGid);
+ return baseline_spec.Pass();
+ }
+
+ void WriteValue(base::Value* to_write, const base::FilePath& file) {
+ std::string value_string;
+ ASSERT_TRUE(base::JSONWriter::Write(to_write, &value_string));
+ ASSERT_EQ(
+ base::WriteFile(file, value_string.c_str(), value_string.length()),
+ value_string.length());
+ }
+
+ void CheckSpecBaseline(ContainerSpec* spec) {
+ ASSERT_TRUE(spec);
+ ASSERT_EQ(spec->service_bundle_path(),
+ base::FilePath(kServiceBundlePath));
+ ASSERT_EQ(spec->uid(), kUid);
+ ASSERT_EQ(spec->gid(), kGid);
+ }
+
+ ContainerSpecReader reader_;
+ base::ScopedTempDir tmpdir_;
+ base::FilePath scratch_;
+
+ private:
+ static const char kServiceBundlePath[];
+ static const uid_t kUid;
+ static const gid_t kGid;
+};
+
+const char ContainerSpecReaderTest::kServiceBundlePath[] = "/services/bundle";
+const uid_t ContainerSpecReaderTest::kUid = 1;
+const gid_t ContainerSpecReaderTest::kGid = 2;
+
+TEST_F(ContainerSpecReaderTest, BaselineSpec) {
+ scoped_ptr<base::DictionaryValue> baseline = BuildBaselineValue();
+ WriteValue(baseline.get(), scratch_);
+
+ scoped_ptr<ContainerSpec> spec = reader_.Read(scratch_);
+ CheckSpecBaseline(spec.get());
+}
+
+namespace {
+scoped_ptr<base::ListValue> ListFromPair(const std::pair<int, int>& pair) {
+ scoped_ptr<base::ListValue> list(new base::ListValue);
+ list->AppendInteger(pair.first);
+ list->AppendInteger(pair.second);
+ return list.Pass();
+}
+} // anonymous namespace
+
+TEST_F(ContainerSpecReaderTest, SpecWithDeviceFilters) {
+ scoped_ptr<base::DictionaryValue> baseline = BuildBaselineValue();
+
+ const char kPathFilter1[] = "/dev/d1";
+ const char kPathFilter2[] = "/dev/d2";
+ scoped_ptr<base::ListValue> device_path_filters(new base::ListValue);
+ device_path_filters->AppendString(kPathFilter1);
+ device_path_filters->AppendString(kPathFilter2);
+ baseline->Set(ContainerSpecReader::kDevicePathFiltersKey,
+ device_path_filters.release());
+
+ scoped_ptr<base::ListValue> device_node_filters(new base::ListValue);
+ device_node_filters->Append(ListFromPair(std::make_pair(8, 0)).release());
+ device_node_filters->Append(ListFromPair(std::make_pair(4, -1)).release());
+ baseline->Set(ContainerSpecReader::kDeviceNodeFiltersKey,
+ device_node_filters.release());
+
+ WriteValue(baseline.get(), scratch_);
+
+ scoped_ptr<ContainerSpec> spec = reader_.Read(scratch_);
+ CheckSpecBaseline(spec.get());
+ EXPECT_TRUE(spec->DevicePathIsAllowed(base::FilePath(kPathFilter1)));
+ EXPECT_TRUE(spec->DevicePathIsAllowed(base::FilePath(kPathFilter2)));
+ EXPECT_TRUE(spec->DeviceNodeIsAllowed(8, 0));
+ EXPECT_TRUE(spec->DeviceNodeIsAllowed(4, -1));
+}
+
+} // namespace soma
diff --git a/soma/sysfs_filter.cc b/soma/sysfs_filter.cc
new file mode 100644
index 0000000..86f5aa1
--- /dev/null
+++ b/soma/sysfs_filter.cc
@@ -0,0 +1,16 @@
+// Copyright 2015 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 "soma/sysfs_filter.h"
+
+#include <base/files/file_path.h>
+
+namespace soma {
+
+SysfsFilter::SysfsFilter(const base::FilePath& path) : filter_(path) {
+}
+
+SysfsFilter::~SysfsFilter() {}
+
+} // namespace soma
diff --git a/soma/sysfs_filter.h b/soma/sysfs_filter.h
new file mode 100644
index 0000000..026a132
--- /dev/null
+++ b/soma/sysfs_filter.h
@@ -0,0 +1,23 @@
+// Copyright 2015 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.
+
+#ifndef SOMA_SYSFS_FILTER_H_
+#define SOMA_SYSFS_FILTER_H_
+
+#include <base/files/file_path.h>
+
+namespace soma {
+
+class SysfsFilter {
+ public:
+ explicit SysfsFilter(const base::FilePath& path);
+ virtual ~SysfsFilter();
+
+ private:
+ const base::FilePath filter_;
+ DISALLOW_COPY_AND_ASSIGN(SysfsFilter);
+};
+
+} // namespace soma
+#endif // SOMA_SYSFS_FILTER_H_
diff --git a/soma/usb_device_filter.cc b/soma/usb_device_filter.cc
new file mode 100644
index 0000000..ecaef2c
--- /dev/null
+++ b/soma/usb_device_filter.cc
@@ -0,0 +1,15 @@
+// Copyright 2015 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 "soma/usb_device_filter.h"
+
+namespace soma {
+
+USBDeviceFilter::~USBDeviceFilter() {}
+
+USBDeviceFilter::USBDeviceFilter(int vid, int pid)
+ : vid_(vid), pid_(pid) {
+}
+
+} // namespace soma
diff --git a/soma/usb_device_filter.h b/soma/usb_device_filter.h
new file mode 100644
index 0000000..2153151
--- /dev/null
+++ b/soma/usb_device_filter.h
@@ -0,0 +1,29 @@
+// Copyright 2015 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.
+
+#ifndef SOMA_USB_DEVICE_FILTER_H_
+#define SOMA_USB_DEVICE_FILTER_H_
+
+#include <base/basictypes.h>
+
+namespace soma {
+
+class USBDeviceFilter {
+ public:
+ USBDeviceFilter(int vid, int pid);
+ virtual ~USBDeviceFilter();
+
+ // TODO(cmasone): handle wildcarding in both major and minor.
+ bool Allows(const USBDeviceFilter& rhs) {
+ return vid_ == rhs.vid_ && pid_ == rhs.pid_;
+ }
+
+ private:
+ int vid_;
+ int pid_;
+ DISALLOW_COPY_AND_ASSIGN(USBDeviceFilter);
+};
+
+} // namespace soma
+#endif // SOMA_USB_DEVICE_FILTER_H_