1 Commits

Author SHA1 Message Date
a5d4cd6c13 Update golangci-lint to v2.12.2 with canonical config
All checks were successful
check / check (push) Successful in 32s
- Replace .golangci.yml with the canonical v2-schema config: linter
  settings move under linters.settings (the previous top-level
  linters-settings block was ignored by golangci-lint v2, so the
  configured thresholds were not applied) and the obsolete
  issues.exclude-use-default key is dropped.
- Bump golangci-lint from the v2.10.1-era commit pin to @v2.12.2 in
  Dockerfile and script/bootstrap; refresh pin date comments.
- Wrap long lines in attrsum.go to satisfy the now-effective lll
  limit of 88 columns (15 findings); move one nolint:gosec directive
  to its own line.
- Record the change in TODO.md Completed Steps.
2026-08-07 17:11:06 +00:00
5 changed files with 104 additions and 74 deletions

View File

@@ -1,5 +1,9 @@
version: "2" version: "2"
# Config schema uses the golangci-lint v2 layout (settings live under
# linters.settings, not top-level linters-settings) so that the
# thresholds below are actually applied by golangci-lint >= v2.
run: run:
timeout: 5m timeout: 5m
modules-download-mode: readonly modules-download-mode: readonly
@@ -14,19 +18,17 @@ linters:
- wsl # Deprecated, replaced by wsl_v5 - wsl # Deprecated, replaced by wsl_v5
- wrapcheck # Too verbose for internal packages - wrapcheck # Too verbose for internal packages
- varnamelen # Short names like db, id are idiomatic Go - varnamelen # Short names like db, id are idiomatic Go
settings:
linters-settings: lll:
lll: line-length: 88
line-length: 88 funlen:
funlen: lines: 80
lines: 80 statements: 50
statements: 50 cyclop:
cyclop: max-complexity: 15
max-complexity: 15 dupl:
dupl: threshold: 100
threshold: 100
issues: issues:
exclude-use-default: false
max-issues-per-linter: 0 max-issues-per-linter: 0
max-same-issues: 0 max-same-issues: 0

View File

@@ -4,8 +4,8 @@ FROM golang@sha256:f6751d823c26342f9506c03797d2527668d095b0a15f1862cddb4d927a7a4
RUN apk add --no-cache git make gcc musl-dev binutils-gold RUN apk add --no-cache git make gcc musl-dev binutils-gold
# golangci-lint v2.10.1 # golangci-lint v2.12.2, 2026-08-07
RUN go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@5d1e709b7be35cb2025444e19de266b056b7b7ee RUN go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
# goimports v0.42.0 # goimports v0.42.0
RUN go install golang.org/x/tools/cmd/goimports@009367f5c17a8d4c45a961a3a509277190a9a6f0 RUN go install golang.org/x/tools/cmd/goimports@009367f5c17a8d4c45a961a3a509277190a9a6f0

View File

@@ -26,6 +26,11 @@ $(HOME)/Documents/_SYSADMIN/cyberdyne path.
# Completed Steps # Completed Steps
* 2026-08-07: updated golangci-lint to v2.12.2: canonical
`.golangci.yml` (settings moved under `linters.settings` so the
configured thresholds actually apply), version pins bumped in
`Dockerfile` and `script/bootstrap`, and long lines wrapped to
satisfy the now-effective `lll` limit of 88
* 2026-02-02: correctness pass: track actual bytes read instead of * 2026-02-02: correctness pass: track actual bytes read instead of
stale file size, atomic failure tracking in ProcessCheck, detect stale file size, atomic failure tracking in ProcessCheck, detect
file modification during checksum (TOCTOU), propagate countFiles file modification during checksum (TOCTOU), propagate countFiles

View File

