Commit d1d027ea authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Add support for SetAttr on open files.

Adds the following methods to fuse.File:

	GetAttr() *Attr
	Utimens(atimeNs uint64, mtimeNs uint64) Status
	Truncate(size uint64) Status
	Chown(uid uint32, gid uint32) Status
	Chmod(perms uint32) Status

Largely untested.
parent 9dac968e
package fuse
import (
"fmt"
"log"
)
var _ = log.Println
var _ = fmt.Println
func (me *DefaultRawFileSystem) Destroy(h *InHeader, input *InitIn) {
......@@ -150,10 +152,30 @@ func (me *DefaultFile) Release() {
}
func (me *DefaultFile) GetAttr() *Attr {
return nil
}
func (me *DefaultFile) Fsync(*FsyncIn) (code Status) {
return ENOSYS
}
func (me *DefaultFile) Utimens(atimeNs uint64, mtimeNs uint64) Status {
return ENOSYS
}
func (me *DefaultFile) Truncate(size uint64) Status {
return ENOSYS
}
func (me *DefaultFile) Chown(uid uint32, gid uint32) Status {
return ENOSYS
}
func (me *DefaultFile) Chmod(perms uint32) Status {
return ENOSYS
}
////////////////////////////////////////////////////////////////
// DefaultFileSystem
......
package fuse
import (
"fmt"
"log"
"json"
"os"
"syscall"
"testing"
)
type MutableDataFile struct {
data []byte
os.FileInfo
GetAttrCalled bool
}
func (me *MutableDataFile) Read(r *ReadIn, bp *BufferPool) ([]byte, Status) {
return me.data[r.Offset:r.Offset+uint64(r.Size)], OK
}
func (me *MutableDataFile) Write(w *WriteIn, d []byte) (uint32, Status) {
end := uint64(w.Size) + w.Offset
if int(end) > len(me.data) {
data := make([]byte, len(me.data), end)
copy(data, me.data)
me.data = data
}
copy(me.data[w.Offset:end], d)
return w.Size, OK
}
func (me *MutableDataFile) Flush() Status {
return OK
}
func (me *MutableDataFile) Release() {
}
func (me *MutableDataFile) getAttr() *Attr {
a := &Attr{}
CopyFileInfo(&me.FileInfo, a)
a.Size = uint64(len(me.data))
return a
}
func (me *MutableDataFile) GetAttr() *Attr {
me.GetAttrCalled = true
return me.getAttr()
}
func (me *MutableDataFile) Fsync(*FsyncIn) (code Status) {
return OK
}
func (me *MutableDataFile) Utimens(atimeNs uint64, mtimeNs uint64) Status {
me.FileInfo.Atime_ns = int64(atimeNs)
me.FileInfo.Mtime_ns = int64(mtimeNs)
return OK
}
func (me *MutableDataFile) Truncate(size uint64) Status {
me.data = me.data[:size]
return OK
}
func (me *MutableDataFile) Chown(uid uint32, gid uint32) Status {
me.FileInfo.Uid = int(uid)
me.FileInfo.Gid = int(uid)
return OK
}
func (me *MutableDataFile) Chmod(perms uint32) Status {
me.FileInfo.Mode = (me.FileInfo.Mode &^ 07777) | perms
return OK
}
////////////////
type FSetAttrFs struct {
DefaultFileSystem
file *MutableDataFile
}
func (me *FSetAttrFs) GetXAttr(name string, attr string) ([]byte, Status) {
return nil, syscall.ENODATA
}
func (me *FSetAttrFs) GetAttr(name string) (*Attr, Status) {
if name == "" {
return &Attr{Mode: S_IFDIR | 0700}, OK
}
if name == "file" && me.file != nil {
a := me.file.getAttr()
a.Mode |= S_IFREG
return a, OK
}
return nil, ENOENT
}
func (me *FSetAttrFs) Open(name string, flags uint32) (File, Status) {
if name == "file" {
return me.file, OK
}
return nil, ENOENT
}
func (me *FSetAttrFs) Create(name string, flags uint32, mode uint32) (File, Status) {
if name == "file" {
f := NewFile()
me.file = f
me.file.FileInfo.Mode = mode
return f, OK
}
return nil, ENOENT
}
func NewFile() *MutableDataFile {
return &MutableDataFile{}
}
func TestFSetAttr(t *testing.T) {
fs := &FSetAttrFs{}
c := NewFileSystemConnector(fs, nil)
state := NewMountState(c)
dir := MakeTempDir()
state.Mount(dir)
state.Debug = true
defer state.Unmount()
go state.Loop(false)
fn := dir + "/file"
f, err := os.OpenFile(fn, os.O_CREATE | os.O_WRONLY, 0755)
CheckSuccess(err)
defer f.Close()
_, err = f.WriteString("hello")
CheckSuccess(err)
fmt.Println("Ftruncate")
code := syscall.Ftruncate(f.Fd(), 3)
if code != 0 {
t.Error("truncate retval", os.NewSyscallError("Ftruncate", code))
}
if len(fs.file.data) != 3 {
t.Error("truncate")
}
if state.KernelSettings().Flags & CAP_FILE_OPS == 0 {
log.Println("Mount does not support file operations")
m, _ := json.Marshal(state.KernelSettings())
log.Println("Kernel settings: ", string(m))
return
}
_, err = os.Lstat(fn)
CheckSuccess(err)
if !fs.file.GetAttrCalled {
t.Error("Should have called File.GetAttr")
}
err = os.Chmod(fn, 024)
CheckSuccess(err)
if fs.file.FileInfo.Mode & 07777 != 024 {
t.Error("chmod")
}
err = os.Chtimes(fn, 100, 101)
CheckSuccess(err)
if fs.file.FileInfo.Atime_ns != 100 || fs.file.FileInfo.Atime_ns != 101 {
t.Error("Utimens")
}
// TODO - test chown if run as root.
}
......@@ -52,7 +52,7 @@ const (
_OP_IOCTL = opcode(39)
_OP_POLL = opcode(40)
OPCODE_COUNT = opcode(41)
_OPCODE_COUNT = opcode(41)
)
......@@ -72,12 +72,12 @@ func doInit(state *MountState, req *request) {
}
state.kernelSettings = *input
state.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES)
state.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS)
out := &InitOut{
Major: FUSE_KERNEL_VERSION,
Minor: FUSE_KERNEL_MINOR_VERSION,
MaxReadAhead: input.MaxReadAhead,
Flags: state.kernelSettings.Flags,
Flags: state.kernelSettings.Flags,
MaxWrite: maxRead,
CongestionThreshold: _BACKGROUND_TASKS * 3 / 4,
MaxBackground: _BACKGROUND_TASKS,
......@@ -314,14 +314,14 @@ func (op opcode) String() string {
}
func getHandler(o opcode) *operationHandler {
if o >= OPCODE_COUNT {
if o >= _OPCODE_COUNT {
return nil
}
return operationHandlers[o]
}
func init() {
operationHandlers = make([]*operationHandler, OPCODE_COUNT)
operationHandlers = make([]*operationHandler, _OPCODE_COUNT)
for i, _ := range operationHandlers {
operationHandlers[i] = &operationHandler{Name: "UNKNOWN"}
}
......
......@@ -142,6 +142,12 @@ func (me *inode) setParent(newParent *inode) {
return
}
if me.Parent != nil {
if paranoia {
ch := me.Parent.Children[me.Name]
if ch == nil {
panic(fmt.Sprintf("parent has no child named %v", me.Name))
}
}
me.Parent.Children[me.Name] = nil, false
me.Parent = nil
}
......@@ -190,6 +196,7 @@ type FileSystemConnector struct {
}
type interfaceBridge struct {
*mountData
Iface interface{}
}
......@@ -218,12 +225,13 @@ func (me *FileSystemConnector) unregisterFile(node *inode, handle uint64) interf
return b.Iface
}
func (me *FileSystemConnector) registerFile(node *inode, f interface{}) uint64 {
func (me *FileSystemConnector) registerFile(node *inode, mount *mountData, f interface{}) uint64 {
me.fileLock.Lock()
defer me.fileLock.Unlock()
b := &interfaceBridge{
Iface: f,
mountData: mount,
}
h := uint64(uintptr(unsafe.Pointer(b)))
_, ok := me.openFiles[h]
......@@ -236,14 +244,19 @@ func (me *FileSystemConnector) registerFile(node *inode, f interface{}) uint64 {
return h
}
func (me *FileSystemConnector) getDir(h uint64) RawDir {
func (me *FileSystemConnector) decodeFileHandle(h uint64) (interface{}, *mountData) {
b := (*interfaceBridge)(unsafe.Pointer(uintptr(h)))
return b.Iface.(RawDir)
return b.Iface, b.mountData
}
func (me *FileSystemConnector) getFile(h uint64) File {
b := (*interfaceBridge)(unsafe.Pointer(uintptr(h)))
return b.Iface.(File)
func (me *FileSystemConnector) getDir(h uint64) (RawDir, *mountData) {
f, m := me.decodeFileHandle(h)
return f.(RawDir), m
}
func (me *FileSystemConnector) getFile(h uint64) (File, *mountData) {
f, m := me.decodeFileHandle(h)
return f.(File), m
}
func (me *FileSystemConnector) verify() {
......@@ -271,7 +284,7 @@ func (me *FileSystemConnector) verify() {
open := root.totalOpenCount()
openFiles := len(me.openFiles)
if open+hiddenOpen != openFiles {
panic(fmt.Sprintf("opencount mismatch totalOpen=%v openFiles=%v mounted=%v hidden=%v", open, openFiles, hiddenOpen))
panic(fmt.Sprintf("opencount mismatch totalOpen=%v openFiles=%v hiddenOpen=%v", open, openFiles, hiddenOpen))
}
}
......@@ -361,6 +374,7 @@ func (me *FileSystemConnector) unlinkUpdate(parent *inode, name string) {
node := parent.Children[name]
node.setParent(nil)
node.Name = ".deleted"
}
// Walk the file system starting from the root.
......@@ -498,3 +512,19 @@ func (me *FileSystemConnector) GetPath(nodeid uint64) (path string, mount *mount
return p, m, n
}
func (me *FileSystemConnector) getOpenFileData(nodeid uint64, fh uint64) (f File, m *mountData, p string) {
if fh != 0 {
f, m = me.getFile(fh)
}
me.treeLock.RLock()
defer me.treeLock.RUnlock()
node := me.getInodeData(nodeid)
if node.Parent != nil {
p, m = node.GetPath()
}
return
}
......@@ -74,11 +74,26 @@ func (me *FileSystemConnector) Forget(h *InHeader, input *ForgetIn) {
}
func (me *FileSystemConnector) GetAttr(header *InHeader, input *GetAttrIn) (out *AttrOut, code Status) {
// TODO - do something intelligent with input.Fh.
if input.Flags & FUSE_GETATTR_FH != 0 {
f, mount := me.getFile(input.Fh)
attr := f.GetAttr()
if attr != nil {
out = &AttrOut{
Attr: *attr,
}
out.Attr.Ino = header.NodeId
SplitNs(mount.options.AttrTimeout, &out.AttrValid, &out.AttrValidNsec)
return out, OK
}
}
fullPath, mount, _ := me.GetPath(header.NodeId)
if mount == nil {
return nil, ENOENT
}
attr, err := mount.fs.GetAttr(fullPath)
if err != OK {
return nil, err
......@@ -107,13 +122,13 @@ func (me *FileSystemConnector) OpenDir(header *InHeader, input *OpenIn) (flags u
de := &Dir{
stream: stream,
}
h := me.registerFile(node, de)
h := me.registerFile(node, mount, de)
return 0, h, OK
}
func (me *FileSystemConnector) ReadDir(header *InHeader, input *ReadIn) (*DirEntryList, Status) {
d := me.getDir(input.Fh)
d, _ := me.getDir(input.Fh)
de, code := d.ReadDir(input)
if code != OK {
return nil, code
......@@ -132,47 +147,90 @@ func (me *FileSystemConnector) Open(header *InHeader, input *OpenIn) (flags uint
if err != OK {
return 0, 0, err
}
h := me.registerFile(node, f)
h := me.registerFile(node, mount, f)
return 0, h, OK
}
func (me *FileSystemConnector) SetAttr(header *InHeader, input *SetAttrIn) (out *AttrOut, code Status) {
var err Status = OK
var getAttrIn GetAttrIn
fh := uint64(0)
if input.Valid&FATTR_FH != 0 {
fh = input.Fh
getAttrIn.Fh = fh
getAttrIn.Flags |= FUSE_GETATTR_FH
}
// TODO - support Fh. (FSetAttr/FGetAttr/FTruncate.)
fullPath, mount, _ := me.GetPath(header.NodeId)
f, mount, fullPath := me.getOpenFileData(header.NodeId, fh)
if mount == nil {
return nil, ENOENT
}
if input.Valid&FATTR_MODE != 0 {
permissionMask := uint32(07777)
err = mount.fs.Chmod(fullPath, input.Mode&permissionMask)
}
if err == OK && (input.Valid&FATTR_UID != 0 || input.Valid&FATTR_GID != 0) {
// TODO - can we get just FATTR_GID but not FATTR_UID ?
err = mount.fs.Chown(fullPath, uint32(input.Uid), uint32(input.Gid))
}
if input.Valid&FATTR_SIZE != 0 {
mount.fs.Truncate(fullPath, input.Size)
}
if err == OK && (input.Valid&FATTR_ATIME != 0 || input.Valid&FATTR_MTIME != 0) {
err = mount.fs.Utimens(fullPath,
uint64(input.Atime*1e9)+uint64(input.Atimensec),
uint64(input.Mtime*1e9)+uint64(input.Mtimensec))
}
if err == OK && (input.Valid&FATTR_ATIME_NOW != 0 || input.Valid&FATTR_MTIME_NOW != 0) {
ns := time.Nanoseconds()
err = mount.fs.Utimens(fullPath, uint64(ns), uint64(ns))
fileResult := ENOSYS
if err == OK && input.Valid&FATTR_MODE != 0 {
permissions := uint32(07777) & input.Mode
if f != nil {
fileResult = f.Chmod(permissions)
}
if fileResult == ENOSYS {
err = mount.fs.Chmod(fullPath, permissions)
} else {
err = fileResult
fileResult = ENOSYS
}
}
if err == OK && (input.Valid&(FATTR_UID|FATTR_GID) != 0) {
if f != nil {
fileResult = f.Chown(uint32(input.Uid), uint32(input.Gid))
}
if fileResult == ENOSYS {
// TODO - can we get just FATTR_GID but not FATTR_UID ?
err = mount.fs.Chown(fullPath, uint32(input.Uid), uint32(input.Gid))
} else {
err = fileResult
fileResult = ENOSYS
}
}
if err == OK && input.Valid&FATTR_SIZE != 0 {
if f != nil {
fileResult = f.Truncate(input.Size)
}
if fileResult == ENOSYS {
err = mount.fs.Truncate(fullPath, input.Size)
} else {
err = fileResult
fileResult = ENOSYS
}
}
if err == OK && (input.Valid& (FATTR_ATIME|FATTR_MTIME|FATTR_ATIME_NOW|FATTR_MTIME_NOW) != 0) {
atime := uint64(input.Atime*1e9)+uint64(input.Atimensec)
if input.Valid & FATTR_ATIME_NOW != 0 {
atime = uint64(time.Nanoseconds())
}
mtime := uint64(input.Mtime*1e9)+uint64(input.Mtimensec)
if input.Valid & FATTR_MTIME_NOW != 0 {
mtime = uint64(time.Nanoseconds())
}
if f != nil {
fileResult = f.Utimens(atime, mtime)
}
if fileResult == ENOSYS {
err = mount.fs.Utimens(fullPath, atime, mtime)
} else {
err = fileResult
fileResult = ENOSYS
}
}
if err != OK {
return nil, err
}
// TODO - where to get GetAttrIn.Flags / Fh ?
return me.GetAttr(header, &GetAttrIn{})
return me.GetAttr(header, &getAttrIn)
}
func (me *FileSystemConnector) Readlink(header *InHeader) (out []byte, code Status) {
......@@ -308,7 +366,7 @@ func (me *FileSystemConnector) Create(header *InHeader, input *CreateIn, name st
}
out, code, inode := me.internalLookupWithNode(parent, name, 1)
return 0, me.registerFile(inode, f), out, code
return 0, me.registerFile(inode, mount, f), out, code
}
func (me *FileSystemConnector) Release(header *InHeader, input *ReleaseIn) {
......@@ -379,11 +437,11 @@ func (me *FileSystemConnector) ListXAttr(header *InHeader) (data []byte, code St
}
func (me *FileSystemConnector) Write(input *WriteIn, data []byte) (written uint32, code Status) {
f := me.getFile(input.Fh).(File)
f, _ := me.getFile(input.Fh)
return f.Write(input, data)
}
func (me *FileSystemConnector) Read(input *ReadIn, bp *BufferPool) ([]byte, Status) {
f := me.getFile(input.Fh)
f, _ := me.getFile(input.Fh)
return f.Read(input, bp)
}
......@@ -12,42 +12,19 @@ const (
FUSE_ROOT_ID = 1
// SetAttrIn.Valid
FATTR_MODE = (1 << 0)
FATTR_UID = (1 << 1)
FATTR_GID = (1 << 2)
FATTR_SIZE = (1 << 3)
FATTR_ATIME = (1 << 4)
FATTR_MTIME = (1 << 5)
FATTR_FH = (1 << 6)
FATTR_ATIME_NOW = (1 << 7)
FATTR_MTIME_NOW = (1 << 8)
FATTR_LOCKOWNER = (1 << 9)
// OpenIn.Flags
FOPEN_DIRECT_IO = (1 << 0)
FOPEN_KEEP_CACHE = (1 << 1)
FOPEN_NONSEEKABLE = (1 << 2)
// To be set in InitOut.Flags.
CAP_ASYNC_READ = (1 << 0)
CAP_POSIX_LOCKS = (1 << 1)
CAP_FILE_OPS = (1 << 2)
CAP_ATOMIC_O_TRUNC = (1 << 3)
CAP_EXPORT_SUPPORT = (1 << 4)
CAP_BIG_WRITES = (1 << 5)
CAP_DONT_MASK = (1 << 6)
CAP_SPLICE_WRITE = (1 << 7)
CAP_SPLICE_MOVE = (1 << 8)
CAP_SPLICE_READ = (1 << 9)
FUSE_UNKNOWN_INO = 0xffffffff
CUSE_UNRESTRICTED_IOCTL = (1 << 0)
FUSE_RELEASE_FLUSH = (1 << 0)
// If set, GetAttrIn has a file handle set.
FUSE_GETATTR_FH = (1 << 0)
FUSE_LK_FLOCK = (1 << 0)
......@@ -173,7 +150,7 @@ type ForgetIn struct {
}
type GetAttrIn struct {
GetAttrFlags uint32
Flags uint32
Dummy uint32
Fh uint64
}
......@@ -205,6 +182,20 @@ type LinkIn struct {
Oldnodeid uint64
}
const ( // SetAttrIn.Valid
FATTR_MODE = (1 << 0)
FATTR_UID = (1 << 1)
FATTR_GID = (1 << 2)
FATTR_SIZE = (1 << 3)
FATTR_ATIME = (1 << 4)
FATTR_MTIME = (1 << 5)
FATTR_FH = (1 << 6)
FATTR_ATIME_NOW = (1 << 7)
FATTR_MTIME_NOW = (1 << 8)
FATTR_LOCKOWNER = (1 << 9)
)
type SetAttrIn struct {
Valid uint32
Padding uint32
......@@ -327,6 +318,20 @@ type AccessIn struct {
Padding uint32
}
// To be set in InitIn/InitOut.Flags.
const (
CAP_ASYNC_READ = (1 << 0)
CAP_POSIX_LOCKS = (1 << 1)
CAP_FILE_OPS = (1 << 2)
CAP_ATOMIC_O_TRUNC = (1 << 3)
CAP_EXPORT_SUPPORT = (1 << 4)
CAP_BIG_WRITES = (1 << 5)
CAP_DONT_MASK = (1 << 6)
CAP_SPLICE_WRITE = (1 << 7)
CAP_SPLICE_MOVE = (1 << 8)
CAP_SPLICE_READ = (1 << 9)
)
type InitIn struct {
Major uint32
Minor uint32
......@@ -458,7 +463,6 @@ type NotifyInvalEntryOut struct {
// threading right etc. are somewhat tricky and not very interesting.
type RawFileSystem interface {
Destroy(h *InHeader, input *InitIn)
Lookup(header *InHeader, name string) (out *EntryOut, status Status)
Forget(header *InHeader, input *ForgetIn)
......@@ -512,6 +516,12 @@ type File interface {
Flush() Status
Release()
Fsync(*FsyncIn) (code Status)
GetAttr() *Attr
Utimens(atimeNs uint64, mtimeNs uint64) Status
Truncate(size uint64) Status
Chown(uid uint32, gid uint32) Status
Chmod(perms uint32) Status
}
type RawDir interface {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment