mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
committed by
gVisor bot
parent
f375784d83
commit
5c95e1d39c
@@ -394,3 +394,9 @@ const (
|
||||
FALLOC_FL_INSERT_RANGE = 0x20
|
||||
FALLOC_FL_UNSHARE_RANGE = 0x40
|
||||
)
|
||||
|
||||
// Constants related to close_range(2). Source: /include/uapi/linux/close_range.h
|
||||
const (
|
||||
CLOSE_RANGE_UNSHARE = uint32(1 << 1)
|
||||
CLOSE_RANGE_CLOEXEC = uint32(1 << 2)
|
||||
)
|
||||
|
||||
+32
-5
@@ -16,10 +16,15 @@
|
||||
package bitmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// MaxBitEntryLimit defines the upper limit on how many bit entries are supported by this Bitmap
|
||||
// implementation.
|
||||
const MaxBitEntryLimit uint32 = math.MaxInt32
|
||||
|
||||
// Bitmap implements an efficient bitmap.
|
||||
//
|
||||
// +stateify savable
|
||||
@@ -53,21 +58,21 @@ func (b *Bitmap) Minimum() uint32 {
|
||||
return uint32(r + i*64)
|
||||
}
|
||||
}
|
||||
return math.MaxInt32
|
||||
return MaxBitEntryLimit
|
||||
}
|
||||
|
||||
// FirstZero returns the first unset bit from the range [start, ).
|
||||
func (b *Bitmap) FirstZero(start uint32) uint32 {
|
||||
func (b *Bitmap) FirstZero(start uint32) (bit uint32, err error) {
|
||||
i, nbit := int(start/64), start%64
|
||||
n := len(b.bitBlock)
|
||||
if i >= n {
|
||||
return math.MaxInt32
|
||||
return MaxBitEntryLimit, fmt.Errorf("given start of range exceeds bitmap size")
|
||||
}
|
||||
w := b.bitBlock[i] | ((1 << nbit) - 1)
|
||||
for {
|
||||
if w != ^uint64(0) {
|
||||
r := bits.TrailingZeros64(^w)
|
||||
return uint32(r + i*64)
|
||||
return uint32(r + i*64), nil
|
||||
}
|
||||
i++
|
||||
if i == n {
|
||||
@@ -75,7 +80,29 @@ func (b *Bitmap) FirstZero(start uint32) uint32 {
|
||||
}
|
||||
w = b.bitBlock[i]
|
||||
}
|
||||
return math.MaxInt32
|
||||
return MaxBitEntryLimit, fmt.Errorf("bitmap has no unset bits")
|
||||
}
|
||||
|
||||
// FirstOne returns the first set bit from the range [start, )
|
||||
func (b *Bitmap) FirstOne(start uint32) (bit uint32, err error) {
|
||||
i, nbit := int(start/64), start%64
|
||||
n := len(b.bitBlock)
|
||||
if i >= n {
|
||||
return MaxBitEntryLimit, fmt.Errorf("given start of range exceeds bitmap size")
|
||||
}
|
||||
w := b.bitBlock[i] & (math.MaxUint64 << nbit)
|
||||
for {
|
||||
if w != uint64(0) {
|
||||
r := bits.TrailingZeros64(w)
|
||||
return uint32(r + i*64), nil
|
||||
}
|
||||
i++
|
||||
if i == n {
|
||||
break
|
||||
}
|
||||
w = b.bitBlock[i]
|
||||
}
|
||||
return MaxBitEntryLimit, fmt.Errorf("bitmap has no set bits")
|
||||
}
|
||||
|
||||
// Maximum return the largest value in the Bitmap.
|
||||
|
||||
@@ -294,13 +294,49 @@ func TestBitmapNumOnes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type BitmapGetFirstTestcase struct {
|
||||
queryValue uint32
|
||||
expectedValue uint32
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
func TestFirstZero(t *testing.T) {
|
||||
bitmap := New(uint32(1000))
|
||||
bitmap.FlipRange(200, 400)
|
||||
for i, j := range map[uint32]uint32{0: 0, 201: 400, 200: 400, 199: 199, 400: 400, 10000: math.MaxInt32} {
|
||||
v := bitmap.FirstZero(i)
|
||||
if v != j {
|
||||
t.Errorf("Minimum() returns: %v, wanted: %v", v, j)
|
||||
testcases := []BitmapGetFirstTestcase{
|
||||
{0, 0, false},
|
||||
{201, 400, false},
|
||||
{200, 400, false},
|
||||
{199, 199, false},
|
||||
{400, 400, false},
|
||||
{10000, math.MaxInt32, true},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
v, err := bitmap.FirstZero(tc.queryValue)
|
||||
if v != tc.expectedValue && (err != nil) == tc.wantErr {
|
||||
t.Errorf("FirstZero() returns: %v, wanted: %v", v, tc.expectedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstOne(t *testing.T) {
|
||||
bitmap := New(uint32(1000))
|
||||
bitmap.FlipRange(200, 400)
|
||||
bitmap.FlipRange(700, 701)
|
||||
testcases := []BitmapGetFirstTestcase{
|
||||
{0, 200, false},
|
||||
{199, 200, false},
|
||||
{200, 200, false},
|
||||
{399, 399, false},
|
||||
{400, 700, false},
|
||||
{700, 700, false},
|
||||
{701, math.MaxInt32, true},
|
||||
{10000, math.MaxInt32, true},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
v, err := bitmap.FirstOne(tc.queryValue)
|
||||
if v != tc.expectedValue && (err != nil) == tc.wantErr {
|
||||
t.Errorf("FirstOne() returns: %v, wanted: %v", v, tc.expectedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+129
-25
@@ -73,6 +73,9 @@ type descriptor struct {
|
||||
flags FDFlags
|
||||
}
|
||||
|
||||
// MaxFdLimit defines the upper limit on the integer value of file descriptors.
|
||||
const MaxFdLimit int32 = int32(bitmap.MaxBitEntryLimit)
|
||||
|
||||
// FDTable is used to manage File references and flags.
|
||||
//
|
||||
// +stateify savable
|
||||
@@ -184,16 +187,19 @@ func (f *FDTable) DecRef(ctx context.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// forEach iterates over all non-nil files in sorted order.
|
||||
// forEachUpTo iterates over all non-nil files upto maxFds (non-inclusive) in sorted order.
|
||||
//
|
||||
// It is the caller's responsibility to acquire an appropriate lock.
|
||||
func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags)) {
|
||||
func (f *FDTable) forEachUpTo(ctx context.Context, maxFds int32, fn func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags)) {
|
||||
// retries tracks the number of failed TryIncRef attempts for the same FD.
|
||||
retries := 0
|
||||
fds := f.fdBitmap.ToSlice()
|
||||
// Iterate through the fdBitmap.
|
||||
for _, ufd := range fds {
|
||||
fd := int32(ufd)
|
||||
if fd >= maxFds {
|
||||
break
|
||||
}
|
||||
file, fileVFS2, flags, ok := f.getAll(fd)
|
||||
if !ok {
|
||||
break
|
||||
@@ -224,6 +230,13 @@ func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *fs.File,
|
||||
}
|
||||
}
|
||||
|
||||
// forEach iterates over all non-nil files upto maxFd in sorted order.
|
||||
//
|
||||
// It is the caller's responsibility to acquire an appropriate lock.
|
||||
func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags)) {
|
||||
f.forEachUpTo(ctx, MaxFdLimit, fn)
|
||||
}
|
||||
|
||||
// String is a stringer for FDTable.
|
||||
func (f *FDTable) String() string {
|
||||
var buf strings.Builder
|
||||
@@ -263,7 +276,7 @@ func (f *FDTable) NewFDs(ctx context.Context, minFD int32, files []*fs.File, fla
|
||||
}
|
||||
|
||||
// Default limit.
|
||||
end := int32(math.MaxInt32)
|
||||
end := MaxFdLimit
|
||||
|
||||
// Ensure we don't get past the provided limit.
|
||||
if limitSet := limits.FromContext(ctx); limitSet != nil {
|
||||
@@ -294,8 +307,8 @@ func (f *FDTable) NewFDs(ctx context.Context, minFD int32, files []*fs.File, fla
|
||||
for len(fds) < len(files) {
|
||||
// Try to use free bit in fdBitmap.
|
||||
// If all bits in fdBitmap are used, expand fd to the max.
|
||||
fd := f.fdBitmap.FirstZero(uint32(minFD))
|
||||
if fd == math.MaxInt32 {
|
||||
fd, err := f.fdBitmap.FirstZero(uint32(minFD))
|
||||
if err != nil {
|
||||
fd = uint32(max)
|
||||
max++
|
||||
}
|
||||
@@ -340,7 +353,7 @@ func (f *FDTable) NewFDsVFS2(ctx context.Context, minFD int32, files []*vfs.File
|
||||
}
|
||||
|
||||
// Default limit.
|
||||
end := int32(math.MaxInt32)
|
||||
end := MaxFdLimit
|
||||
|
||||
// Ensure we don't get past the provided limit.
|
||||
if limitSet := limits.FromContext(ctx); limitSet != nil {
|
||||
@@ -371,8 +384,8 @@ func (f *FDTable) NewFDsVFS2(ctx context.Context, minFD int32, files []*vfs.File
|
||||
for len(fds) < len(files) {
|
||||
// Try to use free bit in fdBitmap.
|
||||
// If all bits in fdBitmap are used, expand fd to the max.
|
||||
fd := f.fdBitmap.FirstZero(uint32(minFD))
|
||||
if fd == math.MaxInt32 {
|
||||
fd, err := f.fdBitmap.FirstZero(uint32(minFD))
|
||||
if err != nil {
|
||||
fd = uint32(max)
|
||||
max++
|
||||
}
|
||||
@@ -494,6 +507,25 @@ func (f *FDTable) SetFlags(ctx context.Context, fd int32, flags FDFlags) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFlagsForRange sets the flags for the given range of file descriptors
|
||||
// (inclusive: [startFd, endFd]).
|
||||
func (f *FDTable) SetFlagsForRange(ctx context.Context, startFd int32, endFd int32, flags FDFlags) error {
|
||||
if startFd < 0 || startFd > endFd {
|
||||
return unix.EBADF
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
for fd, err := f.fdBitmap.FirstOne(uint32(startFd)); err == nil && fd <= uint32(endFd); fd, err = f.fdBitmap.FirstOne(fd + 1) {
|
||||
fdI32 := int32(fd)
|
||||
file, _, _ := f.get(fdI32)
|
||||
f.set(ctx, fdI32, file, flags)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFlagsVFS2 sets the flags for the given file descriptor.
|
||||
//
|
||||
// True is returned iff flags were changed.
|
||||
@@ -517,6 +549,25 @@ func (f *FDTable) SetFlagsVFS2(ctx context.Context, fd int32, flags FDFlags) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFlagsForRangeVFS2 sets the flags for the given range of file descriptors
|
||||
// (inclusive: [startFd, endFd]).
|
||||
func (f *FDTable) SetFlagsForRangeVFS2(ctx context.Context, startFd int32, endFd int32, flags FDFlags) error {
|
||||
if startFd < 0 || startFd > endFd {
|
||||
return unix.EBADF
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
for fd, err := f.fdBitmap.FirstOne(uint32(startFd)); err == nil && fd <= uint32(endFd); fd, err = f.fdBitmap.FirstOne(fd + 1) {
|
||||
fdI32 := int32(fd)
|
||||
file, _, _ := f.getVFS2(fdI32)
|
||||
f.setVFS2(ctx, fdI32, file, flags)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns a reference to the file and the flags for the FD or nil if no
|
||||
// file is defined for the given fd.
|
||||
//
|
||||
@@ -581,12 +632,12 @@ func (f *FDTable) GetFDs(ctx context.Context) []int32 {
|
||||
return fds
|
||||
}
|
||||
|
||||
// Fork returns an independent FDTable.
|
||||
func (f *FDTable) Fork(ctx context.Context) *FDTable {
|
||||
// Fork returns an independent FDTable, cloning all FDs up to maxFds (non-inclusive).
|
||||
func (f *FDTable) Fork(ctx context.Context, maxFds int32) *FDTable {
|
||||
clone := f.k.NewFDTable()
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.forEach(ctx, func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) {
|
||||
f.forEachUpTo(ctx, maxFds, func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) {
|
||||
// The set function here will acquire an appropriate table
|
||||
// reference for the clone. We don't need anything else.
|
||||
if df, dfVFS2 := clone.setAll(ctx, fd, file, fileVFS2, flags); df != nil || dfVFS2 != nil {
|
||||
@@ -597,9 +648,10 @@ func (f *FDTable) Fork(ctx context.Context) *FDTable {
|
||||
return clone
|
||||
}
|
||||
|
||||
// Remove removes an FD from and returns a non-file iff successful.
|
||||
// Remove removes an FD from and returns a tuple where one of the files is non-nil
|
||||
// iff successful.
|
||||
//
|
||||
// N.B. Callers are required to use DecRef when they are done.
|
||||
// N.B. Callers are required to use DecRef on the returned file when they are done.
|
||||
func (f *FDTable) Remove(ctx context.Context, fd int32) (*fs.File, *vfs.FileDescription) {
|
||||
if fd < 0 {
|
||||
return nil, nil
|
||||
@@ -607,30 +659,30 @@ func (f *FDTable) Remove(ctx context.Context, fd int32) (*fs.File, *vfs.FileDesc
|
||||
|
||||
f.mu.Lock()
|
||||
|
||||
orig, orig2, _, _ := f.getAll(fd)
|
||||
file, fileVFS2, _, _ := f.getAll(fd)
|
||||
|
||||
// Add reference for caller.
|
||||
switch {
|
||||
case orig != nil:
|
||||
orig.IncRef()
|
||||
case orig2 != nil:
|
||||
orig2.IncRef()
|
||||
case file != nil:
|
||||
file.IncRef()
|
||||
case fileVFS2 != nil:
|
||||
fileVFS2.IncRef()
|
||||
}
|
||||
|
||||
if orig != nil || orig2 != nil {
|
||||
orig, orig2 = f.setAll(ctx, fd, nil, nil, FDFlags{}) // Zap entry.
|
||||
if file != nil || fileVFS2 != nil {
|
||||
file, fileVFS2 = f.setAll(ctx, fd, nil, nil, FDFlags{}) // Zap entry.
|
||||
f.fdBitmap.Remove(uint32(fd))
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
if orig != nil {
|
||||
f.drop(ctx, orig)
|
||||
if file != nil {
|
||||
f.drop(ctx, file)
|
||||
}
|
||||
if orig2 != nil {
|
||||
f.dropVFS2(ctx, orig2)
|
||||
if fileVFS2 != nil {
|
||||
f.dropVFS2(ctx, fileVFS2)
|
||||
}
|
||||
|
||||
return orig, orig2
|
||||
return file, fileVFS2
|
||||
}
|
||||
|
||||
// RemoveIf removes all FDs where cond is true.
|
||||
@@ -662,3 +714,55 @@ func (f *FDTable) RemoveIf(ctx context.Context, cond func(*fs.File, *vfs.FileDes
|
||||
f.dropVFS2(ctx, file)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveNextInRange removes the next FD that falls within the given range,
|
||||
// and returns a tuple where one of the files is non-nil iff successful.
|
||||
//
|
||||
// N.B. Callers are required to use DecRef on the returned file when they are done.
|
||||
func (f *FDTable) RemoveNextInRange(ctx context.Context, startFd int32, endFd int32) (int32, *fs.File, *vfs.FileDescription) {
|
||||
if startFd < 0 || startFd > endFd {
|
||||
return MaxFdLimit, nil, nil
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
|
||||
fdUint, err := f.fdBitmap.FirstOne(uint32(startFd))
|
||||
fd := int32(fdUint)
|
||||
if err != nil || fd > endFd {
|
||||
f.mu.Unlock()
|
||||
return MaxFdLimit, nil, nil
|
||||
}
|
||||
file, fileVFS2, _, _ := f.getAll(fd)
|
||||
|
||||
// Add reference for caller.
|
||||
switch {
|
||||
case file != nil:
|
||||
file.IncRef()
|
||||
case fileVFS2 != nil:
|
||||
fileVFS2.IncRef()
|
||||
}
|
||||
|
||||
if file != nil || fileVFS2 != nil {
|
||||
file, fileVFS2 = f.setAll(ctx, fd, nil, nil, FDFlags{}) // Zap entry.
|
||||
f.fdBitmap.Remove(uint32(fd))
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
if file != nil {
|
||||
f.drop(ctx, file)
|
||||
}
|
||||
if fileVFS2 != nil {
|
||||
f.dropVFS2(ctx, fileVFS2)
|
||||
}
|
||||
|
||||
return fd, file, fileVFS2
|
||||
}
|
||||
|
||||
// GetLastFd returns the last set FD in the FDTable bitmap.
|
||||
func (f *FDTable) GetLastFd() int32 {
|
||||
last := f.fdBitmap.Maximum()
|
||||
if last > bitmap.MaxBitEntryLimit {
|
||||
return MaxFdLimit
|
||||
}
|
||||
return int32(last)
|
||||
}
|
||||
|
||||
@@ -226,3 +226,48 @@ func BenchmarkFDLookupAndDecRefConcurrent(b *testing.B) {
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetFlagsForRange(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
startFd int32
|
||||
endFd int32
|
||||
wantErr bool
|
||||
}
|
||||
testCases := []testCase{
|
||||
{"negative ranges", -100, -10, true},
|
||||
{"inverted positive ranges", 100, 10, true},
|
||||
{"good range", maxFD / 4, maxFD / 2, false},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
runTest(t, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) {
|
||||
for i := 0; i < maxFD; i++ {
|
||||
if _, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err != nil {
|
||||
t.Fatalf("testCase: %v\nfdTable.NewFDs(_, 0, %+v, FDFlags{}): %d, want: nil", test, []*fs.File{file}, err)
|
||||
}
|
||||
}
|
||||
|
||||
newFlags := FDFlags{CloseOnExec: true}
|
||||
if err := fdTable.SetFlagsForRange(ctx, test.startFd, test.endFd, newFlags); (err == nil) == test.wantErr {
|
||||
t.Fatalf("testCase: %v\nfdTable.SetFlagsForRange(_, %d, %d, %v): %v, waf: %t", test, test.startFd, test.endFd, newFlags, err, test.wantErr)
|
||||
}
|
||||
|
||||
if test.wantErr {
|
||||
return
|
||||
}
|
||||
|
||||
testRangeFlags := func(start int32, end int32, expected FDFlags) {
|
||||
for i := start; i <= end; i++ {
|
||||
file, flags := fdTable.Get(i)
|
||||
if file == nil || flags != expected {
|
||||
t.Fatalf("testCase: %v\nfdTable.Get(%d): (%v, %v), wanted (non-nil, %v)", test, i, file, flags, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
testRangeFlags(0, test.startFd-1, FDFlags{})
|
||||
testRangeFlags(test.startFd, test.endFd, newFlags)
|
||||
testRangeFlags(test.endFd+1, maxFD-1, FDFlags{})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) {
|
||||
|
||||
var fdTable *FDTable
|
||||
if args.Flags&linux.CLONE_FILES == 0 {
|
||||
fdTable = t.fdTable.Fork(t)
|
||||
fdTable = t.fdTable.Fork(t, MaxFdLimit)
|
||||
} else {
|
||||
fdTable = t.fdTable
|
||||
fdTable.IncRef()
|
||||
@@ -480,7 +480,7 @@ func (t *Task) Unshare(flags int32) error {
|
||||
var oldFDTable *FDTable
|
||||
if flags&linux.CLONE_FILES != 0 {
|
||||
oldFDTable = t.fdTable
|
||||
t.fdTable = oldFDTable.Fork(t)
|
||||
t.fdTable = oldFDTable.Fork(t, MaxFdLimit)
|
||||
}
|
||||
var oldFSContext *FSContext
|
||||
if flags&linux.CLONE_FS != 0 {
|
||||
@@ -497,6 +497,19 @@ func (t *Task) Unshare(flags int32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnshareFdTable unshares the FdTable that task t shares with other tasks, upto
|
||||
// the maxFd.
|
||||
//
|
||||
// Preconditions: The caller must be running on the task goroutine.
|
||||
func (t *Task) UnshareFdTable(maxFd int32) {
|
||||
t.mu.Lock()
|
||||
oldFDTable := t.fdTable
|
||||
t.fdTable = oldFDTable.Fork(t, maxFd)
|
||||
t.mu.Unlock()
|
||||
|
||||
oldFDTable.DecRef(t)
|
||||
}
|
||||
|
||||
// vforkStop is a TaskStop imposed on a task that creates a child with
|
||||
// CLONE_VFORK or vfork(2), that ends when the child task ceases to use its
|
||||
// current MM. (Normally, CLONE_VFORK is used in conjunction with CLONE_VM, so
|
||||
|
||||
@@ -214,7 +214,7 @@ func (r *runSyscallAfterExecStop) execute(t *Task) taskRunState {
|
||||
t.tg.pidns.owner.mu.Unlock()
|
||||
|
||||
oldFDTable := t.fdTable
|
||||
t.fdTable = t.fdTable.Fork(t)
|
||||
t.fdTable = t.fdTable.Fork(t, int32(t.fdTable.CurrentMaxFDs()))
|
||||
oldFDTable.DecRef(t)
|
||||
|
||||
// Remove FDs with the CloseOnExec flag set.
|
||||
|
||||
@@ -7,6 +7,7 @@ go_library(
|
||||
srcs = [
|
||||
"capability.go",
|
||||
"clone.go",
|
||||
"close_range.go",
|
||||
"epoll.go",
|
||||
"futex.go",
|
||||
"linux64_amd64.go",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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 strace
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
)
|
||||
|
||||
// CloseRangeFlagSet is the set of close_range(2) flags.
|
||||
var CloseRangeFlagSet = abi.FlagSet{
|
||||
{
|
||||
Flag: uint64(linux.CLOSE_RANGE_CLOEXEC),
|
||||
Name: "CLOSE_RANGE_CLOEXEC",
|
||||
},
|
||||
{
|
||||
Flag: uint64(linux.CLOSE_RANGE_UNSHARE),
|
||||
Name: "CLOSE_RANGE_UNSHARE",
|
||||
},
|
||||
}
|
||||
@@ -372,6 +372,7 @@ var linuxAMD64 = SyscallMap{
|
||||
433: makeSyscallInfo("fspick", FD, Path, Hex),
|
||||
434: makeSyscallInfo("pidfd_open", Hex, Hex),
|
||||
435: makeSyscallInfo("clone3", Hex, Hex),
|
||||
436: makeSyscallInfo("close_range", FD, FD, CloseRangeFlags),
|
||||
441: makeSyscallInfo("epoll_pwait2", FD, EpollEvents, Hex, Timespec, SigSet),
|
||||
}
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ var linuxARM64 = SyscallMap{
|
||||
433: makeSyscallInfo("fspick", FD, Path, Hex),
|
||||
434: makeSyscallInfo("pidfd_open", Hex, Hex),
|
||||
435: makeSyscallInfo("clone3", Hex, Hex),
|
||||
436: makeSyscallInfo("close_range", FD, FD, CloseRangeFlags),
|
||||
441: makeSyscallInfo("epoll_pwait2", FD, EpollEvents, Hex, Timespec, SigSet),
|
||||
}
|
||||
|
||||
|
||||
@@ -496,6 +496,8 @@ func (i *SyscallInfo) pre(t *kernel.Task, args arch.SyscallArguments, maximumBlo
|
||||
output = append(output, ProtectionFlagSet.Parse(uint64(args[arg].Uint())))
|
||||
case MmapFlags:
|
||||
output = append(output, MmapFlagSet.Parse(uint64(args[arg].Uint())))
|
||||
case CloseRangeFlags:
|
||||
output = append(output, CloseRangeFlagSet.Parse(uint64(args[arg].Uint())))
|
||||
case Oct:
|
||||
output = append(output, "0o"+strconv.FormatUint(args[arg].Uint64(), 8))
|
||||
case Hex:
|
||||
|
||||
@@ -244,6 +244,9 @@ const (
|
||||
|
||||
// MmapFlags is the flags argument in mmap(2).
|
||||
MmapFlags
|
||||
|
||||
// CloseRangeFlags are close_range(2) flags.
|
||||
CloseRangeFlags
|
||||
)
|
||||
|
||||
// defaultFormat is the syscall argument format to use if the actual format is
|
||||
|
||||
@@ -404,6 +404,7 @@ var AMD64 = &kernel.SyscallTable{
|
||||
433: syscalls.ErrorWithEvent("fspick", linuxerr.ENOSYS, "", nil),
|
||||
434: syscalls.ErrorWithEvent("pidfd_open", linuxerr.ENOSYS, "", nil),
|
||||
435: syscalls.ErrorWithEvent("clone3", linuxerr.ENOSYS, "", nil),
|
||||
436: syscalls.Supported("close_range", CloseRange),
|
||||
441: syscalls.Supported("epoll_pwait2", EpollPwait2),
|
||||
},
|
||||
Emulate: map[hostarch.Addr]uintptr{
|
||||
@@ -723,6 +724,7 @@ var ARM64 = &kernel.SyscallTable{
|
||||
433: syscalls.ErrorWithEvent("fspick", linuxerr.ENOSYS, "", nil),
|
||||
434: syscalls.ErrorWithEvent("pidfd_open", linuxerr.ENOSYS, "", nil),
|
||||
435: syscalls.ErrorWithEvent("clone3", linuxerr.ENOSYS, "", nil),
|
||||
436: syscalls.Supported("close_range", CloseRange),
|
||||
441: syscalls.Supported("epoll_pwait2", EpollPwait2),
|
||||
},
|
||||
Emulate: map[hostarch.Addr]uintptr{},
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package linux
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
@@ -801,6 +803,60 @@ func Close(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
|
||||
return 0, nil, handleIOError(t, false /* partial */, err, linuxerr.EINTR, "close", file)
|
||||
}
|
||||
|
||||
// CloseRange implements linux syscall close_range(2).
|
||||
func CloseRange(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
first := args[0].Uint()
|
||||
last := args[1].Uint()
|
||||
flags := args[2].Uint()
|
||||
|
||||
if (first > last) || (last > math.MaxInt32) {
|
||||
return 0, nil, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
if (flags & ^(linux.CLOSE_RANGE_CLOEXEC | linux.CLOSE_RANGE_UNSHARE)) != 0 {
|
||||
return 0, nil, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
cloexec := flags & linux.CLOSE_RANGE_CLOEXEC
|
||||
unshare := flags & linux.CLOSE_RANGE_UNSHARE
|
||||
|
||||
if unshare != 0 {
|
||||
// If possible, we don't want to copy FDs to the new unshared table, because those FDs will
|
||||
// be promptly closed and no longer used. So in the case where we know the range extends all
|
||||
// the way to the end of the FdTable, we can simply copy the FdTable only up to the start of
|
||||
// the range that we are closing.
|
||||
if cloexec == 0 && int32(last) >= t.FDTable().GetLastFd() {
|
||||
t.UnshareFdTable(int32(first))
|
||||
} else {
|
||||
t.UnshareFdTable(math.MaxInt32)
|
||||
}
|
||||
}
|
||||
|
||||
if cloexec != 0 {
|
||||
flagToApply := kernel.FDFlags{
|
||||
CloseOnExec: true,
|
||||
}
|
||||
t.FDTable().SetFlagsForRange(t.AsyncContext(), int32(first), int32(last), flagToApply)
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
fdTable := t.FDTable()
|
||||
fd := int32(first)
|
||||
for {
|
||||
fd, file, _ := fdTable.RemoveNextInRange(t, fd, int32(last))
|
||||
if file == nil {
|
||||
break
|
||||
}
|
||||
|
||||
fd++
|
||||
// Per the close_range(2) documentation, errors upon closing file descriptors are ignored.
|
||||
_ = file.Flush(t)
|
||||
file.DecRef(t)
|
||||
}
|
||||
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
// Dup implements linux syscall dup(2).
|
||||
func Dup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
fd := args[0].Int()
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package vfs2
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
@@ -44,6 +46,60 @@ func Close(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
|
||||
return 0, nil, slinux.HandleIOErrorVFS2(t, false /* partial */, err, linuxerr.EINTR, "close", file)
|
||||
}
|
||||
|
||||
// CloseRange implements linux syscall close_range(2).
|
||||
func CloseRange(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
first := args[0].Uint()
|
||||
last := args[1].Uint()
|
||||
flags := args[2].Uint()
|
||||
|
||||
if (first > last) || (last > math.MaxInt32) {
|
||||
return 0, nil, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
if (flags & ^(linux.CLOSE_RANGE_CLOEXEC | linux.CLOSE_RANGE_UNSHARE)) != 0 {
|
||||
return 0, nil, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
cloexec := flags & linux.CLOSE_RANGE_CLOEXEC
|
||||
unshare := flags & linux.CLOSE_RANGE_UNSHARE
|
||||
|
||||
if unshare != 0 {
|
||||
// If possible, we don't want to copy FDs to the new unshared table, because those FDs will
|
||||
// be promptly closed and no longer used. So in the case where we know the range extends all
|
||||
// the way to the end of the FdTable, we can simply copy the FdTable only up to the start of
|
||||
// the range that we are closing.
|
||||
if cloexec == 0 && int32(last) >= t.FDTable().GetLastFd() {
|
||||
t.UnshareFdTable(int32(first))
|
||||
} else {
|
||||
t.UnshareFdTable(math.MaxInt32)
|
||||
}
|
||||
}
|
||||
|
||||
if cloexec != 0 {
|
||||
flagToApply := kernel.FDFlags{
|
||||
CloseOnExec: true,
|
||||
}
|
||||
t.FDTable().SetFlagsForRangeVFS2(t.AsyncContext(), int32(first), int32(last), flagToApply)
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
fdTable := t.FDTable()
|
||||
fd := int32(first)
|
||||
for {
|
||||
fd, _, file := fdTable.RemoveNextInRange(t, fd, int32(last))
|
||||
if file == nil {
|
||||
break
|
||||
}
|
||||
|
||||
fd++
|
||||
// Per the close_range(2) documentation, errors upon closing file descriptors are ignored.
|
||||
_ = file.OnClose(t)
|
||||
file.DecRef(t)
|
||||
}
|
||||
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
// Dup implements Linux syscall dup(2).
|
||||
func Dup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
fd := args[0].Int()
|
||||
|
||||
@@ -162,6 +162,7 @@ func Override() {
|
||||
s.Table[327] = syscalls.Supported("preadv2", Preadv2)
|
||||
s.Table[328] = syscalls.Supported("pwritev2", Pwritev2)
|
||||
s.Table[332] = syscalls.Supported("statx", Statx)
|
||||
s.Table[436] = syscalls.Supported("close_range", CloseRange)
|
||||
s.Table[439] = syscalls.Supported("faccessat2", Faccessat2)
|
||||
s.Table[441] = syscalls.Supported("epoll_pwait2", EpollPwait2)
|
||||
s.Init()
|
||||
@@ -277,6 +278,7 @@ func Override() {
|
||||
s.Table[286] = syscalls.Supported("preadv2", Preadv2)
|
||||
s.Table[287] = syscalls.Supported("pwritev2", Pwritev2)
|
||||
s.Table[291] = syscalls.Supported("statx", Statx)
|
||||
s.Table[436] = syscalls.Supported("close_range", CloseRange)
|
||||
s.Table[439] = syscalls.Supported("faccessat2", Faccessat2)
|
||||
s.Table[441] = syscalls.Supported("epoll_pwait2", EpollPwait2)
|
||||
s.Init()
|
||||
|
||||
@@ -1078,3 +1078,8 @@ syscall_test(
|
||||
syscall_test(
|
||||
test = "//test/syscalls/linux:deleted_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "small",
|
||||
test = "//test/syscalls/linux:close_range_test",
|
||||
)
|
||||
|
||||
@@ -4524,3 +4524,20 @@ cc_binary(
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "close_range_test",
|
||||
testonly = 1,
|
||||
srcs = ["close_range.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
"//test/util:file_descriptor",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
gtest,
|
||||
"//test/util:cleanup",
|
||||
"//test/util:posix_error",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
// 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.
|
||||
|
||||
#include <asm-generic/errno-base.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/base/macros.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
#ifndef CLOSE_RANGE_UNSHARE
|
||||
#define CLOSE_RANGE_UNSHARE (1U << 1)
|
||||
#endif
|
||||
#ifndef CLOSE_RANGE_CLOEXEC
|
||||
#define CLOSE_RANGE_CLOEXEC (1U << 2)
|
||||
#endif
|
||||
|
||||
#ifndef SYS_close_range
|
||||
#if defined(__x86_64__) || defined(__aarch64__)
|
||||
#define SYS_close_range 436
|
||||
#else
|
||||
#error "Unknown architecture"
|
||||
#endif
|
||||
#endif // SYS_close_range
|
||||
|
||||
int close_range(unsigned int first, unsigned int last, unsigned int flags) {
|
||||
return syscall(SYS_close_range, first, last, flags);
|
||||
}
|
||||
|
||||
class CloseRangeTest : public ::testing::Test {
|
||||
public:
|
||||
void CreateFiles(int num_files) {
|
||||
file_names_.reserve(num_files);
|
||||
for (int i = 0; i < num_files; ++i) {
|
||||
file_names_.push_back(NewTempAbsPath());
|
||||
int fd;
|
||||
ASSERT_THAT(fd = open(file_names_[i].c_str(), O_CREAT, 0644),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(close(fd), SyscallSucceeds());
|
||||
}
|
||||
}
|
||||
|
||||
void OpenFilesRdwr() {
|
||||
fds_.clear();
|
||||
fds_.reserve(file_names_.size());
|
||||
for (std::string &file_name : file_names_) {
|
||||
int fd;
|
||||
ASSERT_THAT(fd = open(file_name.c_str(), O_RDWR), SyscallSucceeds());
|
||||
fds_.push_back(fd);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void TearDown() override {
|
||||
for (std::string &name : file_names_) {
|
||||
unlink(name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<std::string> file_names_;
|
||||
std::vector<unsigned int> fds_;
|
||||
};
|
||||
|
||||
// Base test to confirm that all files in contiguous range get closed.
|
||||
TEST_F(CloseRangeTest, ContiguousRange) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = 0;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallSucceeds());
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
||||
}
|
||||
}
|
||||
|
||||
// Test to confirm that a range with files already closed in the range still
|
||||
// closes the remaining files.
|
||||
TEST_F(CloseRangeTest, RangeWithHoles) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = 0;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
|
||||
EXPECT_THAT(close(fds_[2]), SyscallSucceeds());
|
||||
EXPECT_THAT(close(fds_[7]), SyscallSucceeds());
|
||||
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallSucceeds());
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
||||
}
|
||||
}
|
||||
|
||||
// Test to confirm that closing a range with fds preceding and following the
|
||||
// range leaves those other fds open.
|
||||
TEST_F(CloseRangeTest, RangeInMiddleOfOpenFiles) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = 0;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
|
||||
size_t slice_start = 4;
|
||||
size_t slice_end = 7;
|
||||
EXPECT_THAT(close_range(fds_[slice_start], fds_[slice_end], flags),
|
||||
SyscallSucceeds());
|
||||
for (int fd :
|
||||
std::vector(fds_.begin() + slice_start, fds_.begin() + slice_end + 1)) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
||||
}
|
||||
for (int fd : std::vector(fds_.begin(), fds_.begin() + slice_start)) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
for (int fd : std::vector(fds_.begin() + slice_end + 1, fds_.end())) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
}
|
||||
|
||||
// Test to confirm that calling close_range on just one file succeeds.
|
||||
TEST_F(CloseRangeTest, SingleFile) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 1;
|
||||
unsigned int flags = 0; /* */
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
auto ret = ReadAllFd(fds_[0]);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[0], flags), SyscallSucceeds());
|
||||
|
||||
ret = ReadAllFd(fds_[0]);
|
||||
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
||||
}
|
||||
|
||||
// Test to confirm that calling close_range twice on the same range does not
|
||||
// cause errors.
|
||||
TEST_F(CloseRangeTest, CallCloseRangeTwice) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = 0;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallSucceeds());
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
||||
}
|
||||
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
// Test that using CLOEXEC flag does not close the file for this process.
|
||||
TEST_F(CloseRangeTest, CloexecFlagTest) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = CLOSE_RANGE_CLOEXEC;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallSucceeds());
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
}
|
||||
|
||||
// Test that using UNSHARE flag still properly closes the files.
|
||||
TEST_F(CloseRangeTest, UnshareFlagTest) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = CLOSE_RANGE_UNSHARE;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallSucceeds());
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
||||
}
|
||||
}
|
||||
|
||||
// Test that using the UNSHARE flag and closing files at the start of the range
|
||||
// still leaves the latter files opened.
|
||||
TEST_F(CloseRangeTest, UnshareFlagAndCloseRangeAtStart) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = CLOSE_RANGE_UNSHARE;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
|
||||
size_t range_split = 5;
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[range_split - 1], flags),
|
||||
SyscallSucceeds());
|
||||
for (int fd : std::vector(fds_.begin(), fds_.begin() + range_split)) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
||||
}
|
||||
for (int fd : std::vector(fds_.begin() + range_split, fds_.end())) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
}
|
||||
|
||||
// Test that using the UNSHARE flag and closing files at the end of the range
|
||||
// still leaves the earlier files opened.
|
||||
TEST_F(CloseRangeTest, UnshareFlagAndCloseRangeAtEnd) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = CLOSE_RANGE_UNSHARE;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
|
||||
size_t range_split = 5;
|
||||
EXPECT_THAT(
|
||||
close_range(fds_[range_split], fds_[num_files_in_range - 1], flags),
|
||||
SyscallSucceeds());
|
||||
for (int fd : std::vector(fds_.begin(), fds_.begin() + range_split)) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
for (int fd : std::vector(fds_.begin() + range_split, fds_.end())) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, PosixErrorIs(EBADF));
|
||||
}
|
||||
}
|
||||
|
||||
// Test that using both CLOEXEC and UNSHARE flags does not close files for this
|
||||
// process.
|
||||
TEST_F(CloseRangeTest, CloexecAndUnshareFlagTest) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallSucceeds());
|
||||
for (int fd : fds_) {
|
||||
auto ret = ReadAllFd(fd);
|
||||
EXPECT_THAT(ret, IsPosixErrorOkMatcher());
|
||||
}
|
||||
}
|
||||
|
||||
// Test that calling with invalid range does not succeed.
|
||||
TEST_F(CloseRangeTest, RangeFirstGreaterThanLast) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
unsigned int flags = 0;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
EXPECT_THAT(close_range(fds_[num_files_in_range - 1], fds_[0], flags),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
// Test that calling with invalid flags does not succeed.
|
||||
TEST_F(CloseRangeTest, InvalidFlags) {
|
||||
SKIP_IF(!IsRunningOnGvisor() && close_range(1, 0, 0) < 0 && errno == ENOSYS);
|
||||
int num_files_in_range = 10;
|
||||
|
||||
CreateFiles(num_files_in_range);
|
||||
OpenFilesRdwr();
|
||||
|
||||
unsigned int flags = CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE | 0xF;
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
|
||||
flags = 0xF0;
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
|
||||
flags = CLOSE_RANGE_CLOEXEC | 0xF00;
|
||||
EXPECT_THAT(close_range(fds_[0], fds_[num_files_in_range - 1], flags),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
Reference in New Issue
Block a user