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

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

	core "github.com/ipfs/go-ipfs/core"
14
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
Jan Winkelmann's avatar
Jan Winkelmann committed
15
	e "github.com/ipfs/go-ipfs/core/commands/e"
16
	"github.com/ipfs/go-ipfs/core/coreapi/interface"
17

Michael Muré's avatar
Michael Muré committed
18
	humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
19
	cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
20
	mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
Hector Sanjuan's avatar
Hector Sanjuan committed
21
	ipld "gx/ipfs/QmR7TcHkR9nxkUorfi8XMTAMLUK7GiP64TWWBzY3aacc1o/go-ipld-format"
22
	cmds "gx/ipfs/QmSXUokcP4TJpFfqozT69AVAYRtzXVMUjzQVkYX41R9Svs/go-ipfs-cmds"
Hector Sanjuan's avatar
Hector Sanjuan committed
23
	dag "gx/ipfs/QmSei8kFMfqdJq7Q68d2LMnHbTWKKg2daA29ezUYFAUNgc/go-merkledag"
Steven Allen's avatar
Steven Allen committed
24
	offline "gx/ipfs/QmT6dHGp3UYd3vUMpy7rzX2CXQv7HLcj42Vtq8qwwjgASb/go-ipfs-exchange-offline"
Steven Allen's avatar
Steven Allen committed
25
	mfs "gx/ipfs/QmUwXQs8aZ472DmXZ8uJNf7HJNKoMJQVa7RaCz7ujZ3ua9/go-mfs"
Steven Allen's avatar
Steven Allen committed
26
	bservice "gx/ipfs/QmWfhv1D18DRSiSm73r4QGcByspzPtxxRTcmHW3axFXZo8/go-blockservice"
Steven Allen's avatar
Steven Allen committed
27
	logging "gx/ipfs/QmZChCsSt8DctjceaL56Eibc29CVQq4dGKRXC5JRZ6Ppae/go-log"
28
	cmdkit "gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
Hector Sanjuan's avatar
Hector Sanjuan committed
29
	ft "gx/ipfs/QmfB3oNXGGq9S4B2a9YeCajoATms3Zw2VvDm8fK7VeLSV8/go-unixfs"
Jeromy's avatar
Jeromy committed
30 31
)

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

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

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

Kejie Zhang's avatar
Kejie Zhang committed
69 70 71 72 73 74 75
const (
	filesCidVersionOptionName = "cid-version"
	filesHashOptionName       = "hash"
)

var cidVersionOption = cmdkit.IntOption(filesCidVersionOptionName, "cid-ver", "Cid version to use. (experimental)")
var hashOption = cmdkit.StringOption(filesHashOptionName, "Hash function to use. Will set Cid version to 1 if used. (experimental)")
76

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

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

Kejie Zhang's avatar
Kejie Zhang committed
90 91
const (
	defaultStatFormat = `<hash>
keks's avatar
keks committed
92 93 94 95
Size: <size>
CumulativeSize: <cumulsize>
ChildBlocks: <childs>
Type: <type>`
Kejie Zhang's avatar
Kejie Zhang committed
96 97 98 99
	filesFormatOptionName    = "format"
	filesSizeOptionName      = "size"
	filesWithLocalOptionName = "with-local"
)
keks's avatar
keks committed
100

101
var filesStatCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
102
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
103
		Tagline: "Display file status.",
Jeromy's avatar
Jeromy committed
104 105
	},

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

		_, err := statGetFormatOptions(req)
		if err != nil {
keks's avatar
keks committed
120
			return cmdkit.Errorf(cmdkit.ErrClient, err.Error())
121 122
		}

123
		node, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
124
		if err != nil {
keks's avatar
keks committed
125
			return err
Jeromy's avatar
Jeromy committed
126 127
		}

128 129 130 131 132
		api, err := cmdenv.GetApi(env)
		if err != nil {
			return err
		}

133
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
134
		if err != nil {
keks's avatar
keks committed
135
			return err
Jeromy's avatar
Jeromy committed
136 137
		}

Kejie Zhang's avatar
Kejie Zhang committed
138
		withLocal, _ := req.Options[filesWithLocalOptionName].(bool)
139 140 141 142 143 144 145 146 147 148 149 150

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

151
		nd, err := getNodeFromPath(req.Context, node, api, path)
Jeromy's avatar
Jeromy committed
152
		if err != nil {
keks's avatar
keks committed
153
			return err
Jeromy's avatar
Jeromy committed
154 155
		}

156
		o, err := statNode(nd)
Jeromy's avatar
Jeromy committed
157
		if err != nil {
keks's avatar
keks committed
158
			return err
Jeromy's avatar
Jeromy committed
159 160
		}

