github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/core/commands/mount_unix.go (about)

     1  // +build linux darwin freebsd
     2  // +build !nofuse
     3  
     4  package commands
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"strings"
    10  	"time"
    11  
    12  	cmds "github.com/ipfs/go-ipfs/commands"
    13  	core "github.com/ipfs/go-ipfs/core"
    14  	ipns "github.com/ipfs/go-ipfs/fuse/ipns"
    15  	mount "github.com/ipfs/go-ipfs/fuse/mount"
    16  	rofs "github.com/ipfs/go-ipfs/fuse/readonly"
    17  	config "github.com/ipfs/go-ipfs/repo/config"
    18  )
    19  
    20  // amount of time to wait for mount errors
    21  // TODO is this non-deterministic?
    22  const mountTimeout = time.Second
    23  
    24  // fuseNoDirectory used to check the returning fuse error
    25  const fuseNoDirectory = "fusermount: failed to access mountpoint"
    26  
    27  // fuseExitStatus1 used to check the returning fuse error
    28  const fuseExitStatus1 = "fusermount: exit status 1"
    29  
    30  // platformFuseChecks can get overridden by arch-specific files
    31  // to run fuse checks (like checking the OSXFUSE version)
    32  var platformFuseChecks = func(*core.IpfsNode) error {
    33  	return nil
    34  }
    35  
    36  var MountCmd = &cmds.Command{
    37  	Helptext: cmds.HelpText{
    38  		Tagline: "Mounts IPFS to the filesystem (read-only)",
    39  		Synopsis: `
    40  ipfs mount [-f <ipfs mount path>] [-n <ipns mount path>]
    41  `,
    42  		ShortDescription: `
    43  Mount ipfs at a read-only mountpoint on the OS (default: /ipfs and /ipns).
    44  All ipfs objects will be accessible under that directory. Note that the
    45  root will not be listable, as it is virtual. Access known paths directly.
    46  
    47  You may have to create /ipfs and /ipns before using 'ipfs mount':
    48  
    49  > sudo mkdir /ipfs /ipns
    50  > sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
    51  > ipfs daemon &
    52  > ipfs mount
    53  `,
    54  		LongDescription: `
    55  Mount ipfs at a read-only mountpoint on the OS (default: /ipfs and /ipns).
    56  All ipfs objects will be accessible under that directory. Note that the
    57  root will not be listable, as it is virtual. Access known paths directly.
    58  
    59  You may have to create /ipfs and /ipns before using 'ipfs mount':
    60  
    61  > sudo mkdir /ipfs /ipns
    62  > sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
    63  > ipfs daemon &
    64  > ipfs mount
    65  
    66  EXAMPLE:
    67  
    68  # setup
    69  > mkdir foo
    70  > echo "baz" > foo/bar
    71  > ipfs add -r foo
    72  added QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR foo/bar
    73  added QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC foo
    74  > ipfs ls QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
    75  QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR 12 bar
    76  > ipfs cat QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
    77  baz
    78  
    79  # mount
    80  > ipfs daemon &
    81  > ipfs mount
    82  IPFS mounted at: /ipfs
    83  IPNS mounted at: /ipns
    84  > cd /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
    85  > ls
    86  bar
    87  > cat bar
    88  baz
    89  > cat /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC/bar
    90  baz
    91  > cat /ipfs/QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
    92  baz
    93  `,
    94  	},
    95  	Options: []cmds.Option{
    96  		cmds.StringOption("ipfs-path", "f", "The path where IPFS should be mounted"),
    97  		cmds.StringOption("ipns-path", "n", "The path where IPNS should be mounted"),
    98  	},
    99  	Run: func(req cmds.Request, res cmds.Response) {
   100  		cfg, err := req.InvocContext().GetConfig()
   101  		if err != nil {
   102  			res.SetError(err, cmds.ErrNormal)
   103  			return
   104  		}
   105  
   106  		node, err := req.InvocContext().GetNode()
   107  		if err != nil {
   108  			res.SetError(err, cmds.ErrNormal)
   109  			return
   110  		}
   111  
   112  		// error if we aren't running node in online mode
   113  		if !node.OnlineMode() {
   114  			res.SetError(errNotOnline, cmds.ErrClient)
   115  			return
   116  		}
   117  
   118  		fsdir, found, err := req.Option("f").String()
   119  		if err != nil {
   120  			res.SetError(err, cmds.ErrNormal)
   121  			return
   122  		}
   123  		if !found {
   124  			fsdir = cfg.Mounts.IPFS // use default value
   125  		}
   126  
   127  		// get default mount points
   128  		nsdir, found, err := req.Option("n").String()
   129  		if err != nil {
   130  			res.SetError(err, cmds.ErrNormal)
   131  			return
   132  		}
   133  		if !found {
   134  			nsdir = cfg.Mounts.IPNS // NB: be sure to not redeclare!
   135  		}
   136  
   137  		err = Mount(node, fsdir, nsdir)
   138  		if err != nil {
   139  			res.SetError(err, cmds.ErrNormal)
   140  			return
   141  		}
   142  
   143  		var output config.Mounts
   144  		output.IPFS = fsdir
   145  		output.IPNS = nsdir
   146  		res.SetOutput(&output)
   147  	},
   148  	Type: config.Mounts{},
   149  	Marshalers: cmds.MarshalerMap{
   150  		cmds.Text: func(res cmds.Response) (io.Reader, error) {
   151  			v := res.Output().(*config.Mounts)
   152  			s := fmt.Sprintf("IPFS mounted at: %s\n", v.IPFS)
   153  			s += fmt.Sprintf("IPNS mounted at: %s\n", v.IPNS)
   154  			return strings.NewReader(s), nil
   155  		},
   156  	},
   157  }
   158  
   159  func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
   160  	// check if we already have live mounts.
   161  	// if the user said "Mount", then there must be something wrong.
   162  	// so, close them and try again.
   163  	if node.Mounts.Ipfs != nil {
   164  		node.Mounts.Ipfs.Unmount()
   165  	}
   166  	if node.Mounts.Ipns != nil {
   167  		node.Mounts.Ipns.Unmount()
   168  	}
   169  
   170  	if err := platformFuseChecks(node); err != nil {
   171  		return err
   172  	}
   173  
   174  	var err error
   175  	if err = doMount(node, fsdir, nsdir); err != nil {
   176  		return err
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
   183  	fmtFuseErr := func(err error, mountpoint string) error {
   184  		s := err.Error()
   185  		if strings.Contains(s, fuseNoDirectory) {
   186  			s = strings.Replace(s, `fusermount: "fusermount:`, "", -1)
   187  			s = strings.Replace(s, `\n", exit status 1`, "", -1)
   188  			return cmds.ClientError(s)
   189  		}
   190  		if s == fuseExitStatus1 {
   191  			s = fmt.Sprintf("fuse failed to access mountpoint %s", mountpoint)
   192  			return cmds.ClientError(s)
   193  		}
   194  		return err
   195  	}
   196  
   197  	// this sync stuff is so that both can be mounted simultaneously.
   198  	var fsmount mount.Mount
   199  	var nsmount mount.Mount
   200  	var err1 error
   201  	var err2 error
   202  
   203  	done := make(chan struct{})
   204  
   205  	go func() {
   206  		fsmount, err1 = rofs.Mount(node, fsdir)
   207  		done <- struct{}{}
   208  	}()
   209  
   210  	go func() {
   211  		nsmount, err2 = ipns.Mount(node, nsdir, fsdir)
   212  		done <- struct{}{}
   213  	}()
   214  
   215  	<-done
   216  	<-done
   217  
   218  	if err1 != nil || err2 != nil {
   219  		log.Errorf("error mounting: %s %s", err1, err2)
   220  		if fsmount != nil {
   221  			fsmount.Unmount()
   222  		}
   223  		if nsmount != nil {
   224  			nsmount.Unmount()
   225  		}
   226  
   227  		if err1 != nil {
   228  			return fmtFuseErr(err1, fsdir)
   229  		}
   230  		return fmtFuseErr(err2, nsdir)
   231  	}
   232  
   233  	// setup node state, so that it can be cancelled
   234  	node.Mounts.Ipfs = fsmount
   235  	node.Mounts.Ipns = nsmount
   236  	return nil
   237  }