files.go 27.9 KB
Newer Older
Jeromy's avatar
Jeromy committed
1 2 3 4
package commands

import (
	"bytes"
Jeromy's avatar
Jeromy committed
5
	"context"
Jeromy's avatar
Jeromy committed
6 7 8 9 10 11 12
	"errors"
	"fmt"
	"io"
	"os"
	gopath "path"
	"strings"

13
	bservice "github.com/ipfs/go-ipfs/blockservice"
Michael Muré's avatar
Michael Muré committed
14 15
	oldcmds "github.com/ipfs/go-ipfs/commands"
	lgc "github.com/ipfs/go-ipfs/commands/legacy"
Jeromy's avatar
Jeromy committed
16
	core "github.com/ipfs/go-ipfs/core"
Jan Winkelmann's avatar
Jan Winkelmann committed
17
	e "github.com/ipfs/go-ipfs/core/commands/e"
Jeromy's avatar
Jeromy committed
18 19 20
	dag "github.com/ipfs/go-ipfs/merkledag"
	mfs "github.com/ipfs/go-ipfs/mfs"
	path "github.com/ipfs/go-ipfs/path"
21
	resolver "github.com/ipfs/go-ipfs/path/resolver"
Jeromy's avatar
Jeromy committed
22
	ft "github.com/ipfs/go-ipfs/unixfs"
23
	uio "github.com/ipfs/go-ipfs/unixfs/io"
Jeromy's avatar
Jeromy committed
24

Michael Muré's avatar
Michael Muré committed
25
	humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
Steven Allen's avatar
Steven Allen committed
26
	logging "gx/ipfs/QmRb5jh8z2E8hMGN2tkvs1yHynUanqnZ3UeKwgN1i9P1F8/go-log"
Dominic Della Valle's avatar
Dominic Della Valle committed
27
	cmds "gx/ipfs/QmTjNRVt2fvaRFu93keEC7z5M1GS1iH6qZ9227htQioTUY/go-ipfs-cmds"
28
	offline "gx/ipfs/QmWM5HhdG5ZQNyHQ5XhMdGmV9CvLpFynQfGpTxN2MEM7Lc/go-ipfs-exchange-offline"
Steven Allen's avatar
Steven Allen committed
29 30
	mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
31
	cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
32
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
Jeromy's avatar
Jeromy committed
33 34
)

35
var flog = logging.Logger("cmds/files")
Jeromy's avatar
Jeromy committed
36

