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 29
	"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"
	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{
Jan Winkelmann's avatar
Jan Winkelmann committed
36
	Helptext: cmdkit.HelpText{
37
		Tagline: "Interact with unixfs files.",
Jeromy's avatar
Jeromy committed
38
		ShortDescription: `
39 40
Files is an API for manipulating IPFS objects as if they were a unix
filesystem.
Jeromy's avatar
Jeromy committed
41

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Overbool's avatar
Overbool committed
327
		src, err := checkPath(req.Arguments[0])
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
		src = strings.TrimRight(src, "/")

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

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

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

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

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

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

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

		return fsn.GetNode()
	}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Examples:

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

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

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

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

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

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

		defer rfd.Close()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

686
EXAMPLE:
Jeromy's avatar
Jeromy committed
687

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

691
WARNING:
692

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Examples:

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

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

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

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

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

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

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

857 858 859 860
type flushRes struct {
	Cid string
}

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

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

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

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

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

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

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

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

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

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

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

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

	return nil
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1048
func getPrefixNew(req *cmds.Request) (cid.Builder, error) {
Kejie Zhang's avatar
Kejie Zhang committed
1049 1050
	cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int)
	hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string)
1051 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

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

	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
1093

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

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

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

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

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

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

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

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