main.go 16.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 (
5
	"context"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
6
	"errors"
7 8
	"fmt"
	"io"
9
	"math/rand"
Jeromy's avatar
Jeromy committed
10 11
	"net"
	"net/url"
12
	"os"
Matt Bell's avatar
Matt Bell committed
13
	"os/signal"
14
	"runtime/pprof"
15
	"strings"
16
	"sync"
17
	"syscall"
18
	"time"
19

20 21 22 23
	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"
24 25
	coreCmds "github.com/ipfs/go-ipfs/core/commands"
	repo "github.com/ipfs/go-ipfs/repo"
26 27
	config "github.com/ipfs/go-ipfs/repo/config"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
28 29

	logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
30 31
	loggables "gx/ipfs/QmVesPmqbPp7xRGyY96tnBwzDtVV1nqv4SCVxo5zCqKyH8/go-libp2p-loggables"
	u "gx/ipfs/QmWbjfz3u6HkAdPh34dgPchGbQjob6LXLhAeCGii2TX69n/go-ipfs-util"
32
	osh "gx/ipfs/QmXuBJ7DR6k3rmUEKtvVMhwjmXDuJgXXPUt4LQXKBMsU93/go-os-helper"
33 34
	ma "gx/ipfs/QmcyqRMCAXVtYPS4DiBrA7sezL9rRGfW8Ctx7cywL4TXJj/go-multiaddr"
	manet "gx/ipfs/Qmf1Gq7N45Rpuw7ev47uWgH6dLPtdnvcMRNPkVBwqjLJg2/go-multiaddr-net"
35 36
)

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

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

46
const (
47 48 49
	EnvEnableProfiling = "IPFS_PROF"
	cpuProfile         = "ipfs.cpuprof"
	heapProfile        = "ipfs.memprof"
50
	// 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 69 70
	os.Exit(mainRet())
}

func mainRet() int {
71
	rand.Seed(time.Now().UnixNano())
72
	ctx := logging.ContextWithLoggable(context.Background(), loggables.Uuid("session"))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73
	var err error
74 75
	var invoc cmdInvocation
	defer invoc.close()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
76 77 78 79 80 81 82

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

83 84 85
	stopFunc, err := profileIfEnabled()
	if err != nil {
		printErr(err)
86
		return 1
87 88 89
	}
	defer stopFunc() // to be executed as late as possible

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
90 91
	// 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
92
	printHelp := func(long bool, w io.Writer) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
93 94 95 96 97
		helpFunc := cmdsCli.ShortHelp
		if long {
			helpFunc = cmdsCli.LongHelp
		}

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

101 102 103 104 105 106
	// 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
107
	// Handle `ipfs help'
108 109 110
	if len(os.Args) == 2 {
		if os.Args[1] == "help" {
			printHelp(false, os.Stdout)
111
			return 0
112 113 114
		} else if os.Args[1] == "--version" {
			os.Args[1] = "version"
		}
Etienne Laurin's avatar
Etienne Laurin committed
115 116
	}

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

	// BEFORE handling the parse error, if we have enough information
	// AND the user requested help, print it out and exit
122
	if invoc.req != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
123 124 125
		longH, shortH, err := invoc.requestedHelp()
		if err != nil {
			printErr(err)
126
			return 1
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
127 128
		}
		if longH || shortH {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129
			printHelp(longH, os.Stdout)
130
			return 0
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
131 132 133
		}
	}

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

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

Etienne Laurin's avatar
Etienne Laurin committed
148 149 150 151 152
	// 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)
153
		return 0
Etienne Laurin's avatar
Etienne Laurin committed
154 155
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
156
	// ok, finally, run the command invocation.
157
	intrh, ctx := invoc.SetupInterruptHandler(ctx)
158
	defer intrh.Close()
159

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

		// if this error was a client error, print short help too.
		if isClientError(err) {
166
			printMetaHelp(os.Stderr)
167
		}
168
		return 1
Brian Tiger Chow's avatar
Brian Tiger Chow committed
169 170
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
171
	// everything went better than expected :)
Jeromy's avatar
Jeromy committed
172 173 174
	_, err = io.Copy(os.Stdout, output)
	if err != nil {
		printErr(err)
175
		return 1
Jeromy's avatar
Jeromy committed
176
	}
177
	return 0
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
178 179
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
280
	return nil
281 282
}

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

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

297
	log.Event(ctx, "callPreCommandHooks", &details)
