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