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

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

Jakub Sztandera's avatar
Jakub Sztandera committed
16 17 18 19
	"github.com/dustin/go-humanize"
	bservice "github.com/ipfs/go-blockservice"
	cid "github.com/ipfs/go-cid"
	cidenc "github.com/ipfs/go-cidutil/cidenc"
20 21
	cmds "github.com/ipfs/go-ipfs-cmds"
	offline "github.com/ipfs/go-ipfs-exchange-offline"
Jakub Sztandera's avatar
Jakub Sztandera committed
22 23 24 25 26
	ipld "github.com/ipfs/go-ipld-format"
	logging "github.com/ipfs/go-log"
	dag "github.com/ipfs/go-merkledag"
	"github.com/ipfs/go-mfs"
	ft "github.com/ipfs/go-unixfs"
27
	iface "github.com/ipfs/interface-go-ipfs-core"
28
	path "github.com/ipfs/interface-go-ipfs-core/path"
Jakub Sztandera's avatar
Jakub Sztandera committed
29
	mh "github.com/multiformats/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{
Steven Allen's avatar
Steven Allen committed
36
	Helptext: cmds.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 43 44 45 46 47
The files facility interacts with MFS (Mutable File System). MFS acts as a
single, dynamic filesystem mount. MFS has a root CID which is transparently
updated when a change happens (and can be checked with "ipfs files stat /").

All files and folders within MFS are respected and will not be cleaned up
during garbage collections. MFS is independent from the list of pinned items
48
("ipfs pin ls"). Calls to "ipfs pin add" and "ipfs pin rm" will add and remove
Steven Allen's avatar
Steven Allen committed
49
pins independently of MFS. If MFS content that was
50 51 52 53 54 55
additionally pinned is removed by calling "ipfs files rm", it will still
remain pinned.

Content added with "ipfs add" (which by default also becomes pinned), is not
added to MFS. Any content can be put into MFS with the command "ipfs files cp
/ipfs/<cid> /some/path/".
56 57


58
NOTE:
59 60 61 62 63 64 65
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
66 67
`,
	},
Steven Allen's avatar
Steven Allen committed
68 69
	Options: []cmds.Option{
		cmds.BoolOption(filesFlushOptionName, "f", "Flush target and ancestors after write.").WithDefault(true),
Jeromy's avatar
Jeromy committed
70
	},
Jeromy's avatar
Jeromy committed
71
	Subcommands: map[string]*cmds.Command{
Overbool's avatar
Overbool committed
72
		"read":  filesReadCmd,
73
		"write": filesWriteCmd,
Overbool's avatar
Overbool committed
74 75 76 77
		"mv":    filesMvCmd,
		"cp":    filesCpCmd,
		"ls":    filesLsCmd,
		"mkdir": filesMkdirCmd,
78
		"stat":  filesStatCmd,
Overbool's avatar
Overbool committed
79 80 81
		"rm":    filesRmCmd,
		"flush": filesFlushCmd,
		"chcid": filesChcidCmd,
Jeromy's avatar
Jeromy committed
82 83 84
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
85 86 87 88 89
const (
	filesCidVersionOptionName = "cid-version"
	filesHashOptionName       = "hash"
)

Steven Allen's avatar
Steven Allen committed
90 91
var cidVersionOption = cmds.IntOption(filesCidVersionOptionName, "cid-ver", "Cid version to use. (experimental)")
var hashOption = cmds.StringOption(filesHashOptionName, "Hash function to use. Will set Cid version to 1 if used. (experimental)")
92

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

Michael Muré's avatar
Michael Muré committed
95
type statOutput struct {
96 97 98 99 100
	Hash           string
	Size           uint64
	CumulativeSize uint64
	Blocks         int
	Type           string
Michael Muré's avatar
Michael Muré committed
101
	WithLocality   bool   `json:",omitempty"`
102 103 104 105
	Local          bool   `json:",omitempty"`
	SizeLocal      uint64 `json:",omitempty"`
}

Kejie Zhang's avatar
Kejie Zhang committed
106 107
const (
	defaultStatFormat = `<hash>
