readonly_unix.go 5.85 KB
Newer Older
1
// +build linux darwin freebsd
2

3 4
// package fuse/readonly implements a fuse filesystem to access files
// stored inside of ipfs.
5 6 7
package readonly

import (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
8
	"fmt"
9
	"io/ioutil"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
10 11 12 13
	"os"
	"os/exec"
	"runtime"
	"time"
14

15 16 17
	fuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
	fs "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
	proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
Jeromy's avatar
Jeromy committed
18

19
	core "github.com/jbenet/go-ipfs/core"
20
	mount "github.com/jbenet/go-ipfs/fuse/mount"
21
	mdag "github.com/jbenet/go-ipfs/merkledag"
22
	uio "github.com/jbenet/go-ipfs/unixfs/io"
23
	ftpb "github.com/jbenet/go-ipfs/unixfs/pb"
24
	u "github.com/jbenet/go-ipfs/util"
25 26
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
27
var log = u.Logger("ipfs")
Jeromy's avatar
Jeromy committed
28

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
29
// FileSystem is the readonly Ipfs Fuse Filesystem.
30
type FileSystem struct {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
31
	Ipfs *core.IpfsNode
32 33
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
34
// NewFileSystem constructs new fs using given core.IpfsNode instance.
35
func NewFileSystem(ipfs *core.IpfsNode) *FileSystem {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
36
	return &FileSystem{Ipfs: ipfs}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
37 38
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
39
// Root constructs the Root of the filesystem, a Root object.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
40
func (f FileSystem) Root() (fs.Node, fuse.Error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
41
	return &Root{Ipfs: f.Ipfs}, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
42 43
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
44
// Root is the root object of the filesystem tree.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
45
type Root struct {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
46
	Ipfs *core.IpfsNode
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
47 48
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
49
// Attr returns file attributes.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
50 51
func (*Root) Attr() fuse.Attr {
	return fuse.Attr{Mode: os.ModeDir | 0111} // -rw+x
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
52 53
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
54
// Lookup performs a lookup under this node.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
55
func (s *Root) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
56
	log.Debugf("Root Lookup: '%s'", name)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
57 58 59 60 61 62 63 64 65 66 67 68 69
	switch name {
	case "mach_kernel", ".hidden", "._.":
		// Just quiet some log noise on OS X.
		return nil, fuse.ENOENT
	}

	nd, err := s.Ipfs.Resolver.ResolvePath(name)
	if err != nil {
		// todo: make this error more versatile.
		return nil, fuse.ENOENT
	}

	return &Node{Ipfs: s.Ipfs, Nd: nd}, nil
70 71
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
72
// ReadDir reads a particular directory. Disallowed for root.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73
func (*Root) ReadDir(intr fs.Intr) ([]fuse.Dirent, fuse.Error) {
Jeromy's avatar
Jeromy committed
74
	log.Debug("Read Root.")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
75
	return nil, fuse.EPERM
76 77
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
78
// Node is the core object representing a filesystem tree node.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
79
type Node struct {
Jeromy's avatar
Jeromy committed
80 81
	Ipfs   *core.IpfsNode
	Nd     *mdag.Node
82
	fd     *uio.DagReader
83
	cached *ftpb.Data
Jeromy's avatar
Jeromy committed
84 85 86
}

func (s *Node) loadData() error {
87
	s.cached = new(ftpb.Data)
Jeromy's avatar
Jeromy committed
88
	return proto.Unmarshal(s.Nd.Data, s.cached)
89 90
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
91
// Attr returns the attributes of a given node.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
92
func (s *Node) Attr() fuse.Attr {
Jeromy's avatar
Jeromy committed
93
	log.Debug("Node attr.")
Jeromy's avatar
Jeromy committed
94 95 96 97
	if s.cached == nil {
		s.loadData()
	}
	switch s.cached.GetType() {
98
	case ftpb.Data_Directory:
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
99
		return fuse.Attr{Mode: os.ModeDir | 0555}
Jeromy's avatar
Jeromy committed
100 101
	case ftpb.Data_File:
		size := s.cached.GetFilesize()
Jeromy's avatar
Jeromy committed
102 103 104 105 106
		return fuse.Attr{
			Mode:   0444,
			Size:   uint64(size),
			Blocks: uint64(len(s.Nd.Links)),
		}
Jeromy's avatar
Jeromy committed
107 108 109 110 111 112 113
	case ftpb.Data_Raw:
		return fuse.Attr{
			Mode:   0444,
			Size:   uint64(len(s.cached.GetData())),
			Blocks: uint64(len(s.Nd.Links)),
		}

Jeromy's avatar
Jeromy committed
114
	default:
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
115
		log.Error("Invalid data type.")
Jeromy's avatar
Jeromy committed
116
		return fuse.Attr{}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
117
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
118
}
119

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
120
// Lookup performs a lookup under this node.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
121
func (s *Node) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) {
122
	log.Debugf("Lookup '%s'", name)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
123 124 125 126 127
	nd, err := s.Ipfs.Resolver.ResolveLinks(s.Nd, []string{name})
	if err != nil {
		// todo: make this error more versatile.
		return nil, fuse.ENOENT
	}
128

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129
	return &Node{Ipfs: s.Ipfs, Nd: nd}, nil
130 131
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
132
// ReadDir reads the link structure as directory entries
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133
func (s *Node) ReadDir(intr fs.Intr) ([]fuse.Dirent, fuse.Error) {
Jeromy's avatar
Jeromy committed
134
	log.Debug("Node ReadDir")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
135 136 137 138 139 140 141 142 143 144 145 146 147
	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
148 149
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
150
// ReadAll reads the object data as file data
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
151
func (s *Node) ReadAll(intr fs.Intr) ([]byte, fuse.Error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
152
	log.Debug("Read node.")
153
	r, err := uio.NewDagReader(s.Nd, s.Ipfs.DAG)
154 155 156 157 158 159
	if err != nil {
		return nil, err
	}
	// this is a terrible function... 'ReadAll'?
	// what if i have a 6TB file? GG RAM.
	return ioutil.ReadAll(r)
160 161
}

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
162 163
// Mount mounts an IpfsNode instance at a particular path. It
// serves until the process receives exit signals (to Unmount).
164 165
func Mount(ipfs *core.IpfsNode, fpath string) (mount.Mount, error) {
	log.Infof("Mounting ipfs at %s...", fpath)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
166

167
	// setup the Mount abstraction.
168
	m := mount.New(ipfs.Context(), fpath)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
169

170
	// go serve the mount
171 172 173
	m.Mount(func(m mount.Mount) error {
		return internalMount(ipfs, m)
	}, internalUnmount)
174 175 176 177 178 179

	select {
	case <-m.Closed():
		return nil, fmt.Errorf("failed to mount")
	case <-time.After(time.Second):
		// assume it worked...
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
180
	}
181 182 183 184 185

	// bind the mount (ContextCloser) to the node, so that when the node exits
	// the fsclosers are automatically closed.
	ipfs.AddCloserChild(m)
	return m, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
186
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
187

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
// mount attempts to mount the provided FUSE mount point
func internalMount(ipfs *core.IpfsNode, m mount.Mount) error {
	c, err := fuse.Mount(m.MountPoint())
	if err != nil {
		return err
	}
	defer c.Close()

	fsys := FileSystem{Ipfs: ipfs}

	log.Infof("Mounted ipfs at %s.", m.MountPoint())
	if err := fs.Serve(c, fsys); err != nil {
		return err
	}

	// check if the mount process has an error to report
	<-c.Ready
	if err := c.MountError; err != nil {
		m.Unmount()
		return err
	}
	return nil
}

// unmount attempts to unmount the provided FUSE mount point, forcibly
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
213
// if necessary.
214 215
func internalUnmount(m mount.Mount) error {
	point := m.MountPoint()
216
	log.Infof("Unmounting ipfs at %s...", point)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242

	var cmd *exec.Cmd
	switch runtime.GOOS {
	case "darwin":
		cmd = exec.Command("diskutil", "umount", "force", point)
	case "linux":
		cmd = exec.Command("fusermount", "-u", point)
	default:
		return fmt.Errorf("unmount: unimplemented")
	}

	errc := make(chan error, 1)
	go func() {
		if err := exec.Command("umount", point).Run(); err == nil {
			errc <- err
		}
		// retry to unmount with the fallback cmd
		errc <- cmd.Run()
	}()

	select {
	case <-time.After(1 * time.Second):
		return fmt.Errorf("umount timeout")
	case err := <-errc:
		return err
	}
243
}