block.go 6.73 KB
Newer Older
1 2 3 4
package commands

import (
	"bytes"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5
	"fmt"
6
	"io"
7
	"io/ioutil"
8
	"strings"
9

10
	"github.com/ipfs/go-ipfs/blocks"
11
	util "github.com/ipfs/go-ipfs/blocks/blockstore/util"
12
	cmds "github.com/ipfs/go-ipfs/commands"
13 14

	mh "gx/ipfs/QmYDds3421prZgqKbLpEK7T9Aa2eVdQ7o3YarX1LVLdP2J/go-multihash"
15
	u "gx/ipfs/Qmb912gdngC1UWwTkhuW8knyRbcWeu5kqkxBpveLmW8bSr/go-ipfs-util"
16
	cid "gx/ipfs/QmcEcrBAMrwMyhSjXt4yfyPpzgSuV8HLHavnfmiKCSRqZU/go-cid"
17 18
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
19 20 21 22 23 24 25
type BlockStat struct {
	Key  string
	Size int
}

func (bs BlockStat) String() string {
	return fmt.Sprintf("Key: %s\nSize: %d\n", bs.Key, bs.Size)
26 27
}

28
var BlockCmd = &cmds.Command{
29
	Helptext: cmds.HelpText{
30
		Tagline: "Interact with raw IPFS blocks.",
31
		ShortDescription: `
32
'ipfs block' is a plumbing command used to manipulate raw IPFS blocks.
33
Reads from stdin or writes to stdout, and <key> is a base58 encoded
34 35 36 37
multihash.
`,
	},

38
	Subcommands: map[string]*cmds.Command{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
39 40 41
		"stat": blockStatCmd,
		"get":  blockGetCmd,
		"put":  blockPutCmd,
Kevin Atkinson's avatar
Kevin Atkinson committed
42
		"rm":   blockRmCmd,
43 44 45
	},
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
46
var blockStatCmd = &cmds.Command{
47
	Helptext: cmds.HelpText{
rht's avatar
rht committed
48
		Tagline: "Print information of a raw IPFS block.",
49
		ShortDescription: `
rht's avatar
rht committed
50
'ipfs block stat' is a plumbing command for retrieving information
51
on raw IPFS blocks. It outputs the following to stdout:
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
52 53 54 55

	Key  - the base58 encoded multihash
	Size - the size of the block in bytes

56 57
`,
	},
58

59
	Arguments: []cmds.Argument{
60
		cmds.StringArg("key", true, false, "The base58 multihash of an existing block to stat.").EnableStdin(),
61
	},
62
	Run: func(req cmds.Request, res cmds.Response) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
63
		b, err := getBlockForKey(req, req.Arguments()[0])
64
		if err != nil {
65 66
			res.SetError(err, cmds.ErrNormal)
			return
67
		}
68

69
		res.SetOutput(&BlockStat{
70
			Key:  b.Cid().String(),
Jeromy's avatar
Jeromy committed
71
			Size: len(b.RawData()),
72
		})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73 74 75 76 77 78 79 80 81
	},
	Type: BlockStat{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			bs := res.Output().(*BlockStat)
			return strings.NewReader(bs.String()), nil
		},
	},
}
82

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
83 84
var blockGetCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
85
		Tagline: "Get a raw IPFS block.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
86
		ShortDescription: `
87
'ipfs block get' is a plumbing command for retrieving raw IPFS blocks.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
88 89 90
It outputs to stdout, and <key> is a base58 encoded multihash.
`,
	},
91

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
92
	Arguments: []cmds.Argument{
93
		cmds.StringArg("key", true, false, "The base58 multihash of an existing block to get.").EnableStdin(),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
94
	},
95
	Run: func(req cmds.Request, res cmds.Response) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
96
		b, err := getBlockForKey(req, req.Arguments()[0])
97
		if err != nil {
98 99
			res.SetError(err, cmds.ErrNormal)
			return
100 101
		}

Jeromy's avatar
Jeromy committed
102
		res.SetOutput(bytes.NewReader(b.RawData()))
103 104 105 106
	},
}

