github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/fuse/ipns/ipns_unix.go (about)

     1  // +build !nofuse
     2  
     3  // package fuse/ipns implements a fuse filesystem that interfaces
     4  // with ipns, the naming system for ipfs.
     5  package ipns
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"strings"
    12  
    13  	fuse "github.com/ipfs/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
    14  	fs "github.com/ipfs/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
    15  	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
    16  	eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
    17  
    18  	key "github.com/ipfs/go-ipfs/blocks/key"
    19  	core "github.com/ipfs/go-ipfs/core"
    20  	nsfs "github.com/ipfs/go-ipfs/ipnsfs"
    21  	dag "github.com/ipfs/go-ipfs/merkledag"
    22  	ci "github.com/ipfs/go-ipfs/p2p/crypto"
    23  	ft "github.com/ipfs/go-ipfs/unixfs"
    24  )
    25  
    26  var log = eventlog.Logger("fuse/ipns")
    27  
    28  // FileSystem is the readwrite IPNS Fuse Filesystem.
    29  type FileSystem struct {
    30  	Ipfs     *core.IpfsNode
    31  	RootNode *Root
    32  }
    33  
    34  // NewFileSystem constructs new fs using given core.IpfsNode instance.
    35  func NewFileSystem(ipfs *core.IpfsNode, sk ci.PrivKey, ipfspath, ipnspath string) (*FileSystem, error) {
    36  	root, err := CreateRoot(ipfs, []ci.PrivKey{sk}, ipfspath, ipnspath)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	return &FileSystem{Ipfs: ipfs, RootNode: root}, nil
    41  }
    42  
    43  // Root constructs the Root of the filesystem, a Root object.
    44  func (f *FileSystem) Root() (fs.Node, error) {
    45  	log.Debug("Filesystem, get root")
    46  	return f.RootNode, nil
    47  }
    48  
    49  func (f *FileSystem) Destroy() {
    50  	err := f.RootNode.Close()
    51  	if err != nil {
    52  		log.Errorf("Error Shutting Down Filesystem: %s\n", err)
    53  	}
    54  }
    55  
    56  // Root is the root object of the filesystem tree.
    57  type Root struct {
    58  	Ipfs *core.IpfsNode
    59  	Keys []ci.PrivKey
    60  
    61  	// Used for symlinking into ipfs
    62  	IpfsRoot  string
    63  	IpnsRoot  string
    64  	LocalDirs map[string]fs.Node
    65  	Roots     map[string]*nsfs.KeyRoot
    66  
    67  	fs        *nsfs.Filesystem
    68  	LocalLink *Link
    69  }
    70  
    71  func CreateRoot(ipfs *core.IpfsNode, keys []ci.PrivKey, ipfspath, ipnspath string) (*Root, error) {
    72  	ldirs := make(map[string]fs.Node)
    73  	roots := make(map[string]*nsfs.KeyRoot)
    74  	for _, k := range keys {
    75  		pkh, err := k.GetPublic().Hash()
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  		name := key.Key(pkh).B58String()
    80  		root, err := ipfs.IpnsFs.GetRoot(name)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  
    85  		roots[name] = root
    86  
    87  		switch val := root.GetValue().(type) {
    88  		case *nsfs.Directory:
    89  			ldirs[name] = &Directory{dir: val}
    90  		case *nsfs.File:
    91  			ldirs[name] = &File{fi: val}
    92  		default:
    93  			return nil, errors.New("unrecognized type")
    94  		}
    95  	}
    96  
    97  	return &Root{
    98  		fs:        ipfs.IpnsFs,
    99  		Ipfs:      ipfs,
   100  		IpfsRoot:  ipfspath,
   101  		IpnsRoot:  ipnspath,
   102  		Keys:      keys,
   103  		LocalDirs: ldirs,
   104  		LocalLink: &Link{ipfs.Identity.Pretty()},
   105  		Roots:     roots,
   106  	}, nil
   107  }
   108  
   109  // Attr returns file attributes.
   110  func (*Root) Attr(ctx context.Context, a *fuse.Attr) error {
   111  	log.Debug("Root Attr")
   112  	*a = fuse.Attr{Mode: os.ModeDir | 0111} // -rw+x
   113  	return nil
   114  }
   115  
   116  // Lookup performs a lookup under this node.
   117  func (s *Root) Lookup(ctx context.Context, name string) (fs.Node, error) {
   118  	switch name {
   119  	case "mach_kernel", ".hidden", "._.":
   120  		// Just quiet some log noise on OS X.
   121  		return nil, fuse.ENOENT
   122  	}
   123  
   124  	// Local symlink to the node ID keyspace
   125  	if name == "local" {
   126  		if s.LocalLink == nil {
   127  			return nil, fuse.ENOENT
   128  		}
   129  		return s.LocalLink, nil
   130  	}
   131  
   132  	nd, ok := s.LocalDirs[name]
   133  	if ok {
   134  		switch nd := nd.(type) {
   135  		case *Directory:
   136  			return nd, nil
   137  		case *File:
   138  			return nd, nil
   139  		default:
   140  			return nil, fuse.EIO
   141  		}
   142  	}
   143  
   144  	// other links go through ipns resolution and are symlinked into the ipfs mountpoint
   145  	resolved, err := s.Ipfs.Namesys.Resolve(s.Ipfs.Context(), name)
   146  	if err != nil {
   147  		log.Warningf("ipns: namesys resolve error: %s", err)
   148  		return nil, fuse.ENOENT
   149  	}
   150  
   151  	segments := resolved.Segments()
   152  	if segments[0] == "ipfs" {
   153  		p := strings.Join(resolved.Segments()[1:], "/")
   154  		return &Link{s.IpfsRoot + "/" + p}, nil
   155  	} else {
   156  		log.Error("Invalid path.Path: ", resolved)
   157  		return nil, errors.New("invalid path from ipns record")
   158  	}
   159  }
   160  
   161  func (r *Root) Close() error {
   162  	for _, kr := range r.Roots {
   163  		err := kr.Publish(r.Ipfs.Context())
   164  		if err != nil {
   165  			return err
   166  		}
   167  	}
   168  	return nil
   169  }
   170  
   171  // Forget is called when the filesystem is unmounted. probably.
   172  // see comments here: http://godoc.org/bazil.org/fuse/fs#FSDestroyer
   173  func (r *Root) Forget() {
   174  	err := r.Close()
   175  	if err != nil {
   176  		log.Error(err)
   177  	}
   178  }
   179  
   180  // ReadDirAll reads a particular directory. Will show locally available keys
   181  // as well as a symlink to the peerID key
   182  func (r *Root) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
   183  	log.Debug("Root ReadDirAll")
   184  	listing := []fuse.Dirent{
   185  		{
   186  			Name: "local",
   187  			Type: fuse.DT_Link,
   188  		},
   189  	}
   190  	for _, k := range r.Keys {
   191  		pub := k.GetPublic()
   192  		hash, err := pub.Hash()
   193  		if err != nil {
   194  			continue
   195  		}
   196  		ent := fuse.Dirent{
   197  			Name: key.Key(hash).Pretty(),
   198  			Type: fuse.DT_Dir,
   199  		}
   200  		listing = append(listing, ent)
   201  	}
   202  	return listing, nil
   203  }
   204  
   205  // Directory is wrapper over an ipnsfs directory to satisfy the fuse fs interface
   206  type Directory struct {
   207  	dir *nsfs.Directory
   208  
   209  	fs.NodeRef
   210  }
   211  
   212  // File is wrapper over an ipnsfs file to satisfy the fuse fs interface
   213  type File struct {
   214  	fi *nsfs.File
   215  
   216  	fs.NodeRef
   217  }
   218  
   219  // Attr returns the attributes of a given node.
   220  func (d *Directory) Attr(ctx context.Context, a *fuse.Attr) error {
   221  	log.Debug("Directory Attr")
   222  	*a = fuse.Attr{
   223  		Mode: os.ModeDir | 0555,
   224  		Uid:  uint32(os.Getuid()),
   225  		Gid:  uint32(os.Getgid()),
   226  	}
   227  	return nil
   228  }
   229  
   230  // Attr returns the attributes of a given node.
   231  func (fi *File) Attr(ctx context.Context, a *fuse.Attr) error {
   232  	log.Debug("File Attr")
   233  	size, err := fi.fi.Size()
   234  	if err != nil {
   235  		// In this case, the dag node in question may not be unixfs
   236  		return fmt.Errorf("fuse/ipns: failed to get file.Size(): %s", err)
   237  	}
   238  	*a = fuse.Attr{
   239  		Mode: os.FileMode(0666),
   240  		Size: uint64(size),
   241  		Uid:  uint32(os.Getuid()),
   242  		Gid:  uint32(os.Getgid()),
   243  	}
   244  	return nil
   245  }
   246  
   247  // Lookup performs a lookup under this node.
   248  func (s *Directory) Lookup(ctx context.Context, name string) (fs.Node, error) {
   249  	child, err := s.dir.Child(name)
   250  	if err != nil {
   251  		// todo: make this error more versatile.
   252  		return nil, fuse.ENOENT
   253  	}
   254  
   255  	switch child := child.(type) {
   256  	case *nsfs.Directory:
   257  		return &Directory{dir: child}, nil
   258  	case *nsfs.File:
   259  		return &File{fi: child}, nil
   260  	default:
   261  		// NB: if this happens, we do not want to continue, unpredictable behaviour
   262  		// may occur.
   263  		panic("invalid type found under directory. programmer error.")
   264  	}
   265  }
   266  
   267  // ReadDirAll reads the link structure as directory entries
   268  func (dir *Directory) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
   269  	var entries []fuse.Dirent
   270  	for _, name := range dir.dir.List() {
   271  		dirent := fuse.Dirent{Name: name}
   272  
   273  		// TODO: make dir.dir.List() return dirinfos
   274  		child, err := dir.dir.Child(name)
   275  		if err != nil {
   276  			return nil, err
   277  		}
   278  
   279  		switch child.Type() {
   280  		case nsfs.TDir:
   281  			dirent.Type = fuse.DT_Dir
   282  		case nsfs.TFile:
   283  			dirent.Type = fuse.DT_File
   284  		}
   285  
   286  		entries = append(entries, dirent)
   287  	}
   288  
   289  	if len(entries) > 0 {
   290  		return entries, nil
   291  	}
   292  	return nil, fuse.ENOENT
   293  }
   294  
   295  func (fi *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
   296  	_, err := fi.fi.Seek(req.Offset, os.SEEK_SET)
   297  	if err != nil {
   298  		return err
   299  	}
   300  
   301  	fisize, err := fi.fi.Size()
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	select {
   307  	case <-ctx.Done():
   308  		return ctx.Err()
   309  	default:
   310  	}
   311  
   312  	readsize := min(req.Size, int(fisize-req.Offset))
   313  	n, err := fi.fi.CtxReadFull(ctx, resp.Data[:readsize])
   314  	resp.Data = resp.Data[:n]
   315  	return err
   316  }
   317  
   318  func (fi *File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
   319  	// TODO: at some point, ensure that WriteAt here respects the context
   320  	wrote, err := fi.fi.WriteAt(req.Data, req.Offset)
   321  	if err != nil {
   322  		return err
   323  	}
   324  	resp.Size = wrote
   325  	return nil
   326  }
   327  
   328  func (fi *File) Flush(ctx context.Context, req *fuse.FlushRequest) error {
   329  	errs := make(chan error, 1)
   330  	go func() {
   331  		errs <- fi.fi.Close()
   332  	}()
   333  	select {
   334  	case err := <-errs:
   335  		return err
   336  	case <-ctx.Done():
   337  		return ctx.Err()
   338  	}
   339  }
   340  
   341  func (fi *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {
   342  	cursize, err := fi.fi.Size()
   343  	if err != nil {
   344  		return err
   345  	}
   346  	if cursize != int64(req.Size) {
   347  		err := fi.fi.Truncate(int64(req.Size))
   348  		if err != nil {
   349  			return err
   350  		}
   351  	}
   352  	return nil
   353  }
   354  
   355  // Fsync flushes the content in the file to disk, but does not
   356  // update the dag tree internally
   357  func (fi *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
   358  	errs := make(chan error, 1)
   359  	go func() {
   360  		errs <- fi.fi.Sync()
   361  	}()
   362  	select {
   363  	case err := <-errs:
   364  		return err
   365  	case <-ctx.Done():
   366  		return ctx.Err()
   367  	}
   368  }
   369  
   370  func (fi *File) Forget() {
   371  	err := fi.fi.Sync()
   372  	if err != nil {
   373  		log.Debug("Forget file error: ", err)
   374  	}
   375  }
   376  
   377  func (dir *Directory) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
   378  	child, err := dir.dir.Mkdir(req.Name)
   379  	if err != nil {
   380  		return nil, err
   381  	}
   382  
   383  	return &Directory{dir: child}, nil
   384  }
   385  
   386  func (fi *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
   387  	if req.Flags&fuse.OpenTruncate != 0 {
   388  		log.Info("Need to truncate file!")
   389  		err := fi.fi.Truncate(0)
   390  		if err != nil {
   391  			return nil, err
   392  		}
   393  	} else if req.Flags&fuse.OpenAppend != 0 {
   394  		log.Info("Need to append to file!")
   395  
   396  		// seek(0) essentially resets the file object, this is required for appends to work
   397  		// properly
   398  		_, err := fi.fi.Seek(0, os.SEEK_SET)
   399  		if err != nil {
   400  			log.Error("seek reset failed: ", err)
   401  			return nil, err
   402  		}
   403  	}
   404  	return fi, nil
   405  }
   406  
   407  func (fi *File) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
   408  	return fi.fi.Close()
   409  }
   410  
   411  func (dir *Directory) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
   412  	// New 'empty' file
   413  	nd := &dag.Node{Data: ft.FilePBData(nil, 0)}
   414  	err := dir.dir.AddChild(req.Name, nd)
   415  	if err != nil {
   416  		return nil, nil, err
   417  	}
   418  
   419  	child, err := dir.dir.Child(req.Name)
   420  	if err != nil {
   421  		return nil, nil, err
   422  	}
   423  
   424  	fi, ok := child.(*nsfs.File)
   425  	if !ok {
   426  		return nil, nil, errors.New("child creation failed")
   427  	}
   428  
   429  	nodechild := &File{fi: fi}
   430  	return nodechild, nodechild, nil
   431  }
   432  
   433  func (dir *Directory) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
   434  	err := dir.dir.Unlink(req.Name)
   435  	if err != nil {
   436  		return fuse.ENOENT
   437  	}
   438  	return nil
   439  }
   440  
   441  // Rename implements NodeRenamer
   442  func (dir *Directory) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
   443  	cur, err := dir.dir.Child(req.OldName)
   444  	if err != nil {
   445  		return err
   446  	}
   447  
   448  	err = dir.dir.Unlink(req.OldName)
   449  	if err != nil {
   450  		return err
   451  	}
   452  
   453  	switch newDir := newDir.(type) {
   454  	case *Directory:
   455  		nd, err := cur.GetNode()
   456  		if err != nil {
   457  			return err
   458  		}
   459  
   460  		err = newDir.dir.AddChild(req.NewName, nd)
   461  		if err != nil {
   462  			return err
   463  		}
   464  	case *File:
   465  		log.Error("Cannot move node into a file!")
   466  		return fuse.EPERM
   467  	default:
   468  		log.Error("Unknown node type for rename target dir!")
   469  		return errors.New("Unknown fs node type!")
   470  	}
   471  	return nil
   472  }
   473  
   474  func min(a, b int) int {
   475  	if a < b {
   476  		return a
   477  	}
   478  	return b
   479  }
   480  
   481  // to check that out Node implements all the interfaces we want
   482  type ipnsRoot interface {
   483  	fs.Node
   484  	fs.HandleReadDirAller
   485  	fs.NodeStringLookuper
   486  }
   487  
   488  var _ ipnsRoot = (*Root)(nil)
   489  
   490  type ipnsDirectory interface {
   491  	fs.HandleReadDirAller
   492  	fs.Node
   493  	fs.NodeCreater
   494  	fs.NodeMkdirer
   495  	fs.NodeRemover
   496  	fs.NodeRenamer
   497  	fs.NodeStringLookuper
   498  }
   499  
   500  var _ ipnsDirectory = (*Directory)(nil)
   501  
   502  type ipnsFile interface {
   503  	fs.HandleFlusher
   504  	fs.HandleReader
   505  	fs.HandleWriter
   506  	fs.HandleReleaser
   507  	fs.Node
   508  	fs.NodeFsyncer
   509  	fs.NodeOpener
   510  }
   511  
   512  var _ ipnsFile = (*File)(nil)