Andrey Smirnov 9ed45f7090 feat(osctl): implement 'cp' to copy files out of the Talos node (#740)
Actual API is implemented in the `init`, as it has access to root
filesystem. `osd` proxies API back to `init` with some tricks to support
grpc streaming.

Given some absolute path, `init` produces and streams back .tar.gz
archive with filesystem contents.

`osctl cp` works in two modes. First mode streams data to stdout, so
that we can do e.g.: `osctl cp /etc - | tar tz`. Second mode extracts
archive to specified location, dropping ownership info and adjusting
permissions a bit. Timestamps are not preserved.

If full dump with owner/permisisons is required, it's better to stream
data to `tar xz`, for quick and dirty look into filesystem contents
under unprivileged user it's easier to use in-place extraction.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2019-06-20 17:02:58 -07:00

113 lines
2.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"
"context"
"io"
"log"
"os"
)
// Tar creates .tar archive and writes it to output for every item in paths channel
//
//nolint: gocyclo
func Tar(ctx context.Context, paths <-chan FileItem, output io.Writer) error {
tw := tar.NewWriter(output)
//nolint: errcheck
defer tw.Close()
for fi := range paths {
header, err := tar.FileInfoHeader(fi.FileInfo, fi.Link)
if err != nil {
// not supported by tar
log.Printf("skipping %q: %s", fi.FullPath, err)
continue
}
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
}
if header.Size == 0 {
// skip files with zero length
//
// this might skip contents for special files in /proc, but
// anyways we can't archive them properly if we don't know size beforehand
skipData = true
}
var fp *os.File
if !skipData {
fp, err = os.Open(fi.FullPath)
if err != nil {
log.Printf("skipping %q: %s", fi.FullPath, err)
continue
}
}
err = tw.WriteHeader(header)
if err != nil {
//nolint: errcheck
fp.Close()
return err
}
if !skipData {
err = archiveFile(ctx, tw, fi, fp)
if err != nil {
return err
}
}
}
return tw.Close()
}
func archiveFile(ctx context.Context, tw io.Writer, fi FileItem, fp *os.File) error {
//nolint: errcheck
defer fp.Close()
buf := make([]byte, 4096)
for {
n, err := fp.Read(buf)
if err != nil {
if err == io.EOF {
break
}
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
}
}
return fp.Close()
}