Files
bsdaily/internal/bsdaily/copy.go
sneak 7316054b07 Add README, LICENSE, Makefile, Dockerfile, and CI
Add a detailed README, WTFPL LICENSE, and build/CI tooling modeled on
the vaultik repo (Makefile, multi-stage digest-pinned Dockerfile,
.gitea/workflows/check.yml). Bump Go to 1.26.4 and pin golangci-lint
to v2.12.2. gofmt existing sources so the new fmt-check gate passes.
2026-06-28 10:08:44 +02:00

73 lines
1.8 KiB
Go

package bsdaily
import (
"fmt"
"io"
"log/slog"
"os"
"time"
)
const (
copyBufferSize = 256 * 1024 * 1024 // 256MB buffer for large file copies from fast storage
oneGB = 1024 * 1024 * 1024
)
func CopyFile(src, dst string) (err error) {
startTime := time.Now()
slog.Info("copying file", "src", src, "dst", dst)
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("opening source %s: %w", src, err)
}
defer srcFile.Close()
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())
}
dstFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("creating destination %s: %w", dst, err)
}
defer func() {
if cerr := dstFile.Close(); cerr != nil && err == nil {
err = fmt.Errorf("closing destination %s: %w", dst, cerr)
}
}()
// Pre-allocate space for the destination file to avoid fragmentation
if err := dstFile.Truncate(srcInfo.Size()); err != nil {
slog.Warn("failed to pre-allocate destination file", "error", err)
}
// 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("short copy: wrote %d bytes, expected %d", written, srcInfo.Size())
}
if err := dstFile.Sync(); err != nil {
return fmt.Errorf("syncing destination %s: %w", dst, err)
}
elapsed := time.Since(startTime)
throughputMBps := float64(written) / elapsed.Seconds() / (1024 * 1024)
slog.Info("file copied", "dst", dst, "bytes", written,
"elapsed", elapsed.Round(time.Millisecond),
"throughput_mbps", fmt.Sprintf("%.1f", throughputMBps))
return nil
}