Michael Muré's avatar
Michael Muré committed
37
// FilesCmd is the 'ipfs files' command
Jeromy's avatar
Jeromy committed
38
var FilesCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
39
	Helptext: cmdkit.HelpText{
40
		Tagline: "Interact with unixfs files.",
Jeromy's avatar
Jeromy committed
41
		ShortDescription: `
42 43
Files is an API for manipulating IPFS objects as if they were a unix
filesystem.
Jeromy's avatar
Jeromy committed
44

45
NOTE:
46 47 48 49 50 51 52
Most of the subcommands of 'ipfs files' accept the '--flush' flag. It defaults
to true. Use caution when setting this flag to false. It will improve
performance for large numbers of file operations, but it does so at the cost
of consistency guarantees. If the daemon is unexpectedly killed before running
'ipfs files flush' on the files in question, then data may be lost. This also
applies to running 'ipfs repo gc' concurrently with '--flush=false'
operations.
Jeromy's avatar
Jeromy committed
53 54
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
55
	Options: []cmdkit.Option{
Michael Muré's avatar
Michael Muré committed
56
		cmdkit.BoolOption("f", "flush", "Flush target and ancestors after write.").WithDefault(true),
Jeromy's avatar
Jeromy committed
57
	},
Jeromy's avatar
Jeromy committed
58
	Subcommands: map[string]*cmds.Command{
Michael Muré's avatar
Michael Muré committed
59
		"read":  lgc.NewCommand(filesReadCmd),
60
		"write": filesWriteCmd,
Michael Muré's avatar
Michael Muré committed
61 62 63 64
		"mv":    lgc.NewCommand(filesMvCmd),
		"cp":    lgc.NewCommand(filesCpCmd),
		"ls":    lgc.NewCommand(filesLsCmd),
		"mkdir": lgc.NewCommand(filesMkdirCmd),
65
		"stat":  filesStatCmd,
Michael Muré's avatar
Michael Muré committed
66 67 68
		"rm":    lgc.NewCommand(filesRmCmd),
		"flush": lgc.NewCommand(filesFlushCmd),
		"chcid": lgc.NewCommand(filesChcidCmd),
Jeromy's avatar
Jeromy committed
69 70 71
	},
}

Jan Winkelmann's avatar
Jan Winkelmann committed
72 73
var cidVersionOption = cmdkit.IntOption("cid-version", "cid-ver", "Cid version to use. (experimental)")
var hashOption = cmdkit.StringOption("hash", "Hash function to use. Will set Cid version to 1 if used. (experimental)")
74

Michael Muré's avatar
Michael Muré committed
75
var errFormat = errors.New("format was set by multiple options. Only one format option is allowed")
76

Michael Muré's avatar
Michael Muré committed
77
type statOutput struct {
78 79 80 81 82
	Hash           string
	Size           uint64
	CumulativeSize uint64
	Blocks         int
	Type           string
Michael Muré's avatar
Michael Muré committed
83
	WithLocality   bool   `json:",omitempty"`
84 85 86 87
	Local          bool   `json:",omitempty"`
	SizeLocal      uint64 `json:",omitempty"`
}

keks's avatar
keks committed
88 89 90 91 92 93
const defaultStatFormat = `<hash>
Size: <size>
CumulativeSize: <cumulsize>
ChildBlocks: <childs>
Type: <type>`

94
var filesStatCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
95
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
96
		Tagline: "Display file status.",
Jeromy's avatar
Jeromy committed
97 98
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
99 100
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to node to stat."),
Jeromy's avatar
Jeromy committed
101
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
102 103
	Options: []cmdkit.Option{
		cmdkit.StringOption("format", "Print statistics in given format. Allowed tokens: "+
keks's avatar
keks committed
104
			"<hash> <size> <cumulsize> <type> <childs>. Conflicts with other format options.").WithDefault(defaultStatFormat),
105 106
		cmdkit.BoolOption("hash", "Print only hash. Implies '--format=<hash>'. Conflicts with other format options."),
		cmdkit.BoolOption("size", "Print only size. Implies '--format=<cumulsize>'. Conflicts with other format options."),
107
		cmdkit.BoolOption("with-local", "Compute the amount of the dag that is local, and if possible the total size"),
108
	},
109
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) {
110 111 112

		_, err := statGetFormatOptions(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
113
			res.SetError(err, cmdkit.ErrClient)
114 115
		}

116
		node, err := GetNode(env)
Jeromy's avatar
Jeromy committed
117
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
118
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
119 120 121
			return
		}

122
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
123
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
124
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
125 126 127
			return
		}

128 129 130 131 132 133 134 135 136 137 138 139 140 141
		withLocal, _ := req.Options["with-local"].(bool)

		var dagserv ipld.DAGService
		if withLocal {
			// an offline DAGService will not fetch from the network
			dagserv = dag.NewDAGService(bservice.New(
				node.Blockstore,
				offline.Exchange(node.Blockstore),
			))
		} else {
			dagserv = node.DAG
		}

		nd, err := getNodeFromPath(req.Context, node, dagserv, path)
Jeromy's avatar
Jeromy committed
142
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
143
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
144 145 146
			return
		}

147
		o, err := statNode(nd)
Jeromy's avatar
Jeromy committed
148
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
149
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
150 151 152
			return
		}

153
		if !withLocal {
154
			cmds.EmitOnce(res, o)
155 156 157 158 159 160 161 162 163
			return
		}

		local, sizeLocal, err := walkBlock(req.Context, dagserv, nd)

		o.WithLocality = true
		o.Local = local
		o.SizeLocal = sizeLocal

164
		cmds.EmitOnce(res, o)
Jeromy's avatar
Jeromy committed
165
	},
166 167
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
Michael Muré's avatar
Michael Muré committed
168
			out, ok := v.(*statOutput)
Jan Winkelmann's avatar
Jan Winkelmann committed
169
			if !ok {
170
				return e.TypeErr(out, v)
Jan Winkelmann's avatar
Jan Winkelmann committed
171
			}
172

173
			s, _ := statGetFormatOptions(req)
174 175 176 177 178 179
			s = strings.Replace(s, "<hash>", out.Hash, -1)
			s = strings.Replace(s, "<size>", fmt.Sprintf("%d", out.Size), -1)
			s = strings.Replace(s, "<cumulsize>", fmt.Sprintf("%d", out.CumulativeSize), -1)
			s = strings.Replace(s, "<childs>", fmt.Sprintf("%d", out.Blocks), -1)
			s = strings.Replace(s, "<type>", out.Type, -1)

180 181 182 183 184 185 186 187 188 189 190
			fmt.Fprintln(w, s)

			if out.WithLocality {
				fmt.Fprintf(w, "Local: %s of %s (%.2f%%)\n",
					humanize.Bytes(out.SizeLocal),
					humanize.Bytes(out.CumulativeSize),
					100.0*float64(out.SizeLocal)/float64(out.CumulativeSize),
				)
			}

			return nil
191
		}),
Jeromy's avatar
Jeromy committed
192
	},
Michael Muré's avatar
Michael Muré committed
193
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
194 195
}

196 197 198 199
func moreThanOne(a, b, c bool) bool {
	return a && b || b && c || a && c
}

200
func statGetFormatOptions(req *cmds.Request) (string, error) {
201

202 203 204
	hash, _ := req.Options["hash"].(bool)
	size, _ := req.Options["size"].(bool)
	format, _ := req.Options["format"].(string)
205

keks's avatar
keks committed
206
	if moreThanOne(hash, size, format != defaultStatFormat) {
Michael Muré's avatar
Michael Muré committed
207
		return "", errFormat
208 209 210
	}

	if hash {
211 212 213 214 215
		return "<hash>", nil
	} else if size {
		return "<cumulsize>", nil
	} else {
		return format, nil
216 217 218
	}
}

Michael Muré's avatar
Michael Muré committed
219
func statNode(nd ipld.Node) (*statOutput, error) {
Jeromy's avatar
Jeromy committed
220
	c := nd.Cid()
Jeromy's avatar
Jeromy committed
221 222 223 224 225 226

	cumulsize, err := nd.Size()
	if err != nil {
		return nil, err
	}

227 228 229 230 231 232 233 234
	switch n := nd.(type) {
	case *dag.ProtoNode:
		d, err := ft.FromBytes(n.Data())
		if err != nil {
			return nil, err
		}

		var ndtype string
235 236
		switch d.GetType() {
		case ft.TDirectory, ft.THAMTShard:
237
			ndtype = "directory"
238
		case ft.TFile, ft.TMetadata, ft.TRaw:
239 240
			ndtype = "file"
		default:
241
			return nil, fmt.Errorf("unrecognized node type: %s", d.GetType())
242 243
		}

Michael Muré's avatar
Michael Muré committed
244
		return &statOutput{
245 246 247 248 249 250 251
			Hash:           c.String(),
			Blocks:         len(nd.Links()),
			Size:           d.GetFilesize(),
			CumulativeSize: cumulsize,
			Type:           ndtype,
		}, nil
	case *dag.RawNode:
Michael Muré's avatar
Michael Muré committed
252
		return &statOutput{
253 254 255 256 257 258
			Hash:           c.String(),
			Blocks:         0,
			Size:           cumulsize,
			CumulativeSize: cumulsize,
			Type:           "file",
		}, nil
Jeromy's avatar
Jeromy committed
259
	default:
260
		return nil, fmt.Errorf("not unixfs node (proto or raw)")
Jeromy's avatar
Jeromy committed
261
	}
Jeromy's avatar
Jeromy committed
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
func walkBlock(ctx context.Context, dagserv ipld.DAGService, nd ipld.Node) (bool, uint64, error) {
	// Start with the block data size
	sizeLocal := uint64(len(nd.RawData()))

	local := true

	for _, link := range nd.Links() {
		child, err := dagserv.Get(ctx, link.Cid)

		if err == ipld.ErrNotFound {
			local = false
			continue
		}

		if err != nil {
			return local, sizeLocal, err
		}

		childLocal, childLocalSize, err := walkBlock(ctx, dagserv, child)

		if err != nil {
			return local, sizeLocal, err
		}

		// Recursively add the child size
		local = local && childLocal
		sizeLocal += childLocalSize
	}

	return local, sizeLocal, nil
}

Michael Muré's avatar
Michael Muré committed
296
var filesCpCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
297
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
298
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
299
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
300 301 302
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("source", true, false, "Source object to copy."),
		cmdkit.StringArg("dest", true, false, "Destination to copy object to."),
Jeromy's avatar
Jeromy committed
303
	},
Michael Muré's avatar
Michael Muré committed
304
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
305 306
		node, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
307
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
308 309 310
			return
		}

311
		flush, _, _ := req.Option("flush").Bool()
312

Jeromy's avatar
Jeromy committed
313 314
		src, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
315
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
316 317
			return
		}
318 319
		src = strings.TrimRight(src, "/")

Jeromy's avatar
Jeromy committed
320 321
		dst, err := checkPath(req.Arguments()[1])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
322
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
323 324
			return
		}
Jeromy's avatar
Jeromy committed
325

326 327 328 329
		if dst[len(dst)-1] == '/' {
			dst += gopath.Base(src)
		}

330
		nd, err := getNodeFromPath(req.Context(), node, node.DAG, src)
Jeromy's avatar
Jeromy committed
331
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
332
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
333
			return
Jeromy's avatar
Jeromy committed
334 335 336 337
		}

		err = mfs.PutNode(node.FilesRoot, dst, nd)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
338
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
339 340
			return
		}
341 342 343 344

		if flush {
			err := mfs.FlushPath(node.FilesRoot, dst)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
345
				res.SetError(err, cmdkit.ErrNormal)
346 347 348
				return
			}
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
349 350

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
351 352 353
	},
}

354
func getNodeFromPath(ctx context.Context, node *core.IpfsNode, dagservice ipld.DAGService, p string) (ipld.Node, error) {
Jeromy's avatar
Jeromy committed
355 356 357 358 359 360 361
	switch {
	case strings.HasPrefix(p, "/ipfs/"):
		np, err := path.ParsePath(p)
		if err != nil {
			return nil, err
		}

362
		resolver := &resolver.Resolver{
363
			DAG:         dagservice,
364 365 366
			ResolveOnce: uio.ResolveUnixfsOnce,
		}

367
		return core.Resolve(ctx, node.Namesys, resolver, np)
Jeromy's avatar
Jeromy committed
368 369 370 371 372 373 374 375 376 377
	default:
		fsn, err := mfs.Lookup(node.FilesRoot, p)
		if err != nil {
			return nil, err
		}

		return fsn.GetNode()
	}
}

Michael Muré's avatar
Michael Muré committed
378
type filesLsOutput struct {
Jeromy's avatar
Jeromy committed
379 380 381
	Entries []mfs.NodeListing
}

Michael Muré's avatar
Michael Muré committed
382
var filesLsCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
383
	Helptext: cmdkit.HelpText{
384
		Tagline: "List directories in the local mutable namespace.",
Jeromy's avatar
Jeromy committed
385
		ShortDescription: `
386
List directories in the local mutable namespace.
Jeromy's avatar
Jeromy committed
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402

Examples:

    $ ipfs files ls /welcome/docs/
    about
    contact
    help
    quick-start
    readme
    security-notes

    $ ipfs files ls /myfiles/a/b/c/d
    foo
    bar
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
403 404
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to show listing for. Defaults to '/'."),
Jeromy's avatar
Jeromy committed
405
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
406 407
	Options: []cmdkit.Option{
		cmdkit.BoolOption("l", "Use long listing format."),
Jeromy's avatar
Jeromy committed
408
	},
Michael Muré's avatar
Michael Muré committed
409
	Run: func(req oldcmds.Request, res oldcmds.Response) {
410 411 412 413 414 415 416 417 418
		var arg string

		if len(req.Arguments()) == 0 {
			arg = "/"
		} else {
			arg = req.Arguments()[0]
		}

		path, err := checkPath(arg)
Jeromy's avatar
Jeromy committed
419
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
420
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
421 422 423
			return
		}

Jeromy's avatar
Jeromy committed
424 425
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
426
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
427 428 429 430 431
			return
		}

		fsn, err := mfs.Lookup(nd.FilesRoot, path)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
