files.go 27.1 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 11 12
	"errors"
	"fmt"
	"io"
	"os"
	gopath "path"
	"strings"

13
	bservice "github.com/ipfs/go-ipfs/blockservice"
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"
Jan Winkelmann's avatar
Jan Winkelmann committed
17
	e "github.com/ipfs/go-ipfs/core/commands/e"
18
	"github.com/ipfs/go-ipfs/exchange/offline"
Jeromy's avatar
Jeromy committed
19 20 21
	dag "github.com/ipfs/go-ipfs/merkledag"
	mfs "github.com/ipfs/go-ipfs/mfs"
	path "github.com/ipfs/go-ipfs/path"
22
	resolver "github.com/ipfs/go-ipfs/path/resolver"
Jeromy's avatar
Jeromy committed
23
	ft "github.com/ipfs/go-ipfs/unixfs"
24
	uio "github.com/ipfs/go-ipfs/unixfs/io"
Jeromy's avatar
Jeromy committed
25

Michael Muré's avatar
Michael Muré committed
26
	humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
Steven Allen's avatar
Steven Allen committed
27
	logging "gx/ipfs/QmRb5jh8z2E8hMGN2tkvs1yHynUanqnZ3UeKwgN1i9P1F8/go-log"
Steven Allen's avatar
Steven Allen committed
28
	mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
Steven Allen's avatar
Steven Allen committed
29
	cmds "gx/ipfs/QmabLouZTZwhfALuBcssPvkzhbYGMb4394huT7HY4LQ6d3/go-ipfs-cmds"
Steven Allen's avatar
Steven Allen committed
30
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
31
	cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
32
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
Jeromy's avatar
Jeromy committed
33 34
)

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

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

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

Jan Winkelmann's avatar
Jan Winkelmann committed
72 73
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)")
74

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

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

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

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

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

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

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

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

128 129 130 131 132 133 134 135 136 137 138 139 140 141
		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
142
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
143
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
144 145 146
			return
		}

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

153
		if !withLocal {
Michael Muré's avatar
Michael Muré committed
154
			res.Emit(o)
155 156 157 158 159 160 161 162 163
			return
		}

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

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

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

173
			s, _ := statGetFormatOptions(req)
174 175 176 177 178 179
			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)

180 181 182 183 184 185 186 187 188 189 190
			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
191
		}),
Jeromy's avatar
Jeromy committed
192
	},
Michael Muré's avatar
Michael Muré committed
193
	Type: statOutput{},
Jeromy's avatar
Jeromy committed
194 195
}

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

200
func statGetFormatOptions(req *cmds.Request) (string, error) {
201

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

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

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

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

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

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

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

Michael Muré's avatar
Michael Muré committed
244
		return &statOutput{
245 246 247 248 249 250 251
			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
252
		return &statOutput{
253 254 255 256 257 258
			Hash:           c.String(),
			Blocks:         0,
			Size:           cumulsize,
			CumulativeSize: cumulsize,
			Type:           "file",
		}, nil
Jeromy's avatar
Jeromy committed
259
	default:
260
		return nil, fmt.Errorf("not unixfs node (proto or raw)")
Jeromy's avatar
Jeromy committed
261
	}
Jeromy's avatar
Jeromy committed
262 263
}

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
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
296
var filesCpCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
297
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
298
		Tagline: "Copy files into mfs.",
Jeromy's avatar
Jeromy committed
299
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
300 301 302
	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
303
	},
Michael Muré's avatar
Michael Muré committed
304
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
305 306
		node, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
307
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
308 309 310
			return
		}

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

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

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

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

330
		nd, err := getNodeFromPath(req.Context(), node, node.DAG, src)
Jeromy's avatar
Jeromy committed
331
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
332
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
333
			return
Jeromy's avatar
Jeromy committed
334 335 336 337
		}

		err = mfs.PutNode(node.FilesRoot, dst, nd)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
338
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
339 340
			return
		}
341 342 343 344

		if flush {
			err := mfs.FlushPath(node.FilesRoot, dst)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
345
				res.SetError(err, cmdkit.ErrNormal)
346 347 348
				return
			}
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
349 350

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
351 352 353
	},
}

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

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

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

		return fsn.GetNode()
	}
}

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

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

