files.go 27.6 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"
Jeromy's avatar
Jeromy committed
18 19 20
	dag "github.com/ipfs/go-ipfs/merkledag"
	mfs "github.com/ipfs/go-ipfs/mfs"
	path "github.com/ipfs/go-ipfs/path"
21
	resolver "github.com/ipfs/go-ipfs/path/resolver"
Jeromy's avatar
Jeromy committed
22
	ft "github.com/ipfs/go-ipfs/unixfs"
23
	uio "github.com/ipfs/go-ipfs/unixfs/io"
Jeromy's avatar
Jeromy committed
24

Steven Allen's avatar
Steven Allen committed
25
	cmds "gx/ipfs/QmNueRyPRQiV7PUEpnP4GgGLuK1rKQLaRW7sfPvUetYig1/go-ipfs-cmds"
Michael Muré's avatar
Michael Muré committed
26
	humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
27
	mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
Steven Allen's avatar
Steven Allen committed
28
	offline "gx/ipfs/QmRCgkkCmf1nMrW2BLZZtjP3Xyw3GfZVYRLix9wrnW4NoR/go-ipfs-exchange-offline"
29 30
	ipld "gx/ipfs/QmWi2BYBL5gJ3CiAiQchg6rn1A8iBsrWy51EYxvHVjFvLb/go-ipld-format"
	cid "gx/ipfs/QmapdYm1b22Frv3k17fqrBYTFRxwiaVJkB299Mfn33edeB/go-cid"
Steven Allen's avatar
Steven Allen committed
31
	logging "gx/ipfs/QmcVVHfdyv15GVPk7NrxdWjh2hLVccXnoD8j2tyQShiXJb/go-log"
32
	cmdkit "gx/ipfs/QmdE4gMduCKCGAcczM2F5ioYDfdeKuPix138wrES1YSr7f/go-ipfs-cmdkit"
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
	},
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 {
154
			cmds.EmitOnce(res, 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

164
		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 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
	},
}

657
var filesWriteCmd = &cmds.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
	},