161
		if !withLocal {
keks's avatar
keks committed
162
			return cmds.EmitOnce(res, o)
163 164 165 166 167 168 169 170
		}

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

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

keks's avatar
keks committed
171
		return cmds.EmitOnce(res, o)
Jeromy's avatar
Jeromy committed
172
	},
173 174
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
Michael Muré's avatar
Michael Muré committed
175
			out, ok := v.(*statOutput)
Jan Winkelmann's avatar
Jan Winkelmann committed
176
			if !ok {
177
				return e.TypeErr(out, v)
Jan Winkelmann's avatar
Jan Winkelmann committed
178
			}
179

180
			s, _ := statGetFormatOptions(req)
181 182 183 184 185 186
			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)

187 188 189 190 191 192 193 194 195 196 197
			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
198
		}),
Jeromy's avatar
Jeromy committed
199
	},
Michael Muré's avatar
Michael Muré committed
200
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
201 202
}

203 204 205 206
func moreThanOne(a, b, c bool) bool {
	return a && b || b && c || a && c
}

207
func statGetFormatOptions(req *cmds.Request) (string, error) {
208

Kejie Zhang's avatar
Kejie Zhang committed
209 210 211
	hash, _ := req.Options[filesHashOptionName].(bool)
	size, _ := req.Options[filesSizeOptionName].(bool)
	format, _ := req.Options[filesFormatOptionName].(string)
212

keks's avatar
keks committed
213
	if moreThanOne(hash, size, format != defaultStatFormat) {
Michael Muré's avatar
Michael Muré committed
214
		return "", errFormat
215 216 217
	}

	if hash {
218 219 220 221 222
		return "<hash>", nil
	} else if size {
		return "<cumulsize>", nil
	} else {
		return format, nil
223 224 225
	}
}

Michael Muré's avatar
Michael Muré committed
226
func statNode(nd ipld.Node) (*statOutput, error) {
Jeromy's avatar
Jeromy committed
227
	c := nd.Cid()
Jeromy's avatar
Jeromy committed
228 229 230 231 232 233

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

234 235
	switch n := nd.(type) {
	case *dag.ProtoNode:
Overbool's avatar
Overbool committed
236
		d, err := ft.FSNodeFromBytes(n.Data())
237 238 239 240 241
		if err != nil {
			return nil, err
		}

		var ndtype string
Overbool's avatar
Overbool committed
242
		switch d.Type() {
243
		case ft.TDirectory, ft.THAMTShard:
244
			ndtype = "directory"
245
		case ft.TFile, ft.TMetadata, ft.TRaw:
246 247
			ndtype = "file"
		default:
Overbool's avatar
Overbool committed
248
			return nil, fmt.Errorf("unrecognized node type: %s", d.Type())
249 250
		}

Michael Muré's avatar
Michael Muré committed
251
		return &statOutput{
252 253
			Hash:           c.String(),
			Blocks:         len(nd.Links()),
Overbool's avatar
Overbool committed
254
			Size:           d.FileSize(),
255 256 257 258
			CumulativeSize: cumulsize,
			Type:           ndtype,
		}, nil
	case *dag.RawNode:
Michael Muré's avatar
Michael Muré committed
259
		return &statOutput{
260 261 262 263 264 265
			Hash:           c.String(),
			Blocks:         0,
			Size:           cumulsize,
			CumulativeSize: cumulsize,
			Type:           "file",
		}, nil
Jeromy's avatar
Jeromy committed
266
	default:
267
		return nil, fmt.Errorf("not unixfs node (proto or raw)")
Jeromy's avatar
Jeromy committed
268
	}
Jeromy's avatar
Jeromy committed
269 270
}

271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
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
}

Overbool's avatar
Overbool committed
303
var filesCpCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
304
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
305
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
306
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
307 308 309
	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
310
	},
Overbool's avatar
Overbool committed
311 312
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
313
		if err != nil {
Overbool's avatar
Overbool committed
314
			return err
Jeromy's avatar
Jeromy committed
315 316
		}

Overbool's avatar
Overbool committed
317
		api, err := cmdenv.GetApi(env)
318
		if err != nil {
Overbool's avatar
Overbool committed
319
			return err
320 321
		}

Overbool's avatar
Overbool committed
322
		flush, _ := req.Options["flush"].(bool)
323

Overbool's avatar
Overbool committed
324
		src, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
325
		if err != nil {
Overbool's avatar
Overbool committed
326
			return err
Jeromy's avatar
Jeromy committed
327
		}
328 329
		src = strings.TrimRight(src, "/")

Overbool's avatar
Overbool committed
330
		dst, err := checkPath(req.Arguments[1])
