binctr/vendor/github.com/containerd/continuity/continuityfs/fuse.go
Jess Frazelle 94d1cfbfbf
update vendor
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
2018-09-25 12:27:46 -04:00

322 lines
7.3 KiB
Go

// +build linux darwin freebsd
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package continuityfs
import (
"context"
"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"
)
// 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
}