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,
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
// kernel calls this when removing an inode from its internal caches.
ForgetInode(
@ -413,7 +421,24 @@ type GetInodeAttributesRequest struct {
type GetInodeAttributesResponse struct {
// 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
AttributesExpiration time.Time
}

View File

@ -45,6 +45,13 @@ func (fs *NotImplementedFileSystem) GetInodeAttributes(
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(
ctx context.Context,
req *fuse.ForgetInodeRequest) (*fuse.ForgetInodeResponse, error) {

View File

@ -267,6 +267,32 @@ func (fs *memFS) GetInodeAttributes(
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(
ctx context.Context,
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
}
// 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("", 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)
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:
// Convert the request.
req := &MkDirRequest{