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

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

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

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 "gx/ipfs/QmTMy4hVSY28DdwJ9kBz6y7q6MuioFzPcpM3Ma3aPjo1i3/go-libp2p-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
	// Handle `ipfs help'
106 107 108 109 110 111 112
	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
113 114
	}

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

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

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

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

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

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

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

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

		os.Exit(1)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
176 177
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	return nil
}

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

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

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

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

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

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

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

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

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

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

	}
353 354

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

358
	return res, nil
359 360
}

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

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

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

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

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

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

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

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

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

436
		return client, nil
437 438 439
	}

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

443
	return nil, nil
444 445
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

578
	return intrh, ctx
579
}
580 581 582 583

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

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

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

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