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

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

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

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

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

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

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

Kejie Zhang's avatar
Kejie Zhang committed
68 69 70 71 72 73 74
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)")
75

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

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

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

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

Jan Winkelmann's avatar
Jan Winkelmann committed
105 106
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to node to stat."),
Jeromy's avatar
Jeromy committed
107
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
108
	Options: []cmdkit.Option{
Kejie Zhang's avatar
Kejie Zhang committed
109
		cmdkit.StringOption(filesFormatOptionName, "Print statistics in given format. Allowed tokens: "+
keks's avatar
keks committed
110
			"<hash> <size> <cumulsize> <type> <childs>. Conflicts with other format options.").WithDefault(defaultStatFormat),
Kejie Zhang's avatar
Kejie Zhang committed
111 112 113
		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"),
114
	},
keks's avatar
keks committed
115
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
116 117 118

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

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

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

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

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

		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
		}

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

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

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

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

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

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

181 182 183 184 185 186 187 188 189 190 191
			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
192
		}),
Jeromy's avatar
Jeromy committed
193
	},
Michael Muré's avatar
Michael Muré committed
194
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
195 196
}

197 198 199 200
func moreThanOne(a, b, c bool) bool {
	return a && b || b && c || a && c
}

201
func statGetFormatOptions(req *cmds.Request) (string, error) {
202

Kejie Zhang's avatar
Kejie Zhang committed
203 204 205
	hash, _ := req.Options[filesHashOptionName].(bool)
	size, _ := req.Options[filesSizeOptionName].(bool)
	format, _ := req.Options[filesFormatOptionName].(string)
206

keks's avatar
keks committed
207
	if moreThanOne(hash, size, format != defaultStatFormat) {
Michael Muré's avatar
Michael Muré committed
208
		return "", errFormat
209 210 211
	}

	if hash {
212 213 214 215 216
		return "<hash>", nil
	} else if size {
		return "<cumulsize>", nil
	} else {
		return format, nil
217 218 219
	}
}