Examples:

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

    $ ipfs files ls /myfiles/a/b/c/d
    foo
    bar
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
403 404
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to show listing for. Defaults to '/'."),
Jeromy's avatar
Jeromy committed
405
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
406 407
	Options: []cmdkit.Option{
		cmdkit.BoolOption("l", "Use long listing format."),
Jeromy's avatar
Jeromy committed
408
	},
Michael Muré's avatar
Michael Muré committed
409
	Run: func(req oldcmds.Request, res oldcmds.Response) {
410 411 412 413 414 415 416 417 418
		var arg string

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

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

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

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

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

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

				for _, name := range names {
Jeromy's avatar
Jeromy committed
449
					output = append(output, mfs.NodeListing{
Jeromy's avatar
Jeromy committed
450
						Name: name,
Jeromy's avatar
Jeromy committed
451 452
					})
				}
Michael Muré's avatar
Michael Muré committed
453
				res.SetOutput(&filesLsOutput{output})
Jeromy's avatar
Jeromy committed
454
			} else {
Jeromy's avatar
Jeromy committed
455
				listing, err := fsn.List(req.Context())
Jeromy's avatar
Jeromy committed
456
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
457
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
458 459
					return
				}
Michael Muré's avatar
Michael Muré committed
460
				res.SetOutput(&filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
461 462 463
			}
			return
		case *mfs.File:
rht's avatar
rht committed
464
			_, name := gopath.Split(path)
Michael Muré's avatar
Michael Muré committed
465
			out := &filesLsOutput{[]mfs.NodeListing{mfs.NodeListing{Name: name, Type: 1}}}
Jeromy's avatar
Jeromy committed
466 467 468
			res.SetOutput(out)
			return
		default:
Jan Winkelmann's avatar
Jan Winkelmann committed
469
			res.SetError(errors.New("unrecognized type"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
470 471
		}
	},
Michael Muré's avatar
Michael Muré committed
472 473
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Text: func(res oldcmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
474 475 476 477 478
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

Michael Muré's avatar
Michael Muré committed
479
			out, ok := v.(*filesLsOutput)
Jan Winkelmann's avatar
Jan Winkelmann committed
480 481 482 483
			if !ok {
				return nil, e.TypeErr(out, v)
			}

Jeromy's avatar
Jeromy committed
484 485 486 487 488 489 490 491 492 493 494 495 496
			buf := new(bytes.Buffer)
			long, _, _ := res.Request().Option("l").Bool()

			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
497
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
498 499
}

Michael Muré's avatar
Michael Muré committed
500
var filesReadCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
501
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
502
		Tagline: "Read a file in a given mfs.",
Jeromy's avatar
Jeromy committed
503
		ShortDescription: `
504 505
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
506 507 508 509 510

Examples:

    $ ipfs files read /test/hello
    hello
Jeromy's avatar
Jeromy committed
511
        `,
Jeromy's avatar
Jeromy committed
512 513
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
514 515
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
516
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
517 518 519
	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
520
	},
Michael Muré's avatar
Michael Muré committed
521
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
522 523
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
524
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
525 526 527
			return
		}

Jeromy's avatar
Jeromy committed
528 529
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
530
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
531 532 533
			return
		}

Jeromy's avatar
Jeromy committed
534 535
		fsn, err := mfs.Lookup(n.FilesRoot, path)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
536
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
537 538 539 540 541
			return
		}

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

546 547
		rfd, err := fi.Open(mfs.OpenReadOnly, false)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
548
			res.SetError(err, cmdkit.ErrNormal)
549 550 551 552 553
			return
		}

		defer rfd.Close()

Jeromy's avatar
Jeromy committed
554 555
		offset, _, err := req.Option("offset").Int()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
556
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
557 558 559
			return
		}
		if offset < 0 {
Michael Muré's avatar
Michael Muré committed
560
			res.SetError(fmt.Errorf("cannot specify negative offset"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
561 562 563
			return
		}

564
		filen, err := rfd.Size()
Jeromy's avatar
Jeromy committed
565
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
566
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
567 568 569 570
			return
		}

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

575
		_, err = rfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
576
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
577
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
578 579
			return
		}
580 581

		var r io.Reader = &contextReaderWrapper{R: rfd, ctx: req.Context()}
