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

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

13 14
	"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 20 21 22 23 24 25 26 27 28 29
	"github.com/dustin/go-humanize"
	bservice "github.com/ipfs/go-blockservice"
	cid "github.com/ipfs/go-cid"
	cidenc "github.com/ipfs/go-cidutil/cidenc"
	"github.com/ipfs/go-ipfs-cmdkit"
	"github.com/ipfs/go-ipfs-cmds"
	"github.com/ipfs/go-ipfs-exchange-offline"
	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"
	"github.com/ipfs/interface-go-ipfs-core"
	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{
Jan Winkelmann's avatar
Jan Winkelmann committed
36
	Helptext: cmdkit.HelpText{
37
		Tagline: "Interact with unixfs files.",
Jeromy's avatar
Jeromy committed
38
		ShortDescription: `
39 40
Files is an API for manipulating IPFS objects as if they were a unix
filesystem.
Jeromy's avatar
Jeromy committed
41

42
NOTE:
43 44 45 46 47 48 49
Most of the subcommands of 'ipfs files' accept the '--flush' flag. It defaults
to true. Use caution when setting this flag to false. It will improve
performance for large numbers of file operations, but it does so at the cost
of consistency guarantees. If the daemon is unexpectedly killed before running
'ipfs files flush' on the files in question, then data may be lost. This also
applies to running 'ipfs repo gc' concurrently with '--flush=false'
operations.
Jeromy's avatar
Jeromy committed
50 51
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
52
	Options: []cmdkit.Option{
53
		cmdkit.BoolOption(filesFlushOptionName, "f", "Flush target and ancestors after write.").WithDefault(true),
Jeromy's avatar
Jeromy committed
54
	},
Jeromy's avatar
Jeromy committed
55
	Subcommands: map[string]*cmds.Command{
Overbool's avatar
Overbool committed
56
		"read":  filesReadCmd,
57
		"write": filesWriteCmd,
Overbool's avatar
Overbool committed
58 59 60 61
		"mv":    filesMvCmd,
		"cp":    filesCpCmd,
		"ls":    filesLsCmd,
		"mkdir": filesMkdirCmd,
62
		"stat":  filesStatCmd,
Overbool's avatar
Overbool committed
63 64 65
		"rm":    filesRmCmd,
		"flush": filesFlushCmd,
		"chcid": filesChcidCmd,
Jeromy's avatar
Jeromy committed
66 67 68
	},
}

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

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

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

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

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

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

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

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

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

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

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

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

140 141 142 143 144
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

145 146 147 148 149 150 151 152 153 154 155
		var dagserv ipld.DAGService
		if withLocal {
			// an offline DAGService will not fetch from the network
			dagserv = dag.NewDAGService(bservice.New(
				node.Blockstore,
				offline.Exchange(node.Blockstore),
			))
		} else {
			dagserv = node.DAG
		}

156
		nd, err := getNodeFromPath(req.Context, node, api, path)
Jeromy's avatar
Jeromy committed
157
		if err != nil {
keks's avatar
keks committed
158
			return err
Jeromy's avatar
Jeromy committed
159 160
		}

161
		o, err := statNode(nd, enc)
Jeromy's avatar
Jeromy committed
162
		if err != nil {
keks's avatar
keks committed
163
			return err
Jeromy's avatar
Jeromy committed
164 165
		}

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

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

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

keks's avatar
keks committed
176
		return cmds.EmitOnce(res, o)
Jeromy's avatar
Jeromy committed
177
	},
178
	Encoders: cmds.EncoderMap{
179
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *statOutput) error {
180
			s, _ := statGetFormatOptions(req)
181 182 183 184 185 186
			s = strings.Replace(s, "<hash>", out.Hash, -1)
			s = strings.Replace(s, "<size>", fmt.Sprintf("%d", out.Size), -1)
			s = strings.Replace(s, "<cumulsize>", fmt.Sprintf("%d", out.CumulativeSize), -1)
			s = strings.Replace(s, "<childs>", fmt.Sprintf("%d", out.Blocks), -1)
			s = strings.Replace(s, "<type>", out.Type, -1)

187 188 189 190 191 192 193 194 195 196 197
			fmt.Fprintln(w, s)

			if out.WithLocality {
				fmt.Fprintf(w, "Local: %s of %s (%.2f%%)\n",
					humanize.Bytes(out.SizeLocal),
					humanize.Bytes(out.CumulativeSize),
					100.0*float64(out.SizeLocal)/float64(out.CumulativeSize),
				)
			}

			return nil
198
		}),
Jeromy's avatar
Jeromy committed
199
	},
Michael Muré's avatar
Michael Muré committed
200
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
201 202
}

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

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

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

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

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

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

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

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

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

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

271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
func walkBlock(ctx context.Context, dagserv ipld.DAGService, nd ipld.Node) (bool, uint64, error) {
	// Start with the block data size
	sizeLocal := uint64(len(nd.RawData()))

	local := true

	for _, link := range nd.Links() {
		child, err := dagserv.Get(ctx, link.Cid)

		if err == ipld.ErrNotFound {
			local = false
			continue
		}

		if err != nil {
			return local, sizeLocal, err
		}

		childLocal, childLocalSize, err := walkBlock(ctx, dagserv, child)

		if err != nil {
			return local, sizeLocal, err
		}

		// Recursively add the child size
		local = local && childLocal
		sizeLocal += childLocalSize
	}

	return local, sizeLocal, nil
}

Overbool's avatar
Overbool committed
303
var filesCpCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
304
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
305
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
306
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
307 308 309
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("source", true, false, "Source object to copy."),
		cmdkit.StringArg("dest", true, false, "Destination to copy object to."),
Jeromy's avatar
Jeromy committed
310
	},
Overbool's avatar
Overbool committed
311 312
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
313
		if err != nil {
Overbool's avatar
Overbool committed
314
			return err
Jeromy's avatar
Jeromy committed
315 316
		}

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

322
		flush, _ := req.Options[filesFlushOptionName].(bool)
323

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

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

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

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

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

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

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

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

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

		return fsn.GetNode()
	}
}

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

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

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

Examples:

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

    $ ipfs files ls /myfiles/a/b/c/d
    foo
    bar
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
409 410
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to show listing for. Defaults to '/'."),
Jeromy's avatar
Jeromy committed
411
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
412
	Options: []cmdkit.Option{
Kejie Zhang's avatar
Kejie Zhang committed
413 414
		cmdkit.BoolOption(longOptionName, "Use long listing format."),
		cmdkit.BoolOption(dontSortOptionName, "Do not sort; list entries in directory order."),
Jeromy's avatar
Jeromy committed
415
	},
Overbool's avatar
Overbool committed
416
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
417 418
		var arg string

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

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

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

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

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

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

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

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

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

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

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

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

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

Overbool's avatar
Overbool committed
523
var filesReadCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
524
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
525
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
526
		ShortDescription: `
527 528
Read a specified number of bytes from a file at a given offset. By default,
will read the entire file similar to unix cat.
Jeromy's avatar
Jeromy committed
529 530 531 532 533

Examples:

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

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

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

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

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

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

		defer rfd.Close()

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

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

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

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

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

Jeromy's avatar
Jeromy committed
603 604 605 606 607 608 609 610 611 612 613 614 615
type contextReader interface {
	CtxReadFull(context.Context, []byte) (int, error)
}

type contextReaderWrapper struct {
	R   contextReader
	ctx context.Context
}

func (crw *contextReaderWrapper) Read(b []byte) (int, error) {
	return crw.R.CtxReadFull(crw.ctx, b)
}

Overbool's avatar
Overbool committed
616
var filesMvCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
617
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
618
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
619 620 621 622 623 624 625
		ShortDescription: `
Move files around. Just like traditional unix mv.

Example:

    $ ipfs files mv /myfs/a/b/c /myfs/foo/newc

Jeromy's avatar
Jeromy committed
626
`,
Jeromy's avatar
Jeromy committed
627 628
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
629 630 631
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("source", true, false, "Source file to move."),
		cmdkit.StringArg("dest", true, false, "Destination path for file to be moved to."),
Jeromy's avatar
Jeromy committed
632
	},
Overbool's avatar
Overbool committed
633 634
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
635
		if err != nil {
Overbool's avatar
Overbool committed
636
			return err
Jeromy's avatar
Jeromy committed
637 638
		}

639 640
		flush, _ := req.Options[filesFlushOptionName].(bool)

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

650 651
		err = mfs.Mv(nd.FilesRoot, src, dst)
		if err == nil && flush {
652
			_, err = mfs.FlushPath(req.Context, nd.FilesRoot, "/")
653 654
		}
		return err
Jeromy's avatar
Jeromy committed
655 656 657
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
658 659 660 661 662 663 664 665
const (
	filesCreateOptionName    = "create"
	filesParentsOptionName   = "parents"
	filesTruncateOptionName  = "truncate"
	filesRawLeavesOptionName = "raw-leaves"
	filesFlushOptionName     = "flush"
)

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

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

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

684 685 686 687
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.

688
EXAMPLE:
Jeromy's avatar
Jeromy committed
689

Jeromy's avatar
Jeromy committed
690 691
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
692

693
WARNING:
694

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

Kejie Zhang's avatar
Kejie Zhang committed
720 721 722 723 724
		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)
