files.go 28.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
	"github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/core/commands/cmdenv"
15

Jakub Sztandera's avatar
Jakub Sztandera committed
16 17 18 19
	"github.com/dustin/go-humanize"
	bservice "github.com/ipfs/go-blockservice"
	cid "github.com/ipfs/go-cid"
	cidenc "github.com/ipfs/go-cidutil/cidenc"
20 21
	cmds "github.com/ipfs/go-ipfs-cmds"
	offline "github.com/ipfs/go-ipfs-exchange-offline"
Jakub Sztandera's avatar
Jakub Sztandera committed
22 23 24 25 26
	ipld "github.com/ipfs/go-ipld-format"
	logging "github.com/ipfs/go-log"
	dag "github.com/ipfs/go-merkledag"
	"github.com/ipfs/go-mfs"
	ft "github.com/ipfs/go-unixfs"
27
	iface "github.com/ipfs/interface-go-ipfs-core"
28
	path "github.com/ipfs/interface-go-ipfs-core/path"
Jakub Sztandera's avatar
Jakub Sztandera committed
29
	mh "github.com/multiformats/go-multihash"
Jeromy's avatar
Jeromy committed
30 31
)

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

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

42 43 44 45 46 47 48 49 50 51 52 53
The files facility interacts with MFS (Mutable File System). MFS acts as a
single, dynamic filesystem mount. MFS has a root CID which is transparently
updated when a change happens (and can be checked with "ipfs files stat /").

All files and folders within MFS are respected and will not be cleaned up
during garbage collections. MFS is independent from the list of pinned items
("ipfs pin ls"). Calls to "ipfs pin add" and "ipfs pin rm" do not affect
content on MFS. Similarly, content added with "ipfs add" (which by default
pins), is not added to MFS. Any content can be put into MFS with the command
"ipfs files cp /ipfs/<cid> /mfs/path/".


54
NOTE:
55 56 57 58 59 60 61
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
62 63
`,
	},
Steven Allen's avatar
Steven Allen committed
64 65
	Options: []cmds.Option{
		cmds.BoolOption(filesFlushOptionName, "f", "Flush target and ancestors after write.").WithDefault(true),
Jeromy's avatar
Jeromy committed
66
	},
Jeromy's avatar
Jeromy committed
67
	Subcommands: map[string]*cmds.Command{
Overbool's avatar
Overbool committed
68
		"read":  filesReadCmd,
69
		"write": filesWriteCmd,
Overbool's avatar
Overbool committed
70 71 72 73
		"mv":    filesMvCmd,
		"cp":    filesCpCmd,
		"ls":    filesLsCmd,
		"mkdir": filesMkdirCmd,
74
		"stat":  filesStatCmd,
Overbool's avatar
Overbool committed
75 76 77
		"rm":    filesRmCmd,
		"flush": filesFlushCmd,
		"chcid": filesChcidCmd,
Jeromy's avatar
Jeromy committed
78 79 80
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
81 82 83 84 85
const (
	filesCidVersionOptionName = "cid-version"
	filesHashOptionName       = "hash"
)

Steven Allen's avatar
Steven Allen committed
86 87
var cidVersionOption = cmds.IntOption(filesCidVersionOptionName, "cid-ver", "Cid version to use. (experimental)")
var hashOption = cmds.StringOption(filesHashOptionName, "Hash function to use. Will set Cid version to 1 if used. (experimental)")
88

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

Michael Muré's avatar
Michael Muré committed
91
type statOutput struct {
92 93 94 95 96
	Hash           string
	Size           uint64
	CumulativeSize uint64
	Blocks         int
	Type           string
Michael Muré's avatar
Michael Muré committed
97
	WithLocality   bool   `json:",omitempty"`
98 99 100 101
	Local          bool   `json:",omitempty"`
	SizeLocal      uint64 `json:",omitempty"`
}

Kejie Zhang's avatar
Kejie Zhang committed
102 103
const (
	defaultStatFormat = `<hash>
