master
go 363 lines 8.78 KB
Raw
1 package commands
2
3 import (
4 "errors"
5 "fmt"
6 "io"
7 "os"
8
9 "github.com/ipfs/boxo/files"
10
11 "github.com/ipfs/kubo/config"
12 cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
13 "github.com/ipfs/kubo/core/commands/cmdutils"
14
15 options "github.com/ipfs/kubo/core/coreiface/options"
16
17 cmds "github.com/ipfs/go-ipfs-cmds"
18 mh "github.com/multiformats/go-multihash"
19 )
20
21 type BlockStat struct {
22 Key string
23 Size int
24 }
25
26 func (bs BlockStat) String() string {
27 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 ShortDescription: `
34 'ipfs block' is a plumbing command used to manipulate raw IPFS blocks.
35 Reads from stdin or writes to stdout. A block is identified by a Multihash
36 passed with a valid CID.
37 `,
38 },
39
40 Subcommands: map[string]*cmds.Command{
41 "stat": blockStatCmd,
42 "get": blockGetCmd,
43 "put": blockPutCmd,
44 "rm": blockRmCmd,
45 },
46 }
47
48 var blockStatCmd = &cmds.Command{
49 Helptext: cmds.HelpText{
50 Tagline: "Print information of a raw IPFS block.",
51 ShortDescription: `
52 'ipfs block stat' is a plumbing command for retrieving information
53 on raw IPFS blocks. It outputs the following to stdout:
54
55 Key - the CID of the block
56 Size - the size of the block in bytes
57
58 `,
59 },
60
61 Arguments: []cmds.Argument{
62 cmds.StringArg("cid", true, false, "The CID of an existing block to stat.").EnableStdin(),
63 },
64 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
65 api, err := cmdenv.GetApi(env, req)
66 if err != nil {
67 return err
68 }
69
70 enc, err := cmdenv.GetCidEncoder(req)
71 if err != nil {
72 return err
73 }
74
75 p, err := cmdutils.PathOrCidPath(req.Arguments[0])
76 if err != nil {
77 return err
78 }
79
80 b, err := api.Block().Stat(req.Context, p)
81 if err != nil {
82 return err
83 }
84
85 return cmds.EmitOnce(res, &BlockStat{
86 Key: enc.Encode(b.Path().RootCid()),
87 Size: b.Size(),
88 })
89 },
90 Type: BlockStat{},
91 Encoders: cmds.EncoderMap{
92 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, bs *BlockStat) error {
93 _, err := fmt.Fprintf(w, "%s", bs)
94 return err
95 }),
96 },
97 }
98
99 var blockGetCmd = &cmds.Command{
100 Helptext: cmds.HelpText{
101 Tagline: "Get a raw IPFS block.",
102 ShortDescription: `
103 'ipfs block get' is a plumbing command for retrieving raw IPFS blocks.
104 It takes a <cid>, and outputs the block to stdout.
105 `,
106 HTTP: &cmds.HTTPHelpText{
107 ResponseContentType: "application/vnd.ipld.raw",
108 },
109 },
110
111 Arguments: []cmds.Argument{
112 cmds.StringArg("cid", true, false, "The CID of an existing block to get.").EnableStdin(),
113 },
114 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
115 api, err := cmdenv.GetApi(env, req)
116 if err != nil {
117 return err
118 }
119
120 p, err := cmdutils.PathOrCidPath(req.Arguments[0])
121 if err != nil {
122 return err
123 }
124
125 r, err := api.Block().Get(req.Context, p)
126 if err != nil {
127 return err
128 }
129
130 res.SetEncodingType(cmds.OctetStream)
131 res.SetContentType("application/vnd.ipld.raw")
132 return res.Emit(r)
133 },
134 }
135
136 const (
137 blockFormatOptionName = "format"
138 blockCidCodecOptionName = "cid-codec"
139 mhtypeOptionName = "mhtype"
140 mhlenOptionName = "mhlen"
141 )
142
143 var blockPutCmd = &cmds.Command{
144 Helptext: cmds.HelpText{
145 Tagline: "Store input as an IPFS block.",
146 ShortDescription: `
147 'ipfs block put' is a plumbing command for storing raw IPFS blocks.
148 It reads data from stdin, and outputs the block's CID to stdout.
149
150 Unless cid-codec is specified, this command returns raw (0x55) CIDv1 CIDs.
151
152 Passing alternative --cid-codec does not modify imported data, nor run any
153 validation. It is provided solely for convenience for users who create blocks
154 in userland.
155
156 NOTE:
157 Do not use --format for any new code. It got superseded by --cid-codec and left
158 only for backward compatibility when a legacy CIDv0 is required (--format=v0).
159 `,
160 },
161
162 Arguments: []cmds.Argument{
163 cmds.FileArg("data", true, true, "The data to be stored as an IPFS block.").EnableStdin(),
164 },
165 Options: []cmds.Option{
166 cmds.StringOption(blockCidCodecOptionName, "Multicodec to use in returned CID").WithDefault("raw"),
167 cmds.StringOption(mhtypeOptionName, "Multihash hash function"),
168 cmds.IntOption(mhlenOptionName, "Multihash hash length").WithDefault(-1),
169 cmds.BoolOption(pinOptionName, "Pin added blocks recursively").WithDefault(false),
170 cmdutils.AllowBigBlockOption,
171 cmds.StringOption(blockFormatOptionName, "f", "Use legacy format for returned CID (DEPRECATED)"),
172 },
173 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
174 api, err := cmdenv.GetApi(env, req)
175 if err != nil {
176 return err
177 }
178
179 enc, err := cmdenv.GetCidEncoder(req)
180 if err != nil {
181 return err
182 }
183
184 nd, err := cmdenv.GetNode(env)
185 if err != nil {
186 return err
187 }
188
189 cfg, err := nd.Repo.Config()
190 if err != nil {
191 return err
192 }
193
194 mhtype, _ := req.Options[mhtypeOptionName].(string)
195 if mhtype == "" {
196 mhtype = cfg.Import.HashFunction.WithDefault(config.DefaultHashFunction)
197 }
198
199 mhtval, ok := mh.Names[mhtype]
200 if !ok {
201 return fmt.Errorf("unrecognized multihash function: %s", mhtype)
202 }
203
204 mhlen, ok := req.Options[mhlenOptionName].(int)
205 if !ok {
206 return errors.New("missing option \"mhlen\"")
207 }
208
209 cidCodec, _ := req.Options[blockCidCodecOptionName].(string)
210 format, _ := req.Options[blockFormatOptionName].(string) // deprecated
211
212 // use of legacy 'format' needs to suppress 'cid-codec'
213 if format != "" {
214 if cidCodec != "" && cidCodec != "raw" {
215 return fmt.Errorf("unable to use %q (deprecated) and a custom %q at the same time", blockFormatOptionName, blockCidCodecOptionName)
216 }
217 cidCodec = "" // makes it no-op
218 }
219
220 pin, _ := req.Options[pinOptionName].(bool)
221
222 it := req.Files.Entries()
223 for it.Next() {
224 file := files.FileFromEntry(it)
225 if file == nil {
226 return errors.New("expected a file")
227 }
228
229 p, err := api.Block().Put(req.Context, file,
230 options.Block.Hash(mhtval, mhlen),
231 options.Block.CidCodec(cidCodec),
232 options.Block.Format(format),
233 options.Block.Pin(pin))
234 if err != nil {
235 return err
236 }
237
238 if err := cmdutils.CheckBlockSize(req, uint64(p.Size())); err != nil {
239 return err
240 }
241
242 err = res.Emit(&BlockStat{
243 Key: enc.Encode(p.Path().RootCid()),
244 Size: p.Size(),
245 })
246 if err != nil {
247 return err
248 }
249 }
250
251 return it.Err()
252 },
253 Encoders: cmds.EncoderMap{
254 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, bs *BlockStat) error {
255 _, err := fmt.Fprintf(w, "%s\n", bs.Key)
256 return err
257 }),
258 },
259 Type: BlockStat{},
260 }
261
262 const (
263 forceOptionName = "force"
264 blockQuietOptionName = "quiet"
265 )
266
267 type removedBlock struct {
268 Hash string `json:",omitempty"`
269 Error string `json:",omitempty"`
270 }
271
272 var blockRmCmd = &cmds.Command{
273 Helptext: cmds.HelpText{
274 Tagline: "Remove IPFS block(s) from the local datastore.",
275 ShortDescription: `
276 'ipfs block rm' is a plumbing command for removing raw ipfs blocks.
277 It takes a list of CIDs to remove from the local datastore..
278 `,
279 },
280 Arguments: []cmds.Argument{
281 cmds.StringArg("cid", true, true, "CIDs of block(s) to remove."),
282 },
283 Options: []cmds.Option{
284 cmds.BoolOption(forceOptionName, "f", "Ignore nonexistent blocks."),
285 cmds.BoolOption(blockQuietOptionName, "q", "Write minimal output."),
286 },
287 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
288 api, err := cmdenv.GetApi(env, req)
289 if err != nil {
290 return err
291 }
292
293 enc, err := cmdenv.GetCidEncoder(req)
294 if err != nil {
295 return err
296 }
297
298 force, _ := req.Options[forceOptionName].(bool)
299 quiet, _ := req.Options[blockQuietOptionName].(bool)
300
301 // TODO: use batching coreapi when done
302 for _, b := range req.Arguments {
303 p, err := cmdutils.PathOrCidPath(b)
304 if err != nil {
305 return err
306 }
307
308 rp, _, err := api.ResolvePath(req.Context, p)
309 if err != nil {
310 return err
311 }
312
313 err = api.Block().Rm(req.Context, rp, options.Block.Force(force))
314 if err != nil {
315 if err := res.Emit(&removedBlock{
316 Hash: enc.Encode(rp.RootCid()),
317 Error: err.Error(),
318 }); err != nil {
319 return err
320 }
321 continue
322 }
323
324 if !quiet {
325 err := res.Emit(&removedBlock{
326 Hash: enc.Encode(rp.RootCid()),
327 })
328 if err != nil {
329 return err
330 }
331 }
332 }
333
334 return nil
335 },
336 PostRun: cmds.PostRunMap{
337 cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
338 someFailed := false
339 for {
340 res, err := res.Next()
341 if err == io.EOF {
342 break
343 } else if err != nil {
344 return err
345 }
346 r := res.(*removedBlock)
347 if r.Hash == "" && r.Error != "" {
348 return fmt.Errorf("aborted: %s", r.Error)
349 } else if r.Error != "" {
350 someFailed = true
351 fmt.Fprintf(os.Stderr, "cannot remove %s: %s\n", r.Hash, r.Error)
352 } else {
353 fmt.Fprintf(os.Stdout, "removed %s\n", r.Hash)
354 }
355 }
356 if someFailed {
357 return fmt.Errorf("some blocks not removed")
358 }
359 return nil
360 },
361 },
362 Type: removedBlock{},
363 }