main.go 7.15 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 27
	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"
	commands "github.com/jbenet/go-ipfs/core/commands2"
	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")

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

34
func main() {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
35 36 37 38 39 40 41 42
	err := run()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func run() error {
Matt Bell's avatar
Matt Bell committed
43 44
	handleInterrupt()

45
	args := os.Args[1:]
46 47
	req, root, err := createRequest(args)
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
48 49 50
		return err
	}

51
	debug, _, err := req.Option("debug").Bool()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
52 53 54 55 56 57
	if err != nil {
		return err
	}
	if debug {
		u.Debug = true
		u.SetAllLoggers(logging.DEBUG)
58
	}
59 60

	if u.Debug {
61
		stopProfilingFunc, err := startProfiling()
62
		if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
63
			return err
64
		}
65
		defer stopProfilingFunc() // to be executed as late as possible
66
	}
67

Brian Tiger Chow's avatar
Brian Tiger Chow committed
68 69 70 71 72 73 74 75
	helpTextDisplayed, err := handleHelpOption(req, root)
	if err != nil {
		return err
	}
	if helpTextDisplayed {
		return nil
	}

76 77 78 79
	res, err := callCommand(req, root)
	if err != nil {
		return err
	}
80 81 82 83 84

	err = outputResponse(res, root)
	if err != nil {
		return err
	}
85

Brian Tiger Chow's avatar
Brian Tiger Chow committed
86
	return nil
87
}
88

89
func createRequest(args []string) (cmds.Request, *cmds.Command, error) {
90 91 92 93
	req, root, cmd, path, err := cmdsCli.Parse(args, Root, commands.Root)

	// handle parse error (which means the commandline input was wrong,
	// e.g. incorrect number of args, or nonexistent subcommand)
94
	if err != nil {
95
		return nil, nil, handleParseError(req, root, cmd, path, err)
96 97
	}

98
	configPath, err := getConfigRoot(req)
99
	if err != nil {
100
		return nil, nil, err
101 102 103 104
	}

	conf, err := getConfig(configPath)
	if err != nil {
105
		return nil, nil, err
106 107 108 109 110
	}
	ctx := req.Context()
	ctx.ConfigRoot = configPath
	ctx.Config = conf

111 112
	// if no encoding was specified by user, default to plaintext encoding
	// (if command doesn't support plaintext, use JSON instead)
113
	if !req.Option("encoding").Found() {
114
		if req.Command().Marshallers != nil && req.Command().Marshallers[cmds.Text] != nil {
115 116 117 118 119 120
			req.SetOption("encoding", cmds.Text)
		} else {
			req.SetOption("encoding", cmds.JSON)
		}
	}

121
	return req, root, nil
122 123
}

124
func handleParseError(req cmds.Request, root *cmds.Command, cmd *cmds.Command, path []string, parseError error) error {
125 126 127 128
	var longHelp, shortHelp bool

	if req != nil {
		// help and h are defined in the root. We expect them to be bool.
129
		var err error
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
		longHelp, _, err = req.Option("help").Bool()
		if err != nil {
			return err
		}
		shortHelp, _, err = req.Option("h").Bool()
		if err != nil {
			return err
		}
	}

	// if the -help flag wasn't specified, show the error message
	// or if a path was returned (user specified a valid subcommand), show the error message
	// (this means there was an option or argument error)
	if path != nil && len(path) > 0 {
		if !longHelp && !shortHelp {
145
			fmt.Printf(errorFormat, parseError)
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
		}
	}

	if cmd == nil {
		root = commands.Root
	}

	// show the long help text if the -help flag was specified or we are at the root command
	// otherwise, show short help text
	helpFunc := cmdsCli.ShortHelp
	if longHelp || len(path) == 0 {
		helpFunc = cmdsCli.LongHelp
	}

	htErr := helpFunc("ipfs", root, path, os.Stdout)
	if htErr != nil {
		fmt.Println(htErr)
	}
164
	return parseError
165 166
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
167
func handleHelpOption(req cmds.Request, root *cmds.Command) (helpTextDisplayed bool, err error) {
168
	longHelp, _, err := req.Option("help").Bool()
169
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
170
		return false, err
171
	}
172
	shortHelp, _, err := req.Option("h").Bool()
173 174 175 176
	if err != nil {
		return false, err
	}
	if !longHelp && !shortHelp {
177 178
		return false, nil
	}
179 180 181 182 183 184
	helpFunc := cmdsCli.ShortHelp
	if longHelp || len(req.Path()) == 0 {
		helpFunc = cmdsCli.LongHelp
	}

	err = helpFunc("ipfs", root, req.Path(), os.Stdout)
185 186
	if err != nil {
		return false, err
187
	}
188
	return true, nil
189
}
190

191
func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
192
	var res cmds.Response
193

194
	if root == Root { // TODO explain what it means when root == Root
195
		res = root.Call(req)
196 197

	} else {
198
		local, found, err := req.Option("local").Bool()
199
		if err != nil {
200
			return nil, err
201
		}
202

203 204 205
		remote := !found || !local

		if remote && daemon.Locked(req.Context().ConfigRoot) {
206 207
			addr, err := ma.NewMultiaddr(req.Context().Config.Addresses.API)
			if err != nil {
208
				return nil, err
209 210 211 212
			}

			_, host, err := manet.DialArgs(addr)
			if err != nil {
213
				return nil, err
214 215 216 217 218
			}

			client := cmdsHttp.NewClient(host)

			res, err = client.Send(req)
219
			if err != nil {
220
				return nil, err
221 222 223
			}

		} else {
224
			node, err := core.NewIpfsNode(req.Context().Config, false)
225
			if err != nil {
226
				return nil, err
227
			}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
228
			defer node.Close()
229
			req.Context().Node = node
230

231
			res = root.Call(req)
232 233 234
		}
	}

235
	return res, nil
236 237
}

238
func outputResponse(res cmds.Response, root *cmds.Command) error {
239
	if res.Error() != nil {
240
		fmt.Printf(errorFormat, res.Error().Error())
241

242 243 244 245 246
		if res.Error().Code != cmds.ErrClient {
			return res.Error()
		}

		// if this is a client error, we try to display help text
247
		if res.Error().Code == cmds.ErrClient {
248
			err := cmdsCli.ShortHelp("ipfs", root, res.Request().Path(), os.Stdout)
249
			if err != nil {
250
				fmt.Println(err)
251
			}
252 253
		}

254
		emptyErr := errors.New("") // already displayed error text
255
		return emptyErr
256 257
	}

258
	out, err := res.Reader()
259
	if err != nil {
260
		return err
261
	}
262 263

	io.Copy(os.Stdout, out)
264
	return nil
265 266 267
}

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

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

func getConfig(path string) (*config.Config, error) {
	configFile, err := config.Filename(path)
	if err != nil {
		return nil, err
	}

	return config.Load(configFile)
}
291

292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
// 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
}

314 315 316
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
317
		return err
318
	}
319
	defer mprof.Close() // _after_ writing the heap profile
320 321
	return pprof.WriteHeapProfile(mprof)
}
322

Matt Bell's avatar
Matt Bell committed
323 324 325 326 327 328 329 330
// 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...")
331
			os.Exit(0)
Matt Bell's avatar
Matt Bell committed
332 333 334
		}
	}()
}