github.com/google/osv-scalibr@v0.4.1/binary/cli/cli.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package cli defines the structures to store the CLI flags used by the scanner binary. 16 package cli 17 18 import ( 19 "errors" 20 "fmt" 21 "os" 22 "regexp" 23 "slices" 24 "strings" 25 26 "github.com/gobwas/glob" 27 "github.com/google/go-containerregistry/pkg/authn" 28 v1 "github.com/google/go-containerregistry/pkg/v1" 29 "github.com/google/go-containerregistry/pkg/v1/remote" 30 scalibr "github.com/google/osv-scalibr" 31 scalibrimage "github.com/google/osv-scalibr/artifact/image" 32 "github.com/google/osv-scalibr/binary/cdx" 33 "github.com/google/osv-scalibr/binary/platform" 34 "github.com/google/osv-scalibr/binary/proto" 35 binspdx "github.com/google/osv-scalibr/binary/spdx" 36 "github.com/google/osv-scalibr/converter" 37 convspdx "github.com/google/osv-scalibr/converter/spdx" 38 "github.com/google/osv-scalibr/detector" 39 "github.com/google/osv-scalibr/extractor/filesystem" 40 scalibrfs "github.com/google/osv-scalibr/fs" 41 "github.com/google/osv-scalibr/log" 42 "github.com/google/osv-scalibr/plugin" 43 pl "github.com/google/osv-scalibr/plugin/list" 44 "github.com/spdx/tools-golang/spdx/v2/common" 45 "google.golang.org/protobuf/encoding/prototext" 46 47 cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto" 48 ) 49 50 // Array is a type to be passed to flag.Var that supports arrays passed as repeated flags, 51 // e.g. ./scalibr -o binproto=out.bp -o spdx23-json=out.spdx.json 52 type Array []string 53 54 func (i *Array) String() string { 55 return strings.Join(*i, ",") 56 } 57 58 // Set gets called whenever a new instance of a flag is read during CLI arg parsing. 59 // For example, in the case of -o foo -o bar the library will call arr.Set("foo") then arr.Set("bar"). 60 func (i *Array) Set(value string) error { 61 *i = append(*i, strings.TrimSpace(value)) 62 return nil 63 } 64 65 // Get returns the underlying []string value stored by this flag struct. 66 func (i *Array) Get() any { 67 return i 68 } 69 70 // StringListFlag is a type to be passed to flag.Var that supports list flags passed as repeated 71 // flags, e.g. ./scalibr -o a -o b,c the library will call arr.Set("a") then arr.Set("a,b"). 72 type StringListFlag struct { 73 set bool 74 value []string 75 defaultValue []string 76 } 77 78 // NewStringListFlag creates a new StringListFlag with the given default value. 79 func NewStringListFlag(defaultValue []string) StringListFlag { 80 return StringListFlag{defaultValue: defaultValue} 81 } 82 83 // Set gets called whenever a new instance of a flag is read during CLI arg parsing. 84 // For example, in the case of -o foo -o bar the library will call arr.Set("foo") then arr.Set("bar"). 85 func (s *StringListFlag) Set(x string) error { 86 s.value = append(s.value, strings.Split(x, ",")...) 87 s.set = true 88 return nil 89 } 90 91 // Get returns the underlying []string value stored by this flag struct. 92 func (s *StringListFlag) Get() any { 93 return s.GetSlice() 94 } 95 96 // GetSlice returns the underlying []string value stored by this flag struct. 97 func (s *StringListFlag) GetSlice() []string { 98 if s.set { 99 return s.value 100 } 101 return s.defaultValue 102 } 103 104 func (s *StringListFlag) String() string { 105 if len(s.value) == 0 { 106 return "" 107 } 108 return fmt.Sprint(s.value) 109 } 110 111 // Reset resets the flag to its default value. 112 func (s *StringListFlag) Reset() { 113 s.set = false 114 s.value = nil 115 } 116 117 // Flags contains a field for all the cli flags that can be set. 118 type Flags struct { 119 PrintVersion bool 120 Root string 121 ResultFile string 122 Output Array 123 ExtractorsToRun []string 124 DetectorsToRun []string 125 AnnotatorsToRun []string 126 PluginsToRun []string 127 PluginCFG []string 128 ExtractorOverride Array 129 PathsToExtract []string 130 IgnoreSubDirs bool 131 DirsToSkip []string 132 SkipDirRegex string 133 SkipDirGlob string 134 MaxFileSize int 135 UseGitignore bool 136 RemoteImage string 137 ImageLocal string 138 ImageTarball string 139 ImagePlatform string 140 GovulncheckDBPath string 141 SPDXDocumentName string 142 SPDXDocumentNamespace string 143 SPDXCreators string 144 CDXComponentName string 145 CDXComponentType string 146 CDXComponentVersion string 147 CDXAuthors string 148 Verbose bool 149 ExplicitPlugins bool 150 FilterByCapabilities bool 151 StoreAbsolutePath bool 152 WindowsAllDrives bool 153 Offline bool 154 } 155 156 var supportedOutputFormats = []string{ 157 "textproto", "binproto", "spdx23-tag-value", "spdx23-json", "spdx23-yaml", "cdx-json", "cdx-xml", 158 } 159 160 var supportedComponentTypes = []string{ 161 "application", "framework", "library", "container", "platform", 162 "operating-system", "device", "device-driver", "firmware", "file", 163 "machine-learning-model", "data", "cryptographic-asset", 164 } 165 166 // ValidateFlags validates the passed command line flags. 167 func ValidateFlags(flags *Flags) error { 168 if flags.PrintVersion { 169 // SCALIBR prints the version and exits so other flags don't need to be present. 170 return nil 171 } 172 if len(flags.ResultFile) == 0 && len(flags.Output) == 0 { 173 return errors.New("either --result or --o needs to be set") 174 } 175 if flags.Root != "" && flags.WindowsAllDrives { 176 return errors.New("--root and --windows-all-drives cannot be used together") 177 } 178 if flags.ImagePlatform != "" && len(flags.RemoteImage) == 0 { 179 return errors.New("--image-platform cannot be used without --remote-image") 180 } 181 if flags.ImageTarball != "" && flags.RemoteImage != "" { 182 return errors.New("--image-tarball cannot be used with --remote-image") 183 } 184 if flags.ImageTarball != "" && flags.ImagePlatform != "" { 185 return errors.New("--image-tarball cannot be used with --image-platform") 186 } 187 if flags.ImageLocal != "" && flags.RemoteImage != "" { 188 return errors.New("image-local-docker cannot be used with --remote-image") 189 } 190 if flags.ImageLocal != "" && flags.ImagePlatform != "" { 191 return errors.New("image-local-docker cannot be used with --image-platform") 192 } 193 if flags.ImageLocal != "" && flags.ImageTarball != "" { 194 return errors.New("image-local-docker cannot be used with --image-tarball") 195 } 196 if err := validateResultPath(flags.ResultFile); err != nil { 197 return fmt.Errorf("--result %w", err) 198 } 199 if err := validateOutput(flags.Output); err != nil { 200 return fmt.Errorf("--o %w", err) 201 } 202 if err := validateExtractorOverride(flags.ExtractorOverride); err != nil { 203 return fmt.Errorf("--extractor-override: %w", err) 204 } 205 if err := validateImagePlatform(flags.ImagePlatform); err != nil { 206 return fmt.Errorf("--image-platform %w", err) 207 } 208 if err := validateMultiStringArg(flags.PluginsToRun); err != nil { 209 return fmt.Errorf("--plugins: %w", err) 210 } 211 // Legacy args for setting plugins. 212 if err := validateMultiStringArg(flags.ExtractorsToRun); err != nil { 213 return fmt.Errorf("--extractors: %w", err) 214 } 215 if err := validateMultiStringArg(flags.DetectorsToRun); err != nil { 216 return fmt.Errorf("--detectors: %w", err) 217 } 218 if err := validateMultiStringArg(flags.AnnotatorsToRun); err != nil { 219 return fmt.Errorf("--annotators: %w", err) 220 } 221 222 if err := validateMultiStringArg(flags.DirsToSkip); err != nil { 223 return fmt.Errorf("--skip-dirs: %w", err) 224 } 225 if err := validateRegex(flags.SkipDirRegex); err != nil { 226 return fmt.Errorf("--skip-dir-regex: %w", err) 227 } 228 if err := validateGlob(flags.SkipDirGlob); err != nil { 229 return fmt.Errorf("--skip-dir-glob: %w", err) 230 } 231 pluginsToRun := slices.Concat(flags.PluginsToRun, flags.ExtractorsToRun, flags.DetectorsToRun, flags.AnnotatorsToRun) 232 if err := validateDependency(pluginsToRun, flags.ExplicitPlugins); err != nil { 233 return err 234 } 235 if err := validateComponentType(flags.CDXComponentType); err != nil { 236 return err 237 } 238 return nil 239 } 240 241 func validateExtractorOverride(extractorOverride []string) error { 242 for _, item := range extractorOverride { 243 parts := strings.SplitN(item, ":", 2) 244 if len(parts) != 2 || parts[0] == "" || parts[1] == "" { 245 return fmt.Errorf("invalid format for extractor override %q, should be <plugin-name>:<glob-pattern>", item) 246 } 247 if _, err := glob.Compile(parts[1]); err != nil { 248 return fmt.Errorf("invalid glob pattern %q in extractor override %q: %w", parts[1], item, err) 249 } 250 } 251 return nil 252 } 253 254 func validateResultPath(filePath string) error { 255 if len(filePath) == 0 { 256 return nil 257 } 258 if err := proto.ValidExtension(filePath); err != nil { 259 return err 260 } 261 return nil 262 } 263 264 func validateOutput(output []string) error { 265 for _, item := range output { 266 o := strings.Split(item, "=") 267 if len(o) != 2 { 268 return errors.New("invalid output format, should follow a format like -o textproto=result.textproto -o spdx23-json=result.spdx.json") 269 } 270 oFormat := o[0] 271 if !slices.Contains(supportedOutputFormats, oFormat) { 272 return fmt.Errorf("output format %q not recognized, supported formats are %v", oFormat, supportedOutputFormats) 273 } 274 } 275 return nil 276 } 277 278 func validateImagePlatform(imagePlatform string) error { 279 if len(imagePlatform) == 0 { 280 return nil 281 } 282 platformDetails := strings.Split(imagePlatform, "/") 283 if len(platformDetails) < 2 { 284 return fmt.Errorf("image platform '%s' is invalid. Must be in the form OS/Architecture (e.g. linux/amd64)", imagePlatform) 285 } 286 return nil 287 } 288 289 func validateMultiStringArg(arg []string) error { 290 if len(arg) == 0 { 291 return nil 292 } 293 for _, item := range arg { 294 if len(item) == 0 { 295 continue 296 } 297 for item := range strings.SplitSeq(item, ",") { 298 if len(item) == 0 { 299 return errors.New("list item cannot be left empty") 300 } 301 } 302 } 303 return nil 304 } 305 306 func validateRegex(arg string) error { 307 if len(arg) == 0 { 308 return nil 309 } 310 _, err := regexp.Compile(arg) 311 return err 312 } 313 314 func validateGlob(arg string) error { 315 _, err := glob.Compile(arg) 316 return err 317 } 318 319 func validateDependency(pluginNames []string, requireExtractors bool) error { 320 f := &Flags{PluginsToRun: pluginNames} 321 plugins, _, err := f.pluginsToRun() 322 if err != nil { 323 return err 324 } 325 pMap := make(map[string]bool) 326 for _, p := range plugins { 327 pMap[p.Name()] = true 328 } 329 if requireExtractors { 330 for _, p := range plugins { 331 if d, ok := p.(detector.Detector); ok { 332 for _, req := range d.RequiredExtractors() { 333 if !pMap[req] { 334 return fmt.Errorf("extractor %s must be turned on for Detector %s to run", req, d.Name()) 335 } 336 } 337 } 338 } 339 } 340 return nil 341 } 342 343 func validateComponentType(componentType string) error { 344 if len(componentType) > 0 && !slices.Contains(supportedComponentTypes, componentType) { 345 return fmt.Errorf("unsupported cdx-component-type '%s'", componentType) 346 } 347 348 return nil 349 } 350 351 type extractorOverride struct { 352 glob glob.Glob 353 extractor filesystem.Extractor 354 } 355 356 // GetScanConfig constructs a SCALIBR scan config from the provided CLI flags. 357 func (f *Flags) GetScanConfig() (*scalibr.ScanConfig, error) { 358 plugins, pluginCFG, err := f.pluginsToRun() 359 if err != nil { 360 return nil, err 361 } 362 capab := f.capabilities() 363 if f.FilterByCapabilities { 364 plugins = filterByCapabilities(plugins, capab) 365 } 366 if len(f.PluginsToRun)+len(f.ExtractorsToRun)+len(f.DetectorsToRun)+len(f.AnnotatorsToRun) == 0 { 367 names := make([]string, 0, len(plugins)) 368 for _, p := range plugins { 369 names = append(names, p.Name()) 370 } 371 log.Warnf("No plugins specified, using default list: %v", names) 372 } 373 374 var skipDirRegex *regexp.Regexp 375 if f.SkipDirRegex != "" { 376 skipDirRegex, err = regexp.Compile(f.SkipDirRegex) 377 if err != nil { 378 return nil, err 379 } 380 } 381 var skipDirGlob glob.Glob 382 if f.SkipDirGlob != "" { 383 skipDirGlob, err = glob.Compile(f.SkipDirGlob) 384 if err != nil { 385 return nil, err 386 } 387 } 388 389 scanRoots, err := f.scanRoots() 390 if err != nil { 391 return nil, err 392 } 393 394 var overrides []extractorOverride 395 if len(f.ExtractorOverride) > 0 { 396 pluginMap := make(map[string]filesystem.Extractor) 397 for _, p := range plugins { 398 if e, ok := p.(filesystem.Extractor); ok { 399 pluginMap[e.Name()] = e 400 } 401 } 402 403 for _, o := range f.ExtractorOverride { 404 parts := strings.SplitN(o, ":", 2) 405 pluginName := parts[0] 406 globPattern := parts[1] 407 extractor, ok := pluginMap[pluginName] 408 if !ok { 409 return nil, fmt.Errorf("plugin %q specified in --extractor-override not found or not a filesystem extractor", pluginName) 410 } 411 g, err := glob.Compile(globPattern) 412 if err != nil { 413 // This should not happen due to ValidateFlags. 414 return nil, fmt.Errorf("invalid glob pattern %q in extractor override: %w", globPattern, err) 415 } 416 overrides = append(overrides, extractorOverride{ 417 glob: g, 418 extractor: extractor, 419 }) 420 } 421 } 422 423 var extractorOverrideFn func(filesystem.FileAPI) []filesystem.Extractor 424 if len(overrides) > 0 { 425 extractorOverrideFn = func(api filesystem.FileAPI) []filesystem.Extractor { 426 var result []filesystem.Extractor 427 for _, o := range overrides { 428 if o.glob.Match(api.Path()) { 429 result = append(result, o.extractor) 430 } 431 } 432 return result 433 } 434 } 435 436 return &scalibr.ScanConfig{ 437 ScanRoots: scanRoots, 438 Plugins: plugins, 439 Capabilities: capab, 440 PathsToExtract: f.PathsToExtract, 441 IgnoreSubDirs: f.IgnoreSubDirs, 442 DirsToSkip: f.dirsToSkip(scanRoots), 443 SkipDirRegex: skipDirRegex, 444 SkipDirGlob: skipDirGlob, 445 MaxFileSize: f.MaxFileSize, 446 UseGitignore: f.UseGitignore, 447 StoreAbsolutePath: f.StoreAbsolutePath, 448 ExtractorOverride: extractorOverrideFn, 449 ExplicitPlugins: f.ExplicitPlugins, 450 RequiredPluginConfig: pluginCFG, 451 }, nil 452 } 453 454 // GetSPDXConfig creates an SPDXConfig struct based on the CLI flags. 455 func (f *Flags) GetSPDXConfig() convspdx.Config { 456 var creators []common.Creator 457 if len(f.SPDXCreators) > 0 { 458 for item := range strings.SplitSeq(f.SPDXCreators, ",") { 459 c := strings.Split(item, ":") 460 cType := c[0] 461 cName := c[1] 462 creators = append(creators, common.Creator{ 463 CreatorType: cType, 464 Creator: cName, 465 }) 466 } 467 } 468 return convspdx.Config{ 469 DocumentName: f.SPDXDocumentName, 470 DocumentNamespace: f.SPDXDocumentNamespace, 471 Creators: creators, 472 } 473 } 474 475 // GetCDXConfig creates a CDXConfig struct based on the CLI flags. 476 func (f *Flags) GetCDXConfig() converter.CDXConfig { 477 return converter.CDXConfig{ 478 ComponentName: f.CDXComponentName, 479 ComponentType: f.CDXComponentType, 480 ComponentVersion: f.CDXComponentVersion, 481 Authors: strings.Split(f.CDXAuthors, ","), 482 } 483 } 484 485 // WriteScanResults writes SCALIBR scan results to files specified by the CLI flags. 486 func (f *Flags) WriteScanResults(result *scalibr.ScanResult) error { 487 if len(f.ResultFile) > 0 { 488 log.Infof("Writing scan results to %s", f.ResultFile) 489 resultProto, err := proto.ScanResultToProto(result) 490 if err != nil { 491 return err 492 } 493 if err := proto.Write(f.ResultFile, resultProto); err != nil { 494 return err 495 } 496 } 497 if len(f.Output) > 0 { 498 for _, item := range f.Output { 499 o := strings.Split(item, "=") 500 oFormat := o[0] 501 oPath := o[1] 502 log.Infof("Writing scan results to %s", oPath) 503 if strings.Contains(oFormat, "proto") { 504 resultProto, err := proto.ScanResultToProto(result) 505 if err != nil { 506 return err 507 } 508 if err := proto.WriteWithFormat(oPath, resultProto, oFormat); err != nil { 509 return err 510 } 511 } else if strings.Contains(oFormat, "spdx23") { 512 doc := converter.ToSPDX23(result, f.GetSPDXConfig()) 513 if err := binspdx.Write23(doc, oPath, oFormat); err != nil { 514 return err 515 } 516 } else if strings.Contains(oFormat, "cdx") { 517 doc := converter.ToCDX(result, f.GetCDXConfig()) 518 if err := cdx.Write(doc, oPath, oFormat); err != nil { 519 return err 520 } 521 } 522 } 523 } 524 return nil 525 } 526 527 // TODO(b/279413691): Allow commas in argument names. 528 func (f *Flags) pluginsToRun() ([]plugin.Plugin, *cpb.PluginConfig, error) { 529 result := make([]plugin.Plugin, 0, len(f.PluginsToRun)) 530 pluginNames := multiStringToList(f.PluginsToRun) 531 pluginCFG, err := pluginCFGFromFlags(f.PluginCFG) 532 if err != nil { 533 return nil, nil, err 534 } 535 536 extractorNames := addPluginPrefixToGroups("extractors/", multiStringToList(f.ExtractorsToRun)) 537 detectorNames := addPluginPrefixToGroups("detectors/", multiStringToList(f.DetectorsToRun)) 538 annotatorNames := addPluginPrefixToGroups("annotators/", multiStringToList(f.AnnotatorsToRun)) 539 540 // Use the default plugins if nothing is specified. 541 allPluginNames := slices.Concat(pluginNames, extractorNames, detectorNames, annotatorNames) 542 if len(allPluginNames) == 0 { 543 allPluginNames = []string{"default"} 544 } 545 546 for _, name := range allPluginNames { 547 plugins, err := pl.FromNames([]string{name}, pluginCFG) 548 if err != nil { 549 return nil, nil, err 550 } 551 result = append(result, plugins...) 552 } 553 554 return result, pluginCFG, nil 555 } 556 557 // addPluginPrefixToGroups adds the specified prefix to the "default" and "all" 558 // plugin group names so that they're only applied for a specific plugin type 559 // so that e.g. --extractors=all only enables all extractors and not other plugins. 560 func addPluginPrefixToGroups(prefix string, pluginNames []string) []string { 561 result := make([]string, 0, len(pluginNames)) 562 for _, p := range pluginNames { 563 if p == "all" || p == "default" { 564 p = prefix + p 565 } 566 result = append(result, p) 567 } 568 return result 569 } 570 571 // pluginCFGFromFlags parses individually provided 572 // plugin config strings into one proto. 573 func pluginCFGFromFlags(flags []string) (*cpb.PluginConfig, error) { 574 var cfgString strings.Builder 575 for _, flag := range flags { 576 pluginCFG := &cpb.PluginConfig{} 577 err := prototext.Unmarshal([]byte(flag), pluginCFG) 578 if err != nil { 579 // Flags can use a shorthand for specifying a single plugin-specific setting. 580 // In this case we have to add the wrapping proto parts manually. 581 flag = "plugin_specific:{" + flag + "}" 582 err := prototext.Unmarshal([]byte(flag), pluginCFG) 583 if err != nil { 584 return nil, err 585 } 586 } 587 cfgString.WriteString(flag + "\n") 588 } 589 590 allCFGs := &cpb.PluginConfig{} 591 err := prototext.Unmarshal([]byte(cfgString.String()), allCFGs) 592 return allCFGs, err 593 } 594 595 func multiStringToList(arg []string) []string { 596 var result []string 597 for _, item := range arg { 598 result = append(result, strings.Split(item, ",")...) 599 } 600 return result 601 } 602 603 func (f *Flags) scanRoots() ([]*scalibrfs.ScanRoot, error) { 604 if f.RemoteImage != "" { 605 imageOptions := f.scanRemoteImageOptions() 606 fs, err := scalibrimage.NewFromRemoteName(f.RemoteImage, *imageOptions...) 607 if err != nil { 608 return nil, err 609 } 610 // We're scanning a virtual filesystem that describes the remote container. 611 return []*scalibrfs.ScanRoot{{FS: fs, Path: ""}}, nil 612 } 613 614 if len(f.Root) != 0 { 615 return scalibrfs.RealFSScanRoots(f.Root), nil 616 } 617 618 // If ImageTarball is set, do not set the root. 619 // It is computed later on by ScanContainer(...) when the tarball is read. 620 if f.ImageTarball != "" { 621 return nil, nil 622 } 623 // If ImageLocal is set, do not set the root. 624 // It is computed later on by ScanContainer(...) when the tarball is read. 625 if f.ImageLocal != "" { 626 return nil, nil 627 } 628 629 // Compute the default scan roots. 630 var scanRoots []*scalibrfs.ScanRoot 631 var scanRootPaths []string 632 var err error 633 if scanRootPaths, err = platform.DefaultScanRoots(f.WindowsAllDrives); err != nil { 634 return nil, err 635 } 636 for _, r := range scanRootPaths { 637 scanRoots = append(scanRoots, &scalibrfs.ScanRoot{FS: scalibrfs.DirFS(r), Path: r}) 638 } 639 return scanRoots, nil 640 } 641 642 func (f *Flags) scanRemoteImageOptions() *[]remote.Option { 643 imageOptions := []remote.Option{ 644 remote.WithAuthFromKeychain(authn.DefaultKeychain), 645 } 646 if f.ImagePlatform != "" { 647 platformDetails := strings.Split(f.ImagePlatform, "/") 648 imageOptions = append(imageOptions, remote.WithPlatform( 649 v1.Platform{ 650 OS: platformDetails[0], 651 Architecture: platformDetails[1], 652 }, 653 )) 654 } 655 return &imageOptions 656 } 657 658 // All capabilities are enabled when running SCALIBR as a binary. 659 func (f *Flags) capabilities() *plugin.Capabilities { 660 network := plugin.NetworkOnline 661 if f.Offline { 662 network = plugin.NetworkOffline 663 } 664 if f.RemoteImage != "" { 665 // We're scanning a Linux container image whose filesystem is mounted to the host's disk. 666 return &plugin.Capabilities{ 667 OS: plugin.OSLinux, 668 Network: network, 669 DirectFS: true, 670 RunningSystem: false, 671 } 672 } 673 return &plugin.Capabilities{ 674 OS: platform.OS(), 675 Network: network, 676 DirectFS: true, 677 RunningSystem: true, 678 } 679 } 680 681 // Filters the specified list of plugins (filesystem extractors, standalone extractors, detectors, enrichers) 682 // by removing all plugins that don't satisfy the specified capabilities. 683 func filterByCapabilities(plugins []plugin.Plugin, capab *plugin.Capabilities) []plugin.Plugin { 684 fp := make([]plugin.Plugin, 0, len(plugins)) 685 for _, p := range plugins { 686 if err := plugin.ValidateRequirements(p, capab); err == nil { 687 fp = append(fp, p) 688 } 689 } 690 return fp 691 } 692 693 func (f *Flags) dirsToSkip(scanRoots []*scalibrfs.ScanRoot) []string { 694 paths, err := platform.DefaultIgnoredDirectories() 695 if err != nil { 696 log.Warnf("Failed to get default ignored directories: %v", err) 697 } 698 if len(f.DirsToSkip) > 0 { 699 paths = append(paths, multiStringToList(f.DirsToSkip)...) 700 } 701 702 // Ignore paths that are not under Root. 703 result := make([]string, 0, len(paths)) 704 for _, root := range scanRoots { 705 path := root.Path 706 if !strings.HasSuffix(path, string(os.PathSeparator)) { 707 path += string(os.PathSeparator) 708 } 709 for _, p := range paths { 710 if strings.HasPrefix(p, path) { 711 result = append(result, p) 712 } 713 } 714 } 715 return result 716 }