main.go 17.2 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
	"path/filepath"
15
	"runtime/pprof"
16
	"strings"
17
	"sync"
18
	"syscall"
19
	"time"
20

21
	oldcmds "github.com/ipfs/go-ipfs/commands"
22
	core "github.com/ipfs/go-ipfs/core"
23
	coreCmds "github.com/ipfs/go-ipfs/core/commands"
keks's avatar
keks committed
24
	corehttp "github.com/ipfs/go-ipfs/core/corehttp"
25
	"github.com/ipfs/go-ipfs/plugin/loader"
26
	repo "github.com/ipfs/go-ipfs/repo"
27 28
	config "github.com/ipfs/go-ipfs/repo/config"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
29

Steven Allen's avatar
Steven Allen committed
30 31
	u "gx/ipfs/QmPsAfmDBnZN3kZGSuNwvCNDZiHneERSKmRcFyG3UkvcT3/go-ipfs-util"
	manet "gx/ipfs/QmSGL5Uoa6gKHgBBwQG8u1CWKUC8ZnwaZiLgFVTFBR2bxr/go-multiaddr-net"
32
	logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
Steven Allen's avatar
Steven Allen committed
33
	loggables "gx/ipfs/QmSvcDkiRwB8LuMhUtnvhum2C851Mproo75ZDD19jx43tD/go-libp2p-loggables"
34
	"gx/ipfs/QmVD1W3MC8Hk1WZgFQPWWmBECJ3X72BgUYf9eCQ4PGzPps/go-ipfs-cmdkit"
Steven Allen's avatar
Steven Allen committed
35
	ma "gx/ipfs/QmW8s4zTsUoX1Q6CeYxVKPyqSKbF7H1YDUyTostBtZ8DaG/go-multiaddr"
36
	osh "gx/ipfs/QmXuBJ7DR6k3rmUEKtvVMhwjmXDuJgXXPUt4LQXKBMsU93/go-os-helper"
keks's avatar
keks committed
37 38 39
	"gx/ipfs/QmYopJAcV7R9SbxiPBCvqhnt8EusQpWPHewoZakCMt8hps/go-ipfs-cmds"
	"gx/ipfs/QmYopJAcV7R9SbxiPBCvqhnt8EusQpWPHewoZakCMt8hps/go-ipfs-cmds/cli"
	"gx/ipfs/QmYopJAcV7R9SbxiPBCvqhnt8EusQpWPHewoZakCMt8hps/go-ipfs-cmds/http"
40 41
)

42
// log is the command logger
Jeromy's avatar
Jeromy committed
43
var log = logging.Logger("cmd/ipfs")
44

45
var errRequestCanceled = errors.New("request canceled")
46

47
const (
48 49 50
	EnvEnableProfiling = "IPFS_PROF"
	cpuProfile         = "ipfs.cpuprof"
	heapProfile        = "ipfs.memprof"
51
)
52

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
53
type cmdInvocation struct {
54
	req  *cmds.Request
55
	node *core.IpfsNode
56
	ctx  *oldcmds.Context
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
57 58
}

Jan Winkelmann's avatar
Jan Winkelmann committed
59 60 61 62 63 64
type exitErr int

