diff options
Diffstat (limited to 'pkg/sentry/platform/interrupt')
-rw-r--r-- | pkg/sentry/platform/interrupt/BUILD | 19 | ||||
-rw-r--r-- | pkg/sentry/platform/interrupt/interrupt.go | 96 | ||||
-rw-r--r-- | pkg/sentry/platform/interrupt/interrupt_test.go | 99 |
3 files changed, 214 insertions, 0 deletions
diff --git a/pkg/sentry/platform/interrupt/BUILD b/pkg/sentry/platform/interrupt/BUILD new file mode 100644 index 000000000..33dde2a31 --- /dev/null +++ b/pkg/sentry/platform/interrupt/BUILD @@ -0,0 +1,19 @@ +package(licenses = ["notice"]) # Apache 2.0 + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "interrupt", + srcs = [ + "interrupt.go", + ], + importpath = "gvisor.googlesource.com/gvisor/pkg/sentry/platform/interrupt", + visibility = ["//pkg/sentry:internal"], +) + +go_test( + name = "interrupt_test", + size = "small", + srcs = ["interrupt_test.go"], + embed = [":interrupt"], +) diff --git a/pkg/sentry/platform/interrupt/interrupt.go b/pkg/sentry/platform/interrupt/interrupt.go new file mode 100644 index 000000000..ca4f42087 --- /dev/null +++ b/pkg/sentry/platform/interrupt/interrupt.go @@ -0,0 +1,96 @@ +// 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 interrupt provides an interrupt helper. +package interrupt + +import ( + "fmt" + "sync" +) + +// Receiver receives interrupt notifications from a Forwarder. +type Receiver interface { + // NotifyInterrupt is called when the Receiver receives an interrupt. + NotifyInterrupt() +} + +// Forwarder is a helper for delivering delayed signal interruptions. +// +// This helps platform implementations with Interrupt semantics. +type Forwarder struct { + // mu protects the below. + mu sync.Mutex + + // dst is the function to be called when NotifyInterrupt() is called. If + // dst is nil, pending will be set instead, causing the next call to + // Enable() to return false. + dst Receiver + pending bool +} + +// Enable attempts to enable interrupt forwarding to r. If f has already +// received an interrupt, Enable does nothing and returns false. Otherwise, +// future calls to f.NotifyInterrupt() cause r.NotifyInterrupt() to be called, +// and Enable returns true. +// +// Usage: +// +// if !f.Enable(r) { +// // There was an interrupt. +// return +// } +// defer f.Disable() +// +// Preconditions: r must not be nil. f must not already be forwarding +// interrupts to a Receiver. +func (f *Forwarder) Enable(r Receiver) bool { + if r == nil { + panic("nil Receiver") + } + f.mu.Lock() + if f.dst != nil { + f.mu.Unlock() + panic(fmt.Sprintf("already forwarding interrupts to %+v", f.dst)) + } + if f.pending { + f.pending = false + f.mu.Unlock() + return false + } + f.dst = r + f.mu.Unlock() + return true +} + +// Disable stops interrupt forwarding. If interrupt forwarding is already +// disabled, Disable is a no-op. +func (f *Forwarder) Disable() { + f.mu.Lock() + f.dst = nil + f.mu.Unlock() +} + +// NotifyInterrupt implements Receiver.NotifyInterrupt. If interrupt forwarding +// is enabled, the configured Receiver will be notified. Otherwise the +// interrupt will be delivered to the next call to Enable. +func (f *Forwarder) NotifyInterrupt() { + f.mu.Lock() + if f.dst != nil { + f.dst.NotifyInterrupt() + } else { + f.pending = true + } + f.mu.Unlock() +} diff --git a/pkg/sentry/platform/interrupt/interrupt_test.go b/pkg/sentry/platform/interrupt/interrupt_test.go new file mode 100644 index 000000000..7c49eeea6 --- /dev/null +++ b/pkg/sentry/platform/interrupt/interrupt_test.go @@ -0,0 +1,99 @@ +// 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 interrupt + +import ( + "testing" +) + +type countingReceiver struct { + interrupts int +} + +// NotifyInterrupt implements Receiver.NotifyInterrupt. +func (r *countingReceiver) NotifyInterrupt() { + r.interrupts++ +} + +func TestSingleInterruptBeforeEnable(t *testing.T) { + var ( + f Forwarder + r countingReceiver + ) + f.NotifyInterrupt() + // The interrupt should cause the first Enable to fail. + if f.Enable(&r) { + f.Disable() + t.Fatalf("Enable: got true, wanted false") + } + // The failing Enable "acknowledges" the interrupt, allowing future Enables + // to succeed. + if !f.Enable(&r) { + t.Fatalf("Enable: got false, wanted true") + } + f.Disable() +} + +func TestMultipleInterruptsBeforeEnable(t *testing.T) { + var ( + f Forwarder + r countingReceiver + ) + f.NotifyInterrupt() + f.NotifyInterrupt() + // The interrupts should cause the first Enable to fail. + if f.Enable(&r) { + f.Disable() + t.Fatalf("Enable: got true, wanted false") + } + // Interrupts are deduplicated while the Forwarder is disabled, so the + // failing Enable "acknowledges" all interrupts, allowing future Enables to + // succeed. + if !f.Enable(&r) { + t.Fatalf("Enable: got false, wanted true") + } + f.Disable() +} + +func TestSingleInterruptAfterEnable(t *testing.T) { + var ( + f Forwarder + r countingReceiver + ) + if !f.Enable(&r) { + t.Fatalf("Enable: got false, wanted true") + } + defer f.Disable() + f.NotifyInterrupt() + if r.interrupts != 1 { + t.Errorf("interrupts: got %d, wanted 1", r.interrupts) + } +} + +func TestMultipleInterruptsAfterEnable(t *testing.T) { + var ( + f Forwarder + r countingReceiver + ) + if !f.Enable(&r) { + t.Fatalf("Enable: got false, wanted true") + } + defer f.Disable() + f.NotifyInterrupt() + f.NotifyInterrupt() + if r.interrupts != 2 { + t.Errorf("interrupts: got %d, wanted 2", r.interrupts) + } +} |