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