internal/derrors: add an internal errors package

An internal errors package is added that implements some simple common
error semantics.

derrors.Wrap allows errors to be annotated with a defer statement. It is
applied to the following packages / commands:

- cmd/cvetriage
- cmd/dbdiff
- cmd/report2cve
- internal

This pattern will be applied to other packages in later CLs.

Change-Id: I9976647c64f13a8a1cb9cfdfc6af940dfc8b9510
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/356613
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/cmd/cvetriage/main.go b/cmd/cvetriage/main.go
index e44a57a..358eae0 100644
--- a/cmd/cvetriage/main.go
+++ b/cmd/cvetriage/main.go
@@ -23,6 +23,7 @@
 
 	"golang.org/x/vulndb/internal"
 	"golang.org/x/vulndb/internal/cvelist"
+	"golang.org/x/vulndb/internal/derrors"
 )
 
 func main() {
@@ -31,7 +32,7 @@
 	}
 }
 
-func run() error {
+func run() (err error) {
 	triaged, err := readTriagedCVEList()
 	if err != nil {
 		return err
@@ -45,7 +46,8 @@
 	statusTriaged       = "triaged"
 )
 
-func readTriagedCVEList() (map[string]bool, error) {
+func readTriagedCVEList() (_ map[string]bool, err error) {
+	defer derrors.Wrap(&err, "readTriagedCVEList()")
 	triaged := map[string]bool{}
 	lines, err := internal.ReadFileLines(triagedCVEList)
 	if err != nil {
diff --git a/cmd/dbdiff/main.go b/cmd/dbdiff/main.go
index 6247e4e..ed06d8d 100644
--- a/cmd/dbdiff/main.go
+++ b/cmd/dbdiff/main.go
@@ -14,10 +14,12 @@
 
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/vulndb/internal"
+	"golang.org/x/vulndb/internal/derrors"
 	"golang.org/x/vulndb/osv"
 )
 
-func loadDB(dbPath string) (osv.DBIndex, map[string][]osv.Entry, error) {
+func loadDB(dbPath string) (_ osv.DBIndex, _ map[string][]osv.Entry, err error) {
+	defer derrors.Wrap(&err, "loadDB(%q)", dbPath)
 	index := osv.DBIndex{}
 	dbMap := map[string][]osv.Entry{}
 
diff --git a/cmd/report2cve/main.go b/cmd/report2cve/main.go
index 304bc1a..e81e15f 100644
--- a/cmd/report2cve/main.go
+++ b/cmd/report2cve/main.go
@@ -13,11 +13,13 @@
 	"strings"
 
 	"golang.org/x/vulndb/internal/cveschema"
+	"golang.org/x/vulndb/internal/derrors"
 	"golang.org/x/vulndb/internal/report"
 	"gopkg.in/yaml.v2"
 )
 
-func fromReport(r *report.Report) (*cveschema.CVE, error) {
+func fromReport(r *report.Report) (_ *cveschema.CVE, err error) {
+	defer derrors.Wrap(&err, "fromReport(r)")
 	if r.CVE != "" {
 		return nil, errors.New("report has CVE ID is wrong section (should be in cve_metadata for self-issued CVEs)")
 	}
diff --git a/internal/derrors/derrors.go b/internal/derrors/derrors.go
new file mode 100644
index 0000000..209a59c
--- /dev/null
+++ b/internal/derrors/derrors.go
@@ -0,0 +1,21 @@
+// Copyright 2021 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 derrors defines internal error values to categorize the different
+// types error semantics supported by the vulndb.
+package derrors
+
+import "fmt"
+
+// Wrap adds context to the error and allows
+// unwrapping the result to recover the original error.
+//
+// Example:
+//
+//	defer derrors.Wrap(&err, "copy(%s, %s)", dst, src)
+func Wrap(errp *error, format string, args ...interface{}) {
+	if *errp != nil {
+		*errp = fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), *errp)
+	}
+}
diff --git a/internal/internal.go b/internal/internal.go
index fd10ebd..2b0aa00 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -9,6 +9,8 @@
 	"bufio"
 	"os"
 	"strings"
+
+	"golang.org/x/vulndb/internal/derrors"
 )
 
 // IDDirectory is the name of the directory that contains entries
@@ -19,6 +21,7 @@
 // Whitespace on each line is trimmed.
 // Blank lines and lines beginning with '#' are ignored.
 func ReadFileLines(filename string) (lines []string, err error) {
+	defer derrors.Wrap(&err, "ReadFileLines(%q)", filename)
 	f, err := os.Open(filename)
 	if err != nil {
 		return nil, err