var blockPutCmd = &cmds.Command{
107
	Helptext: cmds.HelpText{
108
		Tagline: "Store input as an IPFS block.",
109
		ShortDescription: `
110
'ipfs block put' is a plumbing command for storing raw IPFS blocks.
111 112 113
It reads from stdin, and <key> is a base58 encoded multihash.
`,
	},
114

115
	Arguments: []cmds.Argument{
116
		cmds.FileArg("data", true, false, "The data to be stored as an IPFS block.").EnableStdin(),
117
	},
118 119 120
	Options: []cmds.Option{
		cmds.StringOption("format", "f", "cid format for blocks to be created with.").Default("v0"),
	},
121
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
122
		n, err := req.InvocContext().GetNode()
123
		if err != nil {
124 125
			res.SetError(err, cmds.ErrNormal)
			return
126
		}
127

128 129
		file, err := req.Files().NextFile()
		if err != nil {
130 131
			res.SetError(err, cmds.ErrNormal)
			return
132 133 134 135
		}

		data, err := ioutil.ReadAll(file)
		if err != nil {
136 137
			res.SetError(err, cmds.ErrNormal)
			return
138 139
		}

140
		err = file.Close()
141
		if err != nil {
142 143
			res.SetError(err, cmds.ErrNormal)
			return
144 145
		}

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
		format, _, _ := req.Option("format").String()
		var pref cid.Prefix
		pref.MhType = mh.SHA2_256
		pref.MhLength = -1
		pref.Version = 1
		switch format {
		case "cbor":
			pref.Codec = cid.CBOR
		case "json":
			pref.Codec = cid.JSON
		case "protobuf":
			pref.Codec = cid.Protobuf
		case "raw":
			pref.Codec = cid.Raw
		case "v0":
			pref.Version = 0
			pref.Codec = cid.Protobuf
		default:
			res.SetError(fmt.Errorf("unrecognized format: %s", format), cmds.ErrNormal)
			return
		}

		bcid, err := pref.Sum(data)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		b, err := blocks.NewBlockWithCid(data, bcid)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
179
		log.Debugf("BlockPut key: '%q'", b.Cid())
180

181
		k, err := n.Blocks.AddBlock(b)
182
		if err != nil {
183 184
			res.SetError(err, cmds.ErrNormal)
			return
185 186
		}

187
		res.SetOutput(&BlockStat{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
188 189
			Key:  k.String(),
			Size: len(data),
190
		})
191
	},
192
	Marshalers: cmds.MarshalerMap{
193
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
194 195
			bs := res.Output().(*BlockStat)
			return strings.NewReader(bs.Key + "\n"), nil
196 197
		},
	},
Matt Bell's avatar
Matt Bell committed
198
	Type: BlockStat{},
199
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
200

201
func getBlockForKey(req cmds.Request, skey string) (blocks.Block, error) {
202 203 204 205
	if len(skey) == 0 {
		return nil, fmt.Errorf("zero length cid invalid")
	}

Jeromy's avatar
Jeromy committed
206
	n, err := req.InvocContext().GetNode()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
207 208 209 210
	if err != nil {
		return nil, err
	}

Jeromy's avatar
Jeromy committed
211
	c, err := cid.Decode(skey)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
212 213 214 215
	if err != nil {
		return nil, err
	}

Jeromy's avatar
Jeromy committed
216
	b, err := n.Blocks.GetBlock(req.Context(), c)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
217 218 219
	if err != nil {
		return nil, err
	}
220

221
	log.Debugf("ipfs block: got block with key: %s", b.Cid())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
222 223
	return b, nil
}
Kevin Atkinson's avatar
Kevin Atkinson committed
224 225 226 227 228 229 230 231 232 233 234 235

var blockRmCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Remove IPFS block(s).",
		ShortDescription: `
'ipfs block rm' is a plumbing command for removing raw ipfs blocks.
It takes a list of base58 encoded multihashs to remove.
`,
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("hash", true, true, "Bash58 encoded multihash of block(s) to remove."),
	},
236 237 238 239
	Options: []cmds.Option{
		cmds.BoolOption("force", "f", "Ignore nonexistent blocks.").Default(false),
		cmds.BoolOption("quiet", "q", "Write minimal output.").Default(false),
	},
Kevin Atkinson's avatar
Kevin Atkinson committed
240 241 242 243 244 245 246
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		hashes := req.Arguments()
247 248
		force, _, _ := req.Option("force").Bool()
		quiet, _, _ := req.Option("quiet").Bool()
Jeromy's avatar
Jeromy committed
249
		cids := make([]*cid.Cid, 0, len(hashes))
Kevin Atkinson's avatar
Kevin Atkinson committed
250
		for _, hash := range hashes {
Jeromy's avatar
Jeromy committed
251 252 253 254 255 256 257
			c, err := cid.Decode(hash)
			if err != nil {
				res.SetError(fmt.Errorf("invalid content id: %s (%s)", hash, err), cmds.ErrNormal)
				return
			}

			cids = append(cids, c)
Kevin Atkinson's avatar
Kevin Atkinson committed
258
		}
259
		outChan := make(chan interface{})
260 261 262 263 264 265 266 267
		err = util.RmBlocks(n.Blockstore, n.Pinning, outChan, cids, util.RmBlocksOpts{
			Quiet: quiet,
			Force: force,
		})
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
268
		res.SetOutput((<-chan interface{})(outChan))
Kevin Atkinson's avatar
Kevin Atkinson committed
269
	},
270 271 272 273 274 275 276 277 278 279 280
	PostRun: func(req cmds.Request, res cmds.Response) {
		if res.Error() != nil {
			return
		}
		outChan, ok := res.Output().(<-chan interface{})
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
		res.SetOutput(nil)

281 282 283
		err := util.ProcRmOutput(outChan, res.Stdout(), res.Stderr())
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
284
		}
Kevin Atkinson's avatar
Kevin Atkinson committed
285
	},
286
	Type: util.RemovedBlock{},
Kevin Atkinson's avatar
Kevin Atkinson committed
287
}