block.go 7.22 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"
Jeromy's avatar
Jeromy committed
16
	mh "gx/ipfs/QmYf7ng2hG5XBtJA3tN34DQ2GUN5HNksEw1rLDkmr6vGku/go-multihash"
17
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
18 19
)

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

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

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

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

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

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

57 58
`,
	},
59

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

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

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

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

103
		res.SetOutput(bytes.NewReader(b.Data()))
104 105 106 107
	},
}

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

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

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

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

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

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

		k, err := n.Blocks.AddBlock(b)
		if err != nil {
149 150
			res.SetError(err, cmds.ErrNormal)
			return
151 152
		}

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

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

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

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

182
	k := key.Key(h)
Jeromy's avatar
Jeromy committed
183
	b, err := n.Blocks.GetBlock(req.Context(), k)
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 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222

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."),
	},
	Options: []cmds.Option{
		cmds.BoolOption("ignore-pins", "Ignore pins.").Default(false),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		ignorePins, _, err := req.Option("ignore-pins").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		n, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		hashes := req.Arguments()
		keys := make([]key.Key, 0, len(hashes))
		for _, hash := range hashes {
			k := key.B58KeyDecode(hash)
			keys = append(keys, k)
		}
223 224
		outChan := make(chan interface{})
		res.SetOutput((<-chan interface{})(outChan))
Kevin Atkinson's avatar
Kevin Atkinson committed
225
		go func() {
226
			defer close(outChan)
Kevin Atkinson's avatar
Kevin Atkinson committed
227 228 229 230
			pinning := n.Pinning
			if ignorePins {
				pinning = nil
			}
231
			rmBlocks(n.Blockstore, pinning, outChan, keys)
Kevin Atkinson's avatar
Kevin Atkinson committed
232 233 234
		}()
		return
	},
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
	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)
			if o.Error != "" {
				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
259
	},
260 261 262 263 264 265
	Type: RemovedBlock{},
}

type RemovedBlock struct {
	Hash string
	Error string `json:",omitempty"`
Kevin Atkinson's avatar
Kevin Atkinson committed
266 267 268
}

// pins may be nil
269
func rmBlocks(blocks bs.GCBlockstore, pins pin.Pinner, out chan<- interface{}, keys []key.Key) {
Kevin Atkinson's avatar
Kevin Atkinson committed
270 271 272 273 274 275
	var unlocker bs.Unlocker
	defer func() {
		if unlocker != nil {
			unlocker.Unlock()
		}
	}()
276
	stillOkay := keys
Kevin Atkinson's avatar
Kevin Atkinson committed
277 278 279 280
	if pins != nil {
		// Need to make sure that some operation that is
		// finishing with a pin is ocurr simultaneously.
		unlocker = blocks.GCLock()
281
		stillOkay = checkIfPinned(pins, keys, out)
Kevin Atkinson's avatar
Kevin Atkinson committed
282
	}
283
	for _, k := range stillOkay {
Kevin Atkinson's avatar
Kevin Atkinson committed
284 285
		err := blocks.DeleteBlock(k)
		if err != nil {
286 287 288
			out <- &RemovedBlock{ Hash: k.String(), Error: err.Error()}
		} else {
			out <- &RemovedBlock{ Hash: k.String() }
Kevin Atkinson's avatar
Kevin Atkinson committed
289 290 291 292
		}
	}
}

293 294
func checkIfPinned(pins pin.Pinner, keys []key.Key, out chan<- interface{}) []key.Key {
	stillOkay := make([]key.Key, 0, len(keys))
Kevin Atkinson's avatar
Kevin Atkinson committed
295 296 297
	for _, k := range keys {
		reason, pinned, err := pins.IsPinned(k)
		if err != nil {
298 299 300 301 302 303 304 305 306
			out <- &RemovedBlock {
				Hash: k.String(),
				Error: fmt.Sprintf("pin check failed %s", err.Error()) }
		} else if pinned {
			out <- &RemovedBlock {
				Hash: k.String(),
				Error: fmt.Sprintf("pinned via %s", reason) }
		} else {
			stillOkay = append(stillOkay, k)
Kevin Atkinson's avatar
Kevin Atkinson committed
307 308
		}
	}
309
	return stillOkay
Kevin Atkinson's avatar
Kevin Atkinson committed
310
}