tangled
alpha
login
or
join now
atscan.net
/
plcbundle-go
1
fork
atom
[DEPRECATED] Go implementation of plcbundle
1
fork
atom
overview
issues
pulls
pipelines
cmd verify
tree.fail
4 months ago
99fde59a
9ada429c
+357
-61
2 changed files
expand all
collapse all
unified
split
cmd
plcbundle
commands
verify.go
main.go
+355
-59
cmd/plcbundle/commands/verify.go
···
3
import (
4
"context"
5
"fmt"
0
0
6
7
-
flag "github.com/spf13/pflag"
8
-
9
"tangled.org/atscan.net/plcbundle/bundle"
0
10
)
11
12
-
// VerifyCommand handles the verify subcommand
13
-
func VerifyCommand(args []string) error {
14
-
fs := flag.NewFlagSet("verify", flag.ExitOnError)
15
-
bundleNum := fs.Int("bundle", 0, "specific bundle to verify (0 = verify chain)")
16
-
verbose := fs.Bool("v", false, "verbose output")
0
0
0
17
18
-
if err := fs.Parse(args); err != nil {
19
-
return err
20
-
}
0
21
22
-
mgr, dir, err := getManager("")
23
-
if err != nil {
24
-
return err
25
-
}
26
-
defer mgr.Close()
27
28
-
fmt.Printf("Working in: %s\n", dir)
0
0
0
0
0
29
30
-
ctx := context.Background()
0
0
0
0
0
31
32
-
if *bundleNum > 0 {
33
-
return verifySingleBundle(ctx, mgr, *bundleNum, *verbose)
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
34
}
35
36
-
return verifyChain(ctx, mgr, *verbose)
0
0
0
0
0
0
37
}
38
39
func verifySingleBundle(ctx context.Context, mgr *bundle.Manager, bundleNum int, verbose bool) error {
40
-
fmt.Printf("Verifying bundle %06d...\n", bundleNum)
41
0
42
result, err := mgr.VerifyBundle(ctx, bundleNum)
0
0
43
if err != nil {
44
return fmt.Errorf("verification failed: %w", err)
45
}
46
47
if result.Valid {
48
-
fmt.Printf("✓ Bundle %06d is valid\n", bundleNum)
49
if verbose {
50
-
fmt.Printf(" File exists: %v\n", result.FileExists)
51
-
fmt.Printf(" Hash match: %v\n", result.HashMatch)
52
-
fmt.Printf(" Hash: %s...\n", result.LocalHash[:16])
0
0
53
}
54
return nil
55
}
56
57
-
fmt.Printf("✗ Bundle %06d is invalid\n", bundleNum)
58
if result.Error != nil {
59
-
fmt.Printf(" Error: %v\n", result.Error)
60
}
61
if !result.FileExists {
62
-
fmt.Printf(" File not found\n")
63
}
64
if !result.HashMatch && result.FileExists {
65
-
fmt.Printf(" Expected hash: %s...\n", result.ExpectedHash[:16])
66
-
fmt.Printf(" Actual hash: %s...\n", result.LocalHash[:16])
67
}
68
return fmt.Errorf("bundle verification failed")
69
}
···
73
bundles := index.GetBundles()
74
75
if len(bundles) == 0 {
76
-
fmt.Println("No bundles to verify")
77
return nil
78
}
79
80
-
fmt.Printf("Verifying chain of %d bundles...\n\n", len(bundles))
0
0
0
0
0
0
0
81
82
verifiedCount := 0
83
errorCount := 0
84
-
lastPercent := -1
85
86
for i, meta := range bundles {
87
bundleNum := meta.BundleNumber
88
89
-
percent := (i * 100) / len(bundles)
90
-
if percent != lastPercent || verbose {
91
-
if verbose {
92
-
fmt.Printf(" [%3d%%] Verifying bundle %06d...", percent, bundleNum)
93
-
} else if percent%10 == 0 && percent != lastPercent {
94
-
fmt.Printf(" [%3d%%] Verified %d/%d bundles...\n", percent, i, len(bundles))
95
-
}
96
-
lastPercent = percent
97
}
98
99
result, err := mgr.VerifyBundle(ctx, bundleNum)
100
if err != nil {
101
if verbose {
102
-
fmt.Printf(" ERROR\n")
103
}
104
-
fmt.Printf("\n✗ Failed to verify bundle %06d: %v\n", bundleNum, err)
105
errorCount++
0
0
0
106
continue
107
}
108
109
if !result.Valid {
110
if verbose {
111
-
fmt.Printf(" INVALID\n")
112
}
113
-
fmt.Printf("\n✗ Bundle %06d hash verification failed\n", bundleNum)
114
if result.Error != nil {
115
-
fmt.Printf(" Error: %v\n", result.Error)
0
0
0
116
}
117
errorCount++
118
continue
119
}
120
0
121
if i > 0 {
122
prevMeta := bundles[i-1]
123
if meta.Parent != prevMeta.Hash {
124
if verbose {
125
-
fmt.Printf(" CHAIN BROKEN\n")
126
}
127
-
fmt.Printf("\n✗ Chain broken at bundle %06d\n", bundleNum)
128
-
fmt.Printf(" Expected parent: %s...\n", prevMeta.Hash[:16])
129
-
fmt.Printf(" Actual parent: %s...\n", meta.Parent[:16])
130
errorCount++
0
0
0
131
continue
132
}
133
}
134
135
if verbose {
136
-
fmt.Printf(" ✓\n")
137
}
138
verifiedCount++
0
0
0
0
139
}
140
141
-
fmt.Println()
0
0
0
0
0
0
142
if errorCount == 0 {
143
-
fmt.Printf("✓ Chain is valid (%d bundles verified)\n", verifiedCount)
144
-
fmt.Printf(" First bundle: %06d\n", bundles[0].BundleNumber)
145
-
fmt.Printf(" Last bundle: %06d\n", bundles[len(bundles)-1].BundleNumber)
146
-
fmt.Printf(" Chain head: %s...\n", bundles[len(bundles)-1].Hash[:16])
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
147
return nil
148
}
149
150
-
fmt.Printf("✗ Chain verification failed\n")
151
-
fmt.Printf(" Verified: %d/%d bundles\n", verifiedCount, len(bundles))
152
-
fmt.Printf(" Errors: %d\n", errorCount)
0
153
return fmt.Errorf("chain verification failed")
154
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
3
import (
4
"context"
5
"fmt"
6
+
"os"
7
+
"time"
8
9
+
"github.com/spf13/cobra"
0
10
"tangled.org/atscan.net/plcbundle/bundle"
11
+
"tangled.org/atscan.net/plcbundle/cmd/plcbundle/ui"
12
)
13
14
+
func NewVerifyCommand() *cobra.Command {
15
+
var (
16
+
bundleNum int
17
+
rangeStr string
18
+
chain bool
19
+
parallel bool
20
+
workers int
21
+
)
22
23
+
cmd := &cobra.Command{
24
+
Use: "verify [flags]",
25
+
Short: "Verify bundle integrity and chain",
26
+
Long: `Verify bundle integrity and cryptographic chain
27
28
+
Verifies bundle file hashes, content integrity, and chain linkage.
29
+
By default, verifies the entire chain. You can verify specific bundles
30
+
or ranges using flags.
0
0
31
32
+
Verification checks:
33
+
• Compressed file hash matches index
34
+
• Content hash matches index
35
+
• File sizes match metadata
36
+
• Chain parent references are correct
37
+
• Chain hash calculations are valid`,
38
39
+
Example: ` # Verify entire chain
40
+
plcbundle verify
41
+
plcbundle verify --chain
42
+
43
+
# Verify specific bundle
44
+
plcbundle verify --bundle 42
45
46
+
# Verify range of bundles
47
+
plcbundle verify --range 1-100
48
+
49
+
# Verbose output
50
+
plcbundle verify --chain -v
51
+
52
+
# Parallel verification (faster for ranges)
53
+
plcbundle verify --range 1-1000 --parallel --workers 8`,
54
+
55
+
RunE: func(cmd *cobra.Command, args []string) error {
56
+
verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
57
+
58
+
mgr, dir, err := getManagerFromCommand(cmd, "")
59
+
if err != nil {
60
+
return err
61
+
}
62
+
defer mgr.Close()
63
+
64
+
if !verbose {
65
+
fmt.Fprintf(os.Stderr, "Working in: %s\n\n", dir)
66
+
}
67
+
68
+
ctx := context.Background()
69
+
70
+
// Determine what to verify
71
+
if rangeStr != "" {
72
+
return verifyRange(ctx, mgr, rangeStr, verbose, parallel, workers)
73
+
}
74
+
75
+
if bundleNum > 0 {
76
+
return verifySingleBundle(ctx, mgr, bundleNum, verbose)
77
+
}
78
+
79
+
// Default: verify entire chain
80
+
return verifyChain(ctx, mgr, verbose)
81
+
},
82
}
83
84
+
cmd.Flags().IntVarP(&bundleNum, "bundle", "b", 0, "Verify specific bundle number")
85
+
cmd.Flags().StringVarP(&rangeStr, "range", "r", "", "Verify bundle range (e.g., '1-100')")
86
+
cmd.Flags().BoolVarP(&chain, "chain", "c", false, "Verify entire chain (default)")
87
+
cmd.Flags().BoolVar(¶llel, "parallel", false, "Use parallel verification for ranges")
88
+
cmd.Flags().IntVar(&workers, "workers", 4, "Number of parallel workers")
89
+
90
+
return cmd
91
}
92
93
func verifySingleBundle(ctx context.Context, mgr *bundle.Manager, bundleNum int, verbose bool) error {
94
+
fmt.Fprintf(os.Stderr, "Verifying bundle %06d...\n", bundleNum)
95
96
+
start := time.Now()
97
result, err := mgr.VerifyBundle(ctx, bundleNum)
98
+
elapsed := time.Since(start)
99
+
100
if err != nil {
101
return fmt.Errorf("verification failed: %w", err)
102
}
103
104
if result.Valid {
105
+
fmt.Fprintf(os.Stderr, "✓ Bundle %06d is valid (%s)\n", bundleNum, elapsed.Round(time.Millisecond))
106
if verbose {
107
+
fmt.Fprintf(os.Stderr, "\nDetails:\n")
108
+
fmt.Fprintf(os.Stderr, " File exists: %v\n", result.FileExists)
109
+
fmt.Fprintf(os.Stderr, " Hash match: %v\n", result.HashMatch)
110
+
fmt.Fprintf(os.Stderr, " Hash: %s...%s\n", result.LocalHash[:16], result.LocalHash[len(result.LocalHash)-16:])
111
+
fmt.Fprintf(os.Stderr, " Verification time: %s\n", elapsed)
112
}
113
return nil
114
}
115
116
+
fmt.Fprintf(os.Stderr, "✗ Bundle %06d is invalid (%s)\n", bundleNum, elapsed.Round(time.Millisecond))
117
if result.Error != nil {
118
+
fmt.Fprintf(os.Stderr, " Error: %v\n", result.Error)
119
}
120
if !result.FileExists {
121
+
fmt.Fprintf(os.Stderr, " File not found\n")
122
}
123
if !result.HashMatch && result.FileExists {
124
+
fmt.Fprintf(os.Stderr, " Expected hash: %s...\n", result.ExpectedHash[:16])
125
+
fmt.Fprintf(os.Stderr, " Actual hash: %s...\n", result.LocalHash[:16])
126
}
127
return fmt.Errorf("bundle verification failed")
128
}
···
132
bundles := index.GetBundles()
133
134
if len(bundles) == 0 {
135
+
fmt.Fprintf(os.Stderr, "No bundles to verify\n")
136
return nil
137
}
138
139
+
fmt.Fprintf(os.Stderr, "Verifying chain of %d bundles...\n\n", len(bundles))
140
+
141
+
start := time.Now()
142
+
143
+
var progress *ui.ProgressBar
144
+
if !verbose {
145
+
progress = ui.NewProgressBar(len(bundles))
146
+
}
147
148
verifiedCount := 0
149
errorCount := 0
150
+
var firstError error
151
152
for i, meta := range bundles {
153
bundleNum := meta.BundleNumber
154
155
+
if verbose {
156
+
fmt.Fprintf(os.Stderr, " Verifying bundle %06d... ", bundleNum)
0
0
0
0
0
0
157
}
158
159
result, err := mgr.VerifyBundle(ctx, bundleNum)
160
if err != nil {
161
if verbose {
162
+
fmt.Fprintf(os.Stderr, "ERROR\n")
163
}
164
+
fmt.Fprintf(os.Stderr, "\n✗ Failed to verify bundle %06d: %v\n", bundleNum, err)
165
errorCount++
166
+
if firstError == nil {
167
+
firstError = err
168
+
}
169
continue
170
}
171
172
if !result.Valid {
173
if verbose {
174
+
fmt.Fprintf(os.Stderr, "INVALID\n")
175
}
176
+
fmt.Fprintf(os.Stderr, "\n✗ Bundle %06d hash verification failed\n", bundleNum)
177
if result.Error != nil {
178
+
fmt.Fprintf(os.Stderr, " Error: %v\n", result.Error)
179
+
if firstError == nil {
180
+
firstError = result.Error
181
+
}
182
}
183
errorCount++
184
continue
185
}
186
187
+
// Verify chain link
188
if i > 0 {
189
prevMeta := bundles[i-1]
190
if meta.Parent != prevMeta.Hash {
191
if verbose {
192
+
fmt.Fprintf(os.Stderr, "CHAIN BROKEN\n")
193
}
194
+
fmt.Fprintf(os.Stderr, "\n✗ Chain broken at bundle %06d\n", bundleNum)
195
+
fmt.Fprintf(os.Stderr, " Expected parent: %s...\n", prevMeta.Hash[:16])
196
+
fmt.Fprintf(os.Stderr, " Actual parent: %s...\n", meta.Parent[:16])
197
errorCount++
198
+
if firstError == nil {
199
+
firstError = fmt.Errorf("chain broken at bundle %d", bundleNum)
200
+
}
201
continue
202
}
203
}
204
205
if verbose {
206
+
fmt.Fprintf(os.Stderr, "✓\n")
207
}
208
verifiedCount++
209
+
210
+
if progress != nil {
211
+
progress.Set(i + 1)
212
+
}
213
}
214
215
+
if progress != nil {
216
+
progress.Finish()
217
+
}
218
+
219
+
elapsed := time.Since(start)
220
+
221
+
fmt.Fprintf(os.Stderr, "\n")
222
if errorCount == 0 {
223
+
fmt.Fprintf(os.Stderr, "✓ Chain is valid (%d bundles verified)\n", verifiedCount)
224
+
fmt.Fprintf(os.Stderr, " First bundle: %06d\n", bundles[0].BundleNumber)
225
+
fmt.Fprintf(os.Stderr, " Last bundle: %06d\n", bundles[len(bundles)-1].BundleNumber)
226
+
fmt.Fprintf(os.Stderr, " Chain head: %s...%s\n",
227
+
bundles[len(bundles)-1].Hash[:16],
228
+
bundles[len(bundles)-1].Hash[len(bundles[len(bundles)-1].Hash)-16:])
229
+
230
+
// Timing information
231
+
fmt.Fprintf(os.Stderr, "\nPerformance:\n")
232
+
fmt.Fprintf(os.Stderr, " Time: %s\n", elapsed.Round(time.Millisecond))
233
+
if elapsed.Seconds() > 0 {
234
+
bundlesPerSec := float64(verifiedCount) / elapsed.Seconds()
235
+
fmt.Fprintf(os.Stderr, " Throughput: %.1f bundles/sec\n", bundlesPerSec)
236
+
237
+
// Calculate data throughput
238
+
index := mgr.GetIndex()
239
+
stats := index.GetStats()
240
+
if totalSize, ok := stats["total_size"].(int64); ok && totalSize > 0 {
241
+
mbPerSec := float64(totalSize) / elapsed.Seconds() / (1024 * 1024)
242
+
fmt.Fprintf(os.Stderr, " Data rate: %.1f MB/sec (compressed)\n", mbPerSec)
243
+
}
244
+
}
245
return nil
246
}
247
248
+
fmt.Fprintf(os.Stderr, "✗ Chain verification failed\n")
249
+
fmt.Fprintf(os.Stderr, " Verified: %d/%d bundles\n", verifiedCount, len(bundles))
250
+
fmt.Fprintf(os.Stderr, " Errors: %d\n", errorCount)
251
+
fmt.Fprintf(os.Stderr, " Time: %s\n", elapsed.Round(time.Millisecond))
252
return fmt.Errorf("chain verification failed")
253
}
254
+
255
+
func verifyRange(ctx context.Context, mgr *bundle.Manager, rangeStr string, verbose bool, parallel bool, workers int) error {
256
+
start, end, err := parseBundleRange(rangeStr)
257
+
if err != nil {
258
+
return err
259
+
}
260
+
261
+
fmt.Fprintf(os.Stderr, "Verifying bundles %06d - %06d", start, end)
262
+
if parallel {
263
+
fmt.Fprintf(os.Stderr, " (parallel, %d workers)", workers)
264
+
}
265
+
fmt.Fprintf(os.Stderr, "\n\n")
266
+
267
+
total := end - start + 1
268
+
overallStart := time.Now()
269
+
270
+
var verifyErr error
271
+
if parallel {
272
+
verifyErr = verifyRangeParallel(ctx, mgr, start, end, workers, verbose)
273
+
} else {
274
+
verifyErr = verifyRangeSequential(ctx, mgr, start, end, total, verbose)
275
+
}
276
+
277
+
elapsed := time.Since(overallStart)
278
+
279
+
// Add timing summary
280
+
fmt.Fprintf(os.Stderr, "\nPerformance:\n")
281
+
fmt.Fprintf(os.Stderr, " Time: %s\n", elapsed.Round(time.Millisecond))
282
+
if elapsed.Seconds() > 0 {
283
+
bundlesPerSec := float64(total) / elapsed.Seconds()
284
+
fmt.Fprintf(os.Stderr, " Throughput: %.1f bundles/sec\n", bundlesPerSec)
285
+
286
+
// Estimate average time per bundle
287
+
avgTime := elapsed / time.Duration(total)
288
+
fmt.Fprintf(os.Stderr, " Avg/bundle: %s\n", avgTime.Round(time.Millisecond))
289
+
}
290
+
291
+
return verifyErr
292
+
}
293
+
294
+
func verifyRangeSequential(ctx context.Context, mgr *bundle.Manager, start, end, total int, verbose bool) error {
295
+
var progress *ui.ProgressBar
296
+
if !verbose {
297
+
progress = ui.NewProgressBar(total)
298
+
}
299
+
300
+
verified := 0
301
+
failed := 0
302
+
failedBundles := []int{}
303
+
304
+
for bundleNum := start; bundleNum <= end; bundleNum++ {
305
+
result, err := mgr.VerifyBundle(ctx, bundleNum)
306
+
307
+
if verbose {
308
+
fmt.Fprintf(os.Stderr, "Bundle %06d: ", bundleNum)
309
+
}
310
+
311
+
if err != nil {
312
+
if verbose {
313
+
fmt.Fprintf(os.Stderr, "ERROR - %v\n", err)
314
+
}
315
+
failed++
316
+
failedBundles = append(failedBundles, bundleNum)
317
+
} else if !result.Valid {
318
+
if verbose {
319
+
fmt.Fprintf(os.Stderr, "INVALID - %v\n", result.Error)
320
+
}
321
+
failed++
322
+
failedBundles = append(failedBundles, bundleNum)
323
+
} else {
324
+
if verbose {
325
+
fmt.Fprintf(os.Stderr, "✓\n")
326
+
}
327
+
verified++
328
+
}
329
+
330
+
if progress != nil {
331
+
progress.Set(bundleNum - start + 1)
332
+
}
333
+
}
334
+
335
+
if progress != nil {
336
+
progress.Finish()
337
+
}
338
+
339
+
fmt.Fprintf(os.Stderr, "\n")
340
+
if failed == 0 {
341
+
fmt.Fprintf(os.Stderr, "✓ All %d bundles verified successfully\n", verified)
342
+
return nil
343
+
}
344
+
345
+
fmt.Fprintf(os.Stderr, "✗ Verification failed\n")
346
+
fmt.Fprintf(os.Stderr, " Verified: %d/%d\n", verified, total)
347
+
fmt.Fprintf(os.Stderr, " Failed: %d\n", failed)
348
+
349
+
if len(failedBundles) > 0 && len(failedBundles) <= 20 {
350
+
fmt.Fprintf(os.Stderr, "\nFailed bundles: ")
351
+
for i, num := range failedBundles {
352
+
if i > 0 {
353
+
fmt.Fprintf(os.Stderr, ", ")
354
+
}
355
+
fmt.Fprintf(os.Stderr, "%06d", num)
356
+
}
357
+
fmt.Fprintf(os.Stderr, "\n")
358
+
} else if len(failedBundles) > 20 {
359
+
fmt.Fprintf(os.Stderr, "\nFailed bundles: %06d, %06d, ... and %d more\n",
360
+
failedBundles[0], failedBundles[1], len(failedBundles)-2)
361
+
}
362
+
363
+
return fmt.Errorf("verification failed for %d bundles", failed)
364
+
}
365
+
366
+
func verifyRangeParallel(ctx context.Context, mgr *bundle.Manager, start, end, workers int, verbose bool) error {
367
+
type job struct {
368
+
bundleNum int
369
+
}
370
+
371
+
type result struct {
372
+
bundleNum int
373
+
valid bool
374
+
err error
375
+
}
376
+
377
+
total := end - start + 1
378
+
jobs := make(chan job, total)
379
+
results := make(chan result, total)
380
+
381
+
// Start workers
382
+
for w := 0; w < workers; w++ {
383
+
go func() {
384
+
for j := range jobs {
385
+
vr, err := mgr.VerifyBundle(ctx, j.bundleNum)
386
+
results <- result{
387
+
bundleNum: j.bundleNum,
388
+
valid: err == nil && vr.Valid,
389
+
err: err,
390
+
}
391
+
}
392
+
}()
393
+
}
394
+
395
+
// Send jobs
396
+
for bundleNum := start; bundleNum <= end; bundleNum++ {
397
+
jobs <- job{bundleNum: bundleNum}
398
+
}
399
+
close(jobs)
400
+
401
+
// Collect results with progress
402
+
progress := ui.NewProgressBar(total)
403
+
verified := 0
404
+
failed := 0
405
+
failedBundles := []int{}
406
+
407
+
for i := 0; i < total; i++ {
408
+
res := <-results
409
+
410
+
if res.valid {
411
+
verified++
412
+
} else {
413
+
failed++
414
+
failedBundles = append(failedBundles, res.bundleNum)
415
+
if verbose {
416
+
fmt.Fprintf(os.Stderr, "\nBundle %06d: FAILED - %v\n", res.bundleNum, res.err)
417
+
}
418
+
}
419
+
420
+
progress.Set(i + 1)
421
+
}
422
+
423
+
progress.Finish()
424
+
425
+
fmt.Fprintf(os.Stderr, "\n")
426
+
if failed == 0 {
427
+
fmt.Fprintf(os.Stderr, "✓ All %d bundles verified successfully\n", verified)
428
+
return nil
429
+
}
430
+
431
+
fmt.Fprintf(os.Stderr, "✗ Verification failed\n")
432
+
fmt.Fprintf(os.Stderr, " Verified: %d/%d\n", verified, total)
433
+
fmt.Fprintf(os.Stderr, " Failed: %d\n", failed)
434
+
435
+
if len(failedBundles) > 0 && len(failedBundles) <= 20 {
436
+
fmt.Fprintf(os.Stderr, "\nFailed bundles: ")
437
+
for i, num := range failedBundles {
438
+
if i > 0 {
439
+
fmt.Fprintf(os.Stderr, ", ")
440
+
}
441
+
fmt.Fprintf(os.Stderr, "%06d", num)
442
+
}
443
+
fmt.Fprintf(os.Stderr, "\n")
444
+
} else if len(failedBundles) > 20 {
445
+
fmt.Fprintf(os.Stderr, "\nFailed bundles: %06d, %06d, ... and %d more\n",
446
+
failedBundles[0], failedBundles[1], len(failedBundles)-2)
447
+
}
448
+
449
+
return fmt.Errorf("verification failed for %d bundles", failed)
450
+
}
+2
-2
cmd/plcbundle/main.go
···
55
// Status & info (root level)
56
cmd.AddCommand(commands.NewStatusCommand())
57
/*cmd.AddCommand(commands.NewLogCommand())
58
-
cmd.AddCommand(commands.NewGapsCommand())
59
cmd.AddCommand(commands.NewVerifyCommand())
60
-
cmd.AddCommand(commands.NewDiffCommand())
61
cmd.AddCommand(commands.NewStatsCommand())
62
cmd.AddCommand(commands.NewInspectCommand())
63
···
55
// Status & info (root level)
56
cmd.AddCommand(commands.NewStatusCommand())
57
/*cmd.AddCommand(commands.NewLogCommand())
58
+
cmd.AddCommand(commands.NewGapsCommand())*/
59
cmd.AddCommand(commands.NewVerifyCommand())
60
+
/*cmd.AddCommand(commands.NewDiffCommand())
61
cmd.AddCommand(commands.NewStatsCommand())
62
cmd.AddCommand(commands.NewInspectCommand())
63