main.go 16.5 KB
Newer Older
Jeromy's avatar
Jeromy committed
1
// cmd/ipfs implements the primary CLI binary for ipfs
2 3 4
package main

import (
Brian Tiger Chow's avatar
Brian Tiger Chow committed
5
	"errors"
6 7
	"fmt"
	"io"
8
	"math/rand"
9
	"os"
Matt Bell's avatar
Matt Bell committed
10
	"os/signal"
11
	"runtime"
12
	"runtime/pprof"
13
	"strings"
14
	"sync"
15
	"syscall"
16
	"time"
17

18 19 20 21 22 23 24 25
	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
	manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"

	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
	cmds "github.com/ipfs/go-ipfs/commands"
	cmdsCli "github.com/ipfs/go-ipfs/commands/cli"
	cmdsHttp "github.com/ipfs/go-ipfs/commands/http"
	core "github.com/ipfs/go-ipfs/core"
26 27
	coreCmds "github.com/ipfs/go-ipfs/core/commands"
	repo "github.com/ipfs/go-ipfs/repo"
28 29 30
	config "github.com/ipfs/go-ipfs/repo/config"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
	u "github.com/ipfs/go-ipfs/util"
Jeromy's avatar
Jeromy committed
31
	logging "github.com/ipfs/go-ipfs/vendor/go-log-v1.0.0"
32 33
)

34
// log is the command logger
Jeromy's avatar
Jeromy committed
35
var log = logging.Logger("cmd/ipfs")
36

37 38 39 40
var (
	errUnexpectedApiOutput = errors.New("api returned unexpected output")
	errApiVersionMismatch  = errors.New("api version mismatch")
)
41

42
const (
43 44 45 46
	EnvEnableProfiling = "IPFS_PROF"
	cpuProfile         = "ipfs.cpuprof"
	heapProfile        = "ipfs.memprof"
	errorFormat        = "ERROR: %v\n\n"
47
)
48

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
49 50 51 52
type cmdInvocation struct {
	path []string
	cmd  *cmds.Command
	req  cmds.Request
53
	node *core.IpfsNode
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
54 55 56 57 58 59 60 61
}

