main.go 15.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"
Jeromy's avatar
Jeromy committed
9 10
	"net"
	"net/url"
11
	"os"
Matt Bell's avatar
Matt Bell committed
12
	"os/signal"
13
	"runtime"
14
	"runtime/pprof"
15
	"strings"
16
	"sync"
17
	"syscall"
18
	"time"
19

Jeromy's avatar
Jeromy committed
20
	manet "gx/ipfs/QmTrxSBY8Wqd5aBB4MeizeSzS5xFbK8dQBrYaMsiGnCBhb/go-multiaddr-net"
Jeromy's avatar
Jeromy committed
21
	ma "gx/ipfs/QmcobAGsCjYt5DXoq9et9L8yR8er7o7Cu3DTvpaq12jYSz/go-multiaddr"
22

23 24 25 26
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
	logging "gx/ipfs/QmaDNZ4QMdBdku1YZWBysufYyoQt1negQGNav6PLYarbY8/go-log"

27 28 29 30
	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"
31 32
	coreCmds "github.com/ipfs/go-ipfs/core/commands"
	repo "github.com/ipfs/go-ipfs/repo"
33 34
	config "github.com/ipfs/go-ipfs/repo/config"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
35 36
)

37
// log is the command logger
Jeromy's avatar
Jeromy committed
38
var log = logging.Logger("cmd/ipfs")
39

40 41 42
var (
	errUnexpectedApiOutput = errors.New("api returned unexpected output")
	errApiVersionMismatch  = errors.New("api version mismatch")
rht's avatar
rht committed
43
	errRequestCanceled     = errors.New("request canceled")
44
)
45

46
const (
47 48 49 50
	EnvEnableProfiling = "IPFS_PROF"
	cpuProfile         = "ipfs.cpuprof"
	heapProfile        = "ipfs.memprof"
	errorFormat        = "ERROR: %v\n\n"
51
)
52

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

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

	// 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())
	}

80 81 82 83 84 85 86
	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
87 88
	// 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
89
	printHelp := func(long bool, w io.Writer) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
90 91 92 93 94
		helpFunc := cmdsCli.ShortHelp
		if long {
			helpFunc = cmdsCli.LongHelp
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
95
		helpFunc("ipfs", Root, invoc.path, w)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
96 97
	}

98 99 100 101 102 103
	// 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
104 105 106 107 108 109
	// 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
110
	// parse the commandline into a command invocation
111
	parseErr := invoc.Parse(ctx, os.Args[1:])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
112 113 114

	// BEFORE handling the parse error, if we have enough information
	// AND the user requested help, print it out and exit
115
	if invoc.req != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
116 117 118 119 120 121
		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
122
			printHelp(longH, os.Stdout)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
123 124 125 126
			os.Exit(0)
		}
	}

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

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

Etienne Laurin's avatar
Etienne Laurin committed
141 142 143 144 145 146 147 148
	// 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
149
	// ok, finally, run the command invocation.
150
	intrh, ctx := invoc.SetupInterruptHandler(ctx)
151
	defer intrh.Close()
152

153
	output, err := invoc.Run(ctx)
154
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
155 156 157 158
		printErr(err)

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

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

		os.Exit(1)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
171 172
}

173
func (i *cmdInvocation) Run(ctx context.Context) (output io.Reader, err error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
174 175 176

	// 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
177
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
178
		return nil, err
Brian Tiger Chow's avatar
Brian Tiger Chow committed
179
	}
180
	if debug || os.Getenv("IPFS_LOGGING") == "debug" {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
181
		u.Debug = true
Jeromy's avatar
Jeromy committed
182
		logging.SetDebugLogging()
183
	}
184 185 186
	if u.GetenvBool("DEBUG") {
		u.Debug = true
	}
187

188
	res, err := callCommand(ctx, i.req, Root, i.cmd)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
189
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
190
		return nil, err
191
	}
192

193 194 195 196
	if err := res.Error(); err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
197
	return res.Reader()
198
}
199

Brian Tiger Chow's avatar
Brian Tiger Chow committed
200
func (i *cmdInvocation) constructNodeFunc(ctx context.Context) func() (*core.IpfsNode, error) {
201 202 203 204
	return func() (*core.IpfsNode, error) {
		if i.req == nil {
			return nil, errors.New("constructing node without a request")
		}
205

Jeromy's avatar
Jeromy committed
206
		cmdctx := i.req.InvocContext()
207 208 209
		if cmdctx == nil {
			return nil, errors.New("constructing node without a request context")
		}
210

Jeromy's avatar
Jeromy committed
211
		r, err := fsrepo.Open(i.req.InvocContext().ConfigRoot)
212
		if err != nil { // repo is owned by the node
213
			return nil, err
214
		}
215

216 217
		// ok everything is good. set it on the invocation (for ownership)
		// and return it.
218 219 220 221
		n, err := core.NewNode(ctx, &core.BuildCfg{
			Online: cmdctx.Online,
			Repo:   r,
		})
222 223 224 225 226
		if err != nil {
			return nil, err
		}
		i.node = n
		return i.node, nil
227
	}
228 229 230 231 232 233 234 235 236 237 238 239
}

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()
	}
}

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