keks's avatar
keks committed
108 109 110 111
Size: <size>
CumulativeSize: <cumulsize>
ChildBlocks: <childs>
Type: <type>`
Kejie Zhang's avatar
Kejie Zhang committed
112 113 114 115
	filesFormatOptionName    = "format"
	filesSizeOptionName      = "size"
	filesWithLocalOptionName = "with-local"
)
keks's avatar
keks committed
116

117
var filesStatCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
118
	Helptext: cmds.HelpText{
rht's avatar
rht committed
119
		Tagline: "Display file status.",
Jeromy's avatar
Jeromy committed
120 121
	},

Steven Allen's avatar
Steven Allen committed
122 123
	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, false, "Path to node to stat."),
Jeromy's avatar
Jeromy committed
124
	},
Steven Allen's avatar
Steven Allen committed
125 126
	Options: []cmds.Option{
		cmds.StringOption(filesFormatOptionName, "Print statistics in given format. Allowed tokens: "+
keks's avatar
keks committed
127
			"<hash> <size> <cumulsize> <type> <childs>. Conflicts with other format options.").WithDefault(defaultStatFormat),
Steven Allen's avatar
Steven Allen committed
128 129 130
		cmds.BoolOption(filesHashOptionName, "Print only hash. Implies '--format=<hash>'. Conflicts with other format options."),
		cmds.BoolOption(filesSizeOptionName, "Print only size. Implies '--format=<cumulsize>'. Conflicts with other format options."),
		cmds.BoolOption(filesWithLocalOptionName, "Compute the amount of the dag that is local, and if possible the total size"),
131
	},
keks's avatar
keks committed
132
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
133 134 135

		_, err := statGetFormatOptions(req)
		if err != nil {
Steven Allen's avatar
Steven Allen committed
136
			return cmds.Errorf(cmds.ErrClient, err.Error())
137 138
		}

139
		node, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
140
		if err != nil {
keks's avatar
keks committed
141
			return err
Jeromy's avatar
Jeromy committed
142 143
		}

144
		api, err := cmdenv.GetApi(env, req)
145 146 147 148
		if err != nil {
			return err
		}

149
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
150
		if err != nil {
keks's avatar
keks committed
151
			return err
Jeromy's avatar
Jeromy committed
152 153
		}

Kejie Zhang's avatar
Kejie Zhang committed
154
		withLocal, _ := req.Options[filesWithLocalOptionName].(bool)
155

156 157 158 159 160
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

161 162 163 164 165 166 167 168 169 170 171
		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
		}

172
		nd, err := getNodeFromPath(req.Context, node, api, path)
Jeromy's avatar
Jeromy committed
173
		if err != nil {
keks's avatar
keks committed
174
			return err
Jeromy's avatar
Jeromy committed
175 176
		}

177
		o, err := statNode(nd, enc)
Jeromy's avatar
Jeromy committed
178
		if err != nil {
keks's avatar
keks committed
179
			return err
Jeromy's avatar
Jeromy committed
180 181
		}

182
		if !withLocal {
keks's avatar
keks committed
183
			return cmds.EmitOnce(res, o)
184 185 186
		}

		local, sizeLocal, err := walkBlock(req.Context, dagserv, nd)
187 188 189
		if err != nil {
			return err
		}
190 191 192 193 194

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

keks's avatar
keks committed
195
		return cmds.EmitOnce(res, o)
Jeromy's avatar
Jeromy committed
196
	},
197
	Encoders: cmds.EncoderMap{
198
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *statOutput) error {
199
			s, _ := statGetFormatOptions(req)
200 201 202 203 204 205
			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)

206 207 208 209 210 211 212 213 214 215 216
			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
217
		}),
Jeromy's avatar
Jeromy committed
218
	},
Michael Muré's avatar
Michael Muré committed
219
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
220 221
}

222 223 224 225
func moreThanOne(a, b, c bool) bool {
	return a && b || b && c || a && c
}

226
func statGetFormatOptions(req *cmds.Request) (string, error) {
227

Kejie Zhang's avatar
Kejie Zhang committed
228 229 230
	hash, _ := req.Options[filesHashOptionName].(bool)
	size, _ := req.Options[filesSizeOptionName].(bool)
	format, _ := req.Options[filesFormatOptionName].(string)
231

keks's avatar
keks committed
232
	if moreThanOne(hash, size, format != defaultStatFormat) {
Michael Muré's avatar
Michael Muré committed
233
		return "", errFormat
234 235 236
	}

	if hash {
237 238 239 240 241
		return "<hash>", nil
	} else if size {
		return "<cumulsize>", nil
	} else {
		return format, nil
242 243 244
	}
}

245
func statNode(nd ipld.Node, enc cidenc.Encoder) (*statOutput, error) {
Jeromy's avatar
Jeromy committed
246
	c := nd.Cid()
Jeromy's avatar
Jeromy committed
247 248 249 250 251 252

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

253 254
	switch n := nd.(type) {
	case *dag.ProtoNode:
Overbool's avatar
Overbool committed
255
		d, err := ft.FSNodeFromBytes(n.Data())
256 257 258 259 260
		if err != nil {
			return nil, err
		}

		var ndtype string
Overbool's avatar
Overbool committed
261
		switch d.Type() {
262
		case ft.TDirectory, ft.THAMTShard:
263
			ndtype = "directory"
264
		case ft.TFile, ft.TMetadata, ft.TRaw:
265 266
			ndtype = "file"
		default:
Overbool's avatar
Overbool committed
267
			return nil, fmt.Errorf("unrecognized node type: %s", d.Type())
268 269
		}

Michael Muré's avatar
Michael Muré committed
270
		return &statOutput{
271
			Hash:           enc.Encode(c),
272
			Blocks:         len(nd.Links()),
Overbool's avatar
Overbool committed
273
			Size:           d.FileSize(),
274 275 276 277
			CumulativeSize: cumulsize,
			Type:           ndtype,
		}, nil
	case *dag.RawNode:
Michael Muré's avatar
Michael Muré committed
278
		return &statOutput{
279
			Hash:           enc.Encode(c),
280 281 282 283 284
			Blocks:         0,
			Size:           cumulsize,
			CumulativeSize: cumulsize,
			Type:           "file",
		}, nil
Jeromy's avatar
Jeromy committed
285
	default:
286
		return nil, fmt.Errorf("not unixfs node (proto or raw)")
Jeromy's avatar
Jeromy committed
287
	}
Jeromy's avatar
Jeromy committed
288 289
}

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
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
322
var filesCpCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
323
	Helptext: cmds.HelpText{
rht's avatar
rht committed
324
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
325
	},
Steven Allen's avatar
Steven Allen committed
326 327 328
	Arguments: []cmds.Argument{
		cmds.StringArg("source", true, false, "Source object to copy."),
		cmds.StringArg("dest", true, false, "Destination to copy object to."),
Jeromy's avatar
Jeromy committed
329
	},
Overbool's avatar
Overbool committed
330 331
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
332
		if err != nil {
Overbool's avatar
Overbool committed
333
			return err
Jeromy's avatar
Jeromy committed
334 335
		}

336
		api, err := cmdenv.GetApi(env, req)
337
		if err != nil {
Overbool's avatar
Overbool committed
338
			return err
339 340
		}

341
		flush, _ := req.Options[filesFlushOptionName].(bool)
342

Overbool's avatar
Overbool committed
343
		src, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
344
		if err != nil {
Overbool's avatar
Overbool committed
345
			return err
Jeromy's avatar
Jeromy committed
346
		}
347 348
		src = strings.TrimRight(src, "/")

Overbool's avatar
Overbool committed
349
		dst, err := checkPath(req.Arguments[1])
Jeromy's avatar
Jeromy committed
350
		if err != nil {
Overbool's avatar
Overbool committed
351
			return err
Jeromy's avatar
Jeromy committed
352
		}
Jeromy's avatar
Jeromy committed
353

354 355 356 357
		if dst[len(dst)-1] == '/' {
			dst += gopath.Base(src)
		}

Overbool's avatar
Overbool committed
358
		node, err := getNodeFromPath(req.Context, nd, api, src)
Jeromy's avatar
Jeromy committed
359
		if err != nil {
Overbool's avatar
Overbool committed
360
			return fmt.Errorf("cp: cannot get node from path %s: %s", src, err)
Jeromy's avatar
Jeromy committed
361 362
		}

Overbool's avatar
Overbool committed
363
		err = mfs.PutNode(nd.FilesRoot, dst, node)
Jeromy's avatar
Jeromy committed
364
		if err != nil {
Overbool's avatar
Overbool committed
365
			return fmt.Errorf("cp: cannot put node in path %s: %s", dst, err)
Jeromy's avatar
Jeromy committed
366
		}
367 368

		if flush {
369
			_, err := mfs.FlushPath(req.Context, nd.FilesRoot, dst)
370
			if err != nil {
Overbool's avatar
Overbool committed
371
				return fmt.Errorf("cp: cannot flush the created file %s: %s", dst, err)
372 373
			}
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
374

Overbool's avatar
Overbool committed
375
		return nil
Jeromy's avatar
Jeromy committed
376 377 378
	},
}

379
func getNodeFromPath(ctx context.Context, node *core.IpfsNode, api iface.CoreAPI, p string) (ipld.Node, error) {
Jeromy's avatar
Jeromy committed
380 381
	switch {
	case strings.HasPrefix(p, "/ipfs/"):
382
		return api.ResolveNode(ctx, path.New(p))
Jeromy's avatar
Jeromy committed
383 384 385 386 387 388 389 390 391 392
	default:
		fsn, err := mfs.Lookup(node.FilesRoot, p)
		if err != nil {
			return nil, err
		}

		return fsn.GetNode()
	}
}

Michael Muré's avatar
Michael Muré committed
393
type filesLsOutput struct {
Jeromy's avatar
Jeromy committed
394 395 396
	Entries []mfs.NodeListing
}

Kejie Zhang's avatar
Kejie Zhang committed
397
const (
398
	longOptionName     = "long"
Kejie Zhang's avatar
Kejie Zhang committed
399 400 401
	dontSortOptionName = "U"
)

Overbool's avatar
Overbool committed
402
var filesLsCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
403
	Helptext: cmds.HelpText{
404
		Tagline: "List directories in the local mutable namespace.",
Jeromy's avatar
Jeromy committed
405
		ShortDescription: `