Jeromy's avatar
Jeromy committed
331
		if err != nil {
Overbool's avatar
Overbool committed
332
			return err
Jeromy's avatar
Jeromy committed
333
		}
Jeromy's avatar
Jeromy committed
334

335 336 337 338
		if dst[len(dst)-1] == '/' {
			dst += gopath.Base(src)
		}

Overbool's avatar
Overbool committed
339
		node, err := getNodeFromPath(req.Context, nd, api, src)
Jeromy's avatar
Jeromy committed
340
		if err != nil {
Overbool's avatar
Overbool committed
341
			return fmt.Errorf("cp: cannot get node from path %s: %s", src, err)
Jeromy's avatar
Jeromy committed
342 343
		}

Overbool's avatar
Overbool committed
344
		err = mfs.PutNode(nd.FilesRoot, dst, node)
Jeromy's avatar
Jeromy committed
345
		if err != nil {
Overbool's avatar
Overbool committed
346
			return fmt.Errorf("cp: cannot put node in path %s: %s", dst, err)
Jeromy's avatar
Jeromy committed
347
		}
348 349

		if flush {
Overbool's avatar
Overbool committed
350
			err := mfs.FlushPath(nd.FilesRoot, dst)
351
			if err != nil {
Overbool's avatar
Overbool committed
352
				return fmt.Errorf("cp: cannot flush the created file %s: %s", dst, err)
353 354
			}
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
355

Overbool's avatar
Overbool committed
356
		return nil
Jeromy's avatar
Jeromy committed
357 358 359
	},
}

360
func getNodeFromPath(ctx context.Context, node *core.IpfsNode, api iface.CoreAPI, p string) (ipld.Node, error) {
Jeromy's avatar
Jeromy committed
361 362
	switch {
	case strings.HasPrefix(p, "/ipfs/"):
363
		np, err := iface.ParsePath(p)
Jeromy's avatar
Jeromy committed
364 365 366 367
		if err != nil {
			return nil, err
		}

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

		return fsn.GetNode()
	}
}

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

Kejie Zhang's avatar
Kejie Zhang committed
383 384 385 386 387
const (
	longOptionName     = "l"
	dontSortOptionName = "U"
)

