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
	"strings"

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

Steven Allen's avatar
Steven Allen committed
17
	"gx/ipfs/QmP9eu5X5Ax8169jNWqAJcc42mdZgzLR1aKCEzqhNoBLKk/go-mfs"
18
	"gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
Steven Allen's avatar
Steven Allen committed
19
	ft "gx/ipfs/QmQXze9tG878pa4Euya4rrDpyTNX3kQe4dhCaBzBozGgpe/go-unixfs"
20
	"gx/ipfs/QmR8BauakNcBa3RbE4nbQu76PDiJgoQgz8AJdhJuiU4TAw/go-cid"
Steven Allen's avatar
Steven Allen committed
21
	dag "gx/ipfs/QmTQdH4848iTVCJmKXYyRiK72HufWTLYQQ8iN3JaQ8K1Hq/go-merkledag"
Hector Sanjuan's avatar
Hector Sanjuan committed
22
	"gx/ipfs/QmWGm4AbZEbnmdgVTza52MSNpEmBdFVqzmAysRbjrRyGbH/go-ipfs-cmds"
Steven Allen's avatar
Steven Allen committed
23
	bservice "gx/ipfs/QmYPZzd9VqmJDwxUnThfeSbV1Y5o53aVPDijTB7j7rS9Ep/go-blockservice"
24
	"gx/ipfs/QmYZwey1thDTynSrvd6qQkX24UpTka6TFhQ2v569UpoqxD/go-ipfs-exchange-offline"
Steven Allen's avatar
Steven Allen committed
25 26
	ipld "gx/ipfs/QmcKKBwfz6FyQdHR2jsXrrF6XeSBXYL86anmWNewpFpoF5/go-ipld-format"
	logging "gx/ipfs/QmcuXC5cxs79ro2cUuHs4HQ2bkDLJUYokwL8aivcX6HW3C/go-log"
27
	"gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
Steven Allen's avatar
Steven Allen committed
28
	mh "gx/ipfs/QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW/go-multihash"
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{
52
		cmdkit.BoolOption(filesFlushOptionName, "f", "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
		api, err := cmdenv.GetApi(env, req)
128 129 130 131
		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
		}

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

316
		flush, _ := req.Options[filesFlushOptionName].(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
					})
				}
450
				return cmds.EmitOnce(res, &filesLsOutput{output})
Jeromy's avatar
Jeromy committed
451
			}
452 453 454 455
			listing, err := fsn.List(req.Context)
			if err != nil {
				return err
			}
456
			return cmds.EmitOnce(res, &filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
457
		case *mfs.File:
rht's avatar
rht committed
458
			_, name := gopath.Split(path)
Overbool's avatar
Overbool committed
459
			out := &filesLsOutput{[]mfs.NodeListing{{Name: name}}}
460 461 462 463 464
			if long {
				out.Entries[0].Type = int(fsn.Type())

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

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

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

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

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

Examples:

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

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

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

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

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

Steven Allen's avatar
Steven Allen committed
554
		rfd, err := fi.Open(mfs.Flags{Read: true})
555
		if err != nil {
Overbool's avatar
Overbool committed
556
			return err
557 558 559 560
		}

		defer rfd.Close()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

671
EXAMPLE:
Jeromy's avatar
Jeromy committed
672

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

676
WARNING:
677

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

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

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

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

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

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

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

Steven Allen's avatar
Steven Allen committed
739
		wfd, err := fi.Open(mfs.Flags{Write: true, Sync: flush})
740
		if err != nil {
keks's avatar
keks committed
741
			return err
742
		}
Jeromy's avatar
Jeromy committed
743

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

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

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

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

772 773 774 775
		var r io.Reader
		r, err = cmdenv.GetFileArg(req.Files.Entries())
		if err != nil {
			return err
Jeromy's avatar
Jeromy committed
776
		}
zramsay's avatar
zramsay committed
777 778 779 780
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

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

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

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

795
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
796 797 798

Examples:

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

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

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

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

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

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

Overbool's avatar
Overbool committed
838
		return err
Jeromy's avatar
Jeromy committed
839 840 841
	},
}

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

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

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

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

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

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

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

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

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

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

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

	if flush {
		nd.Flush()
	}

	return nil
}

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

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

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

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

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

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

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

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

Overbool's avatar
Overbool committed
990
			return pdir.Flush()
Jeromy's avatar
Jeromy committed
991 992
		}

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

1000
		dashr, _ := req.Options[recursiveOptionName].(bool)
1001 1002

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

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

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

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

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

	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
1063

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

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

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

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

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1141 1142 1143

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

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

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