Andrey Smirnov 6d5ee0ca80
feat(init): unify filesystem walkers for ls/cp APIs (#779)
This unifies low-level filesystem walker code for `ls` and `cp`.

New features:

* `ls` now reports relative filenames
* `ls` now prints symlink destination for symlinks
* `cp` now properly always reports errors from the API
* `cp` now reports all the errors back to the client

Example for `ls`:

```
osctl-linux-amd64 --talosconfig talosconfig ls -l /var
MODE          SIZE(B)   LASTMOD       NAME
drwxr-xr-x    4096      Jun 26 2019   .
Lrwxrwxrwx    4         Jun 25 2019   etc -> /etc
drwxr-xr-x    4096      Jun 26 2019   lib
drwxr-xr-x    4096      Jun 21 2019   libexec
drwxr-xr-x    4096      Jun 26 2019   log
drwxr-xr-x    4096      Jun 21 2019   mail
drwxr-xr-x    4096      Jun 26 2019   opt
Lrwxrwxrwx    6         Jun 21 2019   run -> ../run
drwxr-xr-x    4096      Jun 21 2019   spool
dtrwxrwxrwx   4096      Jun 21 2019   tmp
-rw-------    14979     Jun 26 2019   userdata.yaml
```

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2019-06-26 17:43:09 +03:00

129 lines
2.6 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"
"fmt"
"io"
"log"
"os"
multierror "github.com/hashicorp/go-multierror"
)
// 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()
var multiErr *multierror.Error
for fi := range paths {
if fi.Error != nil {
multiErr = multierror.Append(multiErr, fmt.Errorf("skipping %q: %s", fi.FullPath, fi.Error))
continue
}
header, err := tar.FileInfoHeader(fi.FileInfo, fi.Link)
if err != nil {
// not supported by tar
multiErr = multierror.Append(multiErr, fmt.Errorf("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 {
multiErr = multierror.Append(multiErr, fmt.Errorf("skipping %q: %s", fi.FullPath, err))
continue
}
}
err = tw.WriteHeader(header)
if err != nil {
//nolint: errcheck
fp.Close()
multiErr = multierror.Append(multiErr, err)
return multiErr
}
if !skipData {
err = archiveFile(ctx, tw, fi, fp)
if err != nil {
multiErr = multierror.Append(multiErr, err)
return multiErr
}
}
}
if err := tw.Close(); err != nil {
multiErr = multierror.Append(multiErr, err)
}
return multiErr.ErrorOrNil()
}
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()
}