files.go 28.7 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"
19
	"github.com/ipfs/go-ipfs/core/coreapi/interface"
20

Michael Muré's avatar
Michael Muré committed
21
	humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
22
	cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
23
	mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
Steven Allen's avatar
Steven Allen committed
24
	offline "gx/ipfs/QmR5miWuikPxWyUrzMYJVmFUcD44pGdtc98h9Qsbp4YcJw/go-ipfs-exchange-offline"
25
	cmdkit "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
26
	ft "gx/ipfs/QmU4x3742bvgfxJsByEDpBnifJqjJdV6x528co4hwKCn46/go-unixfs"
Steven Allen's avatar
Steven Allen committed
27 28 29
	cmds "gx/ipfs/QmXTmUCBtDUrzDYVzASogLiNph7EBuYqEgPL7QoHNMzUnz/go-ipfs-cmds"
	logging "gx/ipfs/QmZChCsSt8DctjceaL56Eibc29CVQq4dGKRXC5JRZ6Ppae/go-log"
	mfs "gx/ipfs/QmahrY1adY4wvtYEtoGjpZ2GUohTyukrkMkwUR9ytRjTG2/go-mfs"
30 31
	dag "gx/ipfs/QmcBoNcAP6qDjgRBew7yjvCqHq7p5jMstE44jPUBWBxzsV/go-merkledag"
	bservice "gx/ipfs/QmcRecCZWM2NZfCQrCe97Ch3Givv8KKEP82tGUDntzdLFe/go-blockservice"
32
	ipld "gx/ipfs/QmdDXJs4axxefSPgK6Y1QhpJWKuDPnGJiqgq4uncb4rFHL/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
		"read":  lgc.NewCommand(filesReadCmd),
60
		"write": filesWriteCmd,
Michael Muré's avatar
Michael Muré committed
61 62 63 64
		"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
	},
keks's avatar
keks committed
109
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
110 111 112

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

116
		node, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
117
		if err != nil {
keks's avatar
keks committed
118
			return err
Jeromy's avatar
Jeromy committed
119 120
		}

121 122 123 124 125
		api, err := cmdenv.GetApi(env)
		if err != nil {
			return err
		}

126
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
127
		if err != nil {
keks's avatar
keks committed
128
			return err
Jeromy's avatar
Jeromy committed
129 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
		}

144
		nd, err := getNodeFromPath(req.Context, node, api, path)
Jeromy's avatar
Jeromy committed
145
		if err != nil {
keks's avatar
keks committed
146
			return err
Jeromy's avatar
Jeromy committed
147 148
		}

149
		o, err := statNode(nd)
Jeromy's avatar
Jeromy committed
150
		if err != nil {
keks's avatar
keks committed
151
			return err
Jeromy's avatar
Jeromy committed
152 153
		}

154
		if !withLocal {
keks's avatar
keks committed
155
			return cmds.EmitOnce(res, o)
156 157 158 159 160 161 162 163
		}

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

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

keks's avatar
keks committed
164
		return cmds.EmitOnce(res, 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
	switch n := nd.(type) {
	case *dag.ProtoNode:
Overbool's avatar
Overbool committed
229
		d, err := ft.FSNodeFromBytes(n.Data())
230 231 232 233 234
		if err != nil {
			return nil, err
		}

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

Michael Muré's avatar
Michael Muré committed
244
		return &statOutput{
245 246
			Hash:           c.String(),
			Blocks:         len(nd.Links()),
Overbool's avatar
Overbool committed
247
			Size:           d.FileSize(),
248 249 250 251
			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 312 313 314 315 316
		api, err := req.InvocContext().GetApi()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

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

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

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

332 333 334 335
		if dst[len(dst)-1] == '/' {
			dst += gopath.Base(src)
		}

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

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

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

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
357 358 359
	},
}

360
func getNodeFromPath(ctx context.Context, node *core.IpfsNode, api iface.CoreAPI, p string) (ipld.Node, error) {
Jeromy's avatar
Jeromy committed
361 362
	switch {
	case strings.HasPrefix(p, "/ipfs/"):
363
		np, err := iface.ParsePath(p)
Jeromy's avatar
Jeromy committed
364 365 366 367
		if err != nil {
			return nil, err
		}

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

		return fsn.GetNode()
	}
}

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

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

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

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

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

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

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

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

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

				for _, name := range names {
Jeromy's avatar
Jeromy committed
451
					output = append(output, mfs.NodeListing{
Jeromy's avatar
Jeromy committed
452
						Name: name,
Jeromy's avatar
Jeromy committed
453 454
					})
				}
Michael Muré's avatar
Michael Muré committed
455
				res.SetOutput(&filesLsOutput{output})
Jeromy's avatar
Jeromy committed
456
			} else {
Jeromy's avatar
Jeromy committed
457
				listing, err := fsn.List(req.Context())
Jeromy's avatar
Jeromy committed
458
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
459
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
460 461
					return
				}