725

726
		prefix, err := getPrefixNew(req)
727
		if err != nil {
keks's avatar
keks committed
728
			return err
729
		}
Jeromy's avatar
Jeromy committed
730

731
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
732
		if err != nil {
keks's avatar
keks committed
733
			return err
Jeromy's avatar
Jeromy committed
734 735
		}

736
		offset, _ := req.Options[filesOffsetOptionName].(int64)
Jeromy's avatar
Jeromy committed
737
		if offset < 0 {
keks's avatar
keks committed
738
			return fmt.Errorf("cannot have negative write offset")
Jeromy's avatar
Jeromy committed
739 740
		}

741 742 743
		if mkParents {
			err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix)
			if err != nil {
keks's avatar
keks committed
744
				return err
745 746 747
			}
		}

748
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
749
		if err != nil {
keks's avatar
keks committed
750
			return err
Jeromy's avatar
Jeromy committed
751
		}
752 753 754
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
755

Steven Allen's avatar
Steven Allen committed
756
		wfd, err := fi.Open(mfs.Flags{Write: true, Sync: flush})
757
		if err != nil {
keks's avatar
keks committed
758
			return err
759
		}
Jeromy's avatar
Jeromy committed
760

761 762 763
		defer func() {
			err := wfd.Close()
			if err != nil {
keks's avatar
keks committed
764 765 766 767 768
				if retErr == nil {
					retErr = err
				} else {
					log.Error("files: error closing file mfs file descriptor", err)
				}
769 770
			}
		}()
