52f4d09f |
//+build windows
package windows
import ( |
5649030e |
"bufio" |
b3bc5e0f |
"bytes" |
dfbb5520 |
"encoding/json" |
1cb9e9b4 |
"errors" |
52f4d09f |
"fmt" |
5649030e |
"io" |
dfbb5520 |
"io/ioutil" |
52f4d09f |
"os" |
5649030e |
"path" |
52f4d09f |
"path/filepath" |
b3bc5e0f |
"strconv" |
dfbb5520 |
"strings" |
4bac8bce |
"sync" |
5649030e |
"syscall" |
d4095a59 |
"time" |
6f887887 |
"unsafe" |
52f4d09f |
|
5649030e |
"github.com/Microsoft/go-winio"
"github.com/Microsoft/go-winio/archive/tar"
"github.com/Microsoft/go-winio/backuptar" |
b9a395c8 |
"github.com/Microsoft/hcsshim" |
52f4d09f |
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/archive" |
442b4562 |
"github.com/docker/docker/pkg/idtools" |
52f4d09f |
"github.com/docker/docker/pkg/ioutils" |
cf7944bf |
"github.com/docker/docker/pkg/longpath" |
b3bc5e0f |
"github.com/docker/docker/pkg/reexec" |
83a2db20 |
"github.com/docker/docker/pkg/system" |
22c83c56 |
units "github.com/docker/go-units"
"golang.org/x/sys/windows" |
52f4d09f |
)
|
dd0fc2be |
// filterDriver is an HCSShim driver type for the Windows Filter driver.
const filterDriver = 1
|
7e5ee6d1 |
var ( |
9c79b0ef |
// 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",
} |
d9294719 |
noreexec = false |
7e5ee6d1 |
)
|
1fffc7a8 |
// init registers the windows graph drivers to the register. |
52f4d09f |
func init() {
graphdriver.Register("windowsfilter", InitFilter) |
d9294719 |
// DOCKER_WINDOWSFILTER_NOREEXEC allows for inline processing which makes
// debugging issues in the re-exec codepath significantly easier.
if os.Getenv("DOCKER_WINDOWSFILTER_NOREEXEC") != "" {
logrus.Warnf("WindowsGraphDriver is set to not re-exec. This is intended for debugging purposes only.")
noreexec = true
} else {
reexec.Register("docker-windows-write-layer", writeLayerReexec)
} |
52f4d09f |
}
|
4bac8bce |
type checker struct {
}
func (c *checker) IsMounted(path string) bool {
return false
} |
52f4d09f |
|
1fffc7a8 |
// Driver represents a windows graph driver. |
dfbb5520 |
type Driver struct { |
1fffc7a8 |
// info stores the shim driver information
info hcsshim.DriverInfo |
4bac8bce |
ctr *graphdriver.RefCounter
// it is safe for windows to use a cache here because it does not support
// restoring containers when the daemon dies.
cacheMu sync.Mutex
cache map[string]string |
52f4d09f |
}
|
1fffc7a8 |
// InitFilter returns a new Windows storage filter driver. |
442b4562 |
func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { |
52f4d09f |
logrus.Debugf("WindowsGraphDriver InitFilter at %s", home) |
1cb9e9b4 |
fsType, err := getFileSystemType(string(home[0]))
if err != nil {
return nil, err
}
if strings.ToLower(fsType) == "refs" {
return nil, fmt.Errorf("%s is on an ReFS volume - ReFS volumes are not supported", home)
}
|
3aa4a007 |
if err := idtools.MkdirAllAs(home, 0700, 0, 0); err != nil {
return nil, fmt.Errorf("windowsfilter failed to create '%s': %v", home, err)
}
|
dfbb5520 |
d := &Driver{ |
52f4d09f |
info: hcsshim.DriverInfo{
HomeDir: home,
Flavour: filterDriver,
}, |
4bac8bce |
cache: make(map[string]string),
ctr: graphdriver.NewRefCounter(&checker{}), |
52f4d09f |
}
return d, nil
}
|
1cb9e9b4 |
// win32FromHresult is a helper function to get the win32 error code from an HRESULT
func win32FromHresult(hr uintptr) uintptr {
if hr&0x1fff0000 == 0x00070000 {
return hr & 0xffff
}
return hr
}
// getFileSystemType obtains the type of a file system through GetVolumeInformation
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364993(v=vs.85).aspx
func getFileSystemType(drive string) (fsType string, hr error) {
var ( |
22c83c56 |
modkernel32 = windows.NewLazySystemDLL("kernel32.dll") |
1cb9e9b4 |
procGetVolumeInformation = modkernel32.NewProc("GetVolumeInformationW")
buf = make([]uint16, 255)
size = syscall.MAX_PATH + 1
)
if len(drive) != 1 {
hr = errors.New("getFileSystemType must be called with a drive letter")
return
}
drive += `:\`
n := uintptr(unsafe.Pointer(nil))
r0, _, _ := syscall.Syscall9(procGetVolumeInformation.Addr(), 8, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive))), n, n, n, n, n, uintptr(unsafe.Pointer(&buf[0])), uintptr(size), 0)
if int32(r0) < 0 {
hr = syscall.Errno(win32FromHresult(r0))
}
fsType = syscall.UTF16ToString(buf)
return
}
|
f182748b |
// String returns the string representation of a driver. This should match
// the name the graph driver has been registered with. |
dfbb5520 |
func (d *Driver) String() string { |
f182748b |
return "windowsfilter" |
52f4d09f |
}
|
1fffc7a8 |
// Status returns the status of the driver. |
dfbb5520 |
func (d *Driver) Status() [][2]string { |
52f4d09f |
return [][2]string{
{"Windows", ""},
}
}
|
bdabc76a |
// panicIfUsedByLcow does exactly what it says. |
4ec9766a |
// TODO @jhowardmsft - this is a temporary measure for the bring-up of |
bdabc76a |
// Linux containers on Windows. It is a failsafe to ensure that the right
// graphdriver is used.
func panicIfUsedByLcow() {
if system.LCOWSupported() {
panic("inconsistency - windowsfilter graphdriver should not be used when in LCOW mode")
}
}
|
1fffc7a8 |
// Exists returns true if the given id is registered with this driver. |
dfbb5520 |
func (d *Driver) Exists(id string) bool { |
bdabc76a |
panicIfUsedByLcow() |
1fffc7a8 |
rID, err := d.resolveID(id) |
dfbb5520 |
if err != nil {
return false
} |
1fffc7a8 |
result, err := hcsshim.LayerExists(d.info, rID) |
52f4d09f |
if err != nil {
return false
}
return result
}
|
ef5bfad3 |
// CreateReadWrite creates a layer that is writable for use as a container
// file system. |
b937aa8e |
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { |
bdabc76a |
panicIfUsedByLcow() |
b937aa8e |
if opts != nil {
return d.create(id, parent, opts.MountLabel, false, opts.StorageOpt)
} |
9c559e6d |
return d.create(id, parent, "", false, nil) |
ef5bfad3 |
}
// Create creates a new read-only layer with the given id. |
b937aa8e |
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { |
bdabc76a |
panicIfUsedByLcow() |
b937aa8e |
if opts != nil {
return d.create(id, parent, opts.MountLabel, true, opts.StorageOpt)
} |
9c559e6d |
return d.create(id, parent, "", true, nil) |
ef5bfad3 |
}
func (d *Driver) create(id, parent, mountLabel string, readOnly bool, storageOpt map[string]string) error { |
1fffc7a8 |
rPId, err := d.resolveID(parent) |
dfbb5520 |
if err != nil {
return err
}
parentChain, err := d.getLayerChain(rPId)
if err != nil {
return err
}
var layerChain []string
|
ef5bfad3 |
if rPId != "" { |
dfbb5520 |
parentPath, err := hcsshim.GetLayerMountPath(d.info, rPId)
if err != nil {
return err
} |
ef5bfad3 |
if _, err := os.Stat(filepath.Join(parentPath, "Files")); err == nil {
// This is a legitimate parent layer (not the empty "-init" layer),
// so include it in the layer chain.
layerChain = []string{parentPath}
} |
dfbb5520 |
}
layerChain = append(layerChain, parentChain...)
|
ef5bfad3 |
if readOnly {
if err := hcsshim.CreateLayer(d.info, id, rPId); err != nil { |
dfbb5520 |
return err
}
} else { |
ef5bfad3 |
var parentPath string
if len(layerChain) != 0 {
parentPath = layerChain[0]
} |
6f887887 |
|
ef5bfad3 |
if err := hcsshim.CreateSandboxLayer(d.info, id, parentPath, layerChain); err != nil { |
dfbb5520 |
return err
} |
7e5ee6d1 |
storageOptions, err := parseStorageOpt(storageOpt)
if err != nil {
return fmt.Errorf("Failed to parse storage options - %s", err)
}
|
0da31c68 |
if storageOptions.size != 0 { |
7e5ee6d1 |
if err := hcsshim.ExpandSandboxSize(d.info, id, storageOptions.size); err != nil {
return err
}
} |
dfbb5520 |
}
|
a456f20b |
if _, err := os.Lstat(d.dir(parent)); err != nil {
if err2 := hcsshim.DestroyLayer(d.info, id); err2 != nil {
logrus.Warnf("Failed to DestroyLayer %s: %s", id, err2) |
dfbb5520 |
} |
a456f20b |
return fmt.Errorf("Cannot create layer with missing parent %s: %s", parent, err)
}
if err := d.setLayerChain(id, layerChain); err != nil { |
dfbb5520 |
if err2 := hcsshim.DestroyLayer(d.info, id); err2 != nil { |
a456f20b |
logrus.Warnf("Failed to DestroyLayer %s: %s", id, err2) |
dfbb5520 |
}
return err
}
return nil |
52f4d09f |
}
|
1fffc7a8 |
// dir returns the absolute path to the layer. |
dfbb5520 |
func (d *Driver) dir(id string) string { |
52f4d09f |
return filepath.Join(d.info.HomeDir, filepath.Base(id))
}
|
1fffc7a8 |
// Remove unmounts and removes the dir information. |
dfbb5520 |
func (d *Driver) Remove(id string) error { |
bdabc76a |
panicIfUsedByLcow() |
1fffc7a8 |
rID, err := d.resolveID(id) |
dfbb5520 |
if err != nil {
return err
} |
be68006d |
|
d4095a59 |
// This retry loop is due to a bug in Windows (Internal bug #9432268)
// if GetContainers fails with ErrVmcomputeOperationInvalidState
// it is a transient error. Retry until it succeeds.
var computeSystems []hcsshim.ContainerProperties
retryCount := 0 |
83a2db20 |
osv := system.GetOSVersion() |
d4095a59 |
for { |
83a2db20 |
// Get and terminate any template VMs that are currently using the layer.
// Note: It is unfortunate that we end up in the graphdrivers Remove() call
// for both containers and images, but the logic for template VMs is only
// needed for images - specifically we are looking to see if a base layer
// is in use by a template VM as a result of having started a Hyper-V
// container at some point.
//
// We have a retry loop for ErrVmcomputeOperationInvalidState and
// ErrVmcomputeOperationAccessIsDenied as there is a race condition
// in RS1 and RS2 building during enumeration when a silo is going away
// for example under it, in HCS. AccessIsDenied added to fix 30278.
//
// TODO @jhowardmsft - For RS3, we can remove the retries. Also consider
// using platform APIs (if available) to get this more succinctly. Also |
39bcaee4 |
// consider enhancing the Remove() interface to have context of why |
83a2db20 |
// the remove is being called - that could improve efficiency by not
// enumerating compute systems during a remove of a container as it's
// not required. |
d4095a59 |
computeSystems, err = hcsshim.GetContainers(hcsshim.ComputeSystemQuery{})
if err != nil { |
83a2db20 |
if (osv.Build < 15139) &&
((err == hcsshim.ErrVmcomputeOperationInvalidState) || (err == hcsshim.ErrVmcomputeOperationAccessIsDenied)) {
if retryCount >= 500 { |
d4095a59 |
break
}
retryCount++ |
83a2db20 |
time.Sleep(10 * time.Millisecond) |
d4095a59 |
continue
}
return err
}
break |
c58e8dea |
}
for _, computeSystem := range computeSystems {
if strings.Contains(computeSystem.RuntimeImagePath, id) && computeSystem.IsRuntimeTemplate {
container, err := hcsshim.OpenContainer(computeSystem.ID)
if err != nil {
return err
}
defer container.Close()
err = container.Terminate()
if hcsshim.IsPending(err) {
err = container.Wait()
} else if hcsshim.IsAlreadyStopped(err) {
err = nil
}
if err != nil {
return err
}
}
}
|
be68006d |
layerPath := filepath.Join(d.info.HomeDir, rID)
tmpID := fmt.Sprintf("%s-removing", rID)
tmpLayerPath := filepath.Join(d.info.HomeDir, tmpID)
if err := os.Rename(layerPath, tmpLayerPath); err != nil && !os.IsNotExist(err) {
return err
}
if err := hcsshim.DestroyLayer(d.info, tmpID); err != nil {
logrus.Errorf("Failed to DestroyLayer %s: %s", id, err)
}
return nil |
52f4d09f |
}
|
4e959ef2 |
// Get returns the rootfs path for the id. This will mount the dir at its given path. |
dfbb5520 |
func (d *Driver) Get(id, mountLabel string) (string, error) { |
bdabc76a |
panicIfUsedByLcow() |
dfbb5520 |
logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel) |
52f4d09f |
var dir string
|
1fffc7a8 |
rID, err := d.resolveID(id) |
dfbb5520 |
if err != nil {
return "", err
} |
4bac8bce |
if count := d.ctr.Increment(rID); count > 1 {
return d.cache[rID], nil
} |
dfbb5520 |
// Getting the layer paths must be done outside of the lock. |
1fffc7a8 |
layerChain, err := d.getLayerChain(rID) |
dfbb5520 |
if err != nil { |
4bac8bce |
d.ctr.Decrement(rID) |
dfbb5520 |
return "", err
}
|
65d79e3e |
if err := hcsshim.ActivateLayer(d.info, rID); err != nil { |
4bac8bce |
d.ctr.Decrement(rID) |
65d79e3e |
return "", err
}
if err := hcsshim.PrepareLayer(d.info, rID, layerChain); err != nil { |
4bac8bce |
d.ctr.Decrement(rID) |
65d79e3e |
if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil {
logrus.Warnf("Failed to Deactivate %s: %s", id, err) |
52f4d09f |
} |
65d79e3e |
return "", err |
52f4d09f |
}
|
1fffc7a8 |
mountPath, err := hcsshim.GetLayerMountPath(d.info, rID) |
52f4d09f |
if err != nil { |
4bac8bce |
d.ctr.Decrement(rID) |
7fab9b8a |
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
logrus.Warnf("Failed to Unprepare %s: %s", id, err)
} |
1fffc7a8 |
if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil { |
dfbb5520 |
logrus.Warnf("Failed to Deactivate %s: %s", id, err)
} |
52f4d09f |
return "", err
} |
4bac8bce |
d.cacheMu.Lock()
d.cache[rID] = mountPath
d.cacheMu.Unlock() |
52f4d09f |
// If the layer has a mount path, use that. Otherwise, use the
// folder path.
if mountPath != "" {
dir = mountPath
} else {
dir = d.dir(id)
}
return dir, nil
}
|
1fffc7a8 |
// Put adds a new layer to the driver. |
dfbb5520 |
func (d *Driver) Put(id string) error { |
bdabc76a |
panicIfUsedByLcow() |
52f4d09f |
logrus.Debugf("WindowsGraphDriver Put() id %s", id)
|
1fffc7a8 |
rID, err := d.resolveID(id) |
dfbb5520 |
if err != nil {
return err
} |
4bac8bce |
if count := d.ctr.Decrement(rID); count > 0 {
return nil
}
d.cacheMu.Lock() |
7fab9b8a |
_, exists := d.cache[rID] |
4bac8bce |
delete(d.cache, rID)
d.cacheMu.Unlock() |
dfbb5520 |
|
7fab9b8a |
// If the cache was not populated, then the layer was left unprepared and deactivated
if !exists {
return nil
}
|
65d79e3e |
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
return err |
52f4d09f |
} |
65d79e3e |
return hcsshim.DeactivateLayer(d.info, rID) |
52f4d09f |
}
|
1fffc7a8 |
// Cleanup ensures the information the driver stores is properly removed. |
9910b9a7 |
// We use this opportunity to cleanup any -removing folders which may be
// still left if the daemon was killed while it was removing a layer. |
dfbb5520 |
func (d *Driver) Cleanup() error { |
9910b9a7 |
items, err := ioutil.ReadDir(d.info.HomeDir)
if err != nil { |
5e4e357f |
if os.IsNotExist(err) {
return nil
} |
9910b9a7 |
return err
}
// Note we don't return an error below - it's possible the files
// are locked. However, next time around after the daemon exits,
// we likely will be able to to cleanup successfully. Instead we log
// warnings if there are errors.
for _, item := range items {
if item.IsDir() && strings.HasSuffix(item.Name(), "-removing") {
if err := hcsshim.DestroyLayer(d.info, item.Name()); err != nil {
logrus.Warnf("Failed to cleanup %s: %s", item.Name(), err)
} else {
logrus.Infof("Cleaned up %s", item.Name())
}
}
}
|
52f4d09f |
return nil
}
// Diff produces an archive of the changes between the specified
// layer and its parent layer which may be "". |
65d79e3e |
// The layer should be mounted when calling this function |
aa2cc187 |
func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) { |
bdabc76a |
panicIfUsedByLcow() |
1fffc7a8 |
rID, err := d.resolveID(id) |
dfbb5520 |
if err != nil {
return
}
|
1fffc7a8 |
layerChain, err := d.getLayerChain(rID) |
dfbb5520 |
if err != nil {
return
}
|
65d79e3e |
// this is assuming that the layer is unmounted
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
return nil, err |
e91de9fb |
} |
b3bc5e0f |
prepare := func() { |
65d79e3e |
if err := hcsshim.PrepareLayer(d.info, rID, layerChain); err != nil {
logrus.Warnf("Failed to Deactivate %s: %s", rID, err)
} |
b3bc5e0f |
} |
dfbb5520 |
|
5649030e |
arch, err := d.exportLayer(rID, layerChain)
if err != nil { |
b3bc5e0f |
prepare() |
5649030e |
return
}
return ioutils.NewReadCloserWrapper(arch, func() error { |
b3bc5e0f |
err := arch.Close()
prepare()
return err |
5649030e |
}), nil |
52f4d09f |
}
// Changes produces a list of changes between the specified layer
// and its parent layer. If parent is "", then all changes will be ADD changes. |
4fac6036 |
// The layer should not be mounted when calling this function. |
dfbb5520 |
func (d *Driver) Changes(id, parent string) ([]archive.Change, error) { |
bdabc76a |
panicIfUsedByLcow() |
5649030e |
rID, err := d.resolveID(id)
if err != nil {
return nil, err
}
parentChain, err := d.getLayerChain(rID)
if err != nil {
return nil, err
}
|
4fac6036 |
if err := hcsshim.ActivateLayer(d.info, rID); err != nil { |
65d79e3e |
return nil, err |
e91de9fb |
} |
65d79e3e |
defer func() { |
4fac6036 |
if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil {
logrus.Errorf("changes() failed to DeactivateLayer %s %s: %s", id, rID, err2) |
65d79e3e |
}
}() |
5649030e |
var changes []archive.Change |
b3bc5e0f |
err = winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error {
r, err := hcsshim.NewLayerReader(d.info, id, parentChain) |
5649030e |
if err != nil { |
b3bc5e0f |
return err |
5649030e |
} |
b3bc5e0f |
defer r.Close()
for {
name, _, fileInfo, err := r.Next()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
name = filepath.ToSlash(name)
if fileInfo == nil {
changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeDelete})
} else {
// Currently there is no way to tell between an add and a modify.
changes = append(changes, archive.Change{Path: name, Kind: archive.ChangeModify})
} |
5649030e |
} |
b3bc5e0f |
})
if err != nil {
return nil, err |
5649030e |
} |
b3bc5e0f |
|
5649030e |
return changes, nil |
52f4d09f |
}
// ApplyDiff extracts the changeset from the given diff into the
// layer with the specified id and parent, returning the size of the
// new layer in bytes. |
65d79e3e |
// The layer should not be mounted when calling this function |
aa2cc187 |
func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) { |
bdabc76a |
panicIfUsedByLcow() |
cf7944bf |
var layerChain []string
if parent != "" {
rPId, err := d.resolveID(parent)
if err != nil {
return 0, err
}
parentChain, err := d.getLayerChain(rPId)
if err != nil {
return 0, err
}
parentPath, err := hcsshim.GetLayerMountPath(d.info, rPId)
if err != nil {
return 0, err
}
layerChain = append(layerChain, parentPath)
layerChain = append(layerChain, parentChain...) |
dfbb5520 |
}
|
cf7944bf |
size, err := d.importLayer(id, diff, layerChain)
if err != nil {
return 0, err |
dfbb5520 |
}
if err = d.setLayerChain(id, layerChain); err != nil { |
cf7944bf |
return 0, err |
52f4d09f |
}
|
cf7944bf |
return size, nil |
52f4d09f |
}
// DiffSize calculates the changes between the specified layer
// and its parent and returns the size in bytes of the changes
// relative to its base filesystem directory. |
dfbb5520 |
func (d *Driver) DiffSize(id, parent string) (size int64, err error) { |
bdabc76a |
panicIfUsedByLcow() |
1fffc7a8 |
rPId, err := d.resolveID(parent) |
dfbb5520 |
if err != nil {
return
}
changes, err := d.Changes(id, rPId) |
52f4d09f |
if err != nil {
return
}
layerFs, err := d.Get(id, "")
if err != nil {
return
}
defer d.Put(id)
return archive.ChangesSize(layerFs, changes), nil
}
|
1fffc7a8 |
// GetMetadata returns custom driver information. |
dfbb5520 |
func (d *Driver) GetMetadata(id string) (map[string]string, error) { |
bdabc76a |
panicIfUsedByLcow() |
dfbb5520 |
m := make(map[string]string)
m["dir"] = d.dir(id)
return m, nil |
52f4d09f |
}
|
5649030e |
func writeTarFromLayer(r hcsshim.LayerReader, w io.Writer) error {
t := tar.NewWriter(w)
for {
name, size, fileInfo, err := r.Next()
if err == io.EOF {
break
} |
52f4d09f |
if err != nil { |
5649030e |
return err
}
if fileInfo == nil {
// Write a whiteout file.
hdr := &tar.Header{
Name: filepath.ToSlash(filepath.Join(filepath.Dir(name), archive.WhiteoutPrefix+filepath.Base(name))),
}
err := t.WriteHeader(hdr)
if err != nil {
return err
}
} else {
err = backuptar.WriteTarFileFromBackupStream(t, r, name, size, fileInfo)
if err != nil {
return err |
52f4d09f |
}
} |
5649030e |
}
return t.Close()
} |
52f4d09f |
|
5649030e |
// exportLayer generates an archive from a layer based on the given ID. |
aa2cc187 |
func (d *Driver) exportLayer(id string, parentLayerPaths []string) (io.ReadCloser, error) { |
5649030e |
archive, w := io.Pipe()
go func() { |
b3bc5e0f |
err := winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error {
r, err := hcsshim.NewLayerReader(d.info, id, parentLayerPaths)
if err != nil {
return err
}
err = writeTarFromLayer(r, w)
cerr := r.Close()
if err == nil {
err = cerr
}
return err
}) |
5649030e |
w.CloseWithError(err)
}() |
52f4d09f |
|
5649030e |
return archive, nil
}
|
9c79b0ef |
// writeBackupStreamFromTarAndSaveMutatedFiles 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 writeBackupStreamFromTarAndSaveMutatedFiles(buf *bufio.Writer, w io.Writer, t *tar.Reader, hdr *tar.Header, root string) (nextHdr *tar.Header, err error) {
var bcdBackup *os.File
var 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 backuptar.WriteBackupStreamFromTarFile(buf, t, hdr)
}
|
aa2cc187 |
func writeLayerFromTar(r io.Reader, w hcsshim.LayerWriter, root string) (int64, error) { |
5649030e |
t := tar.NewReader(r)
hdr, err := t.Next()
totalSize := int64(0)
buf := bufio.NewWriter(nil)
for err == nil {
base := path.Base(hdr.Name)
if strings.HasPrefix(base, archive.WhiteoutPrefix) {
name := path.Join(path.Dir(hdr.Name), base[len(archive.WhiteoutPrefix):])
err = w.Remove(filepath.FromSlash(name))
if err != nil {
return 0, err
}
hdr, err = t.Next() |
cf7944bf |
} else if hdr.Typeflag == tar.TypeLink {
err = w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname))
if err != nil {
return 0, err
}
hdr, err = t.Next() |
5649030e |
} else {
var (
name string
size int64
fileInfo *winio.FileBasicInfo
)
name, size, fileInfo, err = backuptar.FileInfoFromHeader(hdr)
if err != nil {
return 0, err
}
err = w.Add(filepath.FromSlash(name), fileInfo)
if err != nil {
return 0, err
} |
9c79b0ef |
hdr, err = writeBackupStreamFromTarAndSaveMutatedFiles(buf, w, t, hdr, root) |
5649030e |
totalSize += size
}
}
if err != io.EOF {
return 0, err
}
return totalSize, nil |
52f4d09f |
}
|
1fffc7a8 |
// importLayer adds a new layer to the tag and graph store based on the given data. |
aa2cc187 |
func (d *Driver) importLayer(id string, layerData io.Reader, parentLayerPaths []string) (size int64, err error) { |
d9294719 |
if !noreexec {
cmd := reexec.Command(append([]string{"docker-windows-write-layer", d.info.HomeDir, id}, parentLayerPaths...)...)
output := bytes.NewBuffer(nil)
cmd.Stdin = layerData
cmd.Stdout = output
cmd.Stderr = output
if err = cmd.Start(); err != nil {
return
} |
b3bc5e0f |
|
d9294719 |
if err = cmd.Wait(); err != nil {
return 0, fmt.Errorf("re-exec error: %v: output: %s", err, output)
} |
b3bc5e0f |
|
d9294719 |
return strconv.ParseInt(output.String(), 10, 64) |
5649030e |
} |
d9294719 |
return writeLayer(layerData, d.info.HomeDir, id, parentLayerPaths...) |
b3bc5e0f |
}
|
d9294719 |
// writeLayerReexec is the re-exec entry point for writing a layer from a tar file
func writeLayerReexec() {
size, err := writeLayer(os.Stdin, os.Args[1], os.Args[2], os.Args[3:]...)
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
fmt.Fprint(os.Stdout, size)
} |
b3bc5e0f |
|
d9294719 |
// writeLayer writes a layer from a tar file.
func writeLayer(layerData io.Reader, home string, id string, parentLayerPaths ...string) (int64, error) {
err := winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege})
if err != nil {
return 0, err
}
if noreexec {
defer func() {
if err := winio.DisableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege}); err != nil {
// This should never happen, but just in case when in debugging mode.
// See https://github.com/docker/docker/pull/28002#discussion_r86259241 for rationale.
panic("Failed to disabled process privileges while in non re-exec mode")
}
}()
} |
b3bc5e0f |
|
d9294719 |
info := hcsshim.DriverInfo{
Flavour: filterDriver,
HomeDir: home,
} |
b3bc5e0f |
|
d9294719 |
w, err := hcsshim.NewLayerWriter(info, id, parentLayerPaths)
if err != nil {
return 0, err
} |
b3bc5e0f |
|
d9294719 |
size, err := writeLayerFromTar(layerData, w, filepath.Join(home, id))
if err != nil {
return 0, err
} |
b3bc5e0f |
|
d9294719 |
err = w.Close() |
5649030e |
if err != nil { |
d9294719 |
return 0, err |
52f4d09f |
} |
d9294719 |
return size, nil |
52f4d09f |
} |
dfbb5520 |
|
1fffc7a8 |
// resolveID computes the layerID information based on the given id.
func (d *Driver) resolveID(id string) (string, error) {
content, err := ioutil.ReadFile(filepath.Join(d.dir(id), "layerID")) |
dfbb5520 |
if os.IsNotExist(err) {
return id, nil
} else if err != nil {
return "", err
}
return string(content), nil
}
|
1fffc7a8 |
// setID stores the layerId in disk.
func (d *Driver) setID(id, altID string) error { |
3a425180 |
return ioutil.WriteFile(filepath.Join(d.dir(id), "layerId"), []byte(altID), 0600) |
dfbb5520 |
}
|
1fffc7a8 |
// getLayerChain returns the layer chain information. |
dfbb5520 |
func (d *Driver) getLayerChain(id string) ([]string, error) {
jPath := filepath.Join(d.dir(id), "layerchain.json")
content, err := ioutil.ReadFile(jPath)
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("Unable to read layerchain file - %s", err)
}
var layerChain []string
err = json.Unmarshal(content, &layerChain)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshall layerchain json - %s", err)
}
return layerChain, nil
}
|
1fffc7a8 |
// setLayerChain stores the layer chain information in disk. |
dfbb5520 |
func (d *Driver) setLayerChain(id string, chain []string) error {
content, err := json.Marshal(&chain)
if err != nil {
return fmt.Errorf("Failed to marshall layerchain json - %s", err)
}
jPath := filepath.Join(d.dir(id), "layerchain.json")
err = ioutil.WriteFile(jPath, content, 0600)
if err != nil {
return fmt.Errorf("Unable to write layerchain file - %s", err)
}
return nil
} |
4352da78 |
|
5649030e |
type fileGetCloserWithBackupPrivileges struct {
path string
}
func (fg *fileGetCloserWithBackupPrivileges) Get(filename string) (io.ReadCloser, error) { |
9c79b0ef |
if backupPath, ok := mutatedFiles[filename]; ok {
return os.Open(filepath.Join(fg.path, backupPath))
}
|
5649030e |
var f *os.File
// Open the file while holding the Windows backup privilege. This ensures that the
// file can be opened even if the caller does not actually have access to it according |
c98e77c7 |
// to the security descriptor. Also use sequential file access to avoid depleting the
// standby list - Microsoft VSO Bug Tracker #9900466 |
5649030e |
err := winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error { |
cf7944bf |
path := longpath.AddPrefix(filepath.Join(fg.path, filename)) |
5649030e |
p, err := syscall.UTF16FromString(path)
if err != nil {
return err
} |
c98e77c7 |
const fileFlagSequentialScan = 0x08000000 // FILE_FLAG_SEQUENTIAL_SCAN
h, err := syscall.CreateFile(&p[0], syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|fileFlagSequentialScan, 0) |
5649030e |
if err != nil {
return &os.PathError{Op: "open", Path: path, Err: err}
}
f = os.NewFile(uintptr(h), path)
return nil
})
return f, err
}
func (fg *fileGetCloserWithBackupPrivileges) Close() error {
return nil
}
|
58bec40d |
// DiffGetter returns a FileGetCloser that can read files from the directory that
// contains files for the layer differences. Used for direct access for tar-split. |
5649030e |
func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) { |
bdabc76a |
panicIfUsedByLcow() |
5649030e |
id, err := d.resolveID(id) |
4352da78 |
if err != nil { |
5649030e |
return nil, err |
4352da78 |
}
|
5649030e |
return &fileGetCloserWithBackupPrivileges{d.dir(id)}, nil |
4352da78 |
} |
7e5ee6d1 |
type storageOptions struct {
size uint64
}
func parseStorageOpt(storageOpt map[string]string) (*storageOptions, error) {
options := storageOptions{}
// Read size to change the block device size per container.
for key, val := range storageOpt {
key := strings.ToLower(key)
switch key {
case "size":
size, err := units.RAMInBytes(val)
if err != nil {
return nil, err
}
options.size = uint64(size)
default:
return nil, fmt.Errorf("Unknown storage option: %s", key)
}
}
return &options, nil
} |