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

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

11
	"github.com/ipfs/go-ipfs/blocks"
Kevin Atkinson's avatar
Kevin Atkinson committed
12
	bs "github.com/ipfs/go-ipfs/blocks/blockstore"
13
	cmds "github.com/ipfs/go-ipfs/commands"
Kevin Atkinson's avatar
Kevin Atkinson committed
14
	"github.com/ipfs/go-ipfs/pin"
15
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
George Antoniadis's avatar
George Antoniadis committed
16 17
	ds "gx/ipfs/QmbzuUusHqaLLoNTDEVLcSF6vZDHZDLPC7p4bztRvvkXxU/go-datastore"
	key "gx/ipfs/Qmce4Y4zg3sYr7xKM5UueS67vhNni6EeWgCRnb7MbLJMew/go-key"
Jeromy's avatar
Jeromy committed
18
	cid "gx/ipfs/QmfSc2xehWmWLnwwYR91Y8QF4xdASypTFVknutoKQS3GHp/go-cid"
19 20
)

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

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

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

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

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

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

58 59
`,
	},
60

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

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

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

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

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

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

117
	Arguments: []cmds.Argument{
118
		cmds.FileArg("data", true, false, "The data to be stored as an IPFS block.").EnableStdin(),
119
	},
120
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
121
		n, err := req.InvocContext().GetNode()
122
		if err != nil {
123 124
			res.SetError(err, cmds.ErrNormal)
			return
125
		}
126

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

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

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

		b := blocks.NewBlock(data)
146
		log.Debugf("BlockPut key: '%q'", b.Key())
147

Jeromy's avatar
Jeromy committed
148
		k, err := n.Blocks.AddObject(b)
149
		if err != nil {
150 151
			res.SetError(err, cmds.ErrNormal)
			return
152 153
		}

154
		res.SetOutput(&BlockStat{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
155 156
			Key:  k.String(),
			Size: len(data),
157
		})
158
	},
159
	Marshalers: cmds.MarshalerMap{
160
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
161 162
			bs := res.Output().(*BlockStat)
			return strings.NewReader(bs.Key + "\n"), nil
163 164
		},
	},
Matt Bell's avatar
Matt Bell committed
165
	Type: BlockStat{},
166
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
167

168
func getBlockForKey(req cmds.Request, skey string) (blocks.Block, error) {
Jeromy's avatar
Jeromy committed
169
	n, err := req.InvocContext().GetNode()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170 171 172 173
	if err != nil {
		return nil, err
	}

174
	if !u.IsValidHash(skey) {
175
		return nil, errors.New("Not a valid hash")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
176 177
	}

Jeromy's avatar
Jeromy committed
178
	c, err := cid.Decode(skey)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
179 180 181 182
	if err != nil {
		return nil, err
	}

Jeromy's avatar
Jeromy committed
183
	b, err := n.Blocks.GetBlock(req.Context(), c)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
184 185 186
	if err != nil {
		return nil, err
	}
187

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
188 189 190
	log.Debugf("ipfs block: got block with key: %q", b.Key())
	return b, nil
}
Kevin Atkinson's avatar
Kevin Atkinson committed
191 192 193 194 195 196 197 198 199 200 201 202

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."),
	},
203 204 205 206
	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
207 208 209 210 211 212 213
	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()
214 215
		force, _, _ := req.Option("force").Bool()
		quiet, _, _ := req.Option("quiet").Bool()
Jeromy's avatar
Jeromy committed
216
		cids := make([]*cid.Cid, 0, len(hashes))
Kevin Atkinson's avatar
Kevin Atkinson committed
217
		for _, hash := range hashes {
Jeromy's avatar
Jeromy committed
218 219 220 221 222 223 224
			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
225
		}
226 227
		outChan := make(chan interface{})
		res.SetOutput((<-chan interface{})(outChan))
Kevin Atkinson's avatar
Kevin Atkinson committed
228
		go func() {
229
			defer close(outChan)
Kevin Atkinson's avatar
Kevin Atkinson committed
230
			pinning := n.Pinning
Jeromy's avatar
Jeromy committed
231
			err := rmBlocks(n.Blockstore, pinning, outChan, cids, rmBlocksOpts{
232 233 234
				quiet: quiet,
				force: force,
			})
235 236 237
			if err != nil {
				outChan <- &RemovedBlock{Error: err.Error()}
			}
Kevin Atkinson's avatar
Kevin Atkinson committed
238 239 240
		}()
		return
	},
241 242 243 244 245 246 247 248 249 250 251 252 253 254
	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)

		someFailed := false
		for out := range outChan {
			o := out.(*RemovedBlock)
255 256 257 258
			if o.Hash == "" && o.Error != "" {
				res.SetError(fmt.Errorf("aborted: %s", o.Error), cmds.ErrNormal)
				return
			} else if o.Error != "" {
259 260 261 262 263 264 265 266 267
				someFailed = true
				fmt.Fprintf(res.Stderr(), "cannot remove %s: %s\n", o.Hash, o.Error)
			} else {
				fmt.Fprintf(res.Stdout(), "removed %s\n", o.Hash)
			}
		}
		if someFailed {
			res.SetError(fmt.Errorf("some blocks not removed"), cmds.ErrNormal)
		}
Kevin Atkinson's avatar
Kevin Atkinson committed
268
	},
269 270 271 272
	Type: RemovedBlock{},
}

type RemovedBlock struct {
273
	Hash  string `json:",omitempty"`
274
	Error string `json:",omitempty"`
Kevin Atkinson's avatar
Kevin Atkinson committed
275 276
}

277 278
type rmBlocksOpts struct {
	quiet bool
Jeromy's avatar
Jeromy committed
279
	force bool
280 281
}

Jeromy's avatar
Jeromy committed
282
func rmBlocks(blocks bs.GCBlockstore, pins pin.Pinner, out chan<- interface{}, cids []*cid.Cid, opts rmBlocksOpts) error {
283 284 285
	unlocker := blocks.GCLock()
	defer unlocker.Unlock()

Jeromy's avatar
Jeromy committed
286
	stillOkay, err := checkIfPinned(pins, cids, out)
287 288
	if err != nil {
		return fmt.Errorf("pin check failed: %s", err)
Kevin Atkinson's avatar
Kevin Atkinson committed
289
	}
290

Jeromy's avatar
Jeromy committed
291 292
	for _, c := range stillOkay {
		err := blocks.DeleteBlock(key.Key(c.Hash()))
293 294 295
		if err != nil && opts.force && (err == bs.ErrNotFound || err == ds.ErrNotFound) {
			// ignore non-existent blocks
		} else if err != nil {
Jeromy's avatar
Jeromy committed
296
			out <- &RemovedBlock{Hash: c.String(), Error: err.Error()}
297
		} else if !opts.quiet {
Jeromy's avatar
Jeromy committed
298
			out <- &RemovedBlock{Hash: c.String()}
Kevin Atkinson's avatar
Kevin Atkinson committed
299 300
		}
	}
301
	return nil
Kevin Atkinson's avatar
Kevin Atkinson committed
302 303
}

Jeromy's avatar
Jeromy committed
304 305 306
func checkIfPinned(pins pin.Pinner, cids []*cid.Cid, out chan<- interface{}) ([]*cid.Cid, error) {
	stillOkay := make([]*cid.Cid, 0, len(cids))
	res, err := pins.CheckIfPinned(cids...)
307 308 309 310
	if err != nil {
		return nil, err
	}
	for _, r := range res {
311
		if !r.Pinned() {
312
			stillOkay = append(stillOkay, r.Key)
313
		} else {
314 315
			out <- &RemovedBlock{
				Hash:  r.Key.String(),
316 317
				Error: r.String(),
			}
Kevin Atkinson's avatar
Kevin Atkinson committed
318 319
		}
	}
320
	return stillOkay, nil
Kevin Atkinson's avatar
Kevin Atkinson committed
321
}