Michael Muré's avatar
Michael Muré committed
462
				res.SetOutput(&filesLsOutput{listing})
Jeromy's avatar
Jeromy committed
463 464 465
			}
			return
		case *mfs.File:
rht's avatar
rht committed
466
			_, name := gopath.Split(path)
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
			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
485 486 487
			res.SetOutput(out)
			return
		default:
Jan Winkelmann's avatar
Jan Winkelmann committed
488
			res.SetError(errors.New("unrecognized type"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
489 490
		}
	},
Michael Muré's avatar
Michael Muré committed
491 492
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Text: func(res oldcmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
493 494 495 496 497
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

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

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

Lucas Molas's avatar
Lucas Molas committed
505 506 507 508 509 510 511 512
			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
513 514 515 516 517 518 519 520 521 522
			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
523
	Type: filesLsOutput{},
Jeromy's avatar
Jeromy committed
524 525
}

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

Examples:

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

Jan Winkelmann's avatar
Jan Winkelmann committed
540 541
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to file to be read."),
Jeromy's avatar
Jeromy committed
542
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
543 544 545
	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
546
	},
Michael Muré's avatar
Michael Muré committed
547
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
548 549
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
550
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
551 552 553
			return
		}

Jeromy's avatar
Jeromy committed
554 555
		path, err := checkPath(req.Arguments()[0])
		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
		}

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

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

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

		defer rfd.Close()

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

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

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

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

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

		res.SetOutput(r)
	},
}

Jeromy's avatar
Jeromy committed
625 626 627 628 629 630 631 632 633 634 635 636 637
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
638
var filesMvCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
639
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
640
		Tagline: "Move files.",
Jeromy's avatar
Jeromy committed
641 642 643 644 645 646 647
		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
648
`,
Jeromy's avatar
Jeromy committed
649 650
	},

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

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

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

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

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

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

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

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

705
EXAMPLE:
Jeromy's avatar
Jeromy committed
706

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

710
WARNING:
711

712 713 714
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
715
`,
Jeromy's avatar
Jeromy committed
716
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
717 718 719
	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
720
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
721 722 723
	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."),
724
		cmdkit.BoolOption("parents", "p", "Make parent directories as needed."),
Jan Winkelmann's avatar
Jan Winkelmann committed
725 726 727
		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)"),
728 729
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
730
	},
keks's avatar
keks committed
731
	Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) (retErr error) {
732
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
733
		if err != nil {
keks's avatar
keks committed
734
			return err
Jeromy's avatar
Jeromy committed
735 736
		}

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

743
		prefix, err := getPrefixNew(req)
744
		if err != nil {
keks's avatar
keks committed
745
			return err
746
		}
Jeromy's avatar
Jeromy committed
747

748
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
749
		if err != nil {
keks's avatar
keks committed
750
			return err
Jeromy's avatar
Jeromy committed
751 752
		}

753
		offset, _ := req.Options["offset"].(int)
Jeromy's avatar
Jeromy committed
754
		if offset < 0 {
keks's avatar
keks committed
755
			return fmt.Errorf("cannot have negative write offset")
Jeromy's avatar
Jeromy committed
756 757
		}

758 759 760
		if mkParents {
			err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix)
			if err != nil {
keks's avatar
keks committed
761
				return err
762 763 764
			}
		}

765
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
766
		if err != nil {
keks's avatar
keks committed
767
			return err
Jeromy's avatar
Jeromy committed
768
		}
769 770 771
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
772

773 774
		wfd, err := fi.Open(mfs.OpenWriteOnly, flush)
		if err != nil {
keks's avatar
keks committed
775
			return err
776
		}
Jeromy's avatar
Jeromy committed
777

