block.go 7.76 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
	key "github.com/ipfs/go-ipfs/blocks/key"
14
	cmds "github.com/ipfs/go-ipfs/commands"
Kevin Atkinson's avatar
Kevin Atkinson committed
15
	"github.com/ipfs/go-ipfs/pin"
16
	ds "gx/ipfs/QmTxLSvdhwg68WJimdS6icLPhZi28aTp6b7uihC2Yb47Xk/go-datastore"
Jeromy's avatar
Jeromy committed
17
	mh "gx/ipfs/QmYf7ng2hG5XBtJA3tN34DQ2GUN5HNksEw1rLDkmr6vGku/go-multihash"
18
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
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(),
73
			Size: len(b.Data()),
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
		}

104
		res.SetOutput(bytes.NewReader(b.Data()))
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 148 149

		k, err := n.Blocks.AddBlock(b)
		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
	}

178
	h, err := mh.FromB58String(skey)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
179 180 181 182
	if err != nil {
		return nil, err
	}

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

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

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."),
	},
204 205 206 207
	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
208 209 210 211 212 213 214
	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()
215 216
		force, _, _ := req.Option("force").Bool()
		quiet, _, _ := req.Option("quiet").Bool()
Kevin Atkinson's avatar
Kevin Atkinson committed
217 218 219 220 221
		keys := make([]key.Key, 0, len(hashes))
		for _, hash := range hashes {
			k := key.B58KeyDecode(hash)
			keys = append(keys, k)
		}
222 223
		outChan := make(chan interface{})
		res.SetOutput((<-chan interface{})(outChan))
Kevin Atkinson's avatar
Kevin Atkinson committed
224
		go func() {
225
			defer close(outChan)
Kevin Atkinson's avatar
Kevin Atkinson committed
226
			pinning := n.Pinning
227 228 229 230
			err := rmBlocks(n.Blockstore, pinning, outChan, keys, rmBlocksOpts{
				quiet: quiet,
				force: force,
			})
231 232 233
			if err != nil {
				outChan <- &RemovedBlock{Error: err.Error()}
			}
Kevin Atkinson's avatar
Kevin Atkinson committed
234 235 236
		}()
		return
	},
237 238 239 240 241 242 243 244 245 246 247 248 249 250
	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)
251 252 253 254
			if o.Hash == "" && o.Error != "" {
				res.SetError(fmt.Errorf("aborted: %s", o.Error), cmds.ErrNormal)
				return
			} else if o.Error != "" {
255 256 257 258 259 260 261 262 263
				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
264
	},
265 266 267 268
	Type: RemovedBlock{},
}

type RemovedBlock struct {
269
	Hash  string `json:",omitempty"`
270
	Error string `json:",omitempty"`
Kevin Atkinson's avatar
Kevin Atkinson committed
271 272
}

273 274 275 276 277 278
type rmBlocksOpts struct {
	quiet bool
	force bool	
}

func rmBlocks(blocks bs.GCBlockstore, pins pin.Pinner, out chan<- interface{}, keys []key.Key, opts rmBlocksOpts) error {
279 280 281 282 283 284
	unlocker := blocks.GCLock()
	defer unlocker.Unlock()

	stillOkay, err := checkIfPinned(pins, keys, out)
	if err != nil {
		return fmt.Errorf("pin check failed: %s", err)
Kevin Atkinson's avatar
Kevin Atkinson committed
285
	}
286

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

300
func checkIfPinned(pins pin.Pinner, keys []key.Key, out chan<- interface{}) ([]key.Key, error) {
301
	stillOkay := make([]key.Key, 0, len(keys))
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
	res, err := pins.CheckIfPinned(keys...)
	if err != nil {
		return nil, err
	}
	for _, r := range res {
		switch r.Mode {
		case pin.NotPinned:
			stillOkay = append(stillOkay, r.Key)
		case pin.Indirect:
			out <- &RemovedBlock{
				Hash:  r.Key.String(),
				Error: fmt.Sprintf("pinned via %s", r.Via)}
		default:
			modeStr, _ := pin.PinModeToString(r.Mode)
			out <- &RemovedBlock{
				Hash:  r.Key.String(),
				Error: fmt.Sprintf("pinned: %s", modeStr)}

Kevin Atkinson's avatar
Kevin Atkinson committed
320 321
		}
	}
322
	return stillOkay, nil
Kevin Atkinson's avatar
Kevin Atkinson committed
323
}