771

Jeromy's avatar
Jeromy committed
772
		if trunc {
773
			if err := wfd.Truncate(0); err != nil {
keks's avatar
keks committed
774
				return err
Jeromy's avatar
Jeromy committed
775 776 777
			}
		}

778
		count, countfound := req.Options[filesCountOptionName].(int64)
Jeromy's avatar
Jeromy committed
779
		if countfound && count < 0 {
keks's avatar
keks committed
780
			return fmt.Errorf("cannot have negative byte count")
Jeromy's avatar
Jeromy committed
781
		}
Jeromy's avatar
Jeromy committed
782

783
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
784
		if err != nil {
785
			flog.Error("seekfail: ", err)
keks's avatar
keks committed
786
			return err
Jeromy's avatar
Jeromy committed
787 788
		}

789 790 791 792
		var r io.Reader
		r, err = cmdenv.GetFileArg(req.Files.Entries())
		if err != nil {
			return err
Jeromy's avatar
Jeromy committed
793
		}
zramsay's avatar
zramsay committed
794 795 796 797
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
798
		_, err = io.Copy(wfd, r)
keks's avatar
keks committed
799
		return err
Jeromy's avatar
Jeromy committed
800 801 802
	},
}

Overbool's avatar
Overbool committed
803
var filesMkdirCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
804
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
805
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
806 807 808
		ShortDescription: `
Create the directory if it does not already exist.

809 810 811
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

812
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
813 814 815

Examples:

816 817
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
818 819 820
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
821 822
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to dir to make."),
Jeromy's avatar
Jeromy committed
823
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
824
	Options: []cmdkit.Option{
Kejie Zhang's avatar
Kejie Zhang committed
825
		cmdkit.BoolOption(filesParentsOptionName, "p", "No error if existing, make parent directories as needed."),
826 827
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
828
	},
Overbool's avatar
Overbool committed
829 830
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		n, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
831
		if err != nil {
Overbool's avatar
Overbool committed
832
			return err
Jeromy's avatar
Jeromy committed
833 834
		}

Overbool's avatar
Overbool committed
835 836
		dashp, _ := req.Options[filesParentsOptionName].(bool)
		dirtomake, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
837
		if err != nil {
Overbool's avatar
Overbool committed
838
			return err
Jeromy's avatar
Jeromy committed
839 840
		}

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

843 844
		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
845
			return err
846 847 848
		}
		root := n.FilesRoot

849
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
850 851 852
			Mkparents:  dashp,
			Flush:      flush,
			CidBuilder: prefix,
853
		})
keks's avatar
keks committed
854

Overbool's avatar
Overbool committed
855
		return err
Jeromy's avatar
Jeromy committed
856 857 858
	},
}

859 860 861 862
type flushRes struct {
	Cid string
}

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

880 881 882 883 884
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

Jeromy's avatar
Jeromy committed
885
		path := "/"
Overbool's avatar
Overbool committed
886 887
		if len(req.Arguments) > 0 {
			path = req.Arguments[0]
Jeromy's avatar
Jeromy committed
888
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
889

890 891 892 893 894 895
		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
896
	},
897
	Type: flushRes{},
Jeromy's avatar
Jeromy committed
898 899
}

Overbool's avatar
Overbool committed
900
var filesChcidCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
901
	Helptext: cmdkit.HelpText{
902
		Tagline: "Change the cid version or hash function of the root node of a given path.",
Kevin Atkinson's avatar
Kevin Atkinson committed
903
		ShortDescription: `