778 779 780
		defer func() {
			err := wfd.Close()
			if err != nil {
keks's avatar
keks committed
781 782 783 784 785
				if retErr == nil {
					retErr = err
				} else {
					log.Error("files: error closing file mfs file descriptor", err)
				}
786 787
			}
		}()
788

Jeromy's avatar
Jeromy committed
789
		if trunc {
790
			if err := wfd.Truncate(0); err != nil {
keks's avatar
keks committed
791
				return err
Jeromy's avatar
Jeromy committed
792 793 794
			}
		}

795
		count, countfound := req.Options["count"].(int)
Jeromy's avatar
Jeromy committed
796
		if countfound && count < 0 {
keks's avatar
keks committed
797
			return fmt.Errorf("cannot have negative byte count")
Jeromy's avatar
Jeromy committed
798
		}
Jeromy's avatar
Jeromy committed
799

800
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
801
		if err != nil {
802
			flog.Error("seekfail: ", err)
keks's avatar
keks committed
803
			return err
Jeromy's avatar
Jeromy committed
804 805
		}

806
		input, err := req.Files.NextFile()
Jeromy's avatar
Jeromy committed
807
		if err != nil {
keks's avatar
keks committed
808
			return err
Jeromy's avatar
Jeromy committed
809 810
		}

zramsay's avatar
zramsay committed
811 812 813 814 815
		var r io.Reader = input
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
816
		_, err = io.Copy(wfd, r)
keks's avatar
keks committed
817
		return err
Jeromy's avatar
Jeromy committed
818 819 820
	},
}

Michael Muré's avatar
Michael Muré committed
821
var filesMkdirCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
822
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
823
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
824 825 826
		ShortDescription: `
Create the directory if it does not already exist.

827 828 829
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

830
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
831 832 833

Examples:

834 835
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
836 837 838
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
839 840
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to dir to make."),
Jeromy's avatar
Jeromy committed
841
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
842 843
	Options: []cmdkit.Option{
		cmdkit.BoolOption("parents", "p", "No error if existing, make parent directories as needed."),
844 845
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
846
	},
Michael Muré's avatar
Michael Muré committed
847
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
848 849
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
850
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
851 852 853 854
			return
		}

		dashp, _, _ := req.Option("parents").Bool()
Jeromy's avatar
Jeromy committed
855 856
		dirtomake, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
857
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
858 859 860
			return
		}

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

863 864
		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
865
			res.SetError(err, cmdkit.ErrNormal)
866 867 868 869
			return
		}
		root := n.FilesRoot

870
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
871 872 873
			Mkparents:  dashp,
			Flush:      flush,
			CidBuilder: prefix,
874
		})
875
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
876
			res.SetError(err, cmdkit.ErrNormal)
877
			return
Jeromy's avatar
Jeromy committed
878
		}
keks's avatar
keks committed
879

Jan Winkelmann's avatar
Jan Winkelmann committed
880
		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
881 882 883
	},
}

Michael Muré's avatar
Michael Muré committed
884
var filesFlushCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
885
	Helptext: cmdkit.HelpText{
886
		Tagline: "Flush a given path's data to disk.",
Jeromy's avatar
Jeromy committed
887
		ShortDescription: `
888
Flush a given path to disk. This is only useful when other commands
Jeromy's avatar
Jeromy committed
889 890 891
are run with the '--flush=false'.
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
892 893
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", false, false, "Path to flush. Default: '/'."),
Jeromy's avatar
Jeromy committed
894
	},
Michael Muré's avatar
Michael Muré committed
895
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
896 897
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
898
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
899 900 901 902 903 904 905 906 907 908
			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
909
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
910 911
			return
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
912 913

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
914 915 916
	},
}

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

		err = updatePath(nd.FilesRoot, path, prefix, flush)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
953
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
954 955
			return
		}
keks's avatar
keks committed
956 957

		res.SetOutput(nil)
Kevin Atkinson's avatar
Kevin Atkinson committed
958 959 960
	},
}

961 962
func updatePath(rt *mfs.Root, pth string, builder cid.Builder, flush bool) error {
	if builder == nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
963 964 965 966 967 968 969 970 971 972
		return nil
	}

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

	switch n := nd.(type) {
	case *mfs.Directory:
973
		n.SetCidBuilder(builder)
Kevin Atkinson's avatar
Kevin Atkinson committed
974 975 976 977 978 979 980 981 982 983 984
	default:
		return fmt.Errorf("can only update directories")
	}

	if flush {
		nd.Flush()
	}

	return nil
}

