Repository: influxdata/influxdb
File: pkg/tar/stream.go (Go)

Code before:
// / Stream a single file to tw, using tarHeaderFileName instead of the actual filename
// e.g., when we want to write a *.tmp file using the original file's non-tmp name.
func StreamRenameFile(f os.FileInfo, tarHeaderFileName, relativePath, fullPath string, tw *tar.Writer, bufSize uint64) error {
	buf := make([]byte, bufSize)
	h, err := tar.FileInfoHeader(f, f.Name())
	if err != nil {
		return err
	}
	h.Name = filepath.ToSlash(filepath.Join(relativePath, tarHeaderFileName))

	if err := tw.WriteHeader(h); err != nil {
		return err
	}

	if !f.Mode().IsRegular() {
		return nil
	}

	fr, err := os.Open(fullPath)
	if err != nil {
		return err
	}

	defer fr.Close()

	_, err = io.CopyBuffer(tw, fr, buf)

	return err
}

// Restore reads a tar archive from r and extracts all of its files into dir,
// using only the base name of each file.
func Restore(r io.Reader, dir string) error {
	tr := tar.NewReader(r)
	for {
		if err := extractFile(tr, dir); err == io.EOF {
			break
		} else if err != nil {
			return err
		}
	}

	return file.SyncDir(dir)
}

// extractFile copies the next file from tr into dir, using the file's base name.
func extractFile(tr *tar.Reader, dir string) error {
	// Read next archive file.
	hdr, err := tr.Next()
	if err != nil {
		return err

Review comment (suggestion):
`io.CopyBuffer` has slightly different semantics than `io.CopyN` for errors. `io.CopyN` will return an `io.EOF` error if the number of bytes copied is less than `size`, even if there was no error detected. `io.CopyBuffer` will not return an error in that circumstance.

To preserve the semantics of `CopyN`, we should check the number of bytes copied and return an error if it is less than the number we expected to copy. 

Will this make a real world difference? I lean towards no. However, I am aware of at least one lawsuit over a backup product reporting backups as successful when they were in fact incomplete. I don't want to risk introducing such an issue with this PR.

For reference, here's Go 1.24.9's `io.CopyN` implementation:
```
// CopyN copies n bytes (or until an error) from src to dst.
// It returns the number of bytes copied and the earliest
// error encountered while copying.
// On return, written == n if and only if err == nil.
//
// If dst implements [ReaderFrom], the copy is implemented using it.
func CopyN(dst Writer, src Reader, n int64) (written int64, err error) {
	written, err = Copy(dst, LimitReader(src, n))
	if written == n {
		return n, nil
	}
	if written < n && err == nil {
		// src stopped early; must have been EOF.
		err = EOF
	}
	return
}
```



Code after:
// / Stream a single file to tw, using tarHeaderFileName instead of the actual filename
// e.g., when we want to write a *.tmp file using the original file's non-tmp name.
func StreamRenameFile(f os.FileInfo, tarHeaderFileName, relativePath, fullPath string, tw *tar.Writer, bufSize uint64) error {
	// We NEVER want this to be 0 (or less). This is just a safety harness.
	// influxd will panic if we pass a 0 size buffer into CopyBuffer.
	if bufSize <= 0 {
		bufSize = 1024 * 1024 // 1MB
	}
	buf := make([]byte, bufSize)
	h, err := tar.FileInfoHeader(f, f.Name())
	if err != nil {
		return err
	}
	h.Name = filepath.ToSlash(filepath.Join(relativePath, tarHeaderFileName))

	if err := tw.WriteHeader(h); err != nil {
		return err
	}

	if !f.Mode().IsRegular() {
		return nil
	}

	fr, err := os.Open(fullPath)
	if err != nil {
		return err
	}

	defer fr.Close()

	bytesWritten, err := io.CopyBuffer(tw, fr, buf)
	if err != nil {
		return err
	} else if bytesWritten != h.Size {
		return fmt.Errorf("StreamRenameFile: Error while copying buffer, expected %d bytes but wrote %d.", h.Size, bytesWritten)
	}

	return nil
}

// Restore reads a tar archive from r and extracts all of its files into dir,
// using only the base name of each file.
func Restore(r io.Reader, dir string) error {
	tr := tar.NewReader(r)
	for {
		if err := extractFile(tr, dir); err == io.EOF {
			break
		} else if err != nil {
			return err
		}
	}