904
Change the cid version or hash function of the root node of a given path.
Kevin Atkinson's avatar
Kevin Atkinson committed
905 906
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
907 908
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to change. Default: '/'."),
Kevin Atkinson's avatar
Kevin Atkinson committed
909
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
910
	Options: []cmdkit.Option{
911 912 913
		cidVersionOption,
		hashOption,
	},
Overbool's avatar
Overbool committed
914 915
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Kevin Atkinson's avatar
Kevin Atkinson committed
916
		if err != nil {
Overbool's avatar
Overbool committed
917
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
918 919 920
		}

		path := "/"
Overbool's avatar
Overbool committed
921 922
		if len(req.Arguments) > 0 {
			path = req.Arguments[0]
Kevin Atkinson's avatar
Kevin Atkinson committed
923 924
		}

Overbool's avatar
Overbool committed
925
		flush, _ := req.Options[filesFlushOptionName].(bool)
Kevin Atkinson's avatar
Kevin Atkinson committed
926 927 928

		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
929
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
930
		}
keks's avatar
keks committed
931

932 933
		err = updatePath(nd.FilesRoot, path, prefix)
		if err == nil && flush {
934
			_, err = mfs.FlushPath(req.Context, nd.FilesRoot, path)
935 936
		}
		return err
Kevin Atkinson's avatar
Kevin Atkinson committed
937 938 939
	},
}

940
func updatePath(rt *mfs.Root, pth string, builder cid.Builder) error {
941
	if builder == nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
942 943 944 945 946 947 948 949 950 951
		return nil
	}

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

	switch n := nd.(type) {
	case *mfs.Directory:
952
		n.SetCidBuilder(builder)
Kevin Atkinson's avatar
Kevin Atkinson committed
953 954 955 956 957 958 959
	default:
		return fmt.Errorf("can only update directories")
	}

	return nil
}

