1package hcsshim
2
3import (
4	"io"
5	"io/ioutil"
6	"os"
7	"syscall"
8
9	"github.com/Microsoft/go-winio"
10	"github.com/sirupsen/logrus"
11)
12
13// ExportLayer will create a folder at exportFolderPath and fill that folder with
14// the transport format version of the layer identified by layerId. This transport
15// format includes any metadata required for later importing the layer (using
16// ImportLayer), and requires the full list of parent layer paths in order to
17// perform the export.
18func ExportLayer(info DriverInfo, layerId string, exportFolderPath string, parentLayerPaths []string) error {
19	title := "hcsshim::ExportLayer "
20	logrus.Debugf(title+"flavour %d layerId %s folder %s", info.Flavour, layerId, exportFolderPath)
21
22	// Generate layer descriptors
23	layers, err := layerPathsToDescriptors(parentLayerPaths)
24	if err != nil {
25		return err
26	}
27
28	// Convert info to API calling convention
29	infop, err := convertDriverInfo(info)
30	if err != nil {
31		logrus.Error(err)
32		return err
33	}
34
35	err = exportLayer(&infop, layerId, exportFolderPath, layers)
36	if err != nil {
37		err = makeErrorf(err, title, "layerId=%s flavour=%d folder=%s", layerId, info.Flavour, exportFolderPath)
38		logrus.Error(err)
39		return err
40	}
41
42	logrus.Debugf(title+"succeeded flavour=%d layerId=%s folder=%s", info.Flavour, layerId, exportFolderPath)
43	return nil
44}
45
46type LayerReader interface {
47	Next() (string, int64, *winio.FileBasicInfo, error)
48	Read(b []byte) (int, error)
49	Close() error
50}
51
52// FilterLayerReader provides an interface for extracting the contents of an on-disk layer.
53type FilterLayerReader struct {
54	context uintptr
55}
56
57// Next reads the next available file from a layer, ensuring that parent directories are always read
58// before child files and directories.
59//
60// Next returns the file's relative path, size, and basic file metadata. Read() should be used to
61// extract a Win32 backup stream with the remainder of the metadata and the data.
62func (r *FilterLayerReader) Next() (string, int64, *winio.FileBasicInfo, error) {
63	var fileNamep *uint16
64	fileInfo := &winio.FileBasicInfo{}
65	var deleted uint32
66	var fileSize int64
67	err := exportLayerNext(r.context, &fileNamep, fileInfo, &fileSize, &deleted)
68	if err != nil {
69		if err == syscall.ERROR_NO_MORE_FILES {
70			err = io.EOF
71		} else {
72			err = makeError(err, "ExportLayerNext", "")
73		}
74		return "", 0, nil, err
75	}
76	fileName := convertAndFreeCoTaskMemString(fileNamep)
77	if deleted != 0 {
78		fileInfo = nil
79	}
80	if fileName[0] == '\\' {
81		fileName = fileName[1:]
82	}
83	return fileName, fileSize, fileInfo, nil
84}
85
86// Read reads from the current file's Win32 backup stream.
87func (r *FilterLayerReader) Read(b []byte) (int, error) {
88	var bytesRead uint32
89	err := exportLayerRead(r.context, b, &bytesRead)
90	if err != nil {
91		return 0, makeError(err, "ExportLayerRead", "")
92	}
93	if bytesRead == 0 {
94		return 0, io.EOF
95	}
96	return int(bytesRead), nil
97}
98
99// Close frees resources associated with the layer reader. It will return an
100// error if there was an error while reading the layer or of the layer was not
101// completely read.
102func (r *FilterLayerReader) Close() (err error) {
103	if r.context != 0 {
104		err = exportLayerEnd(r.context)
105		if err != nil {
106			err = makeError(err, "ExportLayerEnd", "")
107		}
108		r.context = 0
109	}
110	return
111}
112
113// NewLayerReader returns a new layer reader for reading the contents of an on-disk layer.
114// The caller must have taken the SeBackupPrivilege privilege
115// to call this and any methods on the resulting LayerReader.
116func NewLayerReader(info DriverInfo, layerID string, parentLayerPaths []string) (LayerReader, error) {
117	if procExportLayerBegin.Find() != nil {
118		// The new layer reader is not available on this Windows build. Fall back to the
119		// legacy export code path.
120		path, err := ioutil.TempDir("", "hcs")
121		if err != nil {
122			return nil, err
123		}
124		err = ExportLayer(info, layerID, path, parentLayerPaths)
125		if err != nil {
126			os.RemoveAll(path)
127			return nil, err
128		}
129		return &legacyLayerReaderWrapper{newLegacyLayerReader(path)}, nil
130	}
131
132	layers, err := layerPathsToDescriptors(parentLayerPaths)
133	if err != nil {
134		return nil, err
135	}
136	infop, err := convertDriverInfo(info)
137	if err != nil {
138		return nil, err
139	}
140	r := &FilterLayerReader{}
141	err = exportLayerBegin(&infop, layerID, layers, &r.context)
142	if err != nil {
143		return nil, makeError(err, "ExportLayerBegin", "")
144	}
145	return r, err
146}
147
148type legacyLayerReaderWrapper struct {
149	*legacyLayerReader
150}
151
152func (r *legacyLayerReaderWrapper) Close() error {
153	err := r.legacyLayerReader.Close()
154	os.RemoveAll(r.root)
155	return err
156}
157