From 632b1e612010e42a6a4279d1567a1a33ea29741d Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 6 Mar 2015 05:45:17 +1100 Subject: [PATCH 1/9] Added FileSystem.SetInodeAttributes. --- file_system.go | 27 ++++++++++++++++++++++++++- samples/memfs/memfs_test.go | 8 ++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) 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/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index 0fa821a..10678ad 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -856,3 +856,11 @@ func (t *MemFSTest) ReadsPastEndOfFile() { ExpectEq(0, n) ExpectEq("", string(buf[:n])) } + +func (t *MemFSTest) Chmod() { + AssertTrue(false, "TODO") +} + +func (t *MemFSTest) Chtimes() { + AssertTrue(false, "TODO") +} From 528b112c72fc36d03e3e99ebaa5270663ac95ce6 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 6 Mar 2015 05:45:45 +1100 Subject: [PATCH 2/9] Added NotImplementedFileSystem.SetInodeAttributes. --- fuseutil/not_implemented_file_system.go | 7 +++++++ 1 file changed, 7 insertions(+) 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) { From 08f5bb7cedf71569835e3cd7883f7c44a8682e06 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 6 Mar 2015 05:47:30 +1100 Subject: [PATCH 3/9] Added server support for setattr. --- server.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) 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{ From 08afae57ded7020d33f809aa3fbc3f6d2836e29c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 6 Mar 2015 05:50:58 +1100 Subject: [PATCH 4/9] Added inode.SetAttributes. --- samples/memfs/inode.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/samples/memfs/inode.go b/samples/memfs/inode.go index 3505c13..b011eb5 100644 --- a/samples/memfs/inode.go +++ b/samples/memfs/inode.go @@ -369,3 +369,22 @@ 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() + + // Do we need to truncate? + if size != nil { + intSize := int(*size) + if intSize <= len(inode.contents) { + inode.contents = inode.contents[:intSize] + } else { + padding := make([]byte, intSize-len(inode.contents)) + inode.contents = append(inode.contents, padding...) + } + } +} From 826e9def70f38a25b68f51f6d463c811506f7e0b Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 6 Mar 2015 05:51:49 +1100 Subject: [PATCH 5/9] Implemented memFS.SetInodeAttributes. --- samples/memfs/fs.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) 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) { From 7173c5e9fb6ade9be12eac2f0ebeaad00af64699 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 6 Mar 2015 05:52:08 +1100 Subject: [PATCH 6/9] Added some test names. --- samples/memfs/memfs_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index 10678ad..c551fe4 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -857,6 +857,18 @@ func (t *MemFSTest) ReadsPastEndOfFile() { ExpectEq("", string(buf[:n])) } +func (t *MemFSTest) Truncate_Smaller() { + AssertTrue(false, "TODO") +} + +func (t *MemFSTest) Truncate_SameSize() { + AssertTrue(false, "TODO") +} + +func (t *MemFSTest) Truncate_Larger() { + AssertTrue(false, "TODO") +} + func (t *MemFSTest) Chmod() { AssertTrue(false, "TODO") } From 9dc1d04623326949554754b83d3deb00e6d1acc6 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 6 Mar 2015 05:54:20 +1100 Subject: [PATCH 7/9] MemFSTest.Truncate_Smaller --- samples/memfs/memfs_test.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index c551fe4..76dadec 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -858,7 +858,27 @@ func (t *MemFSTest) ReadsPastEndOfFile() { } func (t *MemFSTest) Truncate_Smaller() { - AssertTrue(false, "TODO") + 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) + + // Read the contents. + contents, err := ioutil.ReadFile(fileName) + AssertEq(nil, err) + ExpectEq("ta", string(contents)) } func (t *MemFSTest) Truncate_SameSize() { From 910d3b2f9fbde33b5c63b92abb6abd384a1f0123 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 6 Mar 2015 05:56:12 +1100 Subject: [PATCH 8/9] Oops, preserve the size invariant. --- samples/memfs/inode.go | 7 ++++++- samples/memfs/memfs_test.go | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/samples/memfs/inode.go b/samples/memfs/inode.go index b011eb5..a878fcc 100644 --- a/samples/memfs/inode.go +++ b/samples/memfs/inode.go @@ -377,14 +377,19 @@ func (inode *inode) SetAttributes(size *uint64) { // Update the modification time. inode.attributes.Mtime = inode.clock.Now() - // Do we need to truncate? + // 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 76dadec..323e3c7 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -875,6 +875,11 @@ func (t *MemFSTest) Truncate_Smaller() { 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) From 1e38634464f40f423ee46282b0cdda2ba2b580c5 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 6 Mar 2015 05:56:40 +1100 Subject: [PATCH 9/9] MemFSTest.Truncate_Larger --- samples/memfs/memfs_test.go | 54 +++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index 323e3c7..6bd4252 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -887,11 +887,61 @@ func (t *MemFSTest) Truncate_Smaller() { } func (t *MemFSTest) Truncate_SameSize() { - AssertTrue(false, "TODO") + 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() { - AssertTrue(false, "TODO") + 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() {