432
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
433 434 435
			return
		}

Jeromy's avatar
Jeromy committed
436 437
		long, _, _ := req.Option("l").Bool()

Jeromy's avatar
Jeromy committed
438 439
		switch fsn := fsn.(type) {
		case *mfs.Directory:
Jeromy's avatar
Jeromy committed
440 441
			if !long {
				var output []mfs.NodeListing
Jeromy's avatar
Jeromy committed
442
				names, err := fsn.ListNames(req.Context())
443
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
444
					res.SetError(err, cmdkit.ErrNormal)
445 446 447 448
					return
				}

				for _, name := range names {
Jeromy's avatar
Jeromy committed
449
					output = append(output, mfs.NodeListing{
Jeromy's avatar
Jeromy committed
450
						Name: name,
Jeromy's avatar
Jeromy committed
451 452
					})
				}
Michael Muré's avatar
Michael Muré committed
453
				res.SetOutput(&filesLsOutput{output})
Jeromy's avatar
Jeromy committed
454
			} else {
Jeromy's avatar
Jeromy committed
455
				listing, err := fsn.List(req.Context())
Jeromy's avatar
Jeromy committed
456
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
457
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
458 459
					return
				}
Michael Muré's avatar
Michael Muré committed
460
				res.SetOutput(&filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
461 462 463
			}
			return
		case *mfs.File:
