// +build linux darwin freebsd // +build !nofuse package readonly import ( "fmt" "io" "os" "syscall" fuse "github.com/ipfs/go-ipfs/Godeps/_workspace/src/bazil.org/fuse" fs "github.com/ipfs/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs" core "github.com/ipfs/go-ipfs/core" mdag "github.com/ipfs/go-ipfs/merkledag" path "github.com/ipfs/go-ipfs/path" lgbl "github.com/ipfs/go-ipfs/thirdparty/loggables" uio "github.com/ipfs/go-ipfs/unixfs/io" ftpb "github.com/ipfs/go-ipfs/unixfs/pb" proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto" "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context" logging "gx/ipfs/Qmazh5oNUVsDZTs2g59rq8aYQqwpss8tcUWQzor5sCCEuH/go-log" ) var log = logging.Logger("fuse/ipfs") // FileSystem is the readonly Ipfs Fuse Filesystem. type FileSystem struct { Ipfs *core.IpfsNode } // NewFileSystem constructs new fs using given core.IpfsNode instance. func NewFileSystem(ipfs *core.IpfsNode) *FileSystem { return &FileSystem{Ipfs: ipfs} } // Root constructs the Root of the filesystem, a Root object. func (f FileSystem) Root() (fs.Node, error) { return &Root{Ipfs: f.Ipfs}, nil } // Root is the root object of the filesystem tree. type Root struct { Ipfs *core.IpfsNode } // Attr returns file attributes. func (*Root) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0111 // -rw+x return nil } // Lookup performs a lookup under this node. func (s *Root) Lookup(ctx context.Context, name string) (fs.Node, error) { log.Debugf("Root Lookup: '%s'", name) switch name { case "mach_kernel", ".hidden", "._.": // Just quiet some log noise on OS X. return nil, fuse.ENOENT } nd, err := s.Ipfs.Resolver.ResolvePath(ctx, path.Path(name)) if err != nil { // todo: make this error more versatile. return nil, fuse.ENOENT } return &Node{Ipfs: s.Ipfs, Nd: nd}, nil } // ReadDirAll reads a particular directory. Disallowed for root. func (*Root) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { log.Debug("Read Root.") return nil, fuse.EPERM } // Node is the core object representing a filesystem tree node. type Node struct { Ipfs *core.IpfsNode Nd *mdag.Node fd *uio.DagReader cached *ftpb.Data } func (s *Node) loadData() error { s.cached = new(ftpb.Data) return proto.Unmarshal(s.Nd.Data, s.cached) } // Attr returns the attributes of a given node. func (s *Node) Attr(ctx context.Context, a *fuse.Attr) error { log.Debug("Node attr.") if s.cached == nil { if err := s.loadData(); err != nil { return fmt.Errorf("readonly: loadData() failed: %s", err) } } switch s.cached.GetType() { case ftpb.Data_Directory: a.Mode = os.ModeDir | 0555 a.Uid = uint32(os.Getuid()) a.Gid = uint32(os.Getgid()) case ftpb.Data_File: size := s.cached.GetFilesize() a.Mode = 0444 a.Size = uint64(size) a.Blocks = uint64(len(s.Nd.Links)) a.Uid = uint32(os.Getuid()) a.Gid = uint32(os.Getgid()) case ftpb.Data_Raw: a.Mode = 0444 a.Size = uint64(len(s.cached.GetData())) a.Blocks = uint64(len(s.Nd.Links)) a.Uid = uint32(os.Getuid()) a.Gid = uint32(os.Getgid()) case ftpb.Data_Symlink: a.Mode = 0777 | os.ModeSymlink a.Size = uint64(len(s.cached.GetData())) a.Uid = uint32(os.Getuid()) a.Gid = uint32(os.Getgid()) default: return fmt.Errorf("Invalid data type - %s", s.cached.GetType()) } return nil } // Lookup performs a lookup under this node. func (s *Node) Lookup(ctx context.Context, name string) (fs.Node, error) { log.Debugf("Lookup '%s'", name) nodes, err := s.Ipfs.Resolver.ResolveLinks(ctx, s.Nd, []string{name}) if err != nil { // todo: make this error more versatile. return nil, fuse.ENOENT } return &Node{Ipfs: s.Ipfs, Nd: nodes[len(nodes)-1]}, nil } // ReadDirAll reads the link structure as directory entries func (s *Node) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { log.Debug("Node ReadDir") entries := make([]fuse.Dirent, len(s.Nd.Links)) for i, link := range s.Nd.Links { n := link.Name if len(n) == 0 { n = link.Hash.B58String() } entries[i] = fuse.Dirent{Name: n, Type: fuse.DT_File} } if len(entries) > 0 { return entries, nil } return nil, fuse.ENOENT } func (s *Node) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) { if s.cached.GetType() != ftpb.Data_Symlink { return "", fuse.Errno(syscall.EINVAL) } return string(s.cached.GetData()), nil } func (s *Node) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { k, err := s.Nd.Key() if err != nil { return err } // setup our logging event lm := make(lgbl.DeferredMap) lm["fs"] = "ipfs" lm["key"] = func() interface{} { return k.Pretty() } lm["req_offset"] = req.Offset lm["req_size"] = req.Size defer log.EventBegin(ctx, "fuseRead", lm).Done() r, err := uio.NewDagReader(ctx, s.Nd, s.Ipfs.DAG) if err != nil { return err } o, err := r.Seek(req.Offset, os.SEEK_SET) lm["res_offset"] = o if err != nil { return err } buf := resp.Data[:min(req.Size, int(int64(r.Size())-req.Offset))] n, err := io.ReadFull(r, buf) if err != nil && err != io.EOF { return err } resp.Data = resp.Data[:n] lm["res_size"] = n return nil // may be non-nil / not succeeded } // to check that out Node implements all the interfaces we want type roRoot interface { fs.Node fs.HandleReadDirAller fs.NodeStringLookuper } var _ roRoot = (*Root)(nil) type roNode interface { fs.HandleReadDirAller fs.HandleReader fs.Node fs.NodeStringLookuper fs.NodeReadlinker } var _ roNode = (*Node)(nil) func min(a, b int) int { if a < b { return a } return b }