keks's avatar
keks committed
104 105 106 107
Size: <size>
CumulativeSize: <cumulsize>
ChildBlocks: <childs>
Type: <type>`
Kejie Zhang's avatar
Kejie Zhang committed
108 109 110 111
	filesFormatOptionName    = "format"
	filesSizeOptionName      = "size"
	filesWithLocalOptionName = "with-local"
)
keks's avatar
keks committed
112

113
var filesStatCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
114
	Helptext: cmds.HelpText{
rht's avatar
rht committed
115
		Tagline: "Display file status.",
Jeromy's avatar
Jeromy committed
116 117
	},

Steven Allen's avatar
Steven Allen committed
118 119
	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, false, "Path to node to stat."),
Jeromy's avatar
Jeromy committed
120
	},
Steven Allen's avatar
Steven Allen committed
121 122
	Options: []cmds.Option{
		cmds.StringOption(filesFormatOptionName, "Print statistics in given format. Allowed tokens: "+
keks's avatar
keks committed
123
			"<hash> <size> <cumulsize> <type> <childs>. Conflicts with other format options.").WithDefault(defaultStatFormat),
Steven Allen's avatar
Steven Allen committed
124 125 126
		cmds.BoolOption(filesHashOptionName, "Print only hash. Implies '--format=<hash>'. Conflicts with other format options."),
		cmds.BoolOption(filesSizeOptionName, "Print only size. Implies '--format=<cumulsize>'. Conflicts with other format options."),
		cmds.BoolOption(filesWithLocalOptionName, "Compute the amount of the dag that is local, and if possible the total size"),
127
	},
keks's avatar
keks committed
128
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
129 130 131

		_, err := statGetFormatOptions(req)
		if err != nil {
Steven Allen's avatar
Steven Allen committed
132
			return cmds.Errorf(cmds.ErrClient, err.Error())
133 134
		}

135
		node, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
136
		if err != nil {
keks's avatar
keks committed
137
			return err
Jeromy's avatar
Jeromy committed
138 139
		}

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

145
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
146
		if err != nil {
keks's avatar
keks committed
147
			return err
Jeromy's avatar
Jeromy committed
148 149
		}

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

152 153 154 155 156
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

157 158 159 160 161 162 163 164 165 166 167
		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
		}

168
		nd, err := getNodeFromPath(req.Context, node, api, path)
Jeromy's avatar
Jeromy committed
169
		if err != nil {
keks's avatar
keks committed
170
			return err
Jeromy's avatar
Jeromy committed
171 172
		}

173
		o, err := statNode(nd, enc)
Jeromy's avatar
Jeromy committed
174
		if err != nil {
keks's avatar
keks committed
175
			return err
Jeromy's avatar
Jeromy committed
176 177
		}

178
		if !withLocal {
keks's avatar
keks committed
179
			return cmds.EmitOnce(res, o)
180 181 182
		}

		local, sizeLocal, err := walkBlock(req.Context, dagserv, nd)
183 184 185
		if err != nil {
			return err
		}
186 187 188 189 190

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

keks's avatar
keks committed
191
		return cmds.EmitOnce(res, o)
Jeromy's avatar
Jeromy committed
192
	},
193
	Encoders: cmds.EncoderMap{
194
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *statOutput) error {
195
			s, _ := statGetFormatOptions(req)
196 197 198 199 200 201
			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)

202 203 204 205 206 207 208 209 210 211 212
			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
213
		}),
Jeromy's avatar
Jeromy committed
214
	},
Michael Muré's avatar
Michael Muré committed
215
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
216 217
}

218 219 220 221
func moreThanOne(a, b, c bool) bool {
	return a && b || b && c || a && c
}

222
func statGetFormatOptions(req *cmds.Request) (string, error) {
223

Kejie Zhang's avatar
Kejie Zhang committed
224 225 226
	hash, _ := req.Options[filesHashOptionName].(bool)
	size, _ := req.Options[filesSizeOptionName].(bool)
	format, _ := req.Options[filesFormatOptionName].(string)
227

keks's avatar
keks committed
228
	if moreThanOne(hash, size, format != defaultStatFormat) {
Michael Muré's avatar
Michael Muré committed
229
		return "", errFormat
230 231 232
	}

	if hash {
233 234 235 236 237
		return "<hash>", nil
	} else if size {
		return "<cumulsize>", nil
	} else {
		return format, nil
238 239 240
	}
}

241
func statNode(nd ipld.Node, enc cidenc.Encoder) (*statOutput, error) {
Jeromy's avatar
Jeromy committed
242
	c := nd.Cid()
Jeromy's avatar
Jeromy committed
243 244 245 246 247 248

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

249 250
	switch n := nd.(type) {
	case *dag.ProtoNode:
Overbool's avatar
Overbool committed
251
		d, err := ft.FSNodeFromBytes(n.Data())
252 253 254 255 256
		if err != nil {
			return nil, err
		}

		var ndtype string
Overbool's avatar
Overbool committed
257
		switch d.Type() {
258
		case ft.TDirectory, ft.THAMTShard:
259
			ndtype = "directory"
260
		case ft.TFile, ft.TMetadata, ft.TRaw:
261 262
			ndtype = "file"
		default:
Overbool's avatar
Overbool committed
263
			return nil, fmt.Errorf("unrecognized node type: %s", d.Type())
264 265
		}

Michael Muré's avatar
Michael Muré committed
266
		return &statOutput{
267
			Hash:           enc.Encode(c),
268
			Blocks:         len(nd.Links()),
Overbool's avatar
Overbool committed
269
			Size:           d.FileSize(),
270 271 272 273
			CumulativeSize: cumulsize,
			Type:           ndtype,
		}, nil
	case *dag.RawNode:
Michael Muré's avatar
Michael Muré committed
274
		return &statOutput{
275
			Hash:           enc.Encode(c),
276 277 278 279 280
			Blocks:         0,
			Size:           cumulsize,
			CumulativeSize: cumulsize,
			Type:           "file",
		}, nil
Jeromy's avatar
Jeromy committed
281
	default:
282
		return nil, fmt.Errorf("not unixfs node (proto or raw)")
Jeromy's avatar
Jeromy committed
283
	}
Jeromy's avatar
Jeromy committed
284 285
}

286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
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
318
var filesCpCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
319
	Helptext: cmds.HelpText{
rht's avatar
rht committed
320
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
321
	},
Steven Allen's avatar
Steven Allen committed
322 323 324
	Arguments: []cmds.Argument{
		cmds.StringArg("source", true, false, "Source object to copy."),
		cmds.StringArg("dest", true, false, "Destination to copy object to."),
Jeromy's avatar
Jeromy committed
325
	},
Overbool's avatar
Overbool committed
326 327
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
328
		if err != nil {
Overbool's avatar
Overbool committed
329
			return err
Jeromy's avatar
Jeromy committed
330 331
		}

332
		api, err := cmdenv.GetApi(env, req)
333
		if err != nil {
Overbool's avatar
Overbool committed
334
			return err
335 336
		}

337
		flush, _ := req.Options[filesFlushOptionName].(bool)
338

Overbool's avatar
Overbool committed
339
		src, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
340
		if err != nil {
Overbool's avatar
Overbool committed
341
			return err
Jeromy's avatar
Jeromy committed
342
		}
343 344
		src = strings.TrimRight(src, "/")

Overbool's avatar
Overbool committed
345
		dst, err := checkPath(req.Arguments[1])
Jeromy's avatar
Jeromy committed
346
		if err != nil {
Overbool's avatar
Overbool committed
347
			return err
Jeromy's avatar
Jeromy committed
348
		}
Jeromy's avatar
Jeromy committed
349

350 351 352 353
		if dst[len(dst)-1] == '/' {
			dst += gopath.Base(src)
		}

Overbool's avatar
Overbool committed
354
		node, err := getNodeFromPath(req.Context, nd, api, src)
Jeromy's avatar
Jeromy committed
355
		if err != nil {
Overbool's avatar
Overbool committed
356
			return fmt.Errorf("cp: cannot get node from path %s: %s", src, err)
Jeromy's avatar
Jeromy committed
357 358
		}

Overbool's avatar
Overbool committed
359
		err = mfs.PutNode(nd.FilesRoot, dst, node)
Jeromy's avatar
Jeromy committed
360
		if err != nil {
Overbool's avatar
Overbool committed
361
			return fmt.Errorf("cp: cannot put node in path %s: %s", dst, err)
Jeromy's avatar
Jeromy committed
362
		}
363 364

		if flush {
365
			_, err := mfs.FlushPath(req.Context, nd.FilesRoot, dst)
366
			if err != nil {
Overbool's avatar
Overbool committed
367
				return fmt.Errorf("cp: cannot flush the created file %s: %s", dst, err)
368 369
			}
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
370

Overbool's avatar
Overbool committed
371
		return nil
Jeromy's avatar
Jeromy committed
372 373 374
	},
}

375
func getNodeFromPath(ctx context.Context, node *core.IpfsNode, api iface.CoreAPI, p string) (ipld.Node, error) {
Jeromy's avatar
Jeromy committed
376 377
	switch {
	case strings.HasPrefix(p, "/ipfs/"):
378
		return api.ResolveNode(ctx, path.New(p))
Jeromy's avatar
Jeromy committed
379 380 381 382 383 384 385 386 387 388
	default:
		fsn, err := mfs.Lookup(node.FilesRoot, p)
		if err != nil {
			return nil, err
		}

		return fsn.GetNode()
	}
}

Michael Muré's avatar
Michael Muré committed
389
type filesLsOutput struct {
Jeromy's avatar
Jeromy committed
390 391 392
	Entries []mfs.NodeListing
}

Kejie Zhang's avatar
Kejie Zhang committed
393
const (
394
	longOptionName     = "long"
Kejie Zhang's avatar
Kejie Zhang committed
395 396 397
	dontSortOptionName = "U"
)

Overbool's avatar
Overbool committed
398
var filesLsCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
399
	Helptext: cmds.HelpText{
400
		Tagline: "List directories in the local mutable namespace.",
Jeromy's avatar
Jeromy committed
401
		ShortDescription: `
