2017-02-01 00:45:59 +00:00
|
|
|
package hcsshim
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"syscall"
|
|
|
|
|
|
|
|
"github.com/Microsoft/go-winio"
|
2017-08-05 11:40:46 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-02-01 00:45:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ExportLayer will create a folder at exportFolderPath and fill that folder with
|
|
|
|
// the transport format version of the layer identified by layerId. This transport
|
|
|
|
// format includes any metadata required for later importing the layer (using
|
|
|
|
// ImportLayer), and requires the full list of parent layer paths in order to
|
|
|
|
// perform the export.
|
|
|
|
func ExportLayer(info DriverInfo, layerId string, exportFolderPath string, parentLayerPaths []string) error {
|
|
|
|
title := "hcsshim::ExportLayer "
|
|
|
|
logrus.Debugf(title+"flavour %d layerId %s folder %s", info.Flavour, layerId, exportFolderPath)
|
|
|
|
|
|
|
|
// Generate layer descriptors
|
|
|
|
layers, err := layerPathsToDescriptors(parentLayerPaths)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert info to API calling convention
|
|
|
|
infop, err := convertDriverInfo(info)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = exportLayer(&infop, layerId, exportFolderPath, layers)
|
|
|
|
if err != nil {
|
|
|
|
err = makeErrorf(err, title, "layerId=%s flavour=%d folder=%s", layerId, info.Flavour, exportFolderPath)
|
|
|
|
logrus.Error(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Debugf(title+"succeeded flavour=%d layerId=%s folder=%s", info.Flavour, layerId, exportFolderPath)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type LayerReader interface {
|
|
|
|
Next() (string, int64, *winio.FileBasicInfo, error)
|
|
|
|
Read(b []byte) (int, error)
|
|
|
|
Close() error
|
|
|
|
}
|
|
|
|
|
|
|
|
// FilterLayerReader provides an interface for extracting the contents of an on-disk layer.
|
|
|
|
type FilterLayerReader struct {
|
|
|
|
context uintptr
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next reads the next available file from a layer, ensuring that parent directories are always read
|
|
|
|
// before child files and directories.
|
|
|
|
//
|
|
|
|
// Next returns the file's relative path, size, and basic file metadata. Read() should be used to
|
|
|
|
// extract a Win32 backup stream with the remainder of the metadata and the data.
|
|
|
|
func (r *FilterLayerReader) Next() (string, int64, *winio.FileBasicInfo, error) {
|
|
|
|
var fileNamep *uint16
|
|
|
|
fileInfo := &winio.FileBasicInfo{}
|
|
|
|
var deleted uint32
|
|
|
|
var fileSize int64
|
|
|
|
err := exportLayerNext(r.context, &fileNamep, fileInfo, &fileSize, &deleted)
|
|
|
|
if err != nil {
|
|
|
|
if err == syscall.ERROR_NO_MORE_FILES {
|
|
|
|
err = io.EOF
|
|
|
|
} else {
|
|
|
|
err = makeError(err, "ExportLayerNext", "")
|
|
|
|
}
|
|
|
|
return "", 0, nil, err
|
|
|
|
}
|
|
|
|
fileName := convertAndFreeCoTaskMemString(fileNamep)
|
|
|
|
if deleted != 0 {
|
|
|
|
fileInfo = nil
|
|
|
|
}
|
|
|
|
if fileName[0] == '\\' {
|
|
|
|
fileName = fileName[1:]
|
|
|
|
}
|
|
|
|
return fileName, fileSize, fileInfo, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read reads from the current file's Win32 backup stream.
|
|
|
|
func (r *FilterLayerReader) Read(b []byte) (int, error) {
|
|
|
|
var bytesRead uint32
|
|
|
|
err := exportLayerRead(r.context, b, &bytesRead)
|
|
|
|
if err != nil {
|
|
|
|
return 0, makeError(err, "ExportLayerRead", "")
|
|
|
|
}
|
|
|
|
if bytesRead == 0 {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
return int(bytesRead), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close frees resources associated with the layer reader. It will return an
|
|
|
|
// error if there was an error while reading the layer or of the layer was not
|
|
|
|
// completely read.
|
|
|
|
func (r *FilterLayerReader) Close() (err error) {
|
|
|
|
if r.context != 0 {
|
|
|
|
err = exportLayerEnd(r.context)
|
|
|
|
if err != nil {
|
|
|
|
err = makeError(err, "ExportLayerEnd", "")
|
|
|
|
}
|
|
|
|
r.context = 0
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewLayerReader returns a new layer reader for reading the contents of an on-disk layer.
|
|
|
|
// The caller must have taken the SeBackupPrivilege privilege
|
|
|
|
// to call this and any methods on the resulting LayerReader.
|
|
|
|
func NewLayerReader(info DriverInfo, layerID string, parentLayerPaths []string) (LayerReader, error) {
|
|
|
|
if procExportLayerBegin.Find() != nil {
|
|
|
|
// The new layer reader is not available on this Windows build. Fall back to the
|
|
|
|
// legacy export code path.
|
|
|
|
path, err := ioutil.TempDir("", "hcs")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = ExportLayer(info, layerID, path, parentLayerPaths)
|
|
|
|
if err != nil {
|
|
|
|
os.RemoveAll(path)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &legacyLayerReaderWrapper{newLegacyLayerReader(path)}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
layers, err := layerPathsToDescriptors(parentLayerPaths)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
infop, err := convertDriverInfo(info)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r := &FilterLayerReader{}
|
|
|
|
err = exportLayerBegin(&infop, layerID, layers, &r.context)
|
|
|
|
if err != nil {
|
|
|
|
return nil, makeError(err, "ExportLayerBegin", "")
|
|
|
|
}
|
|
|
|
return r, err
|
|
|
|
}
|
|
|
|
|
|
|
|
type legacyLayerReaderWrapper struct {
|
|
|
|
*legacyLayerReader
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *legacyLayerReaderWrapper) Close() error {
|
|
|
|
err := r.legacyLayerReader.Close()
|
|
|
|
os.RemoveAll(r.root)
|
|
|
|
return err
|
|
|
|
}
|