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

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

Michael Muré's avatar
Michael Muré committed
14 15
	oldcmds "github.com/ipfs/go-ipfs/commands"
	lgc "github.com/ipfs/go-ipfs/commands/legacy"
Jeromy's avatar
Jeromy committed
16
	core "github.com/ipfs/go-ipfs/core"
17
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
Jan Winkelmann's avatar
Jan Winkelmann committed
18
	e "github.com/ipfs/go-ipfs/core/commands/e"
Steven Allen's avatar
Steven Allen committed
19 20 21 22 23 24
	ft "gx/ipfs/QmPL8bYtbACcSFFiSr4s2du7Na382NxRADR8hC7D9FkEA2/go-unixfs"
	uio "gx/ipfs/QmPL8bYtbACcSFFiSr4s2du7Na382NxRADR8hC7D9FkEA2/go-unixfs/io"
	path "gx/ipfs/QmX7uSbkNz76yNwBhuwYwRbhihLnJqM73VTCjS3UMJud9A/go-path"
	resolver "gx/ipfs/QmX7uSbkNz76yNwBhuwYwRbhihLnJqM73VTCjS3UMJud9A/go-path/resolver"
	dag "gx/ipfs/QmXv5mwmQ74r4aiHcNeQ4GAmfB3aWJuqaE4WyDfDfvkgLM/go-merkledag"
	bservice "gx/ipfs/Qma2KhbQarYTkmSJAeaMGRAg8HAXAhEWK8ge4SReG7ZSD3/go-blockservice"
25

Michael Muré's avatar
Michael Muré committed
26
	humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
27
	cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
28
	cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds"
29
	mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
Steven Allen's avatar
Steven Allen committed
30
	logging "gx/ipfs/QmRREK2CAZ5Re2Bd9zZFG6FeYDppUWt5cMgsoUEp3ktgSr/go-log"
Steven Allen's avatar
Steven Allen committed
31
	mfs "gx/ipfs/QmRkrpnhZqDxTxwGCsDbuZMr7uCFZHH6SGfrcjgEQwxF3t/go-mfs"
32
	cmdkit "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
Steven Allen's avatar
Steven Allen committed
33
	offline "gx/ipfs/QmcRC35JF2pJQneAxa5LdQBQRumWggccWErogSrCkS1h8T/go-ipfs-exchange-offline"
34
	ipld "gx/ipfs/QmdDXJs4axxefSPgK6Y1QhpJWKuDPnGJiqgq4uncb4rFHL/go-ipld-format"
Jeromy's avatar
Jeromy committed
35 36
)

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

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

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

