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

20
	manet "gx/ipfs/QmYVqhVfbK4BKvbW88Lhm26b3ud14sTBvcm1H7uWUx1Fkp/go-multiaddr-net"
Jeromy's avatar
Jeromy committed
21
	ma "gx/ipfs/QmcobAGsCjYt5DXoq9et9L8yR8er7o7Cu3DTvpaq12jYSz/go-multiaddr"
22 23 24 25 26

	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"
27 28
	coreCmds "github.com/ipfs/go-ipfs/core/commands"
	repo "github.com/ipfs/go-ipfs/repo"
29 30
	config "github.com/ipfs/go-ipfs/repo/config"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
31
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
Jeromy's avatar
Jeromy committed
32 33
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
	logging "gx/ipfs/Qmazh5oNUVsDZTs2g59rq8aYQqwpss8tcUWQzor5sCCEuH/go-log"
34 35
)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	return nil
}

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

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

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

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

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

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

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

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

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

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

	}
347 348

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

352
	return res, nil
353 354
}

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

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

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

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

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

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

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

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

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

429
		return client, nil
430 431 432
	}

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

436
	return nil, nil
437 438
}

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

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

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

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

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

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

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

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

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

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

547
func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
548

549
	intrh := NewIntrHandler()
550 551
	ctx, cancelFunc := context.WithCancel(ctx)

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

			ih.wg.Add(1)
			go func() {
				defer ih.wg.Done()
560
				cancelFunc()
561
			}()
562

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

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

571
	return intrh, ctx
572
}
573 574 575 576

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

// 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
604
	return apiClientForAddr(addr)
605 606 607 608 609 610 611 612 613 614 615 616
}

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
617 618 619 620 621 622 623 624 625 626 627
	// 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"
628
}
rht's avatar
rht committed
629 630 631 632 633 634 635

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