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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

146 147 148 149 150 151 152 153 154 155 156
		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
		}

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

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

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

		local, sizeLocal, err := walkBlock(req.Context, dagserv, nd)
172 173 174
		if err != nil {
			return err
		}
175 176 177 178 179

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

keks's avatar
keks committed
180
		return cmds.EmitOnce(res, o)
Jeromy's avatar
Jeromy committed
181
	},
182
	Encoders: cmds.EncoderMap{
183
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *statOutput) error {
184
			s, _ := statGetFormatOptions(req)
185 186 187 188 189 190
			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)

191 192 193 194 195 196 197 198 199 200 201
			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
202
		}),
Jeromy's avatar
Jeromy committed
203
	},
Michael Muré's avatar
Michael Muré committed
204
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
205 206
}

207 208 209 210
func moreThanOne(a, b, c bool) bool {
	return a && b || b && c || a && c
}

211
func statGetFormatOptions(req *cmds.Request) (string, error) {
212

Kejie Zhang's avatar
Kejie Zhang committed
213 214 215
	hash, _ := req.Options[filesHashOptionName].(bool)
	size, _ := req.Options[filesSizeOptionName].(bool)
	format, _ := req.Options[filesFormatOptionName].(string)
216

keks's avatar
keks committed
217
	if moreThanOne(hash, size, format != defaultStatFormat) {
Michael Muré's avatar
Michael Muré committed
218
		return "", errFormat
219 220 221
	}

	if hash {
222 223 224 225 226
		return "<hash>", nil
	} else if size {
		return "<cumulsize>", nil
	} else {
		return format, nil
227 228 229
	}
}

230
func statNode(nd ipld.Node, enc cidenc.Encoder) (*statOutput, error) {
Jeromy's avatar
Jeromy committed
231
	c := nd.Cid()
Jeromy's avatar
Jeromy committed
232 233 234 235 236 237

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

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

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

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

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
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
307
var filesCpCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
308
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
309
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
310
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
311 312 313
	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
314
	},
Overbool's avatar
Overbool committed
315 316
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
317
		if err != nil {
Overbool's avatar
Overbool committed
318
			return err
Jeromy's avatar
Jeromy committed
319 320
		}

321
		api, err := cmdenv.GetApi(env, req)
322
		if err != nil {
Overbool's avatar
Overbool committed
323
			return err
324 325
		}

326
		flush, _ := req.Options[filesFlushOptionName].(bool)
327

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

Overbool's avatar
Overbool committed
334
		dst, err := checkPath(req.Arguments[1])
Jeromy's avatar
Jeromy committed
335
		if err != nil {
Overbool's avatar
Overbool committed
336
			return err
Jeromy's avatar
Jeromy committed
337
		}
Jeromy's avatar
Jeromy committed
338

339 340 341 342
		if dst[len(dst)-1] == '/' {
			dst += gopath.Base(src)
		}

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

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

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

Overbool's avatar
Overbool committed
360
		return nil
Jeromy's avatar
Jeromy committed
361 362 363
	},
}

364
func getNodeFromPath(ctx context.Context, node *core.IpfsNode, api iface.CoreAPI, p string) (ipld.Node, error) {
Jeromy's avatar
Jeromy committed
365 366
	switch {
	case strings.HasPrefix(p, "/ipfs/"):
367
		return api.ResolveNode(ctx, path.New(p))
Jeromy's avatar
Jeromy committed
368 369 370 371 372 373 374 375 376 377
	default:
		fsn, err := mfs.Lookup(node.FilesRoot, p)
		if err != nil {
			return nil, err
		}

		return fsn.GetNode()
	}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Overbool's avatar
Overbool committed
522
var filesReadCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
523
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
524
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
525
		ShortDescription: `
526 527
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
528 529 530 531 532

Examples:

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

Jan Winkelmann's avatar
Jan Winkelmann committed
536 537
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
538
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
539
	Options: []cmdkit.Option{
540 541
		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
542
	},
Overbool's avatar
Overbool committed
543 544
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
545
		if err != nil {
Overbool's avatar
Overbool committed
546
			return err
Jeromy's avatar
Jeromy committed
547 548
		}

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

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

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

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

		defer rfd.Close()

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

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

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

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

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

Jeromy's avatar
Jeromy committed
602 603 604 605 606 607 608 609 610 611 612 613 614
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
615
var filesMvCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
616
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
617
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
618 619 620 621 622 623 624
		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
625
`,
Jeromy's avatar
Jeromy committed
626 627
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
628 629 630
	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
631
	},
Overbool's avatar
Overbool committed
632 633
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
634
		if err != nil {
Overbool's avatar
Overbool committed
635
			return err
Jeromy's avatar
Jeromy committed
636 637
		}

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

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

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

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

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

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

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

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

687
EXAMPLE:
Jeromy's avatar
Jeromy committed
688

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

692
WARNING:
693

694 695 696
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
697
`,
Jeromy's avatar
Jeromy committed
698
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
699 700 701
	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
702
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
703
	Options: []cmdkit.Option{
704
		cmdkit.Int64Option(filesOffsetOptionName, "o", "Byte offset to begin writing at."),
Kejie Zhang's avatar
Kejie Zhang committed
705 706 707
		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."),
708
		cmdkit.Int64Option(filesCountOptionName, "n", "Maximum number of bytes to read."),
Kejie Zhang's avatar
Kejie Zhang committed
709
		cmdkit.BoolOption(filesRawLeavesOptionName, "Use raw blocks for newly created leaf nodes. (experimental)"),
710 711
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
712
	},
keks's avatar
keks committed
713
	Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) (retErr error) {
714
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
715
		if err != nil {
keks's avatar
keks committed
716
			return err
Jeromy's avatar
Jeromy committed
717 718
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Examples:

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

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

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

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

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

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

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

858 859 860 861
type flushRes struct {
	Cid string
}

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

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

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

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

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

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

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

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

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

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

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

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

	return nil
}

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

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

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

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

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

		// '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
1001 1002 1003
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Overbool's avatar
Overbool committed
1004
			return fmt.Errorf("parent lookup: %s", err)
Jeromy's avatar
Jeromy committed
1005 1006 1007 1008
		}

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

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

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

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

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

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

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

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

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

	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
1078 1079 1080
func getPrefix(req *cmds.Request) (cid.Builder, error) {
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093

	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
1094

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

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

1120
func getFileHandle(r *mfs.Root, path string, create bool, builder cid.Builder) (*mfs.File, error) {
Jeromy's avatar
Jeromy committed
1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
	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 {
1139
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1140 1141 1142 1143 1144 1145
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1146 1147
		if builder == nil {
			builder = pdir.GetCidBuilder()
1148
		}
Jeromy's avatar
Jeromy committed
1149

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

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

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

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

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