diff --git a/file_system.go b/file_system.go index 0588966..d67fcee 100644 --- a/file_system.go +++ b/file_system.go @@ -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 } diff --git a/fuseutil/not_implemented_file_system.go b/fuseutil/not_implemented_file_system.go index 7cf7e2d..761723b 100644 --- a/fuseutil/not_implemented_file_system.go +++ b/fuseutil/not_implemented_file_system.go @@ -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) { diff --git a/samples/memfs/fs.go b/samples/memfs/fs.go index b65bc26..730636f 100644 --- a/samples/memfs/fs.go +++ b/samples/memfs/fs.go @@ -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) { diff --git a/samples/memfs/inode.go b/samples/memfs/inode.go index 3505c13..a878fcc 100644 --- a/samples/memfs/inode.go +++ b/samples/memfs/inode.go @@ -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 + } +} diff --git a/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index 0fa821a..6bd4252 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -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") +} diff --git a/server.go b/server.go index cc343b9..811c34b 100644 --- a/server.go +++ b/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{