Jeromy's avatar
Jeromy committed
582
		count, found, err := req.Option("count").Int()
Jeromy's avatar
Jeromy committed
583
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
584
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
585 586 587 588
			return
		}
		if found {
			if count < 0 {
Michael Muré's avatar
Michael Muré committed
589
				res.SetError(fmt.Errorf("cannot specify negative 'count'"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
590 591
				return
			}
592
			r = io.LimitReader(r, int64(count))
Jeromy's avatar
Jeromy committed
593 594 595 596 597 598
		}

		res.SetOutput(r)
	},
}

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

Jan Winkelmann's avatar
Jan Winkelmann committed
625 626 627
	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
628
	},
Michael Muré's avatar
Michael Muré committed
629
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
630 631
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
632
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
633 634 635
			return
		}

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

		err = mfs.Mv(n.FilesRoot, src, dst)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
649
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
650 651
			return
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
652 653

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
654 655 656
	},
}

Michael Muré's avatar
Michael Muré committed
657
var filesWriteCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
658
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
659
		Tagline: "Write to a mutable file in a given filesystem.",
Jeromy's avatar
Jeromy committed
660 661
		ShortDescription: `
Write data to a file in a given filesystem. This command allows you to specify
662 663
a beginning offset to write to. The entire length of the input will be
written.
Jeromy's avatar
Jeromy committed
664

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

668 669 670 671 672 673 674
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.

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

679
EXAMPLE:
Jeromy's avatar
Jeromy committed
680

Jeromy's avatar
Jeromy committed
681 682
    echo "hello world" | ipfs files write --create /myfs/a/b/file
    echo "hello world" | ipfs files write --truncate /myfs/a/b/file
683

684
WARNING:
685

686 687 688
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
689
`,
Jeromy's avatar
Jeromy committed
690
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
691 692 693
	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
694
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
695 696 697 698 699 700
	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."),
		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)"),
701 702
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
703
	},
Michael Muré's avatar
Michael Muré committed
704
	Run: func(req oldcmds.Request, res oldcmds.Response) {
keks's avatar
keks committed
705
		path, err := checkPath(req.StringArguments()[0])
Jeromy's avatar
Jeromy committed
706
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
707
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
708 709 710
			return
		}

Jeromy's avatar
Jeromy committed
711 712
		create, _, _ := req.Option("create").Bool()
		trunc, _, _ := req.Option("truncate").Bool()
713
		flush, _, _ := req.Option("flush").Bool()
714 715 716 717
		rawLeaves, rawLeavesDef, _ := req.Option("raw-leaves").Bool()

		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
718
			res.SetError(err, cmdkit.ErrNormal)
719 720
			return
		}
Jeromy's avatar
Jeromy committed
721 722 723

		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
724
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
725 726 727
			return
		}

Jeromy's avatar
Jeromy committed
728 729
		offset, _, err := req.Option("offset").Int()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
730
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
731 732 733
			return
		}
		if offset < 0 {
Jan Winkelmann's avatar
Jan Winkelmann committed
734
			res.SetError(fmt.Errorf("cannot have negative write offset"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
735 736 737
			return
		}

738
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
739
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
740
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
741 742
			return
		}
743 744 745
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
746

747 748
		wfd, err := fi.Open(mfs.OpenWriteOnly, flush)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
749
			res.SetError(err, cmdkit.ErrNormal)
750
			return
751
		}
Jeromy's avatar
Jeromy committed
752

753 754 755
		defer func() {
			err := wfd.Close()
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
756
				res.SetError(err, cmdkit.ErrNormal)
757 758
			}
		}()
759

Jeromy's avatar
Jeromy committed
760
		if trunc {
761
			if err := wfd.Truncate(0); err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
762
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
763 764 765 766
				return
			}
		}

Jeromy's avatar
Jeromy committed
767 768
		count, countfound, err := req.Option("count").Int()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
