files.go 27 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
	dag "github.com/ipfs/go-ipfs/merkledag"
	mfs "github.com/ipfs/go-ipfs/mfs"
	path "github.com/ipfs/go-ipfs/path"
22
	resolver "github.com/ipfs/go-ipfs/path/resolver"
Jeromy's avatar
Jeromy committed
23
	ft "github.com/ipfs/go-ipfs/unixfs"
24
	uio "github.com/ipfs/go-ipfs/unixfs/io"
Jeromy's avatar
Jeromy committed
25

Michael Muré's avatar
Michael Muré committed
26
	humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
Steven Allen's avatar
Steven Allen committed
27
	logging "gx/ipfs/QmRb5jh8z2E8hMGN2tkvs1yHynUanqnZ3UeKwgN1i9P1F8/go-log"
Steven Allen's avatar
Steven Allen committed
28
	mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
Steven Allen's avatar
Steven Allen committed
29
	cmds "gx/ipfs/QmabLouZTZwhfALuBcssPvkzhbYGMb4394huT7HY4LQ6d3/go-ipfs-cmds"
Steven Allen's avatar
Steven Allen committed
30
	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 60 61 62 63 64
		"read":  lgc.NewCommand(filesReadCmd),
		"write": lgc.NewCommand(filesWriteCmd),
		"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
		nd, err := getNodeFromPath(req.Context, node, path)
Jeromy's avatar
Jeromy committed
129
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
130
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
131 132 133
			return
		}

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

140 141
		withLocal, _ := req.Options["with-local"].(bool)
		if !withLocal {
Michael Muré's avatar
Michael Muré committed
142
			res.Emit(o)
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
			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

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

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

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

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

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

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

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

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

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

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

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

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

Michael Muré's avatar
Michael Muré committed
238
		return &statOutput{
239 240 241 242 243 244 245
			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
246
		return &statOutput{
247 248 249 250 251 252
			Hash:           c.String(),
			Blocks:         0,
			Size:           cumulsize,
			CumulativeSize: cumulsize,
			Type:           "file",
		}, nil
Jeromy's avatar
Jeromy committed
253
	default:
254
		return nil, fmt.Errorf("not unixfs node (proto or raw)")
Jeromy's avatar
Jeromy committed
255
	}
Jeromy's avatar
Jeromy committed
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 289
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
290
var filesCpCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
291
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
292
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
293
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
294 295 296
	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
297
	},
Michael Muré's avatar
Michael Muré committed
298
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
299 300
		node, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
301
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
302 303 304
			return
		}

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

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

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

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

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

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

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

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

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

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

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

		return fsn.GetNode()
	}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

Examples:

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

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

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

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

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

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

		defer rfd.Close()

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

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

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

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

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

		res.SetOutput(r)
	},
}

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

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

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

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

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

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

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

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

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

673
EXAMPLE:
Jeromy's avatar
Jeromy committed
674

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

678
WARNING:
679

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Examples:

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

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

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

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

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

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

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

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

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

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

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

		res.SetOutput(nil)
Kevin Atkinson's avatar
Kevin Atkinson committed
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 962
	},
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	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
1085

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

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

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

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

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

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