406
List directories in the local mutable namespace.
Jeromy's avatar
Jeromy committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422

Examples:

    $ ipfs files ls /welcome/docs/
    about
    contact
    help
    quick-start
    readme
    security-notes

    $ ipfs files ls /myfiles/a/b/c/d
    foo
    bar
`,
	},
Steven Allen's avatar
Steven Allen committed
423 424
	Arguments: []cmds.Argument{
		cmds.StringArg("path", false, false, "Path to show listing for. Defaults to '/'."),
Jeromy's avatar
Jeromy committed
425
	},
Steven Allen's avatar
Steven Allen committed
426
	Options: []cmds.Option{
427
		cmds.BoolOption(longOptionName, "l", "Use long listing format."),
Steven Allen's avatar
Steven Allen committed
428
		cmds.BoolOption(dontSortOptionName, "Do not sort; list entries in directory order."),
Jeromy's avatar
Jeromy committed
429
	},
Overbool's avatar
Overbool committed
430
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
431 432
		var arg string

Overbool's avatar
Overbool committed
433
		if len(req.Arguments) == 0 {
434 435
			arg = "/"
		} else {
Overbool's avatar
Overbool committed
436
			arg = req.Arguments[0]
437 438 439
		}

		path, err := checkPath(arg)
Jeromy's avatar
Jeromy committed
440
		if err != nil {
Overbool's avatar
Overbool committed
441
			return err
Jeromy's avatar
Jeromy committed
442 443
		}

Overbool's avatar
Overbool committed
444
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
445
		if err != nil {
Overbool's avatar
Overbool committed
446
			return err
Jeromy's avatar
Jeromy committed
447 448 449 450
		}

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

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

456 457 458 459 460
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

Jeromy's avatar
Jeromy committed
461 462
		switch fsn := fsn.(type) {
		case *mfs.Directory:
Jeromy's avatar
Jeromy committed
463 464
			if !long {
				var output []mfs.NodeListing
Overbool's avatar
Overbool committed
465
				names, err := fsn.ListNames(req.Context)
466
				if err != nil {
Overbool's avatar
Overbool committed
467
					return err
468 469 470
				}

				for _, name := range names {
Jeromy's avatar
Jeromy committed
471
					output = append(output, mfs.NodeListing{
Jeromy's avatar
Jeromy committed
472
						Name: name,
Jeromy's avatar
Jeromy committed
473 474
					})
				}
475
				return cmds.EmitOnce(res, &filesLsOutput{output})
Jeromy's avatar
Jeromy committed
476
			}
477 478 479 480
			listing, err := fsn.List(req.Context)
			if err != nil {
				return err
			}
481
			return cmds.EmitOnce(res, &filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
482
		case *mfs.File:
rht's avatar
rht committed
483
			_, name := gopath.Split(path)
Overbool's avatar
Overbool committed
484
			out := &filesLsOutput{[]mfs.NodeListing{{Name: name}}}
485 486 487 488 489
			if long {
				out.Entries[0].Type = int(fsn.Type())

				size, err := fsn.Size()
				if err != nil {
Overbool's avatar
Overbool committed
490
					return err
491 492 493 494 495
				}
				out.Entries[0].Size = size

				nd, err := fsn.GetNode()
				if err != nil {
Overbool's avatar
Overbool committed
496
					return err
497
				}
498
				out.Entries[0].Hash = enc.Encode(nd.Cid())
499
			}
500
			return cmds.EmitOnce(res, out)
Jeromy's avatar
Jeromy committed
501
		default:
Overbool's avatar
Overbool committed
502
			return errors.New("unrecognized type")
Jeromy's avatar
Jeromy committed
503 504
		}
	},
Overbool's avatar
Overbool committed
505 506 507
	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
508 509 510 511 512 513
			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
514
			long, _ := req.Options[longOptionName].(bool)
Jeromy's avatar
Jeromy committed
515 516
			for _, o := range out.Entries {
				if long {
Overbool's avatar
Overbool committed
517 518 519
					if o.Type == int(mfs.TDir) {
						o.Name += "/"
					}
Overbool's avatar
Overbool committed
520
					fmt.Fprintf(w, "%s\t%s\t%d\n", o.Name, o.Hash, o.Size)
Jeromy's avatar
Jeromy committed
521
				} else {
Overbool's avatar
Overbool committed
522
					fmt.Fprintf(w, "%s\n", o.Name)
Jeromy's avatar
Jeromy committed
523 524
				}
			}
Overbool's avatar
Overbool committed
525 526 527

			return nil
		}),
Jeromy's avatar
Jeromy committed
528
	},
Michael Muré's avatar
Michael Muré committed
529
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
530 531
}

Kejie Zhang's avatar
Kejie Zhang committed
532 533 534 535 536
const (
	filesOffsetOptionName = "offset"
	filesCountOptionName  = "count"
)

Overbool's avatar
Overbool committed
537
var filesReadCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
538
	Helptext: cmds.HelpText{
rht's avatar
rht committed
539
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
540
		ShortDescription: `