Michael Muré's avatar
Michael Muré committed
220
func statNode(nd ipld.Node) (*statOutput, error) {
Jeromy's avatar
Jeromy committed
221
	c := nd.Cid()
Jeromy's avatar
Jeromy committed
222 223 224 225 226 227

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

228 229
	switch n := nd.(type) {
	case *dag.ProtoNode:
Overbool's avatar
Overbool committed
230
		d, err := ft.FSNodeFromBytes(n.Data())
231 232 233 234 235
		if err != nil {
			return nil, err
		}

		var ndtype string
Overbool's avatar
Overbool committed
236
		switch d.Type() {
237
		case ft.TDirectory, ft.THAMTShard:
238
			ndtype = "directory"
239
		case ft.TFile, ft.TMetadata, ft.TRaw:
240 241
			ndtype = "file"
		default:
Overbool's avatar
Overbool committed
242
			return nil, fmt.Errorf("unrecognized node type: %s", d.Type())
243 244
		}

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

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

Overbool's avatar
Overbool committed
311
		api, err := cmdenv.GetApi(env)
312
		if err != nil {
Overbool's avatar
Overbool committed
313
			return err
314 315
		}

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

Overbool's avatar
Overbool committed
318
		src, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
319
		if err != nil {
Overbool's avatar
Overbool committed
320
			return err
Jeromy's avatar
Jeromy committed
321
		}
322 323
		src = strings.TrimRight(src, "/")

Overbool's avatar
Overbool committed
324
		dst, err := checkPath(req.Arguments[1])
Jeromy's avatar
Jeromy committed
325
		if err != nil {
Overbool's avatar
Overbool committed
326
			return err
Jeromy's avatar
Jeromy committed
327
		}
Jeromy's avatar
Jeromy committed
328

329 330 331 332
		if dst[len(dst)-1] == '/' {
			dst += gopath.Base(src)
		}

Overbool's avatar
Overbool committed
333
		node, err := getNodeFromPath(req.Context, nd, api, src)
Jeromy's avatar
Jeromy committed
334
		if err != nil {
Overbool's avatar
Overbool committed
335
			return fmt.Errorf("cp: cannot get node from path %s: %s", src, err)
Jeromy's avatar
Jeromy committed
336 337
		}

Overbool's avatar
Overbool committed
338
		err = mfs.PutNode(nd.FilesRoot, dst, node)
Jeromy's avatar
Jeromy committed
339
		if err != nil {
Overbool's avatar
Overbool committed
340
			return fmt.Errorf("cp: cannot put node in path %s: %s", dst, err)
Jeromy's avatar
Jeromy committed
341
		}
342 343

		if flush {
Overbool's avatar
Overbool committed
344
			err := mfs.FlushPath(nd.FilesRoot, dst)
345
			if err != nil {
Overbool's avatar
Overbool committed
346
				return fmt.Errorf("cp: cannot flush the created file %s: %s", dst, err)
347 348
			}
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
349

Overbool's avatar
Overbool committed
350
		return nil
Jeromy's avatar
Jeromy committed
351 352 353
	},
}

354
func getNodeFromPath(ctx context.Context, node *core.IpfsNode, api iface.CoreAPI, p string) (ipld.Node, error) {
Jeromy's avatar
Jeromy committed
355 356
	switch {
	case strings.HasPrefix(p, "/ipfs/"):
357
		np, err := iface.ParsePath(p)
Jeromy's avatar
Jeromy committed
358 359 360 361
		if err != nil {
			return nil, err
		}

362
		return api.ResolveNode(ctx, np)
Jeromy's avatar
Jeromy committed
363 364 365 366 367 368 369 370 371 372
	default:
		fsn, err := mfs.Lookup(node.FilesRoot, p)
		if err != nil {
			return nil, err
		}

		return fsn.GetNode()
	}
}

Michael Muré's avatar
Michael Muré committed
373
type filesLsOutput struct {
Jeromy's avatar
Jeromy committed
374 375 376
	Entries []mfs.NodeListing
}

Kejie Zhang's avatar
Kejie Zhang committed
377 378 379 380 381
const (
	longOptionName     = "l"
	dontSortOptionName = "U"
)

Overbool's avatar
Overbool committed
382
var filesLsCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
383
	Helptext: cmdkit.HelpText{
384
		Tagline: "List directories in the local mutable namespace.",
Jeromy's avatar
Jeromy committed
385
		ShortDescription: `
386
List directories in the local mutable namespace.
Jeromy's avatar
Jeromy committed
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402

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

Overbool's avatar
Overbool committed
413
		if len(req.Arguments) == 0 {
414 415
			arg = "/"
		} else {
Overbool's avatar
Overbool committed
416
			arg = req.Arguments[0]
417 418 419
		}

		path, err := checkPath(arg)
Jeromy's avatar
Jeromy committed
420
		if err != nil {
Overbool's avatar
Overbool committed
421
			return err
Jeromy's avatar
Jeromy committed
422 423
		}

Overbool's avatar
Overbool committed
424
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
425
		if err != nil {
Overbool's avatar
Overbool committed
426
			return err
Jeromy's avatar
Jeromy committed
427 428 429 430
		}

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

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

Jeromy's avatar
Jeromy committed
436 437
		switch fsn := fsn.(type) {
		case *mfs.Directory:
Jeromy's avatar
Jeromy committed
438 439
			if !long {
				var output []mfs.NodeListing
Overbool's avatar
Overbool committed
440
				names, err := fsn.ListNames(req.Context)
441
				if err != nil {
Overbool's avatar
Overbool committed
442
					return err
443 444 445
				}

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

				size, err := fsn.Size()
				if err != nil {
Overbool's avatar
Overbool committed
466
					return err
467 468 469 470 471
				}
				out.Entries[0].Size = size

				nd, err := fsn.GetNode()
				if err != nil {
Overbool's avatar
Overbool committed
472
					return err
473 474 475
				}
				out.Entries[0].Hash = nd.Cid().String()
			}
Overbool's avatar
Overbool committed
476
			return res.Emit(out)
Jeromy's avatar
Jeromy committed
477
		default:
Overbool's avatar
Overbool committed
478
			return errors.New("unrecognized type")
Jeromy's avatar
Jeromy committed
479 480
		}
	},
Overbool's avatar
Overbool committed
481 482 483
	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
484 485 486 487 488 489
			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
490
			long, _ := req.Options[longOptionName].(bool)
Jeromy's avatar
Jeromy committed
491 492
			for _, o := range out.Entries {
				if long {
Overbool's avatar
Overbool committed
493 494 495
					if o.Type == int(mfs.TDir) {
						o.Name += "/"
					}
Overbool's avatar
Overbool committed
496
					fmt.Fprintf(w, "%s\t%s\t%d\n", o.Name, o.Hash, o.Size)
Jeromy's avatar
Jeromy committed
497
				} else {
Overbool's avatar
Overbool committed
498
					fmt.Fprintf(w, "%s\n", o.Name)
Jeromy's avatar
Jeromy committed
499 500
				}
			}
Overbool's avatar
Overbool committed
501 502 503

			return nil
		}),
Jeromy's avatar
Jeromy committed
504
	},
Michael Muré's avatar
Michael Muré committed
505
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
506 507
}

Kejie Zhang's avatar
Kejie Zhang committed
508 509 510 511 512
const (
	filesOffsetOptionName = "offset"
	filesCountOptionName  = "count"
)

Overbool's avatar
Overbool committed
513
var filesReadCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
514
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
515
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
516
		ShortDescription: `
517 518
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
519 520 521 522 523

Examples:

    $ ipfs files read /test/hello
    hello
Jeromy's avatar
Jeromy committed
524
        `,
Jeromy's avatar
Jeromy committed
525 526
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
527 528
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
529
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
530
	Options: []cmdkit.Option{
531 532
		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
533
	},
Overbool's avatar
Overbool committed
534 535
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
536
		if err != nil {
Overbool's avatar
Overbool committed
537
			return err
Jeromy's avatar
Jeromy committed
538 539
		}

Overbool's avatar
Overbool committed
540
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
541
		if err != nil {
Overbool's avatar
Overbool committed
542
			return err
Jeromy's avatar
Jeromy committed
543 544
		}

Overbool's avatar
Overbool committed
545
		fsn, err := mfs.Lookup(nd.FilesRoot, path)
Jeromy's avatar
Jeromy committed
546
		if err != nil {
Overbool's avatar
Overbool committed
547
			return err
Jeromy's avatar
Jeromy committed
548 549 550 551
		}

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

555 556
		rfd, err := fi.Open(mfs.OpenReadOnly, false)
		if err != nil {
Overbool's avatar
Overbool committed
557
			return err
558 559 560 561
		}

		defer rfd.Close()

Overbool's avatar
Overbool committed
562
		offset, _ := req.Options[offsetOptionName].(int64)
Jeromy's avatar
Jeromy committed
563
		if offset < 0 {
Overbool's avatar
Overbool committed
564
			return fmt.Errorf("cannot specify negative offset")
Jeromy's avatar
Jeromy committed
565 566
		}

567
		filen, err := rfd.Size()
Jeromy's avatar
Jeromy committed
568
		if err != nil {
Overbool's avatar
Overbool committed
569
			return err
Jeromy's avatar
Jeromy committed
570 571 572
		}

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

576
		_, err = rfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
577
		if err != nil {
Overbool's avatar
Overbool committed
578
			return err
Jeromy's avatar
Jeromy committed
579
		}
580

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

Jeromy's avatar
Jeromy committed
593 594 595 596 597 598 599 600 601 602 603 604 605
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
606
var filesMvCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
607
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
608
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
609 610 611 612 613 614 615
		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
616
`,
Jeromy's avatar
Jeromy committed
617 618
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
619 620 621
	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
622
	},
Overbool's avatar
Overbool committed
623 624
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
625
		if err != nil {
Overbool's avatar
Overbool committed
626
			return err
Jeromy's avatar
Jeromy committed
627 628
		}

Overbool's avatar
Overbool committed
629
		src, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
630
		if err != nil {
Overbool's avatar
Overbool committed
631
			return err
Jeromy's avatar
Jeromy committed
632
		}
Overbool's avatar
Overbool committed
633
		dst, err := checkPath(req.Arguments[1])
Jeromy's avatar
Jeromy committed
634
		if err != nil {
Overbool's avatar
Overbool committed
635
			return err
Jeromy's avatar
Jeromy committed
636
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
637

Overbool's avatar
Overbool committed
638
		return mfs.Mv(nd.FilesRoot, src, dst)
Jeromy's avatar
Jeromy committed
639 640 641
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
642 643 644 645 646 647 648 649
const (
	filesCreateOptionName    = "create"
	filesParentsOptionName   = "parents"
	filesTruncateOptionName  = "truncate"
	filesRawLeavesOptionName = "raw-leaves"
	filesFlushOptionName     = "flush"
)

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

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

661 662 663 664 665 666 667
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.

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

672
EXAMPLE:
Jeromy's avatar
Jeromy committed
673

Jeromy's avatar
Jeromy committed
674 675
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
676

677
WARNING:
678

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

Kejie Zhang's avatar
Kejie Zhang committed
704 705 706 707 708
		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)
709

710
		prefix, err := getPrefixNew(req)
711
		if err != nil {
keks's avatar
keks committed
712
			return err
713
		}
Jeromy's avatar
Jeromy committed
714

715
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
716
		if err != nil {
keks's avatar
keks committed
717
			return err
Jeromy's avatar
Jeromy committed
718 719
		}

720
		offset, _ := req.Options[filesOffsetOptionName].(int64)
Jeromy's avatar
Jeromy committed
721
		if offset < 0 {
keks's avatar
keks committed
722
			return fmt.Errorf("cannot have negative write offset")
Jeromy's avatar
Jeromy committed
723 724
		}

725 726 727
		if mkParents {
			err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix)
			if err != nil {
keks's avatar
keks committed
728
				return err
729 730 731
			}
		}

