block.go 7.16 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

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

type RemovedBlock struct {
259
	Hash  string `json:",omitempty"`
260
	Error string `json:",omitempty"`
Kevin Atkinson's avatar
Kevin Atkinson committed
261 262
}

263
func rmBlocks(blocks bs.GCBlockstore, pins pin.Pinner, out chan<- interface{}, keys []key.Key) error {
264 265 266 267 268 269
	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
270
	}
271

272
	for _, k := range stillOkay {
Kevin Atkinson's avatar
Kevin Atkinson committed
273 274
		err := blocks.DeleteBlock(k)
		if err != nil {
275
			out <- &RemovedBlock{Hash: k.String(), Error: err.Error()}
276
		} else {
277
			out <- &RemovedBlock{Hash: k.String()}
Kevin Atkinson's avatar
Kevin Atkinson committed
278 279
		}
	}
280
	return nil
Kevin Atkinson's avatar
Kevin Atkinson committed
281 282
}

283
func checkIfPinned(pins pin.Pinner, keys []key.Key, out chan<- interface{}) ([]key.Key, error) {
284
	stillOkay := make([]key.Key, 0, len(keys))
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
	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
303 304
		}
	}
305
	return stillOkay, nil
Kevin Atkinson's avatar
Kevin Atkinson committed
306
}