769
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
770 771 772
			return
		}
		if countfound && count < 0 {
Jan Winkelmann's avatar
Jan Winkelmann committed
773
			res.SetError(fmt.Errorf("cannot have negative byte count"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
774 775
			return
		}
Jeromy's avatar
Jeromy committed
776

777
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
778
		if err != nil {
779
			flog.Error("seekfail: ", err)
Jan Winkelmann's avatar
Jan Winkelmann committed
780
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
781 782 783 784 785
			return
		}

		input, err := req.Files().NextFile()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
786
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
787 788 789
			return
		}

zramsay's avatar
zramsay committed
790 791 792 793 794
		var r io.Reader = input
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
795
		_, err = io.Copy(wfd, r)
Jeromy's avatar
Jeromy committed
796
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
797
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
798 799 800
			return
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
801
		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
802 803 804
	},
}

Michael Muré's avatar
Michael Muré committed
805
var filesMkdirCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
806
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
807
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
808 809 810
		ShortDescription: `
Create the directory if it does not already exist.

811 812 813
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

814
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
815 816 817

Examples:

818 819
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
820 821 822
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
823 824
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to dir to make."),
Jeromy's avatar
Jeromy committed
825
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
826 827
	Options: []cmdkit.Option{
		cmdkit.BoolOption("parents", "p", "No error if existing, make parent directories as needed."),
828 829
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
830
	},
Michael Muré's avatar
Michael Muré committed
831
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
832 833
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
834
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
835 836 837 838
			return
		}

		dashp, _, _ := req.Option("parents").Bool()
Jeromy's avatar
Jeromy committed
839 840
		dirtomake, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
841
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
842 843 844
			return
		}

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

847 848
		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
849
			res.SetError(err, cmdkit.ErrNormal)
850 851 852 853
			return
		}
		root := n.FilesRoot

854 855 856 857 858
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
			Mkparents: dashp,
			Flush:     flush,
			Prefix:    prefix,
		})
859
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
860
			res.SetError(err, cmdkit.ErrNormal)
861
			return
Jeromy's avatar
Jeromy committed
862
		}
keks's avatar
keks committed
863

Jan Winkelmann's avatar
Jan Winkelmann committed
864
		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
865 866 867
	},
}

Michael Muré's avatar
Michael Muré committed
868
var filesFlushCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
869
	Helptext: cmdkit.HelpText{
870
		Tagline: "Flush a given path's data to disk.",
Jeromy's avatar
Jeromy committed
871
		ShortDescription: `
872
Flush a given path to disk. This is only useful when other commands
Jeromy's avatar
Jeromy committed
873 874 875
are run with the '--flush=false'.
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
876 877
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to flush. Default: '/'."),
Jeromy's avatar
Jeromy committed
878
	},
Michael Muré's avatar
Michael Muré committed
879
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
880 881
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
882
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
883 884 885 886 887 888 889 890 891 892
			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
893
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
894 895
			return
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
896 897

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
898 899 900
	},
}

Michael Muré's avatar
Michael Muré committed
901
var filesChcidCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
902
	Helptext: cmdkit.HelpText{
903
		Tagline: "Change the cid version or hash function of the root node of a given path.",
Kevin Atkinson's avatar
Kevin Atkinson committed
904
		ShortDescription: `
905
Change the cid version or hash function of the root node of a given path.
Kevin Atkinson's avatar
Kevin Atkinson committed
906 907
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
908 909
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to change. Default: '/'."),
Kevin Atkinson's avatar
Kevin Atkinson committed
910
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
911
	Options: []cmdkit.Option{
912 913 914
		cidVersionOption,
		hashOption,
	},
Michael Muré's avatar
Michael Muré committed
915
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Kevin Atkinson's avatar
Kevin Atkinson committed
916 917
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
918
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
919 920 921 922 923 924 925 926 927 928 929 930
			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
931
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
932 933 934 935 936
			return
		}

		err = updatePath(nd.FilesRoot, path, prefix, flush)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
937
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
938 939
			return
		}
keks's avatar
keks committed
940 941

		res.SetOutput(nil)
Kevin Atkinson's avatar
Kevin Atkinson committed
942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968
	},
}

func updatePath(rt *mfs.Root, pth string, prefix *cid.Prefix, flush bool) error {
	if prefix == nil {
		return nil
	}

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

	switch n := nd.(type) {
	case *mfs.Directory:
		n.SetPrefix(prefix)
	default:
		return fmt.Errorf("can only update directories")
	}

	if flush {
		nd.Flush()
	}

	return nil
}

Michael Muré's avatar
Michael Muré committed
969
var filesRmCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
970
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
971
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
972
		ShortDescription: `
973
Remove files or directories.
Jeromy's avatar
Jeromy committed
974 975 976 977 978 979 980 981

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

Jan Winkelmann's avatar
Jan Winkelmann committed
984 985
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
986
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
987 988
	Options: []cmdkit.Option{
		cmdkit.BoolOption("recursive", "r", "Recursively remove directories."),
Jeromy's avatar
Jeromy committed
989
	},
Michael Muré's avatar
Michael Muré committed
990
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jan Winkelmann's avatar
Jan Winkelmann committed
991 992
		defer res.SetOutput(nil)

Jeromy's avatar
Jeromy committed
993 994
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
995
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
996 997 998
			return
		}