732
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
733
		if err != nil {
keks's avatar
keks committed
734
			return err
Jeromy's avatar
Jeromy committed
735
		}
736 737 738
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
739

740 741
		wfd, err := fi.Open(mfs.OpenWriteOnly, flush)
		if err != nil {
keks's avatar
keks committed
742
			return err
743
		}
Jeromy's avatar
Jeromy committed
744

745 746 747
		defer func() {
			err := wfd.Close()
			if err != nil {
keks's avatar
keks committed
748 749 750 751 752
				if retErr == nil {
					retErr = err
				} else {
					log.Error("files: error closing file mfs file descriptor", err)
				}
753 754
			}
		}()
755

Jeromy's avatar
Jeromy committed
756
		if trunc {
757
			if err := wfd.Truncate(0); err != nil {
keks's avatar
keks committed
758
				return err
Jeromy's avatar
Jeromy committed
759 760 761
			}
		}

762
		count, countfound := req.Options[filesCountOptionName].(int64)
Jeromy's avatar
Jeromy committed
763
		if countfound && count < 0 {
keks's avatar
keks committed
764
			return fmt.Errorf("cannot have negative byte count")
Jeromy's avatar
Jeromy committed
765
		}
Jeromy's avatar
Jeromy committed
766

767
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
768
		if err != nil {
769
			flog.Error("seekfail: ", err)
keks's avatar
keks committed
770
			return err
Jeromy's avatar
Jeromy committed
771 772
		}