402
List directories in the local mutable namespace.
Jeromy's avatar
Jeromy committed
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418

Examples:

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

    $ ipfs files ls /myfiles/a/b/c/d
    foo
    bar
`,
	},
Steven Allen's avatar
Steven Allen committed
419 420
	Arguments: []cmds.Argument{
		cmds.StringArg("path", false, false, "Path to show listing for. Defaults to '/'."),
Jeromy's avatar
Jeromy committed
421
	},
Steven Allen's avatar
Steven Allen committed
422
	Options: []cmds.Option{
423
		cmds.BoolOption(longOptionName, "l", "Use long listing format."),
Steven Allen's avatar
Steven Allen committed
424
		cmds.BoolOption(dontSortOptionName, "Do not sort; list entries in directory order."),
Jeromy's avatar
Jeromy committed
425
	},
Overbool's avatar
Overbool committed
426
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
427 428
		var arg string

Overbool's avatar
Overbool committed
429
		if len(req.Arguments) == 0 {
430 431
			arg = "/"
		} else {
Overbool's avatar
Overbool committed
432
			arg = req.Arguments[0]
433 434 435
		}

		path, err := checkPath(arg)
Jeromy's avatar
Jeromy committed
436
		if err != nil {
Overbool's avatar
Overbool committed
437
			return err
Jeromy's avatar
Jeromy committed
438 439
		}

Overbool's avatar
Overbool committed
440
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
441
		if err != nil {
Overbool's avatar
Overbool committed
442
			return err
Jeromy's avatar
Jeromy committed
443 444 445 446
		}

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

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

452 453 454 455 456
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

Jeromy's avatar
Jeromy committed
457 458
		switch fsn := fsn.(type) {
		case *mfs.Directory:
Jeromy's avatar
Jeromy committed
459 460
			if !long {
				var output []mfs.NodeListing
Overbool's avatar
Overbool committed
461
				names, err := fsn.ListNames(req.Context)
462
				if err != nil {
Overbool's avatar
Overbool committed
463
					return err
464 465 466
				}

				for _, name := range names {
Jeromy's avatar
Jeromy committed
467
					output = append(output, mfs.NodeListing{
Jeromy's avatar
Jeromy committed
468
						Name: name,
Jeromy's avatar
Jeromy committed
469 470
					})
				}
471
				return cmds.EmitOnce(res, &filesLsOutput{output})
Jeromy's avatar
Jeromy committed
472
			}
473 474 475 476
			listing, err := fsn.List(req.Context)
			if err != nil {
				return err
			}
477
			return cmds.EmitOnce(res, &filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
478
		case *mfs.File:
rht's avatar
rht committed
479
			_, name := gopath.Split(path)
Overbool's avatar
Overbool committed
480
			out := &filesLsOutput{[]mfs.NodeListing{{Name: name}}}
481 482 483 484 485
			if long {
				out.Entries[0].Type = int(fsn.Type())

				size, err := fsn.Size()
				if err != nil {
Overbool's avatar
Overbool committed
486
					return err
487 488 489 490 491
				}
				out.Entries[0].Size = size

				nd, err := fsn.GetNode()
				if err != nil {
Overbool's avatar
Overbool committed
492
					return err
493
				}
494
				out.Entries[0].Hash = enc.Encode(nd.Cid())
495
			}
496
			return cmds.EmitOnce(res, out)
Jeromy's avatar
Jeromy committed
497
		default:
Overbool's avatar
Overbool committed
498
			return errors.New("unrecognized type")
Jeromy's avatar
Jeromy committed
499 500
		}
	},
Overbool's avatar
Overbool committed
501 502 503
	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
504 505 506 507 508 509
			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
510
			long, _ := req.Options[longOptionName].(bool)
Jeromy's avatar
Jeromy committed
511 512
			for _, o := range out.Entries {
				if long {
Overbool's avatar
Overbool committed
513 514 515
					if o.Type == int(mfs.TDir) {
						o.Name += "/"
					}
Overbool's avatar
Overbool committed
516
					fmt.Fprintf(w, "%s\t%s\t%d\n", o.Name, o.Hash, o.Size)
Jeromy's avatar
Jeromy committed
517
				} else {
Overbool's avatar
Overbool committed
518
					fmt.Fprintf(w, "%s\n", o.Name)
Jeromy's avatar
Jeromy committed
519 520
				}
			}
Overbool's avatar
Overbool committed
521 522 523

			return nil
		}),
Jeromy's avatar
Jeromy committed
524
	},
Michael Muré's avatar
Michael Muré committed
525
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
526 527
}

Kejie Zhang's avatar
Kejie Zhang committed
528 529 530 531 532
const (
	filesOffsetOptionName = "offset"
	filesCountOptionName  = "count"
)

Overbool's avatar
Overbool committed
533
var filesReadCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
534
	Helptext: cmds.HelpText{
rht's avatar
rht committed
535
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
536
		ShortDescription: `
