files.go 26.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"
18
	"github.com/ipfs/go-ipfs/exchange/offline"
Jeromy's avatar
Jeromy committed
19 20 21 22
	dag "github.com/ipfs/go-ipfs/merkledag"
	mfs "github.com/ipfs/go-ipfs/mfs"
	path "github.com/ipfs/go-ipfs/path"
	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"
Michael Muré's avatar
Michael Muré committed
26
	cmds "gx/ipfs/QmZ9hww8R3FKrDRCYPxhN13m6XgjPDpaSvdUfisPvERzXz/go-ipfs-cmds"
Steven Allen's avatar
Steven Allen committed
27
	logging "gx/ipfs/QmRb5jh8z2E8hMGN2tkvs1yHynUanqnZ3UeKwgN1i9P1F8/go-log"
Steven Allen's avatar
Steven Allen committed
28 29
	mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
30
	cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
31
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
Jeromy's avatar
Jeromy committed
32 33
)

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

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

44
NOTE:
45 46 47 48 49 50 51
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
52 53
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
54
	Options: []cmdkit.Option{
Michael Muré's avatar
Michael Muré committed
55
		cmdkit.BoolOption(	"f", "flush", "Flush target and ancestors after write.").WithDefault(true),
Jeromy's avatar
Jeromy committed
56
	},
Jeromy's avatar
Jeromy committed
57
	Subcommands: map[string]*cmds.Command{
Michael Muré's avatar
Michael Muré committed
58 59 60 61 62 63
		"read":  lgc.NewCommand(filesReadCmd),
		"write": lgc.NewCommand(filesWriteCmd),
		"mv":    lgc.NewCommand(filesMvCmd),
		"cp":    lgc.NewCommand(filesCpCmd),
		"ls":    lgc.NewCommand(filesLsCmd),
		"mkdir": lgc.NewCommand(filesMkdirCmd),
64
		"stat":  filesStatCmd,
Michael Muré's avatar
Michael Muré committed
65 66 67
		"rm":    lgc.NewCommand(filesRmCmd),
		"flush": lgc.NewCommand(filesFlushCmd),
		"chcid": lgc.NewCommand(filesChcidCmd),
Jeromy's avatar
Jeromy committed
68 69 70
	},
}

Jan Winkelmann's avatar
Jan Winkelmann committed
71 72
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)")
73

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

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

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

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

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

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

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

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

127
		nd, err := getNodeFromPath(req.Context, node, path)
Jeromy's avatar
Jeromy committed
128
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
129
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
130 131 132
			return
		}

133
		o, err := statNode(nd)
Jeromy's avatar
Jeromy committed
134
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
135
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
136 137 138
			return
		}

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
		withLocal, _ := req.Options["with-local"].(bool)
		if !withLocal {
			cmds.EmitOnce(res, o)
			return
		}

		// an offline DAGService will not fetch from the network
		dagserv := dag.NewDAGService(bservice.New(
			node.Blockstore,
			offline.Exchange(node.Blockstore),
		))

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

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

157
		cmds.EmitOnce(res, o)
Jeromy's avatar
Jeromy committed
158
	},
159 160
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
Michael Muré's avatar
Michael Muré committed
161
			out, ok := v.(*statOutput)
Jan Winkelmann's avatar
Jan Winkelmann committed
162
			if !ok {
163
				return e.TypeErr(out, v)
Jan Winkelmann's avatar
Jan Winkelmann committed
164
			}
165

166
			s, _ := statGetFormatOptions(req)
167 168 169 170 171 172
			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)

173 174 175 176 177 178 179 180 181 182 183
			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
184
		}),
Jeromy's avatar
Jeromy committed
185
	},
Michael Muré's avatar
Michael Muré committed
186
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
187 188
}

189 190 191 192
func moreThanOne(a, b, c bool) bool {
	return a && b || b && c || a && c
}

193
func statGetFormatOptions(req *cmds.Request) (string, error) {
194

195 196 197
	hash, _ := req.Options["hash"].(bool)
	size, _ := req.Options["size"].(bool)
	format, _ := req.Options["format"].(string)
198

keks's avatar
keks committed
199
	if moreThanOne(hash, size, format != defaultStatFormat) {
Michael Muré's avatar
Michael Muré committed
200
		return "", errFormat
201 202 203
	}

	if hash {
204 205 206 207 208
		return "<hash>", nil
	} else if size {
		return "<cumulsize>", nil
	} else {
		return format, nil
209 210 211
	}
}

