reg/vendor/github.com/containerd/continuity/continuityfs/fuse.go
Jess Frazelle 3834c605e5
update deps
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
2018-03-06 10:32:47 -05:00

307 lines
6.7 KiB
Go

// +build linux darwin freebsd
package continuityfs
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"github.com/containerd/continuity"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)
// File represents any file type (non directory) in the filesystem
type File struct {
inode uint64
uid uint32
gid uint32
provider FileContentProvider
resource continuity.Resource
}
// NewFile creates a new file with the given inode and content provider
func NewFile(inode uint64, provider FileContentProvider) *File {
return &File{
inode: inode,
provider: provider,
}
}
func (f *File) setResource(r continuity.Resource) (err error) {
// TODO: error out if uid excesses uint32?
f.uid = uint32(r.UID())
f.gid = uint32(r.GID())
f.resource = r
return
}
// Attr sets the fuse attribute for the file
func (f *File) Attr(ctx context.Context, attr *fuse.Attr) (err error) {
// Set attributes from resource metadata
attr.Mode = f.resource.Mode()
attr.Uid = f.uid
attr.Gid = f.gid
if rf, ok := f.resource.(continuity.RegularFile); ok {
attr.Nlink = uint32(len(rf.Paths()))
attr.Size = uint64(rf.Size())
} else {
attr.Nlink = 1
}
attr.Inode = f.inode
return nil
}
// Open opens the file for read
// currently only regular files can be opened
func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
var dgst digest.Digest
if rf, ok := f.resource.(continuity.RegularFile); ok {
digests := rf.Digests()
if len(digests) > 0 {
dgst = digests[0]
}
}
// TODO(dmcgowan): else check if device can be opened for read
r, err := f.provider.Open(f.resource.Path(), dgst)
if err != nil {
logrus.Debugf("Error opening handle: %v", err)
return nil, err
}
return &fileHandler{
reader: r,
}, nil
}
func (f *File) getDirent(name string) (fuse.Dirent, error) {
var t fuse.DirentType
switch f.resource.(type) {
case continuity.RegularFile:
t = fuse.DT_File
case continuity.SymLink:
t = fuse.DT_Link
case continuity.Device:
t = fuse.DT_Block
case continuity.NamedPipe:
t = fuse.DT_FIFO
default:
t = fuse.DT_Unknown
}
return fuse.Dirent{
Inode: f.inode,
Type: t,
Name: name,
}, nil
}
type fileHandler struct {
offset int64
reader io.ReadCloser
}
func (h *fileHandler) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
if h.offset != req.Offset {
if seeker, ok := h.reader.(io.Seeker); ok {
if _, err := seeker.Seek(req.Offset, os.SEEK_SET); err != nil {
logrus.Debugf("Error seeking: %v", err)
return err
}
h.offset = req.Offset
} else {
return errors.New("unable to seek to offset")
}
}
n, err := h.reader.Read(resp.Data[:req.Size])
if err != nil {
logrus.Debugf("Read error: %v", err)
return err
}
h.offset = h.offset + int64(n)
resp.Data = resp.Data[:n]
return nil
}
func (h *fileHandler) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
return h.reader.Close()
}
// Dir represents a file system directory
type Dir struct {
inode uint64
uid uint32
gid uint32
nodes map[string]fs.Node
provider FileContentProvider
resource continuity.Resource
}
// Attr sets the fuse attributes for the directory
func (d *Dir) Attr(ctx context.Context, attr *fuse.Attr) (err error) {
if d.resource == nil {
attr.Mode = os.ModeDir | 0555
} else {
attr.Mode = d.resource.Mode()
}
attr.Uid = d.uid
attr.Gid = d.gid
attr.Inode = d.inode
return nil
}
func (d *Dir) getDirent(name string) (fuse.Dirent, error) {
return fuse.Dirent{
Inode: d.inode,
Type: fuse.DT_Dir,
Name: name,
}, nil
}
type direnter interface {
getDirent(name string) (fuse.Dirent, error)
}
// Lookup looks up the filesystem node for the name within the directory
func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
node, ok := d.nodes[name]
if !ok {
return nil, fuse.ENOENT
}
return node, nil
}
// ReadDirAll reads all the directory entries
func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
ents := make([]fuse.Dirent, 0, len(d.nodes))
for name, node := range d.nodes {
if nd, ok := node.(direnter); ok {
de, err := nd.getDirent(name)
if err != nil {
return nil, err
}
ents = append(ents, de)
} else {
logrus.Errorf("%s does not have a directory entry", name)
}
}
return ents, nil
}
func (d *Dir) setResource(r continuity.Resource) (err error) {
d.uid = uint32(r.UID())
d.gid = uint32(r.GID())
d.resource = r
return
}
// NewDir creates a new directory object
func NewDir(inode uint64, provider FileContentProvider) *Dir {
return &Dir{
inode: inode,
nodes: map[string]fs.Node{},
provider: provider,
}
}
var (
rootPath = fmt.Sprintf("%c", filepath.Separator)
)
func addNode(path string, node fs.Node, cache map[string]*Dir, provider FileContentProvider) {
dirPath, file := filepath.Split(path)
d, ok := cache[dirPath]
if !ok {
d = NewDir(0, provider)
cache[dirPath] = d
addNode(filepath.Clean(dirPath), d, cache, provider)
}
d.nodes[file] = node
logrus.Debugf("%s (%#v) added to %s", file, node, dirPath)
}
type treeRoot struct {
root *Dir
}
func (t treeRoot) Root() (fs.Node, error) {
logrus.Debugf("Returning root with %#v", t.root.nodes)
return t.root, nil
}
// NewFSFromManifest creates a fuse filesystem using the given manifest
// to create the node tree and the content provider to serve up
// content for regular files.
func NewFSFromManifest(manifest *continuity.Manifest, mountRoot string, provider FileContentProvider) (fs.FS, error) {
tree := treeRoot{
root: NewDir(0, provider),
}
fi, err := os.Stat(mountRoot)
if err != nil {
return nil, err
}
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return nil, errors.New("could not access directory")
}
tree.root.uid = st.Uid
tree.root.gid = st.Gid
dirCache := map[string]*Dir{
rootPath: tree.root,
}
for i, resource := range manifest.Resources {
inode := uint64(i) + 1
if _, ok := resource.(continuity.Directory); ok {
cleanPath := filepath.Clean(resource.Path())
keyPath := fmt.Sprintf("%s%c", cleanPath, filepath.Separator)
d, ok := dirCache[keyPath]
if !ok {
d = NewDir(inode, provider)
dirCache[keyPath] = d
addNode(cleanPath, d, dirCache, provider)
} else {
d.inode = inode
}
if err := d.setResource(resource); err != nil {
return nil, err
}
continue
}
f := NewFile(inode, provider)
if err := f.setResource(resource); err != nil {
return nil, err
}
if rf, ok := resource.(continuity.RegularFile); ok {
for _, p := range rf.Paths() {
addNode(p, f, dirCache, provider)
}
} else {
addNode(resource.Path(), f, dirCache, provider)
}
}
return tree, nil
}