From 6c43d2aa3bc5b8f809693745999d21674fd7ce14 Mon Sep 17 00:00:00 2001
From: Han-Wen Nienhuys <hanwen@google.com>
Date: Sun, 24 Feb 2013 14:17:53 +0100
Subject: [PATCH] Support FALLOCATE opcode.

---
 fuse/api.go           |  3 +++
 fuse/defaultfile.go   |  4 ++++
 fuse/defaultnode.go   |  4 ++++
 fuse/defaultraw.go    |  4 ++++
 fuse/files.go         | 14 ++++++++++++++
 fuse/fsops.go         |  7 +++++++
 fuse/lockingfs.go     |  5 +++++
 fuse/loopback.go      |  3 +++
 fuse/loopback_test.go | 16 ++++++++++++++++
 fuse/opcode.go        | 17 +++++++++++++----
 fuse/pathfs.go        | 20 ++++++++++++++++++++
 11 files changed, 93 insertions(+), 4 deletions(-)

diff --git a/fuse/api.go b/fuse/api.go
index 7012989..d0e7fbd 100644
--- a/fuse/api.go
+++ b/fuse/api.go
@@ -79,6 +79,7 @@ type FsNode interface {
 	Chown(file File, uid uint32, gid uint32, context *Context) (code Status)
 	Truncate(file File, size uint64, context *Context) (code Status)
 	Utimens(file File, atime *time.Time, mtime *time.Time, context *Context) (code Status)
+	Fallocate(file File, off uint64, size uint64, mode uint32, context *Context) (code Status)
 
 	StatFs() *StatfsOut
 }
@@ -180,6 +181,7 @@ type File interface {
 	Chown(uid uint32, gid uint32) Status
 	Chmod(perms uint32) Status
 	Utimens(atime *time.Time, mtime *time.Time) Status
+	Allocate(off uint64, size uint64, mode uint32) (code Status)
 }
 
 // The result of Read is an array of bytes, but for performance
