files.go 27.7 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
	"strings"

13 14 15
	"github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/core/commands/cmdenv"
	"github.com/ipfs/go-ipfs/core/coreapi/interface"
16

Steven Allen's avatar
Steven Allen committed
17
	"gx/ipfs/QmP9eu5X5Ax8169jNWqAJcc42mdZgzLR1aKCEzqhNoBLKk/go-mfs"
18
	"gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
Steven Allen's avatar
Steven Allen committed
19
	ft "gx/ipfs/QmQXze9tG878pa4Euya4rrDpyTNX3kQe4dhCaBzBozGgpe/go-unixfs"
20
	cid "gx/ipfs/QmR8BauakNcBa3RbE4nbQu76PDiJgoQgz8AJdhJuiU4TAw/go-cid"
Steven Allen's avatar
Steven Allen committed
21
	dag "gx/ipfs/QmTQdH4848iTVCJmKXYyRiK72HufWTLYQQ8iN3JaQ8K1Hq/go-merkledag"
Hector Sanjuan's avatar
Hector Sanjuan committed
22
	"gx/ipfs/QmWGm4AbZEbnmdgVTza52MSNpEmBdFVqzmAysRbjrRyGbH/go-ipfs-cmds"
Steven Allen's avatar
Steven Allen committed
23
	bservice "gx/ipfs/QmYPZzd9VqmJDwxUnThfeSbV1Y5o53aVPDijTB7j7rS9Ep/go-blockservice"
24
	"gx/ipfs/QmYZwey1thDTynSrvd6qQkX24UpTka6TFhQ2v569UpoqxD/go-ipfs-exchange-offline"
Steven Allen's avatar
Steven Allen committed
25 26
	ipld "gx/ipfs/QmcKKBwfz6FyQdHR2jsXrrF6XeSBXYL86anmWNewpFpoF5/go-ipld-format"
	logging "gx/ipfs/QmcuXC5cxs79ro2cUuHs4HQ2bkDLJUYokwL8aivcX6HW3C/go-log"
27
	cidenc "gx/ipfs/QmdPQx9fvN5ExVwMhRmh7YpCQJzJrFhd1AjVBwJmRMFJeX/go-cidutil/cidenc"
28
	"gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
Steven Allen's avatar
Steven Allen committed
29
	mh "gx/ipfs/QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW/go-multihash"
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{
53
		cmdkit.BoolOption(filesFlushOptionName, "f", "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
		api, err := cmdenv.GetApi(env, req)
129 130 131 132
		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
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

145 146 147 148 149 150 151 152 153 154 155
		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
		}

156
		nd, err := getNodeFromPath(req.Context, node, api, path)
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
		o, err := statNode(nd, enc)
Jeromy's avatar
Jeromy committed
162
		if err != nil {
keks's avatar
keks committed
163
			return err
Jeromy's avatar
Jeromy committed
164 165
		}

166
		if !withLocal {
keks's avatar
keks committed
167
			return cmds.EmitOnce(res, o)
168 169 170 171 172 173 174 175
		}

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

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

keks's avatar
keks committed
176
		return cmds.EmitOnce(res, o)
Jeromy's avatar
Jeromy committed
177
	},
178
	Encoders: cmds.EncoderMap{
179
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *statOutput) error {
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
	}
}

226
func statNode(nd ipld.Node, enc cidenc.Encoder) (*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
			Hash:           enc.Encode(c),
253
			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
			Hash:           enc.Encode(c),
261 262 263 264 265
			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
		}

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

322
		flush, _ := req.Options[filesFlushOptionName].(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

442 443 444 445 446
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

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

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

				size, err := fsn.Size()
				if err != nil {
Overbool's avatar
Overbool committed
476
					return err
477 478 479 480 481
				}
				out.Entries[0].Size = size

				nd, err := fsn.GetNode()
				if err != nil {
Overbool's avatar
Overbool committed
482
					return err
483
				}
484
				out.Entries[0].Hash = enc.Encode(nd.Cid())
485
			}
486
			return cmds.EmitOnce(res, out)
Jeromy's avatar
Jeromy committed
487
		default:
Overbool's avatar
Overbool committed
488
			return errors.New("unrecognized type")
Jeromy's avatar
Jeromy committed
489 490
		}
	},
Overbool's avatar
Overbool committed
491 492 493
	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
494 495 496 497 498 499
			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
500
			long, _ := req.Options[longOptionName].(bool)