func (e exitErr) Error() string {
	return fmt.Sprint("exit code", int(e))
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
65 66
// main roadmap:
// - parse the commandline to get a cmdInvocation
67
// - if user requests help, print it and exit.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
68 69 70
// - run the command invocation
// - output the response
// - if anything fails, print error, maybe with help
71
func main() {
72 73 74 75
	os.Exit(mainRet())
}

func mainRet() int {
76
	rand.Seed(time.Now().UnixNano())
77
	ctx := logging.ContextWithLoggable(context.Background(), loggables.Uuid("session"))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
78 79 80 81 82 83 84 85
	var err error

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

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

Jan Winkelmann's avatar
Jan Winkelmann committed
93 94 95
	var invoc cmdInvocation
	defer invoc.close()

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
96 97
	// 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
98
	printHelp := func(long bool, w io.Writer) {
Jan Winkelmann's avatar
Jan Winkelmann committed
99
		helpFunc := cli.ShortHelp
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
100
		if long {
Jan Winkelmann's avatar
Jan Winkelmann committed
101
			helpFunc = cli.LongHelp
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
102 103
		}

104 105 106 107 108 109
		var p []string
		if invoc.req != nil {
			p = invoc.req.Path
		}

		helpFunc("ipfs", Root, p, w)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
110 111
	}

112 113
	// this is a message to tell the user how to get the help text
	printMetaHelp := func(w io.Writer) {
114
		cmdPath := strings.Join(invoc.req.Path, " ")
115 116 117
		fmt.Fprintf(w, "Use 'ipfs %s --help' for information about this command\n", cmdPath)
	}

Etienne Laurin's avatar
Etienne Laurin committed
118
	// Handle `ipfs help'
119 120 121
	if len(os.Args) == 2 {
		if os.Args[1] == "help" {
			printHelp(false, os.Stdout)
122
			return 0
123 124 125
		} else if os.Args[1] == "--version" {
			os.Args[1] = "version"
		}
Etienne Laurin's avatar
Etienne Laurin committed
126 127
	}

128 129 130
	intrh, ctx := invoc.SetupInterruptHandler(ctx)
	defer intrh.Close()

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
131
	// parse the commandline into a command invocation
132
	parseErr := invoc.Parse(ctx, os.Args[1:])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133 134 135

	// BEFORE handling the parse error, if we have enough information
	// AND the user requested help, print it out and exit
136
	if invoc.req != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
137 138 139
		longH, shortH, err := invoc.requestedHelp()
		if err != nil {
			printErr(err)
140
			return 1
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
141 142
		}
		if longH || shortH {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
143
			printHelp(longH, os.Stdout)
144
			return 0
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
145 146 147
		}
	}

148
	// ok now handle parse error (which means cli input was wrong,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
149
	// e.g. incorrect number of args, or nonexistent subcommand)
150 151
	if parseErr != nil {
		printErr(parseErr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
152 153

		// this was a user error, print help.
154
		if invoc.req != nil && invoc.req.Command != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
155 156
			// we need a newline space.
			fmt.Fprintf(os.Stderr, "\n")
157
			printHelp(false, os.Stderr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
158
		}
159
		return 1
Brian Tiger Chow's avatar
Brian Tiger Chow committed
160
	}
Matt Bell's avatar
Matt Bell committed
161

Etienne Laurin's avatar
Etienne Laurin committed
162 163 164
	// here we handle the cases where
	// - commands with no Run func are invoked directly.
	// - the main command is invoked.
165
	if invoc.req == nil || invoc.req.Command == nil || invoc.req.Command.Run == nil {
Etienne Laurin's avatar
Etienne Laurin committed
166
		printHelp(false, os.Stdout)
167
		return 0
Etienne Laurin's avatar
Etienne Laurin committed
168 169
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170
	// ok, finally, run the command invocation.
Jan Winkelmann's avatar
Jan Winkelmann committed
171
	err = invoc.Run(ctx)
172
	if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
173 174 175 176
		if code, ok := err.(exitErr); ok {
			return int(code)
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
177 178 179 180
		printErr(err)

		// if this error was a client error, print short help too.
		if isClientError(err) {
181
			printMetaHelp(os.Stderr)
182
		}
183
		return 1
Brian Tiger Chow's avatar
Brian Tiger Chow committed
184 185
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
186
	// everything went better than expected :)
187
	return 0
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
188 189
}

Jan Winkelmann's avatar
Jan Winkelmann committed
190
func (i *cmdInvocation) Run(ctx context.Context) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
191 192

	// check if user wants to debug. option OR env var.
193
	debug, _ := i.req.Options["debug"].(bool)
194
	if debug || os.Getenv("IPFS_LOGGING") == "debug" {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
195
		u.Debug = true
Jeromy's avatar
Jeromy committed
196
		logging.SetDebugLogging()
197
	}
198 199 200
	if u.GetenvBool("DEBUG") {
		u.Debug = true
	}
201

202
	return callCommand(ctx, i.req, Root, i.ctx)
203
}
204

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

211
		r, err := fsrepo.Open(i.ctx.ConfigRoot)
212
		if err != nil { // repo is owned by the node
213
			return nil, err
214
		}
215

216 217
		// ok everything is good. set it on the invocation (for ownership)
		// and return it.
Jan Winkelmann's avatar
Jan Winkelmann committed
218
		n, err = core.NewNode(ctx, &core.BuildCfg{
219
			Online: i.ctx.Online,
220 221
			Repo:   r,
		})
222 223 224
		if err != nil {
			return nil, err
		}
225
		n.SetLocal(true)
226 227
		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, err = cli.Parse(args, os.Stdin, Root)
245
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
246
		return err
247 248
	}

249 250 251 252 253 254
	//TODO remove this
	//fmt.Printf("%#v\n", i.req)

	// TODO(keks): pass this as arg to cli.Parse()
	i.req.Context = ctx

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

261
	// this sets up the function that will initialize the config lazily.
262 263 264 265 266
	if i.ctx == nil {
		i.ctx = &oldcmds.Context{}
	}
	i.ctx.ConfigRoot = repoPath
	i.ctx.LoadConfig = loadConfig
267 268
	// this sets up the function that will initialize the node
	// this is so that we can construct the node lazily.
269
	i.ctx.ConstructNode = i.constructNodeFunc(ctx)
270

271 272
	// if no encoding was specified by user, default to plaintext encoding
	// (if command doesn't support plaintext, use JSON instead)
keks's avatar
keks committed
273
	if enc := i.req.Options[cmds.EncLong]; enc == "" {
274
		if i.req.Command.Encoders != nil && i.req.Command.Encoders[cmds.Text] != nil {
keks's avatar
keks committed
275
			i.req.SetOption(cmds.EncLong, cmds.Text)
276
		} else {
keks's avatar
keks committed
277
			i.req.SetOption(cmds.EncLong, cmds.JSON)
278 279 280
		}
	}

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
284
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
285 286
	longHelp, _ := i.req.Options["help"].(bool)
	shortHelp, _ := i.req.Options["h"].(bool)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
287
	return longHelp, shortHelp, nil
288
}
289

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

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

	return nil
}

