main.go 15.6 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/QmPpRcbNUXauP3zWZ1NJMLWpe4QnmEHrd2ba2D3yqWznw7/go-multiaddr-net"
21
	ma "gx/ipfs/QmYzDkkgAEmrcNzFCiYo6L1dTX4EAG1gZkbtdbd9trL4vd/go-multiaddr"
22

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

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
	loggables "github.com/ipfs/go-ipfs/thirdparty/loggables"
36 37
)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	return nil
}

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

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

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

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

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

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

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

337
	} else {
338
		log.Debug("executing command locally")
339

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

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

	}
349 350

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

354
	return res, nil
355 356
}

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

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

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

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

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

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

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

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

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

432
		return client, nil
433 434 435
	}

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

439
	return nil, nil
440 441
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

574
	return intrh, ctx
575
}
576 577 578 579

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

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

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

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