repo_description: Talos Linux is a modern Linux distribution built for Kubernetes.
repo_topics: cloud-native containerd go grpc kubernetes kubernetes-distribution linux linux-distribution musl
code: // This Source Code Form is subject to the terms of the Mozilla Public
  // License, v. 2.0. If a copy of the MPL was not distributed with this
  // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  
  package archiver
  
  import (
  	"archive/tar"
  	"bytes"
  	"context"
  	"errors"
  	"fmt"
  	"io"
  	"log"
  	"os"
  	"slices"
  	"syscall"
  
  	multierror "github.com/hashicorp/go-multierror"
  )
  
  // Tar creates .tar archive and writes it to output for every item in paths channel.
  func Tar(ctx context.Context, paths <-chan FileItem, output io.Writer) error {
  	tw := tar.NewWriter(output)
  	//nolint:errcheck
  	defer tw.Close()
  
  	var multiErr *multierror.Error
  
  	buf := make([]byte, 4096)
  
  	for fi := range paths {
  		if fi.Error != nil {
  			multiErr = multierror.Append(multiErr, fmt.Errorf("skipping %q: %s", fi.FullPath, fi.Error))
  
  			continue
  		}
  
  		err := processFile(ctx, tw, fi, buf)
  		if err != nil {
  			multiErr = multierror.Append(multiErr, fmt.Errorf("skipping %q: %s", fi.FullPath, err))
  		}
  	}
  
  	if err := tw.Close(); err != nil {
  		multiErr = multierror.Append(multiErr, err)
  	}
  
  	return multiErr.ErrorOrNil()
  }
  
  //nolint:gocyclo
  func processFile(ctx context.Context, tw *tar.Writer, fi FileItem, buf []byte) error {
  	header, err := tar.FileInfoHeader(fi.FileInfo, fi.Link)
  	if err != nil {
  		// not supported by tar
  		return err
  	}
  
  	header.Name = fi.RelPath
  	if fi.FileInfo.IsDir() {
  		header.Name += string(os.PathSeparator)
  	}
  
  	skipData := false
  
  	switch header.Typeflag {
  	case tar.TypeLink, tar.TypeSymlink, tar.TypeChar, tar.TypeBlock, tar.TypeDir, tar.TypeFifo:
  		// no data for these types, move on
  		skipData = true
  	}
  
  	var r io.Reader
  
  	if !skipData {
  		var fp *os.File
  
  		fp, err = os.Open(fi.FullPath)
  		if err != nil {
  			return err
  		}
  
  		defer fp.Close() //nolint:errcheck
  
  		r = fp
  	}
  
  	if !skipData && header.Size == 0 {
  		// Linux reports /proc files as zero length, but they might have data,
  		// so we try to read limited amount of data from it to determine the size
  		var n int
  
  		n, err = r.Read(buf)
  
  		switch {
  		case err == io.EOF:
  			// file is empty for real
  			skipData = true
  		case err != nil:
  			// error reading from the file
  			if errors.Is(err, syscall.EINVAL) {
  				// some files are not supported by os.Open, e.g. /proc/sys/net/ipv4/conf/all/accept_local
  				skipData = true
  			} else {
  				return err
  			}
  		case n < len(buf):
  			header.Size = int64(n)
  			r = bytes.NewReader(slices.Clone(buf[:n]))
  		default:
  			// none matched so the file is bigger than we expected, ignore it and copy as zero size
  			skipData = true
  		}
  	}
  
  	err = tw.WriteHeader(header)
  	if err != nil {
  		return err
  	}
  
  	if skipData {
  		return nil
  	}
  
  	return archiveFile(ctx, tw, fi, r, buf)
  }
  
  func archiveFile(ctx context.Context, tw io.Writer, fi FileItem, r io.Reader, buf []byte) error {
  	for {
  		n, err := r.Read(buf)
  		if err != nil {
  			if err == io.EOF {
  				return nil
  			}
  
  			return err
  		}
  
  		select {
  		case <-ctx.Done():
  			return ctx.Err()
  		default:
  		}
  
  		_, err = tw.Write(buf[:n])
  		if err != nil {
  			if err == tar.ErrWriteTooLong {
  				log.Printf("ignoring long write for %q", fi.FullPath)
  
  				return nil
  			}
  
  			return err
  		}
  	}
  }
  