@@ -321,6 +323,7 @@ type RawFileSystem interface {
 	Write(*Context, *raw.WriteIn, []byte) (written uint32, code Status)
 	Flush(context *Context, input *raw.FlushIn) Status
 	Fsync(*Context, *raw.FsyncIn) (code Status)
+	Fallocate(Context *Context, in *raw.FallocateIn) (code Status)
 
 	// Directory handling
 	OpenDir(out *raw.OpenOut, context *Context, input *raw.OpenIn) (status Status)
diff --git a/fuse/defaultfile.go b/fuse/defaultfile.go
index 50f21b6..9b7d4ea 100644
--- a/fuse/defaultfile.go
+++ b/fuse/defaultfile.go
@@ -65,3 +65,7 @@ func (f *DefaultFile) Chmod(perms uint32) Status {
 func (f *DefaultFile) Ioctl(input *raw.IoctlIn) (output *raw.IoctlOut, data []byte, code Status) {
 	return nil, nil, ENOSYS
 }
+
+func (f *DefaultFile) Allocate(off uint64, size uint64, mode uint32) (code Status) {
+	return ENOSYS
+}
diff --git a/fuse/defaultnode.go b/fuse/defaultnode.go
index 84505cc..ade80e3 100644
--- a/fuse/defaultnode.go
+++ b/fuse/defaultnode.go
@@ -154,3 +154,7 @@ func (n *DefaultFsNode) Truncate(file File, size uint64, context *Context) (code
 func (n *DefaultFsNode) Utimens(file File, atime *time.Time, mtime *time.Time, context *Context) (code Status) {
 	return ENOSYS
 }
+
+func (n *DefaultFsNode) Fallocate(file File, off uint64, size uint64, mode uint32, context *Context) (code Status) {
+	return ENOSYS
+}
diff --git a/fuse/defaultraw.go b/fuse/defaultraw.go
index 83bfaff..9d9820f 100644
--- a/fuse/defaultraw.go
+++ b/fuse/defaultraw.go
@@ -131,3 +131,7 @@ func (fs *DefaultRawFileSystem) ReleaseDir(context *Context, input *raw.ReleaseI
 func (fs *DefaultRawFileSystem) FsyncDir(context *Context, input *raw.FsyncIn) (code Status) {
 	return ENOSYS
 }
+
+func (fs *DefaultRawFileSystem) Fallocate(context *Context, in *raw.FallocateIn) (code Status) {
+	return ENOSYS
+}
diff --git a/fuse/files.go b/fuse/files.go
index 9fe2369..cf74f50 100644
--- a/fuse/files.go
+++ b/fuse/files.go
@@ -193,6 +193,16 @@ func (f *LoopbackFile) GetAttr(a *Attr) Status {
 	return OK
 }
 
+func (f *LoopbackFile) Allocate(off uint64, sz uint64, mode uint32) Status {
+	f.lock.Lock()
+	err := syscall.Fallocate(int(f.File.Fd()), mode, int64(off), int64(sz))
+	f.lock.Unlock()
+	if err != nil {
+		return ToStatus(err)
+	}
+	return OK
+}
+
 ////////////////////////////////////////////////////////////////
 
 // ReadOnlyFile is a wrapper that denies writable operations
@@ -226,3 +236,7 @@ func (f *ReadOnlyFile) Chmod(mode uint32) Status {
 func (f *ReadOnlyFile) Chown(uid uint32, gid uint32) Status {
 	return EPERM
 }
+
+func (f *ReadOnlyFile) Allocate(off uint64, sz uint64, mode uint32) Status {
+	return EPERM
+}
diff --git a/fuse/fsops.go b/fuse/fsops.go
index 6ad7da2..7513380 100644
--- a/fuse/fsops.go
+++ b/fuse/fsops.go
@@ -214,6 +214,13 @@ func (c *FileSystemConnector) SetAttr(out *raw.AttrOut, context *Context, input
 	return code
 }
 
+func (c *FileSystemConnector) Fallocate(context *Context, in *raw.FallocateIn) (code Status) {
+	n := c.toInode(context.NodeId)
+	opened := n.mount.getOpenedFile(in.Fh)
+	
+	return n.fsInode.Fallocate(opened, in.Offset, in.Length, in.Mode, context)
+}
+
 func (c *FileSystemConnector) Readlink(context *Context) (out []byte, code Status) {
 	n := c.toInode(context.NodeId)
 	return n.fsInode.Readlink(context)
diff --git a/fuse/lockingfs.go b/fuse/lockingfs.go
index 1fe9a9f..02fd9c8 100644
--- a/fuse/lockingfs.go
+++ b/fuse/lockingfs.go
@@ -332,6 +332,11 @@ func (fs *LockingRawFileSystem) StatFs(out *StatfsOut, context *Context) (code S
 	return fs.RawFS.StatFs(out, context)
 }
 
+func (fs *LockingRawFileSystem) Fallocate(c *Context, in *raw.FallocateIn) (code Status) {
+	defer fs.locked()()
+	return fs.RawFS.Fallocate(c, in)
+}
+
 func (fs *LockingRawFileSystem) String() string {
 	defer fs.locked()()
 	return fmt.Sprintf("Locked(%s)", fs.RawFS.String())
diff --git a/fuse/loopback.go b/fuse/loopback.go
index cfb6781..93ef403 100644
--- a/fuse/loopback.go
+++ b/fuse/loopback.go
@@ -166,3 +166,6 @@ func (fs *LoopbackFileSystem) Create(path string, flags uint32, mode uint32, con
 	f, err := os.OpenFile(fs.GetPath(path), int(flags)|os.O_CREATE, os.FileMode(mode))
 	return &LoopbackFile{File: f}, ToStatus(err)
 }
+
+
+
diff --git a/fuse/loopback_test.go b/fuse/loopback_test.go
index 748068c..c3f0aa3 100644
--- a/fuse/loopback_test.go
+++ b/fuse/loopback_test.go
@@ -815,3 +815,19 @@ func TestUmask(t *testing.T) {
 		t.Errorf("got %o, expect mode %o for file %s", got, expect, fn)
 	}
 }
+
+func TestFallocate(t *testing.T) {
+	ts := NewTestCase(t)
+	defer ts.Cleanup()
+	rwFile, err := os.OpenFile(ts.mnt+"/file", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
+	CheckSuccess(err)
+	defer rwFile.Close()
+	err = syscall.Fallocate(int(rwFile.Fd()), 0, 1024, 4096)
+	CheckSuccess(err)
+	fi, err := os.Lstat(ts.orig + "/file")
+	CheckSuccess(err)
+	if fi.Size() < (1024+4096) {
+		t.Fatalf("fallocate should have changed file size. Got %d bytes",
+			fi.Size())
+	}
+}
diff --git a/fuse/opcode.go b/fuse/opcode.go
index fbdeecf..e1bc7ed 100644
--- a/fuse/opcode.go
+++ b/fuse/opcode.go
@@ -54,13 +54,14 @@ const (
 	_OP_POLL         = int32(40)
 	_OP_NOTIFY_REPLY = int32(41)
 	_OP_BATCH_FORGET = int32(42)
+	_OP_FALLOCATE    = int32(43) // protocol version 19.
 
 	// Ugh - what will happen if FUSE introduces a new opcode here?
-	_OP_NOTIFY_ENTRY  = int32(51)
-	_OP_NOTIFY_INODE  = int32(52)
-	_OP_NOTIFY_DELETE = int32(53)
+	_OP_NOTIFY_ENTRY  = int32(100)
+	_OP_NOTIFY_INODE  = int32(101)
+	_OP_NOTIFY_DELETE = int32(102) // protocol version 18
 
-	_OPCODE_COUNT = int32(54)
+	_OPCODE_COUNT = int32(103)
 )
 
 ////////////////////////////////////////////////////////////////
@@ -337,6 +338,10 @@ func doDestroy(state *MountState, req *request) {
 	req.status = OK
 }
 
+func doFallocate(state *MountState, req *request) {
+	req.status = state.fileSystem.Fallocate(&req.context, (*raw.FallocateIn)(req.inData))
+}
+
 ////////////////////////////////////////////////////////////////
 
 type operationFunc func(*MountState, *request)
@@ -410,6 +415,7 @@ func init() {
 		_OP_BMAP:         unsafe.Sizeof(raw.BmapIn{}),
 		_OP_IOCTL:        unsafe.Sizeof(raw.IoctlIn{}),
 		_OP_POLL:         unsafe.Sizeof(raw.PollIn{}),
+		_OP_FALLOCATE:    unsafe.Sizeof(raw.FallocateIn{}),
 	} {
 		operationHandlers[op].InputSize = sz
 	}
@@ -483,6 +489,7 @@ func init() {
 		_OP_NOTIFY_ENTRY:  "NOTIFY_ENTRY",
 		_OP_NOTIFY_INODE:  "NOTIFY_INODE",
 		_OP_NOTIFY_DELETE: "NOTIFY_DELETE",
+		_OP_FALLOCATE:     "FALLOCATE",
 	} {
 		operationHandlers[op].Name = v
 	}
@@ -521,6 +528,7 @@ func init() {
 		_OP_STATFS:       doStatFs,
 		_OP_IOCTL:        doIoctl,
 		_OP_DESTROY: 	  doDestroy,
+		_OP_FALLOCATE:    doFallocate,
 	} {
 		operationHandlers[op].Func = v
 	}
@@ -565,6 +573,7 @@ func init() {
 		_OP_MKDIR:        func(ptr unsafe.Pointer) interface{} { return (*raw.MkdirIn)(ptr) },
 		_OP_RELEASE:      func(ptr unsafe.Pointer) interface{} { return (*raw.ReleaseIn)(ptr) },
 		_OP_RELEASEDIR:   func(ptr unsafe.Pointer) interface{} { return (*raw.ReleaseIn)(ptr) },
+		_OP_FALLOCATE:    func(ptr unsafe.Pointer) interface{} { return (*raw.FallocateIn)(ptr) },
 	} {
 		operationHandlers[op].DecodeIn = f
 	}
diff --git a/fuse/pathfs.go b/fuse/pathfs.go
index c9e4ed9..361f588 100644
--- a/fuse/pathfs.go
+++ b/fuse/pathfs.go
@@ -640,3 +640,23 @@ func (n *pathInode) Utimens(file File, atime *time.Time, mtime *time.Time, conte
 	}
 	return code
 }
+
+func (n *pathInode) Fallocate(file File, off uint64, size uint64, mode uint32, context *Context) (code Status) {
+	if file != nil {
+		code = file.Allocate(off, size, mode)
+		if code.Ok() {
+			return code
+		}
+	}
+	
+	files := n.inode.Files(O_ANYWRITE)
+	for _, f := range files {
+		// TODO - pass context
+		code = f.Allocate(off, size, mode)
+		if code.Ok() {
+			return code
+		}
+	}
+
+	return code
+}
-- 
2.30.9