Overbool's avatar
Overbool committed
388
var filesLsCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
389
	Helptext: cmdkit.HelpText{
390
		Tagline: "List directories in the local mutable namespace.",
Jeromy's avatar
Jeromy committed
391
		ShortDescription: `
392
List directories in the local mutable namespace.
Jeromy's avatar
Jeromy committed
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408

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
409 410
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to show listing for. Defaults to '/'."),
Jeromy's avatar
Jeromy committed
411
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
412
	Options: []cmdkit.Option{
Kejie Zhang's avatar
Kejie Zhang committed
413 414
		cmdkit.BoolOption(longOptionName, "Use long listing format."),
		cmdkit.BoolOption(dontSortOptionName, "Do not sort; list entries in directory order."),
Jeromy's avatar
Jeromy committed
415
	},
Overbool's avatar
Overbool committed
416
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
417 418
		var arg string

Overbool's avatar
Overbool committed
419
		if len(req.Arguments) == 0 {
420 421
			arg = "/"
		} else {
Overbool's avatar
Overbool committed
422
			arg = req.Arguments[0]
423 424 425
		}

		path, err := checkPath(arg)
Jeromy's avatar
Jeromy committed
426
		if err != nil {
Overbool's avatar
Overbool committed
427
			return err
Jeromy's avatar
Jeromy committed
428 429
		}

Overbool's avatar
Overbool committed
430
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
431
		if err != nil {
Overbool's avatar
Overbool committed
432
			return err
Jeromy's avatar
Jeromy committed
433 434 435 436
		}

		fsn, err := mfs.Lookup(nd.FilesRoot, path)
		if err != nil {
Overbool's avatar
Overbool committed
437
			return err
Jeromy's avatar
Jeromy committed
438 439
		}

Overbool's avatar
Overbool committed
440
		long, _ := req.Options[longOptionName].(bool)
Jeromy's avatar
Jeromy committed
441

Jeromy's avatar
Jeromy committed
442 443
		switch fsn := fsn.(type) {
		case *mfs.Directory:
Jeromy's avatar
Jeromy committed
444 445
			if !long {
				var output []mfs.NodeListing
Overbool's avatar
Overbool committed
446
				names, err := fsn.ListNames(req.Context)
447
				if err != nil {
Overbool's avatar
Overbool committed
448
					return err
449 450 451
				}

				for _, name := range names {
Jeromy's avatar
Jeromy committed
452
					output = append(output, mfs.NodeListing{
Jeromy's avatar
Jeromy committed
453
						Name: name,
Jeromy's avatar
Jeromy committed
454 455
					})
				}
Overbool's avatar
Overbool committed
456
				return res.Emit(&filesLsOutput{output})
Jeromy's avatar
Jeromy committed
457
			} else {
Overbool's avatar
Overbool committed
458
				listing, err := fsn.List(req.Context)
Jeromy's avatar
Jeromy committed
459
				if err != nil {
Overbool's avatar
Overbool committed
460
					return err
Jeromy's avatar
Jeromy committed
461
				}
Overbool's avatar
Overbool committed
462
				return res.Emit(&filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
463 464
			}
		case *mfs.File:
rht's avatar
rht committed
465
			_, name := gopath.Split(path)
Overbool's avatar
Overbool committed
466
			out := &filesLsOutput{[]mfs.NodeListing{{Name: name}}}
467 468 469 470 471
			if long {
				out.Entries[0].Type = int(fsn.Type())

				size, err := fsn.Size()
				if err != nil {
Overbool's avatar
Overbool committed
472
					return err
473 474 475 476 477
				}
				out.Entries[0].Size = size

				nd, err := fsn.GetNode()
				if err != nil {
Overbool's avatar
Overbool committed
478
					return err
479 480 481
				}
				out.Entries[0].Hash = nd.Cid().String()
			}
Overbool's avatar
Overbool committed
482
			return res.Emit(out)
Jeromy's avatar
Jeromy committed
483
		default:
Overbool's avatar
Overbool committed
484
			return errors.New("unrecognized type")
Jeromy's avatar
Jeromy committed
485 486
		}
	},
Overbool's avatar
Overbool committed
487 488 489
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *filesLsOutput) error {
			noSort, _ := req.Options[dontSortOptionName].(bool)
Lucas Molas's avatar
Lucas Molas committed
490 491 492 493 494 495
			if !noSort {
				sort.Slice(out.Entries, func(i, j int) bool {
					return strings.Compare(out.Entries[i].Name, out.Entries[j].Name) < 0
				})
			}

Overbool's avatar
Overbool committed
496
			long, _ := req.Options[longOptionName].(bool)
Jeromy's avatar
Jeromy committed
497 498
			for _, o := range out.Entries {
				if long {
Overbool's avatar
Overbool committed
499 500 501
					if o.Type == int(mfs.TDir) {
						o.Name += "/"
					}
Overbool's avatar
Overbool committed
502
					fmt.Fprintf(w, "%s\t%s\t%d\n", o.Name, o.Hash, o.Size)
Jeromy's avatar
Jeromy committed
503
				} else {
Overbool's avatar
Overbool committed
504
					fmt.Fprintf(w, "%s\n", o.Name)
Jeromy's avatar
Jeromy committed
505 506
				}
			}
Overbool's avatar
Overbool committed
507 508 509

			return nil
		}),
Jeromy's avatar
Jeromy committed
510
	},
Michael Muré's avatar
Michael Muré committed
511
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
512 513
}

Kejie Zhang's avatar
Kejie Zhang committed
514 515 516 517 518
const (
	filesOffsetOptionName = "offset"
	filesCountOptionName  = "count"
)

Overbool's avatar
Overbool committed
519
var filesReadCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
520
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
521
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
522
		ShortDescription: `
523 524
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
525 526 527 528 529

Examples:

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

Jan Winkelmann's avatar
Jan Winkelmann committed
533 534
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
535
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
536
	Options: []cmdkit.Option{
537 538
		cmdkit.Int64Option(filesOffsetOptionName, "o", "Byte offset to begin reading from."),
		cmdkit.Int64Option(filesCountOptionName, "n", "Maximum number of bytes to read."),
Jeromy's avatar
Jeromy committed
539
	},
Overbool's avatar
Overbool committed
540 541
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
542
		if err != nil {
Overbool's avatar
Overbool committed
543
			return err
Jeromy's avatar
Jeromy committed
544 545
		}

Overbool's avatar
Overbool committed
546
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
547
		if err != nil {
Overbool's avatar
Overbool committed
548
			return err
Jeromy's avatar
Jeromy committed
549 550
		}

Overbool's avatar
Overbool committed
551
		fsn, err := mfs.Lookup(nd.FilesRoot, path)
Jeromy's avatar
Jeromy committed
552
		if err != nil {
Overbool's avatar
Overbool committed
553
			return err
Jeromy's avatar
Jeromy committed
554 555 556 557
		}

		fi, ok := fsn.(*mfs.File)
		if !ok {
Overbool's avatar
Overbool committed
558
			return fmt.Errorf("%s was not a file", path)
Jeromy's avatar
Jeromy committed
559 560
		}

561 562
		rfd, err := fi.Open(mfs.OpenReadOnly, false)
		if err != nil {
Overbool's avatar
Overbool committed
563
			return err
564 565 566 567
		}

		defer rfd.Close()

Overbool's avatar
Overbool committed
568
		offset, _ := req.Options[offsetOptionName].(int64)
Jeromy's avatar
Jeromy committed
569
		if offset < 0 {
Overbool's avatar
Overbool committed
570
			return fmt.Errorf("cannot specify negative offset")
Jeromy's avatar
Jeromy committed
571 572
		}

573
		filen, err := rfd.Size()
Jeromy's avatar
Jeromy committed
574
		if err != nil {
Overbool's avatar
Overbool committed
575
			return err
Jeromy's avatar
Jeromy committed
576 577 578
		}

		if int64(offset) > filen {
Overbool's avatar
Overbool committed
579
			return fmt.Errorf("offset was past end of file (%d > %d)", offset, filen)
Jeromy's avatar
Jeromy committed
580
		}
Jeromy's avatar
Jeromy committed
581

582
		_, err = rfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
583
		if err != nil {
Overbool's avatar
Overbool committed
584
			return err
Jeromy's avatar
Jeromy committed
585
		}
586

Overbool's avatar
Overbool committed
587 588
		var r io.Reader = &contextReaderWrapper{R: rfd, ctx: req.Context}
		count, found := req.Options[filesCountOptionName].(int64)
Jeromy's avatar
Jeromy committed
589 590
		if found {
			if count < 0 {
Overbool's avatar
Overbool committed
591
				return fmt.Errorf("cannot specify negative 'count'")
Jeromy's avatar
Jeromy committed
592
			}
593
			r = io.LimitReader(r, int64(count))
Jeromy's avatar
Jeromy committed
594
		}
Overbool's avatar
Overbool committed
595
		return res.Emit(r)
Jeromy's avatar
Jeromy committed
596 597 598
	},
}