Michael Muré's avatar
Michael Muré committed
212
func statNode(nd ipld.Node) (*statOutput, error) {
Jeromy's avatar
Jeromy committed
213
	c := nd.Cid()
Jeromy's avatar
Jeromy committed
214 215 216 217 218 219

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

220 221 222 223 224 225 226 227
	switch n := nd.(type) {
	case *dag.ProtoNode:
		d, err := ft.FromBytes(n.Data())
		if err != nil {
			return nil, err
		}

		var ndtype string
228 229
		switch d.GetType() {
		case ft.TDirectory, ft.THAMTShard:
230
			ndtype = "directory"
231
		case ft.TFile, ft.TMetadata, ft.TRaw:
232 233
			ndtype = "file"
		default:
234
			return nil, fmt.Errorf("unrecognized node type: %s", d.GetType())
235 236
		}

Michael Muré's avatar
Michael Muré committed
237
		return &statOutput{
238 239 240 241 242 243 244
			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
245
		return &statOutput{
246 247 248 249 250 251
			Hash:           c.String(),
			Blocks:         0,
			Size:           cumulsize,
			CumulativeSize: cumulsize,
			Type:           "file",
		}, nil
Jeromy's avatar
Jeromy committed
252
	default:
253
		return nil, fmt.Errorf("not unixfs node (proto or raw)")
Jeromy's avatar
Jeromy committed
254
	}
Jeromy's avatar
Jeromy committed
255 256
}

257 258 259 260 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
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
289
var filesCpCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
290
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
291
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
292
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
293 294 295
	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
296
	},
Michael Muré's avatar
Michael Muré committed
297
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
298 299
		node, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
300
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
301 302 303
			return
		}

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

Jeromy's avatar
Jeromy committed
306 307
		src, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
308
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
309 310
			return
		}
311 312
		src = strings.TrimRight(src, "/")

Jeromy's avatar
Jeromy committed
313 314
		dst, err := checkPath(req.Arguments()[1])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
315
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
316 317
			return
		}
Jeromy's avatar
Jeromy committed
318

319 320 321 322
		if dst[len(dst)-1] == '/' {
			dst += gopath.Base(src)
		}

Jeromy's avatar
Jeromy committed
323 324
		nd, err := getNodeFromPath(req.Context(), node, src)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
325
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
326
			return
Jeromy's avatar
Jeromy committed
327 328 329 330
		}

		err = mfs.PutNode(node.FilesRoot, dst, nd)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
331
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
332 333
			return
		}
334 335 336 337

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

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
344 345 346
	},
}

347
func getNodeFromPath(ctx context.Context, node *core.IpfsNode, p string) (ipld.Node, error) {
Jeromy's avatar
Jeromy committed
348 349 350 351 352 353 354
	switch {
	case strings.HasPrefix(p, "/ipfs/"):
		np, err := path.ParsePath(p)
		if err != nil {
			return nil, err
		}

355 356 357 358 359
		resolver := &path.Resolver{
			DAG:         node.DAG,
			ResolveOnce: uio.ResolveUnixfsOnce,
		}

360
		return core.Resolve(ctx, node.Namesys, resolver, np)
Jeromy's avatar
Jeromy committed
361 362 363 364 365 366 367 368 369 370
	default:
		fsn, err := mfs.Lookup(node.FilesRoot, p)
		if err != nil {
			return nil, err
		}

		return fsn.GetNode()
	}
}

Michael Muré's avatar
Michael Muré committed
371
type filesLsOutput struct {
Jeromy's avatar
Jeromy committed
372 373 374
	Entries []mfs.NodeListing
}

