main.go 7.1 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)
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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
func handleParseError(req cmds.Request, root *cmds.Command, cmd *cmds.Command, path []string) (err error) {
	var longHelp, shortHelp bool

	if req != nil {
		// help and h are defined in the root. We expect them to be bool.
		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 {
			fmt.Printf(errorFormat, err)
		}
	}

	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)
	}
	return err
}

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

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

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

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

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

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

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

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

			client := cmdsHttp.NewClient(host)

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

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

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

234
	return res, nil
235 236
}

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

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

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

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

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

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

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

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

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

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

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