main.go 13.8 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"
9
	"os"
Matt Bell's avatar
Matt Bell committed
10
	"os/signal"
11
	"runtime"
12
	"runtime/pprof"
13
	"strings"
14
	"sync"
15
	"syscall"
16
	"time"
17

18 19 20 21 22 23 24 25 26 27 28 29
	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
	manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"

	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
	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"
	config "github.com/ipfs/go-ipfs/repo/config"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
	eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
	u "github.com/ipfs/go-ipfs/util"
30 31
)

32
// log is the command logger
33
var log = eventlog.Logger("cmd/ipfs")
34

35 36 37
// signal to output help
var errHelpRequested = errors.New("Help Requested")

38
const (
39 40 41 42
	EnvEnableProfiling = "IPFS_PROF"
	cpuProfile         = "ipfs.cpuprof"
	heapProfile        = "ipfs.memprof"
	errorFormat        = "ERROR: %v\n\n"
43
)
44

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
45 46 47 48
type cmdInvocation struct {
	path []string
	cmd  *cmds.Command
	req  cmds.Request
49
	node *core.IpfsNode
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
50 51 52 53 54 55 56 57
}

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

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

72 73 74 75 76 77 78
	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
79 80
	// 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
81
	printHelp := func(long bool, w io.Writer) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
82 83 84 85 86
		helpFunc := cmdsCli.ShortHelp
		if long {
			helpFunc = cmdsCli.LongHelp
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
87
		helpFunc("ipfs", Root, invoc.path, w)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
88 89
	}

90 91 92 93 94 95
	// 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
96 97 98 99 100 101
	// Handle `ipfs help'
	if len(os.Args) == 2 && os.Args[1] == "help" {
		printHelp(false, os.Stdout)
		os.Exit(0)
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
102
	// parse the commandline into a command invocation
103
	parseErr := invoc.Parse(ctx, os.Args[1:])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
104 105 106

	// BEFORE handling the parse error, if we have enough information
	// AND the user requested help, print it out and exit
107
	if invoc.req != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108 109 110 111 112 113
		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
114
			printHelp(longH, os.Stdout)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
115 116 117 118
			os.Exit(0)
		}
	}

119
	// ok now handle parse error (which means cli input was wrong,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
120
	// e.g. incorrect number of args, or nonexistent subcommand)