Michael Muré's avatar
Michael Muré committed
375
var filesLsCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
376
	Helptext: cmdkit.HelpText{
377
		Tagline: "List directories in the local mutable namespace.",
Jeromy's avatar
Jeromy committed
378
		ShortDescription: `
379
List directories in the local mutable namespace.
Jeromy's avatar
Jeromy committed
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395

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
396 397
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to show listing for. Defaults to '/'."),
Jeromy's avatar
Jeromy committed
398
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
399 400
	Options: []cmdkit.Option{
		cmdkit.BoolOption("l", "Use long listing format."),
Jeromy's avatar
Jeromy committed
401
	},
Michael Muré's avatar
Michael Muré committed
402
	Run: func(req oldcmds.Request, res oldcmds.Response) {
403 404 405 406 407 408 409 410 411
		var arg string

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

		path, err := checkPath(arg)
Jeromy's avatar
Jeromy committed
412
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
413
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
414 415 416
			return
		}

Jeromy's avatar
Jeromy committed
417 418
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
419
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
420 421 422 423 424
			return
		}

		fsn, err := mfs.Lookup(nd.FilesRoot, path)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
425
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
426 427 428
			return
		}

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

Jeromy's avatar
Jeromy committed
431 432
		switch fsn := fsn.(type) {
		case *mfs.Directory:
Jeromy's avatar
Jeromy committed
433 434
			if !long {
				var output []mfs.NodeListing
Jeromy's avatar
Jeromy committed
435
				names, err := fsn.ListNames(req.Context())
436
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
437
					res.SetError(err, cmdkit.ErrNormal)
438 439 440 441
					return
				}

				for _, name := range names {
Jeromy's avatar
Jeromy committed
442
					output = append(output, mfs.NodeListing{
Jeromy's avatar
Jeromy committed
443
						Name: name,
Jeromy's avatar
Jeromy committed
444 445
					})
				}
Michael Muré's avatar
Michael Muré committed
446
				res.SetOutput(&filesLsOutput{output})
Jeromy's avatar
Jeromy committed
447
			} else {
Jeromy's avatar
Jeromy committed
448
				listing, err := fsn.List(req.Context())
Jeromy's avatar
Jeromy committed
449
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
450
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
451 452
					return
				}
Michael Muré's avatar
Michael Muré committed
453
				res.SetOutput(&filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
454 455 456
			}
			return
		case *mfs.File:
rht's avatar
rht committed
457
			_, name := gopath.Split(path)
Michael Muré's avatar
Michael Muré committed
458
			out := &filesLsOutput{[]mfs.NodeListing{mfs.NodeListing{Name: name, Type: 1}}}
Jeromy's avatar
Jeromy committed
459 460 461
			res.SetOutput(out)
			return
		default:
Jan Winkelmann's avatar
Jan Winkelmann committed
462
			res.SetError(errors.New("unrecognized type"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
463 464
		}
	},
Michael Muré's avatar
Michael Muré committed
465 466
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Text: func(res oldcmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
467 468 469 470 471
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

Michael Muré's avatar
Michael Muré committed
472
			out, ok := v.(*filesLsOutput)
Jan Winkelmann's avatar
Jan Winkelmann committed
473 474 475 476
			if !ok {
				return nil, e.TypeErr(out, v)
			}

Jeromy's avatar
Jeromy committed
477 478 479 480 481 482 483 484 485 486 487 488 489
			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
490
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
491 492
}

Michael Muré's avatar
Michael Muré committed
493
var filesReadCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
494
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
495
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
496
		ShortDescription: `
497 498
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
499 500 501 502 503

Examples:

    $ ipfs files read /test/hello
    hello
Jeromy's avatar
Jeromy committed
504
        `,
Jeromy's avatar
Jeromy committed
505 506
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
507 508
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
509
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
510 511 512
	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
513
	},
Michael Muré's avatar
Michael Muré committed
514
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
515 516
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
517
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
518 519 520
			return
		}

Jeromy's avatar
Jeromy committed
521 522
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
523
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
524 525 526
			return
		}

Jeromy's avatar
Jeromy committed
527 528
		fsn, err := mfs.Lookup(n.FilesRoot, path)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
529
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
530 531 532 533 534
			return
		}

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

539 540
		rfd, err := fi.Open(mfs.OpenReadOnly, false)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
541
			res.SetError(err, cmdkit.ErrNormal)
542 543 544 545 546
			return
		}

		defer rfd.Close()

Jeromy's avatar
Jeromy committed
547 548
		offset, _, err := req.Option("offset").Int()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
549
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
550 551 552
			return
		}
		if offset < 0 {
Michael Muré's avatar
Michael Muré committed
553
			res.SetError(fmt.Errorf("cannot specify negative offset"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
554 555 556
			return
		}

557
		filen, err := rfd.Size()
Jeromy's avatar
Jeromy committed
558
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
559
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
560 561 562 563
			return
		}

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

568
		_, err = rfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
569
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
570
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
571 572
			return
		}
573 574

		var r io.Reader = &contextReaderWrapper{R: rfd, ctx: req.Context()}
Jeromy's avatar
Jeromy committed
575
		count, found, err := req.Option("count").Int()
Jeromy's avatar
Jeromy committed
576
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
577
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
578 579 580 581
			return
		}
		if found {
			if count < 0 {
Michael Muré's avatar
Michael Muré committed
582
				res.SetError(fmt.Errorf("cannot specify negative 'count'"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
583 584
				return
			}
585
			r = io.LimitReader(r, int64(count))
Jeromy's avatar
Jeromy committed
586 587 588 589 590 591
		}

		res.SetOutput(r)
	},
}

Jeromy's avatar
Jeromy committed
592 593 594 595 596 597 598 599 600 601 602 603 604
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
605
var filesMvCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
606
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
607
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
608 609 610 611 612 613 614
		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
615
`,
Jeromy's avatar
Jeromy committed
616 617
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
618 619 620
	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
621
	},