rht's avatar
rht committed
464
			_, name := gopath.Split(path)
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
			out := &filesLsOutput{[]mfs.NodeListing{mfs.NodeListing{Name: name}}}
			if long {
				out.Entries[0].Type = int(fsn.Type())

				size, err := fsn.Size()
				if err != nil {
					res.SetError(err, cmdkit.ErrNormal)
					return
				}
				out.Entries[0].Size = size

				nd, err := fsn.GetNode()
				if err != nil {
					res.SetError(err, cmdkit.ErrNormal)
					return
				}
				out.Entries[0].Hash = nd.Cid().String()
			}
Jeromy's avatar
Jeromy committed
483 484 485
			res.SetOutput(out)
			return
		default:
Jan Winkelmann's avatar
Jan Winkelmann committed
486
			res.SetError(errors.New("unrecognized type"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
487 488
		}
	},
Michael Muré's avatar
Michael Muré committed
489 490
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Text: func(res oldcmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
491 492 493 494 495
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

Michael Muré's avatar
Michael Muré committed
496
			out, ok := v.(*filesLsOutput)
Jan Winkelmann's avatar
Jan Winkelmann committed
497 498 499 500
			if !ok {
				return nil, e.TypeErr(out, v)
			}

Jeromy's avatar
Jeromy committed
501 502 503 504 505 506 507 508 509 510 511 512 513
			buf := new(bytes.Buffer)
			long, _, _ := res.Request().Option("l").Bool()

			for _, o := range out.Entries {
				if long {
					fmt.Fprintf(buf, "%s\t%s\t%d\n", o.Name, o.Hash, o.Size)
				} else {
					fmt.Fprintf(buf, "%s\n", o.Name)
				}
			}
			return buf, nil
		},
	},
Michael Muré's avatar
Michael Muré committed
514
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
515 516
}

Michael Muré's avatar
Michael Muré committed
517
var filesReadCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
518
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
519
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
520
		ShortDescription: `
521 522
Read a specified number of bytes from a file at a given offset. By default,
will read the entire file similar to unix cat.
Jeromy's avatar
Jeromy committed
523 524 525 526 527

Examples:

    $ ipfs files read /test/hello
    hello
Jeromy's avatar
Jeromy committed
528
        `,