Jan Winkelmann's avatar
Jan Winkelmann committed
74 75
var cidVersionOption = cmdkit.IntOption("cid-version", "cid-ver", "Cid version to use. (experimental)")
var hashOption = cmdkit.StringOption("hash", "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"`
}

keks's avatar
keks committed
90 91 92 93 94 95
const defaultStatFormat = `<hash>
Size: <size>
CumulativeSize: <cumulsize>
ChildBlocks: <childs>
Type: <type>`

96
var filesStatCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
97
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
98
		Tagline: "Display file status.",
Jeromy's avatar
Jeromy committed
99 100
	},

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

		_, err := statGetFormatOptions(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
115
			res.SetError(err, cmdkit.ErrClient)
116 117
		}

118
		node, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
119
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
120
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
121 122 123
			return
		}

124
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
125
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
126
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
127 128 129
			return
		}

130 131 132 133 134 135 136 137 138 139 140 141 142 143
		withLocal, _ := req.Options["with-local"].(bool)

		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
		}

		nd, err := getNodeFromPath(req.Context, node, dagserv, path)
Jeromy's avatar
Jeromy committed
144
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
145
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
146 147 148
			return
		}

149
		o, err := statNode(nd)
Jeromy's avatar
Jeromy committed
150
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
151
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
152 153 154
			return
		}

155
		if !withLocal {
156
			cmds.EmitOnce(res, o)
157 158 159 160 161 162 163 164 165
			return
		}

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

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

166
		cmds.EmitOnce(res, o)
Jeromy's avatar
Jeromy committed
167
	},
168 169
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
Michael Muré's avatar
Michael Muré committed
170
			out, ok := v.(*statOutput)
Jan Winkelmann's avatar
Jan Winkelmann committed
171
			if !ok {
172
				return e.TypeErr(out, v)
Jan Winkelmann's avatar
Jan Winkelmann committed
173
			}
174

175
			s, _ := statGetFormatOptions(req)
176 177 178 179 180 181
			s = strings.Replace(s, "<hash>", out.Hash, -1)
			s = strings.Replace(s, "<size>", fmt.Sprintf("%d", out.Size), -1)
			s = strings.Replace(s, "<cumulsize>", fmt.Sprintf("%d", out.CumulativeSize), -1)
			s = strings.Replace(s, "<childs>", fmt.Sprintf("%d", out.Blocks), -1)
			s = strings.Replace(s, "<type>", out.Type, -1)

182 183 184 185 186 187 188 189 190 191 192
			fmt.Fprintln(w, s)

			if out.WithLocality {
				fmt.Fprintf(w, "Local: %s of %s (%.2f%%)\n",
					humanize.Bytes(out.SizeLocal),
					humanize.Bytes(out.CumulativeSize),
					100.0*float64(out.SizeLocal)/float64(out.CumulativeSize),
				)
			}

			return nil
193
		}),
Jeromy's avatar
Jeromy committed
194
	},
Michael Muré's avatar
Michael Muré committed
195
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
196 197
}

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

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

204 205 206
	hash, _ := req.Options["hash"].(bool)
	size, _ := req.Options["size"].(bool)
	format, _ := req.Options["format"].(string)
207

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

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

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

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

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

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

Michael Muré's avatar
Michael Muré committed
246
		return &statOutput{
247 248 249 250 251 252 253
			Hash:           c.String(),
			Blocks:         len(nd.Links()),
			Size:           d.GetFilesize(),
			CumulativeSize: cumulsize,
			Type:           ndtype,
		}, nil
	case *dag.RawNode:
Michael Muré's avatar
Michael Muré committed
254
		return &statOutput{
255 256 257 258 259 260
			Hash:           c.String(),
			Blocks:         0,
			Size:           cumulsize,
			CumulativeSize: cumulsize,
			Type:           "file",
		}, nil
Jeromy's avatar
Jeromy committed
261
	default:
262
		return nil, fmt.Errorf("not unixfs node (proto or raw)")
Jeromy's avatar
Jeromy committed
263
	}
Jeromy's avatar
Jeromy committed
264 265
}

266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
func walkBlock(ctx context.Context, dagserv ipld.DAGService, nd ipld.Node) (bool, uint64, error) {
	// Start with the block data size
	sizeLocal := uint64(len(nd.RawData()))

	local := true

	for _, link := range nd.Links() {
		child, err := dagserv.Get(ctx, link.Cid)

		if err == ipld.ErrNotFound {
			local = false
			continue
		}

		if err != nil {
			return local, sizeLocal, err
		}

		childLocal, childLocalSize, err := walkBlock(ctx, dagserv, child)

		if err != nil {
			return local, sizeLocal, err
		}

		// Recursively add the child size
		local = local && childLocal
		sizeLocal += childLocalSize
	}

	return local, sizeLocal, nil
}

Michael Muré's avatar
Michael Muré committed
298
var filesCpCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
299
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
300
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
301
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
302 303 304
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("source", true, false, "Source object to copy."),
		cmdkit.StringArg("dest", true, false, "Destination to copy object to."),
Jeromy's avatar
Jeromy committed
305
	},
Michael Muré's avatar
Michael Muré committed
306
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
307 308
		node, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
309
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
310 311 312
			return
		}

313
		flush, _, _ := req.Option("flush").Bool()
314

Jeromy's avatar
Jeromy committed
315 316
		src, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
317
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
318 319
			return
		}
320 321
		src = strings.TrimRight(src, "/")

Jeromy's avatar
Jeromy committed
322 323
		dst, err := checkPath(req.Arguments()[1])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
324
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
325 326
			return
		}
Jeromy's avatar
Jeromy committed
327

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

332
		nd, err := getNodeFromPath(req.Context(), node, node.DAG, src)
Jeromy's avatar
Jeromy committed
333
		if err != nil {
334
			res.SetError(fmt.Errorf("cp: cannot get node from path %s: %s", src, err), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
335
			return
Jeromy's avatar
Jeromy committed
336 337 338 339
		}

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

		if flush {
			err := mfs.FlushPath(node.FilesRoot, dst)
			if err != nil {
Lucas Molas's avatar
Lucas Molas committed
347
				res.SetError(fmt.Errorf("cp: cannot flush the created file %s: %s", dst, err), cmdkit.ErrNormal)
348 349 350
				return
			}
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
351 352

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
353 354 355
	},
}

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

364
		resolver := &resolver.Resolver{
365
			DAG:         dagservice,
366 367 368
			ResolveOnce: uio.ResolveUnixfsOnce,
		}

369
		return core.Resolve(ctx, node.Namesys, resolver, np)
Jeromy's avatar
Jeromy committed
370 371 372 373 374 375 376 377 378 379
	default:
		fsn, err := mfs.Lookup(node.FilesRoot, p)
		if err != nil {
			return nil, err
		}

		return fsn.GetNode()
	}
}

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

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

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
405 406
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to show listing for. Defaults to '/'."),
Jeromy's avatar
Jeromy committed
407
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
408 409
	Options: []cmdkit.Option{
		cmdkit.BoolOption("l", "Use long listing format."),
Lucas Molas's avatar
Lucas Molas committed
410
		cmdkit.BoolOption("U", "Do not sort; list entries in directory order."),
Jeromy's avatar
Jeromy committed
411
	},
Michael Muré's avatar
Michael Muré committed
412
	Run: func(req oldcmds.Request, res oldcmds.Response) {
413 414 415 416 417 418 419 420 421
		var arg string

		if len(req.Arguments()) == 0 {
			arg = "/"
		} else {
			arg = req.Arguments()[0]
		}

		path, err := checkPath(arg)
Jeromy's avatar
Jeromy committed
422
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
423
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
424 425 426
			return
		}

Jeromy's avatar
Jeromy committed
427 428
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
429
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
430 431 432 433 434
			return
		}

		fsn, err := mfs.Lookup(nd.FilesRoot, path)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
435
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
436 437 438
			return
		}

Jeromy's avatar
Jeromy committed
439 440
		long, _, _ := req.Option("l").Bool()

Jeromy's avatar
Jeromy committed
441 442
		switch fsn := fsn.(type) {
		case *mfs.Directory:
Jeromy's avatar
Jeromy committed
443 444
			if !long {
				var output []mfs.NodeListing
Jeromy's avatar
Jeromy committed
445
				names, err := fsn.ListNames(req.Context())
446
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
447
					res.SetError(err, cmdkit.ErrNormal)
448 449 450 451
					return
				}

				for _, name := range names {
Jeromy's avatar
Jeromy committed
452
					output = append(output, mfs.NodeListing{
Jeromy's avatar
Jeromy committed
453
						Name: name,
Jeromy's avatar
Jeromy committed
454 455
					})
				}
Michael Muré's avatar
Michael Muré committed
456
				res.SetOutput(&filesLsOutput{output})
Jeromy's avatar
Jeromy committed
457
			} else {
Jeromy's avatar
Jeromy committed
458
				listing, err := fsn.List(req.Context())
Jeromy's avatar
Jeromy committed
459
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
460
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
461 462
					return
				}
Michael Muré's avatar
Michael Muré committed
463
				res.SetOutput(&filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
464 465 466
			}
			return
		case *mfs.File:
rht's avatar
rht committed
467
			_, name := gopath.Split(path)
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
			out := &filesLsOutput{[]mfs.NodeListing{mfs.NodeListing{Name: name}}}
			if long {
				out.Entries[0].Type = int(fsn.Type())

				size, err := fsn.Size()
				if err != nil {
					res.SetError(err, cmdkit.ErrNormal)
					return
				}
				out.Entries[0].Size = size

				nd, err := fsn.GetNode()
				if err != nil {
					res.SetError(err, cmdkit.ErrNormal)
					return
				}
				out.Entries[0].Hash = nd.Cid().String()
			}
Jeromy's avatar
Jeromy committed
486 487 488
			res.SetOutput(out)
			return
		default:
Jan Winkelmann's avatar
Jan Winkelmann committed
489
			res.SetError(errors.New("unrecognized type"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
490 491
		}
	},
Michael Muré's avatar
Michael Muré committed
492 493
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Text: func(res oldcmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
494 495 496 497 498
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

Michael Muré's avatar
Michael Muré committed
499
			out, ok := v.(*filesLsOutput)
Jan Winkelmann's avatar
Jan Winkelmann committed
500 501 502 503
			if !ok {
				return nil, e.TypeErr(out, v)
			}

Jeromy's avatar
Jeromy committed
504 505
			buf := new(bytes.Buffer)

Lucas Molas's avatar
Lucas Molas committed
506 507 508 509 510 511 512 513
			noSort, _, _ := res.Request().Option("U").Bool()
			if !noSort {
				sort.Slice(out.Entries, func(i, j int) bool {
					return strings.Compare(out.Entries[i].Name, out.Entries[j].Name) < 0
				})
			}

			long, _, _ := res.Request().Option("l").Bool()
Jeromy's avatar
Jeromy committed
514 515 516 517 518 519 520 521 522 523
			for _, o := range out.Entries {
				if long {
					fmt.Fprintf(buf, "%s\t%s\t%d\n", o.Name, o.Hash, o.Size)
				} else {
					fmt.Fprintf(buf, "%s\n", o.Name)
				}
			}
			return buf, nil
		},
	},
Michael Muré's avatar
Michael Muré committed
524
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
525 526
}

Michael Muré's avatar
Michael Muré committed
527
var filesReadCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
528
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
529
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
530
		ShortDescription: `
531 532
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
533 534 535 536 537

Examples:

    $ ipfs files read /test/hello
    hello
Jeromy's avatar
Jeromy committed
538
        `,
Jeromy's avatar
Jeromy committed
539 540
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
541 542
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
543
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
544 545 546
	Options: []cmdkit.Option{
		cmdkit.IntOption("offset", "o", "Byte offset to begin reading from."),
		cmdkit.IntOption("count", "n", "Maximum number of bytes to read."),
Jeromy's avatar
Jeromy committed
547
	},
Michael Muré's avatar
Michael Muré committed
548
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
549 550
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
551
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
552 553 554
			return
		}

Jeromy's avatar
Jeromy committed
555 556
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
557
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
558 559 560
			return
		}

Jeromy's avatar
Jeromy committed
561 562
		fsn, err := mfs.Lookup(n.FilesRoot, path)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
563
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
564 565 566 567 568
			return
		}

		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
569
			res.SetError(fmt.Errorf("%s was not a file", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
570 571 572
			return
		}

573 574
		rfd, err := fi.Open(mfs.OpenReadOnly, false)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
575
			res.SetError(err, cmdkit.ErrNormal)
576 577 578 579 580
			return
		}

		defer rfd.Close()

Jeromy's avatar
Jeromy committed
581 582
		offset, _, err := req.Option("offset").Int()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
583
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
584 585 586
			return
		}
		if offset < 0 {
Michael Muré's avatar
Michael Muré committed
587
			res.SetError(fmt.Errorf("cannot specify negative offset"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
588 589 590
			return
		}

591
		filen, err := rfd.Size()
Jeromy's avatar
Jeromy committed
592
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
593
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
594 595 596 597
			return
		}

		if int64(offset) > filen {
Michael Muré's avatar
Michael Muré committed
598
			res.SetError(fmt.Errorf("offset was past end of file (%d > %d)", offset, filen), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
599 600
			return
		}
Jeromy's avatar
Jeromy committed
601

602
		_, err = rfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
603
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
604
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
605 606
			return
		}
607 608

		var r io.Reader = &contextReaderWrapper{R: rfd, ctx: req.Context()}
Jeromy's avatar
Jeromy committed
609
		count, found, err := req.Option("count").Int()
Jeromy's avatar
Jeromy committed
610
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
611
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
612 613 614 615
			return
		}
		if found {
			if count < 0 {
Michael Muré's avatar
Michael Muré committed
616
				res.SetError(fmt.Errorf("cannot specify negative 'count'"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
617 618
				return
			}
619
			r = io.LimitReader(r, int64(count))
Jeromy's avatar
Jeromy committed
620 621 622 623 624 625
		}

		res.SetOutput(r)
	},
}

Jeromy's avatar
Jeromy committed
626 627 628 629 630 631 632 633 634 635 636 637 638
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)
}

Michael Muré's avatar
Michael Muré committed
639
var filesMvCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
640
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
641
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
642 643 644 645 646 647 648
		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
649
`,
Jeromy's avatar
Jeromy committed
650 651
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
652 653 654
	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
655
	},