Michael Muré's avatar
Michael Muré committed
622
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
623 624
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
625
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
626 627 628
			return
		}

Jeromy's avatar
Jeromy committed
629 630
		src, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
631
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
632 633 634 635
			return
		}
		dst, err := checkPath(req.Arguments()[1])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
636
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
637 638
			return
		}
Jeromy's avatar
Jeromy committed
639 640 641

		err = mfs.Mv(n.FilesRoot, src, dst)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
642
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
643 644
			return
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
645 646

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
647 648 649
	},
}

Michael Muré's avatar
Michael Muré committed
650
var filesWriteCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
651
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
652
		Tagline: "Write to a mutable file in a given filesystem.",
Jeromy's avatar
Jeromy committed
653 654
		ShortDescription: `
Write data to a file in a given filesystem. This command allows you to specify
655 656
a beginning offset to write to. The entire length of the input will be
written.
Jeromy's avatar
Jeromy committed
657

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

661 662 663 664 665 666 667
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.

668 669 670 671
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.

672
EXAMPLE:
Jeromy's avatar
Jeromy committed
673

Jeromy's avatar
Jeromy committed
674 675
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
676

677
WARNING:
678

679 680 681
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
682
`,
Jeromy's avatar
Jeromy committed
683
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
684 685 686
	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
687
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
688 689 690 691 692 693
	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)"),
694 695
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
696
	},
Michael Muré's avatar
Michael Muré committed
697
	Run: func(req oldcmds.Request, res oldcmds.Response) {
keks's avatar
keks committed
698
		path, err := checkPath(req.StringArguments()[0])
Jeromy's avatar
Jeromy committed
699
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
700
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
701 702 703
			return
		}

Jeromy's avatar
Jeromy committed
704 705
		create, _, _ := req.Option("create").Bool()
		trunc, _, _ := req.Option("truncate").Bool()
706
		flush, _, _ := req.Option("flush").Bool()
707 708 709 710
		rawLeaves, rawLeavesDef, _ := req.Option("raw-leaves").Bool()

		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
711
			res.SetError(err, cmdkit.ErrNormal)
712 713
			return
		}
Jeromy's avatar
Jeromy committed
714 715 716

		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
717
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
718 719 720
			return
		}

Jeromy's avatar
Jeromy committed
721 722
		offset, _, err := req.Option("offset").Int()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
723
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
724 725 726
			return
		}
		if offset < 0 {
Jan Winkelmann's avatar
Jan Winkelmann committed
727
			res.SetError(fmt.Errorf("cannot have negative write offset"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
728 729 730
			return
		}

731
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
732
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
733
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
734 735
			return
		}
736 737 738
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
739

740 741
		wfd, err := fi.Open(mfs.OpenWriteOnly, flush)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
742
			res.SetError(err, cmdkit.ErrNormal)
743
			return
744
		}