Jeromy's avatar
Jeromy committed
529 530
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
531 532
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
533
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
534 535 536
	Options: []cmdkit.Option{
		cmdkit.IntOption("offset", "o", "Byte offset to begin reading from."),
		cmdkit.IntOption("count", "n", "Maximum number of bytes to read."),
Jeromy's avatar
Jeromy committed
537
	},
Michael Muré's avatar
Michael Muré committed
538
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
539 540
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
541
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
542 543 544
			return
		}

Jeromy's avatar
Jeromy committed
545 546
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
547
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
548 549 550
			return
		}

Jeromy's avatar
Jeromy committed
551 552
		fsn, err := mfs.Lookup(n.FilesRoot, path)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
553
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
554 555 556 557 558
			return
		}

		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
559
			res.SetError(fmt.Errorf("%s was not a file", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
560 561 562
			return
		}

563 564
		rfd, err := fi.Open(mfs.OpenReadOnly, false)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
565
			res.SetError(err, cmdkit.ErrNormal)
566 567 568 569 570
			return
		}

		defer rfd.Close()

Jeromy's avatar
Jeromy committed
571 572
		offset, _, err := req.Option("offset").Int()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
573
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
574 575 576
			return
		}
		if offset < 0 {
Michael Muré's avatar
Michael Muré committed
577
			res.SetError(fmt.Errorf("cannot specify negative offset"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
578 579 580
			return
		}

581
		filen, err := rfd.Size()
Jeromy's avatar
Jeromy committed
582
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
583
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
584 585 586 587
			return
		}

		if int64(offset) > filen {
Michael Muré's avatar
Michael Muré committed
588
			res.SetError(fmt.Errorf("offset was past end of file (%d > %d)", offset, filen), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
589 590
			return
		}
Jeromy's avatar
Jeromy committed
591

592
		_, err = rfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
593
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
594
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
595 596
			return
		}
597 598

		var r io.Reader = &contextReaderWrapper{R: rfd, ctx: req.Context()}
Jeromy's avatar
Jeromy committed
599
		count, found, err := req.Option("count").Int()
Jeromy's avatar
Jeromy committed
600
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
601
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
602 603 604 605
			return
		}
		if found {
			if count < 0 {
Michael Muré's avatar
Michael Muré committed
606
				res.SetError(fmt.Errorf("cannot specify negative 'count'"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
607 608
				return
			}
609
			r = io.LimitReader(r, int64(count))
Jeromy's avatar
Jeromy committed
610 611 612 613 614 615
		}

		res.SetOutput(r)
	},
}

Jeromy's avatar
Jeromy committed
616 617 618 619 620 621 622 623 624 625 626 627 628
type contextReader interface {
	CtxReadFull(context.Context, []byte) (int, error)
}

type contextReaderWrapper struct {
	R   contextReader
	ctx context.Context
}

func (crw *contextReaderWrapper) Read(b []byte) (int, error) {
	return crw.R.CtxReadFull(crw.ctx, b)
}

Michael Muré's avatar
Michael Muré committed
629
var filesMvCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
630
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
631
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
632 633 634 635 636 637 638
		ShortDescription: `
Move files around. Just like traditional unix mv.

Example:

    $ ipfs files mv /myfs/a/b/c /myfs/foo/newc

Jeromy's avatar
Jeromy committed
639
`,
Jeromy's avatar
Jeromy committed
640 641
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
642 643 644
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("source", true, false, "Source file to move."),
		cmdkit.StringArg("dest", true, false, "Destination path for file to be moved to."),
Jeromy's avatar
Jeromy committed
645
	},
Michael Muré's avatar
Michael Muré committed
646
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
647 648
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
649
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
650 651 652
			return
		}

Jeromy's avatar
Jeromy committed
653 654
		src, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
655
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
656 657 658 659
			return
		}
		dst, err := checkPath(req.Arguments()[1])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
660
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
661 662
			return
		}
Jeromy's avatar
Jeromy committed
663 664 665

		err = mfs.Mv(n.FilesRoot, src, dst)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
666
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
667 668
			return
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
669 670

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
671 672 673
	},
}

674
var filesWriteCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
675
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
676
		Tagline: "Write to a mutable file in a given filesystem.",
Jeromy's avatar
Jeromy committed
677 678
		ShortDescription: `
Write data to a file in a given filesystem. This command allows you to specify
679 680
a beginning offset to write to. The entire length of the input will be
written.
Jeromy's avatar
Jeromy committed
681

Jeromy's avatar
Jeromy committed
682
If the '--create' option is specified, the file will be created if it does not
Jeromy's avatar
Jeromy committed
683 684
exist. Nonexistant intermediate directories will not be created.

685 686 687 688 689 690 691
Newly created files will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

Newly created leaves will be in the legacy format (Protobuf) if the
CID version is 0, or raw is the CID version is non-zero.  Use of the
--raw-leaves option will override this behavior.

692 693 694 695
If the '--flush' option is set to false, changes will not be propogated to the
merkledag root. This can make operations much faster when doing a large number
of writes to a deeper directory structure.

696
EXAMPLE:
Jeromy's avatar
Jeromy committed
697

Jeromy's avatar
Jeromy committed
698 699
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
700

701
WARNING:
702

703 704 705
Usage of the '--flush=false' option does not guarantee data durability until
the tree has been flushed. This can be accomplished by running 'ipfs files
stat' on the file or any of its ancestors.
Jeromy's avatar
Jeromy committed
706
`,
Jeromy's avatar
Jeromy committed
707
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
708 709 710
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to write to."),
		cmdkit.FileArg("data", true, false, "Data to write.").EnableStdin(),