773
		input, err := req.Files.NextFile()
Jeromy's avatar
Jeromy committed
774
		if err != nil {
keks's avatar
keks committed
775
			return err
Jeromy's avatar
Jeromy committed
776 777
		}

zramsay's avatar
zramsay committed
778 779 780 781 782
		var r io.Reader = input
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
783
		_, err = io.Copy(wfd, r)
keks's avatar
keks committed
784
		return err
Jeromy's avatar
Jeromy committed
785 786 787
	},
}

Overbool's avatar
Overbool committed
788
var filesMkdirCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
789
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
790
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
791 792 793
		ShortDescription: `
Create the directory if it does not already exist.

794 795 796
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

797
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
798 799 800

Examples:

801 802
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
803 804 805
`,
	},

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

Overbool's avatar
Overbool committed
820 821
		dashp, _ := req.Options[filesParentsOptionName].(bool)
		dirtomake, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
822
		if err != nil {
Overbool's avatar
Overbool committed
823
			return err
Jeromy's avatar
Jeromy committed
824 825
		}

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

828 829
		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
830
			return err
831 832 833
		}
		root := n.FilesRoot

834
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
835 836 837
			Mkparents:  dashp,
			Flush:      flush,
			CidBuilder: prefix,
838
		})
keks's avatar
keks committed
839

Overbool's avatar
Overbool committed
840
		return err
Jeromy's avatar
Jeromy committed
841 842 843
	},
}

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

		path := "/"
Overbool's avatar
Overbool committed
862 863
		if len(req.Arguments) > 0 {
			path = req.Arguments[0]
Jeromy's avatar
Jeromy committed
864
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
865

Overbool's avatar
Overbool committed
866
		return mfs.FlushPath(nd.FilesRoot, path)
Jeromy's avatar
Jeromy committed
867 868 869
	},
}

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

		path := "/"
Overbool's avatar
Overbool committed
891 892
		if len(req.Arguments) > 0 {
			path = req.Arguments[0]
Kevin Atkinson's avatar
Kevin Atkinson committed
893 894
		}