Michael Muré's avatar
Michael Muré committed
656
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
657 658
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
659
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
660 661 662
			return
		}

Jeromy's avatar
Jeromy committed
663 664
		src, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
665
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
666 667 668 669
			return
		}
		dst, err := checkPath(req.Arguments()[1])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
670
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
671 672
			return
		}
Jeromy's avatar
Jeromy committed
673 674 675

		err = mfs.Mv(n.FilesRoot, src, dst)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
676
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
677 678
			return
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
679 680

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
681 682 683
	},
}

684
var filesWriteCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
685
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
686
		Tagline: "Write to a mutable file in a given filesystem.",
Jeromy's avatar
Jeromy committed
687 688
		ShortDescription: `
Write data to a file in a given filesystem. This command allows you to specify
689 690
a beginning offset to write to. The entire length of the input will be
written.
Jeromy's avatar
Jeromy committed
691

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

695 696 697 698 699 700 701
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.

702 703 704 705
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.

706
EXAMPLE:
Jeromy's avatar
Jeromy committed
707

Jeromy's avatar
Jeromy committed
708 709
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
710

711
WARNING:
712

713 714 715
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
716
`,
Jeromy's avatar
Jeromy committed
717
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
718 719 720
	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
721
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
722 723 724
	Options: []cmdkit.Option{
		cmdkit.IntOption("offset", "o", "Byte offset to begin writing at."),
		cmdkit.BoolOption("create", "e", "Create the file if it does not exist."),
725
		cmdkit.BoolOption("parents", "p", "Make parent directories as needed."),
Jan Winkelmann's avatar
Jan Winkelmann committed
726 727 728
		cmdkit.BoolOption("truncate", "t", "Truncate the file to size zero before writing."),
		cmdkit.IntOption("count", "n", "Maximum number of bytes to read."),
		cmdkit.BoolOption("raw-leaves", "Use raw blocks for newly created leaf nodes. (experimental)"),
729 730
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
731
	},