Jeromy's avatar
Jeromy committed
599 600 601 602 603 604 605 606 607 608 609 610 611
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)
}

Overbool's avatar
Overbool committed
612
var filesMvCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
613
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
614
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
615 616 617 618 619 620 621
		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
622
`,
Jeromy's avatar
Jeromy committed
623 624
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
625 626 627
	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
628
	},
Overbool's avatar
Overbool committed
629 630
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
631
		if err != nil {
Overbool's avatar
Overbool committed
632
			return err
Jeromy's avatar
Jeromy committed
633 634
		}

Overbool's avatar
Overbool committed
635
		src, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
636
		if err != nil {
Overbool's avatar
Overbool committed
637
			return err
Jeromy's avatar
Jeromy committed
638
		}
Overbool's avatar
Overbool committed
639
		dst, err := checkPath(req.Arguments[1])
Jeromy's avatar
Jeromy committed
640
		if err != nil {
Overbool's avatar
Overbool committed
641
			return err
Jeromy's avatar
Jeromy committed
642
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
643

Overbool's avatar
Overbool committed
644
		return mfs.Mv(nd.FilesRoot, src, dst)
Jeromy's avatar
Jeromy committed
645 646 647
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
648 649 650 651 652 653 654 655
const (
	filesCreateOptionName    = "create"
	filesParentsOptionName   = "parents"
	filesTruncateOptionName  = "truncate"
	filesRawLeavesOptionName = "raw-leaves"
	filesFlushOptionName     = "flush"
)

656
var filesWriteCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
657
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
658
		Tagline: "Write to a mutable file in a given filesystem.",
Jeromy's avatar
Jeromy committed
659 660
		ShortDescription: `
Write data to a file in a given filesystem. This command allows you to specify
661 662
a beginning offset to write to. The entire length of the input will be
written.
Jeromy's avatar
Jeromy committed
663

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

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

674 675 676 677
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.

678
EXAMPLE:
Jeromy's avatar
Jeromy committed
679

Jeromy's avatar
Jeromy committed
680 681
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
682

683
WARNING:
684