Overbool's avatar
Overbool committed
895
		flush, _ := req.Options[filesFlushOptionName].(bool)
Kevin Atkinson's avatar
Kevin Atkinson committed
896 897 898

		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
899
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
900
		}
keks's avatar
keks committed
901

Overbool's avatar
Overbool committed
902
		return updatePath(nd.FilesRoot, path, prefix, flush)
Kevin Atkinson's avatar
Kevin Atkinson committed
903 904 905
	},
}

906 907
func updatePath(rt *mfs.Root, pth string, builder cid.Builder, flush bool) error {
	if builder == nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
908 909 910 911 912 913 914 915 916 917
		return nil
	}

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

	switch n := nd.(type) {
	case *mfs.Directory:
918
		n.SetCidBuilder(builder)
Kevin Atkinson's avatar
Kevin Atkinson committed
919 920 921 922 923 924 925 926 927 928 929
	default:
		return fmt.Errorf("can only update directories")
	}

	if flush {
		nd.Flush()
	}

	return nil
}

Overbool's avatar
Overbool committed
930
var filesRmCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
931
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
932
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
933
		ShortDescription: `
934
Remove files or directories.
Jeromy's avatar
Jeromy committed
935 936 937 938 939 940 941 942

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

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

Overbool's avatar
Overbool committed
958
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
959
		if err != nil {
Overbool's avatar
Overbool committed
960
			return err
Jeromy's avatar
Jeromy committed
961 962 963
		}

		if path == "/" {
Overbool's avatar
Overbool committed
964
			return fmt.Errorf("cannot delete root")
Jeromy's avatar
Jeromy committed
965 966 967 968 969 970 971
		}

		// '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
972 973 974
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Overbool's avatar
Overbool committed
975
			return fmt.Errorf("parent lookup: %s", err)
Jeromy's avatar
Jeromy committed
976 977 978 979
		}

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

983 984
		// if '--force' specified, it will remove anything else,
		// including file, directory, corrupted node, etc
985
		force, _ := req.Options[forceOptionName].(bool)
986
		if force {
Jeromy's avatar
Jeromy committed
987 988
			err := pdir.Unlink(name)
			if err != nil {
Overbool's avatar
Overbool committed
989
				return err
Jeromy's avatar
Jeromy committed
990 991
			}

Overbool's avatar
Overbool committed
992
			return pdir.Flush()
Jeromy's avatar
Jeromy committed
993 994
		}

995 996 997
		// 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
998
		if err != nil {
Overbool's avatar
Overbool committed
999
			return err
Jeromy's avatar
Jeromy committed
1000 1001
		}

1002
		dashr, _ := req.Options[recursiveOptionName].(bool)
1003 1004

		switch child.(type) {
Jeromy's avatar
Jeromy committed
1005
		case *mfs.Directory:
1006
			if !dashr {
Overbool's avatar
Overbool committed
1007
				return fmt.Errorf("%s is a directory, use -r to remove directories", path)
Jeromy's avatar
Jeromy committed
1008
			}
1009
		}
Jeromy's avatar
Jeromy committed
1010

1011 1012
		err = pdir.Unlink(name)
		if err != nil {
Overbool's avatar
Overbool committed
1013
			return err
Jeromy's avatar
Jeromy committed
1014
		}
1015

Overbool's avatar
Overbool committed
1016
		return pdir.Flush()
Jeromy's avatar
Jeromy committed
1017 1018 1019
	},
}

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

	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
1049 1050 1051
func getPrefix(req *cmds.Request) (cid.Builder, error) {
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064

	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
1065

1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
	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
}

1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090
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,
	})
}

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

1121
		nd := dag.NodeWithData(ft.FilePBData(nil, 0))
1122
		nd.SetCidBuilder(builder)
Jeromy's avatar
Jeromy committed
1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
		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
1133 1134
		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1135
			return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
Jeromy's avatar
Jeromy committed
1136 1137
		}
		return fi, nil
Jeromy's avatar
Jeromy committed
1138 1139 1140 1141 1142

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1143 1144 1145

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

	if p[0] != '/' {
Michael Muré's avatar
Michael Muré committed
1150
		return "", fmt.Errorf("paths must start with a leading slash")
Jeromy's avatar
Jeromy committed
1151 1152 1153 1154 1155 1156 1157 1158
	}

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