func (me *UnionFs) Rmdir(path string, context *fuse.Context) (code fuse.Status) { r := me.getBranch(path) if r.code != fuse.OK { return r.code } if !r.attr.IsDir() { return fuse.Status(syscall.ENOTDIR) } stream, code := me.OpenDir(path, context) found := false for _ = range stream { found = true } if found { return fuse.Status(syscall.ENOTEMPTY) } if r.branch > 0 { code = me.putDeletion(path) return code } code = me.fileSystems[0].Rmdir(path, context) if code != fuse.OK { return code } r = me.branchCache.GetFresh(path).(branchResult) if r.branch > 0 { code = me.putDeletion(path) } return code }
func (vfs FuseVfs) Rmdir(name string, context *fuse.Context) fuse.Status { log.Infof(2, "BEGIN Rmdir(%v)", name) defer log.Infof(2, "END Rmdir(%v)", name) path := vfs.splitPath(name) switch path[0] { case tagsDir: if len(path) != 2 { // can only remove top-level tag directories return fuse.EPERM } tagName := path[1] tag, err := vfs.store.TagByName(tagName) if err != nil { log.Fatalf("could not retrieve tag '%v': %v", tagName, err) } if tag == nil { return fuse.ENOENT } count, err := vfs.store.FileTagCountByTagId(tag.Id) if err != nil { log.Fatalf("could not retrieve file-tag count for tag '%v': %v", tagName, err) } if count > 0 { return fuse.Status(syscall.ENOTEMPTY) } if err := vfs.store.DeleteTag(tag.Id); err != nil { log.Fatalf("could not delete tag '%v': %v", tagName, err) } if err := vfs.store.Commit(); err != nil { log.Fatalf("could not commit transaction: %v", err) } return fuse.OK case queriesDir: if len(path) != 2 { // can only remove top-level queries directories return fuse.EPERM } text := path[1] if err := vfs.store.DeleteQuery(text); err != nil { log.Fatalf("could not remove tag '%v': %v", name, err) } if err := vfs.store.Commit(); err != nil { log.Fatalf("could not commit transaction: %v", err) } return fuse.OK } return fuse.ENOSYS }
func (f *flipNode) GetAttr(out *fuse.Attr, file nodefs.File, c *fuse.Context) fuse.Status { select { case <-f.ok: // use a status that is easily recognizable. return fuse.Status(syscall.EXDEV) default: } return f.Node.GetAttr(out, file, c) }
func (node *inMemNode) Link(name string, existing nodefs.Node, context *fuse.Context) (newNode *nodefs.Inode, code fuse.Status) { if node.Inode().GetChild(name) != nil { return nil, fuse.Status(syscall.EEXIST) } node.Inode().AddChild(name, existing.Inode()) if inMemChild, ok := existing.(*inMemNode); ok { inMemChild.incrementLinks() } return existing.Inode(), fuse.OK }
func (n *configNode) Symlink(name string, content string, context *fuse.Context) (*nodefs.Inode, fuse.Status) { dir := content components := strings.Split(content, ":") if len(components) > 2 || len(components) == 0 { return nil, fuse.Status(syscall.EINVAL) } var root nodefs.Node if len(components) == 2 { dir = components[0] } if fi, err := os.Lstat(dir); err != nil { return nil, fuse.ToStatus(err) } else if !fi.IsDir() { return nil, fuse.Status(syscall.ENOTDIR) } var opts *nodefs.Options if len(components) == 1 { root = pathfs.NewPathNodeFs(pathfs.NewLoopbackFileSystem(content), nil).Root() } else { var err error root, err = NewGitFSRoot(content, n.fs.opts) if err != nil { log.Printf("NewGitFSRoot(%q): %v", content, err) return nil, fuse.ENOENT } opts = &nodefs.Options{ EntryTimeout: time.Hour, NegativeTimeout: time.Hour, AttrTimeout: time.Hour, PortableInodes: true, } } if code := n.fs.fsConn.Mount(n.corresponding.Inode(), name, root, opts); !code.Ok() { return nil, code } linkNode := newGitConfigNode(content) return n.Inode().NewChild(name, false, linkNode), fuse.OK }
// Allocate - FUSE call for fallocate(2) // // mode=FALLOC_FL_KEEP_SIZE is implemented directly. // // mode=FALLOC_DEFAULT is implemented as a two-step process: // // (1) Allocate the space using FALLOC_FL_KEEP_SIZE // (2) Set the file size using ftruncate (via truncateGrowFile) // // This allows us to reuse the file grow mechanics from Truncate as they are // complicated and hard to get right. // // Other modes (hole punching, zeroing) are not supported. func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { if mode != FALLOC_DEFAULT && mode != FALLOC_FL_KEEP_SIZE { f := func() { tlog.Warn.Print("fallocate: only mode 0 (default) and 1 (keep size) are supported") } allocateWarnOnce.Do(f) return fuse.Status(syscall.EOPNOTSUPP) } f.fdLock.RLock() defer f.fdLock.RUnlock() if f.released { return fuse.EBADF } f.fileTableEntry.writeLock.Lock() defer f.fileTableEntry.writeLock.Unlock() blocks := f.contentEnc.ExplodePlainRange(off, sz) firstBlock := blocks[0] lastBlock := blocks[len(blocks)-1] // Step (1): Allocate the space the user wants using FALLOC_FL_KEEP_SIZE. // This will fill file holes and/or allocate additional space past the end of // the file. cipherOff := firstBlock.BlockCipherOff() cipherSz := lastBlock.BlockCipherOff() - cipherOff + f.contentEnc.PlainSizeToCipherSize(lastBlock.Skip+lastBlock.Length) err := syscallcompat.Fallocate(f.intFd(), FALLOC_FL_KEEP_SIZE, int64(cipherOff), int64(cipherSz)) 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) } if mode == FALLOC_FL_KEEP_SIZE { // The user did not want to change the apparent size. We are done. return fuse.OK } // 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) } 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 } // 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. // truncateGrowFile does just that. return f.truncateGrowFile(oldPlainSz, newPlainSz) }
func (m *MNode) makeKnot(name string,isDir bool) (*nodefs.Inode,*MNode,fuse.Status) { m.obj.Lock() defer m.obj.Unlock() d,ok := m.obj.Obj.(*joinf.Directory) if !ok { return nil,nil,fuse.ENOTDIR } { c := m.Inode().GetChild(name) if c!=nil { return nil,nil,fuse.Status(syscall.EEXIST) } id,_ := d.Find(name) if id!="" { return nil,nil,fuse.Status(syscall.EEXIST) } } id := uuid.NewV4().String() if isDir { r := m.lm.Dirs(id) e := joinf.CreateDir(r) if e!=nil { return nil,nil,fuse.ToStatus(e) } e = d.Insert(name,id) if e!=nil { r.Delete() return nil,nil,fuse.ToStatus(e) } r.Dispose() }else{ r := m.lm.Files(id) e := joinf.CreateFile(r) if e!=nil { return nil,nil,fuse.ToStatus(e) } e = d.Insert(name,id) if e!=nil { r.Delete() return nil,nil,fuse.ToStatus(e) } r.Dispose() } co := m.lm.Load(id) nin := &MNode{nodefs.NewDefaultNode(),m.lm,co} return m.Inode().NewChild(name,isDir,nin),nin,fuse.OK }
func (parent *inMemNode) Symlink(name string, content string, context *fuse.Context) (*nodefs.Inode, fuse.Status) { if parent.Inode().GetChild(name) != nil { return nil, fuse.Status(syscall.EEXIST) } node := parent.fs.createNode() node.attr.Mode = 0777 | fuse.S_IFLNK contentBytes := []byte(content) node.setSize(len(contentBytes)) copy(node.contents, contentBytes) parent.Inode().NewChild(name, false, node) parent.incrementLinks() return node.Inode(), fuse.OK }
func (constor *Constor) Lookup(header *fuse.InHeader, name string, out *fuse.EntryOut) fuse.Status { var stat syscall.Stat_t if len(name) > 255 { constor.error("name too long : %s", name) return fuse.Status(syscall.ENAMETOOLONG) } li := -1 parent := constor.inodemap.findInodePtr(header.NodeId) if parent == nil { constor.error("Unable to find parent inode : %d", header.NodeId) return fuse.ENOENT } constor.log("%s(%s)", parent.id, name) id, err := constor.getid(-1, parent.id, name) if err != nil { // logging this error will produce too many logs as there will be too many // lookps on non-existant files return fuse.ToStatus(err) } inode := constor.inodemap.findInodeId(id) if inode != nil { li = inode.layer } else { li = constor.getLayer(id) } if li == -1 { constor.error("Unable to find inode for %s(%s) id %s", parent.id, name, id) return fuse.ENOENT } err = constor.Lstat(li, id, &stat) if err != nil { constor.error("Unable to Lstat inode for %s(%s) id %s", parent.id, name, id) return fuse.ToStatus(err) } if inode == nil { inode = NewInode(constor, id) inode.layer = li constor.inodemap.hashInode(inode) } else { inode.lookup() } attr := (*fuse.Attr)(&out.Attr) attr.FromStat(&stat) out.NodeId = uint64(uintptr(unsafe.Pointer(inode))) out.Ino = attr.Ino out.EntryValid = 1000 out.AttrValid = 1000 constor.log("%s", id) return fuse.OK }
func (parent *inMemNode) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, child *nodefs.Inode, code fuse.Status) { if parent.Inode().GetChild(name) != nil { return nil, nil, fuse.Status(syscall.EEXIST) } node := parent.fs.createNode() node.attr.Mode = mode | fuse.S_IFREG parent.Inode().NewChild(name, false, node) parent.incrementLinks() f, openStatus := node.Open(flags, context) if openStatus != fuse.OK { return nil, nil, openStatus } return f, node.Inode(), fuse.OK }
func (me *UnionFs) createDeletionStore() (code fuse.Status) { writable := me.fileSystems[0] fi, code := writable.GetAttr(me.options.DeletionDirName, nil) if code == fuse.ENOENT { code = writable.Mkdir(me.options.DeletionDirName, 0755, nil) if code.Ok() { fi, code = writable.GetAttr(me.options.DeletionDirName, nil) } } if !code.Ok() || !fi.IsDir() { code = fuse.Status(syscall.EROFS) } return code }
func (fs *autoUnionFs) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) { comps := strings.Split(linkName, "/") if len(comps) != 2 { return fuse.EPERM } if comps[0] == _CONFIG { roots := fs.getRoots(pointedTo) if roots == nil { return fuse.Status(syscall.ENOTDIR) } name := comps[1] return fs.addFs(name, roots) } return fuse.EPERM }
func (f *androidFile) Read(dest []byte, off int64) (fuse.ReadResult, fuse.Status) { if off >= f.node.Size { // ENXIO = no such address. return nil, fuse.Status(int(syscall.ENXIO)) } if off+int64(len(dest)) > f.node.Size { dest = dest[:f.node.Size-off] } b := bytes.NewBuffer(dest[:0]) err := f.node.fs.dev.AndroidGetPartialObject64(f.node.Handle(), b, off, uint32(len(dest))) if err != nil { log.Println("AndroidGetPartialObject64 failed:", err) return nil, fuse.EIO } return &fuse.ReadResultData{dest[:b.Len()]}, fuse.OK }
func (parent *AppendFSNode) Mkdir(name string, mode uint32, context *fuse.Context) (newNode *nodefs.Inode, code fuse.Status) { if parent.Inode().GetChild(name) != nil { return nil, fuse.Status(syscall.EEXIST) } node := CreateNode(parent) node.attr.Mode = mode | fuse.S_IFDIR node.attr.Nlink = 2 node.name = name inode := parent.inode.NewChild(name, true, node) parent.incrementLinks() err := node.fs.AppendMetadata(node.AsNodeMetadata()) if err != nil { return nil, fuse.EIO } return inode, fuse.OK }
// The isDeleted() method tells us if a path has a marker in the deletion store. // It may return an error code if the store could not be accessed. func (me *UnionFs) isDeleted(name string) (deleted bool, code fuse.Status) { marker := me.deletionPath(name) haveCache, found := me.deletionCache.HasEntry(filepath.Base(marker)) if haveCache { return found, fuse.OK } _, code = me.fileSystems[0].GetAttr(marker, nil) if code == fuse.OK { return true, code } if code == fuse.ENOENT { return false, fuse.OK } log.Println("error accessing deletion marker:", marker) return false, fuse.Status(syscall.EROFS) }
func (parent *AppendFSNode) Symlink(name string, content string, context *fuse.Context) (*nodefs.Inode, fuse.Status) { if parent.Inode().GetChild(name) != nil { return nil, fuse.Status(syscall.EEXIST) } node := CreateNode(parent) node.attr.Mode = 0777 | fuse.S_IFLNK contentBytes := []byte(content) node.setSize(uint64(len(contentBytes))) node.symlink = contentBytes node.name = name parent.Inode().NewChild(name, false, node) parent.incrementLinks() err := node.fs.AppendMetadata(node.AsNodeMetadata()) if err != nil { return nil, fuse.EIO } return node.Inode(), fuse.OK }
func (fs *nomsFS) GetXAttr(path string, attribute string, context *fuse.Context) ([]byte, fuse.Status) { fs.mdLock.Lock() defer fs.mdLock.Unlock() np, code := fs.getPath(path) if code != fuse.OK { return nil, code } xattr := np.inode.Get("attr").(types.Struct).Get("xattr").(types.Map) v, found := xattr.MaybeGet(types.String(attribute)) if !found { return nil, fuse.Status(93) // syscall.ENOATTR } blob := v.(types.Blob) data := make([]byte, blob.Len()) blob.Reader().Read(data) return data, fuse.OK }
func (parent *AppendFSNode) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, child *nodefs.Inode, code fuse.Status) { if parent.Inode().GetChild(name) != nil { return nil, nil, fuse.Status(syscall.EEXIST) } node := CreateNode(parent) node.attr.Mode = mode | fuse.S_IFREG node.attr.Uid = context.Uid node.attr.Gid = context.Gid node.name = name parent.Inode().NewChild(name, false, node) parent.incrementLinks() err := node.fs.AppendMetadata(node.AsNodeMetadata()) if err != nil { return nil, nil, fuse.EIO } f, openStatus := node.Open(flags, context) if openStatus != fuse.OK { return nil, nil, openStatus } return f, node.Inode(), fuse.OK }
func (me *UnionFs) Mkdir(path string, mode uint32, context *fuse.Context) (code fuse.Status) { deleted, code := me.isDeleted(path) if !code.Ok() { return code } if !deleted { r := me.getBranch(path) if r.code != fuse.ENOENT { return fuse.Status(syscall.EEXIST) } } code = me.promoteDirsTo(path) if code.Ok() { code = me.fileSystems[0].Mkdir(path, mode, context) } if code.Ok() { me.removeDeletion(path) attr := &fuse.Attr{ Mode: fuse.S_IFDIR | mode, } me.branchCache.Set(path, branchResult{attr, fuse.OK, 0}) } var stream chan fuse.DirEntry stream, code = me.OpenDir(path, context) if code.Ok() { // This shouldn't happen, but let's be safe. for entry := range stream { me.putDeletion(filepath.Join(path, entry.Name)) } } return code }
func (e errorReadResult) Bytes(buf []byte) ([]byte, fuse.Status) { return nil, fuse.Status(e) }
func (me *UnionFs) OpenDir(directory string, context *fuse.Context) (stream chan fuse.DirEntry, status fuse.Status) { dirBranch := me.getBranch(directory) if dirBranch.branch < 0 { return nil, fuse.ENOENT } // We could try to use the cache, but we have a delay, so // might as well get the fresh results async. var wg sync.WaitGroup var deletions map[string]bool wg.Add(1) go func() { deletions = newDirnameMap(me.fileSystems[0], me.options.DeletionDirName) wg.Done() }() entries := make([]map[string]uint32, len(me.fileSystems)) for i := range me.fileSystems { entries[i] = make(map[string]uint32) } statuses := make([]fuse.Status, len(me.fileSystems)) for i, l := range me.fileSystems { if i >= dirBranch.branch { wg.Add(1) go func(j int, pfs fuse.FileSystem) { ch, s := pfs.OpenDir(directory, context) statuses[j] = s for s.Ok() { v, ok := <-ch if !ok { break } entries[j][v.Name] = v.Mode } wg.Done() }(i, l) } } wg.Wait() if deletions == nil { _, code := me.fileSystems[0].GetAttr(me.options.DeletionDirName, context) if code == fuse.ENOENT { deletions = map[string]bool{} } else { return nil, fuse.Status(syscall.EROFS) } } results := entries[0] // TODO(hanwen): should we do anything with the return // statuses? for i, m := range entries { if statuses[i] != fuse.OK { continue } if i == 0 { // We don't need to further process the first // branch: it has no deleted files. continue } for k, v := range m { _, ok := results[k] if ok { continue } deleted := deletions[filePathHash(filepath.Join(directory, k))] if !deleted { results[k] = v } } } if directory == "" { delete(results, me.options.DeletionDirName) for name, _ := range me.hiddenFiles { delete(results, name) } } stream = make(chan fuse.DirEntry, len(results)) for k, v := range results { stream <- fuse.DirEntry{ Name: k, Mode: v, } } close(stream) return stream, fuse.OK }
func (constor *Constor) Rename(input *fuse.RenameIn, oldName string, newName string) (code fuse.Status) { sendEntryNotify := false var inodedel *Inode oldParent := constor.inodemap.findInodePtr(input.NodeId) if oldParent == nil { constor.error("oldParent == nil") return fuse.ENOENT } newParent := constor.inodemap.findInodePtr(input.Newdir) if newParent == nil { constor.error("newParent == nil") return fuse.ENOENT } if err := constor.copyup(newParent); err != nil { constor.error("copyup failed for %s - %s", newParent.id, err) return fuse.EIO } newParentPath := constor.getPath(0, newParent.id) newentrypath := Path.Join(newParentPath, newName) constor.log("%s %s %s %s", oldParent.id, oldName, newParent.id, newName) // remove any entry that existed in the newName's place if iddel, err := constor.getid(-1, newParent.id, newName); err == nil { if inodedel = constor.inodemap.findInodeId(iddel); inodedel != nil { if inodedel.layer == 0 { linkcnt, err := constor.declinkscnt(inodedel.id) if err != nil { constor.error("declinkscnt %s : %s", inodedel.id, err) return fuse.ToStatus(err) } if linkcnt == 0 { path := constor.getPath(0, iddel) fi, err := os.Lstat(path) if err != nil { constor.error("Lstat failed on %s", path) return fuse.ToStatus(err) } if fi.IsDir() { // FIXME: take care of this situation constor.error("path is a directory") return fuse.Status(syscall.EEXIST) } if err := syscall.Unlink(path); err != nil { constor.error("Unable to remove %s", path) return fuse.ToStatus(err) } inodedel.layer = -1 } } stat := syscall.Stat_t{} // FIXME do copyup and declinkscnt if err := syscall.Lstat(newentrypath, &stat); err == nil { fi, err := os.Lstat(newentrypath) if err != nil { constor.error("Lstat failed on %s", newentrypath) return fuse.ToStatus(err) } if fi.IsDir() { // FIXME: take care of this situation constor.error("path is a directory") return fuse.Status(syscall.EEXIST) } if err := syscall.Unlink(newentrypath); err != nil { constor.error("Unable to remove %s", newentrypath) return fuse.ToStatus(err) } } // inodedel.parentPtr = input.Newdir // inodedel.name = newName sendEntryNotify = true // constor.ms.DeleteNotify(input.Newdir, uint64(uintptr(unsafe.Pointer(inodedel))), newName) // constor.ms.EntryNotify(input.Newdir, newName) } else { constor.error("inodedel == nil for %s %s", newParent.id, newName) return fuse.EIO } } // remove any deleted placeholder if constor.isdeleted(newentrypath, nil) { if err := syscall.Unlink(newentrypath); err != nil { constor.error("Unlink %s : %s", newentrypath, err) return fuse.ToStatus(err) } } oldid, err := constor.getid(-1, oldParent.id, oldName) if err != nil { constor.error("getid error %s %s", oldParent.id, oldName) return fuse.ToStatus(err) } oldinode := constor.inodemap.findInodeId(oldid) if oldinode == nil { constor.error("oldinode == nil for %s", oldid) return fuse.ENOENT } path := constor.getPath(oldinode.layer, oldid) fi, err := os.Lstat(path) if err != nil { constor.error("Lstat %s", path) return fuse.ToStatus(err) } oldParentPath := constor.getPath(0, oldParent.id) oldentrypath := Path.Join(oldParentPath, oldName) oldstat := syscall.Stat_t{} if err := syscall.Lstat(oldentrypath, &oldstat); err == nil { if fi.IsDir() { if err := syscall.Rmdir(oldentrypath); err != nil { constor.error("Rmdir %s : %s", oldentrypath, err) return fuse.ToStatus(err) } } else { if err := syscall.Unlink(oldentrypath); err != nil { constor.error("Unlink %s : %s", oldentrypath, err) return fuse.ToStatus(err) } } } if _, err := constor.getid(-1, oldParent.id, oldName); err == nil { constor.setdeleted(oldentrypath) } if fi.Mode()&os.ModeSymlink == os.ModeSymlink { err = os.Symlink("placeholder", newentrypath) if err != nil { constor.error("Symlink %s : %s", newentrypath, err) return fuse.ToStatus(err) } } else if fi.Mode()&os.ModeDir == os.ModeDir { err := os.Mkdir(newentrypath, fi.Mode()) if err != nil { constor.error("Mkdir %s : %s", newentrypath, err) return fuse.ToStatus(err) } } else { fd, err := syscall.Creat(newentrypath, uint32(fi.Mode())) if err != nil { constor.error("create %s : %s", newentrypath, err) return fuse.ToStatus(err) } syscall.Close(fd) } id := constor.setid(newentrypath, oldid) if id == "" { constor.error("setid %s : %s", newentrypath, err) return fuse.EIO } if sendEntryNotify { go func() { // FIXME: is this needed? constor.ms.DeleteNotify(input.Newdir, uint64(uintptr(unsafe.Pointer(inodedel))), newName) constor.ms.DeleteNotify(input.NodeId, uint64(uintptr(unsafe.Pointer(oldinode))), oldName) }() } return fuse.OK }
package main import ( "github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hashicorp/vault/api" "github.com/square/keywhiz-fs" "golang.org/x/sys/unix" ) const ( EISDIR = fuse.Status(unix.EISDIR) ) type fs struct { pathfs.FileSystem client *api.Client owner keywhizfs.Ownership } // NewKeywhizFs readies a KeywhizFs struct and its parent filesystem objects. func NewFs(client *api.Client, owner keywhizfs.Ownership) (*fs, nodefs.Node) { defaultfs := pathfs.NewDefaultFileSystem() // Returns ENOSYS by default readonlyfs := pathfs.NewReadonlyFileSystem(defaultfs) // R/W calls return EPERM kwfs := &fs{readonlyfs, client, owner} nfs := pathfs.NewPathNodeFs(kwfs, nil) nfs.SetDebug(true) return kwfs, nfs.Root() }
// toFuseStatusLog converts an Errno to a Status and logs it. func toFuseStatusLog(err error, logEntry *LogEntry) fuse.Status { return fuse.Status(toErrnoLog(err, logEntry)) }
func (constor *Constor) Rmdir(header *fuse.InHeader, name string) (code fuse.Status) { constor.log("%d %s", header.NodeId, name) var stat syscall.Stat_t parent := constor.inodemap.findInodePtr(header.NodeId) if parent == nil { constor.error("parent == nil") return fuse.ENOENT } constor.log("%s %s", parent.id, name) id, err := constor.getid(-1, parent.id, name) if err != nil { constor.error("getid failed %s %s", parent.id, name) return fuse.ToStatus(err) } inode := constor.inodemap.findInodeId(id) if inode == nil { constor.error("%s %s : inode == nil", parent.id, name) return fuse.ENOENT } entries := map[string]DirEntry{} for li, _ := range constor.layers { path := constor.getPath(li, inode.id) stat := syscall.Stat_t{} err := syscall.Lstat(path, &stat) if err != nil { // this means that the directory was created on the layer above this layer break } if (stat.Mode & syscall.S_IFMT) != syscall.S_IFDIR { constor.error("Not a dir: %s", path) break } f, err := os.Open(path) if err != nil { constor.error("Open failed on %s", path) break } infos, _ := f.Readdir(0) for i := range infos { // workaround forhttps://code.google.com/p/go/issues/detail?id=5960 if infos[i] == nil { continue } name := infos[i].Name() if _, ok := entries[name]; ok { // skip if the file was in upper layer continue } d := DirEntry{ Name: name, Mode: uint32(infos[i].Mode()), } if constor.isdeleted(Path.Join(path, name), infos[i].Sys().(*syscall.Stat_t)) { d.Deleted = true } else { id, err := constor.getid(li, inode.id, name) if err != nil { constor.error("getid failed on %d %s %s", li, inode.id, name) continue } d.Ino = idtoino(id) } entries[name] = d } f.Close() } output := make([]DirEntry, 0, 500) for _, d := range entries { if d.Deleted { continue } output = append(output, d) } if len(output) > 0 { constor.error("Directory not empty %s %s", parent.id, name) return fuse.Status(syscall.ENOTEMPTY) } if inode.layer == 0 { path := constor.getPath(0, inode.id) if err := os.RemoveAll(path); err != nil { constor.error("RemoveAll on %s : %s", path, err) return fuse.ToStatus(err) } } err = constor.copyup(parent) if err != nil { constor.error("copyup failed for %s - %s", parent.id, err) return fuse.ToStatus(err) } entrypath := Path.Join(constor.getPath(0, parent.id), name) if err := syscall.Lstat(entrypath, &stat); err == nil { if err := syscall.Rmdir(entrypath); err != nil { constor.error("Rmdir on %s : %s", entrypath, err) return fuse.ToStatus(err) } } // if the file is in a lower layer then create a deleted place holder file if _, err := constor.getid(-1, parent.id, name); err == nil { constor.setdeleted(entrypath) } inode.layer = -1 return fuse.OK }