diff options
Diffstat (limited to 'pkg/sentry/device')
-rw-r--r-- | pkg/sentry/device/BUILD | 18 | ||||
-rw-r--r-- | pkg/sentry/device/device.go | 193 | ||||
-rw-r--r-- | pkg/sentry/device/device_test.go | 59 |
3 files changed, 270 insertions, 0 deletions
diff --git a/pkg/sentry/device/BUILD b/pkg/sentry/device/BUILD new file mode 100644 index 000000000..1a8b461ba --- /dev/null +++ b/pkg/sentry/device/BUILD @@ -0,0 +1,18 @@ +package(licenses = ["notice"]) # Apache 2.0 + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "device", + srcs = ["device.go"], + importpath = "gvisor.googlesource.com/gvisor/pkg/sentry/device", + visibility = ["//pkg/sentry:internal"], + deps = ["//pkg/abi/linux"], +) + +go_test( + name = "device_test", + size = "small", + srcs = ["device_test.go"], + embed = [":device"], +) diff --git a/pkg/sentry/device/device.go b/pkg/sentry/device/device.go new file mode 100644 index 000000000..a5514c72f --- /dev/null +++ b/pkg/sentry/device/device.go @@ -0,0 +1,193 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package device defines reserved virtual kernel devices and structures +// for managing them. +// +// Saving and restoring devices is not necessary if the devices are initialized +// as package global variables. Package initialization happens in a single goroutine +// and in a deterministic order, so minor device numbers will be assigned in the +// same order as packages are loaded. +package device + +import ( + "bytes" + "fmt" + "sync" + "sync/atomic" + + "gvisor.googlesource.com/gvisor/pkg/abi/linux" +) + +// ID identifies a device. +type ID struct { + Major uint64 + Minor uint64 +} + +// DeviceID formats a major and minor device number into a standard device number. +func (i *ID) DeviceID() uint64 { + return uint64(linux.MakeDeviceID(uint16(i.Major), uint32(i.Minor))) +} + +// nextAnonDeviceMinor is the next minor number for a new anonymous device. +// Must be accessed atomically. +var nextAnonDeviceMinor uint64 + +// NewAnonDevice creates a new anonymous device. Packages that require an anonymous +// device should initialize the device in a global variable in a file called device.go: +// +// var myDevice = device.NewAnonDevice() +func NewAnonDevice() *Device { + return &Device{ + ID: newAnonID(), + } +} + +// NewAnonMultiDevice creates a new multi-keyed anonymous device. Packages that require +// a multi-key anonymous device should initialize the device in a global variable in a +// file called device.go: +// +// var myDevice = device.NewAnonMultiDevice() +func NewAnonMultiDevice() *MultiDevice { + return &MultiDevice{ + ID: newAnonID(), + } +} + +// newAnonID assigns a major and minor number to an anonymous device ID. +func newAnonID() ID { + return ID{ + // Anon devices always have a major number of 0. + Major: 0, + // Use the next minor number. + Minor: atomic.AddUint64(&nextAnonDeviceMinor, 1), + } +} + +// Device is a simple virtual kernel device. +type Device struct { + ID + + // last is the last generated inode. + last uint64 +} + +// NextIno generates a new inode number +func (d *Device) NextIno() uint64 { + return atomic.AddUint64(&d.last, 1) +} + +// MultiDeviceKey provides a hashable key for a MultiDevice. The key consists +// of a raw device and inode for a resource, which must consistently identify +// the unique resource. It may optionally include a secondary device if +// appropriate. +// +// Note that using the path is not enough, because filesystems may rename a file +// to a different backing resource, at which point the path points to a different +// entity. Using only the inode is also not enough because the inode is assumed +// to be unique only within the device on which the resource exists. +type MultiDeviceKey struct { + Device uint64 + SecondaryDevice string + Inode uint64 +} + +// String stringifies the key. +func (m MultiDeviceKey) String() string { + return fmt.Sprintf("key{device: %d, sdevice: %s, inode: %d}", m.Device, m.SecondaryDevice, m.Inode) +} + +// MultiDevice allows for remapping resources that come from a variety of raw +// devices into a single device. The device ID should be one of the static +// Device IDs above and cannot be reused. +type MultiDevice struct { + ID + + mu sync.Mutex + last uint64 + cache map[MultiDeviceKey]uint64 + rcache map[uint64]MultiDeviceKey +} + +// String stringifies MultiDevice. +func (m *MultiDevice) String() string { + buf := bytes.NewBuffer(nil) + buf.WriteString("cache{") + for k, v := range m.cache { + buf.WriteString(fmt.Sprintf("%s -> %d, ", k, v)) + } + buf.WriteString("}") + return buf.String() +} + +// Map maps a raw device and inode into the inode space of MultiDevice, +// returning a virtualized inode. Raw devices and inodes can be reused; +// in this case, the same virtual inode will be returned. +func (m *MultiDevice) Map(key MultiDeviceKey) uint64 { + m.mu.Lock() + defer m.mu.Unlock() + + if m.cache == nil { + m.cache = make(map[MultiDeviceKey]uint64) + m.rcache = make(map[uint64]MultiDeviceKey) + } + + id, ok := m.cache[key] + if ok { + return id + } + // Step over reserved entries that may have been loaded. + idx := m.last + 1 + for { + if _, ok := m.rcache[idx]; !ok { + break + } + idx++ + } + // We found a non-reserved entry, use it. + m.last = idx + m.cache[key] = m.last + m.rcache[m.last] = key + return m.last +} + +// Load loads a raw device and inode into MultiDevice inode mappings +// with value as the virtual inode. +// +// By design, inodes start from 1 and continue until max uint64. This means +// that the zero value, which is often the uninitialized value, can be rejected +// as invalid. +func (m *MultiDevice) Load(key MultiDeviceKey, value uint64) bool { + // Reject the uninitialized value; see comment above. + if value == 0 { + return false + } + + m.mu.Lock() + defer m.mu.Unlock() + + if m.cache == nil { + m.cache = make(map[MultiDeviceKey]uint64) + m.rcache = make(map[uint64]MultiDeviceKey) + } + + // Cache value at key. + m.cache[key] = value + + // Prevent value from being used by new inode mappings. + m.rcache[value] = key + + return true +} diff --git a/pkg/sentry/device/device_test.go b/pkg/sentry/device/device_test.go new file mode 100644 index 000000000..dfec45046 --- /dev/null +++ b/pkg/sentry/device/device_test.go @@ -0,0 +1,59 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package device + +import ( + "testing" +) + +func TestMultiDevice(t *testing.T) { + device := &MultiDevice{} + + // Check that Load fails to install virtual inodes that are + // uninitialized. + if device.Load(MultiDeviceKey{}, 0) { + t.Fatalf("got load of invalid virtual inode 0, want unsuccessful") + } + + inode := device.Map(MultiDeviceKey{}) + + // Assert that the same raw device and inode map to + // a consistent virtual inode. + if i := device.Map(MultiDeviceKey{}); i != inode { + t.Fatalf("got inode %d, want %d in %s", i, inode, device) + } + + // Assert that a new inode or new device does not conflict. + if i := device.Map(MultiDeviceKey{Device: 0, Inode: 1}); i == inode { + t.Fatalf("got reused inode %d, want new distinct inode in %s", i, device) + } + last := device.Map(MultiDeviceKey{Device: 1, Inode: 0}) + if last == inode { + t.Fatalf("got reused inode %d, want new distinct inode in %s", last, device) + } + + // Virtual is the virtual inode we want to load. + virtual := last + 1 + + // Assert that we can load a virtual inode at a new place. + if !device.Load(MultiDeviceKey{Device: 0, Inode: 2}, virtual) { + t.Fatalf("got load of virtual inode %d failed, want success in %s", virtual, device) + } + + // Assert that the next inode skips over the loaded one. + if i := device.Map(MultiDeviceKey{Device: 0, Inode: 3}); i != virtual+1 { + t.Fatalf("got inode %d, want %d in %s", i, virtual+1, device) + } +} |