Merge pull request #1903 from darrenstahlmsft/ArchiveOpts
Implement Archive.apply on Windows
This commit is contained in:
commit
e479165a38
52
archive/strconv.go
Normal file
52
archive/strconv.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dmcgowan/go-tar"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Forked from https://github.com/golang/go/blob/master/src/archive/tar/strconv.go
|
||||||
|
// as archive/tar doesn't support CreationTime, but does handle PAX time parsing,
|
||||||
|
// and there's no need to re-invent the wheel.
|
||||||
|
|
||||||
|
// parsePAXTime takes a string of the form %d.%d as described in the PAX
|
||||||
|
// specification. Note that this implementation allows for negative timestamps,
|
||||||
|
// which is allowed for by the PAX specification, but not always portable.
|
||||||
|
func parsePAXTime(s string) (time.Time, error) {
|
||||||
|
const maxNanoSecondDigits = 9
|
||||||
|
|
||||||
|
// Split string into seconds and sub-seconds parts.
|
||||||
|
ss, sn := s, ""
|
||||||
|
if pos := strings.IndexByte(s, '.'); pos >= 0 {
|
||||||
|
ss, sn = s[:pos], s[pos+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the seconds.
|
||||||
|
secs, err := strconv.ParseInt(ss, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, tar.ErrHeader
|
||||||
|
}
|
||||||
|
if len(sn) == 0 {
|
||||||
|
return time.Unix(secs, 0), nil // No sub-second values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the nanoseconds.
|
||||||
|
if strings.Trim(sn, "0123456789") != "" {
|
||||||
|
return time.Time{}, tar.ErrHeader
|
||||||
|
}
|
||||||
|
if len(sn) < maxNanoSecondDigits {
|
||||||
|
sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
|
||||||
|
} else {
|
||||||
|
sn = sn[:maxNanoSecondDigits] // Right truncate
|
||||||
|
}
|
||||||
|
nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed
|
||||||
|
if len(ss) > 0 && ss[0] == '-' {
|
||||||
|
return time.Unix(secs, -1*int64(nsecs)), nil // Negative correction
|
||||||
|
}
|
||||||
|
return time.Unix(secs, int64(nsecs)), nil
|
||||||
|
}
|
203
archive/tar.go
203
archive/tar.go
@ -87,12 +87,23 @@ const (
|
|||||||
|
|
||||||
// Apply applies a tar stream of an OCI style diff tar.
|
// Apply applies a tar stream of an OCI style diff tar.
|
||||||
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
||||||
func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
|
func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int64, error) {
|
||||||
root = filepath.Clean(root)
|
root = filepath.Clean(root)
|
||||||
|
|
||||||
|
var options ApplyOptions
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(&options); err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed to apply option")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply(ctx, root, tar.NewReader(r), options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyNaive applies a tar stream of an OCI style diff tar.
|
||||||
|
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
||||||
|
func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||||
var (
|
var (
|
||||||
tr = tar.NewReader(r)
|
|
||||||
size int64
|
|
||||||
dirs []*tar.Header
|
dirs []*tar.Header
|
||||||
|
|
||||||
// Used for handling opaque directory markers which
|
// Used for handling opaque directory markers which
|
||||||
@ -285,6 +296,99 @@ func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
|
|||||||
return size, nil
|
return size, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header, reader io.Reader) error {
|
||||||
|
// hdr.Mode is in linux format, which we can use for syscalls,
|
||||||
|
// but for os.Foo() calls we need the mode converted to os.FileMode,
|
||||||
|
// so use hdrInfo.Mode() (they differ for e.g. setuid bits)
|
||||||
|
hdrInfo := hdr.FileInfo()
|
||||||
|
|
||||||
|
switch hdr.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
// Create directory unless it exists as a directory already.
|
||||||
|
// In that case we just want to merge the two
|
||||||
|
if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) {
|
||||||
|
if err := mkdir(path, hdrInfo.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case tar.TypeReg, tar.TypeRegA:
|
||||||
|
file, err := openFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, hdrInfo.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = copyBuffered(ctx, file, reader)
|
||||||
|
if err1 := file.Close(); err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case tar.TypeBlock, tar.TypeChar:
|
||||||
|
// Handle this is an OS-specific way
|
||||||
|
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case tar.TypeFifo:
|
||||||
|
// Handle this is an OS-specific way
|
||||||
|
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case tar.TypeLink:
|
||||||
|
targetPath, err := fs.RootPath(extractDir, hdr.Linkname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Link(targetPath, path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case tar.TypeSymlink:
|
||||||
|
if err := os.Symlink(hdr.Linkname, path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case tar.TypeXGlobalHeader:
|
||||||
|
log.G(ctx).Debug("PAX Global Extended Headers found and ignored")
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unhandled tar header type %d\n", hdr.Typeflag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lchown is not supported on Windows.
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range hdr.PAXRecords {
|
||||||
|
if strings.HasPrefix(key, paxSchilyXattr) {
|
||||||
|
key = key[len(paxSchilyXattr):]
|
||||||
|
if err := setxattr(path, key, value); err != nil {
|
||||||
|
if errors.Cause(err) == syscall.ENOTSUP {
|
||||||
|
log.G(ctx).WithError(err).Warnf("ignored xattr %s in archive", key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no LChmod, so ignore mode for symlink. Also, this
|
||||||
|
// must happen after chown, as that can modify the file mode
|
||||||
|
if err := handleLChmod(hdr, path, hdrInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime))
|
||||||
|
}
|
||||||
|
|
||||||
type changeWriter struct {
|
type changeWriter struct {
|
||||||
tw *tar.Writer
|
tw *tar.Writer
|
||||||
source string
|
source string
|
||||||
@ -442,99 +546,6 @@ func (cw *changeWriter) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header, reader io.Reader) error {
|
|
||||||
// hdr.Mode is in linux format, which we can use for syscalls,
|
|
||||||
// but for os.Foo() calls we need the mode converted to os.FileMode,
|
|
||||||
// so use hdrInfo.Mode() (they differ for e.g. setuid bits)
|
|
||||||
hdrInfo := hdr.FileInfo()
|
|
||||||
|
|
||||||
switch hdr.Typeflag {
|
|
||||||
case tar.TypeDir:
|
|
||||||
// Create directory unless it exists as a directory already.
|
|
||||||
// In that case we just want to merge the two
|
|
||||||
if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) {
|
|
||||||
if err := mkdir(path, hdrInfo.Mode()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case tar.TypeReg, tar.TypeRegA:
|
|
||||||
file, err := openFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, hdrInfo.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = copyBuffered(ctx, file, reader)
|
|
||||||
if err1 := file.Close(); err == nil {
|
|
||||||
err = err1
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case tar.TypeBlock, tar.TypeChar:
|
|
||||||
// Handle this is an OS-specific way
|
|
||||||
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case tar.TypeFifo:
|
|
||||||
// Handle this is an OS-specific way
|
|
||||||
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case tar.TypeLink:
|
|
||||||
targetPath, err := fs.RootPath(extractDir, hdr.Linkname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.Link(targetPath, path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case tar.TypeSymlink:
|
|
||||||
if err := os.Symlink(hdr.Linkname, path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case tar.TypeXGlobalHeader:
|
|
||||||
log.G(ctx).Debug("PAX Global Extended Headers found and ignored")
|
|
||||||
return nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.Errorf("unhandled tar header type %d\n", hdr.Typeflag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lchown is not supported on Windows.
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range hdr.PAXRecords {
|
|
||||||
if strings.HasPrefix(key, paxSchilyXattr) {
|
|
||||||
key = key[len(paxSchilyXattr):]
|
|
||||||
if err := setxattr(path, key, value); err != nil {
|
|
||||||
if errors.Cause(err) == syscall.ENOTSUP {
|
|
||||||
log.G(ctx).WithError(err).Warnf("ignored xattr %s in archive", key)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is no LChmod, so ignore mode for symlink. Also, this
|
|
||||||
// must happen after chown, as that can modify the file mode
|
|
||||||
if err := handleLChmod(hdr, path, hdrInfo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime))
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyBuffered(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
|
func copyBuffered(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
|
||||||
buf := bufferPool.Get().(*[]byte)
|
buf := bufferPool.Get().(*[]byte)
|
||||||
defer bufferPool.Put(buf)
|
defer bufferPool.Put(buf)
|
||||||
|
4
archive/tar_opts.go
Normal file
4
archive/tar_opts.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package archive
|
||||||
|
|
||||||
|
// ApplyOpt allows setting mutable archive apply properties on creation
|
||||||
|
type ApplyOpt func(options *ApplyOptions) error
|
7
archive/tar_opts_unix.go
Normal file
7
archive/tar_opts_unix.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package archive
|
||||||
|
|
||||||
|
// ApplyOptions provides additional options for an Apply operation
|
||||||
|
type ApplyOptions struct {
|
||||||
|
}
|
28
archive/tar_opts_windows.go
Normal file
28
archive/tar_opts_windows.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package archive
|
||||||
|
|
||||||
|
// ApplyOptions provides additional options for an Apply operation
|
||||||
|
type ApplyOptions struct {
|
||||||
|
ParentLayerPaths []string // Parent layer paths used for Windows layer apply
|
||||||
|
IsWindowsContainerLayer bool // True if the tar stream to be applied is a Windows Container Layer
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithParentLayers adds parent layers to the apply process this is required
|
||||||
|
// for all Windows layers except the base layer.
|
||||||
|
func WithParentLayers(parentPaths []string) ApplyOpt {
|
||||||
|
return func(options *ApplyOptions) error {
|
||||||
|
options.ParentLayerPaths = parentPaths
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsWindowsContainerLayer indicates that the tar stream to apply is that of
|
||||||
|
// a Windows Container Layer. The caller must be holding SeBackupPrivilege and
|
||||||
|
// SeRestorePrivilege.
|
||||||
|
func AsWindowsContainerLayer() ApplyOpt {
|
||||||
|
return func(options *ApplyOptions) error {
|
||||||
|
options.IsWindowsContainerLayer = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
package archive
|
package archive
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -128,3 +129,9 @@ func getxattr(path, attr string) ([]byte, error) {
|
|||||||
func setxattr(path, key, value string) error {
|
func setxattr(path, key, value string) error {
|
||||||
return sysx.LSetxattr(path, key, []byte(value), 0)
|
return sysx.LSetxattr(path, key, []byte(value), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply applies a tar stream of an OCI style diff tar.
|
||||||
|
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
||||||
|
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||||
|
return applyNaive(ctx, root, tr, options)
|
||||||
|
}
|
||||||
|
@ -1,15 +1,55 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
package archive
|
package archive
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio"
|
||||||
|
"github.com/Microsoft/hcsshim"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/sys"
|
"github.com/containerd/containerd/sys"
|
||||||
"github.com/dmcgowan/go-tar"
|
"github.com/dmcgowan/go-tar"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MSWINDOWS pax vendor extensions
|
||||||
|
hdrMSWindowsPrefix = "MSWINDOWS."
|
||||||
|
|
||||||
|
hdrFileAttributes = hdrMSWindowsPrefix + "fileattr"
|
||||||
|
hdrSecurityDescriptor = hdrMSWindowsPrefix + "sd"
|
||||||
|
hdrRawSecurityDescriptor = hdrMSWindowsPrefix + "rawsd"
|
||||||
|
hdrMountPoint = hdrMSWindowsPrefix + "mountpoint"
|
||||||
|
hdrEaPrefix = hdrMSWindowsPrefix + "xattr."
|
||||||
|
|
||||||
|
// LIBARCHIVE pax vendor extensions
|
||||||
|
hdrLibArchivePrefix = "LIBARCHIVE."
|
||||||
|
|
||||||
|
hdrCreateTime = hdrLibArchivePrefix + "creationtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// mutatedFiles is a list of files that are mutated by the import process
|
||||||
|
// and must be backed up and restored.
|
||||||
|
mutatedFiles = map[string]string{
|
||||||
|
"UtilityVM/Files/EFI/Microsoft/Boot/BCD": "bcd.bak",
|
||||||
|
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG": "bcd.log.bak",
|
||||||
|
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG1": "bcd.log1.bak",
|
||||||
|
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG2": "bcd.log2.bak",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// tarName returns platform-specific filepath
|
// tarName returns platform-specific filepath
|
||||||
// to canonical posix-style path for tar archival. p is relative
|
// to canonical posix-style path for tar archival. p is relative
|
||||||
// path.
|
// path.
|
||||||
@ -101,3 +141,286 @@ func setxattr(path, key, value string) error {
|
|||||||
// since xattrs should not exist in windows diff archives
|
// since xattrs should not exist in windows diff archives
|
||||||
return errors.New("xattrs not supported on Windows")
|
return errors.New("xattrs not supported on Windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply applies a tar stream of an OCI style diff tar of a Windows layer.
|
||||||
|
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
||||||
|
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||||
|
if options.IsWindowsContainerLayer {
|
||||||
|
return applyWindowsLayer(ctx, root, tr, options)
|
||||||
|
}
|
||||||
|
return applyNaive(ctx, root, tr, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows layer.
|
||||||
|
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
|
||||||
|
func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
|
||||||
|
home, id := filepath.Split(root)
|
||||||
|
info := hcsshim.DriverInfo{
|
||||||
|
HomeDir: home,
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := hcsshim.NewLayerWriter(info, id, options.ParentLayerPaths)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
log.G(ctx).Errorf("failed to close layer writer: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf := bufio.NewWriter(nil)
|
||||||
|
hdr, nextErr := tr.Next()
|
||||||
|
// Iterate through the files in the archive.
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return 0, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextErr == io.EOF {
|
||||||
|
// end of tar archive
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if nextErr != nil {
|
||||||
|
return 0, nextErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: path is used instead of filepath to prevent OS specific handling
|
||||||
|
// of the tar path
|
||||||
|
base := path.Base(hdr.Name)
|
||||||
|
if strings.HasPrefix(base, whiteoutPrefix) {
|
||||||
|
dir := path.Dir(hdr.Name)
|
||||||
|
originalBase := base[len(whiteoutPrefix):]
|
||||||
|
originalPath := path.Join(dir, originalBase)
|
||||||
|
if err := w.Remove(filepath.FromSlash(originalPath)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
hdr, nextErr = tr.Next()
|
||||||
|
} else if hdr.Typeflag == tar.TypeLink {
|
||||||
|
err := w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
hdr, nextErr = tr.Next()
|
||||||
|
} else {
|
||||||
|
name, fileSize, fileInfo, err := fileInfoFromHeader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err := w.Add(filepath.FromSlash(name), fileInfo); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
size += fileSize
|
||||||
|
hdr, nextErr = tarToBackupStreamWithMutatedFiles(buf, w, tr, hdr, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
|
||||||
|
// WriteTarFileFromBackupStream.
|
||||||
|
func fileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
|
||||||
|
name = hdr.Name
|
||||||
|
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
|
||||||
|
size = hdr.Size
|
||||||
|
}
|
||||||
|
fileInfo = &winio.FileBasicInfo{
|
||||||
|
LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
|
||||||
|
LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
|
||||||
|
ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
|
||||||
|
|
||||||
|
// Default CreationTime to ModTime, updated below if MSWINDOWS.createtime exists
|
||||||
|
CreationTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
|
||||||
|
}
|
||||||
|
if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok {
|
||||||
|
attr, err := strconv.ParseUint(attrStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, nil, err
|
||||||
|
}
|
||||||
|
fileInfo.FileAttributes = uintptr(attr)
|
||||||
|
} else {
|
||||||
|
if hdr.Typeflag == tar.TypeDir {
|
||||||
|
fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if createStr, ok := hdr.PAXRecords[hdrCreateTime]; ok {
|
||||||
|
createTime, err := parsePAXTime(createStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, nil, err
|
||||||
|
}
|
||||||
|
fileInfo.CreationTime = syscall.NsecToFiletime(createTime.UnixNano())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// tarToBackupStreamWithMutatedFiles reads data from a tar stream and
|
||||||
|
// writes it to a backup stream, and also saves any files that will be mutated
|
||||||
|
// by the import layer process to a backup location.
|
||||||
|
func tarToBackupStreamWithMutatedFiles(buf *bufio.Writer, w io.Writer, t *tar.Reader, hdr *tar.Header, root string) (nextHdr *tar.Header, err error) {
|
||||||
|
var (
|
||||||
|
bcdBackup *os.File
|
||||||
|
bcdBackupWriter *winio.BackupFileWriter
|
||||||
|
)
|
||||||
|
if backupPath, ok := mutatedFiles[hdr.Name]; ok {
|
||||||
|
bcdBackup, err = os.Create(filepath.Join(root, backupPath))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
cerr := bcdBackup.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
bcdBackupWriter = winio.NewBackupFileWriter(bcdBackup, false)
|
||||||
|
defer func() {
|
||||||
|
cerr := bcdBackupWriter.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf.Reset(io.MultiWriter(w, bcdBackupWriter))
|
||||||
|
} else {
|
||||||
|
buf.Reset(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
ferr := buf.Flush()
|
||||||
|
if err == nil {
|
||||||
|
err = ferr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return writeBackupStreamFromTarFile(buf, t, hdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
|
||||||
|
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
|
||||||
|
// tar file that was not processed, or io.EOF is there are no more.
|
||||||
|
func writeBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
|
||||||
|
bw := winio.NewBackupStreamWriter(w)
|
||||||
|
var sd []byte
|
||||||
|
var err error
|
||||||
|
// Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written
|
||||||
|
// by this library will have raw binary for the security descriptor.
|
||||||
|
if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok {
|
||||||
|
sd, err = winio.SddlToSecurityDescriptor(sddl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok {
|
||||||
|
sd, err = base64.StdEncoding.DecodeString(sdraw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(sd) != 0 {
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupSecurity,
|
||||||
|
Size: int64(len(sd)),
|
||||||
|
}
|
||||||
|
err := bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = bw.Write(sd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var eas []winio.ExtendedAttribute
|
||||||
|
for k, v := range hdr.PAXRecords {
|
||||||
|
if !strings.HasPrefix(k, hdrEaPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, err := base64.StdEncoding.DecodeString(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
eas = append(eas, winio.ExtendedAttribute{
|
||||||
|
Name: k[len(hdrEaPrefix):],
|
||||||
|
Value: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(eas) != 0 {
|
||||||
|
eadata, err := winio.EncodeExtendedAttributes(eas)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupEaData,
|
||||||
|
Size: int64(len(eadata)),
|
||||||
|
}
|
||||||
|
err = bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = bw.Write(eadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeSymlink {
|
||||||
|
_, isMountPoint := hdr.PAXRecords[hdrMountPoint]
|
||||||
|
rp := winio.ReparsePoint{
|
||||||
|
Target: filepath.FromSlash(hdr.Linkname),
|
||||||
|
IsMountPoint: isMountPoint,
|
||||||
|
}
|
||||||
|
reparse := winio.EncodeReparsePoint(&rp)
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupReparseData,
|
||||||
|
Size: int64(len(reparse)),
|
||||||
|
}
|
||||||
|
err := bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = bw.Write(reparse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupData,
|
||||||
|
Size: hdr.Size,
|
||||||
|
}
|
||||||
|
err := bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(bw, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Copy all the alternate data streams and return the next non-ADS header.
|
||||||
|
for {
|
||||||
|
ahdr, err := t.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
|
||||||
|
return ahdr, nil
|
||||||
|
}
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupAlternateData,
|
||||||
|
Size: ahdr.Size,
|
||||||
|
Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
|
||||||
|
}
|
||||||
|
err = bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(bw, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -34,7 +34,7 @@ github.com/containerd/continuity cf279e6ac893682272b4479d4c67fd3abf878b4e
|
|||||||
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
||||||
github.com/BurntSushi/toml v0.2.0-21-g9906417
|
github.com/BurntSushi/toml v0.2.0-21-g9906417
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
||||||
github.com/Microsoft/go-winio v0.4.4
|
github.com/Microsoft/go-winio v0.4.5
|
||||||
github.com/Microsoft/hcsshim v0.6.7
|
github.com/Microsoft/hcsshim v0.6.7
|
||||||
github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd
|
github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd
|
||||||
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
||||||
|
14
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
14
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
@ -68,10 +68,20 @@ func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
|
|||||||
return &BackupStreamReader{r, 0}
|
return &BackupStreamReader{r, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the next backup stream and prepares for calls to Write(). It skips the remainder of the current stream if
|
// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
|
||||||
// it was not completely read.
|
// it was not completely read.
|
||||||
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
||||||
if r.bytesLeft > 0 {
|
if r.bytesLeft > 0 {
|
||||||
|
if s, ok := r.r.(io.Seeker); ok {
|
||||||
|
// Make sure Seek on io.SeekCurrent sometimes succeeds
|
||||||
|
// before trying the actual seek.
|
||||||
|
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
|
||||||
|
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.bytesLeft = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, err := io.Copy(ioutil.Discard, r); err != nil {
|
if _, err := io.Copy(ioutil.Discard, r); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -220,7 +230,7 @@ type BackupFileWriter struct {
|
|||||||
ctx uintptr
|
ctx uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBackupFileWrtier returns a new BackupFileWriter from a file handle. If includeSecurity is true,
|
// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
|
||||||
// Write() will attempt to restore the security descriptor from the stream.
|
// Write() will attempt to restore the security descriptor from the stream.
|
||||||
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
||||||
w := &BackupFileWriter{f, includeSecurity, 0}
|
w := &BackupFileWriter{f, includeSecurity, 0}
|
||||||
|
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileFullEaInformation struct {
|
||||||
|
NextEntryOffset uint32
|
||||||
|
Flags uint8
|
||||||
|
NameLength uint8
|
||||||
|
ValueLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
|
||||||
|
|
||||||
|
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
|
||||||
|
errEaNameTooLarge = errors.New("extended attribute name too large")
|
||||||
|
errEaValueTooLarge = errors.New("extended attribute value too large")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtendedAttribute represents a single Windows EA.
|
||||||
|
type ExtendedAttribute struct {
|
||||||
|
Name string
|
||||||
|
Value []byte
|
||||||
|
Flags uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
||||||
|
var info fileFullEaInformation
|
||||||
|
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
||||||
|
if err != nil {
|
||||||
|
err = errInvalidEaBuffer
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nameOffset := fileFullEaInformationSize
|
||||||
|
nameLen := int(info.NameLength)
|
||||||
|
valueOffset := nameOffset + int(info.NameLength) + 1
|
||||||
|
valueLen := int(info.ValueLength)
|
||||||
|
nextOffset := int(info.NextEntryOffset)
|
||||||
|
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
|
||||||
|
err = errInvalidEaBuffer
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ea.Name = string(b[nameOffset : nameOffset+nameLen])
|
||||||
|
ea.Value = b[valueOffset : valueOffset+valueLen]
|
||||||
|
ea.Flags = info.Flags
|
||||||
|
if info.NextEntryOffset != 0 {
|
||||||
|
nb = b[info.NextEntryOffset:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
|
||||||
|
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
|
||||||
|
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
||||||
|
for len(b) != 0 {
|
||||||
|
ea, nb, err := parseEa(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
eas = append(eas, ea)
|
||||||
|
b = nb
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
||||||
|
if int(uint8(len(ea.Name))) != len(ea.Name) {
|
||||||
|
return errEaNameTooLarge
|
||||||
|
}
|
||||||
|
if int(uint16(len(ea.Value))) != len(ea.Value) {
|
||||||
|
return errEaValueTooLarge
|
||||||
|
}
|
||||||
|
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
|
||||||
|
withPadding := (entrySize + 3) &^ 3
|
||||||
|
nextOffset := uint32(0)
|
||||||
|
if !last {
|
||||||
|
nextOffset = withPadding
|
||||||
|
}
|
||||||
|
info := fileFullEaInformation{
|
||||||
|
NextEntryOffset: nextOffset,
|
||||||
|
Flags: ea.Flags,
|
||||||
|
NameLength: uint8(len(ea.Name)),
|
||||||
|
ValueLength: uint16(len(ea.Value)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := binary.Write(buf, binary.LittleEndian, &info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write([]byte(ea.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buf.WriteByte(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write(ea.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
|
||||||
|
// buffer for use with BackupWrite, ZwSetEaFile, etc.
|
||||||
|
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := range eas {
|
||||||
|
last := false
|
||||||
|
if i == len(eas)-1 {
|
||||||
|
last = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeEa(&buf, &eas[i], last)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
8
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
8
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
@ -78,6 +78,7 @@ func initIo() {
|
|||||||
type win32File struct {
|
type win32File struct {
|
||||||
handle syscall.Handle
|
handle syscall.Handle
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
wgLock sync.RWMutex
|
||||||
closing atomicBool
|
closing atomicBool
|
||||||
readDeadline deadlineHandler
|
readDeadline deadlineHandler
|
||||||
writeDeadline deadlineHandler
|
writeDeadline deadlineHandler
|
||||||
@ -114,14 +115,18 @@ func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
|||||||
|
|
||||||
// closeHandle closes the resources associated with a Win32 handle
|
// closeHandle closes the resources associated with a Win32 handle
|
||||||
func (f *win32File) closeHandle() {
|
func (f *win32File) closeHandle() {
|
||||||
|
f.wgLock.Lock()
|
||||||
// Atomically set that we are closing, releasing the resources only once.
|
// Atomically set that we are closing, releasing the resources only once.
|
||||||
if !f.closing.swap(true) {
|
if !f.closing.swap(true) {
|
||||||
|
f.wgLock.Unlock()
|
||||||
// cancel all IO and wait for it to complete
|
// cancel all IO and wait for it to complete
|
||||||
cancelIoEx(f.handle, nil)
|
cancelIoEx(f.handle, nil)
|
||||||
f.wg.Wait()
|
f.wg.Wait()
|
||||||
// at this point, no new IO can start
|
// at this point, no new IO can start
|
||||||
syscall.Close(f.handle)
|
syscall.Close(f.handle)
|
||||||
f.handle = 0
|
f.handle = 0
|
||||||
|
} else {
|
||||||
|
f.wgLock.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,10 +139,13 @@ func (f *win32File) Close() error {
|
|||||||
// prepareIo prepares for a new IO operation.
|
// prepareIo prepares for a new IO operation.
|
||||||
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
|
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
|
||||||
func (f *win32File) prepareIo() (*ioOperation, error) {
|
func (f *win32File) prepareIo() (*ioOperation, error) {
|
||||||
|
f.wgLock.RLock()
|
||||||
if f.closing.isSet() {
|
if f.closing.isSet() {
|
||||||
|
f.wgLock.RUnlock()
|
||||||
return nil, ErrFileClosed
|
return nil, ErrFileClosed
|
||||||
}
|
}
|
||||||
f.wg.Add(1)
|
f.wg.Add(1)
|
||||||
|
f.wgLock.RUnlock()
|
||||||
c := &ioOperation{}
|
c := &ioOperation{}
|
||||||
c.ch = make(chan ioResult)
|
c.ch = make(chan ioResult)
|
||||||
return c, nil
|
return c, nil
|
||||||
|
4
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
4
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
@ -265,9 +265,9 @@ func (l *win32PipeListener) listenerRoutine() {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
// Wait for the client to connect.
|
// Wait for the client to connect.
|
||||||
ch := make(chan error)
|
ch := make(chan error)
|
||||||
go func() {
|
go func(p *win32File) {
|
||||||
ch <- connectPipe(p)
|
ch <- connectPipe(p)
|
||||||
}()
|
}(p)
|
||||||
select {
|
select {
|
||||||
case err = <-ch:
|
case err = <-ch:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user