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 36

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

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

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

73 74
var formatError = errors.New("Format was set by multiple options. Only one format option is allowed")

Michael Muré's avatar
Michael Muré committed
75
type statOutput struct {
76 77 78 79 80 81 82 83 84 85
	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
86 87 88 89 90 91
const defaultStatFormat = `<hash>
Size: <size>
CumulativeSize: <cumulsize>
ChildBlocks: <childs>
Type: <type>`

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

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

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

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

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

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

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

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
		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

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

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

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

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

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

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

keks's avatar
keks committed
198
	if moreThanOne(hash, size, format != defaultStatFormat) {
199 200 201 202
		return "", formatError
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return fsn.GetNode()
	}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

Examples:

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

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

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

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

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

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

		defer rfd.Close()

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

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

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

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

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

		res.SetOutput(r)
	},
}

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

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

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

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

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

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

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

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

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

671
EXAMPLE:
Jeromy's avatar
Jeromy committed
672

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

676
WARNING:
677

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Examples:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	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
1083

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

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

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

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

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

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