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/pprof"
14
	"strings"
15
	"sync"
16
	"syscall"
17
	"time"
18

19 20
	manet "gx/ipfs/QmT6Cp31887FpAc25z25YHgpFJohZedrYLWPPspRtj1Brp/go-multiaddr-net"
	ma "gx/ipfs/QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH/go-multiaddr"
21

22
	context "context"
Jeromy's avatar
Jeromy committed
23
	logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
24
	u "gx/ipfs/Qmb912gdngC1UWwTkhuW8knyRbcWeu5kqkxBpveLmW8bSr/go-ipfs-util"
25

26 27 28 29
	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"
30 31
	coreCmds "github.com/ipfs/go-ipfs/core/commands"
	repo "github.com/ipfs/go-ipfs/repo"
32 33
	config "github.com/ipfs/go-ipfs/repo/config"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
34
	loggables "gx/ipfs/QmTMy4hVSY28DdwJ9kBz6y7q6MuioFzPcpM3Ma3aPjo1i3/go-libp2p-loggables"
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
	rand.Seed(time.Now().UnixNano())
68
	ctx := logging.ContextWithLoggable(context.Background(), loggables.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
	// Handle `ipfs help'
104 105 106 107 108 109 110
	if len(os.Args) == 2 {
		if os.Args[1] == "help" {
			printHelp(false, os.Stdout)
			os.Exit(0)
		} else if os.Args[1] == "--version" {
			os.Args[1] = "version"
		}
Etienne Laurin's avatar
Etienne Laurin committed
111 112
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
113
	// parse the commandline into a command invocation
114
	parseErr := invoc.Parse(ctx, os.Args[1:])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
115 116 117

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

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

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

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

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

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

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

		os.Exit(1)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
174 175
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
276
	return nil
277 278
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
279 280
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
	longHelp, _, err := i.req.Option("help").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
	shortHelp, _, err := i.req.Option("h").Bool()
285
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
286
		return false, false, err
287
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
288
	return longHelp, shortHelp, nil
289
}
290

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

293
	log.Event(ctx, "callPreCommandHooks", &details)
294
	log.Debug("calling pre-command hooks...")
295 296 297 298

	return nil
}

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

Jeromy's avatar
Jeromy committed
303 304 305 306
	err := req.SetRootContext(ctx)
	if err != nil {
		return nil, err
	}
307

308 309 310 311 312
	details, err := commandDetails(req.Path(), root)
	if err != nil {
		return nil, err
	}

313
	client, err := commandShouldRunOnDaemon(*details, req, root)
314 315 316
	if err != nil {
		return nil, err
	}
317

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

323 324 325 326 327 328 329
	if cmd.PreRun != nil {
		err = cmd.PreRun(req)
		if err != nil {
			return nil, err
		}
	}

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

340
	} else {
341
		log.Debug("executing command locally")
342

Jeromy's avatar
Jeromy committed
343
		err := req.SetRootContext(ctx)
Jeromy's avatar
Jeromy committed
344 345 346 347
		if err != nil {
			return nil, err
		}

348 349
		// Okay!!!!! NOW we can call the command.
		res = root.Call(req)
350 351

	}
352 353

	if cmd.PostRun != nil {
354
		cmd.PostRun(req, res)
355 356
	}

357
	return res, nil
358 359
}

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

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

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

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

399
	if details.doesNotUseRepo && details.canRunOnClient() {
400
		return nil, nil
401 402
	}

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

412 413 414 415 416 417 418
	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
		}
419

420 421 422 423
		// ok for api not to be running
	} else if err != nil { // some other api error
		return nil, err
	}
424

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

435
		return client, nil
436 437 438
	}

	if details.cannotRunOnClient {
439
		return nil, cmds.ClientError("must run on the ipfs daemon")
440 441
	}

442
	return nil, nil
443 444
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
445
func isClientError(err error) bool {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
446 447 448 449

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

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

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

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

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

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

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

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

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

553
func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
554

555
	intrh := NewIntrHandler()
556 557
	ctx, cancelFunc := context.WithCancel(ctx)

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

			ih.wg.Add(1)
			go func() {
				defer ih.wg.Done()
566
				cancelFunc()
567
			}()
568

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

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

577
	return intrh, ctx
578
}
579 580 581 582

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

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

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

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