mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #22728 from Microsoft/jstarks/improve_import
Windows: reexec during layer import
This commit is contained in:
commit
4c6838137c
7 changed files with 193 additions and 80 deletions
|
@ -4,6 +4,7 @@ package windows
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
@ -28,6 +30,7 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
@ -36,6 +39,7 @@ import (
|
|||
func init() {
|
||||
graphdriver.Register("windowsfilter", InitFilter)
|
||||
graphdriver.Register("windowsdiff", InitDiff)
|
||||
reexec.Register("docker-windows-write-layer", writeLayer)
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -308,18 +312,21 @@ func (d *Driver) Diff(id, parent string) (_ archive.Archive, err error) {
|
|||
if err := hcsshim.UnprepareLayer(d.info, rID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
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 {
|
||||
return arch.Close()
|
||||
err := arch.Close()
|
||||
prepare()
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
|
@ -346,29 +353,35 @@ func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
|
|||
}
|
||||
}()
|
||||
|
||||
r, err := hcsshim.NewLayerReader(d.info, id, parentChain)
|
||||
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
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
var changes []archive.Change
|
||||
for {
|
||||
name, _, fileInfo, err := r.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 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})
|
||||
}
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
|
@ -554,19 +567,21 @@ func writeTarFromLayer(r hcsshim.LayerReader, w io.Writer) error {
|
|||
|
||||
// exportLayer generates an archive from a layer based on the given ID.
|
||||
func (d *Driver) exportLayer(id string, parentLayerPaths []string) (archive.Archive, error) {
|
||||
var r hcsshim.LayerReader
|
||||
r, err := hcsshim.NewLayerReader(d.info, id, parentLayerPaths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archive, w := io.Pipe()
|
||||
go func() {
|
||||
err := writeTarFromLayer(r, w)
|
||||
cerr := r.Close()
|
||||
if err == nil {
|
||||
err = cerr
|
||||
}
|
||||
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)
|
||||
}()
|
||||
|
||||
|
@ -682,21 +697,63 @@ func addAceToSddlDacl(sddl, ace string) (string, bool) {
|
|||
|
||||
// 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) {
|
||||
var w hcsshim.LayerWriter
|
||||
w, err = hcsshim.NewLayerWriter(d.info, id, parentLayerPaths)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
size, err = writeLayerFromTar(layerData, w)
|
||||
if err != nil {
|
||||
w.Close()
|
||||
return
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
return 0, fmt.Errorf("re-exec error: %v: output: %s", err, output)
|
||||
}
|
||||
err = w.Close()
|
||||
|
||||
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 {
|
||||
return
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// resolveID computes the layerID information based on the given id.
|
||||
|
|
|
@ -8,7 +8,7 @@ source 'hack/.vendor-helpers.sh'
|
|||
# the following lines are in sorted order, FYI
|
||||
clone git github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62
|
||||
clone git github.com/Microsoft/hcsshim v0.2.2
|
||||
clone git github.com/Microsoft/go-winio v0.3.0
|
||||
clone git github.com/Microsoft/go-winio v0.3.4
|
||||
clone git github.com/Sirupsen/logrus v0.9.0 # logrus is a common dependency among multiple deps
|
||||
clone git github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
clone git github.com/go-check/check 03a4d9dcf2f92eae8e90ed42aa2656f63fdd0b14 https://github.com/cpuguy83/check.git
|
||||
|
|
1
vendor/src/github.com/Microsoft/go-winio/.gitignore
vendored
Normal file
1
vendor/src/github.com/Microsoft/go-winio/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.exe
|
|
@ -1,6 +1,7 @@
|
|||
package backuptar
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -29,9 +30,10 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
hdrFileAttributes = "fileattr"
|
||||
hdrSecurityDescriptor = "sd"
|
||||
hdrMountPoint = "mountpoint"
|
||||
hdrFileAttributes = "fileattr"
|
||||
hdrSecurityDescriptor = "sd"
|
||||
hdrRawSecurityDescriptor = "rawsd"
|
||||
hdrMountPoint = "mountpoint"
|
||||
)
|
||||
|
||||
func writeZeroes(w io.Writer, count int64) error {
|
||||
|
@ -108,7 +110,7 @@ func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *ta
|
|||
//
|
||||
// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
|
||||
//
|
||||
// MSWINDOWS.sd: The Win32 security descriptor, in SDDL (string) format
|
||||
// MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
|
||||
//
|
||||
// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
|
||||
func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
|
||||
|
@ -133,11 +135,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sddl, err := winio.SecurityDescriptorToSddl(sd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Winheaders[hdrSecurityDescriptor] = sddl
|
||||
hdr.Winheaders[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
|
||||
|
||||
case winio.BackupReparseData:
|
||||
hdr.Mode |= c_ISLNK
|
||||
|
@ -263,16 +261,28 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win
|
|||
// 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.Winheaders[hdrSecurityDescriptor]; ok {
|
||||
sd, err := winio.SddlToSecurityDescriptor(sddl)
|
||||
sd, err = winio.SddlToSecurityDescriptor(sddl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if sdraw, ok := hdr.Winheaders[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)
|
||||
err := bw.WriteHeader(&bhdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -284,7 +294,7 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (
|
|||
if hdr.Typeflag == tar.TypeSymlink {
|
||||
_, isMountPoint := hdr.Winheaders[hdrMountPoint]
|
||||
rp := winio.ReparsePoint{
|
||||
Target: hdr.Linkname,
|
||||
Target: filepath.FromSlash(hdr.Linkname),
|
||||
IsMountPoint: isMountPoint,
|
||||
}
|
||||
reparse := winio.EncodeReparsePoint(&rp)
|
||||
|
|
|
@ -5,14 +5,17 @@ import (
|
|||
"encoding/binary"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//sys adjustTokenPrivileges(token syscall.Handle, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
||||
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
||||
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
|
||||
//sys revertToSelf() (err error) = advapi32.RevertToSelf
|
||||
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Handle) (err error) = advapi32.OpenThreadToken
|
||||
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
|
||||
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
|
||||
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
|
||||
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
|
||||
|
@ -34,6 +37,12 @@ const (
|
|||
securityDelegation
|
||||
)
|
||||
|
||||
var (
|
||||
privNames = make(map[string]uint64)
|
||||
privNameMutex sync.Mutex
|
||||
)
|
||||
|
||||
// PrivilegeError represents an error enabling privileges.
|
||||
type PrivilegeError struct {
|
||||
privileges []uint64
|
||||
}
|
||||
|
@ -56,19 +65,16 @@ func (e *PrivilegeError) Error() string {
|
|||
return s
|
||||
}
|
||||
|
||||
// RunWithPrivilege enables a single privilege for a function call.
|
||||
func RunWithPrivilege(name string, fn func() error) error {
|
||||
return RunWithPrivileges([]string{name}, fn)
|
||||
}
|
||||
|
||||
// RunWithPrivileges enables privileges for a function call.
|
||||
func RunWithPrivileges(names []string, fn func() error) error {
|
||||
var privileges []uint64
|
||||
for _, name := range names {
|
||||
p := uint64(0)
|
||||
err := lookupPrivilegeValue("", name, &p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privileges = append(privileges, p)
|
||||
privileges, err := mapPrivileges(names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
@ -84,7 +90,43 @@ func RunWithPrivileges(names []string, fn func() error) error {
|
|||
return fn()
|
||||
}
|
||||
|
||||
func adjustPrivileges(token syscall.Handle, privileges []uint64) error {
|
||||
func mapPrivileges(names []string) ([]uint64, error) {
|
||||
var privileges []uint64
|
||||
privNameMutex.Lock()
|
||||
defer privNameMutex.Unlock()
|
||||
for _, name := range names {
|
||||
p, ok := privNames[name]
|
||||
if !ok {
|
||||
err := lookupPrivilegeValue("", name, &p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privNames[name] = p
|
||||
}
|
||||
privileges = append(privileges, p)
|
||||
}
|
||||
return privileges, nil
|
||||
}
|
||||
|
||||
// EnableProcessPrivileges enables privileges globally for the process.
|
||||
func EnableProcessPrivileges(names []string) error {
|
||||
privileges, err := mapPrivileges(names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, _ := windows.GetCurrentProcess()
|
||||
var token windows.Token
|
||||
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer token.Close()
|
||||
return adjustPrivileges(token, privileges)
|
||||
}
|
||||
|
||||
func adjustPrivileges(token windows.Token, privileges []uint64) error {
|
||||
var b bytes.Buffer
|
||||
binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
|
||||
for _, p := range privileges {
|
||||
|
@ -113,23 +155,22 @@ func getPrivilegeName(luid uint64) string {
|
|||
|
||||
var displayNameBuffer [256]uint16
|
||||
displayBufSize := uint32(len(displayNameBuffer))
|
||||
var langId uint32
|
||||
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langId)
|
||||
var langID uint32
|
||||
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("<unknown privilege %s>", utf16.Decode(nameBuffer[:bufSize]))
|
||||
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
|
||||
}
|
||||
|
||||
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
|
||||
}
|
||||
|
||||
func newThreadToken() (syscall.Handle, error) {
|
||||
func newThreadToken() (windows.Token, error) {
|
||||
err := impersonateSelf(securityImpersonation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var token syscall.Handle
|
||||
var token windows.Token
|
||||
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
|
||||
if err != nil {
|
||||
rerr := revertToSelf()
|
||||
|
@ -141,10 +182,10 @@ func newThreadToken() (syscall.Handle, error) {
|
|||
return token, nil
|
||||
}
|
||||
|
||||
func releaseThreadToken(h syscall.Handle) {
|
||||
func releaseThreadToken(h windows.Token) {
|
||||
err := revertToSelf()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
syscall.Close(h)
|
||||
h.Close()
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
|||
var ntTarget string
|
||||
relative := false
|
||||
if strings.HasPrefix(rp.Target, `\\?\`) {
|
||||
ntTarget = rp.Target
|
||||
ntTarget = `\??\` + rp.Target[4:]
|
||||
} else if strings.HasPrefix(rp.Target, `\\`) {
|
||||
ntTarget = `\??\UNC\` + rp.Target[2:]
|
||||
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
|
||||
package winio
|
||||
|
||||
import "unsafe"
|
||||
import "syscall"
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
|
@ -300,7 +304,7 @@ func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, si
|
|||
return
|
||||
}
|
||||
|
||||
func adjustTokenPrivileges(token syscall.Handle, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
|
||||
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
|
||||
var _p0 uint32
|
||||
if releaseAll {
|
||||
_p0 = 1
|
||||
|
@ -343,7 +347,7 @@ func revertToSelf() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Handle) (err error) {
|
||||
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
|
||||
var _p0 uint32
|
||||
if openAsSelf {
|
||||
_p0 = 1
|
||||
|
|
Loading…
Reference in a new issue