541 542
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
543 544 545 546 547

Examples:

    $ ipfs files read /test/hello
    hello
Jeromy's avatar
Jeromy committed
548
        `,
Jeromy's avatar
Jeromy committed
549 550
	},

Steven Allen's avatar
Steven Allen committed
551 552
	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
553
	},
Steven Allen's avatar
Steven Allen committed
554 555 556
	Options: []cmds.Option{
		cmds.Int64Option(filesOffsetOptionName, "o", "Byte offset to begin reading from."),
		cmds.Int64Option(filesCountOptionName, "n", "Maximum number of bytes to read."),
Jeromy's avatar
Jeromy committed
557
	},
Overbool's avatar
Overbool committed
558 559
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
560
		if err != nil {
Overbool's avatar
Overbool committed
561
			return err
Jeromy's avatar
Jeromy committed
562 563
		}

Overbool's avatar
Overbool committed
564
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
565
		if err != nil {
Overbool's avatar
Overbool committed
566
			return err
Jeromy's avatar
Jeromy committed
567 568
		}

Overbool's avatar
Overbool committed
569
		fsn, err := mfs.Lookup(nd.FilesRoot, path)
Jeromy's avatar
Jeromy committed
570
		if err != nil {
Overbool's avatar
Overbool committed
571
			return err
Jeromy's avatar
Jeromy committed
572 573 574 575
		}

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

Steven Allen's avatar
Steven Allen committed
579
		rfd, err := fi.Open(mfs.Flags{Read: true})
580
		if err != nil {
Overbool's avatar
Overbool committed
581
			return err
582 583 584 585
		}

		defer rfd.Close()

Overbool's avatar
Overbool committed
586
		offset, _ := req.Options[offsetOptionName].(int64)
Jeromy's avatar
Jeromy committed
587
		if offset < 0 {
Overbool's avatar
Overbool committed
588
			return fmt.Errorf("cannot specify negative offset")
Jeromy's avatar
Jeromy committed
589 590
		}

591
		filen, err := rfd.Size()
Jeromy's avatar
Jeromy committed
592
		if err != nil {
Overbool's avatar
Overbool committed
593
			return err
Jeromy's avatar
Jeromy committed
594 595 596
		}

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

600
		_, err = rfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
601
		if err != nil {
Overbool's avatar
Overbool committed
602
			return err
Jeromy's avatar
Jeromy committed
603
		}
604

Overbool's avatar
Overbool committed
605 606
		var r io.Reader = &contextReaderWrapper{R: rfd, ctx: req.Context}
		count, found := req.Options[filesCountOptionName].(int64)
Jeromy's avatar
Jeromy committed
607 608
		if found {
			if count < 0 {
Overbool's avatar
Overbool committed
609
				return fmt.Errorf("cannot specify negative 'count'")
Jeromy's avatar
Jeromy committed
610
			}
611
			r = io.LimitReader(r, int64(count))
Jeromy's avatar
Jeromy committed
612
		}
Overbool's avatar
Overbool committed
613
		return res.Emit(r)
Jeromy's avatar
Jeromy committed
614 615 616
	},
}

Jeromy's avatar
Jeromy committed
617 618 619 620 621 622 623 624 625 626 627 628 629
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
630
var filesMvCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
631
	Helptext: cmds.HelpText{
rht's avatar
rht committed
632
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
633 634 635 636 637 638 639
		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
640
`,
Jeromy's avatar
Jeromy committed
641 642
	},