243
	i.req, i.cmd, i.path, err = cmdsCli.Parse(args, os.Stdin, Root)
244
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
245
		return err
246 247
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
248
	repoPath, err := getRepoPath(i.req)
249
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
250
		return err
251
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
252
	log.Debugf("config path is %s", repoPath)
253

254
	// this sets up the function that will initialize the config lazily.
Jeromy's avatar
Jeromy committed
255
	cmdctx := i.req.InvocContext()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
256
	cmdctx.ConfigRoot = repoPath
257
	cmdctx.LoadConfig = loadConfig
258 259
	// 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
260
	cmdctx.ConstructNode = i.constructNodeFunc(ctx)
261

262 263
	// 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
264
	if !i.req.Option("encoding").Found() {
265
		if i.req.Command().Marshalers != nil && i.req.Command().Marshalers[cmds.Text] != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
266
			i.req.SetOption("encoding", cmds.Text)
267
		} else {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
268
			i.req.SetOption("encoding", cmds.JSON)
269 270 271
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
272
	return nil
273 274
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
275 276
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
	longHelp, _, err := i.req.Option("help").Bool()
277
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
278
		return false, false, err
279
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
280
	shortHelp, _, err := i.req.Option("h").Bool()
281
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
282
		return false, false, err
283
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
284
	return longHelp, shortHelp, nil
285
}
286

287
func callPreCommandHooks(ctx context.Context, details cmdDetails, req cmds.Request, root *cmds.Command) error {
288

289
	log.Event(ctx, "callPreCommandHooks", &details)
290 291 292 293 294
	log.Debug("Calling pre-command hooks...")

	return nil
}

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

Jeromy's avatar
Jeromy committed
299 300 301 302
	err := req.SetRootContext(ctx)
	if err != nil {
		return nil, err
	}
303

304 305 306 307 308
	details, err := commandDetails(req.Path(), root)
	if err != nil {
		return nil, err
	}

309
	client, err := commandShouldRunOnDaemon(*details, req, root)
310 311 312
	if err != nil {
		return nil, err
	}
313

314
	err = callPreCommandHooks(ctx, *details, req, root)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
315 316 317
	if err != nil {
		return nil, err
	}
318

319 320 321 322 323 324 325
	if cmd.PreRun != nil {
		err = cmd.PreRun(req)
		if err != nil {
			return nil, err
		}
	}

Jeromy's avatar
Jeromy committed
326
	if client != nil && !cmd.External {
327
		log.Debug("Executing command via API")
328 329
		res, err = client.Send(req)
		if err != nil {
330 331 332
			if isConnRefused(err) {
				err = repo.ErrApiNotRunning
			}
rht's avatar
rht committed
333
			return nil, wrapContextCanceled(err)
334
		}
335

336
	} else {
337
		log.Debug("Executing command locally")
338

Jeromy's avatar
Jeromy committed
339
		err := req.SetRootContext(ctx)
Jeromy's avatar
Jeromy committed
340 341 342 343
		if err != nil {
			return nil, err
		}

344 345
		// Okay!!!!! NOW we can call the command.
		res = root.Call(req)
346 347

	}
348 349

	if cmd.PostRun != nil {
350
		cmd.PostRun(req, res)
351 352
	}

353
	return res, nil
354 355
}

356 357 358 359 360
// 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) {
361 362 363 364 365 366 367
	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
368
			return nil, fmt.Errorf("subcommand %s should be in root", cmp)
369
		}
370

371 372 373
		if cmdDetails, found := cmdDetailsMap[cmd]; found {
			details = cmdDetails
		}
374
	}
375 376 377 378 379 380
	return &details, nil
}