685 686 687
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
688
`,
Jeromy's avatar
Jeromy committed
689
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
690 691 692
	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
693
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
694
	Options: []cmdkit.Option{
695
		cmdkit.Int64Option(filesOffsetOptionName, "o", "Byte offset to begin writing at."),
Kejie Zhang's avatar
Kejie Zhang committed
696 697 698
		cmdkit.BoolOption(filesCreateOptionName, "e", "Create the file if it does not exist."),
		cmdkit.BoolOption(filesParentsOptionName, "p", "Make parent directories as needed."),
		cmdkit.BoolOption(filesTruncateOptionName, "t", "Truncate the file to size zero before writing."),
699
		cmdkit.Int64Option(filesCountOptionName, "n", "Maximum number of bytes to read."),
Kejie Zhang's avatar
Kejie Zhang committed
700
		cmdkit.BoolOption(filesRawLeavesOptionName, "Use raw blocks for newly created leaf nodes. (experimental)"),
701 702
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
703
	},
keks's avatar
keks committed
704
	Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) (retErr error) {
705
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
706
		if err != nil {
keks's avatar
keks committed
707
			return err
Jeromy's avatar
Jeromy committed
708 709
		}

Kejie Zhang's avatar
Kejie Zhang committed
710 711 712 713 714
		create, _ := req.Options[filesCreateOptionName].(bool)
		mkParents, _ := req.Options[filesParentsOptionName].(bool)
		trunc, _ := req.Options[filesTruncateOptionName].(bool)
		flush, _ := req.Options[filesFlushOptionName].(bool)
		rawLeaves, rawLeavesDef := req.Options[filesRawLeavesOptionName].(bool)
715

716
		prefix, err := getPrefixNew(req)
717
		if err != nil {
keks's avatar
keks committed
718
			return err
719
		}
Jeromy's avatar
Jeromy committed
720

721
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
722
		if err != nil {
keks's avatar
keks committed
723
			return err
Jeromy's avatar
Jeromy committed
724 725
		}

726
		offset, _ := req.Options[filesOffsetOptionName].(int64)
Jeromy's avatar
Jeromy committed
727
		if offset < 0 {
keks's avatar
keks committed
728
			return fmt.Errorf("cannot have negative write offset")
Jeromy's avatar
Jeromy committed
729 730
		}

731 732 733
		if mkParents {
			err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix)
			if err != nil {
keks's avatar
keks committed
734
				return err
735 736 737
			}
		}

738
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
739
		if err != nil {
keks's avatar
keks committed
740
			return err
Jeromy's avatar
Jeromy committed
741
		}
742 743 744
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
745

746 747
		wfd, err := fi.Open(mfs.OpenWriteOnly, flush)
		if err != nil {
keks's avatar
keks committed
748
			return err
749
		}
Jeromy's avatar
Jeromy committed
750

751 752 753
		defer func() {
			err := wfd.Close()
			if err != nil {
keks's avatar
keks committed
754 755 756 757 758
				if retErr == nil {
					retErr = err
				} else {
					log.Error("files: error closing file mfs file descriptor", err)
				}
759 760
			}
		}()
761

Jeromy's avatar
Jeromy committed
762
		if trunc {
763
			if err := wfd.Truncate(0); err != nil {
keks's avatar
keks committed
764
				return err
Jeromy's avatar
Jeromy committed
765 766 767
			}
		}

768
		count, countfound := req.Options[filesCountOptionName].(int64)
Jeromy's avatar
Jeromy committed
769
		if countfound && count < 0 {
keks's avatar
keks committed
770
			return fmt.Errorf("cannot have negative byte count")
Jeromy's avatar
Jeromy committed
771
		}
Jeromy's avatar
Jeromy committed
772

773
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
774
		if err != nil {
775
			flog.Error("seekfail: ", err)
keks's avatar
keks committed
776
			return err
Jeromy's avatar
Jeromy committed
777 778
		}

779
		input, err := req.Files.NextFile()
Jeromy's avatar
Jeromy committed
780
		if err != nil {
keks's avatar
keks committed
781
			return err
Jeromy's avatar
Jeromy committed
782 783
		}

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)
keks's avatar
keks committed
790
		return err
Jeromy's avatar
Jeromy committed
791 792 793
	},
}

Overbool's avatar
Overbool committed
794
var filesMkdirCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
795
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
796
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
797 798 799
		ShortDescription: `
Create the directory if it does not already exist.

800 801 802
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

803
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
804 805 806

Examples:

807 808
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
809 810 811
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
812 813
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to dir to make."),
Jeromy's avatar
Jeromy committed
814
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
815
	Options: []cmdkit.Option{
Kejie Zhang's avatar
Kejie Zhang committed
816
		cmdkit.BoolOption(filesParentsOptionName, "p", "No error if existing, make parent directories as needed."),
817 818
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
819
	},
Overbool's avatar
Overbool committed
820 821
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		n, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
822
		if err != nil {
Overbool's avatar
Overbool committed
823
			return err
Jeromy's avatar
Jeromy committed
824 825
		}

Overbool's avatar
Overbool committed
826 827
		dashp, _ := req.Options[filesParentsOptionName].(bool)
		dirtomake, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
828
		if err != nil {
Overbool's avatar
Overbool committed
829
			return err
Jeromy's avatar
Jeromy committed
830 831
		}

