| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package scan |
| |
| import ( |
| "context" |
| "fmt" |
| "io" |
| "os/exec" |
| "path" |
| "path/filepath" |
| "runtime/debug" |
| "strings" |
| "time" |
| |
| "golang.org/x/telemetry/counter" |
| "golang.org/x/vuln/internal/client" |
| "golang.org/x/vuln/internal/govulncheck" |
| "golang.org/x/vuln/internal/openvex" |
| "golang.org/x/vuln/internal/sarif" |
| ) |
| |
| // RunGovulncheck performs main govulncheck functionality and exits the |
| // program upon success with an appropriate exit status. Otherwise, |
| // returns an error. |
| func RunGovulncheck(ctx context.Context, env []string, r io.Reader, stdout io.Writer, stderr io.Writer, args []string) error { |
| cfg := &config{env: env} |
| if err := parseFlags(cfg, stderr, args); err != nil { |
| return err |
| } |
| |
| client, err := client.NewClient(cfg.db, nil) |
| if err != nil { |
| return fmt.Errorf("creating client: %w", err) |
| } |
| |
| prepareConfig(ctx, cfg, client) |
| var handler govulncheck.Handler |
| switch cfg.format { |
| case formatJSON: |
| handler = govulncheck.NewJSONHandler(stdout) |
| case formatSarif: |
| handler = sarif.NewHandler(stdout) |
| case formatOpenVEX: |
| handler = openvex.NewHandler(stdout) |
| default: |
| th := NewTextHandler(stdout) |
| cfg.show.Update(th) |
| handler = th |
| } |
| |
| if err := handler.Config(&cfg.Config); err != nil { |
| return err |
| } |
| |
| incTelemetryFlagCounters(cfg) |
| |
| switch cfg.ScanMode { |
| case govulncheck.ScanModeSource: |
| dir := filepath.FromSlash(cfg.dir) |
| err = runSource(ctx, handler, cfg, client, dir) |
| case govulncheck.ScanModeBinary: |
| err = runBinary(ctx, handler, cfg, client) |
| case govulncheck.ScanModeExtract: |
| return runExtract(cfg, stdout) |
| case govulncheck.ScanModeQuery: |
| err = runQuery(ctx, handler, cfg, client) |
| case govulncheck.ScanModeConvert: |
| err = govulncheck.HandleJSON(r, handler) |
| } |
| if err != nil { |
| return err |
| } |
| return Flush(handler) |
| } |
| |
| func prepareConfig(ctx context.Context, cfg *config, client *client.Client) { |
| cfg.ProtocolVersion = govulncheck.ProtocolVersion |
| cfg.DB = cfg.db |
| if cfg.ScanMode == govulncheck.ScanModeSource && cfg.GoVersion == "" { |
| const goverPrefix = "GOVERSION=" |
| for _, env := range cfg.env { |
| if val := strings.TrimPrefix(env, goverPrefix); val != env { |
| cfg.GoVersion = val |
| } |
| } |
| if cfg.GoVersion == "" { |
| if out, err := exec.Command("go", "env", "GOVERSION").Output(); err == nil { |
| cfg.GoVersion = strings.TrimSpace(string(out)) |
| } |
| } |
| } |
| if bi, ok := debug.ReadBuildInfo(); ok { |
| scannerVersion(cfg, bi) |
| } |
| if mod, err := client.LastModifiedTime(ctx); err == nil { |
| cfg.DBLastModified = &mod |
| } |
| } |
| |
| // scannerVersion reconstructs the current version of |
| // this binary used from the build info. |
| func scannerVersion(cfg *config, bi *debug.BuildInfo) { |
| if bi.Path != "" { |
| cfg.ScannerName = path.Base(bi.Path) |
| } |
| if bi.Main.Version != "" && bi.Main.Version != "(devel)" { |
| cfg.ScannerVersion = bi.Main.Version |
| return |
| } |
| |
| // TODO(https://go.dev/issue/29228): we need to manually construct the |
| // version string when it is "(devel)" until #29228 is resolved. |
| var revision, at string |
| for _, s := range bi.Settings { |
| if s.Key == "vcs.revision" { |
| revision = s.Value |
| } |
| if s.Key == "vcs.time" { |
| at = s.Value |
| } |
| } |
| buf := strings.Builder{} |
| buf.WriteString("v0.0.0") |
| if revision != "" { |
| buf.WriteString("-") |
| buf.WriteString(revision[:12]) |
| } |
| if at != "" { |
| // commit time is of the form 2023-01-25T19:57:54Z |
| p, err := time.Parse(time.RFC3339, at) |
| if err == nil { |
| buf.WriteString("-") |
| buf.WriteString(p.Format("20060102150405")) |
| } |
| } |
| cfg.ScannerVersion = buf.String() |
| } |
| |
| func incTelemetryFlagCounters(cfg *config) { |
| counter.Inc(fmt.Sprintf("govulncheck/mode:%s", cfg.ScanMode)) |
| counter.Inc(fmt.Sprintf("govulncheck/scan:%s", cfg.ScanLevel)) |
| counter.Inc(fmt.Sprintf("govulncheck/format:%s", cfg.format)) |
| |
| if len(cfg.show) == 0 { |
| counter.Inc("govulncheck/show:none") |
| } |
| for _, s := range cfg.show { |
| counter.Inc(fmt.Sprintf("govulncheck/show:%s", s)) |
| } |
| } |
| |
| func Flush(h govulncheck.Handler) error { |
| if th, ok := h.(interface{ Flush() error }); ok { |
| return th.Flush() |
| } |
| return nil |
| } |