Jeromy's avatar
Jeromy committed
745

746 747 748
		defer func() {
			err := wfd.Close()
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
749
				res.SetError(err, cmdkit.ErrNormal)
750 751
			}
		}()
752

Jeromy's avatar
Jeromy committed
753
		if trunc {
754
			if err := wfd.Truncate(0); err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
755
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
756 757 758 759
				return
			}
		}

Jeromy's avatar
Jeromy committed
760 761
		count, countfound, err := req.Option("count").Int()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
762
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
763 764 765
			return
		}
		if countfound && count < 0 {
Jan Winkelmann's avatar
Jan Winkelmann committed
766
			res.SetError(fmt.Errorf("cannot have negative byte count"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
767 768
			return
		}
Jeromy's avatar
Jeromy committed
769

770
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
771
		if err != nil {
772
			flog.Error("seekfail: ", err)
Jan Winkelmann's avatar
Jan Winkelmann committed
773
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
774 775 776 777 778
			return
		}

		input, err := req.Files().NextFile()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
779
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
780 781 782
			return
		}

zramsay's avatar
zramsay committed
783 784 785 786 787
		var r io.Reader = input
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
788
		_, err = io.Copy(wfd, r)
Jeromy's avatar
Jeromy committed
789
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
790
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
791 792 793
			return
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
794
		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
795 796 797
	},
}

Michael Muré's avatar
Michael Muré committed
798
var filesMkdirCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
799
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
800
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
801 802 803
		ShortDescription: `
Create the directory if it does not already exist.

804 805 806
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

807
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
808 809 810

Examples:

811 812
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
813 814 815
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
816 817
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to dir to make."),
Jeromy's avatar
Jeromy committed
818
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
819 820
	Options: []cmdkit.Option{
		cmdkit.BoolOption("parents", "p", "No error if existing, make parent directories as needed."),
821 822
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
823
	},
Michael Muré's avatar
Michael Muré committed
824
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
825 826
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
827
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
828 829 830 831
			return
		}

		dashp, _, _ := req.Option("parents").Bool()
Jeromy's avatar
Jeromy committed
832 833
		dirtomake, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
834
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
835 836 837
			return
		}

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

840 841
		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
842
			res.SetError(err, cmdkit.ErrNormal)
843 844 845 846
			return
		}
		root := n.FilesRoot

847 848 849 850 851
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
			Mkparents: dashp,
			Flush:     flush,
			Prefix:    prefix,
		})
852
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
853
			res.SetError(err, cmdkit.ErrNormal)
854
			return
Jeromy's avatar
Jeromy committed
855
		}
keks's avatar
keks committed
856

Jan Winkelmann's avatar
Jan Winkelmann committed
857
		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
858 859 860
	},
}

Michael Muré's avatar
Michael Muré committed
861
var filesFlushCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
862
	Helptext: cmdkit.HelpText{
863
		Tagline: "Flush a given path's data to disk.",
Jeromy's avatar
Jeromy committed
864
		ShortDescription: `
865
Flush a given path to disk. This is only useful when other commands
Jeromy's avatar
Jeromy committed
866 867 868
are run with the '--flush=false'.
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
869 870
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to flush. Default: '/'."),
Jeromy's avatar
Jeromy committed
871
	},
Michael Muré's avatar
Michael Muré committed
872
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
873 874
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
875
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
876 877 878 879 880 881 882 883 884 885
			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
886
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
887 888
			return
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
889 890

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
891 892 893
	},
}