537 538
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
539 540 541 542 543

Examples:

    $ ipfs files read /test/hello
    hello
Jeromy's avatar
Jeromy committed
544
        `,
Jeromy's avatar
Jeromy committed
545 546
	},

Steven Allen's avatar
Steven Allen committed
547 548
	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
549
	},
Steven Allen's avatar
Steven Allen committed
550 551 552
	Options: []cmds.Option{
		cmds.Int64Option(filesOffsetOptionName, "o", "Byte offset to begin reading from."),
		cmds.Int64Option(filesCountOptionName, "n", "Maximum number of bytes to read."),
Jeromy's avatar
Jeromy committed
553
	},
Overbool's avatar
Overbool committed
554 555
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
556
		if err != nil {
Overbool's avatar
Overbool committed
557
			return err
Jeromy's avatar
Jeromy committed
558 559
		}

Overbool's avatar
Overbool committed
560
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
561
		if err != nil {
Overbool's avatar
Overbool committed
562
			return err
Jeromy's avatar
Jeromy committed
563 564
		}

Overbool's avatar
Overbool committed
565
		fsn, err := mfs.Lookup(nd.FilesRoot, path)
Jeromy's avatar
Jeromy committed
566
		if err != nil {
Overbool's avatar
Overbool committed
567
			return err
Jeromy's avatar
Jeromy committed
568 569 570 571
		}

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

Steven Allen's avatar
Steven Allen committed
575
		rfd, err := fi.Open(mfs.Flags{Read: true})
576
		if err != nil {
Overbool's avatar
Overbool committed
577
			return err
578 579 580 581
		}

		defer rfd.Close()

Overbool's avatar
Overbool committed
582
		offset, _ := req.Options[offsetOptionName].(int64)
Jeromy's avatar
Jeromy committed
583
		if offset < 0 {
Overbool's avatar
Overbool committed
584
			return fmt.Errorf("cannot specify negative offset")
Jeromy's avatar
Jeromy committed
585 586
		}

587
		filen, err := rfd.Size()
Jeromy's avatar
Jeromy committed
588
		if err != nil {
Overbool's avatar
Overbool committed
589
			return err
Jeromy's avatar
Jeromy committed
590 591 592
		}

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

596
		_, err = rfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
597
		if err != nil {
Overbool's avatar
Overbool committed
598
			return err
Jeromy's avatar
Jeromy committed
599
		}
600

Overbool's avatar
Overbool committed
601 602
		var r io.Reader = &contextReaderWrapper{R: rfd, ctx: req.Context}
		count, found := req.Options[filesCountOptionName].(int64)
Jeromy's avatar
Jeromy committed
603 604
		if found {
			if count < 0 {
Overbool's avatar
Overbool committed
605
				return fmt.Errorf("cannot specify negative 'count'")
Jeromy's avatar
Jeromy committed
606
			}
607
			r = io.LimitReader(r, int64(count))
Jeromy's avatar
Jeromy committed
608
		}
Overbool's avatar
Overbool committed
609
		return res.Emit(r)
Jeromy's avatar
Jeromy committed
610 611 612
	},
}

Jeromy's avatar
Jeromy committed
613 614 615 616 617 618 619 620 621 622 623 624 625
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
626
var filesMvCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
627
	Helptext: cmds.HelpText{
rht's avatar
rht committed
628
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
629 630 631 632 633 634 635
		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
636
`,
Jeromy's avatar
Jeromy committed
637 638
	},

