mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-08 22:21:16 +02:00
Use new functions and methods from runtime module. Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
157 lines
3.1 KiB
Go
157 lines
3.1 KiB
Go
// 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"
|
|
"syscall"
|
|
|
|
multierror "github.com/hashicorp/go-multierror"
|
|
"github.com/siderolabs/gen/slices"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
}
|
|
}
|