Steven Allen's avatar
Steven Allen committed
643 644 645
	Arguments: []cmds.Argument{
		cmds.StringArg("source", true, false, "Source file to move."),
		cmds.StringArg("dest", true, false, "Destination path for file to be moved to."),
Jeromy's avatar
Jeromy committed
646
	},
Overbool's avatar
Overbool committed
647 648
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
649
		if err != nil {
Overbool's avatar
Overbool committed
650
			return err
Jeromy's avatar
Jeromy committed
651 652
		}

653 654
		flush, _ := req.Options[filesFlushOptionName].(bool)

Overbool's avatar
Overbool committed
655
		src, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
656
		if err != nil {
Overbool's avatar
Overbool committed
657
			return err
Jeromy's avatar
Jeromy committed
658
		}
Overbool's avatar
Overbool committed
659
		dst, err := checkPath(req.Arguments[1])
Jeromy's avatar
Jeromy committed
660
		if err != nil {
Overbool's avatar
Overbool committed
661
			return err
Jeromy's avatar
Jeromy committed
662
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
663

664 665
		err = mfs.Mv(nd.FilesRoot, src, dst)
		if err == nil && flush {
666
			_, err = mfs.FlushPath(req.Context, nd.FilesRoot, "/")
667 668
		}
		return err
Jeromy's avatar
Jeromy committed
669 670 671
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
672 673 674 675 676 677 678 679
const (
	filesCreateOptionName    = "create"
	filesParentsOptionName   = "parents"
	filesTruncateOptionName  = "truncate"
	filesRawLeavesOptionName = "raw-leaves"
	filesFlushOptionName     = "flush"
)

680
var filesWriteCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
681
	Helptext: cmds.HelpText{
rht's avatar
rht committed
682
		Tagline: "Write to a mutable file in a given filesystem.",
Jeromy's avatar
Jeromy committed
683 684
		ShortDescription: `
Write data to a file in a given filesystem. This command allows you to specify
685 686
a beginning offset to write to. The entire length of the input will be
written.
Jeromy's avatar
Jeromy committed
687

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

691 692 693 694 695 696 697
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.

698 699 700 701
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.

702
EXAMPLE:
Jeromy's avatar
Jeromy committed
703

Jeromy's avatar
Jeromy committed
704 705
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
706

707
WARNING:
708

709 710 711
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
712
`,
Jeromy's avatar
Jeromy committed
713
	},
Steven Allen's avatar
Steven Allen committed
714 715 716 717 718 719 720 721 722 723 724
	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, false, "Path to write to."),
		cmds.FileArg("data", true, false, "Data to write.").EnableStdin(),
	},
	Options: []cmds.Option{
		cmds.Int64Option(filesOffsetOptionName, "o", "Byte offset to begin writing at."),
		cmds.BoolOption(filesCreateOptionName, "e", "Create the file if it does not exist."),
		cmds.BoolOption(filesParentsOptionName, "p", "Make parent directories as needed."),
		cmds.BoolOption(filesTruncateOptionName, "t", "Truncate the file to size zero before writing."),
		cmds.Int64Option(filesCountOptionName, "n", "Maximum number of bytes to read."),
		cmds.BoolOption(filesRawLeavesOptionName, "Use raw blocks for newly created leaf nodes. (experimental)"),
725 726
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
727
	},