732 733
	Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) {
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
734
		if err != nil {
735
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
736 737 738
			return
		}

739
		create, _ := req.Options["create"].(bool)
740
		mkParents, _ := req.Options["parents"].(bool)
741 742 743
		trunc, _ := req.Options["truncate"].(bool)
		flush, _ := req.Options["flush"].(bool)
		rawLeaves, rawLeavesDef := req.Options["raw-leaves"].(bool)
744

745
		prefix, err := getPrefixNew(req)
746
		if err != nil {
747
			re.SetError(err, cmdkit.ErrNormal)
748 749
			return
		}
Jeromy's avatar
Jeromy committed
750

751
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
752
		if err != nil {
753
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
754 755 756
			return
		}

757
		offset, _ := req.Options["offset"].(int)
Jeromy's avatar
Jeromy committed
758
		if offset < 0 {
759
			re.SetError(fmt.Errorf("cannot have negative write offset"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
760 761 762
			return
		}

763 764 765 766 767 768 769 770
		if mkParents {
			err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix)
			if err != nil {
				re.SetError(err, cmdkit.ErrNormal)
				return
			}
		}

771
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
772
		if err != nil {
773
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
774 775
			return
		}
776 777 778
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
779

780 781
		wfd, err := fi.Open(mfs.OpenWriteOnly, flush)
		if err != nil {
782
			re.SetError(err, cmdkit.ErrNormal)
783
			return
784
		}
Jeromy's avatar
Jeromy committed
785

786 787 788
		defer func() {
			err := wfd.Close()
			if err != nil {
789
				re.SetError(err, cmdkit.ErrNormal)
790 791
			}
		}()
792

Jeromy's avatar
Jeromy committed
793
		if trunc {
794
			if err := wfd.Truncate(0); err != nil {
795
				re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
796 797 798 799
				return
			}
		}

800
		count, countfound := req.Options["count"].(int)
Jeromy's avatar
Jeromy committed
801
		if countfound && count < 0 {
802
			re.SetError(fmt.Errorf("cannot have negative byte count"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
803 804
			return
		}
Jeromy's avatar
Jeromy committed
805

806
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
807
		if err != nil {
808
			flog.Error("seekfail: ", err)
809
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
810 811 812
			return
		}

813
		input, err := req.Files.NextFile()
Jeromy's avatar
Jeromy committed
814
		if err != nil {
815
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
816 817 818
			return
		}

zramsay's avatar
zramsay committed
819 820 821 822 823
		var r io.Reader = input
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
824
		_, err = io.Copy(wfd, r)
Jeromy's avatar
Jeromy committed
825
		if err != nil {
826
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
827 828 829 830 831
			return
		}
	},
}

Michael Muré's avatar
Michael Muré committed
832
var filesMkdirCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
833
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
834
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
835 836 837
		ShortDescription: `
Create the directory if it does not already exist.

838 839 840
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

841
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
842 843 844

Examples:

845 846
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
847 848 849
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
850 851
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to dir to make."),
Jeromy's avatar
Jeromy committed
852
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
853 854
	Options: []cmdkit.Option{
		cmdkit.BoolOption("parents", "p", "No error if existing, make parent directories as needed."),
855 856
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
857
	},
Michael Muré's avatar
Michael Muré committed
858
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
859 860
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
861
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
862 863 864 865
			return
		}

		dashp, _, _ := req.Option("parents").Bool()
Jeromy's avatar
Jeromy committed
866 867
		dirtomake, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
868
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
869 870 871
			return
		}

872
		flush, _, _ := req.Option("flush").Bool()
Jeromy's avatar
Jeromy committed
873

874 875
		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
876
			res.SetError(err, cmdkit.ErrNormal)
877 878 879 880
			return
		}
		root := n.FilesRoot

881
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
882 883 884
			Mkparents:  dashp,
			Flush:      flush,
			CidBuilder: prefix,
885
		})
886
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
887
			res.SetError(err, cmdkit.ErrNormal)
888
			return
Jeromy's avatar
Jeromy committed
889
		}
keks's avatar
keks committed
890

Jan Winkelmann's avatar
Jan Winkelmann committed
891
		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
892 893 894
	},
}

Michael Muré's avatar
Michael Muré committed
895
var filesFlushCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
896
	Helptext: cmdkit.HelpText{
897
		Tagline: "Flush a given path's data to disk.",
Jeromy's avatar
Jeromy committed
898
		ShortDescription: `
899
Flush a given path to disk. This is only useful when other commands
Jeromy's avatar
Jeromy committed
900 901 902
are run with the '--flush=false'.
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
903 904
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to flush. Default: '/'."),
Jeromy's avatar
Jeromy committed
905
	},
Michael Muré's avatar
Michael Muré committed
906
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
907 908
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
909
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
910 911 912 913 914 915 916 917 918 919
			return
		}

		path := "/"
		if len(req.Arguments()) > 0 {
			path = req.Arguments()[0]
		}

		err = mfs.FlushPath(nd.FilesRoot, path)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
