main.go 8.94 KB
Newer Older
1 2 3
package main

import (
4
	"errors"
5 6 7
	"fmt"
	"io"
	"os"
Matt Bell's avatar
Matt Bell committed
8
	"os/signal"
9 10
	"runtime/pprof"

11
	logging "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-logging"
12 13 14
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
	manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr/net"

15 16 17 18 19 20 21 22 23 24 25 26
	cmds "github.com/jbenet/go-ipfs/commands"
	cmdsCli "github.com/jbenet/go-ipfs/commands/cli"
	cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
	"github.com/jbenet/go-ipfs/config"
	"github.com/jbenet/go-ipfs/core"
	daemon "github.com/jbenet/go-ipfs/daemon2"
	u "github.com/jbenet/go-ipfs/util"
)

// log is the command logger
var log = u.Logger("cmd/ipfs")

27 28 29
// signal to output help
var errHelpRequested = errors.New("Help Requested")

30
const (
31 32
	cpuProfile  = "ipfs.cpuprof"
	heapProfile = "ipfs.memprof"
33 34
	errorFormat = "ERROR: %v\n\n"
)
35

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
36 37 38 39 40 41 42 43 44 45 46 47
type cmdInvocation struct {
	path []string
	cmd  *cmds.Command
	req  cmds.Request
}

// 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
48
func main() {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
	var invoc cmdInvocation
	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())
	}

	// this is a local helper to print out help text.
	// there's some considerations that this makes easier.
	printHelp := func(long bool) {
		helpFunc := cmdsCli.ShortHelp
		if long {
			helpFunc = cmdsCli.LongHelp
		}

Matt Bell's avatar
Matt Bell committed
66
		helpFunc("ipfs", Root, invoc.path, os.Stderr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
67 68 69
	}

	// parse the commandline into a command invocation
70
	parseErr := invoc.Parse(os.Args[1:])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
71 72 73

	// BEFORE handling the parse error, if we have enough information
	// AND the user requested help, print it out and exit
74
	if invoc.req != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
75 76 77 78 79 80 81 82 83 84 85
		longH, shortH, err := invoc.requestedHelp()
		if err != nil {
			printErr(err)
			os.Exit(1)
		}
		if longH || shortH {
			printHelp(longH)
			os.Exit(0)
		}
	}

86 87 88 89 90 91 92 93 94
	// 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.Exit(0)
	}

	// ok now handle parse error (which means cli input was wrong,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
95
	// e.g. incorrect number of args, or nonexistent subcommand)
96 97
	if parseErr != nil {
		printErr(parseErr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
98 99 100 101 102 103 104

		// this was a user error, print help.
		if invoc.cmd != nil {
			// we need a newline space.
			fmt.Fprintf(os.Stderr, "\n")
			printHelp(false)
		}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
105 106
		os.Exit(1)
	}
Matt Bell's avatar
Matt Bell committed
107

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108 109
	// ok, finally, run the command invocation.
	output, err := invoc.Run()
110
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
111 112 113 114 115
		printErr(err)

		// if this error was a client error, print short help too.
		if isClientError(err) {
			printHelp(false)
116
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
117
		os.Exit(1)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
118 119
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
120 121 122 123 124 125 126 127 128
	// everything went better than expected :)
	io.Copy(os.Stdout, output)
}

func (i *cmdInvocation) Run() (output io.Reader, err error) {
	handleInterrupt()

	// 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
129
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
130
		return nil, err
Brian Tiger Chow's avatar
Brian Tiger Chow committed
131
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
132
	if debug || u.GetenvBool("DEBUG") {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
133 134
		u.Debug = true
		u.SetAllLoggers(logging.DEBUG)
135
	}
136

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
137 138
	// if debugging, let's profile.
	// TODO maybe change this to its own option... profiling makes it slower.
139
	if u.Debug {
140
		stopProfilingFunc, err := startProfiling()
141
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
142
			return nil, err
143
		}
144
		defer stopProfilingFunc() // to be executed as late as possible
145
	}
146

Matt Bell's avatar
Matt Bell committed
147
	res, err := callCommand(i.req, Root)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
148
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
149
		return nil, err
150
	}
151

152 153 154 155
	if err := res.Error(); err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
156
	return res.Reader()
157
}
158

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
159 160
func (i *cmdInvocation) Parse(args []string) error {
	var err error
161

162
	i.req, i.cmd, i.path, err = cmdsCli.Parse(args, Root)
163
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
164
		return err
165 166
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
167
	configPath, err := getConfigRoot(i.req)
168
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
169
		return err
170 171
	}

172
	// this sets up the function that will initialize the config lazily.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
173
	ctx := i.req.Context()
174
	ctx.ConfigRoot = configPath
175
	ctx.LoadConfig = loadConfig
176

177 178
	// 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