Overbool's avatar
Overbool committed
832
		flush, _ := req.Options[filesFlushOptionName].(bool)
Jeromy's avatar
Jeromy committed
833

834 835
		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
836
			return err
837 838 839
		}
		root := n.FilesRoot

840
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
841 842 843
			Mkparents:  dashp,
			Flush:      flush,
			CidBuilder: prefix,
844
		})
keks's avatar
keks committed
845

Overbool's avatar
Overbool committed
846
		return err
Jeromy's avatar
Jeromy committed
847 848 849
	},
}

Overbool's avatar
Overbool committed
850
var filesFlushCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
851
	Helptext: cmdkit.HelpText{
852
		Tagline: "Flush a given path's data to disk.",
Jeromy's avatar
Jeromy committed
853
		ShortDescription: `
854
Flush a given path to disk. This is only useful when other commands
Jeromy's avatar
Jeromy committed
855 856 857
are run with the '--flush=false'.
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
858 859
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to flush. Default: '/'."),
Jeromy's avatar
Jeromy committed
860
	},
Overbool's avatar
Overbool committed
861 862
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
863
		if err != nil {
Overbool's avatar
Overbool committed
864
			return err
Jeromy's avatar
Jeromy committed
865 866 867
		}

		path := "/"
Overbool's avatar
Overbool committed
868 869
		if len(req.Arguments) > 0 {
			path = req.Arguments[0]
Jeromy's avatar
Jeromy committed
870
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
871

Overbool's avatar
Overbool committed
872
		return mfs.FlushPath(nd.FilesRoot, path)
Jeromy's avatar
Jeromy committed
873 874 875
	},
}

Overbool's avatar
Overbool committed
876
var filesChcidCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
877
	Helptext: cmdkit.HelpText{
878
		Tagline: "Change the cid version or hash function of the root node of a given path.",
Kevin Atkinson's avatar
Kevin Atkinson committed
879
		ShortDescription: `
880
Change the cid version or hash function of the root node of a given path.
Kevin Atkinson's avatar
Kevin Atkinson committed
881 882
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
883 884
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to change. Default: '/'."),
Kevin Atkinson's avatar
Kevin Atkinson committed
885
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
886
	Options: []cmdkit.Option{
887 888 889
		cidVersionOption,
		hashOption,
	},
Overbool's avatar
Overbool committed
890 891
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Kevin Atkinson's avatar
Kevin Atkinson committed
892
		if err != nil {
Overbool's avatar
Overbool committed
893
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
894 895 896
		}

		path := "/"
Overbool's avatar
Overbool committed
897 898
		if len(req.Arguments) > 0 {
			path = req.Arguments[0]
Kevin Atkinson's avatar
Kevin Atkinson committed
899 900
		}

Overbool's avatar
Overbool committed
901
		flush, _ := req.Options[filesFlushOptionName].(bool)
Kevin Atkinson's avatar
Kevin Atkinson committed
902 903 904

		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
905
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
906
		}
keks's avatar
keks committed
907

Overbool's avatar
Overbool committed
908
		return updatePath(nd.FilesRoot, path, prefix, flush)
Kevin Atkinson's avatar
Kevin Atkinson committed
909 910 911
	},
}

912 913
func updatePath(rt *mfs.Root, pth string, builder cid.Builder, flush bool) error {
	if builder == nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
914 915 916 917 918 919 920 921 922 923
		return nil
	}

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

	switch n := nd.(type) {
	case *mfs.Directory:
924
		n.SetCidBuilder(builder)
Kevin Atkinson's avatar
Kevin Atkinson committed
925 926 927 928 929 930 931 932 933 934 935
	default:
		return fmt.Errorf("can only update directories")
	}

	if flush {
		nd.Flush()
	}

	return nil
}