Steven Allen's avatar
Steven Allen committed
639 640 641
	Arguments: []cmds.Argument{
		cmds.StringArg("source", true, false, "Source file to move."),
		cmds.StringArg("dest", true, false, "Destination path for file to be moved to."),
Jeromy's avatar
Jeromy committed
642
	},
Overbool's avatar
Overbool committed
643 644
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
645
		if err != nil {
Overbool's avatar
Overbool committed
646
			return err
Jeromy's avatar
Jeromy committed
647 648
		}

649 650
		flush, _ := req.Options[filesFlushOptionName].(bool)

Overbool's avatar
Overbool committed
651
		src, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
652
		if err != nil {
Overbool's avatar
Overbool committed
653
			return err
Jeromy's avatar
Jeromy committed
654
		}
Overbool's avatar
Overbool committed
655
		dst, err := checkPath(req.Arguments[1])
Jeromy's avatar
Jeromy committed
656
		if err != nil {
Overbool's avatar
Overbool committed
657
			return err
Jeromy's avatar
Jeromy committed
658
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
659

660 661
		err = mfs.Mv(nd.FilesRoot, src, dst)
		if err == nil && flush {
662
			_, err = mfs.FlushPath(req.Context, nd.FilesRoot, "/")
663 664
		}
		return err
Jeromy's avatar
Jeromy committed
665 666 667
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
668 669 670 671 672 673 674 675
const (
	filesCreateOptionName    = "create"
	filesParentsOptionName   = "parents"
	filesTruncateOptionName  = "truncate"
	filesRawLeavesOptionName = "raw-leaves"
	filesFlushOptionName     = "flush"
)

676
var filesWriteCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
677
	Helptext: cmds.HelpText{
rht's avatar
rht committed
678
		Tagline: "Write to a mutable file in a given filesystem.",
Jeromy's avatar
Jeromy committed
679 680
		ShortDescription: `
Write data to a file in a given filesystem. This command allows you to specify
681 682
a beginning offset to write to. The entire length of the input will be
written.
Jeromy's avatar
Jeromy committed
683

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

687 688 689 690 691 692 693
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.

694 695 696 697
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.

698
EXAMPLE:
Jeromy's avatar
Jeromy committed
699

Jeromy's avatar
Jeromy committed
700 701
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
702

703
WARNING:
704

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

Kejie Zhang's avatar
Kejie Zhang committed
730 731 732 733 734
		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)
735

736
		prefix, err := getPrefixNew(req)
737
		if err != nil {
keks's avatar
keks committed
738
			return err
739
		}
Jeromy's avatar
Jeromy committed
740

741
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
742
		if err != nil {
keks's avatar
keks committed
743
			return err
Jeromy's avatar
Jeromy committed
744 745
		}

746
		offset, _ := req.Options[filesOffsetOptionName].(int64)
Jeromy's avatar
Jeromy committed
747
		if offset < 0 {
keks's avatar
keks committed
748
			return fmt.Errorf("cannot have negative write offset")
Jeromy's avatar
Jeromy committed
749 750
		}

751 752 753
		if mkParents {
			err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix)
			if err != nil {
keks's avatar
keks committed
754
				return err
755 756 757
			}
		}

758
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
759
		if err != nil {
keks's avatar
keks committed
760
			return err
Jeromy's avatar
Jeromy committed
761
		}
762 763 764
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
765

Steven Allen's avatar
Steven Allen committed
766
		wfd, err := fi.Open(mfs.Flags{Write: true, Sync: flush})
767
		if err != nil {
keks's avatar
keks committed
768
			return err
769
		}
Jeromy's avatar
Jeromy committed
770

771 772 773
		defer func() {
			err := wfd.Close()
			if err != nil {
keks's avatar
keks committed
774 775 776 777 778
				if retErr == nil {
					retErr = err
				} else {
					log.Error("files: error closing file mfs file descriptor", err)
				}
779 780
			}
		}()
781

Jeromy's avatar
Jeromy committed
782
		if trunc {
783
			if err := wfd.Truncate(0); err != nil {
keks's avatar
keks committed
784
				return err
Jeromy's avatar
Jeromy committed
785 786 787
			}
		}

788
		count, countfound := req.Options[filesCountOptionName].(int64)
Jeromy's avatar
Jeromy committed
789
		if countfound && count < 0 {
keks's avatar
keks committed
790
			return fmt.Errorf("cannot have negative byte count")
Jeromy's avatar
Jeromy committed
791
		}
Jeromy's avatar
Jeromy committed
792

793
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
794
		if err != nil {
795
			flog.Error("seekfail: ", err)
keks's avatar
keks committed
796
			return err
Jeromy's avatar
Jeromy committed
797 798
		}

799 800 801 802
		var r io.Reader
		r, err = cmdenv.GetFileArg(req.Files.Entries())
		if err != nil {
			return err
Jeromy's avatar
Jeromy committed
803
		}
zramsay's avatar
zramsay committed
804 805 806 807
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
808
		_, err = io.Copy(wfd, r)
keks's avatar
keks committed
809
		return err
Jeromy's avatar
Jeromy committed
810 811 812
	},
}

Overbool's avatar
Overbool committed
813
var filesMkdirCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
814
	Helptext: cmds.HelpText{
rht's avatar
rht committed
815
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
816 817 818
		ShortDescription: `
Create the directory if it does not already exist.

819 820 821
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

822
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
823 824 825

Examples:

826 827
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
828 829 830
`,
	},

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

Overbool's avatar
Overbool committed
845 846
		dashp, _ := req.Options[filesParentsOptionName].(bool)
		dirtomake, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
847
		if err != nil {
Overbool's avatar
Overbool committed
848
			return err
Jeromy's avatar
Jeromy committed
849 850
		}

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

853 854
		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
855
			return err
856 857 858
		}
		root := n.FilesRoot

859
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
860 861 862
			Mkparents:  dashp,
			Flush:      flush,
			CidBuilder: prefix,
863
		})
keks's avatar
keks committed
864

Overbool's avatar
Overbool committed
865
		return err
Jeromy's avatar
Jeromy committed
866 867 868
	},
}

869 870 871 872
type flushRes struct {
	Cid string
}

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

890 891 892 893 894
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

Jeromy's avatar
Jeromy committed
895
		path := "/"
Overbool's avatar
Overbool committed
896 897
		if len(req.Arguments) > 0 {
			path = req.Arguments[0]
Jeromy's avatar
Jeromy committed
898
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
899

900 901 902 903 904 905
		n, err := mfs.FlushPath(req.Context, nd.FilesRoot, path)
		if err != nil {
			return err
		}

		return cmds.EmitOnce(res, &flushRes{enc.Encode(n.Cid())})
Jeromy's avatar
Jeromy committed
906
	},
907
	Type: flushRes{},
Jeromy's avatar
Jeromy committed
908 909
}

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

		path := "/"
Overbool's avatar
Overbool committed
931 932
		if len(req.Arguments) > 0 {
			path = req.Arguments[0]
Kevin Atkinson's avatar
Kevin Atkinson committed
933 934
		}

Overbool's avatar
Overbool committed
935
		flush, _ := req.Options[filesFlushOptionName].(bool)
Kevin Atkinson's avatar
Kevin Atkinson committed
936 937 938

		prefix, err := getPrefix(req)
		if err != nil {
Overbool's avatar
Overbool committed
939
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
940
		}
keks's avatar
keks committed
941

942 943
		err = updatePath(nd.FilesRoot, path, prefix)
		if err == nil && flush {
944
			_, err = mfs.FlushPath(req.Context, nd.FilesRoot, path)
945 946
		}
		return err
Kevin Atkinson's avatar
Kevin Atkinson committed
947 948 949
	},
}

950
func updatePath(rt *mfs.Root, pth string, builder cid.Builder) error {
951
	if builder == nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
952 953 954 955 956 957 958 959 960 961
		return nil
	}

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

	switch n := nd.(type) {
	case *mfs.Directory:
962
		n.SetCidBuilder(builder)
Kevin Atkinson's avatar
Kevin Atkinson committed
963 964 965 966 967 968 969
	default:
		return fmt.Errorf("can only update directories")
	}

	return nil
}

Overbool's avatar
Overbool committed
970
var filesRmCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
971
	Helptext: cmds.HelpText{
rht's avatar
rht committed
972
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
973
		ShortDescription: `
974
Remove files or directories.
Jeromy's avatar
Jeromy committed
975 976 977 978 979 980 981 982

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

Steven Allen's avatar
Steven Allen committed
985 986
	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
987
	},
Steven Allen's avatar
Steven Allen committed
988 989 990
	Options: []cmds.Option{
		cmds.BoolOption(recursiveOptionName, "r", "Recursively remove directories."),
		cmds.BoolOption(forceOptionName, "Forcibly remove target at path; implies -r for directories"),
Jeromy's avatar
Jeromy committed
991
	},
Overbool's avatar
Overbool committed
992
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
993
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
994
		if err != nil {
Overbool's avatar
Overbool committed
995
			return err
Jeromy's avatar
Jeromy committed
996 997
		}

Overbool's avatar
Overbool committed
998
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
999
		if err != nil {
Overbool's avatar
Overbool committed
1000
			return err
Jeromy's avatar
Jeromy committed
1001 1002 1003
		}

		if path == "/" {
Overbool's avatar
Overbool committed
1004
			return fmt.Errorf("cannot delete root")
Jeromy's avatar
Jeromy committed
1005 1006 1007 1008 1009 1010 1011
		}

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

1012 1013 1014 1015
		// if '--force' specified, it will remove anything else,
		// including file, directory, corrupted node, etc
		force, _ := req.Options[forceOptionName].(bool)

Jeromy's avatar
Jeromy committed
1016
		dir, name := gopath.Split(path)
1017 1018

		pdir, err := getParentDir(nd.FilesRoot, dir)
Jeromy's avatar
Jeromy committed
1019
		if err != nil {
Oli Evans's avatar
Oli Evans committed
1020 1021
			if force && err == os.ErrNotExist {
				return nil
1022
			}
Overbool's avatar
Overbool committed
1023
			return fmt.Errorf("parent lookup: %s", err)
Jeromy's avatar
Jeromy committed
1024 1025
		}

1026
		if force {
Jeromy's avatar
Jeromy committed
1027 1028
			err := pdir.Unlink(name)
			if err != nil {
Oli Evans's avatar
Oli Evans committed
1029
				if err == os.ErrNotExist {
1030 1031
					return nil
				}
Oli Evans's avatar
Oli Evans committed
1032
				return err
Jeromy's avatar
Jeromy committed
1033
			}
Overbool's avatar
Overbool committed
1034
			return pdir.Flush()
Jeromy's avatar
Jeromy committed
1035 1036
		}

1037 1038 1039
		// 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
1040
		if err != nil {
Overbool's avatar
Overbool committed
1041
			return err
Jeromy's avatar
Jeromy committed
1042 1043
		}

1044
		dashr, _ := req.Options[recursiveOptionName].(bool)
1045 1046

		switch child.(type) {
Jeromy's avatar
Jeromy committed
1047
		case *mfs.Directory:
1048
			if !dashr {
Overbool's avatar
Overbool committed
1049
				return fmt.Errorf("%s is a directory, use -r to remove directories", path)
Jeromy's avatar
Jeromy committed
1050
			}
1051
		}
Jeromy's avatar
Jeromy committed
1052

1053 1054
		err = pdir.Unlink(name)
		if err != nil {
Overbool's avatar
Overbool committed
1055
			return err
Jeromy's avatar
Jeromy committed
1056
		}
1057

Overbool's avatar
Overbool committed
1058
		return pdir.Flush()
Jeromy's avatar
Jeromy committed
1059 1060 1061
	},
}

1062
func getPrefixNew(req *cmds.Request) (cid.Builder, error) {
Kejie Zhang's avatar
Kejie Zhang committed
1063 1064
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090

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

	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
1107

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

1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
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,
	})
}

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

1155 1156
		if builder == nil {
			builder = pdir.GetCidBuilder()
1157
		}
Jeromy's avatar
Jeromy committed
1158

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

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1181 1182 1183

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

	if p[0] != '/' {
Michael Muré's avatar
Michael Muré committed
1188
		return "", fmt.Errorf("paths must start with a leading slash")
Jeromy's avatar
Jeromy committed
1189 1190 1191 1192 1193 1194 1195 1196
	}

	cleaned := gopath.Clean(p)
	if p[len(p)-1] == '/' && p != "/" {
		cleaned += "/"
	}
	return cleaned, nil
}
1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209

func getParentDir(root *mfs.Root, dir string) (*mfs.Directory, error) {
	parent, err := mfs.Lookup(root, dir)
	if err != nil {
		return nil, err
	}

	pdir, ok := parent.(*mfs.Directory)
	if !ok {
		return nil, errors.New("expected *mfs.Directory, didnt get it. This is likely a race condition")
	}
	return pdir, nil
}