298 299 300
func callCommand(ctx context.Context, req *cmds.Request, root *cmds.Command, cctx *oldcmds.Context) error {
	log.Info(config.EnvDir, " ", cctx.ConfigRoot)
	cmd := req.Command
301

302
	details, err := commandDetails(req.Path, root)
Jeromy's avatar
Jeromy committed
303
	if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
304
		return err
Jeromy's avatar
Jeromy committed
305
	}
306

307
	client, err := commandShouldRunOnDaemon(*details, req, root, cctx)
308
	if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
309
		return err
310
	}
311

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

keks's avatar
keks committed
317
	encTypeStr, _ := req.Options[cmds.EncLong].(string)
Jan Winkelmann's avatar
Jan Winkelmann committed
318 319 320 321 322 323 324 325 326 327 328 329 330 331
	encType := cmds.EncodingType(encTypeStr)

	var (
		re     cmds.ResponseEmitter
		exitCh <-chan int
	)

	// first if condition checks the command's encoder map, second checks global encoder map (cmd vs. cmds)
	if enc, ok := cmd.Encoders[encType]; ok {
		re, exitCh = cli.NewResponseEmitter(os.Stdout, os.Stderr, enc, req)
	} else if enc, ok := cmds.Encoders[encType]; ok {
		re, exitCh = cli.NewResponseEmitter(os.Stdout, os.Stderr, enc, req)
	} else {
		return fmt.Errorf("could not find matching encoder for enctype %#v", encType)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
332
	}
333

334
	if cmd.PreRun != nil {
335
		err = cmd.PreRun(req, cctx)
336
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
337
			return err
338 339 340
		}
	}

Jan Winkelmann's avatar
Jan Winkelmann committed
341 342 343 344
	if cmd.PostRun != nil && cmd.PostRun[cmds.CLI] != nil {
		re = cmd.PostRun[cmds.CLI](req, re)
	}

