Compare commits

4 Commits

Author SHA1 Message Date
efb0cea1c2 Suppress the revive package-name findings with no fix (closes #61)
All checks were successful
check / check (pull_request) Successful in 3m11s
Three findings remain that cannot be fixed without making a repo-wide
naming decision, so each carries a per-site //nolint directive with its
justification.

revive var-naming (internal/log, internal/crypto, internal/types):
fixing these means renaming packages across the whole codebase, which
is the repo owner's call, not a lint fix. Neither stdlib log nor stdlib
crypto is imported anywhere in the repo, so nothing is actually
shadowed today. The rename decision is tracked in issue #76. revive
reports a package-name failure only once per package directory, on
whichever file it happens to lint first, so every file of the affected
packages carries the directive and lists nolintlint alongside revive so
the ones that lose the race are not reported as unused.

No gosec directives are needed: the pinned golangci-lint v2.12.2 that
CI and the Dockerfile use reports nothing at the term.IsTerminal
conversions in internal/log and internal/ui or at the os.Remove calls
in internal/vaultik/verify.go, so suppressing there would itself fail
nolintlint as an unused directive.

Verification is script/cibuild, which builds the hash-pinned lint
image: it exits 0, with make lint reporting "0 issues" under the
canonical .golangci.yml (sha256
021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb,
unmodified), plus make fmt-check, make test and the release build. That
also unblocks issue #59. make check is not a valid gate here: script/lint
runs whatever golangci-lint is on PATH rather than the pinned version,
which is tracked in issue #78.

Also corrects the capacity comment in collectBatchFlushData: an empty
file contributes no chunk mappings, so the pending-file count is a
rough starting capacity, not a lower bound.

TODO.md: record this work, note that the previous next step (reconciling
uncommitted ARCHITECTURE.md edits) needed no work because the tree is
clean, and move the next step on to the stale-branch triage.
2026-08-09 02:13:57 +00:00
cb25b01e70 Preallocate append targets flagged by prealloc (refs #61)
collectBatchFlushData now sizes the file-chunk and chunk-file slices to
the number of pending files, a safe lower bound since every file
contributes at least one mapping of each kind. The chunker test sizes
its reconstruction buffer to the input length, which is exactly what it
ends up holding. Append semantics and results are unchanged.
2026-08-09 01:48:44 +00:00
7a37a66d88 Close sql.Rows inline so sqlclosecheck can see it (refs #61)
The ten sqlclosecheck findings were not leaks: every one of these
queries already deferred a close through the package-local CloseRows
helper. sqlclosecheck only recognises a Close call on the rows value
in the function that produced it (directly deferred, or inside a
deferred closure), so a call that hands rows to a helper reads as
unhandled.

Rather than keep a helper the linter cannot see through, drop
CloseRows and defer a closure that calls rows.Close() directly at each
of the eighteen call sites, keeping the existing fatal-on-close-error
behaviour byte for byte. The close still runs exactly once, on
function exit, after the rows have been read.

Fatalf stays; it is still used by the transaction helpers.
2026-08-09 01:48:44 +00:00
047bd7f1c4 Fix remaining wsl_v5 whitespace findings (refs #61)
Insert the blank line wsl_v5 requires above `defer` and `go`
statements that share no variables with the statement above them.
Applied mechanically via `make lint-fix`; the diff is 60 added blank
lines and nothing else.
2026-08-09 01:39:58 +00:00
42 changed files with 223 additions and 45 deletions

30
TODO.md
View File

@@ -14,16 +14,36 @@ pre-1.0
# Next Step # Next Step
Reconcile the uncommitted ARCHITECTURE.md edits on main: finish and Triage the stale remote branches (issue #71): for each, merge the work
commit, or revert. or delete the branch.
# Completed Steps # Completed Steps
- 2026-08-09: Finished the lint remediation under the canonical
`.golangci.yml` (issue #61, which also unblocks issue #59). The
remaining findings were fixed behavior-preservingly: `wsl_v5`
whitespace, `sqlclosecheck`, and `prealloc`. The `sqlclosecheck` sites
now close `sql.Rows` in a deferred closure instead of via the
`CloseRows` helper, which the linter could not see through. Only the
`revive` package-name findings remain suppressed, with per-site
`//nolint` directives; the package-rename question behind them is
tracked in issue #76. Verified with `script/cibuild`, which exits 0 —
that is the only trustworthy gate, because `script/lint` runs whatever
`golangci-lint` happens to be on `PATH` rather than the pinned
v2.12.2 that CI and the `Dockerfile` use, so `make check` can report
green on findings CI still fails. That tooling gap is tracked in issue
#78.
- 2026-08-09: The earlier next step "reconcile the uncommitted
`ARCHITECTURE.md` edits on `main`" needed no work: the working tree is
clean and `ARCHITECTURE.md` is committed on `main`.
- 2026-08-07: Updated golangci-lint to v2.12.2 everywhere it is pinned - 2026-08-07: Updated golangci-lint to v2.12.2 everywhere it is pinned
(`Dockerfile` lint stage, `Makefile` deps target), replaced (`Dockerfile` lint stage, `Makefile` deps target), replaced
`.golangci.yml` with the canonical config (v2 schema, `default: all`), `.golangci.yml` with the canonical config (v2 schema, `default: all`),
and remediated all lint findings it surfaced (issue #61): and remediated the bulk of the lint findings it surfaced (issue #61):
behavior-preserving fixes across every package, `make check` green. behavior-preserving fixes across every package, 2,990 findings down to
80. `make test` and `make fmt-check` were green at that point but
`make lint` was still red; the commit message claiming `make check`
was green was wrong.
- 2026-08-07: Added the standard `.golangci.yml` and `.editorconfig` - 2026-08-07: Added the standard `.golangci.yml` and `.editorconfig`
(issue #59); lint findings under the new config are tracked in issue (issue #59); lint findings under the new config are tracked in issue
#61. `script/bootstrap` now installs sqlite3 (needed by tests). #61. `script/bootstrap` now installs sqlite3 (needed by tests).
@@ -49,6 +69,4 @@ commit, or revert.
# Future Steps # Future Steps
- Review stale local branches (add-godoc-to-cli-package,
feature/pluggable-storage-backend) and merge or delete them.
- Define remaining scope for a first tagged release and cut v0.1.0. - Define remaining scope for a first tagged release and cut v0.1.0.

View File

@@ -16,6 +16,7 @@ func main() {
if err != nil { if err != nil {
panic("could not create CPU profile: " + err.Error()) panic("could not create CPU profile: " + err.Error())
} }
defer func() { _ = f.Close() }() defer func() { _ = f.Close() }()
err = pprof.StartCPUProfile(f) err = pprof.StartCPUProfile(f)
@@ -33,6 +34,7 @@ func main() {
if err != nil { if err != nil {
panic("could not create memory profile: " + err.Error()) panic("could not create memory profile: " + err.Error())
} }
defer func() { _ = f.Close() }() defer func() { _ = f.Close() }()
runtime.GC() // get up-to-date statistics runtime.GC() // get up-to-date statistics

View File

@@ -64,6 +64,7 @@ func CompressStream(
} }
closed := false closed := false
defer func() { defer func() {
if !closed { if !closed {
_ = w.Close() _ = w.Close()

View File

@@ -163,6 +163,7 @@ func (c *Chunker) ChunkFile(path string) ([]Chunk, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("opening file: %w", err) return nil, fmt.Errorf("opening file: %w", err)
} }
defer func() { defer func() {
err := file.Close() err := file.Close()
if err != nil && err.Error() != "invalid argument" { if err != nil && err.Error() != "invalid argument" {

View File

@@ -53,7 +53,7 @@ func TestChunkerLargeFileMultipleChunks(t *testing.T) {
} }
// Verify chunks reconstruct original data // Verify chunks reconstruct original data
var reconstructed []byte reconstructed := make([]byte, 0, len(data))
for _, chunk := range chunks { for _, chunk := range chunks {
reconstructed = append(reconstructed, chunk.Data...) reconstructed = append(reconstructed, chunk.Data...)
} }

View File

@@ -157,6 +157,7 @@ func RunApp(ctx context.Context, app *fx.App) error {
// Handle shutdown // Handle shutdown
shutdownComplete := make(chan struct{}) shutdownComplete := make(chan struct{})
go func() { go func() {
defer close(shutdownComplete) defer close(shutdownComplete)

View File

@@ -1,6 +1,6 @@
// Package crypto provides thread-safe age encryption and decryption // Package crypto provides thread-safe age encryption and decryption
// helpers used to protect blob and metadata content. // helpers used to protect blob and metadata content.
package crypto package crypto //nolint:revive,nolintlint // stdlib crypto unused; see #76
import ( import (
"bytes" "bytes"

View File

@@ -57,7 +57,13 @@ func (r *BlobChunkRepository) GetByBlobID(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying blob chunks: %w", err) return nil, fmt.Errorf("querying blob chunks: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
var blobChunks []*BlobChunk var blobChunks []*BlobChunk

View File

@@ -82,7 +82,13 @@ func (r *BlobRepository) GetAll(ctx context.Context) (map[string]*Blob, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("querying blobs: %w", err) return nil, fmt.Errorf("querying blobs: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
out := make(map[string]*Blob) out := make(map[string]*Blob)

View File

@@ -60,7 +60,13 @@ func (r *ChunkFileRepository) GetByChunkHash(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying chunk files: %w", err) return nil, fmt.Errorf("querying chunk files: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
return r.scanChunkFiles(rows) return r.scanChunkFiles(rows)
} }
@@ -80,7 +86,13 @@ func (r *ChunkFileRepository) GetByFilePath(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying chunk files: %w", err) return nil, fmt.Errorf("querying chunk files: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
return r.scanChunkFiles(rows) return r.scanChunkFiles(rows)
} }
@@ -99,7 +111,13 @@ func (r *ChunkFileRepository) GetByFileID(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying chunk files: %w", err) return nil, fmt.Errorf("querying chunk files: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
return r.scanChunkFiles(rows) return r.scanChunkFiles(rows)
} }

View File

@@ -106,7 +106,13 @@ func (r *ChunkRepository) GetByHashes(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying chunks: %w", err) return nil, fmt.Errorf("querying chunks: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
var chunks []*Chunk var chunks []*Chunk
@@ -145,7 +151,13 @@ func (r *ChunkRepository) ListUnpacked(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying unpacked chunks: %w", err) return nil, fmt.Errorf("querying unpacked chunks: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
var chunks []*Chunk var chunks []*Chunk

View File

@@ -17,7 +17,13 @@ func (r *ChunkRepository) List(ctx context.Context) ([]*Chunk, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("querying chunks: %w", err) return nil, fmt.Errorf("querying chunks: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
var chunks []*Chunk var chunks []*Chunk

View File

@@ -19,6 +19,7 @@ func TestDatabase(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to create database: %v", err) t.Fatalf("failed to create database: %v", err)
} }
defer func() { defer func() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {
@@ -73,6 +74,7 @@ func TestDatabaseConcurrentAccess(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to create database: %v", err) t.Fatalf("failed to create database: %v", err)
} }
defer func() { defer func() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {
@@ -182,6 +184,7 @@ func TestApplyMigrations_Idempotent(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to open database: %v", err) t.Fatalf("failed to open database: %v", err)
} }
defer func() { defer func() {
err := conn.Close() err := conn.Close()
if err != nil { if err != nil {
@@ -239,6 +242,7 @@ func TestBootstrapMigrationsTable_FreshDatabase(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to open database: %v", err) t.Fatalf("failed to open database: %v", err)
} }
defer func() { defer func() {
err := conn.Close() err := conn.Close()
if err != nil { if err != nil {

View File

@@ -1,7 +1,6 @@
package database package database
import ( import (
"database/sql"
"fmt" "fmt"
"os" "os"
) )
@@ -11,11 +10,3 @@ func Fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "FATAL: "+format+"\n", args...) fmt.Fprintf(os.Stderr, "FATAL: "+format+"\n", args...)
os.Exit(1) os.Exit(1)
} }
// CloseRows closes rows and exits on error
func CloseRows(rows *sql.Rows) {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}

View File

@@ -61,7 +61,13 @@ func (r *FileChunkRepository) GetByPath(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying file chunks: %w", err) return nil, fmt.Errorf("querying file chunks: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
return r.scanFileChunks(rows) return r.scanFileChunks(rows)
} }
@@ -81,7 +87,13 @@ func (r *FileChunkRepository) GetByFileID(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying file chunks: %w", err) return nil, fmt.Errorf("querying file chunks: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
return r.scanFileChunks(rows) return r.scanFileChunks(rows)
} }
@@ -104,7 +116,13 @@ func (r *FileChunkRepository) GetByPathTx(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying file chunks: %w", err) return nil, fmt.Errorf("querying file chunks: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
fileChunks, err := r.scanFileChunks(rows) fileChunks, err := r.scanFileChunks(rows)
LogSQL("GetByPathTx", "Complete", path, "count", len(fileChunks)) LogSQL("GetByPathTx", "Complete", path, "count", len(fileChunks))

View File

@@ -168,7 +168,13 @@ func (r *FileRepository) ListModifiedSince(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying files: %w", err) return nil, fmt.Errorf("querying files: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
var files []*File var files []*File
@@ -238,7 +244,13 @@ func (r *FileRepository) ListByPrefix(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying files: %w", err) return nil, fmt.Errorf("querying files: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
var files []*File var files []*File
@@ -266,7 +278,13 @@ func (r *FileRepository) ListAll(ctx context.Context) ([]*File, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("querying files: %w", err) return nil, fmt.Errorf("querying files: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
var files []*File var files []*File

View File

@@ -17,6 +17,7 @@ func logSnapshotFileIDs(t *testing.T, db *DB) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() { defer func() {
err := rows.Close() err := rows.Close()
if err != nil { if err != nil {

View File

@@ -223,7 +223,13 @@ func (r *SnapshotRepository) ListRecent(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying snapshots: %w", err) return nil, fmt.Errorf("querying snapshots: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
return r.scanSnapshotRows(rows) return r.scanSnapshotRows(rows)
} }
@@ -437,7 +443,13 @@ func (r *SnapshotRepository) GetBlobHashes(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying blob hashes: %w", err) return nil, fmt.Errorf("querying blob hashes: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
var blobs []string var blobs []string
@@ -561,7 +573,13 @@ func (r *SnapshotRepository) GetIncompleteSnapshots(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying incomplete snapshots: %w", err) return nil, fmt.Errorf("querying incomplete snapshots: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
return r.scanSnapshotRows(rows) return r.scanSnapshotRows(rows)
} }
@@ -583,7 +601,13 @@ func (r *SnapshotRepository) GetIncompleteByHostname(
if err != nil { if err != nil {
return nil, fmt.Errorf("querying incomplete snapshots: %w", err) return nil, fmt.Errorf("querying incomplete snapshots: %w", err)
} }
defer CloseRows(rows)
defer func() {
err := rows.Close()
if err != nil {
Fatalf("failed to close rows: %v", err)
}
}()
var snapshots []*Snapshot var snapshots []*Snapshot

View File

@@ -96,6 +96,7 @@ func (r *UploadRepository) GetRecentUploads(
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() { defer func() {
err := rows.Close() err := rows.Close()
if err != nil { if err != nil {

View File

@@ -1,6 +1,6 @@
// Package log provides the application-wide structured logger: slog // Package log provides the application-wide structured logger: slog
// with a colorized TTY handler on terminals and JSON output otherwise. // with a colorized TTY handler on terminals and JSON output otherwise.
package log package log //nolint:revive,nolintlint // stdlib log unused here; see #76
import ( import (
"context" "context"
@@ -69,7 +69,7 @@ func Initialize(cfg Config) {
Level: level, Level: level,
} }
// Check if stdout is a TTY // Check if stdout is a TTY.
if term.IsTerminal(int(os.Stdout.Fd())) { if term.IsTerminal(int(os.Stdout.Fd())) {
// Use colorized TTY handler // Use colorized TTY handler
logger = slog.New(NewTTYHandler(os.Stdout, opts)) logger = slog.New(NewTTYHandler(os.Stdout, opts))

View File

@@ -1,4 +1,4 @@
package log package log //nolint:revive,nolintlint // stdlib log unused here; see #76
import ( import (
"go.uber.org/fx" "go.uber.org/fx"

View File

@@ -1,4 +1,4 @@
package log package log //nolint:revive,nolintlint // stdlib log unused here; see #76
import ( import (
"context" "context"

View File

@@ -48,6 +48,7 @@ func TestAcquireBlocksSecondInstance(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, lock1) require.NotNil(t, lock1)
defer func() { _ = lock1.Release() }() defer func() { _ = lock1.Release() }()
// Try to acquire second lock - should fail // Try to acquire second lock - should fail
@@ -72,6 +73,7 @@ func TestAcquireWithStaleLock(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, lock) require.NotNil(t, lock)
defer func() { _ = lock.Release() }() defer func() { _ = lock.Release() }()
// Verify our PID is now in the file // Verify our PID is now in the file
@@ -117,6 +119,7 @@ func TestAcquireCreatesDirectory(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, lock) require.NotNil(t, lock)
defer func() { _ = lock.Release() }() defer func() { _ = lock.Release() }()
// Verify directory was created // Verify directory was created

View File

@@ -12,6 +12,7 @@ import (
//nolint:paralleltest // test servers share a fixed localhost port //nolint:paralleltest // test servers share a fixed localhost port
func TestClient(t *testing.T) { func TestClient(t *testing.T) {
ts := NewTestServer(t) ts := NewTestServer(t)
defer func() { defer func() {
err := ts.Cleanup() err := ts.Cleanup()
if err != nil { if err != nil {
@@ -58,6 +59,7 @@ func verifyPutGetHead(
if err != nil { if err != nil {
t.Fatalf("failed to get object: %v", err) t.Fatalf("failed to get object: %v", err)
} }
defer func() { defer func() {
err := reader.Close() err := reader.Close()
if err != nil { if err != nil {

View File

@@ -147,6 +147,7 @@ func (ts *TestServer) Client() *s3.Client {
//nolint:paralleltest // test servers share a fixed localhost port //nolint:paralleltest // test servers share a fixed localhost port
func TestBasicS3Operations(t *testing.T) { func TestBasicS3Operations(t *testing.T) {
ts := NewTestServer(t) ts := NewTestServer(t)
defer func() { defer func() {
err := ts.Cleanup() err := ts.Cleanup()
if err != nil { if err != nil {
@@ -179,6 +180,7 @@ func TestBasicS3Operations(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to get object: %v", err) t.Fatalf("failed to get object: %v", err)
} }
defer func() { defer func() {
err := result.Body.Close() err := result.Body.Close()
if err != nil { if err != nil {
@@ -202,6 +204,7 @@ func TestBasicS3Operations(t *testing.T) {
//nolint:paralleltest // test servers share a fixed localhost port //nolint:paralleltest // test servers share a fixed localhost port
func TestBlobOperations(t *testing.T) { func TestBlobOperations(t *testing.T) {
ts := NewTestServer(t) ts := NewTestServer(t)
defer func() { defer func() {
err := ts.Cleanup() err := ts.Cleanup()
if err != nil { if err != nil {
@@ -268,6 +271,7 @@ func TestBlobOperations(t *testing.T) {
//nolint:paralleltest // test servers share a fixed localhost port //nolint:paralleltest // test servers share a fixed localhost port
func TestMetadataOperations(t *testing.T) { func TestMetadataOperations(t *testing.T) {
ts := NewTestServer(t) ts := NewTestServer(t)
defer func() { defer func() {
err := ts.Cleanup() err := ts.Cleanup()
if err != nil { if err != nil {

View File

@@ -461,6 +461,7 @@ func (b *BackupEngine) backupOneFile(
if err != nil { if err != nil {
return err return err
} }
defer func() { defer func() {
err := f.Close() err := f.Close()
if err != nil { if err != nil {

View File

@@ -75,6 +75,7 @@ func TestFileContentChange(t *testing.T) {
db, err := database.NewTestDB() db, err := database.NewTestDB()
require.NoError(t, err) require.NoError(t, err)
defer func() { defer func() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {
@@ -165,6 +166,7 @@ func TestMultipleFileChanges(t *testing.T) {
db, err := database.NewTestDB() db, err := database.NewTestDB()
require.NoError(t, err) require.NoError(t, err)
defer func() { defer func() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {

View File

@@ -127,6 +127,7 @@ func NewProgressReporter() *ProgressReporter {
// Start begins the progress reporting // Start begins the progress reporting
func (pr *ProgressReporter) Start() { func (pr *ProgressReporter) Start() {
pr.wg.Add(1) pr.wg.Add(1)
go pr.run() go pr.run()
// Print initial multi-line status // Print initial multi-line status

View File

@@ -624,11 +624,12 @@ func (s *Scanner) collectBatchFlushData(
collectStart := time.Now() collectStart := time.Now()
var ( // A pending file contributes one mapping of each kind per chunk, and
allFileChunks []database.FileChunk // an empty file contributes none, so the file count is only a rough
allChunkFiles []database.ChunkFile // starting capacity for the mapping slices; append grows them as
) // needed. It is exact for the file and file-ID slices.
allFileChunks := make([]database.FileChunk, 0, len(canFlush))
allChunkFiles := make([]database.ChunkFile, 0, len(canFlush))
allFileIDs := make([]types.FileID, 0, len(canFlush)) allFileIDs := make([]types.FileID, 0, len(canFlush))
allFiles := make([]*database.File, 0, len(canFlush)) allFiles := make([]*database.File, 0, len(canFlush))
@@ -1673,6 +1674,7 @@ func (s *Scanner) processFileStreaming(
if err != nil { if err != nil {
return fmt.Errorf("opening file: %w", wrapPermissionError(fileToProcess.Path, err)) return fmt.Errorf("opening file: %w", wrapPermissionError(fileToProcess.Path, err))
} }
defer func() { _ = file.Close() }() defer func() { _ = file.Close() }()
var chunks []streamingChunkInfo var chunks []streamingChunkInfo

View File

@@ -164,6 +164,7 @@ func TestScannerSimpleDirectory(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to create test database: %v", err) t.Fatalf("failed to create test database: %v", err)
} }
defer func() { defer func() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {
@@ -241,6 +242,7 @@ func TestScannerLargeFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to create test database: %v", err) t.Fatalf("failed to create test database: %v", err)
} }
defer func() { defer func() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {

View File

@@ -259,6 +259,7 @@ func (sm *SnapshotManager) ExportSnapshotMetadata(
} }
log.Debug("Created temporary directory", "path", tempDir) log.Debug("Created temporary directory", "path", tempDir)
defer func() { defer func() {
log.Debug("Cleaning up temporary directory", "path", tempDir) log.Debug("Cleaning up temporary directory", "path", tempDir)
@@ -555,6 +556,7 @@ func (sm *SnapshotManager) cleanSnapshotDB(
if err != nil { if err != nil {
return nil, fmt.Errorf("opening temp database: %w", err) return nil, fmt.Errorf("opening temp database: %w", err)
} }
defer func() { defer func() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {
@@ -567,6 +569,7 @@ func (sm *SnapshotManager) cleanSnapshotDB(
if err != nil { if err != nil {
return nil, fmt.Errorf("beginning transaction: %w", err) return nil, fmt.Errorf("beginning transaction: %w", err)
} }
defer func() { defer func() {
rbErr := tx.Rollback() rbErr := tx.Rollback()
if rbErr != nil && !errors.Is(rbErr, sql.ErrTxDone) { if rbErr != nil && !errors.Is(rbErr, sql.ErrTxDone) {
@@ -685,6 +688,7 @@ func (sm *SnapshotManager) compressFile(inputPath, outputPath string) error {
if err != nil { if err != nil {
return fmt.Errorf("opening input file: %w", err) return fmt.Errorf("opening input file: %w", err)
} }
defer func() { defer func() {
err := input.Close() err := input.Close()
if err != nil { if err != nil {
@@ -696,6 +700,7 @@ func (sm *SnapshotManager) compressFile(inputPath, outputPath string) error {
if err != nil { if err != nil {
return fmt.Errorf("creating output file: %w", err) return fmt.Errorf("creating output file: %w", err)
} }
defer func() { defer func() {
err := output.Close() err := output.Close()
if err != nil { if err != nil {
@@ -714,6 +719,7 @@ func (sm *SnapshotManager) compressFile(inputPath, outputPath string) error {
// Track if writer has been closed to avoid double-close // Track if writer has been closed to avoid double-close
writerClosed := false writerClosed := false
defer func() { defer func() {
if !writerClosed { if !writerClosed {
err := writer.Close() err := writer.Close()
@@ -749,6 +755,7 @@ func (sm *SnapshotManager) copyFile(src, dst string) error {
if err != nil { if err != nil {
return err return err
} }
defer func() { defer func() {
log.Debug("Closing source file", "path", src) log.Debug("Closing source file", "path", src)
@@ -764,6 +771,7 @@ func (sm *SnapshotManager) copyFile(src, dst string) error {
if err != nil { if err != nil {
return err return err
} }
defer func() { defer func() {
log.Debug("Closing destination file", "path", dst) log.Debug("Closing destination file", "path", dst)
@@ -794,6 +802,7 @@ func (sm *SnapshotManager) generateBlobManifest(
if err != nil { if err != nil {
return nil, fmt.Errorf("opening database: %w", err) return nil, fmt.Errorf("opening database: %w", err)
} }
defer func() { _ = db.Close() }() defer func() { _ = db.Close() }()
// Create repositories to access the data // Create repositories to access the data

View File

@@ -25,12 +25,14 @@ func copyFile(fs afero.Fs, src, dst string) error {
if err != nil { if err != nil {
return err return err
} }
defer func() { _ = sourceFile.Close() }() defer func() { _ = sourceFile.Close() }()
destFile, err := fs.Create(dst) destFile, err := fs.Create(dst)
if err != nil { if err != nil {
return err return err
} }
defer func() { _ = destFile.Close() }() defer func() { _ = destFile.Close() }()
_, err = io.Copy(destFile, sourceFile) _, err = io.Copy(destFile, sourceFile)
@@ -53,6 +55,7 @@ func verifyCleanedDB(
if err != nil { if err != nil {
t.Fatalf("failed to open cleaned database: %v", err) t.Fatalf("failed to open cleaned database: %v", err)
} }
defer func() { defer func() {
err := cleanedDB.Close() err := cleanedDB.Close()
if err != nil { if err != nil {

View File

@@ -62,6 +62,7 @@ func (f *FileStorer) Put(_ context.Context, key string, data io.Reader) error {
if err != nil { if err != nil {
return fmt.Errorf("creating file: %w", err) return fmt.Errorf("creating file: %w", err)
} }
defer func() { _ = file.Close() }() defer func() { _ = file.Close() }()
_, err = io.Copy(file, data) _, err = io.Copy(file, data)
@@ -91,6 +92,7 @@ func (f *FileStorer) PutWithProgress(
if err != nil { if err != nil {
return fmt.Errorf("creating file: %w", err) return fmt.Errorf("creating file: %w", err)
} }
defer func() { _ = file.Close() }() defer func() { _ = file.Close() }()
// Wrap with progress tracking // Wrap with progress tracking
@@ -209,6 +211,7 @@ func (f *FileStorer) List(ctx context.Context, prefix string) ([]string, error)
// ListStream returns a channel of ObjectInfo for large result sets. // ListStream returns a channel of ObjectInfo for large result sets.
func (f *FileStorer) ListStream(ctx context.Context, prefix string) <-chan ObjectInfo { func (f *FileStorer) ListStream(ctx context.Context, prefix string) <-chan ObjectInfo {
ch := make(chan ObjectInfo) ch := make(chan ObjectInfo)
go func() { go func() {
defer close(ch) defer close(ch)

View File

@@ -68,6 +68,7 @@ func (s *S3Storer) List(ctx context.Context, prefix string) ([]string, error) {
// ListStream returns a channel of ObjectInfo for large result sets. // ListStream returns a channel of ObjectInfo for large result sets.
func (s *S3Storer) ListStream(ctx context.Context, prefix string) <-chan ObjectInfo { func (s *S3Storer) ListStream(ctx context.Context, prefix string) <-chan ObjectInfo {
ch := make(chan ObjectInfo) ch := make(chan ObjectInfo)
go func() { go func() {
defer close(ch) defer close(ch)

View File

@@ -2,7 +2,7 @@
// vaultik codebase. Using distinct types for IDs, hashes, paths, and // vaultik codebase. Using distinct types for IDs, hashes, paths, and
// credentials prevents accidental mixing of semantically different values // credentials prevents accidental mixing of semantically different values
// that happen to share the same underlying type. // that happen to share the same underlying type.
package types package types //nolint:revive,nolintlint // rename decision tracked in #76
import ( import (
"database/sql/driver" "database/sql/driver"

View File

@@ -238,6 +238,7 @@ func (c *blobDiskCache) ReadAt(key string, offset, length int64) ([]byte, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() { _ = f.Close() }() defer func() { _ = f.Close() }()
buf := make([]byte, length) buf := make([]byte, length)

View File

@@ -14,6 +14,7 @@ func TestBlobDiskCache_BasicGetPut(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() { _ = cache.Close() }() defer func() { _ = cache.Close() }()
data := []byte("hello world") data := []byte("hello world")
@@ -79,6 +80,7 @@ func TestBlobDiskCache_OversizedEntryRejected(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() { _ = cache.Close() }() defer func() { _ = cache.Close() }()
data := make([]byte, 200) data := make([]byte, 200)
@@ -100,6 +102,7 @@ func TestBlobDiskCache_UpdateInPlace(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() { _ = cache.Close() }() defer func() { _ = cache.Close() }()
err = cache.Put("key1", []byte("v1")) err = cache.Put("key1", []byte("v1"))
@@ -137,6 +140,7 @@ func TestBlobDiskCache_ReadAt(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() { _ = cache.Close() }() defer func() { _ = cache.Close() }()
data := make([]byte, 1024) data := make([]byte, 1024)
@@ -197,6 +201,7 @@ func TestBlobDiskCache_LRUOrder(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() { _ = cache.Close() }() defer func() { _ = cache.Close() }()
d := make([]byte, 100) d := make([]byte, 100)

View File

@@ -134,6 +134,7 @@ func (m *MockStorer) ListStream(
_ context.Context, prefix string, _ context.Context, prefix string,
) <-chan storage.ObjectInfo { ) <-chan storage.ObjectInfo {
ch := make(chan storage.ObjectInfo) ch := make(chan storage.ObjectInfo)
go func() { go func() {
defer close(ch) defer close(ch)
@@ -326,6 +327,7 @@ func TestEndToEndBackup(t *testing.T) {
db, err := database.New(ctx, ":memory:") db, err := database.New(ctx, ":memory:")
require.NoError(t, err) require.NoError(t, err)
defer func() { defer func() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {
@@ -411,6 +413,7 @@ func TestBackupAndVerify(t *testing.T) {
db, err := database.New(ctx, ":memory:") db, err := database.New(ctx, ":memory:")
require.NoError(t, err) require.NoError(t, err)
defer func() { defer func() {
err := db.Close() err := db.Close()
if err != nil { if err != nil {
@@ -968,6 +971,7 @@ func TestDedupOnlySnapshotRestores(t *testing.T) {
env := setupDedupBackupEnv( env := setupDedupBackupEnv(
ctx, t, fs, storeDir, dbPath, chunkSize, maxBlobSize) ctx, t, fs, storeDir, dbPath, chunkSize, maxBlobSize)
defer func() { _ = env.db.Close() }() defer func() { _ = env.db.Close() }()
cfg, storer, repos, sm := env.cfg, env.storer, env.repos, env.sm cfg, storer, repos, sm := env.cfg, env.storer, env.repos, env.sm

View File

@@ -590,6 +590,7 @@ func (v *Vaultik) downloadSnapshotDB(
if err != nil { if err != nil {
return nil, fmt.Errorf("downloading %s: %w", dbKey, err) return nil, fmt.Errorf("downloading %s: %w", dbKey, err)
} }
defer func() { _ = reader.Close() }() defer func() { _ = reader.Close() }()
// Read all data // Read all data
@@ -606,6 +607,7 @@ func (v *Vaultik) downloadSnapshotDB(
if err != nil { if err != nil {
return nil, fmt.Errorf("creating decryption reader: %w", err) return nil, fmt.Errorf("creating decryption reader: %w", err)
} }
defer func() { _ = blobReader.Close() }() defer func() { _ = blobReader.Close() }()
// Read the binary SQLite database // Read the binary SQLite database
@@ -1115,6 +1117,7 @@ func (v *Vaultik) verifyFile(
if err != nil { if err != nil {
return 0, fmt.Errorf("opening file: %w", err) return 0, fmt.Errorf("opening file: %w", err)
} }
defer func() { _ = f.Close() }() defer func() { _ = f.Close() }()
// Verify each chunk // Verify each chunk

View File

@@ -110,6 +110,7 @@ func (s *restoreSweeper) blobStillNeeded(blobHash string) (bool, error) {
if err != nil { if err != nil {
return true, fmt.Errorf("querying referencing files: %w", err) return true, fmt.Errorf("querying referencing files: %w", err)
} }
defer func() { _ = rows.Close() }() defer func() { _ = rows.Close() }()
for rows.Next() { for rows.Next() {

View File

@@ -1130,6 +1130,7 @@ func (v *Vaultik) downloadManifestByKey(remoteKey string) (*snapshot.Manifest, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() { _ = reader.Close() }() defer func() { _ = reader.Close() }()
manifest, err := snapshot.DecodeManifest(reader) manifest, err := snapshot.DecodeManifest(reader)

View File

@@ -96,6 +96,7 @@ func (v *Vaultik) RunDeepVerify(snapshotID string, opts *VerifyOptions) error {
if err != nil { if err != nil {
return err return err
} }
defer func() { defer func() {
if tempDB != nil { if tempDB != nil {
_ = tempDB.Close() _ = tempDB.Close()
@@ -343,6 +344,7 @@ func (v *Vaultik) verifyBlob(blobInfo snapshot.BlobInfo, db *sql.DB) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to download: %w", err) return fmt.Errorf("failed to download: %w", err)
} }
defer func() { _ = reader.Close() }() defer func() { _ = reader.Close() }()
// Get decryptor // Get decryptor