// main roadmap:
// - parse the commandline to get a cmdInvocation
// - if user requests, help, print it and exit.
// - run the command invocation
// - output the response
// - if anything fails, print error, maybe with help
62
func main() {
63 64
	rand.Seed(time.Now().UnixNano())
	runtime.GOMAXPROCS(3) // FIXME rm arbitrary choice for n
Jeromy's avatar
Jeromy committed
65
	ctx := logging.ContextWithLoggable(context.Background(), logging.Uuid("session"))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
66
	var err error
67 68
	var invoc cmdInvocation
	defer invoc.close()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
69 70 71 72 73 74 75

	// we'll call this local helper to output errors.
	// this is so we control how to print errors in one place.
	printErr := func(err error) {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
	}

76 77 78 79 80 81 82
	stopFunc, err := profileIfEnabled()
	if err != nil {
		printErr(err)
		os.Exit(1)
	}
	defer stopFunc() // to be executed as late as possible

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
83 84
	// this is a local helper to print out help text.
	// there's some considerations that this makes easier.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
85
	printHelp := func(long bool, w io.Writer) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
86 87 88 89 90
		helpFunc := cmdsCli.ShortHelp
		if long {
			helpFunc = cmdsCli.LongHelp
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
91
		helpFunc("ipfs", Root, invoc.path, w)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
92 93
	}

94 95 96 97 98 99
	// this is a message to tell the user how to get the help text
	printMetaHelp := func(w io.Writer) {
		cmdPath := strings.Join(invoc.path, " ")
		fmt.Fprintf(w, "Use 'ipfs %s --help' for information about this command\n", cmdPath)
	}

Etienne Laurin's avatar
Etienne Laurin committed
100 101 102 103 104 105
	// Handle `ipfs help'
	if len(os.Args) == 2 && os.Args[1] == "help" {
		printHelp(false, os.Stdout)
		os.Exit(0)
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
106
	// parse the commandline into a command invocation
107
	parseErr := invoc.Parse(ctx, os.Args[1:])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108 109 110

	// BEFORE handling the parse error, if we have enough information
	// AND the user requested help, print it out and exit
111
	if invoc.req != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
112 113 114 115 116 117
		longH, shortH, err := invoc.requestedHelp()
		if err != nil {
			printErr(err)
			os.Exit(1)
		}
		if longH || shortH {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
118
			printHelp(longH, os.Stdout)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
119 120 121 122
			os.Exit(0)
		}
	}

123
	// ok now handle parse error (which means cli input was wrong,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
124
	// e.g. incorrect number of args, or nonexistent subcommand)
125 126
	if parseErr != nil {
		printErr(parseErr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
127 128 129 130 131

		// this was a user error, print help.
		if invoc.cmd != nil {
			// we need a newline space.
			fmt.Fprintf(os.Stderr, "\n")
132
			printMetaHelp(os.Stderr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133
		}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
134 135
		os.Exit(1)
	}
Matt Bell's avatar
Matt Bell committed
136

Etienne Laurin's avatar
Etienne Laurin committed
137 138 139 140 141 142 143 144
	// here we handle the cases where
	// - commands with no Run func are invoked directly.
	// - the main command is invoked.
	if invoc.cmd == nil || invoc.cmd.Run == nil {
		printHelp(false, os.Stdout)
		os.Exit(0)
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
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 {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
151 152 153 154
		printErr(err)

		// if this error was a client error, print short help too.
		if isClientError(err) {
155
			printMetaHelp(os.Stderr)
156
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
157
		os.Exit(1)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
158 159
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
160
	// everything went better than expected :)
Jeromy's avatar
Jeromy committed
161 162 163 164 165 166
	_, err = io.Copy(os.Stdout, output)
	if err != nil {
		printErr(err)

		os.Exit(1)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
167 168
}

169
func (i *cmdInvocation) Run(ctx context.Context) (output io.Reader, err error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170 171 172

	// check if user wants to debug. option OR env var.
	debug, _, err := i.req.Option("debug").Bool()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
173
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
174
		return nil, err
Brian Tiger Chow's avatar
Brian Tiger Chow committed
175
	}
176
	if debug || u.GetenvBool("DEBUG") || os.Getenv("IPFS_LOGGING") == "debug" {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
177
		u.Debug = true
Jeromy's avatar
Jeromy committed
178
		logging.SetDebugLogging()
179
	}
180

181
	res, err := callCommand(ctx, i.req, Root, i.cmd)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
182
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
183
		return nil, err
184
	}
185

186 187 188 189
	if err := res.Error(); err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
190
	return res.Reader()
191
}
192

Brian Tiger Chow's avatar
Brian Tiger Chow committed
193
func (i *cmdInvocation) constructNodeFunc(ctx context.Context) func() (*core.IpfsNode, error) {
194 195 196 197
	return func() (*core.IpfsNode, error) {
		if i.req == nil {
			return nil, errors.New("constructing node without a request")
		}
198

Jeromy's avatar
Jeromy committed
199
		cmdctx := i.req.InvocContext()
200 201 202
		if cmdctx == nil {
			return nil, errors.New("constructing node without a request context")
		}
203

Jeromy's avatar
Jeromy committed
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 210
		// ok everything is good. set it on the invocation (for ownership)
		// and return it.
211 212 213 214
		n, err := core.NewNode(ctx, &core.BuildCfg{
			Online: cmdctx.Online,
			Repo:   r,
		})
215 216 217 218 219
		if err != nil {
			return nil, err
		}
		i.node = n
		return i.node, nil
220
	}
221 222 223 224 225 226 227 228 229 230 231 232
}

func (i *cmdInvocation) close() {
	// let's not forget teardown. If a node was initialized, we must close it.
	// Note that this means the underlying req.Context().Node variable is exposed.
	// this is gross, and should be changed when we extract out the exec Context.
	if i.node != nil {
		log.Info("Shutting down node...")
		i.node.Close()
	}
}

233
func (i *cmdInvocation) Parse(ctx context.Context, args []string) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
234
	var err error
235

236
	i.req, i.cmd, i.path, err = cmdsCli.Parse(args, os.Stdin, Root)
237
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
238
		return err
239 240
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
241
	repoPath, err := getRepoPath(i.req)
242
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
243
		return err
244
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
245
	log.Debugf("config path is %s", repoPath)
246

247
	// this sets up the function that will initialize the config lazily.
Jeromy's avatar
Jeromy committed
248
	cmdctx := i.req.InvocContext()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
249
	cmdctx.ConfigRoot = repoPath
250
	cmdctx.LoadConfig = loadConfig
251 252
	// this sets up the function that will initialize the node
	// this is so that we can construct the node lazily.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
253
	cmdctx.ConstructNode = i.constructNodeFunc(ctx)
254

255 256
	// if no encoding was specified by user, default to plaintext encoding
	// (if command doesn't support plaintext, use JSON instead)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
257
	if !i.req.Option("encoding").Found() {
258
		if i.req.Command().Marshalers != nil && i.req.Command().Marshalers[cmds.Text] != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
259
			i.req.SetOption("encoding", cmds.Text)
260
		} else {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
261
			i.req.SetOption("encoding", cmds.JSON)
262 263 264
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
265
	return nil
266 267
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
268 269
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
	longHelp, _, err := i.req.Option("help").Bool()
270
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
271
		return false, false, err
272
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
273
	shortHelp, _, err := i.req.Option("h").Bool()
274
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
275
		return false, false, err
276
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
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 284 285 286 287
	log.Debug("Calling pre-command hooks...")

	return nil
}

288
func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd *cmds.Command) (cmds.Response, error) {
Jeromy's avatar
Jeromy committed
289
	log.Info(config.EnvDir, " ", req.InvocContext().ConfigRoot)
290
	var res cmds.Response
291

Jeromy's avatar
Jeromy committed
292 293 294 295
	err := req.SetRootContext(ctx)
	if err != nil {
		return nil, err
	}
296

297 298 299 300 301
	details, err := commandDetails(req.Path(), root)
	if err != nil {
		return nil, err
	}

302
	client, err := commandShouldRunOnDaemon(*details, req, root)
303 304 305
	if err != nil {
		return nil, err
	}
306

307
	err = callPreCommandHooks(ctx, *details, req, root)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
308 309 310
	if err != nil {
		return nil, err
	}
311

312 313 314 315 316 317 318
	if cmd.PreRun != nil {
		err = cmd.PreRun(req)
		if err != nil {
			return nil, err
		}
	}

319 320
	if client != nil {
		log.Debug("Executing command via API")
321 322
		res, err = client.Send(req)
		if err != nil {
323 324 325
			if isConnRefused(err) {
				err = repo.ErrApiNotRunning
			}
326 327
			return nil, err
		}
328

329
	} else {
330
		log.Debug("Executing command locally")
331

Jeromy's avatar
Jeromy committed
332
		err := req.SetRootContext(ctx)
Jeromy's avatar
Jeromy committed
333 334 335 336
		if err != nil {
			return nil, err
		}

337 338
		// Okay!!!!! NOW we can call the command.
		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 350 351 352 353
// commandDetails returns a command's details for the command given by |path|
// within the |root| command tree.
//
// Returns an error if the command is not found in the Command tree.
func commandDetails(path []string, root *cmds.Command) (*cmdDetails, error) {
354 355 356 357 358 359 360
	var details cmdDetails
	// find the last command in path that has a cmdDetailsMap entry
	cmd := root
	for _, cmp := range path {
		var found bool
		cmd, found = cmd.Subcommands[cmp]
		if !found {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
361
			return nil, fmt.Errorf("subcommand %s should be in root", cmp)
362
		}
363

364 365 366
		if cmdDetails, found := cmdDetailsMap[cmd]; found {
			details = cmdDetails
		}
367
	}
368 369 370 371 372 373
	return &details, nil
}

// commandShouldRunOnDaemon determines, from commmand details, whether a
// command ought to be executed on an IPFS daemon.
//
374
// It returns a client if the command should be executed on a daemon and nil if
375 376
// it should be executed on a client. It returns an error if the command must
// NOT be executed on either.
377
func commandShouldRunOnDaemon(details cmdDetails, req cmds.Request, root *cmds.Command) (cmdsHttp.Client, error) {
378 379 380
	path := req.Path()
	// root command.
	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 393 394 395 396
	// at this point need to know whether api is running. we defer
	// to this point so that we dont check unnecessarily

	// did user specify an api to use for this command?
	apiAddrStr, _, err := req.Option(coreCmds.ApiOption).String()
397
	if err != nil {
398
		return nil, err
399
	}
400

401 402 403 404 405 406 407
	client, err := getApiClient(req.InvocContext().ConfigRoot, apiAddrStr)
	if err == repo.ErrApiNotRunning {
		if apiAddrStr != "" && req.Command() != daemonCmd {
			// if user SPECIFIED an api, and this cmd is not daemon
			// we MUST use it. so error out.
			return nil, err
		}
408

409 410 411 412
		// ok for api not to be running
	} else if err != nil { // some other api error
		return nil, err
	}
413

414
	if client != nil { // daemon is running
415
		if details.cannotRunOnDaemon {
416 417 418 419 420 421 422 423 424
			e := "cannot use API with this command."

			// check if daemon locked. legacy error text, for now.
			daemonLocked, _ := fsrepo.LockedByOtherProcess(req.InvocContext().ConfigRoot)
			if daemonLocked {
				e = "ipfs daemon is running. please stop it to run this command"
			}

			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
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
437
func isClientError(err error) bool {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
438 439 440 441

	// Somewhat suprisingly, the pointer cast fails to recognize commands.Error
	// passed as values, so we check both.

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
442
	// cast to cmds.Error
Brian Tiger Chow's avatar
Brian Tiger Chow committed
443 444 445 446 447
	switch e := err.(type) {
	case *cmds.Error:
		return e.Code == cmds.ErrClient
	case cmds.Error:
		return e.Code == cmds.ErrClient
448
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
449
	return false
450 451
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
452 453
func getRepoPath(req cmds.Request) (string, error) {
	repoOpt, found, err := req.Option("config").String()
454 455 456
	if err != nil {
		return "", err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
457 458
	if found && repoOpt != "" {
		return repoOpt, nil
459 460
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
461
	repoPath, err := fsrepo.BestKnownPath()
462 463 464
	if err != nil {
		return "", err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
465
	return repoPath, nil
466 467
}

468
func loadConfig(path string) (*config.Config, error) {
Brian Tiger Chow's avatar
huh  
Brian Tiger Chow committed
469
	return fsrepo.ConfigAt(path)
470
}
471

472 473 474 475 476 477 478 479 480 481
// startProfiling begins CPU profiling and returns a `stop` function to be
// executed as late as possible. The stop function captures the memprofile.
func startProfiling() (func(), error) {

	// start CPU profiling as early as possible
	ofi, err := os.Create(cpuProfile)
	if err != nil {
		return nil, err
	}
	pprof.StartCPUProfile(ofi)
482 483 484 485
	go func() {
		for _ = range time.NewTicker(time.Second * 30).C {
			err := writeHeapProfileToFile()
			if err != nil {
rht's avatar
rht committed
486
				log.Error(err)
487 488 489
			}
		}
	}()
490 491 492 493 494 495 496 497

	stopProfiling := func() {
		pprof.StopCPUProfile()
		defer ofi.Close() // captured by the closure
	}
	return stopProfiling, nil
}

498 499 500
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
501
		return err
502
	}
503
	defer mprof.Close() // _after_ writing the heap profile
504 505
	return pprof.WriteHeapProfile(mprof)
}
506

507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
// IntrHandler helps set up an interrupt handler that can
// be cleanly shut down through the io.Closer interface.
type IntrHandler struct {
	sig chan os.Signal
	wg  sync.WaitGroup
}

func NewIntrHandler() *IntrHandler {
	ih := &IntrHandler{}
	ih.sig = make(chan os.Signal, 1)
	return ih
}

func (ih *IntrHandler) Close() error {
	close(ih.sig)
	ih.wg.Wait()
	return nil
}
525

526 527 528 529 530 531 532 533
// Handle starts handling the given signals, and will call the handler
// callback function each time a signal is catched. The function is passed
// the number of times the handler has been triggered in total, as
// well as the handler itself, so that the handling logic can use the
// handler's wait group to ensure clean shutdown when Close() is called.
func (ih *IntrHandler) Handle(handler func(count int, ih *IntrHandler), sigs ...os.Signal) {
	signal.Notify(ih.sig, sigs...)
	ih.wg.Add(1)
Matt Bell's avatar
Matt Bell committed
534
	go func() {
535 536 537 538 539 540 541 542 543 544
		defer ih.wg.Done()
		count := 0
		for _ = range ih.sig {
			count++
			handler(count, ih)
		}
		signal.Stop(ih.sig)
	}()
}

545
func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
546

547
	intrh := NewIntrHandler()
548 549
	ctx, cancelFunc := context.WithCancel(ctx)

550 551 552
	handlerFunc := func(count int, ih *IntrHandler) {
		switch count {
		case 1:
553
			fmt.Println() // Prevent un-terminated ^C character in terminal
554 555 556 557

			ih.wg.Add(1)
			go func() {
				defer ih.wg.Done()
558
				cancelFunc()
559
			}()
560

561 562 563
		default:
			fmt.Println("Received another interrupt before graceful shutdown, terminating...")
			os.Exit(-1)
Matt Bell's avatar
Matt Bell committed
564
		}
565 566 567
	}

	intrh.Handle(handlerFunc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
568

569
	return intrh, ctx
570
}
571 572 573 574

func profileIfEnabled() (func(), error) {
	// FIXME this is a temporary hack so profiling of asynchronous operations
	// works as intended.
575
	if os.Getenv(EnvEnableProfiling) != "" {
576 577 578 579 580 581 582 583
		stopProfilingFunc, err := startProfiling() // TODO maybe change this to its own option... profiling makes it slower.
		if err != nil {
			return nil, err
		}
		return stopProfilingFunc, nil
	}
	return func() {}, nil
}
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670

// getApiClient checks the repo, and the given options, checking for
// a running API service. if there is one, it returns a client.
// otherwise, it returns errApiNotRunning, or another error.
func getApiClient(repoPath, apiAddrStr string) (cmdsHttp.Client, error) {

	if apiAddrStr == "" {
		var err error
		if apiAddrStr, err = fsrepo.APIAddr(repoPath); err != nil {
			return nil, err
		}
	}

	addr, err := ma.NewMultiaddr(apiAddrStr)
	if err != nil {
		return nil, err
	}

	client, err := apiClientForAddr(addr)
	if err != nil {
		return nil, err
	}

	// make sure the api is actually running.
	// this is slow, as it might mean an RTT to a remote server.
	// TODO: optimize some way
	if err := apiVersionMatches(client); err != nil {
		return nil, err
	}

	return client, nil
}

// apiVersionMatches checks whether the api server is running the
// same version of go-ipfs. for now, only the exact same version of
// client + server work. In the future, we should use semver for
// proper API versioning! \o/
func apiVersionMatches(client cmdsHttp.Client) (err error) {
	ver, err := doVersionRequest(client)
	if err != nil {
		return err
	}

	currv := config.CurrentVersionNumber
	if ver.Version != currv {
		return fmt.Errorf("%s (%s != %s)", errApiVersionMismatch, ver.Version, currv)
	}
	return nil
}

func doVersionRequest(client cmdsHttp.Client) (*coreCmds.VersionOutput, error) {
	cmd := coreCmds.VersionCmd
	optDefs, err := cmd.GetOptions([]string{})
	if err != nil {
		return nil, err
	}

	req, err := cmds.NewRequest([]string{"version"}, nil, nil, nil, cmd, optDefs)
	if err != nil {
		return nil, err
	}

	res, err := client.Send(req)
	if err != nil {
		if isConnRefused(err) {
			err = repo.ErrApiNotRunning
		}
		return nil, err
	}

	ver, ok := res.Output().(*coreCmds.VersionOutput)
	if !ok {
		return nil, errUnexpectedApiOutput
	}
	return ver, nil
}

func apiClientForAddr(addr ma.Multiaddr) (cmdsHttp.Client, error) {
	_, host, err := manet.DialArgs(addr)
	if err != nil {
		return nil, err
	}

	return cmdsHttp.NewClient(host), nil
}

func isConnRefused(err error) bool {
671 672
	return  strings.Contains(err.Error(), "connection refused") ||
	        strings.Contains(err.Error(), "target machine actively refused it")
673
}