Files
bsdaily/internal/bsdaily/copy.go
T
clawbot c16f177575
check / check (push) Waiting to run
Adopt the shared .golangci.yml and fix the code to it (closes #6)
Vendor .golangci.yml byte-identical from sneak/prompts at cc440118 and
move the Dockerfile lint phase to golangci-lint v2.14.0 by the digest
REPO_POLICIES.md names. Fix the code to that config with flags, help
text, output files, SQL and the order of steps unchanged; long
functions are split into named steps.

Judgement call: the extraction transaction is now rolled back on every
early return; the old deferred rollback missed most failures and could
dereference a nil transaction.
Thirteen //nolint directives (gosec, unconvert, mnd, unqueryvet), each
with its reason.

Model: opus-5-5
2026-10-06 14:24:48 +02:00

97 lines
2.3 KiB
Go

package bsdaily
import (
"errors"
"fmt"
"io"
"log/slog"
"os"
"time"
)
const (
// 256MB buffer for large file copies from fast storage
copyBufferSize = 256 * 1024 * 1024
oneMB = 1024 * 1024
oneGB = 1024 * 1024 * 1024
)
var errShortCopy = errors.New("short copy")
// CopyFile copies src to dst through a large buffer, pre-allocating dst
// and syncing it to disk before returning.
func CopyFile(src, dst string) (err error) {
startTime := time.Now()
slog.Info("copying file", "src", src, "dst", dst)
//nolint:gosec // src is a path this package built
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("opening source %s: %w", src, err)
}
defer func() {
cerr := srcFile.Close()
if cerr != nil {
slog.Warn("failed to close source file", "src", src, "error", cerr)
}
}()
srcInfo, err := srcFile.Stat()
if err != nil {
return fmt.Errorf("stat source %s: %w", src, err)
}
// For large files, advise kernel about sequential read pattern
if srcInfo.Size() > oneGB {
applyFileAdvice(srcFile, srcInfo.Size())
}
//nolint:gosec // dst is a path this package built
dstFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("creating destination %s: %w", dst, err)
}
defer func() {
cerr := dstFile.Close()
if cerr != nil && err == nil {
err = fmt.Errorf("closing destination %s: %w", dst, cerr)
}
}()
// Pre-allocate space for the destination file to avoid fragmentation
truncErr := dstFile.Truncate(srcInfo.Size())
if truncErr != nil {
slog.Warn("failed to pre-allocate destination file", "error", truncErr)
}
// Use a much larger buffer for NVMe-speed copies
buf := make([]byte, copyBufferSize)
written, err := io.CopyBuffer(dstFile, srcFile, buf)
if err != nil {
return fmt.Errorf("copying data: %w", err)
}
if written != srcInfo.Size() {
return fmt.Errorf("%w: wrote %d bytes, expected %d",
errShortCopy, written, srcInfo.Size())
}
err = dstFile.Sync()
if err != nil {
return fmt.Errorf("syncing destination %s: %w", dst, err)
}
elapsed := time.Since(startTime)
throughputMBps := float64(written) / elapsed.Seconds() / oneMB
slog.Info("file copied", "dst", dst, "bytes", written,
"elapsed", elapsed.Round(time.Millisecond),
"throughput_mbps", fmt.Sprintf("%.1f", throughputMBps))
return nil
}