mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Pass OpenTruncate to gofer in Open call when opening file with O_TRUNC.
Note that the Sentry still calls Truncate() on the file before calling Open. A new p9 version check was added to ensure that the p9 server can handle the the OpenTruncate flag. If not, then the flag is stripped before sending. PiperOrigin-RevId: 281609112
This commit is contained in:
committed by
gVisor bot
parent
ef6f936254
commit
012102eefd
+9
-9
@@ -272,15 +272,15 @@ func (t *Tlopen) handle(cs *connState) message {
|
||||
return newErr(syscall.EINVAL)
|
||||
}
|
||||
|
||||
// Are flags valid?
|
||||
flags := t.Flags &^ OpenFlagsIgnoreMask
|
||||
if flags&^OpenFlagsModeMask != 0 {
|
||||
return newErr(syscall.EINVAL)
|
||||
}
|
||||
|
||||
// Is this an attempt to open a directory as writable? Don't accept.
|
||||
if ref.mode.IsDir() && flags != ReadOnly {
|
||||
return newErr(syscall.EINVAL)
|
||||
if ref.mode.IsDir() {
|
||||
// Directory must be opened ReadOnly.
|
||||
if t.Flags&OpenFlagsModeMask != ReadOnly {
|
||||
return newErr(syscall.EISDIR)
|
||||
}
|
||||
// Directory not truncatable.
|
||||
if t.Flags&OpenTruncate != 0 {
|
||||
return newErr(syscall.EISDIR)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -47,10 +47,6 @@ const (
|
||||
// OpenTruncate is a Tlopen flag indicating that the opened file should be
|
||||
// truncated.
|
||||
OpenTruncate OpenFlags = 01000
|
||||
|
||||
// OpenFlagsIgnoreMask is a list of OpenFlags mode bits that are ignored for Tlopen.
|
||||
// Note that syscall.O_LARGEFILE is set to zero, use value from Linux fcntl.h.
|
||||
OpenFlagsIgnoreMask OpenFlags = syscall.O_DIRECTORY | syscall.O_NOATIME | 0100000
|
||||
)
|
||||
|
||||
// ConnectFlags is the mode passed to Connect operations.
|
||||
|
||||
@@ -1044,11 +1044,11 @@ func TestReaddir(t *testing.T) {
|
||||
if _, err := f.Readdir(0, 1); err != syscall.EINVAL {
|
||||
t.Errorf("readdir got %v, wanted EINVAL", err)
|
||||
}
|
||||
if _, _, _, err := f.Open(p9.ReadWrite); err != syscall.EINVAL {
|
||||
t.Errorf("readdir got %v, wanted EINVAL", err)
|
||||
if _, _, _, err := f.Open(p9.ReadWrite); err != syscall.EISDIR {
|
||||
t.Errorf("readdir got %v, wanted EISDIR", err)
|
||||
}
|
||||
if _, _, _, err := f.Open(p9.WriteOnly); err != syscall.EINVAL {
|
||||
t.Errorf("readdir got %v, wanted EINVAL", err)
|
||||
if _, _, _, err := f.Open(p9.WriteOnly); err != syscall.EISDIR {
|
||||
t.Errorf("readdir got %v, wanted EISDIR", err)
|
||||
}
|
||||
backend.EXPECT().Open(p9.ReadOnly).Times(1)
|
||||
if _, _, _, err := f.Open(p9.ReadOnly); err != nil {
|
||||
@@ -1065,75 +1065,93 @@ func TestReaddir(t *testing.T) {
|
||||
func TestOpen(t *testing.T) {
|
||||
type openTest struct {
|
||||
name string
|
||||
mode p9.OpenFlags
|
||||
flags p9.OpenFlags
|
||||
err error
|
||||
match func(p9.FileMode) bool
|
||||
}
|
||||
|
||||
cases := []openTest{
|
||||
{
|
||||
name: "invalid",
|
||||
mode: ^p9.OpenFlagsModeMask,
|
||||
err: syscall.EINVAL,
|
||||
match: func(p9.FileMode) bool { return true },
|
||||
},
|
||||
{
|
||||
name: "not-openable-read-only",
|
||||
mode: p9.ReadOnly,
|
||||
flags: p9.ReadOnly,
|
||||
err: syscall.EINVAL,
|
||||
match: func(mode p9.FileMode) bool { return !p9.CanOpen(mode) },
|
||||
},
|
||||
{
|
||||
name: "not-openable-write-only",
|
||||
mode: p9.WriteOnly,
|
||||
flags: p9.WriteOnly,
|
||||
err: syscall.EINVAL,
|
||||
match: func(mode p9.FileMode) bool { return !p9.CanOpen(mode) },
|
||||
},
|
||||
{
|
||||
name: "not-openable-read-write",
|
||||
mode: p9.ReadWrite,
|
||||
flags: p9.ReadWrite,
|
||||
err: syscall.EINVAL,
|
||||
match: func(mode p9.FileMode) bool { return !p9.CanOpen(mode) },
|
||||
},
|
||||
{
|
||||
name: "directory-read-only",
|
||||
mode: p9.ReadOnly,
|
||||
flags: p9.ReadOnly,
|
||||
err: nil,
|
||||
match: func(mode p9.FileMode) bool { return mode.IsDir() },
|
||||
},
|
||||
{
|
||||
name: "directory-read-write",
|
||||
mode: p9.ReadWrite,
|
||||
err: syscall.EINVAL,
|
||||
flags: p9.ReadWrite,
|
||||
err: syscall.EISDIR,
|
||||
match: func(mode p9.FileMode) bool { return mode.IsDir() },
|
||||
},
|
||||
{
|
||||
name: "directory-write-only",
|
||||
mode: p9.WriteOnly,
|
||||
err: syscall.EINVAL,
|
||||
flags: p9.WriteOnly,
|
||||
err: syscall.EISDIR,
|
||||
match: func(mode p9.FileMode) bool { return mode.IsDir() },
|
||||
},
|
||||
{
|
||||
name: "read-only",
|
||||
mode: p9.ReadOnly,
|
||||
flags: p9.ReadOnly,
|
||||
err: nil,
|
||||
match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) },
|
||||
},
|
||||
{
|
||||
name: "write-only",
|
||||
mode: p9.WriteOnly,
|
||||
flags: p9.WriteOnly,
|
||||
err: nil,
|
||||
match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) && !mode.IsDir() },
|
||||
},
|
||||
{
|
||||
name: "read-write",
|
||||
mode: p9.ReadWrite,
|
||||
flags: p9.ReadWrite,
|
||||
err: nil,
|
||||
match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) && !mode.IsDir() },
|
||||
},
|
||||
{
|
||||
name: "directory-read-only-truncate",
|
||||
flags: p9.ReadOnly | p9.OpenTruncate,
|
||||
err: syscall.EISDIR,
|
||||
match: func(mode p9.FileMode) bool { return mode.IsDir() },
|
||||
},
|
||||
{
|
||||
name: "read-only-truncate",
|
||||
flags: p9.ReadOnly | p9.OpenTruncate,
|
||||
err: nil,
|
||||
match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) && !mode.IsDir() },
|
||||
},
|
||||
{
|
||||
name: "write-only-truncate",
|
||||
flags: p9.WriteOnly | p9.OpenTruncate,
|
||||
err: nil,
|
||||
match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) && !mode.IsDir() },
|
||||
},
|
||||
{
|
||||
name: "read-write-truncate",
|
||||
flags: p9.ReadWrite | p9.OpenTruncate,
|
||||
err: nil,
|
||||
match: func(mode p9.FileMode) bool { return p9.CanOpen(mode) && !mode.IsDir() },
|
||||
},
|
||||
}
|
||||
|
||||
// Open(mode OpenFlags) (*fd.FD, QID, uint32, error)
|
||||
// Open(flags OpenFlags) (*fd.FD, QID, uint32, error)
|
||||
// - only works on Regular, NamedPipe, BLockDevice, CharacterDevice
|
||||
// - returning a file works as expected
|
||||
for name := range newTypeMap(nil) {
|
||||
@@ -1171,25 +1189,25 @@ func TestOpen(t *testing.T) {
|
||||
// Attempt the given open.
|
||||
if tc.err != nil {
|
||||
// We expect an error, just test and return.
|
||||
if _, _, _, err := f.Open(tc.mode); err != tc.err {
|
||||
t.Fatalf("open with mode %v got %v, want %v", tc.mode, err, tc.err)
|
||||
if _, _, _, err := f.Open(tc.flags); err != tc.err {
|
||||
t.Fatalf("open with flags %v got %v, want %v", tc.flags, err, tc.err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Run an FD test, since we expect success.
|
||||
fdTest(t, func(send *fd.FD) *fd.FD {
|
||||
backend.EXPECT().Open(tc.mode).Return(send, p9.QID{}, uint32(0), nil).Times(1)
|
||||
recv, _, _, err := f.Open(tc.mode)
|
||||
backend.EXPECT().Open(tc.flags).Return(send, p9.QID{}, uint32(0), nil).Times(1)
|
||||
recv, _, _, err := f.Open(tc.flags)
|
||||
if err != tc.err {
|
||||
t.Fatalf("open with mode %v got %v, want %v", tc.mode, err, tc.err)
|
||||
t.Fatalf("open with flags %v got %v, want %v", tc.flags, err, tc.err)
|
||||
}
|
||||
return recv
|
||||
})
|
||||
|
||||
// If the open was successful, attempt another one.
|
||||
if _, _, _, err := f.Open(tc.mode); err != syscall.EINVAL {
|
||||
t.Errorf("second open with mode %v got %v, want EINVAL", tc.mode, err)
|
||||
if _, _, _, err := f.Open(tc.flags); err != syscall.EINVAL {
|
||||
t.Errorf("second open with flags %v got %v, want EINVAL", tc.flags, err)
|
||||
}
|
||||
|
||||
// Ensure that all illegal operations fail.
|
||||
|
||||
+7
-1
@@ -26,7 +26,7 @@ const (
|
||||
//
|
||||
// Clients are expected to start requesting this version number and
|
||||
// to continuously decrement it until a Tversion request succeeds.
|
||||
highestSupportedVersion uint32 = 8
|
||||
highestSupportedVersion uint32 = 9
|
||||
|
||||
// lowestSupportedVersion is the lowest supported version X in a
|
||||
// version string of the format 9P2000.L.Google.X.
|
||||
@@ -155,3 +155,9 @@ func versionSupportsTallocate(v uint32) bool {
|
||||
func versionSupportsFlipcall(v uint32) bool {
|
||||
return v >= 8
|
||||
}
|
||||
|
||||
// VersionSupportsOpenTruncateFlag returns true if version v supports
|
||||
// passing the OpenTruncate flag to Tlopen.
|
||||
func VersionSupportsOpenTruncateFlag(v uint32) bool {
|
||||
return v >= 9
|
||||
}
|
||||
|
||||
@@ -64,6 +64,10 @@ type FileFlags struct {
|
||||
|
||||
// NonSeekable indicates that file.offset isn't used.
|
||||
NonSeekable bool
|
||||
|
||||
// Truncate indicates that the file should be truncated before opened.
|
||||
// This is only applicable if the file is regular.
|
||||
Truncate bool
|
||||
}
|
||||
|
||||
// SettableFileFlags is a subset of FileFlags above that can be changed
|
||||
@@ -118,6 +122,9 @@ func (f FileFlags) ToLinux() (mask uint) {
|
||||
if f.LargeFile {
|
||||
mask |= linux.O_LARGEFILE
|
||||
}
|
||||
if f.Truncate {
|
||||
mask |= linux.O_TRUNC
|
||||
}
|
||||
|
||||
switch {
|
||||
case f.Read && f.Write:
|
||||
|
||||
@@ -28,8 +28,14 @@ func (f *fileOperations) afterLoad() {
|
||||
|
||||
// Manually load the open handles.
|
||||
var err error
|
||||
|
||||
// The file may have been opened with Truncate, but we don't
|
||||
// want to re-open it with Truncate or we will lose data.
|
||||
flags := f.flags
|
||||
flags.Truncate = false
|
||||
|
||||
// TODO(b/38173783): Context is not plumbed to save/restore.
|
||||
f.handles, err = f.inodeOperations.fileState.getHandles(context.Background(), f.flags, f.inodeOperations.cachingInodeOps)
|
||||
f.handles, err = f.inodeOperations.fileState.getHandles(context.Background(), flags, f.inodeOperations.cachingInodeOps)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to re-open handle: %v", err)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func (h *handles) DecRef() {
|
||||
})
|
||||
}
|
||||
|
||||
func newHandles(ctx context.Context, file contextFile, flags fs.FileFlags) (*handles, error) {
|
||||
func newHandles(ctx context.Context, client *p9.Client, file contextFile, flags fs.FileFlags) (*handles, error) {
|
||||
_, newFile, err := file.walk(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -81,6 +81,9 @@ func newHandles(ctx context.Context, file contextFile, flags fs.FileFlags) (*han
|
||||
default:
|
||||
panic("impossible fs.FileFlags")
|
||||
}
|
||||
if flags.Truncate && p9.VersionSupportsOpenTruncateFlag(client.Version()) {
|
||||
p9flags |= p9.OpenTruncate
|
||||
}
|
||||
|
||||
hostFile, _, _, err := newFile.open(ctx, p9flags)
|
||||
if err != nil {
|
||||
|
||||
@@ -180,7 +180,7 @@ func (i *inodeFileState) setSharedHandlesLocked(flags fs.FileFlags, h *handles)
|
||||
// given flags.
|
||||
func (i *inodeFileState) getHandles(ctx context.Context, flags fs.FileFlags, cache *fsutil.CachingInodeOperations) (*handles, error) {
|
||||
if !i.canShareHandles() {
|
||||
return newHandles(ctx, i.file, flags)
|
||||
return newHandles(ctx, i.s.client, i.file, flags)
|
||||
}
|
||||
|
||||
i.handlesMu.Lock()
|
||||
@@ -201,19 +201,25 @@ func (i *inodeFileState) getHandles(ctx context.Context, flags fs.FileFlags, cac
|
||||
// whether previously open read handle was recreated. Host mappings must be
|
||||
// invalidated if so.
|
||||
func (i *inodeFileState) getHandlesLocked(ctx context.Context, flags fs.FileFlags) (*handles, bool, error) {
|
||||
// Do we already have usable shared handles?
|
||||
if flags.Write {
|
||||
// Check if we are able to use cached handles.
|
||||
if flags.Truncate && p9.VersionSupportsOpenTruncateFlag(i.s.client.Version()) {
|
||||
// If we are truncating (and the gofer supports it), then we
|
||||
// always need a new handle. Don't return one from the cache.
|
||||
} else if flags.Write {
|
||||
if i.writeHandles != nil && (i.writeHandlesRW || !flags.Read) {
|
||||
// File is opened for writing, and we have cached write
|
||||
// handles that we can use.
|
||||
i.writeHandles.IncRef()
|
||||
return i.writeHandles, false, nil
|
||||
}
|
||||
} else if i.readHandles != nil {
|
||||
// File is opened for reading and we have cached handles.
|
||||
i.readHandles.IncRef()
|
||||
return i.readHandles, false, nil
|
||||
}
|
||||
|
||||
// No; get new handles and cache them for future sharing.
|
||||
h, err := newHandles(ctx, i.file, flags)
|
||||
// Get new handles and cache them for future sharing.
|
||||
h, err := newHandles(ctx, i.s.client, i.file, flags)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -239,7 +245,7 @@ func (i *inodeFileState) recreateReadHandles(ctx context.Context, writer *handle
|
||||
if !flags.Read {
|
||||
// Writer can't be used for read, must create a new handle.
|
||||
var err error
|
||||
h, err = newHandles(ctx, i.file, fs.FileFlags{Read: true})
|
||||
h, err = newHandles(ctx, i.s.client, i.file, fs.FileFlags{Read: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -50,5 +50,6 @@ func linuxToFlags(mask uint) fs.FileFlags {
|
||||
Directory: mask&linux.O_DIRECTORY != 0,
|
||||
Async: mask&linux.O_ASYNC != 0,
|
||||
LargeFile: mask&linux.O_LARGEFILE != 0,
|
||||
Truncate: mask&linux.O_TRUNC != 0,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user