diff options
| author | Jakob Unterwurzacher | 2020-07-12 12:59:01 +0200 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2020-07-12 12:59:01 +0200 | 
| commit | 6196a5b5fe78f7a5b8e38c00e656f70c1592e1df (patch) | |
| tree | 6bb2eb3058080f589a29d7104a09c2c6f3a1162e | |
| parent | d539a4c21474a5d45bb3b8432d7b6f07664e61bd (diff) | |
v2api: File2: implement Release, Read, Write, Fsync, Flush, Allocate
Fortunately, this just means fixing up the function
signatures.
| -rw-r--r-- | internal/fusefrontend/file2.go | 78 | ||||
| -rw-r--r-- | internal/fusefrontend/file2_allocate_truncate.go | 60 | ||||
| -rw-r--r-- | internal/fusefrontend/file2_api_check.go | 12 | ||||
| -rw-r--r-- | internal/fusefrontend/file2_holes.go | 24 | ||||
| -rw-r--r-- | internal/fusefrontend/node.go | 2 | 
5 files changed, 89 insertions, 87 deletions
| diff --git a/internal/fusefrontend/file2.go b/internal/fusefrontend/file2.go index 7fe3d3a..0de325c 100644 --- a/internal/fusefrontend/file2.go +++ b/internal/fusefrontend/file2.go @@ -25,6 +25,7 @@ import (  	"github.com/rfjakob/gocryptfs/internal/tlog"  ) +// File2 implements the go-fuse v2 API (github.com/hanwen/go-fuse/v2/fs)  type File2 struct {  	fd *os.File  	// Has Release() already been called on this file? This also means that the @@ -129,7 +130,7 @@ func (f *File2) createHeader() (fileID []byte, err error) {  //  // Called by Read() for normal reading,  // by Write() and Truncate() via doWrite() for Read-Modify-Write. -func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Status) { +func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Errno) {  	// Get the file ID, either from the open file table, or from disk.  	var fileID []byte  	f.fileTableEntry.IDLock.Lock() @@ -144,7 +145,7 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat  			f.fileTableEntry.IDLock.Unlock()  			if err == io.EOF {  				// Empty file -				return nil, fuse.OK +				return nil, 0  			}  			buf := make([]byte, 100)  			n, _ := f.fd.ReadAt(buf, 0) @@ -152,7 +153,7 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat  			hexdump := hex.EncodeToString(buf)  			tlog.Warn.Printf("doRead %d: corrupt header: %v\nFile hexdump (%d bytes): %s",  				f.qIno.Ino, err, n, hexdump) -			return nil, fuse.EIO +			return nil, syscall.EIO  		}  		// Save into the file table  		f.fileTableEntry.ID = fileID @@ -173,12 +174,12 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat  	n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset))  	if err != nil && err != io.EOF {  		tlog.Warn.Printf("read: ReadAt: %s", err.Error()) -		return nil, fuse.ToStatus(err) +		return nil, fs.ToErrno(err)  	}  	// The ReadAt came back empty. We can skip all the decryption and return early.  	if n == 0 {  		f.rootNode.contentEnc.CReqPool.Put(ciphertext) -		return dst, fuse.OK +		return dst, 0  	}  	// Truncate ciphertext buffer down to actually read bytes  	ciphertext = ciphertext[0:n] @@ -198,7 +199,7 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat  		} else {  			curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))  			tlog.Warn.Printf("doRead %d: corrupt block #%d: %v", f.qIno.Ino, curruptBlockNo, err) -			return nil, fuse.EIO +			return nil, syscall.EIO  		}  	} @@ -216,15 +217,15 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Stat  	out = append(dst, out...)  	f.rootNode.contentEnc.PReqPool.Put(plaintext) -	return out, fuse.OK +	return out, 0  }  // Read - FUSE call -func (f *File2) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fuse.Status) { +func (f *File2) Read(ctx context.Context, buf []byte, off int64) (resultData fuse.ReadResult, errno syscall.Errno) {  	if len(buf) > fuse.MAX_KERNEL_WRITE {  		// This would crash us due to our fixed-size buffer pool  		tlog.Warn.Printf("Read: rejecting oversized request with EMSGSIZE, len=%d", len(buf)) -		return nil, fuse.Status(syscall.EMSGSIZE) +		return nil, syscall.EMSGSIZE  	}  	f.fdLock.RLock()  	defer f.fdLock.RUnlock() @@ -236,15 +237,15 @@ func (f *File2) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fu  	if f.rootNode.args.SerializeReads {  		serialize_reads.Wait(off, len(buf))  	} -	out, status := f.doRead(buf[:0], uint64(off), uint64(len(buf))) +	out, errno := f.doRead(buf[:0], uint64(off), uint64(len(buf)))  	if f.rootNode.args.SerializeReads {  		serialize_reads.Done()  	} -	if status != fuse.OK { -		return nil, status +	if errno != 0 { +		return nil, errno  	} -	tlog.Debug.Printf("ino%d: Read: status %v, returning %d bytes", f.qIno.Ino, status, len(out)) -	return fuse.ReadResultData(out), status +	tlog.Debug.Printf("ino%d: Read: errno=%d, returning %d bytes", f.qIno.Ino, errno, len(out)) +	return fuse.ReadResultData(out), errno  }  // doWrite - encrypt "data" and write it to plaintext offset "off" @@ -256,7 +257,7 @@ func (f *File2) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fu  // and by Truncate() to rewrite the last file block.  //  // Empty writes do nothing and are allowed. -func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) { +func (f *File2) doWrite(data []byte, off int64) (uint32, syscall.Errno) {  	fileWasEmpty := false  	// Get the file ID, create a new one if it does not exist yet.  	var fileID []byte @@ -274,7 +275,7 @@ func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) {  			fileWasEmpty = true  		}  		if err != nil { -			return 0, fuse.ToStatus(err) +			return 0, fs.ToErrno(err)  		}  		f.fileTableEntry.ID = fileID  	} @@ -287,10 +288,10 @@ func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) {  		// Incomplete block -> Read-Modify-Write  		if b.IsPartial() {  			// Read -			oldData, status := f.doRead(nil, b.BlockPlainOff(), f.contentEnc.PlainBS()) -			if status != fuse.OK { -				tlog.Warn.Printf("ino%d fh%d: RMW read failed: %s", f.qIno.Ino, f.intFd(), status.String()) -				return 0, status +			oldData, errno := f.doRead(nil, b.BlockPlainOff(), f.contentEnc.PlainBS()) +			if errno != 0 { +				tlog.Warn.Printf("ino%d fh%d: RMW read failed: errno=%d", f.qIno.Ino, f.intFd(), errno) +				return 0, errno  			}  			// Modify  			blockData = f.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip)) @@ -321,7 +322,7 @@ func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) {  					tlog.Warn.Printf("ino%d fh%d: doWrite: rollback failed: %v", f.qIno.Ino, f.intFd(), err2)  				}  			} -			return 0, fuse.ToStatus(err) +			return 0, fs.ToErrno(err)  		}  	}  	// Write @@ -331,9 +332,9 @@ func (f *File2) doWrite(data []byte, off int64) (uint32, fuse.Status) {  	if err != nil {  		tlog.Warn.Printf("ino%d fh%d: doWrite: WriteAt off=%d len=%d failed: %v",  			f.qIno.Ino, f.intFd(), cOff, len(ciphertext), err) -		return 0, fuse.ToStatus(err) +		return 0, fs.ToErrno(err)  	} -	return uint32(len(data)), fuse.OK +	return uint32(len(data)), 0  }  // isConsecutiveWrite returns true if the current write @@ -349,18 +350,18 @@ func (f *File2) isConsecutiveWrite(off int64) bool {  // Write - FUSE call  //  // If the write creates a hole, pads the file to the next block boundary. -func (f *File2) Write(data []byte, off int64) (uint32, fuse.Status) { +func (f *File2) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) {  	if len(data) > fuse.MAX_KERNEL_WRITE {  		// This would crash us due to our fixed-size buffer pool  		tlog.Warn.Printf("Write: rejecting oversized request with EMSGSIZE, len=%d", len(data)) -		return 0, fuse.Status(syscall.EMSGSIZE) +		return 0, syscall.EMSGSIZE  	}  	f.fdLock.RLock()  	defer f.fdLock.RUnlock()  	if f.released {  		// The file descriptor has been closed concurrently  		tlog.Warn.Printf("ino%d fh%d: Write on released file", f.qIno.Ino, f.intFd()) -		return 0, fuse.EBADF +		return 0, syscall.EBADF  	}  	f.fileTableEntry.ContentLock.Lock()  	defer f.fileTableEntry.ContentLock.Unlock() @@ -369,21 +370,21 @@ func (f *File2) Write(data []byte, off int64) (uint32, fuse.Status) {  	// But if the write directly follows an earlier write, it cannot create a  	// hole, and we can save one Stat() call.  	if !f.isConsecutiveWrite(off) { -		status := f.writePadHole(off) -		if !status.Ok() { -			return 0, status +		errno := f.writePadHole(off) +		if errno != 0 { +			return 0, errno  		}  	} -	n, status := f.doWrite(data, off) -	if status.Ok() { +	n, errno := f.doWrite(data, off) +	if errno != 0 {  		f.lastOpCount = openfiletable.WriteOpCount()  		f.lastWrittenOffset = off + int64(len(data)) - 1  	} -	return n, status +	return n, errno  }  // Release - FUSE call, close file -func (f *File2) Release() { +func (f *File2) Release(ctx context.Context) syscall.Errno {  	f.fdLock.Lock()  	if f.released {  		log.Panicf("ino%d fh%d: double release", f.qIno.Ino, f.intFd()) @@ -392,10 +393,11 @@ func (f *File2) Release() {  	openfiletable.Unregister(f.qIno)  	f.fd.Close()  	f.fdLock.Unlock() +	return 0  }  // Flush - FUSE call -func (f *File2) Flush() fuse.Status { +func (f *File2) Flush(ctx context.Context) syscall.Errno {  	f.fdLock.RLock()  	defer f.fdLock.RUnlock() @@ -405,18 +407,18 @@ func (f *File2) Flush() fuse.Status {  	newFd, err := syscall.Dup(f.intFd())  	if err != nil { -		return fuse.ToStatus(err) +		return fs.ToErrno(err)  	}  	err = syscall.Close(newFd) -	return fuse.ToStatus(err) +	return fs.ToErrno(err)  }  // Fsync FUSE call -func (f *File2) Fsync(flags int) (code fuse.Status) { +func (f *File2) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {  	f.fdLock.RLock()  	defer f.fdLock.RUnlock() -	return fuse.ToStatus(syscall.Fsync(f.intFd())) +	return fs.ToErrno(syscall.Fsync(f.intFd()))  }  // Getattr FUSE call (like stat) diff --git a/internal/fusefrontend/file2_allocate_truncate.go b/internal/fusefrontend/file2_allocate_truncate.go index 9a3d7d1..9413b90 100644 --- a/internal/fusefrontend/file2_allocate_truncate.go +++ b/internal/fusefrontend/file2_allocate_truncate.go @@ -4,10 +4,11 @@ package fusefrontend  // i.e. ftruncate and fallocate  import ( +	"context"  	"log"  	"syscall" -	"github.com/hanwen/go-fuse/v2/fuse" +	"github.com/hanwen/go-fuse/v2/fs"  	"github.com/rfjakob/gocryptfs/internal/syscallcompat"  	"github.com/rfjakob/gocryptfs/internal/tlog" @@ -26,19 +27,19 @@ import (  // complicated and hard to get right.  //  // Other modes (hole punching, zeroing) are not supported. -func (f *File2) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { +func (f *File2) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {  	if mode != FALLOC_DEFAULT && mode != FALLOC_FL_KEEP_SIZE {  		f := func() {  			tlog.Info.Printf("fallocate: only mode 0 (default) and 1 (keep size) are supported")  		}  		allocateWarnOnce.Do(f) -		return fuse.Status(syscall.EOPNOTSUPP) +		return syscall.EOPNOTSUPP  	}  	f.fdLock.RLock()  	defer f.fdLock.RUnlock()  	if f.released { -		return fuse.EBADF +		return syscall.EBADF  	}  	f.fileTableEntry.ContentLock.Lock()  	defer f.fileTableEntry.ContentLock.Unlock() @@ -57,23 +58,23 @@ func (f *File2) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {  	tlog.Debug.Printf("Allocate off=%d sz=%d mode=%x cipherOff=%d cipherSz=%d\n",  		off, sz, mode, cipherOff, cipherSz)  	if err != nil { -		return fuse.ToStatus(err) +		return fs.ToErrno(err)  	}  	if mode == FALLOC_FL_KEEP_SIZE {  		// The user did not want to change the apparent size. We are done. -		return fuse.OK +		return 0  	}  	// Step (2): Grow the apparent file size  	// We need the old file size to determine if we are growing the file at all.  	newPlainSz := off + sz  	oldPlainSz, err := f.statPlainSize()  	if err != nil { -		return fuse.ToStatus(err) +		return fs.ToErrno(err)  	}  	if newPlainSz <= oldPlainSz {  		// The new size is smaller (or equal). Fallocate with mode = 0 never  		// truncates a file, so we are done. -		return fuse.OK +		return 0  	}  	// The file grows. The space has already been allocated in (1), so what is  	// left to do is to pad the first and last block and call truncate. @@ -82,13 +83,13 @@ func (f *File2) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {  }  // truncate - called from Setattr. -func (f *File2) truncate(newSize uint64) fuse.Status { +func (f *File2) truncate(newSize uint64) (errno syscall.Errno) {  	f.fdLock.RLock()  	defer f.fdLock.RUnlock()  	if f.released {  		// The file descriptor has been closed concurrently.  		tlog.Warn.Printf("ino%d fh%d: Truncate on released file", f.qIno.Ino, f.intFd()) -		return fuse.EBADF +		return syscall.EBADF  	}  	f.fileTableEntry.ContentLock.Lock()  	defer f.fileTableEntry.ContentLock.Unlock() @@ -98,17 +99,17 @@ func (f *File2) truncate(newSize uint64) fuse.Status {  		err = syscall.Ftruncate(int(f.fd.Fd()), 0)  		if err != nil {  			tlog.Warn.Printf("ino%d fh%d: Ftruncate(fd, 0) returned error: %v", f.qIno.Ino, f.intFd(), err) -			return fuse.ToStatus(err) +			return fs.ToErrno(err)  		}  		// Truncate to zero kills the file header  		f.fileTableEntry.ID = nil -		return fuse.OK +		return 0  	}  	// We need the old file size to determine if we are growing or shrinking  	// the file  	oldSize, err := f.statPlainSize()  	if err != nil { -		return fuse.ToStatus(err) +		return fs.ToErrno(err)  	}  	oldB := float32(oldSize) / float32(f.contentEnc.PlainBS()) @@ -117,7 +118,7 @@ func (f *File2) truncate(newSize uint64) fuse.Status {  	// File size stays the same - nothing to do  	if newSize == oldSize { -		return fuse.OK +		return 0  	}  	// File grows  	if newSize > oldSize { @@ -131,25 +132,24 @@ func (f *File2) truncate(newSize uint64) fuse.Status {  	lastBlockLen := newSize - plainOff  	var data []byte  	if lastBlockLen > 0 { -		var status fuse.Status -		data, status = f.doRead(nil, plainOff, lastBlockLen) -		if status != fuse.OK { +		data, errno = f.doRead(nil, plainOff, lastBlockLen) +		if errno != 0 {  			tlog.Warn.Printf("Truncate: shrink doRead returned error: %v", err) -			return status +			return errno  		}  	}  	// Truncate down to the last complete block  	err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff))  	if err != nil {  		tlog.Warn.Printf("Truncate: shrink Ftruncate returned error: %v", err) -		return fuse.ToStatus(err) +		return fs.ToErrno(err)  	}  	// Append partial block  	if lastBlockLen > 0 {  		_, status := f.doWrite(data, int64(plainOff))  		return status  	} -	return fuse.OK +	return 0  }  // statPlainSize stats the file and returns the plaintext size @@ -167,7 +167,7 @@ func (f *File2) statPlainSize() (uint64, error) {  // truncateGrowFile extends a file using seeking or ftruncate performing RMW on  // the first and last block as necessary. New blocks in the middle become  // file holes unless they have been fallocate()'d beforehand. -func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Status { +func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Errno {  	if newPlainSz <= oldPlainSz {  		log.Panicf("BUG: newSize=%d <= oldSize=%d", newPlainSz, oldPlainSz)  	} @@ -179,17 +179,17 @@ func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Stat  		// Write a single zero to the last byte and let doWrite figure out the RMW.  		if n1 == n2 {  			buf := make([]byte, 1) -			_, status := f.doWrite(buf, int64(newEOFOffset)) -			return status +			_, errno := f.doWrite(buf, int64(newEOFOffset)) +			return errno  		}  	}  	// The truncate creates at least one new block.  	//  	// Make sure the old last block is padded to the block boundary. This call  	// is a no-op if it is already block-aligned. -	status := f.zeroPad(oldPlainSz) -	if !status.Ok() { -		return status +	errno := f.zeroPad(oldPlainSz) +	if errno != 0 { +		return errno  	}  	// The new size is block-aligned. In this case we can do everything ourselves  	// and avoid the call to doWrite. @@ -198,7 +198,7 @@ func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Stat  		if oldPlainSz == 0 {  			id, err := f.createHeader()  			if err != nil { -				return fuse.ToStatus(err) +				return fs.ToErrno(err)  			}  			f.fileTableEntry.ID = id  		} @@ -207,11 +207,11 @@ func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Stat  		if err != nil {  			tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err)  		} -		return fuse.ToStatus(err) +		return fs.ToErrno(err)  	}  	// The new size is NOT aligned, so we need to write a partial block.  	// Write a single zero to the last byte and let doWrite figure it out.  	buf := make([]byte, 1) -	_, status = f.doWrite(buf, int64(newEOFOffset)) -	return status +	_, errno = f.doWrite(buf, int64(newEOFOffset)) +	return errno  } diff --git a/internal/fusefrontend/file2_api_check.go b/internal/fusefrontend/file2_api_check.go index 3f2c595..4a6d6a1 100644 --- a/internal/fusefrontend/file2_api_check.go +++ b/internal/fusefrontend/file2_api_check.go @@ -7,17 +7,17 @@ import (  // Check that we have implemented the fs.File* interfaces  var _ = (fs.FileGetattrer)((*File2)(nil))  var _ = (fs.FileSetattrer)((*File2)(nil)) - -/* TODO -var _ = (fs.FileHandle)((*File2)(nil))  var _ = (fs.FileReleaser)((*File2)(nil))  var _ = (fs.FileReader)((*File2)(nil))  var _ = (fs.FileWriter)((*File2)(nil)) +var _ = (fs.FileFsyncer)((*File2)(nil)) +var _ = (fs.FileFlusher)((*File2)(nil)) +var _ = (fs.FileAllocater)((*File2)(nil)) + +/* TODO +var _ = (fs.FileHandle)((*File2)(nil))  var _ = (fs.FileGetlker)((*File2)(nil))  var _ = (fs.FileSetlker)((*File2)(nil))  var _ = (fs.FileSetlkwer)((*File2)(nil))  var _ = (fs.FileLseeker)((*File2)(nil)) -var _ = (fs.FileFlusher)((*File2)(nil)) -var _ = (fs.FileFsyncer)((*File2)(nil)) -var _ = (fs.FileAllocater)((*File2)(nil))  */ diff --git a/internal/fusefrontend/file2_holes.go b/internal/fusefrontend/file2_holes.go index 5e06981..83918d2 100644 --- a/internal/fusefrontend/file2_holes.go +++ b/internal/fusefrontend/file2_holes.go @@ -6,19 +6,19 @@ import (  	"runtime"  	"syscall" -	"github.com/hanwen/go-fuse/v2/fuse" +	"github.com/hanwen/go-fuse/v2/fs"  	"github.com/rfjakob/gocryptfs/internal/tlog"  )  // Will a write to plaintext offset "targetOff" create a file hole in the  // ciphertext? If yes, zero-pad the last ciphertext block. -func (f *File2) writePadHole(targetOff int64) fuse.Status { +func (f *File2) writePadHole(targetOff int64) syscall.Errno {  	// Get the current file size.  	fi, err := f.fd.Stat()  	if err != nil {  		tlog.Warn.Printf("checkAndPadHole: Fstat failed: %v", err) -		return fuse.ToStatus(err) +		return fs.ToErrno(err)  	}  	plainSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size()))  	// Appending a single byte to the file (equivalent to writing to @@ -29,31 +29,31 @@ func (f *File2) writePadHole(targetOff int64) fuse.Status {  	// The write goes into an existing block or (if the last block was full)  	// starts a new one directly after the last block. Nothing to do.  	if targetBlock <= nextBlock { -		return fuse.OK +		return 0  	}  	// The write goes past the next block. nextBlock has  	// to be zero-padded to the block boundary and (at least) nextBlock+1  	// will contain a file hole in the ciphertext. -	status := f.zeroPad(plainSize) -	if status != fuse.OK { -		return status +	errno := f.zeroPad(plainSize) +	if errno != 0 { +		return errno  	} -	return fuse.OK +	return 0  }  // Zero-pad the file of size plainSize to the next block boundary. This is a no-op  // if the file is already block-aligned. -func (f *File2) zeroPad(plainSize uint64) fuse.Status { +func (f *File2) zeroPad(plainSize uint64) syscall.Errno {  	lastBlockLen := plainSize % f.contentEnc.PlainBS()  	if lastBlockLen == 0 {  		// Already block-aligned -		return fuse.OK +		return 0  	}  	missing := f.contentEnc.PlainBS() - lastBlockLen  	pad := make([]byte, missing)  	tlog.Debug.Printf("zeroPad: Writing %d bytes\n", missing) -	_, status := f.doWrite(pad, int64(plainSize)) -	return status +	_, errno := f.doWrite(pad, int64(plainSize)) +	return errno  }  // SeekData calls the lseek syscall with SEEK_DATA. It returns the offset of the diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go index ee2d8d9..6cdc552 100644 --- a/internal/fusefrontend/node.go +++ b/internal/fusefrontend/node.go @@ -286,7 +286,7 @@ func (n *Node) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn,  			return errno  		}  		f2 = f.(*File2) -		defer f2.Release() +		defer f2.Release(ctx)  	}  	return f2.Setattr(ctx, in, out)  } | 