Jeromy's avatar
Jeromy committed
711
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
712 713 714 715 716 717
	Options: []cmdkit.Option{
		cmdkit.IntOption("offset", "o", "Byte offset to begin writing at."),
		cmdkit.BoolOption("create", "e", "Create the file if it does not exist."),
		cmdkit.BoolOption("truncate", "t", "Truncate the file to size zero before writing."),
		cmdkit.IntOption("count", "n", "Maximum number of bytes to read."),
		cmdkit.BoolOption("raw-leaves", "Use raw blocks for newly created leaf nodes. (experimental)"),
718 719
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
720
	},
721 722
	Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) {
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
723
		if err != nil {
724
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
725 726 727
			return
		}

728 729 730 731
		create, _ := req.Options["create"].(bool)
		trunc, _ := req.Options["truncate"].(bool)
		flush, _ := req.Options["flush"].(bool)
		rawLeaves, rawLeavesDef := req.Options["raw-leaves"].(bool)
732

733
		prefix, err := getPrefixNew(req)
734
		if err != nil {
735
			re.SetError(err, cmdkit.ErrNormal)
736 737
			return
		}
Jeromy's avatar
Jeromy committed
738

739
		nd, err := GetNode(env)
Jeromy's avatar
Jeromy committed
740
		if err != nil {
741
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
742 743 744
			return
		}

745
		offset, _ := req.Options["offset"].(int)
Jeromy's avatar
Jeromy committed
746
		if offset < 0 {
747
			re.SetError(fmt.Errorf("cannot have negative write offset"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
748 749 750
			return
		}

751
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
752
		if err != nil {
753
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
754 755
			return
		}
756 757 758
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
759

760 761
		wfd, err := fi.Open(mfs.OpenWriteOnly, flush)
		if err != nil {
762
			re.SetError(err, cmdkit.ErrNormal)
763
			return
764
		}
Jeromy's avatar
Jeromy committed
765

766 767 768
		defer func() {
			err := wfd.Close()
			if err != nil {
769
				re.SetError(err, cmdkit.ErrNormal)
770 771
			}
		}()
772

Jeromy's avatar
Jeromy committed
773
		if trunc {
774
			if err := wfd.Truncate(0); err != nil {
775
				re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
776 777 778 779
				return
			}
		}

780
		count, countfound := req.Options["count"].(int)
Jeromy's avatar
Jeromy committed
781
		if countfound && count < 0 {
782
			re.SetError(fmt.Errorf("cannot have negative byte count"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
783 784
			return
		}
Jeromy's avatar
Jeromy committed
785

786
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
787
		if err != nil {
788
			flog.Error("seekfail: ", err)
789
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
790 791 792
			return
		}

793
		input, err := req.Files.NextFile()
Jeromy's avatar
Jeromy committed
794
		if err != nil {
795
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
796 797 798
			return
		}

zramsay's avatar
zramsay committed
799 800 801 802 803
		var r io.Reader = input
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
804
		_, err = io.Copy(wfd, r)
Jeromy's avatar
Jeromy committed
805
		if err != nil {
806
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
807 808 809 810 811
			return
		}
	},
}

Michael Muré's avatar
Michael Muré committed
812
var filesMkdirCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
813
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
814
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
815 816 817
		ShortDescription: `
Create the directory if it does not already exist.

818 819 820
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

821
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
822 823 824

Examples:

825 826
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
827 828 829
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
830 831
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to dir to make."),
Jeromy's avatar
Jeromy committed
832
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
833 834
	Options: []cmdkit.Option{
		cmdkit.BoolOption("parents", "p", "No error if existing, make parent directories as needed."),
835 836
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
837
	},
Michael Muré's avatar
Michael Muré committed
838
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
839 840
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
841
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
842 843 844 845
			return
		}

		dashp, _, _ := req.Option("parents").Bool()
Jeromy's avatar
Jeromy committed
846 847
		dirtomake, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
848
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
849 850 851
			return
		}

852
		flush, _, _ := req.Option("flush").Bool()
Jeromy's avatar
Jeromy committed
853

854 855
		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
856
			res.SetError(err, cmdkit.ErrNormal)
857 858 859 860
			return
		}
		root := n.FilesRoot

861 862 863 864 865
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
			Mkparents: dashp,
			Flush:     flush,
			Prefix:    prefix,
		})
866
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
867
			res.SetError(err, cmdkit.ErrNormal)
868
			return
Jeromy's avatar
Jeromy committed
869
		}
keks's avatar
keks committed
870

Jan Winkelmann's avatar
Jan Winkelmann committed
871
		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
872 873 874
	},
}

Michael Muré's avatar
Michael Muré committed
875
var filesFlushCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
876
	Helptext: cmdkit.HelpText{
877
		Tagline: "Flush a given path's data to disk.",
Jeromy's avatar
Jeromy committed
878
		ShortDescription: `
879
Flush a given path to disk. This is only useful when other commands
Jeromy's avatar
Jeromy committed
880 881 882
are run with the '--flush=false'.
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
883 884
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to flush. Default: '/'."),
Jeromy's avatar
Jeromy committed
885
	},
Michael Muré's avatar
Michael Muré committed
886
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
887 888
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
889
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
890 891 892 893 894 895 896 897 898 899
			return
		}

		path := "/"
		if len(req.Arguments()) > 0 {
			path = req.Arguments()[0]
		}

		err = mfs.FlushPath(nd.FilesRoot, path)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
900
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
901 902
			return
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
903 904

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
905 906 907
	},
}

