Added support for file truncation.

geesefs-0-30-9
Aaron Jacobs 2015-03-06 05:56:54 +11:00
commit 510f051c33
6 changed files with 206 additions and 1 deletions

View File

@ -57,6 +57,14 @@ type FileSystem interface {
ctx context.Context, ctx context.Context,
req *GetInodeAttributesRequest) (*GetInodeAttributesResponse, error) req *GetInodeAttributesRequest) (*GetInodeAttributesResponse, error)
// Change attributes for an inode.
//
// The kernel calls this for obvious cases like chmod(2), and for less
// obvious cases like ftrunctate(2).
SetInodeAttributes(
ctx context.Context,
req *SetInodeAttributesRequest) (*SetInodeAttributesResponse, error)
// Forget an inode ID previously issued (e.g. by LookUpInode or MkDir). The // Forget an inode ID previously issued (e.g. by LookUpInode or MkDir). The
// kernel calls this when removing an inode from its internal caches. // kernel calls this when removing an inode from its internal caches.
ForgetInode( ForgetInode(
@ -413,7 +421,24 @@ type GetInodeAttributesRequest struct {
type GetInodeAttributesResponse struct { type GetInodeAttributesResponse struct {
// Attributes for the inode, and the time at which they should expire. See // Attributes for the inode, and the time at which they should expire. See
// notes on LookUpInodeResponse.AttributesExpiration for more. // notes on ChildInodeEntry.AttributesExpiration for more.
Attributes InodeAttributes
AttributesExpiration time.Time
}
type SetInodeAttributesRequest struct {
Header RequestHeader
// The inode of interest.
Inode InodeID
// The attributes to modify, or nil for attributes that don't need a change.
Size *uint64
}
type SetInodeAttributesResponse struct {
// The new attributes for the inode, and the time at which they should
// expire. See notes on ChildInodeEntry.AttributesExpiration for more.
Attributes InodeAttributes Attributes InodeAttributes
AttributesExpiration time.Time AttributesExpiration time.Time
} }

View File

@ -45,6 +45,13 @@ func (fs *NotImplementedFileSystem) GetInodeAttributes(
return nil, fuse.ENOSYS return nil, fuse.ENOSYS
} }
func (fs *NotImplementedFileSystem) SetInodeAttributes(
ctx context.Context,
req *fuse.SetInodeAttributesRequest) (
*fuse.SetInodeAttributesResponse, error) {
return nil, fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) ForgetInode( func (fs *NotImplementedFileSystem) ForgetInode(
ctx context.Context, ctx context.Context,
req *fuse.ForgetInodeRequest) (*fuse.ForgetInodeResponse, error) { req *fuse.ForgetInodeRequest) (*fuse.ForgetInodeResponse, error) {

View File

@ -267,6 +267,32 @@ func (fs *memFS) GetInodeAttributes(
return return
} }
func (fs *memFS) SetInodeAttributes(
ctx context.Context,
req *fuse.SetInodeAttributesRequest) (
resp *fuse.SetInodeAttributesResponse, err error) {
resp = &fuse.SetInodeAttributesResponse{}
fs.mu.RLock()
defer fs.mu.RUnlock()
// Grab the inode.
inode := fs.getInodeForModifyingOrDie(req.Inode)
defer inode.mu.Unlock()
// Handle the request.
inode.SetAttributes(req.Size)
// Fill in the response.
resp.Attributes = inode.attributes
// We don't spontaneously mutate, so the kernel can cache as long as it wants
// (since it also handles invalidation).
resp.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
return
}
func (fs *memFS) MkDir( func (fs *memFS) MkDir(
ctx context.Context, ctx context.Context,
req *fuse.MkDirRequest) (resp *fuse.MkDirResponse, err error) { req *fuse.MkDirRequest) (resp *fuse.MkDirResponse, err error) {

View File

@ -369,3 +369,27 @@ func (inode *inode) WriteAt(p []byte, off int64) (n int, err error) {
return return
} }
// Update attributes from non-nil parameters.
//
// EXCLUSIVE_LOCKS_REQUIRED(inode.mu)
func (inode *inode) SetAttributes(size *uint64) {
// Update the modification time.
inode.attributes.Mtime = inode.clock.Now()
// Truncate?
if size != nil {
intSize := int(*size)
// Update contents.
if intSize <= len(inode.contents) {
inode.contents = inode.contents[:intSize]
} else {
padding := make([]byte, intSize-len(inode.contents))
inode.contents = append(inode.contents, padding...)
}
// Update attributes.
inode.attributes.Size = *size
}
}

View File

@ -856,3 +856,98 @@ func (t *MemFSTest) ReadsPastEndOfFile() {
ExpectEq(0, n) ExpectEq(0, n)
ExpectEq("", string(buf[:n])) ExpectEq("", string(buf[:n]))
} }
func (t *MemFSTest) Truncate_Smaller() {
var err error
fileName := path.Join(t.mfs.Dir(), "foo")
// Create a file.
err = ioutil.WriteFile(fileName, []byte("taco"), 0600)
AssertEq(nil, err)
// Open it for modification.
f, err := os.OpenFile(fileName, os.O_RDWR, 0)
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Truncate it.
err = f.Truncate(2)
AssertEq(nil, err)
// Stat it.
fi, err := f.Stat()
AssertEq(nil, err)
ExpectEq(2, fi.Size())
// Read the contents.
contents, err := ioutil.ReadFile(fileName)
AssertEq(nil, err)
ExpectEq("ta", string(contents))
}
func (t *MemFSTest) Truncate_SameSize() {
var err error
fileName := path.Join(t.mfs.Dir(), "foo")
// Create a file.
err = ioutil.WriteFile(fileName, []byte("taco"), 0600)
AssertEq(nil, err)
// Open it for modification.
f, err := os.OpenFile(fileName, os.O_RDWR, 0)
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Truncate it.
err = f.Truncate(4)
AssertEq(nil, err)
// Stat it.
fi, err := f.Stat()
AssertEq(nil, err)
ExpectEq(4, fi.Size())
// Read the contents.
contents, err := ioutil.ReadFile(fileName)
AssertEq(nil, err)
ExpectEq("taco", string(contents))
}
func (t *MemFSTest) Truncate_Larger() {
var err error
fileName := path.Join(t.mfs.Dir(), "foo")
// Create a file.
err = ioutil.WriteFile(fileName, []byte("taco"), 0600)
AssertEq(nil, err)
// Open it for modification.
f, err := os.OpenFile(fileName, os.O_RDWR, 0)
t.toClose = append(t.toClose, f)
AssertEq(nil, err)
// Truncate it.
err = f.Truncate(6)
AssertEq(nil, err)
// Stat it.
fi, err := f.Stat()
AssertEq(nil, err)
ExpectEq(6, fi.Size())
// Read the contents.
contents, err := ioutil.ReadFile(fileName)
AssertEq(nil, err)
ExpectEq("taco\x00\x00", string(contents))
}
func (t *MemFSTest) Chmod() {
AssertTrue(false, "TODO")
}
func (t *MemFSTest) Chtimes() {
AssertTrue(false, "TODO")
}

View File

@ -171,6 +171,34 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
s.logger.Println("Responding:", fuseResp) s.logger.Println("Responding:", fuseResp)
typed.Respond(fuseResp) typed.Respond(fuseResp)
case *bazilfuse.SetattrRequest:
// Convert the request.
req := &SetInodeAttributesRequest{
Header: convertHeader(typed.Header),
Inode: InodeID(typed.Header.Node),
}
if typed.Valid&bazilfuse.SetattrSize != 0 {
req.Size = &typed.Size
}
// Call the file system.
resp, err := s.fs.SetInodeAttributes(ctx, req)
if err != nil {
s.logger.Println("Responding:", err)
typed.RespondError(err)
return
}
// Convert the response.
fuseResp := &bazilfuse.SetattrResponse{
Attr: convertAttributes(req.Inode, resp.Attributes),
AttrValid: resp.AttributesExpiration.Sub(s.clock.Now()),
}
s.logger.Println("Responding:", fuseResp)
typed.Respond(fuseResp)
case *bazilfuse.MkdirRequest: case *bazilfuse.MkdirRequest:
// Convert the request. // Convert the request.
req := &MkDirRequest{ req := &MkDirRequest{