keks's avatar
keks committed
728
	Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) (retErr error) {
729
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
730
		if err != nil {
keks's avatar
keks committed
731
			return err
Jeromy's avatar
Jeromy committed
732 733
		}

Kejie Zhang's avatar
Kejie Zhang committed
734 735 736 737 738
		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)
739

740
		prefix, err := getPrefixNew(req)
741
		if err != nil {
keks's avatar
keks committed
742
			return err
743
		}
Jeromy's avatar
Jeromy committed
744

745
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
746
		if err != nil {
keks's avatar
keks committed
747
			return err
Jeromy's avatar
Jeromy committed
748 749
		}

750
		offset, _ := req.Options[filesOffsetOptionName].(int64)
Jeromy's avatar
Jeromy committed
751
		if offset < 0 {
keks's avatar
keks committed
752
			return fmt.Errorf("cannot have negative write offset")
Jeromy's avatar
Jeromy committed
753 754
		}

755 756 757
		if mkParents {
			err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix)
			if err != nil {
keks's avatar
keks committed
758
				return err
759 760 761
			}
		}

762
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
763
		if err != nil {
keks's avatar
keks committed
764
			return err
Jeromy's avatar
Jeromy committed
765
		}
766 767 768
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
769

Steven Allen's avatar
Steven Allen committed
770
		wfd, err := fi.Open(mfs.Flags{Write: true, Sync: flush})
771
		if err != nil {
keks's avatar
keks committed
772
			return err
773
		}
Jeromy's avatar
Jeromy committed
774

775 776 777
		defer func() {
			err := wfd.Close()
			if err != nil {
keks's avatar
keks committed
778 779 780
				if retErr == nil {
					retErr = err
				} else {
781
					flog.Error("files: error closing file mfs file descriptor", err)
keks's avatar
keks committed
782
				}
783 784
			}
		}()
785

Jeromy's avatar
Jeromy committed
786
		if trunc {
787
			if err := wfd.Truncate(0); err != nil {
keks's avatar
keks committed
788
				return err
Jeromy's avatar
Jeromy committed
789 790 791
			}
		}

792
		count, countfound := req.Options[filesCountOptionName].(int64)
Jeromy's avatar
Jeromy committed
793
		if countfound && count < 0 {
keks's avatar
keks committed
794
			return fmt.Errorf("cannot have negative byte count")
Jeromy's avatar
Jeromy committed
795
		}
Jeromy's avatar
Jeromy committed
796

797
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
798
		if err != nil {
799
			flog.Error("seekfail: ", err)
keks's avatar
keks committed
800
			return err
Jeromy's avatar
Jeromy committed
801 802
		}

803 804 805 806
		var r io.Reader
		r, err = cmdenv.GetFileArg(req.Files.Entries())
		if err != nil {
			return err
Jeromy's avatar
Jeromy committed
807
		}
zramsay's avatar
zramsay committed
808 809 810 811
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
812
		_, err = io.Copy(wfd, r)
keks's avatar
keks committed
813
		return err
Jeromy's avatar
Jeromy committed
814 815 816
	},
}

Overbool's avatar
Overbool committed
817
var filesMkdirCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
818
	Helptext: cmds.HelpText{
rht's avatar
rht committed
819
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
820 821 822
		ShortDescription: `
Create the directory if it does not already exist.

823 824 825
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

826
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
827 828 829

Examples:

830 831
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
832 833 834
`,
	},

Steven Allen's avatar
Steven Allen committed
835 836
	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, false, "Path to dir to make."),
Jeromy's avatar
Jeromy committed
837
	},
Steven Allen's avatar
Steven Allen committed
838 839
	Options: []cmds.Option{
		cmds.BoolOption(filesParentsOptionName, "p", "No error if existing, make parent directories as needed."),
840 841
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
842
	},
Overbool's avatar
Overbool committed
843 844
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		n, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
845
		if err != nil {
Overbool's avatar
Overbool committed
846
			return err
Jeromy's avatar
Jeromy committed
847 848
		}

Overbool's avatar
Overbool committed
849 850
		dashp, _ := req.Options[filesParentsOptionName].(bool)
		dirtomake, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
