1// +build linux darwin freebsd
2
3/*
4   Copyright The containerd Authors.
5
6   Licensed under the Apache License, Version 2.0 (the "License");
7   you may not use this file except in compliance with the License.
8   You may obtain a copy of the License at
9
10       http://www.apache.org/licenses/LICENSE-2.0
11
12   Unless required by applicable law or agreed to in writing, software
13   distributed under the License is distributed on an "AS IS" BASIS,
14   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   See the License for the specific language governing permissions and
16   limitations under the License.
17*/
18
19package continuityfs
20
21import (
22	"context"
23	"errors"
24	"fmt"
25	"io"
26	"os"
27	"path/filepath"
28	"syscall"
29
30	"bazil.org/fuse"
31	"bazil.org/fuse/fs"
32	"github.com/containerd/continuity"
33	"github.com/opencontainers/go-digest"
34	"github.com/sirupsen/logrus"
35)
36
37// File represents any file type (non directory) in the filesystem
38type File struct {
39	inode    uint64
40	uid      uint32
41	gid      uint32
42	provider FileContentProvider
43	resource continuity.Resource
44}
45
46// NewFile creates a new file with the given inode and content provider
47func NewFile(inode uint64, provider FileContentProvider) *File {
48	return &File{
49		inode:    inode,
50		provider: provider,
51	}
52}
53
54func (f *File) setResource(r continuity.Resource) (err error) {
55	// TODO: error out if uid excesses uint32?
56	f.uid = uint32(r.UID())
57	f.gid = uint32(r.GID())
58	f.resource = r
59
60	return
61}
62
63// Attr sets the fuse attribute for the file
64func (f *File) Attr(ctx context.Context, attr *fuse.Attr) (err error) {
65	// Set attributes from resource metadata
66	attr.Mode = f.resource.Mode()
67	attr.Uid = f.uid
68	attr.Gid = f.gid
69
70	if rf, ok := f.resource.(continuity.RegularFile); ok {
71		attr.Nlink = uint32(len(rf.Paths()))
72		attr.Size = uint64(rf.Size())
73	} else {
74		attr.Nlink = 1
75	}
76
77	attr.Inode = f.inode
78
79	return nil
80}
81
82// Open opens the file for read
83// currently only regular files can be opened
84func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
85	var dgst digest.Digest
86	if rf, ok := f.resource.(continuity.RegularFile); ok {
87		digests := rf.Digests()
88		if len(digests) > 0 {
89			dgst = digests[0]
90		}
91	}
92	// TODO(dmcgowan): else check if device can be opened for read
93	r, err := f.provider.Open(f.resource.Path(), dgst)
94	if err != nil {
95		logrus.Debugf("Error opening handle: %v", err)
96		return nil, err
97	}
98	return &fileHandler{
99		reader: r,
100	}, nil
101
102}
103
104func (f *File) getDirent(name string) (fuse.Dirent, error) {
105	var t fuse.DirentType
106	switch f.resource.(type) {
107	case continuity.RegularFile:
108		t = fuse.DT_File
109	case continuity.SymLink:
110		t = fuse.DT_Link
111	case continuity.Device:
112		t = fuse.DT_Block
113	case continuity.NamedPipe:
114		t = fuse.DT_FIFO
115	default:
116		t = fuse.DT_Unknown
117	}
118
119	return fuse.Dirent{
120		Inode: f.inode,
121		Type:  t,
122		Name:  name,
123	}, nil
124}
125
126type fileHandler struct {
127	offset int64
128	reader io.ReadCloser
129}
130
131func (h *fileHandler) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
132	if h.offset != req.Offset {
133		if seeker, ok := h.reader.(io.Seeker); ok {
134			if _, err := seeker.Seek(req.Offset, io.SeekStart); err != nil {
135				logrus.Debugf("Error seeking: %v", err)
136				return err
137			}
138			h.offset = req.Offset
139		} else {
140			return errors.New("unable to seek to offset")
141		}
142	}
143
144	n, err := h.reader.Read(resp.Data[:req.Size])
145	if err != nil {
146		logrus.Debugf("Read error: %v", err)
147		return err
148	}
149	h.offset = h.offset + int64(n)
150
151	resp.Data = resp.Data[:n]
152
153	return nil
154}
155
156func (h *fileHandler) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
157	return h.reader.Close()
158}
159
160// Dir represents a file system directory
161type Dir struct {
162	inode    uint64
163	uid      uint32
164	gid      uint32
165	nodes    map[string]fs.Node
166	provider FileContentProvider
167	resource continuity.Resource
168}
169
170// Attr sets the fuse attributes for the directory
171func (d *Dir) Attr(ctx context.Context, attr *fuse.Attr) (err error) {
172	if d.resource == nil {
173		attr.Mode = os.ModeDir | 0555
174	} else {
175		attr.Mode = d.resource.Mode()
176	}
177
178	attr.Uid = d.uid
179	attr.Gid = d.gid
180	attr.Inode = d.inode
181
182	return nil
183}
184
185func (d *Dir) getDirent(name string) (fuse.Dirent, error) {
186	return fuse.Dirent{
187		Inode: d.inode,
188		Type:  fuse.DT_Dir,
189		Name:  name,
190	}, nil
191}
192
193type direnter interface {
194	getDirent(name string) (fuse.Dirent, error)
195}
196
197// Lookup looks up the filesystem node for the name within the directory
198func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
199	node, ok := d.nodes[name]
200	if !ok {
201		return nil, fuse.ENOENT
202	}
203	return node, nil
204}
205
206// ReadDirAll reads all the directory entries
207func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
208	ents := make([]fuse.Dirent, 0, len(d.nodes))
209	for name, node := range d.nodes {
210		if nd, ok := node.(direnter); ok {
211			de, err := nd.getDirent(name)
212			if err != nil {
213				return nil, err
214			}
215			ents = append(ents, de)
216		} else {
217			logrus.Errorf("%s does not have a directory entry", name)
218		}
219	}
220
221	return ents, nil
222}
223
224func (d *Dir) setResource(r continuity.Resource) (err error) {
225	d.uid = uint32(r.UID())
226	d.gid = uint32(r.GID())
227	d.resource = r
228
229	return
230}
231
232// NewDir creates a new directory object
233func NewDir(inode uint64, provider FileContentProvider) *Dir {
234	return &Dir{
235		inode:    inode,
236		nodes:    map[string]fs.Node{},
237		provider: provider,
238	}
239}
240
241var (
242	rootPath = fmt.Sprintf("%c", filepath.Separator)
243)
244
245func addNode(path string, node fs.Node, cache map[string]*Dir, provider FileContentProvider) {
246	dirPath, file := filepath.Split(path)
247	d, ok := cache[dirPath]
248	if !ok {
249		d = NewDir(0, provider)
250		cache[dirPath] = d
251		addNode(filepath.Clean(dirPath), d, cache, provider)
252	}
253	d.nodes[file] = node
254	logrus.Debugf("%s (%#v) added to %s", file, node, dirPath)
255}
256
257type treeRoot struct {
258	root *Dir
259}
260
261func (t treeRoot) Root() (fs.Node, error) {
262	logrus.Debugf("Returning root with %#v", t.root.nodes)
263	return t.root, nil
264}
265
266// NewFSFromManifest creates a fuse filesystem using the given manifest
267// to create the node tree and the content provider to serve up
268// content for regular files.
269func NewFSFromManifest(manifest *continuity.Manifest, mountRoot string, provider FileContentProvider) (fs.FS, error) {
270	tree := treeRoot{
271		root: NewDir(0, provider),
272	}
273
274	fi, err := os.Stat(mountRoot)
275	if err != nil {
276		return nil, err
277	}
278	st, ok := fi.Sys().(*syscall.Stat_t)
279	if !ok {
280		return nil, errors.New("could not access directory")
281	}
282	tree.root.uid = st.Uid
283	tree.root.gid = st.Gid
284
285	dirCache := map[string]*Dir{
286		rootPath: tree.root,
287	}
288
289	for i, resource := range manifest.Resources {
290		inode := uint64(i) + 1
291		if _, ok := resource.(continuity.Directory); ok {
292			cleanPath := filepath.Clean(resource.Path())
293			keyPath := fmt.Sprintf("%s%c", cleanPath, filepath.Separator)
294			d, ok := dirCache[keyPath]
295			if !ok {
296				d = NewDir(inode, provider)
297				dirCache[keyPath] = d
298				addNode(cleanPath, d, dirCache, provider)
299			} else {
300				d.inode = inode
301			}
302			if err := d.setResource(resource); err != nil {
303				return nil, err
304			}
305			continue
306		}
307		f := NewFile(inode, provider)
308		if err := f.setResource(resource); err != nil {
309			return nil, err
310		}
311		if rf, ok := resource.(continuity.RegularFile); ok {
312
313			for _, p := range rf.Paths() {
314				addNode(p, f, dirCache, provider)
315			}
316		} else {
317			addNode(resource.Path(), f, dirCache, provider)
318		}
319	}
320
321	return tree, nil
322}
323