hdfs-mount/Dir.go

136 строки
4.0 KiB
Go

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
package main
import (
"bazil.org/fuse"
"bazil.org/fuse/fs"
"golang.org/x/net/context"
"log"
"os"
"path"
"sync"
"time"
)
// Encapsulates state and operations for directory node on the HDFS file system
type Dir struct {
FileSystem *FileSystem // Pointer to the owning filesystem
Attrs Attrs // Cached attributes of the directory, TODO: add TTL
Parent *Dir // Pointer to the parent directory (allows computing fully-qualified paths on demand)
Entries map[string]fs.Node // Cahed directory entries
EntriesMutex sync.Mutex // Used to protect Entries
}
// Verify that *Dir implements necesary FUSE interfaces
var _ fs.Node = (*Dir)(nil)
var _ fs.HandleReadDirAller = (*Dir)(nil)
var _ fs.NodeStringLookuper = (*Dir)(nil)
// Retunds absolute path of the dir in HDFS namespace
func (this *Dir) AbsolutePath() string {
if this.Parent == nil {
return "/"
} else {
return path.Join(this.Parent.AbsolutePath(), this.Attrs.Name)
}
}
// Responds on FUSE request to get directory attributes
func (this *Dir) Attr(ctx context.Context, a *fuse.Attr) error {
if this.Parent != nil && this.FileSystem.Clock.Now().After(this.Attrs.Expires) {
err := this.Parent.LookupAttrs(this.Attrs.Name, &this.Attrs)
if err != nil {
return err
}
}
return this.Attrs.Attr(a)
}
func (this *Dir) EntriesGet(name string) fs.Node {
this.EntriesMutex.Lock()
defer this.EntriesMutex.Unlock()
if this.Entries == nil {
this.Entries = make(map[string]fs.Node)
return nil
}
return this.Entries[name]
}
func (this *Dir) EntriesSet(name string, node fs.Node) {
this.EntriesMutex.Lock()
defer this.EntriesMutex.Unlock()
this.Entries[name] = node
}
// Responds on FUSE request to lookup the directory
func (this *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
//log.Printf("[%s]Lookup: %s", this.AbsolutePath(), name)
if node := this.EntriesGet(name); node != nil {
return node, nil
}
var attrs Attrs
err := this.LookupAttrs(name, &attrs)
if err != nil {
return nil, err
}
return this.NodeFromAttrs(attrs), nil
}
// Responds on FUSE request to read directory
func (this *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
if this.Entries == nil {
this.Entries = make(map[string]fs.Node)
}
absolutePath := this.AbsolutePath()
log.Printf("[%s]ReadDirAll", absolutePath)
allAttrs, err := this.FileSystem.HdfsAccessor.ReadDir(absolutePath)
if err != nil {
log.Print("Error/ls: ", err)
return nil, err
}
entries := make([]fuse.Dirent, len(allAttrs))
for i, a := range allAttrs {
// Creating Dirent structure as required by FUSE
entries[i] = fuse.Dirent{
Inode: a.Inode,
Name: a.Name,
Type: a.FuseNodeType()}
// Speculatively pre-creating child Dir or File node with cached attributes,
// since it's highly likely that we will have Lookup() call for this name
// This is the key trick which dramatically speeds up 'ls'
this.NodeFromAttrs(a)
}
return entries, nil
}
// Creates typed node (Dir or File) from the attributes
func (this *Dir) NodeFromAttrs(attrs Attrs) fs.Node {
var node fs.Node
if (attrs.Mode & os.ModeDir) == 0 {
node = &File{FileSystem: this.FileSystem, Parent: this, Attrs: attrs}
} else {
node = &Dir{FileSystem: this.FileSystem, Parent: this, Attrs: attrs}
}
this.EntriesSet(attrs.Name, node)
return node
}
// Performs Stat() query on the backend
func (this *Dir) LookupAttrs(name string, attrs *Attrs) error {
var err error
*attrs, err = this.FileSystem.HdfsAccessor.Stat(path.Join(this.AbsolutePath(), name))
if err != nil {
log.Printf("Error/stat: %s %v", err.Error(), err)
if pathError, ok := err.(*os.PathError); ok && (pathError.Err == os.ErrNotExist) {
return fuse.ENOENT
}
return err
}
// expiration time := now + 1 minute // TODO: make configurable
attrs.Expires = this.FileSystem.Clock.Now().Add(time.Minute)
return nil
}