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 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"
Steven Allen's avatar
Steven Allen committed
18
	bservice "gx/ipfs/QmPoh3SrQzFBWtdGK6qmHDV4EanKR6kYPj4DD3J2NLoEmZ/go-blockservice"
Steven Allen's avatar
Steven Allen committed
19
	cid "gx/ipfs/QmR8BauakNcBa3RbE4nbQu76PDiJgoQgz8AJdhJuiU4TAw/go-cid"
20
	mfs "gx/ipfs/QmU3iDRUrxyTYdV2j5MuWLFvP1k7w98vD66PLnNChgvUmZ/go-mfs"
Łukasz Magiera's avatar
Łukasz Magiera committed
21
	files "gx/ipfs/QmXWZCd8jfaHmt4UDSnjKmGcrQMw95bDGWqEeVLVJjoANX/go-ipfs-files"
22
	offline "gx/ipfs/QmYZwey1thDTynSrvd6qQkX24UpTka6TFhQ2v569UpoqxD/go-ipfs-exchange-offline"
23 24
	cmds "gx/ipfs/QmaAP56JAwdjwisPTu4yx17whcjTr6y5JCSCF77Y1rahWV/go-ipfs-cmds"
	ft "gx/ipfs/Qmbvw7kpSM2p6rbQ57WGRhhqNfCiNGW6EKH4xgHLw4bsnB/go-unixfs"
Steven Allen's avatar
Steven Allen committed
25 26
	ipld "gx/ipfs/QmcKKBwfz6FyQdHR2jsXrrF6XeSBXYL86anmWNewpFpoF5/go-ipld-format"
	logging "gx/ipfs/QmcuXC5cxs79ro2cUuHs4HQ2bkDLJUYokwL8aivcX6HW3C/go-log"
Steven Allen's avatar
Steven Allen committed
27
	dag "gx/ipfs/QmdV35UHnL1FM52baPkeUo6u7Fxm2CRUkPTLRPxeF8a4Ap/go-merkledag"
28
	cmdkit "gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
Steven Allen's avatar
Steven Allen committed
29
	mh "gx/ipfs/QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW/go-multihash"
Jeromy's avatar
Jeromy committed
30 31
)

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

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

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

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

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

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

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

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

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

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

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

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

128 129 130 131 132
		api, err := cmdenv.GetApi(env)
		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 145 146 147 148 149 150

		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
		}

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

156
		o, err := statNode(nd)
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
		if !withLocal {
keks's avatar
keks committed
162
			return cmds.EmitOnce(res, o)
163 164 165 166 167 168 169 170
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

Michael Muré's avatar
Michael Muré committed
246
		return &statOutput{
247 248
			Hash:           c.String(),
			Blocks:         len(nd.Links()),
Overbool's avatar
Overbool committed
249
			Size:           d.FileSize(),
250 251 252 253
			CumulativeSize: cumulsize,
			Type:           ndtype,
		}, nil
	case *dag.RawNode:
Michael Muré's avatar
Michael Muré committed
254
		return &statOutput{
255 256 257 258 259 260
			Hash:           c.String(),
			Blocks:         0,
			Size:           cumulsize,
			CumulativeSize: cumulsize,
			Type:           "file",
		}, nil
Jeromy's avatar
Jeromy committed
261
	default:
262
		return nil, fmt.Errorf("not unixfs node (proto or raw)")
Jeromy's avatar
Jeromy committed
263
	}
Jeromy's avatar
Jeromy committed
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 297
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
298
var filesCpCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
299
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
300
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
301
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
302 303 304
	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
305
	},
Overbool's avatar
Overbool committed
306 307
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
308
		if err != nil {
Overbool's avatar
Overbool committed
309
			return err
Jeromy's avatar
Jeromy committed
310 311
		}

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

317
		flush, _ := req.Options[filesFlushOptionName].(bool)
318

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

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

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

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

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

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

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

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

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

		return fsn.GetNode()
	}
}

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

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

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

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

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

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

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

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

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

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

				for _, name := range names {
Jeromy's avatar
Jeromy committed
447
					output = append(output, mfs.NodeListing{
Jeromy's avatar
Jeromy committed
448
						Name: name,
Jeromy's avatar
Jeromy committed
449 450
					})
				}
451
				return cmds.EmitOnce(res, &filesLsOutput{output})
Jeromy's avatar
Jeromy committed
452
			}
453 454 455 456
			listing, err := fsn.List(req.Context)
			if err != nil {
				return err
			}
457
			return cmds.EmitOnce(res, &filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
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()
			}
476
			return cmds.EmitOnce(res, 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
		it := req.Files.Entries()
774 775 776
		if !it.Next() && it.Err() != nil {
			return it.Err()
		}
Łukasz Magiera's avatar
Łukasz Magiera committed
777
		if files.FileFromEntry(it) == nil {
778
			return fmt.Errorf("expected a regular file")
Jeromy's avatar
Jeromy committed
779 780
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
781
		var r io.Reader = files.FileFromEntry(it)
zramsay's avatar
zramsay committed
782 783 784 785
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
786
		_, err = io.Copy(wfd, r)
keks's avatar
keks committed
787
		return err
Jeromy's avatar
Jeromy committed
788 789 790
	},
}

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

797 798 799
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

800
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
801 802 803

Examples:

804 805
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
806 807 808
`,
	},

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

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

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

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

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

Overbool's avatar
Overbool committed
843
		return err
Jeromy's avatar
Jeromy committed
844 845 846
	},
}

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

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

Overbool's avatar
Overbool committed
869
		return mfs.FlushPath(nd.FilesRoot, path)
Jeromy's avatar
Jeromy committed
870 871 872
	},
}

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

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

Overbool's avatar
Overbool committed
898
		flush, _ := req.Options[filesFlushOptionName].(bool)
Kevin Atkinson's avatar
Kevin Atkinson committed
899 900 901

		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
902
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
903
		}
keks's avatar
keks committed
904

Overbool's avatar
Overbool committed
905
		return updatePath(nd.FilesRoot, path, prefix, flush)
Kevin Atkinson's avatar
Kevin Atkinson committed
906 907 908
	},
}

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

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

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

	if flush {
		nd.Flush()
	}

	return nil
}

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

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

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

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

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

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

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

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

Overbool's avatar
Overbool committed
995
			return pdir.Flush()
Jeromy's avatar
Jeromy committed
996 997
		}

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

1005
		dashr, _ := req.Options[recursiveOptionName].(bool)
1006 1007

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

1014 1015
		err = pdir.Unlink(name)
		if err != nil {
Overbool's avatar
Overbool committed
1016
			return err
Jeromy's avatar
Jeromy committed
1017
		}
1018

Overbool's avatar
Overbool committed
1019
		return pdir.Flush()
Jeromy's avatar
Jeromy committed
1020 1021 1022
	},
}

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

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

	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
1068

1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
	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
}

1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
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,
	})
}

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

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

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1146 1147 1148

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

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

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