Michael Muré's avatar
Michael Muré committed
908
var filesChcidCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
909
	Helptext: cmdkit.HelpText{
910
		Tagline: "Change the cid version or hash function of the root node of a given path.",
Kevin Atkinson's avatar
Kevin Atkinson committed
911
		ShortDescription: `
912
Change the cid version or hash function of the root node of a given path.
Kevin Atkinson's avatar
Kevin Atkinson committed
913 914
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
915 916
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to change. Default: '/'."),
Kevin Atkinson's avatar
Kevin Atkinson committed
917
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
918
	Options: []cmdkit.Option{
919 920 921
		cidVersionOption,
		hashOption,
	},
Michael Muré's avatar
Michael Muré committed
922
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Kevin Atkinson's avatar
Kevin Atkinson committed
923 924
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
925
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
926 927 928 929 930 931 932 933 934 935 936 937
			return
		}

		path := "/"
		if len(req.Arguments()) > 0 {
			path = req.Arguments()[0]
		}

		flush, _, _ := req.Option("flush").Bool()

		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
938
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
939 940 941 942 943
			return
		}

		err = updatePath(nd.FilesRoot, path, prefix, flush)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
944
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
945 946
			return
		}
keks's avatar
keks committed
947 948

		res.SetOutput(nil)
Kevin Atkinson's avatar
Kevin Atkinson committed
949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975
	},
}

func updatePath(rt *mfs.Root, pth string, prefix *cid.Prefix, flush bool) error {
	if prefix == nil {
		return nil
	}

	nd, err := mfs.Lookup(rt, pth)
	if err != nil {
		return err
	}

	switch n := nd.(type) {
	case *mfs.Directory:
		n.SetPrefix(prefix)
	default:
		return fmt.Errorf("can only update directories")
	}

	if flush {
		nd.Flush()
	}

	return nil
}

Michael Muré's avatar
Michael Muré committed
976
var filesRmCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
977
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
978
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
979
		ShortDescription: `
980
Remove files or directories.
Jeromy's avatar
Jeromy committed
981 982 983 984 985 986 987 988

    $ ipfs files rm /foo
    $ ipfs files ls /bar
    cat
    dog
    fish
    $ ipfs files rm -r /bar
`,
Jeromy's avatar
Jeromy committed
989 990
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
991 992
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
993
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
994 995
	Options: []cmdkit.Option{
		cmdkit.BoolOption("recursive", "r", "Recursively remove directories."),
Jeromy's avatar
Jeromy committed
996
	},
Michael Muré's avatar
Michael Muré committed
997
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jan Winkelmann's avatar
Jan Winkelmann committed
998 999
		defer res.SetOutput(nil)

Jeromy's avatar
Jeromy committed
1000 1001
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1002
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1003 1004 1005
			return
		}

Jeromy's avatar
Jeromy committed
1006 1007
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1008
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1009 1010 1011 1012
			return
		}

		if path == "/" {
Jan Winkelmann's avatar
Jan Winkelmann committed
1013
			res.SetError(fmt.Errorf("cannot delete root"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1014 1015 1016 1017 1018 1019 1020 1021
			return
		}

		// 'rm a/b/c/' will fail unless we trim the slash at the end
		if path[len(path)-1] == '/' {
			path = path[:len(path)-1]
		}

Jeromy's avatar
Jeromy committed
1022 1023 1024
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1025
			res.SetError(fmt.Errorf("parent lookup: %s", err), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1026 1027 1028 1029 1030
			return
		}

		pdir, ok := parent.(*mfs.Directory)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1031
			res.SetError(fmt.Errorf("no such file or directory: %s", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1032 1033 1034
			return
		}

Jeromy's avatar
Jeromy committed
1035 1036
		dashr, _, _ := req.Option("r").Bool()

Jeromy's avatar
Jeromy committed
1037 1038 1039 1040 1041
		var success bool
		defer func() {
			if success {
				err := pdir.Flush()
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1042
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1043 1044 1045 1046 1047
					return
				}
			}
		}()

Jeromy's avatar
Jeromy committed
1048 1049 1050 1051
		// if '-r' specified, don't check file type (in bad scenarios, the block may not exist)
		if dashr {
			err := pdir.Unlink(name)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1052
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1053 1054 1055
				return
			}

Jeromy's avatar
Jeromy committed
1056
			success = true
Jeromy's avatar
Jeromy committed
1057 1058 1059
			return
		}

Jeromy's avatar
Jeromy committed
1060 1061
		childi, err := pdir.Child(name)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1062
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1063 1064 1065 1066 1067
			return
		}

		switch childi.(type) {
		case *mfs.Directory:
Jan Winkelmann's avatar
Jan Winkelmann committed
1068
			res.SetError(fmt.Errorf("%s is a directory, use -r to remove directories", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1069
			return
Jeromy's avatar
Jeromy committed
1070 1071 1072
		default:
			err := pdir.Unlink(name)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1073
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1074 1075
				return
			}
Jeromy's avatar
Jeromy committed
1076 1077

			success = true
Jeromy's avatar
Jeromy committed
1078 1079 1080 1081
		}
	},
}

1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110
func getPrefixNew(req *cmds.Request) (*cid.Prefix, error) {
	cidVer, cidVerSet := req.Options["cid-version"].(int)
	hashFunStr, hashFunSet := req.Options["hash"].(string)

	if !cidVerSet && !hashFunSet {
		return nil, nil
	}

	if hashFunSet && cidVer == 0 {
		cidVer = 1
	}

	prefix, err := dag.PrefixForCidVersion(cidVer)
	if err != nil {
		return nil, err
	}

	if hashFunSet {
		hashFunCode, ok := mh.Names[strings.ToLower(hashFunStr)]
		if !ok {
			return nil, fmt.Errorf("unrecognized hash function: %s", strings.ToLower(hashFunStr))
		}
		prefix.MhType = hashFunCode
		prefix.MhLength = -1
	}

	return &prefix, nil
}

Michael Muré's avatar
Michael Muré committed
1111
func getPrefix(req oldcmds.Request) (*cid.Prefix, error) {
1112
	cidVer, cidVerSet, _ := req.Option("cid-version").Int()
1113
	hashFunStr, hashFunSet, _ := req.Option("hash").String()
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126

	if !cidVerSet && !hashFunSet {
		return nil, nil
	}

	if hashFunSet && cidVer == 0 {
		cidVer = 1
	}

	prefix, err := dag.PrefixForCidVersion(cidVer)
	if err != nil {
		return nil, err
	}
Jeromy's avatar
Jeromy committed
1127

1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
	if hashFunSet {
		hashFunCode, ok := mh.Names[strings.ToLower(hashFunStr)]
		if !ok {
			return nil, fmt.Errorf("unrecognized hash function: %s", strings.ToLower(hashFunStr))
		}
		prefix.MhType = hashFunCode
		prefix.MhLength = -1
	}

	return &prefix, nil
}

func getFileHandle(r *mfs.Root, path string, create bool, prefix *cid.Prefix) (*mfs.File, error) {
Jeromy's avatar
Jeromy committed
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
	target, err := mfs.Lookup(r, path)
	switch err {
	case nil:
		fi, ok := target.(*mfs.File)
		if !ok {
			return nil, fmt.Errorf("%s was not a file", path)
		}
		return fi, nil

	case os.ErrNotExist:
		if !create {
			return nil, err
		}

		// if create is specified and the file doesnt exist, we create the file
		dirname, fname := gopath.Split(path)
		pdiri, err := mfs.Lookup(r, dirname)
		if err != nil {
1159
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1160 1161 1162 1163 1164 1165
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1166 1167 1168
		if prefix == nil {
			prefix = pdir.GetPrefix()
		}
Jeromy's avatar
Jeromy committed
1169

1170
		nd := dag.NodeWithData(ft.FilePBData(nil, 0))
1171
		nd.SetPrefix(prefix)
Jeromy's avatar
Jeromy committed
1172 1173 1174 1175 1176 1177 1178 1179 1180 1181
		err = pdir.AddChild(fname, nd)
		if err != nil {
			return nil, err
		}

		fsn, err := pdir.Child(fname)
		if err != nil {
			return nil, err
		}

Jeromy's avatar
Jeromy committed
1182 1183
		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1184
			return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
Jeromy's avatar
Jeromy committed
1185 1186
		}
		return fi, nil
Jeromy's avatar
Jeromy committed
1187 1188 1189 1190 1191

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1192 1193 1194

func checkPath(p string) (string, error) {
	if len(p) == 0 {
Michael Muré's avatar
Michael Muré committed
1195
		return "", fmt.Errorf("paths must not be empty")
Jeromy's avatar
Jeromy committed
1196 1197 1198
	}

	if p[0] != '/' {
Michael Muré's avatar
Michael Muré committed
1199
		return "", fmt.Errorf("paths must start with a leading slash")
Jeromy's avatar
Jeromy committed
1200 1201 1202 1203 1204 1205 1206 1207
	}

	cleaned := gopath.Clean(p)
	if p[len(p)-1] == '/' && p != "/" {
		cleaned += "/"
	}
	return cleaned, nil
}