Overbool's avatar
Overbool committed
960
var filesRmCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
961
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
962
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
963
		ShortDescription: `
964
Remove files or directories.
Jeromy's avatar
Jeromy committed
965 966 967 968 969 970 971 972

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

Jan Winkelmann's avatar
Jan Winkelmann committed
975 976
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
977
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
978
	Options: []cmdkit.Option{
979 980
		cmdkit.BoolOption(recursiveOptionName, "r", "Recursively remove directories."),
		cmdkit.BoolOption(forceOptionName, "Forcibly remove target at path; implies -r for directories"),
Jeromy's avatar
Jeromy committed
981
	},
Overbool's avatar
Overbool committed
982
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
983
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
984
		if err != nil {
Overbool's avatar
Overbool committed
985
			return err
Jeromy's avatar
Jeromy committed
986 987
		}

Overbool's avatar
Overbool committed
988
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
989
		if err != nil {
Overbool's avatar
Overbool committed
990
			return err
Jeromy's avatar
Jeromy committed
991 992 993
		}

		if path == "/" {
Overbool's avatar
Overbool committed
994
			return fmt.Errorf("cannot delete root")
Jeromy's avatar
Jeromy committed
995 996 997 998 999 1000 1001
		}

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

Jeromy's avatar
Jeromy committed
1002 1003 1004
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Overbool's avatar
Overbool committed
1005
			return fmt.Errorf("parent lookup: %s", err)
Jeromy's avatar
Jeromy committed
1006 1007 1008 1009
		}

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

1013 1014
		// if '--force' specified, it will remove anything else,
		// including file, directory, corrupted node, etc
1015
		force, _ := req.Options[forceOptionName].(bool)
1016
		if force {
Jeromy's avatar
Jeromy committed
1017 1018
			err := pdir.Unlink(name)
			if err != nil {
Overbool's avatar
Overbool committed
1019
				return err
Jeromy's avatar
Jeromy committed
1020 1021
			}

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

1025 1026 1027
		// 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
1028
		if err != nil {
Overbool's avatar
Overbool committed
1029
			return err
Jeromy's avatar
Jeromy committed
1030 1031
		}

1032
		dashr, _ := req.Options[recursiveOptionName].(bool)
1033 1034

		switch child.(type) {
Jeromy's avatar
Jeromy committed
1035
		case *mfs.Directory:
1036
			if !dashr {
Overbool's avatar
Overbool committed
1037
				return fmt.Errorf("%s is a directory, use -r to remove directories", path)
Jeromy's avatar
Jeromy committed
1038
			}
1039
		}
Jeromy's avatar
Jeromy committed
1040

1041 1042
		err = pdir.Unlink(name)
		if err != nil {
Overbool's avatar
Overbool committed
1043
			return err
Jeromy's avatar
Jeromy committed
1044
		}
1045

Overbool's avatar
Overbool committed
1046
		return pdir.Flush()
Jeromy's avatar
Jeromy committed
1047 1048 1049
	},
}

1050
func getPrefixNew(req *cmds.Request) (cid.Builder, error) {
Kejie Zhang's avatar
Kejie Zhang committed
1051 1052
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078

	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
1079 1080 1081
func getPrefix(req *cmds.Request) (cid.Builder, error) {
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
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
	}
Jeromy's avatar
Jeromy committed
1095

1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
	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
}

1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120
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,
	})
}

1121
func getFileHandle(r *mfs.Root, path string, create bool, builder cid.Builder) (*mfs.File, error) {
Jeromy's avatar
Jeromy committed
1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
	target, err := mfs.Lookup(r, path)
	switch err {
	case nil:
		fi, ok := target.(*mfs.File)
		if !ok {
			return nil, fmt.Errorf("%s was not a file", path)
		}
		return fi, nil

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

		// if create is specified and the file doesnt exist, we create the file
		dirname, fname := gopath.Split(path)
		pdiri, err := mfs.Lookup(r, dirname)
		if err != nil {
1140
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1141 1142 1143 1144 1145 1146
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1147 1148
		if builder == nil {
			builder = pdir.GetCidBuilder()
1149
		}
Jeromy's avatar
Jeromy committed
1150

1151
		nd := dag.NodeWithData(ft.FilePBData(nil, 0))
1152
		nd.SetCidBuilder(builder)
Jeromy's avatar
Jeromy committed
1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
		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
1163 1164
		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1165
			return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
Jeromy's avatar
Jeromy committed
1166 1167
		}
		return fi, nil
Jeromy's avatar
Jeromy committed
1168 1169 1170 1171 1172

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1173 1174 1175

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

	if p[0] != '/' {
Michael Muré's avatar
Michael Muré committed
1180
		return "", fmt.Errorf("paths must start with a leading slash")
Jeromy's avatar
Jeromy committed
1181 1182 1183 1184 1185 1186 1187 1188
	}

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