179
	if !i.req.Option("encoding").Found() {
180
		if i.req.Command().Marshalers != nil && i.req.Command().Marshalers[cmds.Text] != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
181
			i.req.SetOption("encoding", cmds.Text)
182
		} else {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
183
			i.req.SetOption("encoding", cmds.JSON)
184 185 186
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
187
	return nil
188 189
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
190 191
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
	longHelp, _, err := i.req.Option("help").Bool()
192
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
193
		return false, false, err
194
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
195
	shortHelp, _, err := i.req.Option("h").Bool()
196
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
197
		return false, false, err
198
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
199
	return longHelp, shortHelp, nil
200
}
201

202
func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
203
	var res cmds.Response
204

205
	useDaemon, err := commandShouldRunOnDaemon(req, root)
206 207 208
	if err != nil {
		return nil, err
	}
209

210
	if useDaemon {
211 212

		cfg, err := req.Context().GetConfig()
213
		if err != nil {
214
			return nil, err
215
		}
216

217 218 219 220
		addr, err := ma.NewMultiaddr(cfg.Addresses.API)
		if err != nil {
			return nil, err
		}
221

222
		log.Infof("Executing command on daemon running at %s", addr)
223 224 225 226
		_, host, err := manet.DialArgs(addr)
		if err != nil {
			return nil, err
		}
227

228
		client := cmdsHttp.NewClient(host)
229

230 231 232 233
		res, err = client.Send(req)
		if err != nil {
			return nil, err
		}
234

235
	} else {
236
		log.Info("Executing command locally")
237

238 239 240 241 242
		// this sets up the function that will initialize the node
		// this is so that we can construct the node lazily.
		ctx := req.Context()
		ctx.ConstructNode = func() (*core.IpfsNode, error) {
			cfg, err := ctx.GetConfig()
243
			if err != nil {
244
				return nil, err
245
			}
246 247 248 249 250
			return core.NewIpfsNode(cfg, false)
		}

		// Okay!!!!! NOW we can call the command.
		res = root.Call(req)
251

252 253 254 255 256 257
		// 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.
		node := req.Context().NodeWithoutConstructing()
		if node != nil {
			node.Close()
258 259
		}
	}
260
	return res, nil
261 262
}

263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
func commandShouldRunOnDaemon(req cmds.Request, root *cmds.Command) (bool, error) {
	path := req.Path()
	// root command.
	if len(path) < 1 {
		return false, nil
	}

	cmd, found := root.Subcommands[path[0]]
	if !found {
		return false, fmt.Errorf("subcommand %s should be in root", path[0])
	}

	details, found := cmdDetailsMap[cmd]
	if !found {
		details = cmdDetails{} // defaults
	}

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

	if details.doesNotUseRepo && !details.cannotRunOnClient {
		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.
	daemonLocked := daemon.Locked(req.Context().ConfigRoot)
	log.Info("Daemon is running.")

	if daemonLocked {

		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
310
func isClientError(err error) bool {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
311 312 313 314

	// 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
315
	// cast to cmds.Error
Brian Tiger Chow's avatar
Brian Tiger Chow committed
316 317 318 319 320
	switch e := err.(type) {
	case *cmds.Error:
		return e.Code == cmds.ErrClient
	case cmds.Error:
		return e.Code == cmds.ErrClient
321
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
322
	return false
323 324 325
}

func getConfigRoot(req cmds.Request) (string, error) {
326
	configOpt, found, err := req.Option("config").String()
327 328 329
	if err != nil {
		return "", err
	}
330
	if found && configOpt != "" {
331
		return configOpt, nil
332 333 334 335 336 337 338 339 340
	}

	configPath, err := config.PathRoot()
	if err != nil {
		return "", err
	}
	return configPath, nil
}

341
func loadConfig(path string) (*config.Config, error) {
342 343 344 345 346 347 348
	configFile, err := config.Filename(path)
	if err != nil {
		return nil, err
	}

	return config.Load(configFile)
}
349

350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
// 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)

	stopProfiling := func() {
		pprof.StopCPUProfile()
		defer ofi.Close() // captured by the closure
		err := writeHeapProfileToFile()
		if err != nil {
			log.Critical(err)
		}
	}
	return stopProfiling, nil
}

372 373 374
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
375
		return err
376
	}
377
	defer mprof.Close() // _after_ writing the heap profile
378 379
	return pprof.WriteHeapProfile(mprof)
}
380

Matt Bell's avatar
Matt Bell committed
381 382 383 384 385 386 387 388
// listen for and handle SIGTERM
func handleInterrupt() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)

	go func() {
		for _ = range c {
			log.Info("Received interrupt signal, terminating...")
389
			os.Exit(0)
Matt Bell's avatar
Matt Bell committed
390 391 392
		}
	}()
}