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 func() { if cerr := srcFile.Close(); 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()) } 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 }