main.go 7.76 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 206 207 208
	local, found, err := req.Option("local").Bool()
	if err != nil {
		return nil, err
	}
209 210

	remote := !isLocal(req.Command()) && (!found || !local)
211

212 213 214 215
	log.Info("Checking if daemon is running...")
	if remote && daemon.Locked(req.Context().ConfigRoot) {

		cfg, err := req.Context().GetConfig()
216
		if err != nil {
217
			return nil, err
218
		}
219

220 221 222 223
		addr, err := ma.NewMultiaddr(cfg.Addresses.API)
		if err != nil {
			return nil, err
		}
224

225 226 227 228
		_, host, err := manet.DialArgs(addr)
		if err != nil {
			return nil, err
		}
229

230
		client := cmdsHttp.NewClient(host)
231

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

237 238
	} else {
		log.Info("Executing command locally: daemon not running")
239

240 241 242 243 244
		// 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()
245
			if err != nil {
246
				return nil, err
247
			}
248 249 250 251 252
			return core.NewIpfsNode(cfg, false)
		}

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

254 255 256 257 258 259
		// 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()
260 261
		}
	}
262
	return res, nil
263 264
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
265 266 267 268 269
func isClientError(err error) bool {
	// cast to cmds.Error
	cmdErr, ok := err.(*cmds.Error)
	if !ok {
		return false
270
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
271
	return cmdErr.Code == cmds.ErrClient
272 273 274
}

func getConfigRoot(req cmds.Request) (string, error) {
275
	configOpt, found, err := req.Option("config").String()
276 277 278
	if err != nil {
		return "", err
	}
279
	if found && configOpt != "" {
280
		return configOpt, nil
281 282 283 284 285 286 287 288 289
	}

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

290
func loadConfig(path string) (*config.Config, error) {
291 292 293 294 295 296 297
	configFile, err := config.Filename(path)
	if err != nil {
		return nil, err
	}

	return config.Load(configFile)
}
298

299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
// 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
}

321 322 323
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
324
		return err
325
	}
326
	defer mprof.Close() // _after_ writing the heap profile
327 328
	return pprof.WriteHeapProfile(mprof)
}
329

Matt Bell's avatar
Matt Bell committed
330 331 332 333 334 335 336 337
// 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...")
338
			os.Exit(0)
Matt Bell's avatar
Matt Bell committed
339 340 341
		}
	}()
}