github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/cmd/ipfs/main.go (about)

     1  // cmd/ipfs implements the primary CLI binary for ipfs
     2  package main
     3  
     4  import (
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"math/rand"
     9  	"os"
    10  	"os/signal"
    11  	"runtime"
    12  	"runtime/pprof"
    13  	"strings"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  
    18  	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
    19  	manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
    20  
    21  	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
    22  	cmds "github.com/ipfs/go-ipfs/commands"
    23  	cmdsCli "github.com/ipfs/go-ipfs/commands/cli"
    24  	cmdsHttp "github.com/ipfs/go-ipfs/commands/http"
    25  	core "github.com/ipfs/go-ipfs/core"
    26  	coreCmds "github.com/ipfs/go-ipfs/core/commands"
    27  	repo "github.com/ipfs/go-ipfs/repo"
    28  	config "github.com/ipfs/go-ipfs/repo/config"
    29  	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
    30  	eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
    31  	u "github.com/ipfs/go-ipfs/util"
    32  )
    33  
    34  // log is the command logger
    35  var log = eventlog.Logger("cmd/ipfs")
    36  
    37  var (
    38  	errUnexpectedApiOutput = errors.New("api returned unexpected output")
    39  	errApiVersionMismatch  = errors.New("api version mismatch")
    40  )
    41  
    42  const (
    43  	EnvEnableProfiling = "IPFS_PROF"
    44  	cpuProfile         = "ipfs.cpuprof"
    45  	heapProfile        = "ipfs.memprof"
    46  	errorFormat        = "ERROR: %v\n\n"
    47  )
    48  
    49  type cmdInvocation struct {
    50  	path []string
    51  	cmd  *cmds.Command
    52  	req  cmds.Request
    53  	node *core.IpfsNode
    54  }
    55  
    56  // main roadmap:
    57  // - parse the commandline to get a cmdInvocation
    58  // - if user requests, help, print it and exit.
    59  // - run the command invocation
    60  // - output the response
    61  // - if anything fails, print error, maybe with help
    62  func main() {
    63  	rand.Seed(time.Now().UnixNano())
    64  	runtime.GOMAXPROCS(3) // FIXME rm arbitrary choice for n
    65  	ctx := eventlog.ContextWithLoggable(context.Background(), eventlog.Uuid("session"))
    66  	var err error
    67  	var invoc cmdInvocation
    68  	defer invoc.close()
    69  
    70  	// we'll call this local helper to output errors.
    71  	// this is so we control how to print errors in one place.
    72  	printErr := func(err error) {
    73  		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
    74  	}
    75  
    76  	stopFunc, err := profileIfEnabled()
    77  	if err != nil {
    78  		printErr(err)
    79  		os.Exit(1)
    80  	}
    81  	defer stopFunc() // to be executed as late as possible
    82  
    83  	// this is a local helper to print out help text.
    84  	// there's some considerations that this makes easier.
    85  	printHelp := func(long bool, w io.Writer) {
    86  		helpFunc := cmdsCli.ShortHelp
    87  		if long {
    88  			helpFunc = cmdsCli.LongHelp
    89  		}
    90  
    91  		helpFunc("ipfs", Root, invoc.path, w)
    92  	}
    93  
    94  	// this is a message to tell the user how to get the help text
    95  	printMetaHelp := func(w io.Writer) {
    96  		cmdPath := strings.Join(invoc.path, " ")
    97  		fmt.Fprintf(w, "Use 'ipfs %s --help' for information about this command\n", cmdPath)
    98  	}
    99  
   100  	// Handle `ipfs help'
   101  	if len(os.Args) == 2 && os.Args[1] == "help" {
   102  		printHelp(false, os.Stdout)
   103  		os.Exit(0)
   104  	}
   105  
   106  	// parse the commandline into a command invocation
   107  	parseErr := invoc.Parse(ctx, os.Args[1:])
   108  
   109  	// BEFORE handling the parse error, if we have enough information
   110  	// AND the user requested help, print it out and exit
   111  	if invoc.req != nil {
   112  		longH, shortH, err := invoc.requestedHelp()
   113  		if err != nil {
   114  			printErr(err)
   115  			os.Exit(1)
   116  		}
   117  		if longH || shortH {
   118  			printHelp(longH, os.Stdout)
   119  			os.Exit(0)
   120  		}
   121  	}
   122  
   123  	// ok now handle parse error (which means cli input was wrong,
   124  	// e.g. incorrect number of args, or nonexistent subcommand)
   125  	if parseErr != nil {
   126  		printErr(parseErr)
   127  
   128  		// this was a user error, print help.
   129  		if invoc.cmd != nil {
   130  			// we need a newline space.
   131  			fmt.Fprintf(os.Stderr, "\n")
   132  			printMetaHelp(os.Stderr)
   133  		}
   134  		os.Exit(1)
   135  	}
   136  
   137  	// here we handle the cases where
   138  	// - commands with no Run func are invoked directly.
   139  	// - the main command is invoked.
   140  	if invoc.cmd == nil || invoc.cmd.Run == nil {
   141  		printHelp(false, os.Stdout)
   142  		os.Exit(0)
   143  	}
   144  
   145  	// ok, finally, run the command invocation.
   146  	intrh, ctx := invoc.SetupInterruptHandler(ctx)
   147  	defer intrh.Close()
   148  
   149  	output, err := invoc.Run(ctx)
   150  	if err != nil {
   151  		printErr(err)
   152  
   153  		// if this error was a client error, print short help too.
   154  		if isClientError(err) {
   155  			printMetaHelp(os.Stderr)
   156  		}
   157  		os.Exit(1)
   158  	}
   159  
   160  	// everything went better than expected :)
   161  	_, err = io.Copy(os.Stdout, output)
   162  	if err != nil {
   163  		printErr(err)
   164  
   165  		os.Exit(1)
   166  	}
   167  }
   168  
   169  func (i *cmdInvocation) Run(ctx context.Context) (output io.Reader, err error) {
   170  
   171  	// check if user wants to debug. option OR env var.
   172  	debug, _, err := i.req.Option("debug").Bool()
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	if debug || u.GetenvBool("DEBUG") || os.Getenv("IPFS_LOGGING") == "debug" {
   177  		u.Debug = true
   178  		u.SetDebugLogging()
   179  	}
   180  
   181  	res, err := callCommand(ctx, i.req, Root, i.cmd)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	if err := res.Error(); err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	return res.Reader()
   191  }
   192  
   193  func (i *cmdInvocation) constructNodeFunc(ctx context.Context) func() (*core.IpfsNode, error) {
   194  	return func() (*core.IpfsNode, error) {
   195  		if i.req == nil {
   196  			return nil, errors.New("constructing node without a request")
   197  		}
   198  
   199  		cmdctx := i.req.InvocContext()
   200  		if cmdctx == nil {
   201  			return nil, errors.New("constructing node without a request context")
   202  		}
   203  
   204  		r, err := fsrepo.Open(i.req.InvocContext().ConfigRoot)
   205  		if err != nil { // repo is owned by the node
   206  			return nil, err
   207  		}
   208  
   209  		// ok everything is good. set it on the invocation (for ownership)
   210  		// and return it.
   211  		n, err := core.NewNode(ctx, &core.BuildCfg{
   212  			Online: cmdctx.Online,
   213  			Repo:   r,
   214  		})
   215  		if err != nil {
   216  			return nil, err
   217  		}
   218  		i.node = n
   219  		return i.node, nil
   220  	}
   221  }
   222  
   223  func (i *cmdInvocation) close() {
   224  	// let's not forget teardown. If a node was initialized, we must close it.
   225  	// Note that this means the underlying req.Context().Node variable is exposed.
   226  	// this is gross, and should be changed when we extract out the exec Context.
   227  	if i.node != nil {
   228  		log.Info("Shutting down node...")
   229  		i.node.Close()
   230  	}
   231  }
   232  
   233  func (i *cmdInvocation) Parse(ctx context.Context, args []string) error {
   234  	var err error
   235  
   236  	i.req, i.cmd, i.path, err = cmdsCli.Parse(args, os.Stdin, Root)
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	repoPath, err := getRepoPath(i.req)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	log.Debugf("config path is %s", repoPath)
   246  
   247  	// this sets up the function that will initialize the config lazily.
   248  	cmdctx := i.req.InvocContext()
   249  	cmdctx.ConfigRoot = repoPath
   250  	cmdctx.LoadConfig = loadConfig
   251  	// this sets up the function that will initialize the node
   252  	// this is so that we can construct the node lazily.
   253  	cmdctx.ConstructNode = i.constructNodeFunc(ctx)
   254  
   255  	// if no encoding was specified by user, default to plaintext encoding
   256  	// (if command doesn't support plaintext, use JSON instead)
   257  	if !i.req.Option("encoding").Found() {
   258  		if i.req.Command().Marshalers != nil && i.req.Command().Marshalers[cmds.Text] != nil {
   259  			i.req.SetOption("encoding", cmds.Text)
   260  		} else {
   261  			i.req.SetOption("encoding", cmds.JSON)
   262  		}
   263  	}
   264  
   265  	return nil
   266  }
   267  
   268  func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
   269  	longHelp, _, err := i.req.Option("help").Bool()
   270  	if err != nil {
   271  		return false, false, err
   272  	}
   273  	shortHelp, _, err := i.req.Option("h").Bool()
   274  	if err != nil {
   275  		return false, false, err
   276  	}
   277  	return longHelp, shortHelp, nil
   278  }
   279  
   280  func callPreCommandHooks(ctx context.Context, details cmdDetails, req cmds.Request, root *cmds.Command) error {
   281  
   282  	log.Event(ctx, "callPreCommandHooks", &details)
   283  	log.Debug("Calling pre-command hooks...")
   284  
   285  	return nil
   286  }
   287  
   288  func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd *cmds.Command) (cmds.Response, error) {
   289  	log.Info(config.EnvDir, " ", req.InvocContext().ConfigRoot)
   290  	var res cmds.Response
   291  
   292  	err := req.SetRootContext(ctx)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	details, err := commandDetails(req.Path(), root)
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  
   302  	client, err := commandShouldRunOnDaemon(*details, req, root)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	err = callPreCommandHooks(ctx, *details, req, root)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	if cmd.PreRun != nil {
   313  		err = cmd.PreRun(req)
   314  		if err != nil {
   315  			return nil, err
   316  		}
   317  	}
   318  
   319  	if client != nil {
   320  		log.Debug("Executing command via API")
   321  		res, err = client.Send(req)
   322  		if err != nil {
   323  			if isConnRefused(err) {
   324  				err = repo.ErrApiNotRunning
   325  			}
   326  			return nil, err
   327  		}
   328  
   329  	} else {
   330  		log.Debug("Executing command locally")
   331  
   332  		err := req.SetRootContext(ctx)
   333  		if err != nil {
   334  			return nil, err
   335  		}
   336  
   337  		// Okay!!!!! NOW we can call the command.
   338  		res = root.Call(req)
   339  
   340  	}
   341  
   342  	if cmd.PostRun != nil {
   343  		cmd.PostRun(req, res)
   344  	}
   345  
   346  	return res, nil
   347  }
   348  
   349  // commandDetails returns a command's details for the command given by |path|
   350  // within the |root| command tree.
   351  //
   352  // Returns an error if the command is not found in the Command tree.
   353  func commandDetails(path []string, root *cmds.Command) (*cmdDetails, error) {
   354  	var details cmdDetails
   355  	// find the last command in path that has a cmdDetailsMap entry
   356  	cmd := root
   357  	for _, cmp := range path {
   358  		var found bool
   359  		cmd, found = cmd.Subcommands[cmp]
   360  		if !found {
   361  			return nil, fmt.Errorf("subcommand %s should be in root", cmp)
   362  		}
   363  
   364  		if cmdDetails, found := cmdDetailsMap[cmd]; found {
   365  			details = cmdDetails
   366  		}
   367  	}
   368  	return &details, nil
   369  }
   370  
   371  // commandShouldRunOnDaemon determines, from commmand details, whether a
   372  // command ought to be executed on an IPFS daemon.
   373  //
   374  // It returns a client if the command should be executed on a daemon and nil if
   375  // it should be executed on a client. It returns an error if the command must
   376  // NOT be executed on either.
   377  func commandShouldRunOnDaemon(details cmdDetails, req cmds.Request, root *cmds.Command) (cmdsHttp.Client, error) {
   378  	path := req.Path()
   379  	// root command.
   380  	if len(path) < 1 {
   381  		return nil, nil
   382  	}
   383  
   384  	if details.cannotRunOnClient && details.cannotRunOnDaemon {
   385  		return nil, fmt.Errorf("command disabled: %s", path[0])
   386  	}
   387  
   388  	if details.doesNotUseRepo && details.canRunOnClient() {
   389  		return nil, nil
   390  	}
   391  
   392  	// at this point need to know whether api is running. we defer
   393  	// to this point so that we dont check unnecessarily
   394  
   395  	// did user specify an api to use for this command?
   396  	apiAddrStr, _, err := req.Option(coreCmds.ApiOption).String()
   397  	if err != nil {
   398  		return nil, err
   399  	}
   400  
   401  	client, err := getApiClient(req.InvocContext().ConfigRoot, apiAddrStr)
   402  	if err == repo.ErrApiNotRunning {
   403  		if apiAddrStr != "" && req.Command() != daemonCmd {
   404  			// if user SPECIFIED an api, and this cmd is not daemon
   405  			// we MUST use it. so error out.
   406  			return nil, err
   407  		}
   408  
   409  		// ok for api not to be running
   410  	} else if err != nil { // some other api error
   411  		return nil, err
   412  	}
   413  
   414  	if client != nil { // daemon is running
   415  		if details.cannotRunOnDaemon {
   416  			e := "cannot use API with this command."
   417  
   418  			// check if daemon locked. legacy error text, for now.
   419  			daemonLocked, _ := fsrepo.LockedByOtherProcess(req.InvocContext().ConfigRoot)
   420  			if daemonLocked {
   421  				e = "ipfs daemon is running. please stop it to run this command"
   422  			}
   423  
   424  			return nil, cmds.ClientError(e)
   425  		}
   426  
   427  		return client, nil
   428  	}
   429  
   430  	if details.cannotRunOnClient {
   431  		return nil, cmds.ClientError("must run on the ipfs daemon")
   432  	}
   433  
   434  	return nil, nil
   435  }
   436  
   437  func isClientError(err error) bool {
   438  
   439  	// Somewhat suprisingly, the pointer cast fails to recognize commands.Error
   440  	// passed as values, so we check both.
   441  
   442  	// cast to cmds.Error
   443  	switch e := err.(type) {
   444  	case *cmds.Error:
   445  		return e.Code == cmds.ErrClient
   446  	case cmds.Error:
   447  		return e.Code == cmds.ErrClient
   448  	}
   449  	return false
   450  }
   451  
   452  func getRepoPath(req cmds.Request) (string, error) {
   453  	repoOpt, found, err := req.Option("config").String()
   454  	if err != nil {
   455  		return "", err
   456  	}
   457  	if found && repoOpt != "" {
   458  		return repoOpt, nil
   459  	}
   460  
   461  	repoPath, err := fsrepo.BestKnownPath()
   462  	if err != nil {
   463  		return "", err
   464  	}
   465  	return repoPath, nil
   466  }
   467  
   468  func loadConfig(path string) (*config.Config, error) {
   469  	return fsrepo.ConfigAt(path)
   470  }
   471  
   472  // startProfiling begins CPU profiling and returns a `stop` function to be
   473  // executed as late as possible. The stop function captures the memprofile.
   474  func startProfiling() (func(), error) {
   475  
   476  	// start CPU profiling as early as possible
   477  	ofi, err := os.Create(cpuProfile)
   478  	if err != nil {
   479  		return nil, err
   480  	}
   481  	pprof.StartCPUProfile(ofi)
   482  	go func() {
   483  		for _ = range time.NewTicker(time.Second * 30).C {
   484  			err := writeHeapProfileToFile()
   485  			if err != nil {
   486  				log.Error(err)
   487  			}
   488  		}
   489  	}()
   490  
   491  	stopProfiling := func() {
   492  		pprof.StopCPUProfile()
   493  		defer ofi.Close() // captured by the closure
   494  	}
   495  	return stopProfiling, nil
   496  }
   497  
   498  func writeHeapProfileToFile() error {
   499  	mprof, err := os.Create(heapProfile)
   500  	if err != nil {
   501  		return err
   502  	}
   503  	defer mprof.Close() // _after_ writing the heap profile
   504  	return pprof.WriteHeapProfile(mprof)
   505  }
   506  
   507  // IntrHandler helps set up an interrupt handler that can
   508  // be cleanly shut down through the io.Closer interface.
   509  type IntrHandler struct {
   510  	sig chan os.Signal
   511  	wg  sync.WaitGroup
   512  }
   513  
   514  func NewIntrHandler() *IntrHandler {
   515  	ih := &IntrHandler{}
   516  	ih.sig = make(chan os.Signal, 1)
   517  	return ih
   518  }
   519  
   520  func (ih *IntrHandler) Close() error {
   521  	close(ih.sig)
   522  	ih.wg.Wait()
   523  	return nil
   524  }
   525  
   526  // Handle starts handling the given signals, and will call the handler
   527  // callback function each time a signal is catched. The function is passed
   528  // the number of times the handler has been triggered in total, as
   529  // well as the handler itself, so that the handling logic can use the
   530  // handler's wait group to ensure clean shutdown when Close() is called.
   531  func (ih *IntrHandler) Handle(handler func(count int, ih *IntrHandler), sigs ...os.Signal) {
   532  	signal.Notify(ih.sig, sigs...)
   533  	ih.wg.Add(1)
   534  	go func() {
   535  		defer ih.wg.Done()
   536  		count := 0
   537  		for _ = range ih.sig {
   538  			count++
   539  			handler(count, ih)
   540  		}
   541  		signal.Stop(ih.sig)
   542  	}()
   543  }
   544  
   545  func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
   546  
   547  	intrh := NewIntrHandler()
   548  	ctx, cancelFunc := context.WithCancel(ctx)
   549  
   550  	handlerFunc := func(count int, ih *IntrHandler) {
   551  		switch count {
   552  		case 1:
   553  			fmt.Println() // Prevent un-terminated ^C character in terminal
   554  
   555  			ih.wg.Add(1)
   556  			go func() {
   557  				defer ih.wg.Done()
   558  				cancelFunc()
   559  			}()
   560  
   561  		default:
   562  			fmt.Println("Received another interrupt before graceful shutdown, terminating...")
   563  			os.Exit(-1)
   564  		}
   565  	}
   566  
   567  	intrh.Handle(handlerFunc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
   568  
   569  	return intrh, ctx
   570  }
   571  
   572  func profileIfEnabled() (func(), error) {
   573  	// FIXME this is a temporary hack so profiling of asynchronous operations
   574  	// works as intended.
   575  	if os.Getenv(EnvEnableProfiling) != "" {
   576  		stopProfilingFunc, err := startProfiling() // TODO maybe change this to its own option... profiling makes it slower.
   577  		if err != nil {
   578  			return nil, err
   579  		}
   580  		return stopProfilingFunc, nil
   581  	}
   582  	return func() {}, nil
   583  }
   584  
   585  // getApiClient checks the repo, and the given options, checking for
   586  // a running API service. if there is one, it returns a client.
   587  // otherwise, it returns errApiNotRunning, or another error.
   588  func getApiClient(repoPath, apiAddrStr string) (cmdsHttp.Client, error) {
   589  
   590  	if apiAddrStr == "" {
   591  		var err error
   592  		if apiAddrStr, err = fsrepo.APIAddr(repoPath); err != nil {
   593  			return nil, err
   594  		}
   595  	}
   596  
   597  	addr, err := ma.NewMultiaddr(apiAddrStr)
   598  	if err != nil {
   599  		return nil, err
   600  	}
   601  
   602  	client, err := apiClientForAddr(addr)
   603  	if err != nil {
   604  		return nil, err
   605  	}
   606  
   607  	// make sure the api is actually running.
   608  	// this is slow, as it might mean an RTT to a remote server.
   609  	// TODO: optimize some way
   610  	if err := apiVersionMatches(client); err != nil {
   611  		return nil, err
   612  	}
   613  
   614  	return client, nil
   615  }
   616  
   617  // apiVersionMatches checks whether the api server is running the
   618  // same version of go-ipfs. for now, only the exact same version of
   619  // client + server work. In the future, we should use semver for
   620  // proper API versioning! \o/
   621  func apiVersionMatches(client cmdsHttp.Client) (err error) {
   622  	ver, err := doVersionRequest(client)
   623  	if err != nil {
   624  		return err
   625  	}
   626  
   627  	currv := config.CurrentVersionNumber
   628  	if ver.Version != currv {
   629  		return fmt.Errorf("%s (%s != %s)", errApiVersionMismatch, ver.Version, currv)
   630  	}
   631  	return nil
   632  }
   633  
   634  func doVersionRequest(client cmdsHttp.Client) (*coreCmds.VersionOutput, error) {
   635  	cmd := coreCmds.VersionCmd
   636  	optDefs, err := cmd.GetOptions([]string{})
   637  	if err != nil {
   638  		return nil, err
   639  	}
   640  
   641  	req, err := cmds.NewRequest([]string{"version"}, nil, nil, nil, cmd, optDefs)
   642  	if err != nil {
   643  		return nil, err
   644  	}
   645  
   646  	res, err := client.Send(req)
   647  	if err != nil {
   648  		if isConnRefused(err) {
   649  			err = repo.ErrApiNotRunning
   650  		}
   651  		return nil, err
   652  	}
   653  
   654  	ver, ok := res.Output().(*coreCmds.VersionOutput)
   655  	if !ok {
   656  		return nil, errUnexpectedApiOutput
   657  	}
   658  	return ver, nil
   659  }
   660  
   661  func apiClientForAddr(addr ma.Multiaddr) (cmdsHttp.Client, error) {
   662  	_, host, err := manet.DialArgs(addr)
   663  	if err != nil {
   664  		return nil, err
   665  	}
   666  
   667  	return cmdsHttp.NewClient(host), nil
   668  }
   669  
   670  func isConnRefused(err error) bool {
   671  	return strings.Contains(err.Error(), "connection refused")
   672  }