Jeromy's avatar
Jeromy committed
999 1000
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1001
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1002 1003 1004 1005
			return
		}

		if path == "/" {
Jan Winkelmann's avatar
Jan Winkelmann committed
1006
			res.SetError(fmt.Errorf("cannot delete root"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1007 1008 1009 1010 1011 1012 1013 1014
			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
1015 1016 1017
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1018
			res.SetError(fmt.Errorf("parent lookup: %s", err), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1019 1020 1021 1022 1023
			return
		}

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

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

Jeromy's avatar
Jeromy committed
1030 1031 1032 1033 1034
		var success bool
		defer func() {
			if success {
				err := pdir.Flush()
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1035
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1036 1037 1038 1039 1040
					return
				}
			}
		}()

Jeromy's avatar
Jeromy committed
1041 1042 1043 1044
		// 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
1045
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1046 1047 1048
				return
			}

Jeromy's avatar
Jeromy committed
1049
			success = true
Jeromy's avatar
Jeromy committed
1050 1051 1052
			return
		}

Jeromy's avatar
Jeromy committed
1053 1054
		childi, err := pdir.Child(name)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1055
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1056 1057 1058 1059 1060
			return
		}

		switch childi.(type) {
		case *mfs.Directory:
Jan Winkelmann's avatar
Jan Winkelmann committed
1061
			res.SetError(fmt.Errorf("%s is a directory, use -r to remove directories", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1062
			return
Jeromy's avatar
Jeromy committed
1063 1064 1065
		default:
			err := pdir.Unlink(name)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1066
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1067 1068
				return
			}
Jeromy's avatar
Jeromy committed
1069 1070

			success = true
Jeromy's avatar
Jeromy committed
1071 1072 1073 1074
		}
	},
}

Michael Muré's avatar
Michael Muré committed
1075
func getPrefix(req oldcmds.Request) (*cid.Prefix, error) {
1076
	cidVer, cidVerSet, _ := req.Option("cid-version").Int()
1077
	hashFunStr, hashFunSet, _ := req.Option("hash").String()
1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090

	if !cidVerSet && !hashFunSet {
		return nil, nil
	}

	if hashFunSet && cidVer == 0 {
		cidVer = 1
	}

	prefix, err := dag.PrefixForCidVersion(cidVer)
	if err != nil {
		return nil, err
	}
Jeromy's avatar
Jeromy committed
1091

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

func getFileHandle(r *mfs.Root, path string, create bool, prefix *cid.Prefix) (*mfs.File, error) {
Jeromy's avatar
Jeromy committed
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
	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 {
1123
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1124 1125 1126 1127 1128 1129
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1130 1131 1132
		if prefix == nil {
			prefix = pdir.GetPrefix()
		}
Jeromy's avatar
Jeromy committed
1133

1134
		nd := dag.NodeWithData(ft.FilePBData(nil, 0))
1135
		nd.SetPrefix(prefix)
Jeromy's avatar
Jeromy committed
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145
		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
1146 1147
		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1148
			return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
Jeromy's avatar
Jeromy committed
1149 1150
		}
		return fi, nil
Jeromy's avatar
Jeromy committed
1151 1152 1153 1154 1155

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1156 1157 1158

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

	if p[0] != '/' {
Michael Muré's avatar
Michael Muré committed
1163
		return "", fmt.Errorf("paths must start with a leading slash")
Jeromy's avatar
Jeromy committed
1164 1165 1166 1167 1168 1169 1170 1171
	}

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