Implement fallocate(2)

Closes #225

PiperOrigin-RevId: 247508791
Change-Id: I04f47cf2770b30043e5a272aba4ba6e11d0476cc
This commit is contained in:
Fabricio Voznika
2019-05-09 15:35:49 -07:00
committed by Shentubot
parent 0f4be95a33
commit 1bee43be13
44 changed files with 592 additions and 47 deletions
+11
View File
@@ -254,3 +254,14 @@ const (
F_SEAL_GROW = 0x0004 // Prevent file from growing.
F_SEAL_WRITE = 0x0008 // Prevent writes.
)
// Constants related to fallocate(2). Source: include/uapi/linux/falloc.h
const (
FALLOC_FL_KEEP_SIZE = 0x01
FALLOC_FL_PUNCH_HOLE = 0x02
FALLOC_FL_NO_HIDE_STALE = 0x04
FALLOC_FL_COLLAPSE_RANGE = 0x08
FALLOC_FL_ZERO_RANGE = 0x10
FALLOC_FL_INSERT_RANGE = 0x20
FALLOC_FL_UNSHARE_RANGE = 0x40
)
+1
View File
@@ -26,6 +26,7 @@ go_library(
"//pkg/fd",
"//pkg/log",
"//pkg/unet",
"@org_golang_x_sys//unix:go_default_library",
],
)
+12
View File
@@ -171,6 +171,18 @@ func (c *clientFile) SetAttr(valid SetAttrMask, attr SetAttr) error {
return c.client.sendRecv(&Tsetattr{FID: c.fid, Valid: valid, SetAttr: attr}, &Rsetattr{})
}
// Allocate implements File.Allocate.
func (c *clientFile) Allocate(mode AllocateMode, offset, length uint64) error {
if atomic.LoadUint32(&c.closed) != 0 {
return syscall.EBADF
}
if !versionSupportsTallocate(c.client.version) {
return syscall.EOPNOTSUPP
}
return c.client.sendRecv(&Tallocate{FID: c.fid, Mode: mode, Offset: offset, Length: length}, &Rallocate{})
}
// Remove implements File.Remove.
//
// N.B. This method is no longer part of the file interface and should be
+4
View File
@@ -89,6 +89,10 @@ type File interface {
// On the server, SetAttr has a write concurrency guarantee.
SetAttr(valid SetAttrMask, attr SetAttr) error
// Allocate allows the caller to directly manipulate the allocated disk space
// for the file. See fallocate(2) for more details.
Allocate(mode AllocateMode, offset, length uint64) error
// Close is called when all references are dropped on the server side,
// and Close should be called by the client to drop all references.
//
+34
View File
@@ -877,6 +877,40 @@ func (t *Tsetattr) handle(cs *connState) message {
return &Rsetattr{}
}
// handle implements handler.handle.
func (t *Tallocate) handle(cs *connState) message {
// Lookup the FID.
ref, ok := cs.LookupFID(t.FID)
if !ok {
return newErr(syscall.EBADF)
}
defer ref.DecRef()
if err := ref.safelyWrite(func() error {
// Has it been opened already?
openFlags, opened := ref.OpenFlags()
if !opened {
return syscall.EINVAL
}
// Can it be written? Check permissions.
if openFlags&OpenFlagsModeMask == ReadOnly {
return syscall.EBADF
}
// We don't allow allocate on files that have been deleted.
if ref.isDeleted() {
return syscall.EINVAL
}
return ref.file.Allocate(t.Mode, t.Offset, t.Length)
}); err != nil {
return newErr(err)
}
return &Rallocate{}
}
// handle implements handler.handle.
func (t *Txattrwalk) handle(cs *connState) message {
// Lookup the FID.
+5
View File
@@ -323,6 +323,11 @@ func (l *local) Renamed(parent p9.File, newName string) {
l.path = path.Join(parent.(*local).path, newName)
}
// Allocate implements p9.File.Allocate.
func (l *local) Allocate(mode p9.AllocateMode, offset, length uint64) error {
return syscall.Fallocate(int(l.file.Fd()), mode.ToLinux(), int64(offset), int64(length))
}
func main() {
log.SetLevel(log.Debug)
+59
View File
@@ -1424,6 +1424,63 @@ func (r *Rsetattr) String() string {
return fmt.Sprintf("Rsetattr{}")
}
// Tallocate is an allocate request. This is an extension to 9P protocol, not
// present in the 9P2000.L standard.
type Tallocate struct {
FID FID
Mode AllocateMode
Offset uint64
Length uint64
}
// Decode implements encoder.Decode.
func (t *Tallocate) Decode(b *buffer) {
t.FID = b.ReadFID()
t.Mode.Decode(b)
t.Offset = b.Read64()
t.Length = b.Read64()
}
// Encode implements encoder.Encode.
func (t *Tallocate) Encode(b *buffer) {
b.WriteFID(t.FID)
t.Mode.Encode(b)
b.Write64(t.Offset)
b.Write64(t.Length)
}
// Type implements message.Type.
func (*Tallocate) Type() MsgType {
return MsgTallocate
}
// String implements fmt.Stringer.
func (t *Tallocate) String() string {
return fmt.Sprintf("Tallocate{FID: %d, Offset: %d, Length: %d}", t.FID, t.Offset, t.Length)
}
// Rallocate is an allocate response.
type Rallocate struct {
}
// Decode implements encoder.Decode.
func (*Rallocate) Decode(b *buffer) {
}
// Encode implements encoder.Encode.
func (*Rallocate) Encode(b *buffer) {
}
// Type implements message.Type.
func (*Rallocate) Type() MsgType {
return MsgRallocate
}
// String implements fmt.Stringer.
func (r *Rallocate) String() string {
return fmt.Sprintf("Rallocate{}")
}
// Txattrwalk walks extended attributes.
type Txattrwalk struct {
// FID is the FID to check for attributes.
@@ -2297,4 +2354,6 @@ func init() {
msgRegistry.register(MsgRusymlink, func() message { return &Rusymlink{} })
msgRegistry.register(MsgTlconnect, func() message { return &Tlconnect{} })
msgRegistry.register(MsgRlconnect, func() message { return &Rlconnect{} })
msgRegistry.register(MsgTallocate, func() message { return &Tallocate{} })
msgRegistry.register(MsgRallocate, func() message { return &Rallocate{} })
}
+81
View File
@@ -22,6 +22,8 @@ import (
"strings"
"sync/atomic"
"syscall"
"golang.org/x/sys/unix"
)
// OpenFlags is the mode passed to Open and Create operations.
@@ -374,6 +376,8 @@ const (
MsgRusymlink = 135
MsgTlconnect = 136
MsgRlconnect = 137
MsgTallocate = 138
MsgRallocate = 139
)
// QIDType represents the file type for QIDs.
@@ -1058,3 +1062,80 @@ func (d *Dirent) Encode(b *buffer) {
b.WriteQIDType(d.Type)
b.WriteString(d.Name)
}
// AllocateMode are possible modes to p9.File.Allocate().
type AllocateMode struct {
KeepSize bool
PunchHole bool
NoHideStale bool
CollapseRange bool
ZeroRange bool
InsertRange bool
Unshare bool
}
// ToLinux converts to a value compatible with fallocate(2)'s mode.
func (a *AllocateMode) ToLinux() uint32 {
rv := uint32(0)
if a.KeepSize {
rv |= unix.FALLOC_FL_KEEP_SIZE
}
if a.PunchHole {
rv |= unix.FALLOC_FL_PUNCH_HOLE
}
if a.NoHideStale {
rv |= unix.FALLOC_FL_NO_HIDE_STALE
}
if a.CollapseRange {
rv |= unix.FALLOC_FL_COLLAPSE_RANGE
}
if a.ZeroRange {
rv |= unix.FALLOC_FL_ZERO_RANGE
}
if a.InsertRange {
rv |= unix.FALLOC_FL_INSERT_RANGE
}
if a.Unshare {
rv |= unix.FALLOC_FL_UNSHARE_RANGE
}
return rv
}
// Decode implements encoder.Decode.
func (a *AllocateMode) Decode(b *buffer) {
mask := b.Read32()
a.KeepSize = mask&0x01 != 0
a.PunchHole = mask&0x02 != 0
a.NoHideStale = mask&0x04 != 0
a.CollapseRange = mask&0x08 != 0
a.ZeroRange = mask&0x10 != 0
a.InsertRange = mask&0x20 != 0
a.Unshare = mask&0x40 != 0
}
// Encode implements encoder.Encode.
func (a *AllocateMode) Encode(b *buffer) {
mask := uint32(0)
if a.KeepSize {
mask |= 0x01
}
if a.PunchHole {
mask |= 0x02
}
if a.NoHideStale {
mask |= 0x04
}
if a.CollapseRange {
mask |= 0x08
}
if a.ZeroRange {
mask |= 0x10
}
if a.InsertRange {
mask |= 0x20
}
if a.Unshare {
mask |= 0x40
}
b.Write32(mask)
}
+6 -1
View File
@@ -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 = 6
highestSupportedVersion uint32 = 7
// lowestSupportedVersion is the lowest supported version X in a
// version string of the format 9P2000.L.Google.X.
@@ -143,3 +143,8 @@ func VersionSupportsAnonymous(v uint32) bool {
func VersionSupportsMultiUser(v uint32) bool {
return v >= 6
}
// versionSupportsTallocate returns true if version v supports Allocate().
func versionSupportsTallocate(v uint32) bool {
return v >= 7
}
+1
View File
@@ -29,6 +29,7 @@ import (
type Device struct {
fsutil.InodeGenericChecker `state:"nosave"`
fsutil.InodeNoExtendedAttributes `state:"nosave"`
fsutil.InodeNoopAllocate `state:"nosave"`
fsutil.InodeNoopRelease `state:"nosave"`
fsutil.InodeNoopTruncate `state:"nosave"`
fsutil.InodeNoopWriteOut `state:"nosave"`
+1
View File
@@ -46,6 +46,7 @@ const (
type Device struct {
fsutil.InodeGenericChecker `state:"nosave"`
fsutil.InodeNoExtendedAttributes `state:"nosave"`
fsutil.InodeNoopAllocate `state:"nosave"`
fsutil.InodeNoopRelease `state:"nosave"`
fsutil.InodeNoopTruncate `state:"nosave"`
fsutil.InodeNoopWriteOut `state:"nosave"`
+2 -1
View File
@@ -30,6 +30,7 @@ import (
type fullDevice struct {
fsutil.InodeGenericChecker `state:"nosave"`
fsutil.InodeNoExtendedAttributes `state:"nosave"`
fsutil.InodeNoopAllocate `state:"nosave"`
fsutil.InodeNoopRelease `state:"nosave"`
fsutil.InodeNoopTruncate `state:"nosave"`
fsutil.InodeNoopWriteOut `state:"nosave"`
@@ -59,7 +60,6 @@ func (f *fullDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.Fi
// +stateify savable
type fullFileOperations struct {
waiter.AlwaysReady `state:"nosave"`
fsutil.FileGenericSeek `state:"nosave"`
fsutil.FileNoIoctl `state:"nosave"`
fsutil.FileNoMMap `state:"nosave"`
@@ -69,6 +69,7 @@ type fullFileOperations struct {
fsutil.FileNotDirReaddir `state:"nosave"`
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
readZeros `state:"nosave"`
waiter.AlwaysReady `state:"nosave"`
}
var _ fs.FileOperations = (*fullFileOperations)(nil)
+5 -4
View File
@@ -29,6 +29,7 @@ import (
type nullDevice struct {
fsutil.InodeGenericChecker `state:"nosave"`
fsutil.InodeNoExtendedAttributes `state:"nosave"`
fsutil.InodeNoopAllocate `state:"nosave"`
fsutil.InodeNoopRelease `state:"nosave"`
fsutil.InodeNoopTruncate `state:"nosave"`
fsutil.InodeNoopWriteOut `state:"nosave"`
@@ -60,17 +61,17 @@ func (n *nullDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.Fi
// +stateify savable
type nullFileOperations struct {
waiter.AlwaysReady `state:"nosave"`
fsutil.FileGenericSeek `state:"nosave"`
fsutil.FileNoIoctl `state:"nosave"`
fsutil.FileNoMMap `state:"nosave"`
fsutil.FileNoopFlush `state:"nosave"`
fsutil.FileNoopFsync `state:"nosave"`
fsutil.FileNoopRead `state:"nosave"`
fsutil.FileNoopWrite `state:"nosave"`
fsutil.FileNoopRelease `state:"nosave"`
fsutil.FileNoopWrite `state:"nosave"`
fsutil.FileNotDirReaddir `state:"nosave"`
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
waiter.AlwaysReady `state:"nosave"`
}
var _ fs.FileOperations = (*nullFileOperations)(nil)
@@ -101,16 +102,16 @@ func (zd *zeroDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.F
// +stateify savable
type zeroFileOperations struct {
waiter.AlwaysReady `state:"nosave"`
fsutil.FileGenericSeek `state:"nosave"`
fsutil.FileNoIoctl `state:"nosave"`
fsutil.FileNoopFlush `state:"nosave"`
fsutil.FileNoopFsync `state:"nosave"`
fsutil.FileNoopRelease `state:"nosave"`
fsutil.FileNoopWrite `state:"nosave"`
fsutil.FileNotDirReaddir `state:"nosave"`
fsutil.FileNoIoctl `state:"nosave"`
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
readZeros `state:"nosave"`
waiter.AlwaysReady `state:"nosave"`
}
var _ fs.FileOperations = (*zeroFileOperations)(nil)
+6 -5
View File
@@ -29,6 +29,7 @@ import (
type randomDevice struct {
fsutil.InodeGenericChecker `state:"nosave"`
fsutil.InodeNoExtendedAttributes `state:"nosave"`
fsutil.InodeNoopAllocate `state:"nosave"`
fsutil.InodeNoopRelease `state:"nosave"`
fsutil.InodeNoopTruncate `state:"nosave"`
fsutil.InodeNoopWriteOut `state:"nosave"`
@@ -57,16 +58,16 @@ func (*randomDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.Fi
// +stateify savable
type randomFileOperations struct {
waiter.AlwaysReady `state:"nosave"`
fsutil.FileGenericSeek `state:"nosave"`
fsutil.FileNotDirReaddir `state:"nosave"`
fsutil.FileNoMMap `state:"nosave"`
fsutil.FileNoopFsync `state:"nosave"`
fsutil.FileNoopFlush `state:"nosave"`
fsutil.FileNoIoctl `state:"nosave"`
fsutil.FileNoMMap `state:"nosave"`
fsutil.FileNoopFlush `state:"nosave"`
fsutil.FileNoopFsync `state:"nosave"`
fsutil.FileNoopRelease `state:"nosave"`
fsutil.FileNoopWrite `state:"nosave"`
fsutil.FileNotDirReaddir `state:"nosave"`
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
waiter.AlwaysReady `state:"nosave"`
}
var _ fs.FileOperations = (*randomFileOperations)(nil)
+1
View File
@@ -113,5 +113,6 @@ go_test(
"//pkg/sentry/memmap",
"//pkg/sentry/safemem",
"//pkg/sentry/usermem",
"//pkg/syserror",
],
)
+9 -1
View File
@@ -149,7 +149,7 @@ func (h *HostMappable) Truncate(ctx context.Context, newSize int64) error {
}
// Invalidate COW mappings that may exist beyond the new size in case the file
// is being shrunk. Other mappinsg don't need to be invalidated because
// is being shrunk. Other mappings don't need to be invalidated because
// translate will just return identical mappings after invalidation anyway,
// and SIGBUS will be raised and handled when the mappings are touched.
//
@@ -167,6 +167,14 @@ func (h *HostMappable) Truncate(ctx context.Context, newSize int64) error {
return nil
}
// Allocate reserves space in the backing file.
func (h *HostMappable) Allocate(ctx context.Context, offset int64, length int64) error {
h.truncateMu.RLock()
err := h.backingFile.Allocate(ctx, offset, length)
h.truncateMu.RUnlock()
return err
}
// Write writes to the file backing this mappable.
func (h *HostMappable) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
h.truncateMu.RLock()
+25
View File
@@ -34,6 +34,7 @@ type SimpleFileInode struct {
InodeNoExtendedAttributes `state:"nosave"`
InodeNoopRelease `state:"nosave"`
InodeNoopWriteOut `state:"nosave"`
InodeNotAllocatable `state:"nosave"`
InodeNotDirectory `state:"nosave"`
InodeNotMappable `state:"nosave"`
InodeNotOpenable `state:"nosave"`
@@ -61,6 +62,7 @@ type NoReadWriteFileInode struct {
InodeNoExtendedAttributes `state:"nosave"`
InodeNoopRelease `state:"nosave"`
InodeNoopWriteOut `state:"nosave"`
InodeNotAllocatable `state:"nosave"`
InodeNotDirectory `state:"nosave"`
InodeNotMappable `state:"nosave"`
InodeNotSocket `state:"nosave"`
@@ -465,3 +467,26 @@ func (InodeDenyWriteChecker) Check(ctx context.Context, inode *fs.Inode, p fs.Pe
}
return fs.ContextCanAccessFile(ctx, inode, p)
}
//InodeNotAllocatable can be used by Inodes that do not support Allocate().
type InodeNotAllocatable struct{}
func (InodeNotAllocatable) Allocate(_ context.Context, _ *fs.Inode, _, _ int64) error {
return syserror.EOPNOTSUPP
}
// InodeNoopAllocate implements fs.InodeOperations.Allocate as a noop.
type InodeNoopAllocate struct{}
// Allocate implements fs.InodeOperations.Allocate.
func (InodeNoopAllocate) Allocate(_ context.Context, _ *fs.Inode, _, _ int64) error {
return nil
}
// InodeIsDirAllocate implements fs.InodeOperations.Allocate for directories.
type InodeIsDirAllocate struct{}
// Allocate implements fs.InodeOperations.Allocate.
func (InodeIsDirAllocate) Allocate(_ context.Context, _ *fs.Inode, _, _ int64) error {
return syserror.EISDIR
}
+28
View File
@@ -135,6 +135,10 @@ type CachedFileObject interface {
// the file was opened.
SetMaskedAttributes(ctx context.Context, mask fs.AttrMask, attr fs.UnstableAttr) error
// Allocate allows the caller to reserve disk space for the inode.
// It's equivalent to fallocate(2) with 'mode=0'.
Allocate(ctx context.Context, offset int64, length int64) error
// Sync instructs the remote filesystem to sync the file to stable storage.
Sync(ctx context.Context) error
@@ -336,6 +340,30 @@ func (c *CachingInodeOperations) Truncate(ctx context.Context, inode *fs.Inode,
return nil
}
// Allocate implements fs.InodeOperations.Allocate.
func (c *CachingInodeOperations) Allocate(ctx context.Context, offset, length int64) error {
newSize := offset + length
// c.attr.Size is protected by both c.attrMu and c.dataMu.
c.attrMu.Lock()
defer c.attrMu.Unlock()
c.dataMu.Lock()
defer c.dataMu.Unlock()
if newSize <= c.attr.Size {
return nil
}
now := ktime.NowFromContext(ctx)
if err := c.backingFile.Allocate(ctx, offset, length); err != nil {
return err
}
c.attr.Size = newSize
c.touchModificationTimeLocked(now)
return nil
}
// WriteOut implements fs.InodeOperations.WriteOut.
func (c *CachingInodeOperations) WriteOut(ctx context.Context, inode *fs.Inode) error {
c.attrMu.Lock()
@@ -26,6 +26,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/sentry/memmap"
"gvisor.googlesource.com/gvisor/pkg/sentry/safemem"
"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
"gvisor.googlesource.com/gvisor/pkg/syserror"
)
type noopBackingFile struct{}
@@ -50,6 +51,10 @@ func (noopBackingFile) FD() int {
return -1
}
func (noopBackingFile) Allocate(ctx context.Context, offset int64, length int64) error {
return nil
}
func TestSetPermissions(t *testing.T) {
ctx := contexttest.Context(t)
@@ -237,6 +242,10 @@ func (*sliceBackingFile) FD() int {
return -1
}
func (f *sliceBackingFile) Allocate(ctx context.Context, offset int64, length int64) error {
return syserror.EOPNOTSUPP
}
type noopMappingSpace struct{}
// Invalidate implements memmap.MappingSpace.Invalidate.
+7
View File
@@ -59,6 +59,13 @@ func (c *contextFile) setAttr(ctx context.Context, valid p9.SetAttrMask, attr p9
return err
}
func (c *contextFile) allocate(ctx context.Context, mode p9.AllocateMode, offset, length uint64) error {
ctx.UninterruptibleSleepStart(false)
err := c.file.Allocate(mode, offset, length)
ctx.UninterruptibleSleepFinish(false)
return err
}
func (c *contextFile) rename(ctx context.Context, directory contextFile, name string) error {
ctx.UninterruptibleSleepStart(false)
err := c.file.Rename(directory.file, name)

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