851
		if err != nil {
Overbool's avatar
Overbool committed
852
			return err
Jeromy's avatar
Jeromy committed
853 854
		}

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

857 858
		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
859
			return err
860 861 862
		}
		root := n.FilesRoot

863
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
864 865 866
			Mkparents:  dashp,
			Flush:      flush,
			CidBuilder: prefix,
867
		})
keks's avatar
keks committed
868

Overbool's avatar
Overbool committed
869
		return err
Jeromy's avatar
Jeromy committed
870 871 872
	},
}

873 874 875 876
type flushRes struct {
	Cid string
}

Overbool's avatar
Overbool committed
877
var filesFlushCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
878
	Helptext: cmds.HelpText{
879
		Tagline: "Flush a given path's data to disk.",
Jeromy's avatar
Jeromy committed
880
		ShortDescription: `
881
Flush a given path to disk. This is only useful when other commands
Jeromy's avatar
Jeromy committed
882 883 884
are run with the '--flush=false'.
`,
	},
Steven Allen's avatar
Steven Allen committed
885 886
	Arguments: []cmds.Argument{
		cmds.StringArg("path", false, false, "Path to flush. Default: '/'."),
Jeromy's avatar
Jeromy committed
887
	},
Overbool's avatar
Overbool committed
888 889
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
890
		if err != nil {
Overbool's avatar
Overbool committed
891
			return err
Jeromy's avatar
Jeromy committed
892 893
		}

894 895 896 897 898
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

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

904 905 906 907 908 909
		n, err := mfs.FlushPath(req.Context, nd.FilesRoot, path)
		if err != nil {
			return err
		}

		return cmds.EmitOnce(res, &flushRes{enc.Encode(n.Cid())})
Jeromy's avatar
Jeromy committed
910
	},
911
	Type: flushRes{},
Jeromy's avatar
Jeromy committed
912 913
}

Overbool's avatar
Overbool committed
914
var filesChcidCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
915
	Helptext: cmds.HelpText{
916
		Tagline: "Change the cid version or hash function of the root node of a given path.",
Kevin Atkinson's avatar
Kevin Atkinson committed
917
		ShortDescription: `
918
Change the cid version or hash function of the root node of a given path.
Kevin Atkinson's avatar
Kevin Atkinson committed
919 920
`,
	},
Steven Allen's avatar
Steven Allen committed
921 922
	Arguments: []cmds.Argument{
		cmds.StringArg("path", false, false, "Path to change. Default: '/'."),
Kevin Atkinson's avatar
Kevin Atkinson committed
923
	},
Steven Allen's avatar
Steven Allen committed
924
	Options: []cmds.Option{
925 926 927
		cidVersionOption,
		hashOption,
	},
Overbool's avatar
Overbool committed
928 929
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Kevin Atkinson's avatar
Kevin Atkinson committed
930
		if err != nil {
Overbool's avatar
Overbool committed
931
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
932 933 934
		}

		path := "/"
Overbool's avatar
Overbool committed
935 936
		if len(req.Arguments) > 0 {
			path = req.Arguments[0]
Kevin Atkinson's avatar
Kevin Atkinson committed
937 938
		}

Overbool's avatar
Overbool committed
939
		flush, _ := req.Options[filesFlushOptionName].(bool)
Kevin Atkinson's avatar
Kevin Atkinson committed
940 941 942

		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
943
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
944
		}
keks's avatar
keks committed
945

946 947
		err = updatePath(nd.FilesRoot, path, prefix)
		if err == nil && flush {
948
			_, err = mfs.FlushPath(req.Context, nd.FilesRoot, path)
949 950
		}
		return err
Kevin Atkinson's avatar
Kevin Atkinson committed
951 952 953
	},
}

954
func updatePath(rt *mfs.Root, pth string, builder cid.Builder) error {
955
	if builder == nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
956 957 958 959 960 961 962 963 964 965
		return nil
	}

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

	switch n := nd.(type) {
	case *mfs.Directory:
966
		n.SetCidBuilder(builder)
Kevin Atkinson's avatar
Kevin Atkinson committed
967 968 969 970 971 972 973
	default:
		return fmt.Errorf("can only update directories")
	}

	return nil
}