Michael Muré's avatar
Michael Muré committed
894
var filesChcidCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
895
	Helptext: cmdkit.HelpText{
896
		Tagline: "Change the cid version or hash function of the root node of a given path.",
Kevin Atkinson's avatar
Kevin Atkinson committed
897
		ShortDescription: `
898
Change the cid version or hash function of the root node of a given path.
Kevin Atkinson's avatar
Kevin Atkinson committed
899 900
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
901 902
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to change. Default: '/'."),
Kevin Atkinson's avatar
Kevin Atkinson committed
903
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
904
	Options: []cmdkit.Option{
905 906 907
		cidVersionOption,
		hashOption,
	},
Michael Muré's avatar
Michael Muré committed
908
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Kevin Atkinson's avatar
Kevin Atkinson committed
909 910
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
911
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
912 913 914 915 916 917 918 919 920 921 922 923
			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
924
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
925 926 927 928 929
			return
		}

		err = updatePath(nd.FilesRoot, path, prefix, flush)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
930
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
931 932
			return
		}
keks's avatar
keks committed
933 934

		res.SetOutput(nil)
Kevin Atkinson's avatar
Kevin Atkinson committed
935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961
	},
}

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
962
var filesRmCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
963
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
964
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
965
		ShortDescription: `
966
Remove files or directories.
Jeromy's avatar
Jeromy committed
967 968 969 970 971 972 973 974

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

Jan Winkelmann's avatar
Jan Winkelmann committed
977 978
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
979
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
980 981
	Options: []cmdkit.Option{
		cmdkit.BoolOption("recursive", "r", "Recursively remove directories."),
Jeromy's avatar
Jeromy committed
982
	},
Michael Muré's avatar
Michael Muré committed
983
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jan Winkelmann's avatar
Jan Winkelmann committed
984 985
		defer res.SetOutput(nil)

Jeromy's avatar
Jeromy committed
986 987
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
988
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
989 990 991
			return
		}

Jeromy's avatar
Jeromy committed
992 993
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
994
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
995 996 997 998
			return
		}

		if path == "/" {
Jan Winkelmann's avatar
Jan Winkelmann committed
999
			res.SetError(fmt.Errorf("cannot delete root"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1000 1001 1002 1003 1004 1005 1006 1007
			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
1008 1009 1010
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1011
			res.SetError(fmt.Errorf("parent lookup: %s", err), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1012 1013 1014 1015 1016
			return
		}

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

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

Jeromy's avatar
Jeromy committed
1023 1024 1025 1026 1027
		var success bool
		defer func() {
			if success {
				err := pdir.Flush()
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1028
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1029 1030 1031 1032 1033
					return
				}
			}
		}()

Jeromy's avatar
Jeromy committed
1034 1035 1036 1037
		// 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
1038
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1039 1040 1041
				return
			}

Jeromy's avatar
Jeromy committed
1042
			success = true
Jeromy's avatar
Jeromy committed
1043 1044 1045
			return
		}

Jeromy's avatar
Jeromy committed
1046 1047
		childi, err := pdir.Child(name)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1048
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1049 1050 1051 1052 1053
			return
		}

		switch childi.(type) {
		case *mfs.Directory:
Jan Winkelmann's avatar
Jan Winkelmann committed
1054
			res.SetError(fmt.Errorf("%s is a directory, use -r to remove directories", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1055
			return
Jeromy's avatar
Jeromy committed
1056 1057 1058
		default:
			err := pdir.Unlink(name)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1059
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1060 1061
				return
			}
Jeromy's avatar
Jeromy committed
1062 1063

			success = true
Jeromy's avatar
Jeromy committed
1064 1065 1066 1067
		}
	},
}

Michael Muré's avatar
Michael Muré committed
1068
func getPrefix(req oldcmds.Request) (*cid.Prefix, error) {
1069
	cidVer, cidVerSet, _ := req.Option("cid-version").Int()
1070
	hashFunStr, hashFunSet, _ := req.Option("hash").String()
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083

	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
1084

1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
	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
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
	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 {
1116
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1117 1118 1119 1120 1121 1122
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1123 1124 1125
		if prefix == nil {
			prefix = pdir.GetPrefix()
		}
Jeromy's avatar
Jeromy committed
1126

1127
		nd := dag.NodeWithData(ft.FilePBData(nil, 0))
1128
		nd.SetPrefix(prefix)
Jeromy's avatar
Jeromy committed
1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
		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
1139 1140
		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1141
			return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
Jeromy's avatar
Jeromy committed
1142 1143
		}
		return fi, nil
Jeromy's avatar
Jeromy committed
1144 1145 1146 1147 1148

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1149 1150 1151

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

	if p[0] != '/' {
Michael Muré's avatar
Michael Muré committed
1156
		return "", fmt.Errorf("paths must start with a leading slash")
Jeromy's avatar
Jeromy committed
1157 1158 1159 1160 1161 1162 1163 1164
	}

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