Michael Muré's avatar
Michael Muré committed
985
var filesRmCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
986
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
987
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
988
		ShortDescription: `
989
Remove files or directories.
Jeromy's avatar
Jeromy committed
990 991 992 993 994 995 996 997

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

Jan Winkelmann's avatar
Jan Winkelmann committed
1000 1001
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
1002
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
1003 1004
	Options: []cmdkit.Option{
		cmdkit.BoolOption("recursive", "r", "Recursively remove directories."),
Jeromy's avatar
Jeromy committed
1005
	},
Michael Muré's avatar
Michael Muré committed
1006
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jan Winkelmann's avatar
Jan Winkelmann committed
1007 1008
		defer res.SetOutput(nil)

Jeromy's avatar
Jeromy committed
1009 1010
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1011
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1012 1013 1014
			return
		}

Jeromy's avatar
Jeromy committed
1015 1016
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1017
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1018 1019 1020 1021
			return
		}

		if path == "/" {
Jan Winkelmann's avatar
Jan Winkelmann committed
1022
			res.SetError(fmt.Errorf("cannot delete root"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1023 1024 1025 1026 1027 1028 1029 1030
			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
1031 1032 1033
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1034
			res.SetError(fmt.Errorf("parent lookup: %s", err), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1035 1036 1037 1038 1039
			return
		}

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

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

Jeromy's avatar
Jeromy committed
1046 1047 1048 1049 1050
		var success bool
		defer func() {
			if success {
				err := pdir.Flush()
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1051
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1052 1053 1054 1055 1056
					return
				}
			}
		}()

Jeromy's avatar
Jeromy committed
1057 1058 1059 1060
		// 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
1061
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1062 1063 1064
				return
			}

Jeromy's avatar
Jeromy committed
1065
			success = true
Jeromy's avatar
Jeromy committed
1066 1067 1068
			return
		}

Jeromy's avatar
Jeromy committed
1069 1070
		childi, err := pdir.Child(name)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1071
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1072 1073 1074 1075 1076
			return
		}

		switch childi.(type) {
		case *mfs.Directory:
Jan Winkelmann's avatar
Jan Winkelmann committed
1077
			res.SetError(fmt.Errorf("%s is a directory, use -r to remove directories", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1078
			return
Jeromy's avatar
Jeromy committed
1079 1080 1081
		default:
			err := pdir.Unlink(name)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1082
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1083 1084
				return
			}
Jeromy's avatar
Jeromy committed
1085 1086

			success = true
Jeromy's avatar
Jeromy committed
1087 1088 1089 1090
		}
	},
}

1091
func getPrefixNew(req *cmds.Request) (cid.Builder, error) {
1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
	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
}

1120
func getPrefix(req oldcmds.Request) (cid.Builder, error) {
1121
	cidVer, cidVerSet, _ := req.Option("cid-version").Int()
1122
	hashFunStr, hashFunSet, _ := req.Option("hash").String()
1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135

	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
1136

1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148
	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
}

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

1162
func getFileHandle(r *mfs.Root, path string, create bool, builder cid.Builder) (*mfs.File, error) {
Jeromy's avatar
Jeromy committed
1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
	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 {
1181
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1182 1183 1184 1185 1186 1187
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1188 1189
		if builder == nil {
			builder = pdir.GetCidBuilder()
1190
		}
Jeromy's avatar
Jeromy committed
1191

1192
		nd := dag.NodeWithData(ft.FilePBData(nil, 0))
1193
		nd.SetCidBuilder(builder)
Jeromy's avatar
Jeromy committed
1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
		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
1204 1205
		fi, ok := fsn.(*mfs.File)
		if !ok {
Michael Muré's avatar
Michael Muré committed
1206
			return nil, errors.New("expected *mfs.File, didnt get it. This is likely a race condition")
Jeromy's avatar
Jeromy committed
1207 1208
		}
		return fi, nil
Jeromy's avatar
Jeromy committed
1209 1210 1211 1212 1213

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1214 1215 1216

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

	if p[0] != '/' {
Michael Muré's avatar
Michael Muré committed
1221
		return "", fmt.Errorf("paths must start with a leading slash")
Jeromy's avatar
Jeromy committed
1222 1223 1224 1225 1226 1227 1228 1229
	}

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