Overbool's avatar
Overbool committed
974
var filesRmCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
975
	Helptext: cmds.HelpText{
rht's avatar
rht committed
976
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
977
		ShortDescription: `
978
Remove files or directories.
Jeromy's avatar
Jeromy committed
979 980 981 982 983 984 985 986

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

Steven Allen's avatar
Steven Allen committed
989 990
	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
991
	},
Steven Allen's avatar
Steven Allen committed
992 993 994
	Options: []cmds.Option{
		cmds.BoolOption(recursiveOptionName, "r", "Recursively remove directories."),
		cmds.BoolOption(forceOptionName, "Forcibly remove target at path; implies -r for directories"),
Jeromy's avatar
Jeromy committed
995
	},
Overbool's avatar
Overbool committed
996
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
997
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
998
		if err != nil {
Overbool's avatar
Overbool committed
999
			return err
Jeromy's avatar
Jeromy committed
1000 1001
		}

Overbool's avatar
Overbool committed
1002
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
1003
		if err != nil {
Overbool's avatar
Overbool committed
1004
			return err
Jeromy's avatar
Jeromy committed
1005 1006 1007
		}

		if path == "/" {
Overbool's avatar
Overbool committed
1008
			return fmt.Errorf("cannot delete root")
Jeromy's avatar
Jeromy committed
1009 1010 1011 1012 1013 1014 1015
		}

		// 'rm a/b/c/' will fail unless we trim the slash at the end
		if path[len(path)-1] == '/' {
			path = path[:len(path)-1]
		}

1016 1017 1018 1019
		// if '--force' specified, it will remove anything else,
		// including file, directory, corrupted node, etc
		force, _ := req.Options[forceOptionName].(bool)

Jeromy's avatar
Jeromy committed
1020
		dir, name := gopath.Split(path)
1021 1022

		pdir, err := getParentDir(nd.FilesRoot, dir)
Jeromy's avatar
Jeromy committed
1023
		if err != nil {
Oli Evans's avatar
Oli Evans committed
1024 1025
			if force && err == os.ErrNotExist {
				return nil
1026
			}
Overbool's avatar
Overbool committed
1027
			return fmt.Errorf("parent lookup: %s", err)
Jeromy's avatar
Jeromy committed
1028 1029
		}

1030
		if force {
Jeromy's avatar
Jeromy committed
1031 1032
			err := pdir.Unlink(name)
			if err != nil {
Oli Evans's avatar
Oli Evans committed
1033
				if err == os.ErrNotExist {
1034 1035
					return nil
				}
Oli Evans's avatar
Oli Evans committed
1036
				return err
Jeromy's avatar
Jeromy committed
1037
			}
Overbool's avatar
Overbool committed
1038
			return pdir.Flush()
Jeromy's avatar
Jeromy committed
1039 1040
		}

1041 1042 1043
		// 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
1044
		if err != nil {
Overbool's avatar
Overbool committed
1045
			return err
Jeromy's avatar
Jeromy committed
1046 1047
		}

1048
		dashr, _ := req.Options[recursiveOptionName].(bool)
1049 1050

		switch child.(type) {
Jeromy's avatar
Jeromy committed
1051
		case *mfs.Directory:
1052
			if !dashr {
Overbool's avatar
Overbool committed
1053
				return fmt.Errorf("%s is a directory, use -r to remove directories", path)
Jeromy's avatar
Jeromy committed
1054
			}
1055
		}
Jeromy's avatar
Jeromy committed
1056

1057 1058
		err = pdir.Unlink(name)
		if err != nil {
Overbool's avatar
Overbool committed
1059
			return err
Jeromy's avatar
Jeromy committed
1060
		}
1061

Overbool's avatar
Overbool committed
1062
		return pdir.Flush()
Jeromy's avatar
Jeromy committed
1063 1064 1065
	},
}

1066
func getPrefixNew(req *cmds.Request) (cid.Builder, error) {
Kejie Zhang's avatar
Kejie Zhang committed
1067 1068
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094

	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
1095 1096 1097
func getPrefix(req *cmds.Request) (cid.Builder, error) {
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110

	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
1111

1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123
	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
}

1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136
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,
	})
}

1137
func getFileHandle(r *mfs.Root, path string, create bool, builder cid.Builder) (*mfs.File, error) {
Jeromy's avatar
Jeromy committed
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
	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)
1154
		pdir, err := getParentDir(r, dirname)
Jeromy's avatar
Jeromy committed
1155 1156 1157
		if err != nil {
			return nil, err
		}
1158

1159 1160
		if builder == nil {
			builder = pdir.GetCidBuilder()
1161
		}
Jeromy's avatar
Jeromy committed
1162

1163
		nd := dag.NodeWithData(ft.FilePBData(nil, 0))
1164
		nd.SetCidBuilder(builder)
Jeromy's avatar
Jeromy committed
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174
		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
1175 1176
		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1177
			return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
Jeromy's avatar
Jeromy committed
1178 1179
		}
		return fi, nil
Jeromy's avatar
Jeromy committed
1180 1181 1182 1183 1184

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1185 1186 1187

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

	if p[0] != '/' {
Michael Muré's avatar
Michael Muré committed
1192
		return "", fmt.Errorf("paths must start with a leading slash")
Jeromy's avatar
Jeromy committed
1193 1194 1195 1196 1197 1198 1199 1200
	}

	cleaned := gopath.Clean(p)
	if p[len(p)-1] == '/' && p != "/" {
		cleaned += "/"
	}
	return cleaned, nil
}
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213

func getParentDir(root *mfs.Root, dir string) (*mfs.Directory, error) {
	parent, err := mfs.Lookup(root, dir)
	if err != nil {
		return nil, err
	}

	pdir, ok := parent.(*mfs.Directory)
	if !ok {
		return nil, errors.New("expected *mfs.Directory, didnt get it. This is likely a race condition")
	}
	return pdir, nil
}