mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
3e109f349f
Now that Windows base images can be loaded directly into docker via "docker load" of a specialized tar file (with docker pull support on the horizon) we no longer have need of the custom images code path that loads images from a shared central location. Removing that code and it's call points. Signed-off-by: Stefan J. Wernli <swernli@microsoft.com>
779 lines
19 KiB
Go
779 lines
19 KiB
Go
//+build windows
|
|
|
|
package windows
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/Microsoft/go-winio"
|
|
"github.com/Microsoft/go-winio/archive/tar"
|
|
"github.com/Microsoft/go-winio/backuptar"
|
|
"github.com/Microsoft/hcsshim"
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/daemon/graphdriver"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/docker/docker/pkg/idtools"
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
"github.com/docker/docker/pkg/longpath"
|
|
"github.com/docker/docker/pkg/reexec"
|
|
"github.com/docker/docker/pkg/system"
|
|
"github.com/vbatts/tar-split/tar/storage"
|
|
)
|
|
|
|
// filterDriver is an HCSShim driver type for the Windows Filter driver.
|
|
const filterDriver = 1
|
|
|
|
// init registers the windows graph drivers to the register.
|
|
func init() {
|
|
graphdriver.Register("windowsfilter", InitFilter)
|
|
reexec.Register("docker-windows-write-layer", writeLayer)
|
|
}
|
|
|
|
type checker struct {
|
|
}
|
|
|
|
func (c *checker) IsMounted(path string) bool {
|
|
return false
|
|
}
|
|
|
|
// Driver represents a windows graph driver.
|
|
type Driver struct {
|
|
// info stores the shim driver information
|
|
info hcsshim.DriverInfo
|
|
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
|
|
}
|
|
|
|
func isTP5OrOlder() bool {
|
|
return system.GetOSVersion().Build <= 14300
|
|
}
|
|
|
|
// InitFilter returns a new Windows storage filter driver.
|
|
func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
|
|
logrus.Debugf("WindowsGraphDriver InitFilter at %s", home)
|
|
d := &Driver{
|
|
info: hcsshim.DriverInfo{
|
|
HomeDir: home,
|
|
Flavour: filterDriver,
|
|
},
|
|
cache: make(map[string]string),
|
|
ctr: graphdriver.NewRefCounter(&checker{}),
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// String returns the string representation of a driver. This should match
|
|
// the name the graph driver has been registered with.
|
|
func (d *Driver) String() string {
|
|
return "windowsfilter"
|
|
}
|
|
|
|
// Status returns the status of the driver.
|
|
func (d *Driver) Status() [][2]string {
|
|
return [][2]string{
|
|
{"Windows", ""},
|
|
}
|
|
}
|
|
|
|
// Exists returns true if the given id is registered with this driver.
|
|
func (d *Driver) Exists(id string) bool {
|
|
rID, err := d.resolveID(id)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
result, err := hcsshim.LayerExists(d.info, rID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return result
|
|
}
|
|
|
|
// CreateReadWrite creates a layer that is writable for use as a container
|
|
// file system.
|
|
func (d *Driver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error {
|
|
return d.create(id, parent, mountLabel, false, storageOpt)
|
|
}
|
|
|
|
// Create creates a new read-only layer with the given id.
|
|
func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) error {
|
|
return d.create(id, parent, mountLabel, true, storageOpt)
|
|
}
|
|
|
|
func (d *Driver) create(id, parent, mountLabel string, readOnly bool, storageOpt map[string]string) error {
|
|
if len(storageOpt) != 0 {
|
|
return fmt.Errorf("--storage-opt is not supported for windows")
|
|
}
|
|
|
|
rPId, err := d.resolveID(parent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
parentChain, err := d.getLayerChain(rPId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var layerChain []string
|
|
|
|
if rPId != "" {
|
|
parentPath, err := hcsshim.GetLayerMountPath(d.info, rPId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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}
|
|
}
|
|
}
|
|
|
|
layerChain = append(layerChain, parentChain...)
|
|
|
|
if readOnly {
|
|
if err := hcsshim.CreateLayer(d.info, id, rPId); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
var parentPath string
|
|
if len(layerChain) != 0 {
|
|
parentPath = layerChain[0]
|
|
}
|
|
|
|
if isTP5OrOlder() {
|
|
// Pre-create the layer directory, providing an ACL to give the Hyper-V Virtual Machines
|
|
// group access. This is necessary to ensure that Hyper-V containers can access the
|
|
// virtual machine data. This is not necessary post-TP5.
|
|
path, err := syscall.UTF16FromString(filepath.Join(d.info.HomeDir, id))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Give system and administrators full control, and VMs read, write, and execute.
|
|
// Mark these ACEs as inherited.
|
|
sd, err := winio.SddlToSecurityDescriptor("D:(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FRFWFX;;;S-1-5-83-0)")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = syscall.CreateDirectory(&path[0], &syscall.SecurityAttributes{
|
|
Length: uint32(unsafe.Sizeof(syscall.SecurityAttributes{})),
|
|
SecurityDescriptor: uintptr(unsafe.Pointer(&sd[0])),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := hcsshim.CreateSandboxLayer(d.info, id, parentPath, layerChain); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
return fmt.Errorf("Cannot create layer with missing parent %s: %s", parent, err)
|
|
}
|
|
|
|
if err := d.setLayerChain(id, layerChain); err != nil {
|
|
if err2 := hcsshim.DestroyLayer(d.info, id); err2 != nil {
|
|
logrus.Warnf("Failed to DestroyLayer %s: %s", id, err2)
|
|
}
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// dir returns the absolute path to the layer.
|
|
func (d *Driver) dir(id string) string {
|
|
return filepath.Join(d.info.HomeDir, filepath.Base(id))
|
|
}
|
|
|
|
// Remove unmounts and removes the dir information.
|
|
func (d *Driver) Remove(id string) error {
|
|
rID, err := d.resolveID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
os.RemoveAll(filepath.Join(d.info.HomeDir, "sysfile-backups", rID)) // ok to fail
|
|
return hcsshim.DestroyLayer(d.info, rID)
|
|
}
|
|
|
|
// Get returns the rootfs path for the id. This will mount the dir at it's given path.
|
|
func (d *Driver) Get(id, mountLabel string) (string, error) {
|
|
logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel)
|
|
var dir string
|
|
|
|
rID, err := d.resolveID(id)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if count := d.ctr.Increment(rID); count > 1 {
|
|
return d.cache[rID], nil
|
|
}
|
|
|
|
// Getting the layer paths must be done outside of the lock.
|
|
layerChain, err := d.getLayerChain(rID)
|
|
if err != nil {
|
|
d.ctr.Decrement(rID)
|
|
return "", err
|
|
}
|
|
|
|
if err := hcsshim.ActivateLayer(d.info, rID); err != nil {
|
|
d.ctr.Decrement(rID)
|
|
return "", err
|
|
}
|
|
if err := hcsshim.PrepareLayer(d.info, rID, layerChain); err != nil {
|
|
d.ctr.Decrement(rID)
|
|
if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil {
|
|
logrus.Warnf("Failed to Deactivate %s: %s", id, err)
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
mountPath, err := hcsshim.GetLayerMountPath(d.info, rID)
|
|
if err != nil {
|
|
d.ctr.Decrement(rID)
|
|
if err2 := hcsshim.DeactivateLayer(d.info, rID); err2 != nil {
|
|
logrus.Warnf("Failed to Deactivate %s: %s", id, err)
|
|
}
|
|
return "", err
|
|
}
|
|
d.cacheMu.Lock()
|
|
d.cache[rID] = mountPath
|
|
d.cacheMu.Unlock()
|
|
|
|
// 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
|
|
}
|
|
|
|
// Put adds a new layer to the driver.
|
|
func (d *Driver) Put(id string) error {
|
|
logrus.Debugf("WindowsGraphDriver Put() id %s", id)
|
|
|
|
rID, err := d.resolveID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count := d.ctr.Decrement(rID); count > 0 {
|
|
return nil
|
|
}
|
|
d.cacheMu.Lock()
|
|
delete(d.cache, rID)
|
|
d.cacheMu.Unlock()
|
|
|
|
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
|
|
return err
|
|
}
|
|
return hcsshim.DeactivateLayer(d.info, rID)
|
|
}
|
|
|
|
// Cleanup ensures the information the driver stores is properly removed.
|
|
func (d *Driver) Cleanup() error {
|
|
return nil
|
|
}
|
|
|
|
// Diff produces an archive of the changes between the specified
|
|
// layer and its parent layer which may be "".
|
|
// The layer should be mounted when calling this function
|
|
func (d *Driver) Diff(id, parent string) (_ archive.Archive, err error) {
|
|
rID, err := d.resolveID(id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
layerChain, err := d.getLayerChain(rID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// this is assuming that the layer is unmounted
|
|
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
|
|
return nil, err
|
|
}
|
|
prepare := func() {
|
|
if err := hcsshim.PrepareLayer(d.info, rID, layerChain); err != nil {
|
|
logrus.Warnf("Failed to Deactivate %s: %s", rID, err)
|
|
}
|
|
}
|
|
|
|
arch, err := d.exportLayer(rID, layerChain)
|
|
if err != nil {
|
|
prepare()
|
|
return
|
|
}
|
|
return ioutils.NewReadCloserWrapper(arch, func() error {
|
|
err := arch.Close()
|
|
prepare()
|
|
return err
|
|
}), nil
|
|
}
|
|
|
|
// Changes produces a list of changes between the specified layer
|
|
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
|
// The layer should be mounted when calling this function
|
|
func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
|
|
rID, err := d.resolveID(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parentChain, err := d.getLayerChain(rID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// this is assuming that the layer is unmounted
|
|
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
if err := hcsshim.PrepareLayer(d.info, rID, parentChain); err != nil {
|
|
logrus.Warnf("Failed to Deactivate %s: %s", rID, err)
|
|
}
|
|
}()
|
|
|
|
var changes []archive.Change
|
|
err = winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error {
|
|
r, err := hcsshim.NewLayerReader(d.info, id, parentChain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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})
|
|
}
|
|
}
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
// 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.
|
|
// The layer should not be mounted when calling this function
|
|
func (d *Driver) ApplyDiff(id, parent string, diff archive.Reader) (int64, error) {
|
|
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...)
|
|
}
|
|
|
|
size, err := d.importLayer(id, diff, layerChain)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if err = d.setLayerChain(id, layerChain); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return size, nil
|
|
}
|
|
|
|
// 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.
|
|
func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
|
|
rPId, err := d.resolveID(parent)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
changes, err := d.Changes(id, rPId)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
layerFs, err := d.Get(id, "")
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer d.Put(id)
|
|
|
|
return archive.ChangesSize(layerFs, changes), nil
|
|
}
|
|
|
|
// GetMetadata returns custom driver information.
|
|
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
|
|
m := make(map[string]string)
|
|
m["dir"] = d.dir(id)
|
|
return m, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
if err != nil {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
return t.Close()
|
|
}
|
|
|
|
// exportLayer generates an archive from a layer based on the given ID.
|
|
func (d *Driver) exportLayer(id string, parentLayerPaths []string) (archive.Archive, error) {
|
|
archive, w := io.Pipe()
|
|
go func() {
|
|
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
|
|
})
|
|
w.CloseWithError(err)
|
|
}()
|
|
|
|
return archive, nil
|
|
}
|
|
|
|
func writeLayerFromTar(r archive.Reader, w hcsshim.LayerWriter) (int64, error) {
|
|
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()
|
|
} 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()
|
|
} 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
|
|
}
|
|
buf.Reset(w)
|
|
|
|
// Add the Hyper-V Virtual Machine group ACE to the security descriptor
|
|
// for TP5 so that Xenons can access all files. This is not necessary
|
|
// for post-TP5 builds.
|
|
if isTP5OrOlder() {
|
|
if sddl, ok := hdr.Winheaders["sd"]; ok {
|
|
var ace string
|
|
if hdr.Typeflag == tar.TypeDir {
|
|
ace = "(A;OICI;0x1200a9;;;S-1-5-83-0)"
|
|
} else {
|
|
ace = "(A;;0x1200a9;;;S-1-5-83-0)"
|
|
}
|
|
if hdr.Winheaders["sd"], ok = addAceToSddlDacl(sddl, ace); !ok {
|
|
logrus.Debugf("failed to add VM ACE to %s", sddl)
|
|
}
|
|
}
|
|
}
|
|
|
|
hdr, err = backuptar.WriteBackupStreamFromTarFile(buf, t, hdr)
|
|
ferr := buf.Flush()
|
|
if ferr != nil {
|
|
err = ferr
|
|
}
|
|
totalSize += size
|
|
}
|
|
}
|
|
if err != io.EOF {
|
|
return 0, err
|
|
}
|
|
return totalSize, nil
|
|
}
|
|
|
|
func addAceToSddlDacl(sddl, ace string) (string, bool) {
|
|
daclStart := strings.Index(sddl, "D:")
|
|
if daclStart < 0 {
|
|
return sddl, false
|
|
}
|
|
|
|
dacl := sddl[daclStart:]
|
|
daclEnd := strings.Index(dacl, "S:")
|
|
if daclEnd < 0 {
|
|
daclEnd = len(dacl)
|
|
}
|
|
dacl = dacl[:daclEnd]
|
|
|
|
if strings.Contains(dacl, ace) {
|
|
return sddl, true
|
|
}
|
|
|
|
i := 2
|
|
for i+1 < len(dacl) {
|
|
if dacl[i] != '(' {
|
|
return sddl, false
|
|
}
|
|
|
|
if dacl[i+1] == 'A' {
|
|
break
|
|
}
|
|
|
|
i += 2
|
|
for p := 1; i < len(dacl) && p > 0; i++ {
|
|
if dacl[i] == '(' {
|
|
p++
|
|
} else if dacl[i] == ')' {
|
|
p--
|
|
}
|
|
}
|
|
}
|
|
|
|
return sddl[:daclStart+i] + ace + sddl[daclStart+i:], true
|
|
}
|
|
|
|
// importLayer adds a new layer to the tag and graph store based on the given data.
|
|
func (d *Driver) importLayer(id string, layerData archive.Reader, parentLayerPaths []string) (size int64, err error) {
|
|
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
|
|
}
|
|
|
|
if err = cmd.Wait(); err != nil {
|
|
return 0, fmt.Errorf("re-exec error: %v: output: %s", err, output)
|
|
}
|
|
|
|
return strconv.ParseInt(output.String(), 10, 64)
|
|
}
|
|
|
|
// writeLayer is the re-exec entry point for writing a layer from a tar file
|
|
func writeLayer() {
|
|
home := os.Args[1]
|
|
id := os.Args[2]
|
|
parentLayerPaths := os.Args[3:]
|
|
|
|
err := func() error {
|
|
err := winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
info := hcsshim.DriverInfo{
|
|
Flavour: filterDriver,
|
|
HomeDir: home,
|
|
}
|
|
|
|
w, err := hcsshim.NewLayerWriter(info, id, parentLayerPaths)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
size, err := writeLayerFromTar(os.Stdin, w)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = w.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Fprint(os.Stdout, size)
|
|
return nil
|
|
}()
|
|
|
|
if err != nil {
|
|
fmt.Fprint(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// 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"))
|
|
if os.IsNotExist(err) {
|
|
return id, nil
|
|
} else if err != nil {
|
|
return "", err
|
|
}
|
|
return string(content), nil
|
|
}
|
|
|
|
// setID stores the layerId in disk.
|
|
func (d *Driver) setID(id, altID string) error {
|
|
err := ioutil.WriteFile(filepath.Join(d.dir(id), "layerId"), []byte(altID), 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getLayerChain returns the layer chain information.
|
|
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
|
|
}
|
|
|
|
// setLayerChain stores the layer chain information in disk.
|
|
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
|
|
}
|
|
|
|
type fileGetCloserWithBackupPrivileges struct {
|
|
path string
|
|
}
|
|
|
|
func (fg *fileGetCloserWithBackupPrivileges) Get(filename string) (io.ReadCloser, error) {
|
|
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
|
|
// to the security descriptor.
|
|
err := winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error {
|
|
path := longpath.AddPrefix(filepath.Join(fg.path, filename))
|
|
p, err := syscall.UTF16FromString(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h, err := syscall.CreateFile(&p[0], syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
|
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
|
|
}
|
|
|
|
type fileGetDestroyCloser struct {
|
|
storage.FileGetter
|
|
path string
|
|
}
|
|
|
|
func (f *fileGetDestroyCloser) Close() error {
|
|
// TODO: activate layers and release here?
|
|
return os.RemoveAll(f.path)
|
|
}
|
|
|
|
// 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.
|
|
func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
|
|
id, err := d.resolveID(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &fileGetCloserWithBackupPrivileges{d.dir(id)}, nil
|
|
}
|