master
go 350 lines 8.98 KB
Raw
1 package commands
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "io"
8 "strings"
9
10 cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
11 "github.com/ipfs/kubo/core/commands/cmdutils"
12
13 merkledag "github.com/ipfs/boxo/ipld/merkledag"
14 cid "github.com/ipfs/go-cid"
15 cidenc "github.com/ipfs/go-cidutil/cidenc"
16 cmds "github.com/ipfs/go-ipfs-cmds"
17 ipld "github.com/ipfs/go-ipld-format"
18 iface "github.com/ipfs/kubo/core/coreiface"
19 )
20
21 var refsEncoderMap = cmds.EncoderMap{
22 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *RefWrapper) error {
23 if out.Err != "" {
24 return errors.New(out.Err)
25 }
26 fmt.Fprintln(w, out.Ref)
27
28 return nil
29 }),
30 }
31
32 // KeyList is a general type for outputting lists of keys
33 type KeyList struct {
34 Keys []cid.Cid
35 }
36
37 const (
38 refsFormatOptionName = "format"
39 refsEdgesOptionName = "edges"
40 refsUniqueOptionName = "unique"
41 refsRecursiveOptionName = "recursive"
42 refsMaxDepthOptionName = "max-depth"
43 )
44
45 // RefsCmd is the `ipfs refs` command
46 var RefsCmd = &cmds.Command{
47 Helptext: cmds.HelpText{
48 Tagline: "List links (references) from an object.",
49 ShortDescription: `
50 Lists the hashes of all the links an IPFS or IPNS object(s) contains,
51 with the following format:
52
53 <link base58 hash>
54
55 List all references recursively by using the flag '-r'.
56
57 NOTE: Like most other commands, Kubo will try to fetch the blocks of the passed path if they can't be found in the local store if it is running in online mode.
58 `,
59 },
60 Subcommands: map[string]*cmds.Command{
61 "local": RefsLocalCmd,
62 },
63 Arguments: []cmds.Argument{
64 cmds.StringArg("ipfs-path", true, true, "Path to the object(s) to list refs from.").EnableStdin(),
65 },
66 Options: []cmds.Option{
67 cmds.StringOption(refsFormatOptionName, "Emit edges with given format. Available tokens: <src> <dst> <linkname>.").WithDefault("<dst>"),
68 cmds.BoolOption(refsEdgesOptionName, "e", "Emit edge format: `<from> -> <to>`."),
69 cmds.BoolOption(refsUniqueOptionName, "u", "Omit duplicate refs from output."),
70 cmds.BoolOption(refsRecursiveOptionName, "r", "Recursively list links of child nodes."),
71 cmds.IntOption(refsMaxDepthOptionName, "Only for recursive refs, limits fetch and listing to the given depth").WithDefault(-1),
72 },
73 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
74 err := req.ParseBodyArgs()
75 if err != nil {
76 return err
77 }
78
79 ctx := req.Context
80 api, err := cmdenv.GetApi(env, req)
81 if err != nil {
82 return err
83 }
84
85 enc, err := cmdenv.GetCidEncoder(req)
86 if err != nil {
87 return err
88 }
89
90 unique, _ := req.Options[refsUniqueOptionName].(bool)
91 recursive, _ := req.Options[refsRecursiveOptionName].(bool)
92 maxDepth, _ := req.Options[refsMaxDepthOptionName].(int)
93 edges, _ := req.Options[refsEdgesOptionName].(bool)
94 format, _ := req.Options[refsFormatOptionName].(string)
95
96 if !recursive {
97 maxDepth = 1 // write only direct refs
98 }
99
100 if edges {
101 if format != "<dst>" {
102 return errors.New("using format argument with edges is not allowed")
103 }
104
105 format = "<src> -> <dst>"
106 }
107
108 // TODO: use session for resolving as well.
109 objs, err := objectsForPaths(ctx, api, req.Arguments)
110 if err != nil {
111 return err
112 }
113
114 rw := RefWriter{
115 res: res,
116 DAG: merkledag.NewSession(ctx, api.Dag()),
117 Ctx: ctx,
118 Unique: unique,
119 PrintFmt: format,
120 MaxDepth: maxDepth,
121 }
122
123 for _, o := range objs {
124 if _, err := rw.WriteRefs(o, enc); err != nil {
125 if err := res.Emit(&RefWrapper{Err: err.Error()}); err != nil {
126 return err
127 }
128 }
129 }
130
131 return nil
132 },
133 Encoders: refsEncoderMap,
134 Type: RefWrapper{},
135 }
136
137 var RefsLocalCmd = &cmds.Command{
138 Helptext: cmds.HelpText{
139 Tagline: "List all local references.",
140 ShortDescription: `
141 Displays the hashes of all local objects. NOTE: This treats all local objects as "raw blocks" and returns CIDv1-Raw CIDs.
142 `,
143 },
144
145 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
146 ctx := req.Context
147 n, err := cmdenv.GetNode(env)
148 if err != nil {
149 return err
150 }
151
152 enc, err := cmdenv.GetCidEncoder(req)
153 if err != nil {
154 return err
155 }
156
157 // todo: make async
158 allKeys, err := n.Blockstore.AllKeysChan(ctx)
159 if err != nil {
160 return err
161 }
162
163 for k := range allKeys {
164 err := res.Emit(&RefWrapper{Ref: enc.Encode(k)})
165 if err != nil {
166 return err
167 }
168 }
169
170 return nil
171 },
172 Encoders: refsEncoderMap,
173 Type: RefWrapper{},
174 }
175
176 func objectsForPaths(ctx context.Context, n iface.CoreAPI, paths []string) ([]cid.Cid, error) {
177 roots := make([]cid.Cid, len(paths))
178 for i, sp := range paths {
179 p, err := cmdutils.PathOrCidPath(sp)
180 if err != nil {
181 return nil, err
182 }
183 o, _, err := n.ResolvePath(ctx, p)
184 if err != nil {
185 return nil, err
186 }
187 roots[i] = o.RootCid()
188 }
189 return roots, nil
190 }
191
192 type RefWrapper struct {
193 Ref string
194 Err string
195 }
196
197 type RefWriter struct {
198 res cmds.ResponseEmitter
199 DAG ipld.NodeGetter
200 Ctx context.Context
201
202 Unique bool
203 MaxDepth int
204 PrintFmt string
205
206 seen map[string]int
207 }
208
209 // WriteRefs writes refs of the given object to the underlying writer.
210 func (rw *RefWriter) WriteRefs(c cid.Cid, enc cidenc.Encoder) (int, error) {
211 n, err := rw.DAG.Get(rw.Ctx, c)
212 if err != nil {
213 return 0, err
214 }
215 return rw.writeRefsRecursive(n, 0, enc)
216 }
217
218 func (rw *RefWriter) writeRefsRecursive(n ipld.Node, depth int, enc cidenc.Encoder) (int, error) {
219 nc := n.Cid()
220
221 var count int
222 for i, ng := range ipld.GetDAG(rw.Ctx, rw.DAG, n) {
223 lc := n.Links()[i].Cid
224 goDeeper, shouldWrite := rw.visit(lc, depth+1) // The children are at depth+1
225
226 // Avoid "Get()" on the node and continue with next Link.
227 // We can do this if:
228 // - We printed it before (thus it was already seen and
229 // fetched with Get()
230 // - AND we must not go deeper.
231 // This is an optimization for pruned branches which have been
232 // visited before.
233 if !shouldWrite && !goDeeper {
234 continue
235 }
236
237 // We must Get() the node because:
238 // - it is new (never written)
239 // - OR we need to go deeper.
240 // This ensures printed refs are always fetched.
241 nd, err := ng.Get(rw.Ctx)
242 if err != nil {
243 return count, err
244 }
245
246 // Write this node if not done before (or !Unique)
247 if shouldWrite {
248 if err := rw.WriteEdge(nc, lc, n.Links()[i].Name, enc); err != nil {
249 return count, err
250 }
251 count++
252 }
253
254 // Keep going deeper. This happens:
255 // - On unexplored branches
256 // - On branches not explored deep enough
257 // Note when !Unique, branches are always considered
258 // unexplored and only depth limits apply.
259 if goDeeper {
260 c, err := rw.writeRefsRecursive(nd, depth+1, enc)
261 count += c
262 if err != nil {
263 return count, err
264 }
265 }
266 }
267
268 return count, nil
269 }
270
271 // visit returns two values:
272 // - the first boolean is true if we should keep traversing the DAG
273 // - the second boolean is true if we should print the CID
274 //
275 // visit will do branch pruning depending on rw.MaxDepth, previously visited
276 // cids and whether rw.Unique is set. i.e. rw.Unique = false and
277 // rw.MaxDepth = -1 disables any pruning. But setting rw.Unique to true will
278 // prune already visited branches at the cost of keeping as set of visited
279 // CIDs in memory.
280 func (rw *RefWriter) visit(c cid.Cid, depth int) (bool, bool) {
281 atMaxDepth := rw.MaxDepth >= 0 && depth == rw.MaxDepth
282 overMaxDepth := rw.MaxDepth >= 0 && depth > rw.MaxDepth
283
284 // Shortcut when we are over max depth. In practice, this
285 // only applies when calling refs with --maxDepth=0, as root's
286 // children are already over max depth. Otherwise nothing should
287 // hit this.
288 if overMaxDepth {
289 return false, false
290 }
291
292 // We can shortcut right away if we don't need unique output:
293 // - we keep traversing when not atMaxDepth
294 // - always print
295 if !rw.Unique {
296 return !atMaxDepth, true
297 }
298
299 // Unique == true from this point.
300 // Thus, we keep track of seen Cids, and their depth.
301 if rw.seen == nil {
302 rw.seen = make(map[string]int)
303 }
304 key := string(c.Bytes())
305 oldDepth, ok := rw.seen[key]
306
307 // Unique == true && depth < MaxDepth (or unlimited) from this point
308
309 // Branch pruning cases:
310 // - We saw the Cid before and either:
311 // - Depth is unlimited (MaxDepth = -1)
312 // - We saw it higher (smaller depth) in the DAG (means we must have
313 // explored deep enough before)
314 // Because we saw the CID, we don't print it again.
315 if ok && (rw.MaxDepth < 0 || oldDepth <= depth) {
316 return false, false
317 }
318
319 // Final case, we must keep exploring the DAG from this CID
320 // (unless we hit the depth limit).
321 // We note down its depth because it was either not seen
322 // or is lower than last time.
323 // We print if it was not seen.
324 rw.seen[key] = depth
325 return !atMaxDepth, !ok
326 }
327
328 // Write one edge
329 func (rw *RefWriter) WriteEdge(from, to cid.Cid, linkname string, enc cidenc.Encoder) error {
330 if rw.Ctx != nil {
331 select {
332 case <-rw.Ctx.Done(): // just in case.
333 return rw.Ctx.Err()
334 default:
335 }
336 }
337
338 var s string
339 switch {
340 case rw.PrintFmt != "":
341 s = rw.PrintFmt
342 s = strings.Replace(s, "<src>", enc.Encode(from), -1)
343 s = strings.Replace(s, "<dst>", enc.Encode(to), -1)
344 s = strings.Replace(s, "<linkname>", linkname, -1)
345 default:
346 s += enc.Encode(to)
347 }
348
349 return rw.res.Emit(&RefWrapper{Ref: s})
350 }