Added support for file truncation.
commit
510f051c33
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
28
server.go
28
server.go
|
@ -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{
|
||||
|
|
Loading…
Reference in New Issue