298
	log.Debug("calling pre-command hooks...")
299 300 301 302

	return nil
}

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

Jeromy's avatar
Jeromy committed
307 308 309 310
	err := req.SetRootContext(ctx)
	if err != nil {
		return nil, err
	}
311

312 313 314 315 316
	details, err := commandDetails(req.Path(), root)
	if err != nil {
		return nil, err
	}

317
	client, err := commandShouldRunOnDaemon(*details, req, root)
318 319 320
	if err != nil {
		return nil, err
	}
321

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

327 328 329 330 331 332 333
	if cmd.PreRun != nil {
		err = cmd.PreRun(req)
		if err != nil {
			return nil, err
		}
	}

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

344
	} else {
345
		log.Debug("executing command locally")
346

Jeromy's avatar
Jeromy committed
347
		err := req.SetRootContext(ctx)
Jeromy's avatar
Jeromy committed
348 349 350 351
		if err != nil {
			return nil, err
		}

352 353
		// Okay!!!!! NOW we can call the command.
		res = root.Call(req)
354 355

	}
356 357

	if cmd.PostRun != nil {
358
		cmd.PostRun(req, res)
359 360
	}

361
	return res, nil
362 363
}

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

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

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

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

403
	if details.doesNotUseRepo && details.canRunOnClient() {
404
		return nil, nil
405 406
	}

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

416 417 418 419 420 421 422
	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
		}
423

424 425 426 427
		// ok for api not to be running
	} else if err != nil { // some other api error
		return nil, err
	}
428

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

439
		return client, nil
440 441 442
	}

	if details.cannotRunOnClient {
443
		return nil, cmds.ClientError("must run on the ipfs daemon")
444 445
	}

446
	return nil, nil
447 448
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
449
func isClientError(err error) bool {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
450 451 452 453

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

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

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

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

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

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

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

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

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

557
func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
558

559
	intrh := NewIntrHandler()
560 561
	ctx, cancelFunc := context.WithCancel(ctx)

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

			ih.wg.Add(1)
			go func() {
				defer ih.wg.Done()
570
				cancelFunc()
571
			}()
572

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

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

581
	return intrh, ctx
582
}
583 584 585 586

func profileIfEnabled() (func(), error) {
	// FIXME this is a temporary hack so profiling of asynchronous operations
	// works as intended.
587
	if os.Getenv(EnvEnableProfiling) != "" {
588 589 590 591 592 593 594 595
		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
}
596

597 598 599 600
var apiFileErrorFmt string = `Failed to parse '%[1]s/api' file.
	error: %[2]s
If you're sure go-ipfs isn't running, you can just delete it.
`
601 602
var checkIPFSUnixFmt = "Otherwise check:\n\tps aux | grep ipfs"
var checkIPFSWinFmt = "Otherwise check:\n\ttasklist | findstr ipfs"
603

604 605 606 607
// 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) {
608 609 610 611 612 613 614 615 616
	var apiErrorFmt string
	switch {
	case osh.IsUnix():
		apiErrorFmt = apiFileErrorFmt + checkIPFSUnixFmt
	case osh.IsWindows():
		apiErrorFmt = apiFileErrorFmt + checkIPFSWinFmt
	default:
		apiErrorFmt = apiFileErrorFmt
	}
617

618 619 620 621 622 623 624 625 626 627 628 629 630
	var addr ma.Multiaddr
	var err error
	if len(apiAddrStr) != 0 {
		addr, err = ma.NewMultiaddr(apiAddrStr)
		if err != nil {
			return nil, err
		}
		if len(addr.Protocols()) == 0 {
			return nil, fmt.Errorf("mulitaddr doesn't provide any protocols")
		}
	} else {
		addr, err = fsrepo.APIAddr(repoPath)
		if err == repo.ErrApiNotRunning {
631 632 633
			return nil, err
		}

634 635 636 637 638 639
		if err != nil {
			return nil, fmt.Errorf(apiErrorFmt, repoPath, err.Error())
		}
	}
	if len(addr.Protocols()) == 0 {
		return nil, fmt.Errorf(apiErrorFmt, repoPath, "multiaddr doesn't provide any protocols")
640
	}
rht's avatar
rht committed
641
	return apiClientForAddr(addr)
642 643 644 645 646 647 648 649 650 651 652 653
}

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
654 655 656 657 658 659 660 661 662 663 664
	// 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"
665
}
rht's avatar
rht committed
666 667 668 669 670 671 672

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