121 122
	if parseErr != nil {
		printErr(parseErr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
123 124 125 126 127

		// this was a user error, print help.
		if invoc.cmd != nil {
			// we need a newline space.
			fmt.Fprintf(os.Stderr, "\n")
128
			printMetaHelp(os.Stderr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129
		}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
130 131
		os.Exit(1)
	}
Matt Bell's avatar
Matt Bell committed
132

Etienne Laurin's avatar
Etienne Laurin committed
133 134 135 136 137 138 139 140
	// 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
141
	// ok, finally, run the command invocation.
142
	intrh, ctx := invoc.SetupInterruptHandler(ctx)
143
	defer intrh.Close()
144

145
	output, err := invoc.Run(ctx)
146
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
147 148 149 150
		printErr(err)

		// if this error was a client error, print short help too.
		if isClientError(err) {
151
			printMetaHelp(os.Stderr)
152
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
153
		os.Exit(1)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
154 155
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
156
	// everything went better than expected :)
Jeromy's avatar
Jeromy committed
157 158 159 160 161 162
	_, err = io.Copy(os.Stdout, output)
	if err != nil {
		printErr(err)

		os.Exit(1)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163 164
}

165
func (i *cmdInvocation) Run(ctx context.Context) (output io.Reader, err error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
166 167 168

	// 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
169
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170
		return nil, err
Brian Tiger Chow's avatar
Brian Tiger Chow committed
171
	}
172
	if debug || u.GetenvBool("DEBUG") || os.Getenv("IPFS_LOGGING") == "debug" {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
173
		u.Debug = true
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
174
		u.SetDebugLogging()
175
	}
176

177
	res, err := callCommand(ctx, i.req, Root, i.cmd)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
178
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
179
		return nil, err
180
	}
181

182 183 184 185
	if err := res.Error(); err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
186
	return res.Reader()
187
}
188

Brian Tiger Chow's avatar
Brian Tiger Chow committed
189
func (i *cmdInvocation) constructNodeFunc(ctx context.Context) func() (*core.IpfsNode, error) {
190 191 192 193
	return func() (*core.IpfsNode, error) {
		if i.req == nil {
			return nil, errors.New("constructing node without a request")
		}
194

195 196 197 198
		cmdctx := i.req.Context()
		if cmdctx == nil {
			return nil, errors.New("constructing node without a request context")
		}
199

200 201
		r, err := fsrepo.Open(i.req.Context().ConfigRoot)
		if err != nil { // repo is owned by the node
202
			return nil, err
203
		}
204

205 206
		// ok everything is good. set it on the invocation (for ownership)
		// and return it.
207 208 209 210 211 212
		n, err := core.NewIPFSNode(ctx, core.Standard(r, cmdctx.Online))
		if err != nil {
			return nil, err
		}
		i.node = n
		return i.node, nil
213
	}
214 215 216 217 218 219 220 221 222 223 224 225
}

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

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

229
	i.req, i.cmd, i.path, err = cmdsCli.Parse(args, os.Stdin, Root)
230
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
231
		return err
232 233
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
234
	repoPath, err := getRepoPath(i.req)
235
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
236
		return err
237
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
238
	log.Debugf("config path is %s", repoPath)
239

240
	// this sets up the function that will initialize the config lazily.
241
	cmdctx := i.req.Context()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
242
	cmdctx.ConfigRoot = repoPath
243
	cmdctx.LoadConfig = loadConfig
244 245
	// 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
246
	cmdctx.ConstructNode = i.constructNodeFunc(ctx)
247

248 249
	// 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
250
	if !i.req.Option("encoding").Found() {
251
		if i.req.Command().Marshalers != nil && i.req.Command().Marshalers[cmds.Text] != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
252
			i.req.SetOption("encoding", cmds.Text)
253
		} else {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
254
			i.req.SetOption("encoding", cmds.JSON)
255 256 257
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
258
	return nil
259 260
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
261 262
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
	longHelp, _, err := i.req.Option("help").Bool()
263
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
264
		return false, false, err
265
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
266
	shortHelp, _, err := i.req.Option("h").Bool()
267
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
268
		return false, false, err
269
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
270
	return longHelp, shortHelp, nil
271
}
272

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

275
	log.Event(ctx, "callPreCommandHooks", &details)
276 277 278 279 280
	log.Debug("Calling pre-command hooks...")

	return nil
}

281
func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd *cmds.Command) (cmds.Response, error) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
282
	log.Info(config.EnvDir, " ", req.Context().ConfigRoot)
283
	var res cmds.Response
284

285 286
	req.Context().Context = ctx

287 288 289 290 291
	details, err := commandDetails(req.Path(), root)
	if err != nil {
		return nil, err
	}

292
	log.Debug("looking for running daemon...")
293
	useDaemon, err := commandShouldRunOnDaemon(*details, req, root)
294 295 296
	if err != nil {
		return nil, err
	}
297

298
	err = callPreCommandHooks(ctx, *details, req, root)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
299 300 301
	if err != nil {
		return nil, err
	}
302