Jeromy's avatar
Jeromy committed
501 502
			for _, o := range out.Entries {
				if long {
Overbool's avatar
Overbool committed
503 504 505
					if o.Type == int(mfs.TDir) {
						o.Name += "/"
					}
Overbool's avatar
Overbool committed
506
					fmt.Fprintf(w, "%s\t%s\t%d\n", o.Name, o.Hash, o.Size)
Jeromy's avatar
Jeromy committed
507
				} else {
Overbool's avatar
Overbool committed
508
					fmt.Fprintf(w, "%s\n", o.Name)
Jeromy's avatar
Jeromy committed
509 510
				}
			}
Overbool's avatar
Overbool committed
511 512 513

			return nil
		}),
Jeromy's avatar
Jeromy committed
514
	},
Michael Muré's avatar
Michael Muré committed
515
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
516 517
}

Kejie Zhang's avatar
Kejie Zhang committed
518 519 520 521 522
const (
	filesOffsetOptionName = "offset"
	filesCountOptionName  = "count"
)

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

Examples:

    $ ipfs files read /test/hello
    hello
Jeromy's avatar
Jeromy committed
534
        `,
Jeromy's avatar
Jeromy committed
535 536
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
537 538
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
539
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
540
	Options: []cmdkit.Option{
541 542
		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
543
	},
Overbool's avatar
Overbool committed
544 545
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
546
		if err != nil {
Overbool's avatar
Overbool committed
547
			return err
Jeromy's avatar
Jeromy committed
548 549
		}

Overbool's avatar
Overbool committed
550
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
551
		if err != nil {
Overbool's avatar
Overbool committed
552
			return err
Jeromy's avatar
Jeromy committed
553 554
		}

Overbool's avatar
Overbool committed
555
		fsn, err := mfs.Lookup(nd.FilesRoot, path)
Jeromy's avatar
Jeromy committed
556
		if err != nil {
Overbool's avatar
Overbool committed
557
			return err
Jeromy's avatar
Jeromy committed
558 559 560 561
		}

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

Steven Allen's avatar
Steven Allen committed
565
		rfd, err := fi.Open(mfs.Flags{Read: true})
566
		if err != nil {
Overbool's avatar
Overbool committed
567
			return err
568 569 570 571
		}

		defer rfd.Close()

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

577
		filen, err := rfd.Size()
Jeromy's avatar
Jeromy committed
578
		if err != nil {
Overbool's avatar
Overbool committed
579
			return err
Jeromy's avatar
Jeromy committed
580 581 582
		}

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

586
		_, err = rfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
587
		if err != nil {
Overbool's avatar
Overbool committed
588
			return err
Jeromy's avatar
Jeromy committed
589
		}
590

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

Jeromy's avatar
Jeromy committed
603 604 605 606 607 608 609 610 611 612 613 614 615
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
616
var filesMvCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
617
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
618
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
619 620 621 622 623 624 625
		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
626
`,
Jeromy's avatar
Jeromy committed
627 628
	},

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

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

Overbool's avatar
Overbool committed
648
		return mfs.Mv(nd.FilesRoot, src, dst)
Jeromy's avatar
Jeromy committed
649 650 651
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
652 653 654 655 656 657 658 659
const (
	filesCreateOptionName    = "create"
	filesParentsOptionName   = "parents"
	filesTruncateOptionName  = "truncate"
	filesRawLeavesOptionName = "raw-leaves"
	filesFlushOptionName     = "flush"
)

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

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

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

678 679 680 681
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.

682
EXAMPLE:
Jeromy's avatar
Jeromy committed
683

Jeromy's avatar
Jeromy committed
684 685
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
686

687
WARNING:
688

