files.go 28.6 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
Alan Shaw's avatar
Alan Shaw committed
689 690
exist. Nonexistant intermediate directories will not be created unless the
'--parents' option is specified.
Jeromy's avatar
Jeromy committed
691

692
Newly created files will have the same CID version and hash function of the
Alan Shaw's avatar
Alan Shaw committed
693
parent directory unless the '--cid-version' and '--hash' options are used.
694 695

Newly created leaves will be in the legacy format (Protobuf) if the
Alan Shaw's avatar
Alan Shaw committed
696
CID version is 0, or raw if the CID version is non-zero.  Use of the
Alan Shaw's avatar
Alan Shaw committed
697
'--raw-leaves' option will override this behavior.
698

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

703
EXAMPLE:
Jeromy's avatar
Jeromy committed
704

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

708
WARNING:
709

710 711 712
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
713
`,
Jeromy's avatar
Jeromy committed
714
	},
Steven Allen's avatar
Steven Allen committed
715 716 717 718 719 720 721 722 723 724 725
	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)"),
726 727
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
728
	},
keks's avatar
keks committed
729
	Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) (retErr error) {
730
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
731
		if err != nil {
keks's avatar
keks committed
732
			return err
Jeromy's avatar
Jeromy committed
733 734
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Examples:

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

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

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

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

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

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

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

874 875 876 877
type flushRes struct {
	Cid string
}

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

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

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

905 906 907 908 909 910
		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
911
	},
912
	Type: flushRes{},
Jeromy's avatar
Jeromy committed
913 914
}

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

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

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

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

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

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

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

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

	return nil
}

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

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

Steven Allen's avatar
Steven Allen committed
990 991
	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
992
	},
Steven Allen's avatar
Steven Allen committed
993 994 995
	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
996
	},
Overbool's avatar
Overbool committed
997
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
998
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
999
		if err != nil {
Overbool's avatar
Overbool committed
1000
			return err
Jeromy's avatar
Jeromy committed
1001 1002
		}

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

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

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

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

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

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

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

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

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

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

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

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

1067
func getPrefixNew(req *cmds.Request) (cid.Builder, error) {
Kejie Zhang's avatar
Kejie Zhang committed
1068 1069
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
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 1095

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

	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
1112

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

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

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

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

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

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

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

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

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

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
}