920
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
921 922
			return
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
923 924

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
925 926 927
	},
}

Michael Muré's avatar
Michael Muré committed
928
var filesChcidCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
929
	Helptext: cmdkit.HelpText{
930
		Tagline: "Change the cid version or hash function of the root node of a given path.",
Kevin Atkinson's avatar
Kevin Atkinson committed
931
		ShortDescription: `
932
Change the cid version or hash function of the root node of a given path.
Kevin Atkinson's avatar
Kevin Atkinson committed
933 934
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
935 936
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to change. Default: '/'."),
Kevin Atkinson's avatar
Kevin Atkinson committed
937
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
938
	Options: []cmdkit.Option{
939 940 941
		cidVersionOption,
		hashOption,
	},
Michael Muré's avatar
Michael Muré committed
942
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Kevin Atkinson's avatar
Kevin Atkinson committed
943 944
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
945
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
946 947 948 949 950 951 952 953 954 955 956 957
			return
		}

		path := "/"
		if len(req.Arguments()) > 0 {
			path = req.Arguments()[0]
		}

		flush, _, _ := req.Option("flush").Bool()

		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
958
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
959 960 961 962 963
			return
		}

		err = updatePath(nd.FilesRoot, path, prefix, flush)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
964
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
965 966
			return
		}
keks's avatar
keks committed
967 968

		res.SetOutput(nil)
Kevin Atkinson's avatar
Kevin Atkinson committed
969 970 971
	},
}

972 973
func updatePath(rt *mfs.Root, pth string, builder cid.Builder, flush bool) error {
	if builder == nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
974 975 976 977 978 979 980 981 982 983
		return nil
	}

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

	switch n := nd.(type) {
	case *mfs.Directory:
984
		n.SetCidBuilder(builder)
Kevin Atkinson's avatar
Kevin Atkinson committed
985 986 987 988 989 990 991 992 993 994 995
	default:
		return fmt.Errorf("can only update directories")
	}

	if flush {
		nd.Flush()
	}

	return nil
}

Michael Muré's avatar
Michael Muré committed
996
var filesRmCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
997
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
998
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
999
		ShortDescription: `
1000
Remove files or directories.
Jeromy's avatar
Jeromy committed
1001 1002 1003 1004 1005 1006 1007 1008

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

Jan Winkelmann's avatar
Jan Winkelmann committed
1011 1012
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
1013
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
1014 1015
	Options: []cmdkit.Option{
		cmdkit.BoolOption("recursive", "r", "Recursively remove directories."),
Jeromy's avatar
Jeromy committed
1016
	},
Michael Muré's avatar
Michael Muré committed
1017
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jan Winkelmann's avatar
Jan Winkelmann committed
1018 1019
		defer res.SetOutput(nil)

Jeromy's avatar
Jeromy committed
1020 1021
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1022
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1023 1024 1025
			return
		}