704 705
	Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) {
		path, err := checkPath(req.Arguments[0])
Jeromy's avatar
Jeromy committed
706
		if err != nil {
707
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
708 709 710
			return
		}

711 712 713 714
		create, _ := req.Options["create"].(bool)
		trunc, _ := req.Options["truncate"].(bool)
		flush, _ := req.Options["flush"].(bool)
		rawLeaves, rawLeavesDef := req.Options["raw-leaves"].(bool)
715

716
		prefix, err := getPrefixNew(req)
717
		if err != nil {
718
			re.SetError(err, cmdkit.ErrNormal)
719 720
			return
		}
Jeromy's avatar
Jeromy committed
721

722
		nd, err := GetNode(env)
Jeromy's avatar
Jeromy committed
723
		if err != nil {
724
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
725 726 727
			return
		}

728
		offset, _ := req.Options["offset"].(int)
Jeromy's avatar
Jeromy committed
729
		if offset < 0 {
730
			re.SetError(fmt.Errorf("cannot have negative write offset"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
731 732 733
			return
		}

734
		fi, err := getFileHandle(nd.FilesRoot, path, create, prefix)
Jeromy's avatar
Jeromy committed
735
		if err != nil {
736
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
737 738
			return
		}
739 740 741
		if rawLeavesDef {
			fi.RawLeaves = rawLeaves
		}
742

743 744
		wfd, err := fi.Open(mfs.OpenWriteOnly, flush)
		if err != nil {
745
			re.SetError(err, cmdkit.ErrNormal)
746
			return
747
		}
Jeromy's avatar
Jeromy committed
748

749 750 751
		defer func() {
			err := wfd.Close()
			if err != nil {
752
				re.SetError(err, cmdkit.ErrNormal)
753 754
			}
		}()
755

Jeromy's avatar
Jeromy committed
756
		if trunc {
757
			if err := wfd.Truncate(0); err != nil {
758
				re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
759 760 761 762
				return
			}
		}

763
		count, countfound := req.Options["count"].(int)
Jeromy's avatar
Jeromy committed
764
		if countfound && count < 0 {
765
			re.SetError(fmt.Errorf("cannot have negative byte count"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
766 767
			return
		}
Jeromy's avatar
Jeromy committed
768

769
		_, err = wfd.Seek(int64(offset), io.SeekStart)
Jeromy's avatar
Jeromy committed
770
		if err != nil {
771
			flog.Error("seekfail: ", err)
772
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
773 774 775
			return
		}

776
		input, err := req.Files.NextFile()
Jeromy's avatar
Jeromy committed
777
		if err != nil {
778
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
779 780 781
			return
		}

zramsay's avatar
zramsay committed
782 783 784 785 786
		var r io.Reader = input
		if countfound {
			r = io.LimitReader(r, int64(count))
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
787
		_, err = io.Copy(wfd, r)
Jeromy's avatar
Jeromy committed
788
		if err != nil {
789
			re.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
790 791 792 793 794
			return
		}
	},
}

Michael Muré's avatar
Michael Muré committed
795
var filesMkdirCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
796
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
797
		Tagline: "Make directories.",
Jeromy's avatar
Jeromy committed
798 799 800
		ShortDescription: `
Create the directory if it does not already exist.

801 802 803
The directory will have the same CID version and hash function of the
parent directory unless the --cid-version and --hash options are used.

804
NOTE: All paths must be absolute.
Jeromy's avatar
Jeromy committed
805 806 807

Examples:

808 809
    $ ipfs files mkdir /test/newdir
    $ ipfs files mkdir -p /test/does/not/exist/yet
Jeromy's avatar
Jeromy committed
810 811 812
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
813 814
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, false, "Path to dir to make."),
Jeromy's avatar
Jeromy committed
815
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
816 817
	Options: []cmdkit.Option{
		cmdkit.BoolOption("parents", "p", "No error if existing, make parent directories as needed."),
818 819
		cidVersionOption,
		hashOption,
Jeromy's avatar
Jeromy committed
820
	},
Michael Muré's avatar
Michael Muré committed
821
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
822 823
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
824
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
825 826 827 828
			return
		}

		dashp, _, _ := req.Option("parents").Bool()
Jeromy's avatar
Jeromy committed
829 830
		dirtomake, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
831
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
832 833 834
			return
		}

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

837 838
		prefix, err := getPrefix(req)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
839
			res.SetError(err, cmdkit.ErrNormal)
840 841 842 843
			return
		}
		root := n.FilesRoot

844 845 846 847 848
		err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{
			Mkparents: dashp,
			Flush:     flush,
			Prefix:    prefix,
		})
849
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
850
			res.SetError(err, cmdkit.ErrNormal)
851
			return
Jeromy's avatar
Jeromy committed
852
		}
keks's avatar
keks committed
853

Jan Winkelmann's avatar
Jan Winkelmann committed
854
		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
855 856 857
	},
}

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

		res.SetOutput(nil)
Jeromy's avatar
Jeromy committed
888 889 890
	},
}

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

		err = updatePath(nd.FilesRoot, path, prefix, flush)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
927
			res.SetError(err, cmdkit.ErrNormal)
Kevin Atkinson's avatar
Kevin Atkinson committed
928 929
			return
		}
keks's avatar
keks committed
930 931

		res.SetOutput(nil)
Kevin Atkinson's avatar
Kevin Atkinson committed
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
	},
}

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
959
var filesRmCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
960
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
961
		Tagline: "Remove a file.",
Jeromy's avatar
Jeromy committed
962
		ShortDescription: `
963
Remove files or directories.
Jeromy's avatar
Jeromy committed
964 965 966 967 968 969 970 971

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

Jan Winkelmann's avatar
Jan Winkelmann committed
974 975
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("path", true, true, "File to remove."),
Jeromy's avatar
Jeromy committed
976
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
977 978
	Options: []cmdkit.Option{
		cmdkit.BoolOption("recursive", "r", "Recursively remove directories."),
Jeromy's avatar
Jeromy committed
979
	},
Michael Muré's avatar
Michael Muré committed
980
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jan Winkelmann's avatar
Jan Winkelmann committed
981 982
		defer res.SetOutput(nil)

Jeromy's avatar
Jeromy committed
983 984
		nd, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
985
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
986 987 988
			return
		}

Jeromy's avatar
Jeromy committed
989 990
		path, err := checkPath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
991
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
992 993 994 995
			return
		}

		if path == "/" {
Jan Winkelmann's avatar
Jan Winkelmann committed
996
			res.SetError(fmt.Errorf("cannot delete root"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
997 998 999 1000 1001 1002 1003 1004
			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
1005 1006 1007
		dir, name := gopath.Split(path)
		parent, err := mfs.Lookup(nd.FilesRoot, dir)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1008
			res.SetError(fmt.Errorf("parent lookup: %s", err), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1009 1010 1011 1012 1013
			return
		}

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

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

Jeromy's avatar
Jeromy committed
1020 1021 1022 1023 1024
		var success bool
		defer func() {
			if success {
				err := pdir.Flush()
				if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1025
					res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1026 1027 1028 1029 1030
					return
				}
			}
		}()

Jeromy's avatar
Jeromy committed
1031 1032 1033 1034
		// 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
1035
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1036 1037 1038
				return
			}

Jeromy's avatar
Jeromy committed
1039
			success = true
Jeromy's avatar
Jeromy committed
1040 1041 1042
			return
		}

Jeromy's avatar
Jeromy committed
1043 1044
		childi, err := pdir.Child(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 1049 1050
			return
		}

		switch childi.(type) {
		case *mfs.Directory:
Jan Winkelmann's avatar
Jan Winkelmann committed
1051
			res.SetError(fmt.Errorf("%s is a directory, use -r to remove directories", path), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1052
			return
Jeromy's avatar
Jeromy committed
1053 1054 1055
		default:
			err := pdir.Unlink(name)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
1056
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
1057 1058
				return
			}
Jeromy's avatar
Jeromy committed
1059 1060

			success = true
Jeromy's avatar
Jeromy committed
1061 1062 1063 1064
		}
	},
}

1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
func getPrefixNew(req *cmds.Request) (*cid.Prefix, error) {
	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
}

Michael Muré's avatar
Michael Muré committed
1094
func getPrefix(req oldcmds.Request) (*cid.Prefix, error) {
1095
	cidVer, cidVerSet, _ := req.Option("cid-version").Int()
1096
	hashFunStr, hashFunSet, _ := req.Option("hash").String()
1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109

	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
1110

1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123
	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
1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
	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 {
1142
			flog.Error("lookupfail ", dirname)
Jeromy's avatar
Jeromy committed
1143 1144 1145 1146 1147 1148
			return nil, err
		}
		pdir, ok := pdiri.(*mfs.Directory)
		if !ok {
			return nil, fmt.Errorf("%s was not a directory", dirname)
		}
1149 1150 1151
		if prefix == nil {
			prefix = pdir.GetPrefix()
		}
Jeromy's avatar
Jeromy committed
1152

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

	default:
		return nil, err
	}
}
Jeromy's avatar
Jeromy committed
1175 1176 1177

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

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

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