Jeromy's avatar
Jeromy committed
345
	if client != nil && !cmd.External {
346
		log.Debug("executing command via API")
Jan Winkelmann's avatar
Jan Winkelmann committed
347 348

		res, err := client.Send(req)
349
		if err != nil {
350 351 352
			if isConnRefused(err) {
				err = repo.ErrApiNotRunning
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
353 354

			return wrapContextCanceled(err)
355
		}
356

Jan Winkelmann's avatar
Jan Winkelmann committed
357
		go func() {
keks's avatar
keks committed
358
			err := cmds.Copy(re, res)
Jan Winkelmann's avatar
Jan Winkelmann committed
359
			if err != nil {
keks's avatar
keks committed
360 361 362 363
				err = re.Emit(cmdkit.Error{err.Error(), cmdkit.ErrNormal | cmdkit.ErrFatal})
				if err != nil {
					log.Error(err)
				}
Jan Winkelmann's avatar
Jan Winkelmann committed
364 365
			}
		}()
366
	} else {
367
		log.Debug("executing command locally")
368

369
		pluginpath := filepath.Join(cctx.ConfigRoot, "plugins")
370
		if _, err := loader.LoadPlugins(pluginpath); err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
371
			return err
372 373
		}

374
		// Okay!!!!! NOW we can call the command.
Jan Winkelmann's avatar
Jan Winkelmann committed
375
		go func() {
376
			err := root.Call(req, re, cctx)
Jan Winkelmann's avatar
Jan Winkelmann committed
377 378 379 380
			if err != nil {
				re.SetError(err, cmdkit.ErrNormal)
			}
		}()
381
	}
382

Jan Winkelmann's avatar
Jan Winkelmann committed
383 384
	if returnCode := <-exitCh; returnCode != 0 {
		err = exitErr(returnCode)
385 386
	}

Jan Winkelmann's avatar
Jan Winkelmann committed
387
	return err
388 389
}

390 391 392 393 394
// 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) {
395 396 397 398
	var details cmdDetails
	// find the last command in path that has a cmdDetailsMap entry
	cmd := root
	for _, cmp := range path {
Jan Winkelmann's avatar
Jan Winkelmann committed
399 400
		cmd = cmd.Subcommand(cmp)
		if cmd == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
401
			return nil, fmt.Errorf("subcommand %s should be in root", cmp)
402
		}
403

Jan Winkelmann's avatar
Jan Winkelmann committed
404
		if cmdDetails, found := cmdDetailsMap[strings.Join(path, "/")]; found {
405 406
			details = cmdDetails
		}
407
	}
408 409 410 411
	return &details, nil
}

// commandShouldRunOnDaemon determines, from commmand details, whether a
412
// command ought to be executed on an ipfs daemon.
413
//
414
// It returns a client if the command should be executed on a daemon and nil if
415 416
// it should be executed on a client. It returns an error if the command must
// NOT be executed on either.
417 418
func commandShouldRunOnDaemon(details cmdDetails, req *cmds.Request, root *cmds.Command, cctx *oldcmds.Context) (http.Client, error) {
	path := req.Path
419 420
	// root command.
	if len(path) < 1 {
421
		return nil, nil
422
	}
423 424

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

428
	if details.doesNotUseRepo && details.canRunOnClient() {
429
		return nil, nil
430 431
	}

432 433 434 435
	// 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?
436
	apiAddrStr, _ := req.Options[coreCmds.ApiOption].(string)
437

438
	client, err := getApiClient(cctx.ConfigRoot, apiAddrStr)
439
	if err == repo.ErrApiNotRunning {
440
		if apiAddrStr != "" && req.Command != daemonCmd {
441 442 443 444
			// if user SPECIFIED an api, and this cmd is not daemon
			// we MUST use it. so error out.
			return nil, err
		}
445

446 447 448 449
		// ok for api not to be running
	} else if err != nil { // some other api error
		return nil, err
	}
450

michael's avatar
michael committed
451
	if client != nil {
452
		if details.cannotRunOnDaemon {
453
			// check if daemon locked. legacy error text, for now.
michael's avatar
michael committed
454
			log.Debugf("Command cannot run on daemon. Checking if daemon is locked")
455
			if daemonLocked, _ := fsrepo.LockedByOtherProcess(cctx.ConfigRoot); daemonLocked {
rht's avatar
rht committed
456
				return nil, cmds.ClientError("ipfs daemon is running. please stop it to run this command")
457
			}
rht's avatar
rht committed
458
			return nil, nil
459 460
		}

461
		return client, nil
462 463 464
	}

	if details.cannotRunOnClient {
465
		return nil, cmds.ClientError("must run on the ipfs daemon")
466 467
	}

468
	return nil, nil
469 470
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
471
func isClientError(err error) bool {
Jan Winkelmann's avatar
Jan Winkelmann committed
472 473
	if e, ok := err.(*cmdkit.Error); ok {
		return e.Code == cmdkit.ErrClient
474
	}
Jan Winkelmann's avatar
Jan Winkelmann committed
475

Brian Tiger Chow's avatar
Brian Tiger Chow committed
476
	return false
477 478
}

479 480
func getRepoPath(req *cmds.Request) (string, error) {
	repoOpt, found := req.Options["config"].(string)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
481 482
	if found && repoOpt != "" {
		return repoOpt, nil
483 484
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
485
	repoPath, err := fsrepo.BestKnownPath()
486 487 488
	if err != nil {
		return "", err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
489
	return repoPath, nil
490 491
}

492
func loadConfig(path string) (*config.Config, error) {
Brian Tiger Chow's avatar
huh  
Brian Tiger Chow committed
493
	return fsrepo.ConfigAt(path)
494
}
495

496 497 498 499 500 501 502 503 504 505
// 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)
506
	go func() {
507
		for range time.NewTicker(time.Second * 30).C {
508 509
			err := writeHeapProfileToFile()
			if err != nil {
rht's avatar
rht committed
510
				log.Error(err)
511 512 513
			}
		}
	}()
514 515 516 517 518 519 520 521

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

522 523 524
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
525
		return err
526
	}
527
	defer mprof.Close() // _after_ writing the heap profile
528 529
	return pprof.WriteHeapProfile(mprof)
}
530

531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
// 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
}
549