Jeromy's avatar
Jeromy committed
1026 1027
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1028
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1029 1030 1031 1032
			return
		}

		if path == "/" {
Jan Winkelmann's avatar
Jan Winkelmann committed
1033
			res.SetError(fmt.Errorf("cannot delete root"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1034 1035 1036 1037 1038 1039 1040 1041
			return
		}

		// '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
1042 1043 1044
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1045
			res.SetError(fmt.Errorf("parent lookup: %s", err), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1046 1047 1048 1049 1050
			return
		}

		pdir, ok := parent.(*mfs.Directory)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1051
			res.SetError(fmt.Errorf("no such file or directory: %s", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1052 1053 1054
			return
		}

Jeromy's avatar
Jeromy committed
1055 1056
		dashr, _, _ := req.Option("r").Bool()

Jeromy's avatar
Jeromy committed
1057 1058 1059 1060 1061
		var success bool
		defer func() {
			if success {
				err := pdir.Flush()
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1062
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1063 1064 1065 1066 1067
					return
				}
			}
		}()

Jeromy's avatar
Jeromy committed
1068 1069 1070 1071
		// if '-r' specified, don't check file type (in bad scenarios, the block may not exist)
		if dashr {
			err := pdir.Unlink(name)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1072
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1073 1074 1075
				return
			}

Jeromy's avatar
Jeromy committed
1076
			success = true
Jeromy's avatar
Jeromy committed
1077 1078 1079
			return
		}

Jeromy's avatar
Jeromy committed
1080 1081
		childi, err := pdir.Child(name)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1082
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1083 1084 1085 1086 1087
			return
		}

		switch childi.(type) {
		case *mfs.Directory:
Jan Winkelmann's avatar
Jan Winkelmann committed
1088
			res.SetError(fmt.Errorf("%s is a directory, use -r to remove directories", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1089
			return
Jeromy's avatar
Jeromy committed
1090 1091 1092
		default:
			err := pdir.Unlink(name)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1093
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1094 1095
				return
			}
Jeromy's avatar
Jeromy committed
1096 1097

			success = true
Jeromy's avatar
Jeromy committed
1098 1099 1100 1101
		}
	},
}

