| // 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" |
| "path" |
| "path/filepath" |
| "runtime/debug" |
| "strings" |
| "time" |
| |
| "golang.org/x/vuln/internal" |
| "golang.org/x/vuln/internal/client" |
| "golang.org/x/vuln/internal/govulncheck" |
| ) |
| |
| // 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, r io.Reader, stdout io.Writer, stderr io.Writer, args []string) error { |
| cfg, err := parseFlags(stderr, args) |
| if err != nil { |
| return err |
| } |
| if cfg.mode == modeConvert { |
| return convertJSONToText(r, stdout) |
| } |
| |
| 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 { |
| case cfg.json: |
| handler = govulncheck.NewJSONHandler(stdout) |
| default: |
| th := NewTextHandler(stdout) |
| th.Show = cfg.show |
| handler = th |
| } |
| |
| // Write the introductory message to the user. |
| if err := handler.Config(&cfg.Config); err != nil { |
| return err |
| } |
| |
| switch cfg.mode { |
| case modeSource: |
| dir := filepath.FromSlash(cfg.dir) |
| err = runSource(ctx, handler, cfg, client, dir) |
| case modeBinary: |
| err = runBinary(ctx, handler, cfg, client) |
| case modeQuery: |
| err = runQuery(ctx, handler, cfg, client) |
| } |
| if err != nil { |
| return err |
| } |
| if err := Flush(handler); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func prepareConfig(ctx context.Context, cfg *config, client *client.Client) { |
| cfg.ProtocolVersion = govulncheck.ProtocolVersion |
| cfg.DB = cfg.db |
| if cfg.mode == modeSource { |
| // The Go version is only relevant for source analysis, so omit it for |
| // binary mode. |
| if v, err := internal.GoEnv("GOVERSION"); err == nil { |
| cfg.GoVersion = v |
| } |
| } |
| 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() |
| } |
| |
| // convertJSONToText converts r, which is expected to be the JSON output of govulncheck, |
| // into the text output, and writes the output to w. |
| func convertJSONToText(r io.Reader, w io.Writer) error { |
| h := NewTextHandler(w) |
| if err := govulncheck.HandleJSON(r, h); err != nil { |
| return err |
| } |
| Flush(h) |
| return nil |
| } |