// commandShouldRunOnDaemon determines, from commmand details, whether a
// command ought to be executed on an IPFS daemon.
//
381
// It returns a client if the command should be executed on a daemon and nil if
382 383
// it should be executed on a client. It returns an error if the command must
// NOT be executed on either.
384
func commandShouldRunOnDaemon(details cmdDetails, req cmds.Request, root *cmds.Command) (cmdsHttp.Client, error) {
385 386 387
	path := req.Path()
	// root command.
	if len(path) < 1 {
388
		return nil, nil
389
	}
390 391

	if details.cannotRunOnClient && details.cannotRunOnDaemon {
392
		return nil, fmt.Errorf("command disabled: %s", path[0])
393 394
	}

395
	if details.doesNotUseRepo && details.canRunOnClient() {
396
		return nil, nil
397 398
	}

399 400 401 402 403
	// 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()
404
	if err != nil {
405
		return nil, err
406
	}
407

408 409 410 411 412 413 414
	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
		}
415

416 417 418 419
		// ok for api not to be running
	} else if err != nil { // some other api error
		return nil, err
	}
420

michael's avatar
michael committed
421
	if client != nil {
422
		if details.cannotRunOnDaemon {
423
			// check if daemon locked. legacy error text, for now.
michael's avatar
michael committed
424
			log.Debugf("Command cannot run on daemon. Checking if daemon is locked")
rht's avatar
rht committed
425 426
			if daemonLocked, _ := fsrepo.LockedByOtherProcess(req.InvocContext().ConfigRoot); daemonLocked {
				return nil, cmds.ClientError("ipfs daemon is running. please stop it to run this command")
427
			}
rht's avatar
rht committed
428
			return nil, nil
429 430
		}

431
		return client, nil
432 433 434
	}

	if details.cannotRunOnClient {
435
		return nil, cmds.ClientError("must run on the ipfs daemon")
436 437
	}

438
	return nil, nil
439 440
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
441
func isClientError(err error) bool {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
442 443 444 445

	// 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
446
	// cast to cmds.Error
Brian Tiger Chow's avatar
Brian Tiger Chow committed
447 448 449 450 451
	switch e := err.(type) {
	case *cmds.Error:
		return e.Code == cmds.ErrClient
	case cmds.Error:
		return e.Code == cmds.ErrClient
452
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
453
	return false
454 455
}

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

Brian Tiger Chow's avatar
Brian Tiger Chow committed
465
	repoPath, err := fsrepo.BestKnownPath()
466 467 468
	if err != nil {
		return "", err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
469
	return repoPath, nil
470 471
}

472
func loadConfig(path string) (*config.Config, error) {
Brian Tiger Chow's avatar
huh  
Brian Tiger Chow committed
473
	return fsrepo.ConfigAt(path)
474
}
475

476 477 478 479 480 481 482 483 484 485
// 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)
486 487 488 489
	go func() {
		for _ = range time.NewTicker(time.Second * 30).C {
			err := writeHeapProfileToFile()
			if err != nil {
rht's avatar
rht committed
490
				log.Error(err)
491 492 493
			}
		}
	}()
494 495 496 497 498 499 500 501

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

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

511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
// 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
}
529

530 531 532 533 534 535 536 537
// 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
538
	go func() {
539 540 541 542 543 544 545 546 547 548
		defer ih.wg.Done()
		count := 0
		for _ = range ih.sig {
			count++
			handler(count, ih)
		}
		signal.Stop(ih.sig)
	}()
}

549
func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
550

551
	intrh := NewIntrHandler()
552 553
	ctx, cancelFunc := context.WithCancel(ctx)

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

			ih.wg.Add(1)
			go func() {
				defer ih.wg.Done()
562
				cancelFunc()
563
			}()
564

565 566 567
		default:
			fmt.Println("Received another interrupt before graceful shutdown, terminating...")
			os.Exit(-1)
Matt Bell's avatar
Matt Bell committed
568
		}
569 570 571
	}

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

573
	return intrh, ctx
574
}
575 576 577 578

func profileIfEnabled() (func(), error) {
	// FIXME this is a temporary hack so profiling of asynchronous operations
	// works as intended.
579
	if os.Getenv(EnvEnableProfiling) != "" {
580 581 582 583 584 585 586 587
		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
}
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605

// 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
	}

rht's avatar
rht committed
606
	return apiClientForAddr(addr)
607 608 609 610 611 612 613 614 615 616 617 618
}

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 {
Jeromy's avatar
Jeromy committed
619 620 621 622 623 624 625 626 627 628 629
	// unwrap url errors from http calls
	if urlerr, ok := err.(*url.Error); ok {
		err = urlerr.Err
	}

	netoperr, ok := err.(*net.OpError)
	if !ok {
		return false
	}

	return netoperr.Op == "dial"
630
}
rht's avatar
rht committed
631 632 633 634 635 636 637

func wrapContextCanceled(err error) error {
	if strings.Contains(err.Error(), "request canceled") {
		err = errRequestCanceled
	}
	return err
}