Merge branch 'google:master' into patch-1

This commit is contained in:
ignoramous
2022-06-17 04:56:56 +05:30
committed by GitHub
104 changed files with 5400 additions and 606 deletions
+16 -5
View File
@@ -13,8 +13,8 @@ function install_pkgs() {
done
}
install_pkgs make linux-libc-dev graphviz jq curl binutils gnupg gnupg-agent \
gcc pkg-config apt-transport-https ca-certificates software-properties-common \
rsync kmod
gcc pkg-config apt-transport-https ca-certificates \
software-properties-common rsync kmod systemd
# Install headers, only if available.
if test -n "$(apt-cache search --names-only "^linux-headers-$(uname -r)$")"; then
@@ -34,9 +34,20 @@ export TOTAL_PARTITIONS=${BUILDKITE_PARALLEL_JOB_COUNT:-1}
export RUNTIME="${BUILDKITE_BRANCH}-${BUILDKITE_BUILD_ID}"
# Ensure Docker has experimental enabled.
EXPERIMENTAL=$(sudo docker version --format='{{.Server.Experimental}}')
make sudo TARGETS=//runsc:runsc \
ARGS="install --experimental=true --runtime=${RUNTIME} -- ${RUNTIME_ARGS:-}"
if [[ -n "${STAGED_BINARIES:-}" ]]; then
# Used `runsc` from STAGED_BINARIES instead of building it from scratch.
tmpdir="$(mktemp -d)"
gsutil cat "$(STAGED_BINARIES)" | tar -C "$tmpdir" -zxvf - runsc
chmod +x "$tmpdir/runsc"
"$tmpdir/runsc" install --experimental=true --runtime="${RUNTIME}" \
-- "${RUNTIME_ARGS:-}"
rm -rf "$tmpdir"
else
make sudo TARGETS=//runsc:runsc \
ARGS="install --experimental=true --runtime=${RUNTIME} -- ${RUNTIME_ARGS:-}"
fi
# WARNING: We may be running in a container when this command executes.
# This only makes sense if Docker's `live-restore` feature is enabled.
sudo systemctl restart docker
# Helper for benchmarks, based on the branch.
+7 -7
View File
@@ -59,6 +59,13 @@ steps:
label: ":fire: Smoke race tests"
command: make smoke-race-tests
# Build everything.
- <<: *common
label: ":world_map: Build everything"
command: "make build OPTIONS=--build_tag_filters=-nogo TARGETS=//..."
agents:
arch: "amd64"
# Check that the Go branch builds. This is not technically required, as this build is maintained
# as a GitHub action in order to preserve this maintaince across forks. However, providing the
# action here may provide easier debuggability and diagnosis on failure.
@@ -364,13 +371,6 @@ steps:
arch: "amd64"
os: "ubuntu"
# Build everything.
- <<: *common
label: ":world_map: Build everything"
command: "make build OPTIONS=--build_tag_filters=-nogo TARGETS=//..."
agents:
arch: "amd64"
# Run basic benchmarks smoke tests (no upload).
- <<: *common
label: ":fire: Benchmarks smoke test"
+65
View File
@@ -0,0 +1,65 @@
load("//tools:defs.bzl", "go_library", "go_test")
load("//tools/go_generics:defs.bzl", "go_template_instance")
package(licenses = ["notice"])
go_template_instance(
name = "chunk_refs",
out = "chunk_refs.go",
package = "buffer",
prefix = "chunk",
template = "//pkg/refsvfs2:refs_template",
types = {
"T": "chunk",
},
)
go_template_instance(
name = "view_list",
out = "view_list.go",
package = "buffer",
prefix = "view",
template = "//pkg/ilist:generic_list",
types = {
"Element": "*View",
"Linker": "*View",
},
)
go_library(
name = "buffer",
srcs = [
"buffer.go",
"buffer_state.go",
"chunk.go",
"chunk_refs.go",
"view.go",
"view_list.go",
"view_unsafe.go",
],
visibility = ["//visibility:public"],
deps = [
"//pkg/atomicbitops",
"//pkg/bits",
"//pkg/context",
"//pkg/ilist",
"//pkg/log",
"//pkg/pool",
"//pkg/refsvfs2",
"//pkg/sync",
],
)
go_test(
name = "buffer_test",
size = "small",
srcs = [
"buffer_test.go",
"view_test.go",
],
library = ":buffer",
deps = [
"//pkg/state",
"@com_github_google_go_cmp//cmp:go_default_library",
],
)
File diff suppressed because it is too large Load Diff
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2022 The gVisor Authors.
//
// 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 buffer
// saveBuf is invoked by stateify.
func (b *Buffer) saveData() []byte {
return b.Flatten()
}
// loadBuf is invoked by stateify.
func (b *Buffer) loadData(data []byte) {
v := NewViewWithData(data)
b.Append(v)
}
File diff suppressed because it is too large Load Diff
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2022 The gVisor Authors.
//
// 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 buffer
import (
"fmt"
"gvisor.dev/gvisor/pkg/bits"
"gvisor.dev/gvisor/pkg/sync"
)
const (
// This is log2(baseChunkSize). This number is used to calculate which pool
// to use for a payload size by right shifting the payload size by this
// number and passing the result to MostSignificantOne64.
baseChunkSizeLog2 = 6
// This is the size of the buffers in the first pool. Each subsquent pool
// creates payloads 2^(pool index) times larger than the first pool's
// payloads.
baseChunkSize = 1 << baseChunkSizeLog2 // 64
// The largest payload size that we pool. Payloads larger than this will
// allocated from the heap and garbage collected as normal.
maxChunkSize = baseChunkSize << (numPools - 1) // 65536
// The number of chunk pools we have for use.
numPools = 11
)
// chunkPools is a collection of pools for payloads of different sizes. The
// size of the payloads doubles in each successive pool.
var chunkPools [numPools]sync.Pool
func init() {
for i := 0; i < numPools; i++ {
chunkSize := baseChunkSize * (1 << i)
chunkPools[i].New = func() interface{} {
return &chunk{
data: make([]byte, chunkSize),
}
}
}
}
// Precondition: 0 <= size <= maxChunkSize
func getChunkPool(size int) *sync.Pool {
idx := 0
if size > baseChunkSize {
idx = bits.MostSignificantOne64(uint64(size) >> baseChunkSizeLog2)
if size > 1<<(idx+baseChunkSizeLog2) {
idx++
}
}
if idx >= numPools {
panic(fmt.Sprintf("pool for chunk size %d does not exist", size))
}
return &chunkPools[idx]
}
// Chunk represents a slice of pooled memory.
type chunk struct {
chunkRefs
data []byte
}
func newChunk(size int) *chunk {
var c *chunk
if size > maxChunkSize {
c = &chunk{
data: make([]byte, size),
}
} else {
pool := getChunkPool(size)
c = pool.Get().(*chunk)
for i := range c.data {
c.data[i] = 0
}
}
c.InitRefs()
return c
}
func (c *chunk) destroy() {
if len(c.data) > maxChunkSize {
c.data = nil
return
}
pool := getChunkPool(len(c.data))
pool.Put(c)
}
func (c *chunk) DecRef() {
c.chunkRefs.DecRef(c.destroy)
}
func (c *chunk) Clone() *chunk {
cpy := newChunk(len(c.data))
copy(cpy.data, c.data)
return cpy
}
+232
View File
@@ -0,0 +1,232 @@
// Copyright 2022 The gVisor Authors.
//
// 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 buffer
import (
"fmt"
"io"
"gvisor.dev/gvisor/pkg/sync"
)
var viewPool = sync.Pool{
New: func() interface{} {
return &View{}
},
}
// View is a window into a shared chunk. Views are held by Buffers in
// viewLists to represent contiguous memory.
//
// A View must be created with NewView, NewViewWithData, or Clone. Owners are
// responsible for maintaining ownership over their views. When Views need to be
// shared or copied, the owner should create a new View with Clone. Clone must
// only ever be called on a owned View, not a borrowed one.
//
// Users are responsible for calling Release when finished with their View so
// that its resources can be returned to the pool.
//
// Users must not write directly to slices returned by AsSlice. Instead, they
// must use Write/WriteAt/CopyIn to modify the underlying View. This preserves
// the safety guarantees of copy-on-write.
type View struct {
sync.NoCopy
viewEntry
read int
write int
chunk *chunk
}
// NewView creates a new view with capacity at least as big as cap. It is
// analogous to make([]byte, 0, cap).
func NewView(cap int) *View {
c := newChunk(cap)
v := viewPool.Get().(*View)
*v = View{chunk: c}
return v
}
// NewViewSize creates a new view with capacity at least as big as size and
// length that is exactly size. It is analogous to make([]byte, size).
func NewViewSize(size int) *View {
v := NewView(size)
v.Grow(size)
return v
}
// NewViewWithData creates a new view and initializes it with data. This
// function should be used with caution to avoid unnecessary []byte allocations.
// When in doubt use NewWithView to maximize chunk reuse in production
// environments.
func NewViewWithData(data []byte) *View {
c := newChunk(len(data))
v := viewPool.Get().(*View)
*v = View{chunk: c}
v.Write(data)
return v
}
// Clone creates a shallow clone of v where the underlying chunk is shared.
//
// The caller must own the View to call Clone. It is not safe to call Clone
// on a borrowed or shared View because it can race with other View methods.
func (v *View) Clone() *View {
v.chunk.IncRef()
newV := viewPool.Get().(*View)
newV.chunk = v.chunk
newV.read = v.read
newV.write = v.write
return newV
}
// Release releases the chunk held by v and returns v to the pool.
func (v *View) Release() {
v.chunk.DecRef()
*v = View{}
viewPool.Put(v)
}
func (v *View) sharesChunk() bool {
return v.chunk.refCount.Load() > 1
}
// Full indicates the chunk is full.
//
// This indicates there is no capacity left to write.
func (v *View) Full() bool {
return v == nil || v.write == len(v.chunk.data)
}
// Capacity returns the total size of this view's chunk.
func (v *View) Capacity() int {
return len(v.chunk.data)
}
// Size returns the size of data written to the view.
func (v *View) Size() int {
return v.write - v.read
}
// TrimFront advances the read index by the given amount.
func (v *View) TrimFront(n int) {
v.read += n
}
// AsSlice returns a slice of the data written to this view.
func (v *View) AsSlice() []byte {
if v == nil {
return nil
}
return v.chunk.data[v.read:v.write]
}
// AvailableSize returns the number of bytes available for writing.
func (v *View) AvailableSize() int {
return len(v.chunk.data) - v.write
}
// Read reads v's data into p.
//
// Implements the io.Reader interface.
func (v *View) Read(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
if v.Size() == 0 {
return 0, io.EOF
}
n := copy(p, v.AsSlice())
v.TrimFront(n)
return n, nil
}
// ReadAt reads data to the p starting at offset.
//
// Implements the io.ReaderAt interface.
func (v *View) ReadAt(p []byte, off int) (int, error) {
if off < 0 || off > v.Size() {
return 0, fmt.Errorf("ReadAt(): offset out of bounds: want 0 < off < %d, got off=%d", v.Size(), off)
}
n := copy(p, v.AsSlice()[off:])
return n, nil
}
// Write writes data to the view's chunk starting at the v.write index. If the
// view's chunk has a reference count greater than 1, the chunk is copied first
// and then written to.
//
// Implements the io.Writer interface.
func (v *View) Write(p []byte) (int, error) {
if v.sharesChunk() {
defer v.chunk.DecRef()
v.chunk = v.chunk.Clone()
}
n := copy(v.chunk.data[v.write:], p)
v.write += n
if n < len(p) {
return n, fmt.Errorf("could not finish write: want len(p) <= v.AvailableSize(), got len(p)=%d, v.AvailableSize()=%d", len(p), v.AvailableSize())
}
return n, nil
}
// WriteAt writes data to the views's chunk starting at start. If the
// view's chunk has a reference count greater than 1, the chunk is copied first
// and then written to.
//
// Implements the io.WriterAt interface.
func (v *View) WriteAt(p []byte, off int) (int, error) {
if off < 0 || off > v.Size() {
return 0, fmt.Errorf("write offset out of bounds: want 0 < off < %d, got off=%d", v.Size(), off)
}
if v.sharesChunk() {
defer v.chunk.DecRef()
v.chunk = v.chunk.Clone()
}
n := copy(v.AsSlice()[off:], p)
if n < len(p) {
return n, fmt.Errorf("could not finish write: want off + len(p) < v.Capacity(), got off=%d, len(p)=%d ,v.Size() = %d", off, len(p), v.Size())
}
return n, nil
}
// Grow advances the write index by the given amount.
func (v *View) Grow(n int) {
if n+v.write > v.Capacity() {
panic("cannot grow view past capacity")
}
v.write += n
}
// CapLength caps the length of the view's read slice to n. If n > v.Size(),
// the function is a no-op.
func (v *View) CapLength(n int) {
if n < 0 {
panic("n must be >= 0")
}
if n > v.Size() {
n = v.Size()
}
v.write = v.read + n
}
func (v *View) availableSlice() []byte {
if v.sharesChunk() {
defer v.chunk.DecRef()
c := v.chunk.Clone()
v.chunk = c
}
return v.chunk.data[v.write:]
}
+174
View File
@@ -0,0 +1,174 @@
// Copyright 2022 The gVisor Authors.
//
// 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 buffer
import (
"math/rand"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestNewView(t *testing.T) {
for sz := baseChunkSize; sz < maxChunkSize; sz <<= 1 {
v := NewView(sz - 1)
defer v.Release()
if v.Capacity() != sz {
t.Errorf("v.Capacity() = %d, want %d", v.Capacity(), sz)
}
if v.AvailableSize() != sz {
t.Errorf("v.WriteSize() = %d, want %d", v.AvailableSize(), sz)
}
if v.Size() != 0 {
t.Errorf("v.ReadSize() = %d, want %d", v.Size(), 0)
}
v1 := NewView(sz)
defer v1.Release()
if v1.Capacity() != sz {
t.Errorf("v.Capacity() = %d, want %d", v.Capacity(), sz)
}
if v1.AvailableSize() != sz {
t.Errorf("v.WriteSize() = %d, want %d", v.AvailableSize(), sz)
}
if v1.Size() != 0 {
t.Errorf("v.ReadSize() = %d, want %d", v.Size(), 0)
}
}
// Allocating from heap should produce a chunk with the exact size requested
// instead of a chunk where the size is contingent on the pool it came from.
viewSize := maxChunkSize + 1
v := NewView(viewSize)
defer v.Release()
if v.Capacity() != viewSize {
t.Errorf("v.Capacity() = %d, want %d", v.Capacity(), viewSize)
}
}
func TestClone(t *testing.T) {
orig := NewView(100)
clone := orig.Clone()
if orig.chunk != clone.chunk {
t.Errorf("orig.Clone().chunk = %p, want %p", clone.chunk, orig.chunk)
}
if orig.chunk.refCount.Load() != 2 {
t.Errorf("got orig.chunk.chunkRefs.Load() = %d, want 2", orig.chunk.refCount.Load())
}
orig.Release()
if clone.chunk.refCount.Load() != 1 {
t.Errorf("got clone.chunk.chunkRefs.Load() = %d, want 1", clone.chunk.refCount.Load())
}
clone.Release()
}
func TestWrite(t *testing.T) {
for _, tc := range []struct {
name string
view *View
initData []byte
writeSize int
}{
{
name: "empty view",
view: NewView(100),
writeSize: 50,
},
{
name: "full view",
view: NewView(100),
initData: make([]byte, 100),
writeSize: 50,
},
{
name: "full write to partially full view",
view: NewView(100),
initData: make([]byte, 20),
writeSize: 50,
},
{
name: "partial write to partially full view",
view: NewView(100),
initData: make([]byte, 80),
writeSize: 50,
},
} {
t.Run(tc.name, func(t *testing.T) {
tc.view.Write(tc.initData)
defer tc.view.Release()
origWriteSize := tc.view.AvailableSize()
var orig []byte
orig = append(orig, tc.view.AsSlice()...)
toWrite := make([]byte, tc.writeSize)
rand.Read(toWrite)
n, _ := tc.view.Write(toWrite)
if n > origWriteSize {
t.Errorf("got tc.view.Write() = %d, want <=%d", n, origWriteSize)
}
if tc.writeSize > origWriteSize {
toWrite = toWrite[:origWriteSize]
}
if tc.view.AvailableSize() != tc.view.Capacity()-(len(toWrite)+len(orig)) {
t.Errorf("got tc.view.WriteSize() = %d, want %d", tc.view.AvailableSize(), tc.view.Capacity()-(len(toWrite)+len(orig)))
}
if !cmp.Equal(tc.view.AsSlice(), append(orig, toWrite...)) {
t.Errorf("got tc.view.ReadSlice() = %d, want %d", tc.view.AsSlice(), toWrite)
}
})
}
}
func TestWriteToCloned(t *testing.T) {
orig := NewView(100)
toWrite := make([]byte, 20)
rand.Read(toWrite)
orig.Write(toWrite)
clone := orig.Clone()
clone.Write(toWrite)
if !cmp.Equal(orig.AsSlice(), toWrite) {
t.Errorf("got orig.ReadSlice() = %v, want %v", orig.AsSlice(), toWrite)
}
toWrite = append(toWrite, toWrite...)
if !cmp.Equal(clone.AsSlice(), toWrite) {
t.Errorf("got clone.ReadSlice() = %v, want %v", clone.AsSlice(), toWrite)
}
}
func TestWriteAt(t *testing.T) {
size := 10
off := 5
v := NewViewSize(size)
p := make([]byte, 20)
rand.Read(p)
orig := v.Clone()
if n, _ := v.WriteAt(p, off); n != size-off {
t.Errorf("got v.CopyIn()= %v, want %v", n, size-off)
}
if !cmp.Equal(v.AsSlice()[off:], p[:size-off]) {
t.Errorf("got v.AsSlice()[off:] = %v, want %v", v.AsSlice()[off:], p[off:size])
}
if !cmp.Equal(v.AsSlice()[:off], orig.AsSlice()[:off]) {
t.Errorf("got v.AsSlice()[:off] = %v, want %v", v.AsSlice()[:off], orig.AsSlice()[:off])
}
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2020 The gVisor Authors.
//
// 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 buffer
import (
"reflect"
"unsafe"
)
// minBatch is the smallest Read or Write operation that the
// WriteFromReader and ReadToWriter functions will use.
//
// This is defined as the size of a native pointer.
const minBatch = int(unsafe.Sizeof(uintptr(0)))
// BasePtr returns a pointer to the view's chunk.
func (v *View) BasePtr() *byte {
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&v.chunk.data))
return (*byte)(unsafe.Pointer(hdr.Data))
}
+2
View File
@@ -9,6 +9,7 @@ go_library(
"json.go",
"json_k8s.go",
"log.go",
"rate_limited.go",
],
marshal = False,
stateify = False,
@@ -18,6 +19,7 @@ go_library(
deps = [
"//pkg/linewriter",
"//pkg/sync",
"@org_golang_x_time//rate:go_default_library",
],
)
+63
View File
@@ -0,0 +1,63 @@
// Copyright 2022 The gVisor Authors.
//
// 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 log
import (
"time"
"golang.org/x/time/rate"
)
type rateLimitedLogger struct {
logger Logger
limit *rate.Limiter
}
func (rl *rateLimitedLogger) Debugf(format string, v ...interface{}) {
if rl.limit.Allow() {
rl.logger.Debugf(format, v...)
}
}
func (rl *rateLimitedLogger) Infof(format string, v ...interface{}) {
if rl.limit.Allow() {
rl.logger.Infof(format, v...)
}
}
func (rl *rateLimitedLogger) Warningf(format string, v ...interface{}) {
if rl.limit.Allow() {
rl.logger.Warningf(format, v...)
}
}
func (rl *rateLimitedLogger) IsLogging(level Level) bool {
return rl.logger.IsLogging(level)
}
// BasicRateLimitedLogger returns a Logger that logs to the global logger no
// more than once per the provided duration.
func BasicRateLimitedLogger(every time.Duration) Logger {
return RateLimitedLogger(Log(), every)
}
// RateLimitedLogger returns a Logger that logs to the provided logger no more
// than once per the provided duration.
func RateLimitedLogger(logger Logger, every time.Duration) Logger {
return &rateLimitedLogger{
logger: logger,
limit: rate.NewLimiter(rate.Every(every), 1),
}
}
+32 -10
View File
@@ -26,6 +26,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/limits"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/urpc"
)
@@ -34,13 +35,19 @@ type Lifecycle struct {
// Kernel is the kernel where the tasks belong to.
Kernel *kernel.Kernel
// Sends a message to the sentry that the task has been started.
// StartedCh is the channel used to send a message to the sentry that
// all the containers in the sandbox have been started.
StartedCh chan struct{}
// TODO(b/202052732): Root mount namespace. When running multiple
// containers, create the mount namespace using the mount spec in
// the StartContainerArgs.
MountNamespaceVFS2 *vfs.MountNamespace
// mu protects the fields below.
mu sync.RWMutex
// containersStarted is the number of containers started in the sandbox.
containersStarted int32
// MountNamespacesMap is a map of container id/names and the mount
// namespaces.
MountNamespacesMap map[string]*vfs.MountNamespace
}
// StartContainerArgs is the set of arguments to start a container.
@@ -127,12 +134,22 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
AbstractSocketNamespace: l.Kernel.RootAbstractSocketNamespace(),
ContainerID: args.ContainerID,
PIDNamespace: l.Kernel.RootPIDNamespace(),
MountNamespaceVFS2: l.MountNamespaceVFS2,
}
ctx := initArgs.NewContext(l.Kernel)
defer fdTable.DecRef(ctx)
// VFS2 is supported in multi-container mode by default.
l.mu.RLock()
mntns, ok := l.MountNamespacesMap[initArgs.ContainerID]
if !ok {
l.mu.RUnlock()
return fmt.Errorf("mount namespace is nil for %s", initArgs.ContainerID)
}
initArgs.MountNamespaceVFS2 = mntns
l.mu.RUnlock()
initArgs.MountNamespaceVFS2.IncRef()
resolved, err := user.ResolveExecutablePath(ctx, &initArgs)
if err != nil {
return err
@@ -154,12 +171,17 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
return err
}
l.mu.Lock()
numContainers := int32(len(l.MountNamespacesMap))
// Start the newly created process.
l.Kernel.StartProcess(tg)
log.Infof("Started the new container")
l.StartedCh <- struct{}{}
log.Infof("Started the new container %v ", l.containersStarted)
l.containersStarted++
if numContainers == l.containersStarted {
l.StartedCh <- struct{}{}
}
l.mu.Unlock()
return nil
}
+4 -3
View File
@@ -268,9 +268,6 @@ func (d *pidsMaxData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *pidsMaxData) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
d.c.mu.Lock()
defer d.c.mu.Unlock()
t := kernel.TaskFromContext(ctx)
buf := t.CopyScratchBuffer(hostarch.PageSize)
ncpy, err := src.CopyIn(ctx, buf)
@@ -278,6 +275,8 @@ func (d *pidsMaxData) Write(ctx context.Context, _ *vfs.FileDescription, src use
return 0, err
}
if strings.TrimSpace(string(buf)) == "max" {
d.c.mu.Lock()
defer d.c.mu.Unlock()
d.c.max = pidLimitUnlimited
return int64(ncpy), nil
}
@@ -290,6 +289,8 @@ func (d *pidsMaxData) Write(ctx context.Context, _ *vfs.FileDescription, src use
return 0, linuxerr.EINVAL
}
d.c.mu.Lock()
defer d.c.mu.Unlock()
d.c.max = val
return int64(n), nil
}
+11
View File
@@ -146,6 +146,13 @@ func (d *dentry) createSyntheticChildLocked(opts *createSyntheticOpts) {
d.syntheticChildren++
}
// Preconditions:
// - d.dirMu must be locked.
func (d *dentry) clearDirentsLocked() {
d.dirents = nil
d.childrenSet = nil
}
// +stateify savable
type directoryFD struct {
fileDescription
@@ -346,6 +353,10 @@ func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
// Cache dirents for future directoryFDs if permitted.
if d.cachedMetadataAuthoritative() {
d.dirents = dirents
d.childrenSet = make(map[string]struct{}, len(dirents))
for _, dirent := range d.dirents {
d.childrenSet[dirent.Name] = struct{}{}
}
}
return dirents, nil
}
+31 -6
View File
@@ -276,6 +276,13 @@ func (fs *filesystem) getChildAndWalkPathLocked(ctx context.Context, parent *den
return child, nil
}
if parent.childrenSet != nil {
// Is the first child even there? Don't make RPC if not.
if _, ok := parent.childrenSet[first]; !ok {
return nil, linuxerr.ENOENT
}
}
// Walk as much of the path as possible in 1 RPC.
names := []string{first}
for pit = pit.Next(); pit.Ok(); pit = pit.Next() {
@@ -369,6 +376,13 @@ func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name s
return child, nil
}
if parent.childrenSet != nil {
// Is the child even there? Don't make RPC if not.
if _, ok := parent.childrenSet[name]; !ok {
return nil, linuxerr.ENOENT
}
}
var child *dentry
if fs.opts.lisaEnabled {
childInode, err := parent.controlFDLisa.Walk(ctx, name)
@@ -512,6 +526,11 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
if child, ok := parent.children[name]; ok && child != nil {
return linuxerr.EEXIST
}
if parent.childrenSet != nil {
if _, ok := parent.childrenSet[name]; ok {
return linuxerr.EEXIST
}
}
checkExistence := func() error {
if child, err := fs.getChildLocked(ctx, parent, name, &ds); err != nil && !linuxerr.Equals(linuxerr.ENOENT, err) {
return err
@@ -549,7 +568,7 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
return err
}
parent.touchCMtime()
parent.dirents = nil
parent.clearDirentsLocked()
ev := linux.IN_CREATE
if dir {
ev |= linux.IN_ISDIR
@@ -569,7 +588,7 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
delete(parent.children, name)
}
parent.touchCMtime()
parent.dirents = nil
parent.clearDirentsLocked()
}
ev := linux.IN_CREATE
if dir {
@@ -622,6 +641,12 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
parent.dirMu.Lock()
defer parent.dirMu.Unlock()
if parent.childrenSet != nil {
if _, ok := parent.childrenSet[name]; !ok {
return linuxerr.ENOENT
}
}
// Load child if sticky bit is set because we need to determine whether
// deletion is allowed.
var child *dentry
@@ -745,7 +770,7 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
}
parent.cacheNegativeLookupLocked(name)
if parent.cachedMetadataAuthoritative() {
parent.dirents = nil
parent.clearDirentsLocked()
parent.touchCMtime()
if dir {
parent.decLinks()
@@ -1391,7 +1416,7 @@ func (d *dentry) createAndOpenChildLocked(ctx context.Context, rp *vfs.Resolving
appendNewChildDentry(ds, d, child)
if d.cachedMetadataAuthoritative() {
d.touchCMtime()
d.dirents = nil
d.clearDirentsLocked()
}
// Finally, construct a file description representing the created file.
@@ -1623,14 +1648,14 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
renamed.touchCtime()
}
if oldParent.cachedMetadataAuthoritative() {
oldParent.dirents = nil
oldParent.clearDirentsLocked()
oldParent.touchCMtime()
if renamed.isDir() {
oldParent.decLinks()
}
}
if newParent.cachedMetadataAuthoritative() {
newParent.dirents = nil
newParent.clearDirentsLocked()
newParent.touchCMtime()
if renamed.isDir() && (replaced == nil || !replaced.isDir()) {
// Increase the link count if we did not replace another directory.
+5 -2
View File
@@ -850,8 +850,11 @@ type dentry struct {
// If this dentry represents a directory,
// dentry.cachedMetadataAuthoritative() == true, and dirents is not nil, it
// is a cache of all entries in the directory, in the order they were
// returned by the server. dirents is protected by dirMu.
dirents []vfs.Dirent
// returned by the server. childrenSet just stores the `Name` field of all
// dirents in a set for fast query. dirents and childrenSet are protected by
// dirMu and share the same lifecycle.
dirents []vfs.Dirent
childrenSet map[string]struct{}
// Cached metadata; protected by metadataMu.
// To access:
+1 -1
View File
@@ -318,7 +318,7 @@ func (fs *filesystem) revalidateHelper(ctx context.Context, vfsObj *vfs.VirtualF
*ds = appendDentry(*ds, d)
d.parent.syntheticChildren--
d.parent.dirents = nil
d.parent.clearDirentsLocked()
}
// Since the dirMu was released and reacquired, re-check that the
+66
View File
@@ -1,8 +1,65 @@
load("//tools:defs.bzl", "go_library")
load("//tools/go_generics:defs.bzl", "go_template_instance")
load("//pkg/sync/locking:locking.bzl", "declare_mutex", "declare_rwmutex")
licenses(["notice"])
declare_mutex(
name = "dir_mutex",
out = "dir_mutex.go",
package = "overlay",
prefix = "dir",
)
declare_mutex(
name = "dev_mutex",
out = "dev_mutex.go",
package = "overlay",
prefix = "dev",
)
declare_mutex(
name = "dir_cache_mutex",
out = "dir_cache_mutex.go",
package = "overlay",
prefix = "dirInoCache",
)
declare_mutex(
name = "reg_file_fd_mutex",
out = "req_file_fd_mutex.go",
package = "overlay",
prefix = "regularFileFD",
)
declare_mutex(
name = "dir_fd_mutex",
out = "dir_fd_mutex.go",
package = "overlay",
prefix = "directoryFD",
)
declare_rwmutex(
name = "rename_rwmutex",
out = "rename_rwmutex.go",
package = "overlay",
prefix = "rename",
)
declare_rwmutex(
name = "data_rwmutex",
out = "data_rwmutex.go",
package = "overlay",
prefix = "data",
)
declare_mutex(
name = "maps_mutex",
out = "maps_mutex.go",
package = "overlay",
prefix = "maps",
)
go_template_instance(
name = "fstree",
out = "fstree.go",
@@ -18,11 +75,19 @@ go_library(
name = "overlay",
srcs = [
"copy_up.go",
"data_rwmutex.go",
"dev_mutex.go",
"dir_cache_mutex",
"dir_fd_mutex.go",
"dir_mutex.go",
"directory.go",
"filesystem.go",
"fstree.go",
"maps_mutex.go",
"overlay.go",
"regular_file.go",
"rename_rwmutex.go",
"req_file_fd_mutex.go",
"save_restore.go",
],
visibility = ["//pkg/sentry:internal"],
@@ -43,6 +108,7 @@ go_library(
"//pkg/sentry/socket/unix/transport",
"//pkg/sentry/vfs",
"//pkg/sync",
"//pkg/sync/locking",
"//pkg/usermem",
"//pkg/waiter",
],
+1 -2
View File
@@ -20,7 +20,6 @@ import (
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
)
func (d *dentry) isDir() bool {
@@ -104,7 +103,7 @@ type directoryFD struct {
vfs.DirectoryFileDescriptionDefaultImpl
vfs.DentryMetadataFileDescriptionImpl
mu sync.Mutex `state:"nosave"`
mu directoryFDMutex `state:"nosave"`
off int64
dirents []vfs.Dirent
}

Some files were not shown because too many files have changed in this diff Show More