@@ -72,7 +72,8 @@ func (s *Stats) Print(opts *options, operation string) {
return return
} }
fmt.Fprintf(os.Stderr, "\n%s complete: %d files processed, %d skipped, %d failed, %s bytes in %s\n", fmt.Fprintf(os.Stderr,
"\n%s complete: %d files processed, %d skipped, %d failed, %s bytes in %s\n",
operation, operation,
s.FilesProcessed, s.FilesProcessed,
s.FilesSkipped, s.FilesSkipped,
@@ -166,11 +167,15 @@ func expandPaths(args []string) ([]string, error) {
} }
// processFunc processes a single path within a command's run loop. // processFunc processes a single path within a command's run loop.
type processFunc func(opts *options, path string, stats *Stats, bar *progressbar.ProgressBar) error type processFunc func(
opts *options, path string, stats *Stats, bar *progressbar.ProgressBar,
) error
// countAndBar counts the files under paths and returns a progress bar sized // countAndBar counts the files under paths and returns a progress bar sized
// to that total. It always returns either a non-nil bar or a non-nil error. // to that total. It always returns either a non-nil bar or a non-nil error.
func countAndBar(opts *options, paths []string, desc string) (*progressbar.ProgressBar, error) { func countAndBar(
opts *options, paths []string, desc string,
) (*progressbar.ProgressBar, error) {
total, err := countFilesMultiple(opts, paths) total, err := countFilesMultiple(opts, paths)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -188,7 +193,9 @@ func finishBar(bar *progressbar.ProgressBar) {
// runOverPaths runs process over each path, sharing the progress/stats // runOverPaths runs process over each path, sharing the progress/stats
// bookkeeping common to the sum-add, sum-update and clear commands. // bookkeeping common to the sum-add, sum-update and clear commands.
func runOverPaths(opts *options, args []string, desc, op string, process processFunc) error { func runOverPaths(
opts *options, args []string, desc, op string, process processFunc,
) error {
paths, err := expandPaths(args) paths, err := expandPaths(args)
if err != nil { if err != nil {
return err return err
@@ -253,46 +260,54 @@ func newSumCmd(opts *options) *cobra.Command {
return cmd return cmd
} }
func processSumAdd(opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar) error { func processSumAdd(
return walkAndProcess(opts, dir, stats, bar, func(p string, info os.FileInfo, s *Stats) error { opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar,
if hasXattr(p, checksumKey) { ) error {
atomic.AddInt64(&s.FilesSkipped, 1) return walkAndProcess(opts, dir, stats, bar,
func(p string, info os.FileInfo, s *Stats) error {
if hasXattr(p, checksumKey) {
atomic.AddInt64(&s.FilesSkipped, 1)
return nil return nil
} }
err := writeChecksumAndTime(opts, p, info, s) err := writeChecksumAndTime(opts, p, info, s)
if err != nil { if err != nil {
atomic.AddInt64(&s.FilesFailed, 1)
return err
}
return nil
})
}
func processSumUpdate(opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar) error {
return walkAndProcess(opts, dir, stats, bar, func(p string, info os.FileInfo, s *Stats) error {
t, err := readSumTime(p)
if err != nil || info.ModTime().After(t) {
werr := writeChecksumAndTime(opts, p, info, s)
if werr != nil {
atomic.AddInt64(&s.FilesFailed, 1) atomic.AddInt64(&s.FilesFailed, 1)
return werr return err
} }
return nil return nil
} })
atomic.AddInt64(&s.FilesSkipped, 1)
return nil
})
} }
func writeChecksumAndTime(opts *options, path string, info os.FileInfo, stats *Stats) error { func processSumUpdate(
opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar,
) error {
return walkAndProcess(opts, dir, stats, bar,
func(p string, info os.FileInfo, s *Stats) error {
t, err := readSumTime(p)
if err != nil || info.ModTime().After(t) {
werr := writeChecksumAndTime(opts, p, info, s)
if werr != nil {
atomic.AddInt64(&s.FilesFailed, 1)
return werr
}
return nil
}
atomic.AddInt64(&s.FilesSkipped, 1)
return nil
})
}
func writeChecksumAndTime(
opts *options, path string, info os.FileInfo, stats *Stats,
) error {
// Record mtime before hashing to detect modifications during hash. // Record mtime before hashing to detect modifications during hash.
mtimeBefore := info.ModTime() mtimeBefore := info.ModTime()
@@ -363,24 +378,27 @@ func newClearCmd(opts *options) *cobra.Command {
} }
} }
func processClear(opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar) error { func processClear(
return walkAndProcess(opts, dir, stats, bar, func(p string, info os.FileInfo, s *Stats) error { opts *options, dir string, stats *Stats, bar *progressbar.ProgressBar,
cleared, err := clearOne(opts, p) ) error {
if err != nil { return walkAndProcess(opts, dir, stats, bar,
atomic.AddInt64(&s.FilesFailed, 1) func(p string, info os.FileInfo, s *Stats) error {
cleared, err := clearOne(opts, p)
if err != nil {
atomic.AddInt64(&s.FilesFailed, 1)
return err return err
} }
if cleared { if cleared {
atomic.AddInt64(&s.FilesProcessed, 1) atomic.AddInt64(&s.FilesProcessed, 1)
atomic.AddInt64(&s.BytesProcessed, info.Size()) atomic.AddInt64(&s.BytesProcessed, info.Size())
} else { } else {
atomic.AddInt64(&s.FilesSkipped, 1) atomic.AddInt64(&s.FilesSkipped, 1)
} }
return nil return nil
}) })
} }
// clearOne removes both checksum xattrs from a single path, reporting // clearOne removes both checksum xattrs from a single path, reporting
@@ -428,7 +446,8 @@ func newCheckCmd(opts *options) *cobra.Command {
return runCheck(opts, a, cont) return runCheck(opts, a, cont)
}, },
} }
cmd.Flags().BoolVar(&cont, "continue", false, "continue after errors and report each file") cmd.Flags().BoolVar(&cont, "continue", false,
"continue after errors and report each file")
return cmd return cmd
} }
@@ -472,13 +491,16 @@ func runCheck(opts *options, args []string, cont bool) error {
return finalErr return finalErr
} }
func processCheck(opts *options, dir string, cont bool, stats *Stats, bar *progressbar.ProgressBar) error { func processCheck(
opts *options, dir string, cont bool, stats *Stats, bar *progressbar.ProgressBar,
) error {
// Track initial failed count to detect failures during this walk. // Track initial failed count to detect failures during this walk.
initialFailed := atomic.LoadInt64(&stats.FilesFailed) initialFailed := atomic.LoadInt64(&stats.FilesFailed)
err := walkAndProcess(opts, dir, stats, bar, func(p string, _ os.FileInfo, s *Stats) error { err := walkAndProcess(opts, dir, stats, bar,
return checkOne(opts, p, cont, s) func(p string, _ os.FileInfo, s *Stats) error {
}) return checkOne(opts, p, cont, s)
})
if err != nil { if err != nil {
if errors.Is(err, errVerification) { if errors.Is(err, errVerification) {
return errVerification return errVerification
@@ -742,7 +764,8 @@ func hasXattr(path, key string) bool {
func fileMultihash(path string) ([]byte, int64, error) { func fileMultihash(path string) ([]byte, int64, error) {
// The path is supplied by the operator as the tree to checksum; reading // The path is supplied by the operator as the tree to checksum; reading
// it is the entire purpose of the tool. // it is the entire purpose of the tool.
f, err := os.Open(path) //nolint:gosec // G304: operator-specified path is the intended input //nolint:gosec // G304: operator-specified path is the intended input
f, err := os.Open(path)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }

View File

@@ -4,14 +4,14 @@
# installed tools are skipped. Base tooling comes from nix, apt, brew, # installed tools are skipped. Base tooling comes from nix, apt, brew,
# or apk (detected in that order); assumes nothing is present. # or apk (detected in that order); assumes nothing is present.
# golangci-lint and goimports are installed via `go install` at the same # golangci-lint and goimports are installed via `go install` at the same
# pinned commits the Dockerfile uses (never "latest"). # pinned refs the Dockerfile uses (never "latest").
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
# Pinned versions, 2026-07-07 (same pins as the Dockerfile) # Pinned versions, 2026-08-07 (same pins as the Dockerfile)
# golangci-lint v2.10.1 # golangci-lint v2.12.2
GOLANGCI_LINT_REF="github.com/golangci/golangci-lint/v2/cmd/golangci-lint@5d1e709b7be35cb2025444e19de266b056b7b7ee" GOLANGCI_LINT_REF="github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2"
# goimports v0.42.0 # goimports v0.42.0
GOIMPORTS_REF="golang.org/x/tools/cmd/goimports@009367f5c17a8d4c45a961a3a509277190a9a6f0" GOIMPORTS_REF="golang.org/x/tools/cmd/goimports@009367f5c17a8d4c45a961a3a509277190a9a6f0"