303 304 305 306 307 308 309
	if cmd.PreRun != nil {
		err = cmd.PreRun(req)
		if err != nil {
			return nil, err
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
310
	if useDaemon {
311

312 313 314 315 316
		cfg, err := req.Context().GetConfig()
		if err != nil {
			return nil, err
		}

317 318 319 320
		addr, err := ma.NewMultiaddr(cfg.Addresses.API)
		if err != nil {
			return nil, err
		}
321

322
		log.Infof("Executing command on daemon running at %s", addr)
323 324 325 326
		_, host, err := manet.DialArgs(addr)
		if err != nil {
			return nil, err
		}
327

328
		client := cmdsHttp.NewClient(host)
329

330 331 332 333
		res, err = client.Send(req)
		if err != nil {
			return nil, err
		}
334

335
	} else {
336
		log.Debug("Executing command locally")
337

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

		req.Context().Context = ctx

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

	}
349 350

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

354
	return res, nil
355 356
}

357 358 359 360 361
// commandDetails returns a command's details for the command given by |path|
// within the |root| command tree.
//
// Returns an error if the command is not found in the Command tree.
func commandDetails(path []string, root *cmds.Command) (*cmdDetails, error) {
362 363 364 365 366 367 368
	var details cmdDetails
	// find the last command in path that has a cmdDetailsMap entry
	cmd := root
	for _, cmp := range path {
		var found bool
		cmd, found = cmd.Subcommands[cmp]
		if !found {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
369
			return nil, fmt.Errorf("subcommand %s should be in root", cmp)
370
		}
371

372 373 374
		if cmdDetails, found := cmdDetailsMap[cmd]; found {
			details = cmdDetails
		}
375
	}
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
	return &details, nil
}

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

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

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

	// at this point need to know whether daemon is running. we defer
	// to this point so that some commands dont open files unnecessarily.
402 403 404 405
	daemonLocked, err := fsrepo.LockedByOtherProcess(req.Context().ConfigRoot)
	if err != nil {
		return false, err
	}
406 407 408

	if daemonLocked {

409 410
		log.Info("a daemon is running...")

411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
		if details.cannotRunOnDaemon {
			e := "ipfs daemon is running. please stop it to run this command"
			return false, cmds.ClientError(e)
		}

		return true, nil
	}

	if details.cannotRunOnClient {
		return false, cmds.ClientError("must run on the ipfs daemon")
	}

	return false, nil
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
426
func isClientError(err error) bool {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
427 428 429 430

	// 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
431
	// cast to cmds.Error
Brian Tiger Chow's avatar
Brian Tiger Chow committed
432 433 434 435 436
	switch e := err.(type) {
	case *cmds.Error:
		return e.Code == cmds.ErrClient
	case cmds.Error:
		return e.Code == cmds.ErrClient
437
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
438
	return false
439 440
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
441 442
func getRepoPath(req cmds.Request) (string, error) {
	repoOpt, found, err := req.Option("config").String()
443 444 445
	if err != nil {
		return "", err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
446 447
	if found && repoOpt != "" {
		return repoOpt, nil
448 449
	}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
450
	repoPath, err := fsrepo.BestKnownPath()
451 452 453
	if err != nil {
		return "", err
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
454
	return repoPath, nil
455 456
}

457
func loadConfig(path string) (*config.Config, error) {
Brian Tiger Chow's avatar
huh  
Brian Tiger Chow committed
458
	return fsrepo.ConfigAt(path)
459
}
460

461 462 463 464 465 466 467 468 469 470
// 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)
471 472 473 474
	go func() {
		for _ = range time.NewTicker(time.Second * 30).C {
			err := writeHeapProfileToFile()
			if err != nil {
rht's avatar
rht committed
475
				log.Error(err)
476 477 478
			}
		}
	}()
479 480 481 482 483 484 485 486

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

487 488 489
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
490
		return err
491
	}
492
	defer mprof.Close() // _after_ writing the heap profile
493 494
	return pprof.WriteHeapProfile(mprof)
}
495

496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
// 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
}
514

515 516 517 518 519 520 521 522
// 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
523
	go func() {
524 525 526 527 528 529 530 531 532 533
		defer ih.wg.Done()
		count := 0
		for _ = range ih.sig {
			count++
			handler(count, ih)
		}
		signal.Stop(ih.sig)
	}()
}

534
func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
535

536
	intrh := NewIntrHandler()
537 538
	ctx, cancelFunc := context.WithCancel(ctx)

539 540 541
	handlerFunc := func(count int, ih *IntrHandler) {
		switch count {
		case 1:
542
			fmt.Println() // Prevent un-terminated ^C character in terminal
543 544 545 546

			ih.wg.Add(1)
			go func() {
				defer ih.wg.Done()
547
				cancelFunc()
548
			}()
549

550 551 552
		default:
			fmt.Println("Received another interrupt before graceful shutdown, terminating...")
			os.Exit(-1)
Matt Bell's avatar
Matt Bell committed
553
		}
554 555 556
	}

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

558
	return intrh, ctx
559
}
560 561 562 563

func profileIfEnabled() (func(), error) {
	// FIXME this is a temporary hack so profiling of asynchronous operations
	// works as intended.
564
	if os.Getenv(EnvEnableProfiling) != "" {
565 566 567 568 569 570 571 572
		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
}