1102
func getPrefixNew(req *cmds.Request) (cid.Builder, error) {
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130
	cidVer, cidVerSet := req.Options["cid-version"].(int)
	hashFunStr, hashFunSet := req.Options["hash"].(string)

	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
}

1131
func getPrefix(req oldcmds.Request) (cid.Builder, error) {
1132
	cidVer, cidVerSet, _ := req.Option("cid-version").Int()
1133
	hashFunStr, hashFunSet, _ := req.Option("hash").String()
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146

	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
1147

1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159
	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
}

1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
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,
	})
}

1173
func getFileHandle(r *mfs.Root, path string, create bool, builder cid.Builder) (*mfs.File, error) {
Jeromy's avatar
Jeromy committed
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191
	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 {
1192
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1193 1194 1195 1196 1197 1198
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1199 1200
		if builder == nil {
			builder = pdir.GetCidBuilder()
1201
		}
Jeromy's avatar
Jeromy committed
1202

1203
		nd := dag.NodeWithData(ft.FilePBData(nil, 0))
1204
		nd.SetCidBuilder(builder)
Jeromy's avatar
Jeromy committed
1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
		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
1215 1216
		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1217
			return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
Jeromy's avatar
Jeromy committed
1218 1219
		}
		return fi, nil
Jeromy's avatar
Jeromy committed
1220 1221 1222 1223 1224

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1225 1226 1227

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

	if p[0] != '/' {
Michael Muré's avatar
Michael Muré committed
1232
		return "", fmt.Errorf("paths must start with a leading slash")
Jeromy's avatar
Jeromy committed
1233 1234 1235 1236 1237 1238 1239 1240
	}

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