Overbool's avatar
Overbool committed
936
var filesRmCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
937
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
938
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
939
		ShortDescription: `
940
Remove files or directories.
Jeromy's avatar
Jeromy committed
941 942 943 944 945 946 947 948

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

Jan Winkelmann's avatar
Jan Winkelmann committed
951 952
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
953
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
954 955
	Options: []cmdkit.Option{
		cmdkit.BoolOption("recursive", "r", "Recursively remove directories."),
956
		cmdkit.BoolOption("force", "Forcibly remove target at path; implies -r for directories"),
Jeromy's avatar
Jeromy committed
957
	},
Overbool's avatar
Overbool committed
958 959
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err :=cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
960
		if err != nil {
Overbool's avatar
Overbool committed
961
			return err
Jeromy's avatar
Jeromy committed
962 963
		}

Overbool's avatar
Overbool committed
964
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
965
		if err != nil {
Overbool's avatar
Overbool committed
966
			return err
Jeromy's avatar
Jeromy committed
967 968 969
		}

		if path == "/" {
Overbool's avatar
Overbool committed
970
			return fmt.Errorf("cannot delete root")
Jeromy's avatar
Jeromy committed
971 972 973 974 975 976 977
		}

		// '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
978 979 980
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Overbool's avatar
Overbool committed
981
			return fmt.Errorf("parent lookup: %s", err)
Jeromy's avatar
Jeromy committed
982 983 984 985
		}

		pdir, ok := parent.(*mfs.Directory)
		if !ok {
Overbool's avatar
Overbool committed
986
			return fmt.Errorf("no such file or directory: %s", path)
Jeromy's avatar
Jeromy committed
987 988
		}

989 990
		// if '--force' specified, it will remove anything else,
		// including file, directory, corrupted node, etc
Overbool's avatar
Overbool committed
991
		force, _ := req.Options["force"].(bool)
992
		if force {
Jeromy's avatar
Jeromy committed
993 994
			err := pdir.Unlink(name)
			if err != nil {
Overbool's avatar
Overbool committed
995
				return err
Jeromy's avatar
Jeromy committed
996 997
			}

Overbool's avatar
Overbool committed
998
			return pdir.Flush()
Jeromy's avatar
Jeromy committed
999 1000
		}

1001 1002 1003
		// get child node by name, when the node is corrupted and nonexistent,
		// it will return specific error.
		child, err := pdir.Child(name)
Jeromy's avatar
Jeromy committed
1004
		if err != nil {
Overbool's avatar
Overbool committed
1005
			return err
Jeromy's avatar
Jeromy committed
1006 1007
		}

Overbool's avatar
Overbool committed
1008
		dashr, _ := req.Options["r"].(bool)
1009 1010

		switch child.(type) {
Jeromy's avatar
Jeromy committed
1011
		case *mfs.Directory:
1012
			if !dashr {
Overbool's avatar
Overbool committed
1013
				return fmt.Errorf("%s is a directory, use -r to remove directories", path)
Jeromy's avatar
Jeromy committed
1014
			}
1015
		}
Jeromy's avatar
Jeromy committed
1016

1017 1018
		err = pdir.Unlink(name)
		if err != nil {
Overbool's avatar
Overbool committed
1019
			return err
Jeromy's avatar
Jeromy committed
1020
		}
1021

Overbool's avatar
Overbool committed
1022
		return pdir.Flush()
Jeromy's avatar
Jeromy committed
1023 1024 1025
	},
}

1026
func getPrefixNew(req *cmds.Request) (cid.Builder, error) {
Kejie Zhang's avatar
Kejie Zhang committed
1027 1028
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054

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

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

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

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

	return &prefix, nil
}

Overbool's avatar
Overbool committed
1055 1056 1057
func getPrefix(req *cmds.Request) (cid.Builder, error) {
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070

	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
1071

1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
	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
}

1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096
func ensureContainingDirectoryExists(r *mfs.Root, path string, builder cid.Builder) error {
	dirtomake := gopath.Dir(path)

	if dirtomake == "/" {
		return nil
	}

	return mfs.Mkdir(r, dirtomake, mfs.MkdirOpts{
		Mkparents:  true,
		CidBuilder: builder,
	})
}

1097
func getFileHandle(r *mfs.Root, path string, create bool, builder cid.Builder) (*mfs.File, error) {
Jeromy's avatar
Jeromy committed
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
	target, err := mfs.Lookup(r, path)
	switch err {
	case nil:
		fi, ok := target.(*mfs.File)
		if !ok {
			return nil, fmt.Errorf("%s was not a file", path)
		}
		return fi, nil

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

		// if create is specified and the file doesnt exist, we create the file
		dirname, fname := gopath.Split(path)
		pdiri, err := mfs.Lookup(r, dirname)
		if err != nil {
1116
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1117 1118 1119 1120 1121 1122
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1123 1124
		if builder == nil {
			builder = pdir.GetCidBuilder()
1125
		}
Jeromy's avatar
Jeromy committed
1126

1127
		nd := dag.NodeWithData(ft.FilePBData(nil, 0))
1128
		nd.SetCidBuilder(builder)
Jeromy's avatar
Jeromy committed
1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
		err = pdir.AddChild(fname, nd)
		if err != nil {
			return nil, err
		}

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

Jeromy's avatar
Jeromy committed
1139 1140
		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1141
			return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
Jeromy's avatar
Jeromy committed
1142 1143
		}
		return fi, nil
Jeromy's avatar
Jeromy committed
1144 1145 1146 1147 1148

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

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

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

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