550 551 552 553 554 555 556 557
// 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
558
	go func() {
559 560
		defer ih.wg.Done()
		count := 0
561
		for range ih.sig {
562 563 564 565 566 567 568
			count++
			handler(count, ih)
		}
		signal.Stop(ih.sig)
	}()
}

569
func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
570

571
	intrh := NewIntrHandler()
572 573
	ctx, cancelFunc := context.WithCancel(ctx)

574 575 576
	handlerFunc := func(count int, ih *IntrHandler) {
		switch count {
		case 1:
577
			fmt.Println() // Prevent un-terminated ^C character in terminal
578 579 580 581

			ih.wg.Add(1)
			go func() {
				defer ih.wg.Done()
582
				cancelFunc()
583
			}()
584

585 586 587
		default:
			fmt.Println("Received another interrupt before graceful shutdown, terminating...")
			os.Exit(-1)
Matt Bell's avatar
Matt Bell committed
588
		}
589 590 591
	}

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

593
	return intrh, ctx
594
}
595 596 597 598

func profileIfEnabled() (func(), error) {
	// FIXME this is a temporary hack so profiling of asynchronous operations
	// works as intended.
599
	if os.Getenv(EnvEnableProfiling) != "" {
600 601 602 603 604 605 606 607
		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
}
608

609 610 611 612
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.
`
613 614
var checkIPFSUnixFmt = "Otherwise check:\n\tps aux | grep ipfs"
var checkIPFSWinFmt = "Otherwise check:\n\ttasklist | findstr ipfs"
615

616 617 618
// 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.
Jan Winkelmann's avatar
Jan Winkelmann committed
619
func getApiClient(repoPath, apiAddrStr string) (http.Client, error) {
620 621 622 623 624 625 626 627 628
	var apiErrorFmt string
	switch {
	case osh.IsUnix():
		apiErrorFmt = apiFileErrorFmt + checkIPFSUnixFmt
	case osh.IsWindows():
		apiErrorFmt = apiFileErrorFmt + checkIPFSWinFmt
	default:
		apiErrorFmt = apiFileErrorFmt
	}
629

630 631 632 633 634 635 636 637 638 639 640 641 642
	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 {
643 644 645
			return nil, err
		}

646 647 648 649 650 651
		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")
652
	}
rht's avatar
rht committed
653
	return apiClientForAddr(addr)
654 655
}

Jan Winkelmann's avatar
Jan Winkelmann committed
656
func apiClientForAddr(addr ma.Multiaddr) (http.Client, error) {
657 658 659 660 661
	_, host, err := manet.DialArgs(addr)
	if err != nil {
		return nil, err
	}

keks's avatar
keks committed
662
	return http.NewClient(host, http.ClientWithAPIPrefix(corehttp.APIPath)), nil
663 664 665
}

func isConnRefused(err error) bool {
Jeromy's avatar
Jeromy committed
666 667 668 669 670 671 672 673 674 675 676
	// 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"
677
}
rht's avatar
rht committed
678 679 680 681 682 683 684

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