689 690 691
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
692
`,
Jeromy's avatar
Jeromy committed
693
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
694 695 696
	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
697
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
698
	Options: []cmdkit.Option{
699
		cmdkit.Int64Option(filesOffsetOptionName, "o", "Byte offset to begin writing at."),
Kejie Zhang's avatar
Kejie Zhang committed
700 701 702
		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."),
703
		cmdkit.Int64Option(filesCountOptionName, "n", "Maximum number of bytes to read."),
Kejie Zhang's avatar
Kejie Zhang committed
704
		cmdkit.BoolOption(filesRawLeavesOptionName, "Use raw blocks for newly created leaf nodes. (experimental)"),
705 706
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
707
	},
keks's avatar
keks committed
708
	Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) (retErr error) {
709
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
710
		if err != nil {
keks's avatar
keks committed
711
			return err
Jeromy's avatar
Jeromy committed
712 713
		}

Kejie Zhang's avatar
Kejie Zhang committed
714 715 716 717 718
		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)
719

720
		prefix, err := getPrefixNew(req)
721
		if err != nil {
keks's avatar
keks committed
722
			return err
723
		}
Jeromy's avatar
Jeromy committed
724

725
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
726
		if err != nil {
keks's avatar
keks committed
727
			return err
Jeromy's avatar
Jeromy committed
728 729
		}

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

735 736 737
		if mkParents {
			err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix)
			if err != nil {
keks's avatar
keks committed
738
				return err
739 740 741
			}
		}

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

Steven Allen's avatar
Steven Allen committed
750
		wfd, err := fi.Open(mfs.Flags{Write: true, Sync: flush})
751
		if err != nil {
keks's avatar
keks committed
752
			return err
753
		}
Jeromy's avatar
Jeromy committed
754

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

Jeromy's avatar
Jeromy committed
766
		if trunc {
767
			if err := wfd.Truncate(0); err != nil {
keks's avatar
keks committed
768
				return err
Jeromy's avatar
Jeromy committed
769 770 771
			}
		}

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

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

783 784 785 786
		var r io.Reader
		r, err = cmdenv.GetFileArg(req.Files.Entries())
		if err != nil {
			return err
Jeromy's avatar
Jeromy committed
787
		}
zramsay's avatar
zramsay committed
788 789 790 791
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
792
		_, err = io.Copy(wfd, r)
keks's avatar
keks committed
793
		return err
Jeromy's avatar
Jeromy committed
794 795 796
	},
}

Overbool's avatar
Overbool committed
797
var filesMkdirCmd = &cmds.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
	Options: []cmdkit.Option{
Kejie Zhang's avatar
Kejie Zhang committed
819
		cmdkit.BoolOption(filesParentsOptionName, "p", "No error if existing, make parent directories as needed."),
820 821
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
822
	},
Overbool's avatar
Overbool committed
823 824
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		n, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
825
		if err != nil {
Overbool's avatar
Overbool committed
826
			return err
Jeromy's avatar
Jeromy committed
827 828
		}

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

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

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

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

Overbool's avatar
Overbool committed
849
		return err
Jeromy's avatar
Jeromy committed
850 851 852
	},
}

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

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

Overbool's avatar
Overbool committed
875
		return mfs.FlushPath(nd.FilesRoot, path)
Jeromy's avatar
Jeromy committed
876 877 878
	},
}

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

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

Overbool's avatar
Overbool committed
904
		flush, _ := req.Options[filesFlushOptionName].(bool)
Kevin Atkinson's avatar
Kevin Atkinson committed
905 906 907

		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
908
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
909
		}
keks's avatar
keks committed
910

Overbool's avatar
Overbool committed
911
		return updatePath(nd.FilesRoot, path, prefix, flush)
Kevin Atkinson's avatar
Kevin Atkinson committed
912 913 914
	},
}

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

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

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

	if flush {
		nd.Flush()
	}

	return nil
}

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

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

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

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

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

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

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

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

Overbool's avatar
Overbool committed
1001
			return pdir.Flush()
Jeromy's avatar
Jeromy committed
1002 1003
		}

1004 1005 1006
		// 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
1007
		if err != nil {
Overbool's avatar
Overbool committed
1008
			return err
Jeromy's avatar
Jeromy committed
1009 1010
		}

1011
		dashr, _ := req.Options[recursiveOptionName].(bool)
1012 1013

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

1020 1021
		err = pdir.Unlink(name)
		if err != nil {
Overbool's avatar
Overbool committed
1022
			return err
Jeromy's avatar
Jeromy committed
1023
		}
1024

Overbool's avatar
Overbool committed
1025
		return pdir.Flush()
Jeromy's avatar
Jeromy committed
1026 1027 1028
	},
}

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

	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
1058 1059 1060
func getPrefix(req *cmds.Request) (cid.Builder, error) {
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073

	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
1074

1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
	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
}

1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
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,
	})
}

1100
func getFileHandle(r *mfs.Root, path string, create bool, builder cid.Builder) (*mfs.File, error) {
Jeromy's avatar
Jeromy committed
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
	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 {
1119
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1120 1121 1122 1123 1124 1125
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1126 1127
		if builder == nil {
			builder = pdir.GetCidBuilder()
1128
		}
Jeromy's avatar
Jeromy committed
1129

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

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1152 1153 1154

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

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

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