Signed-off-by: John Howard <jhoward@microsoft.com>
| ... | ... |
@@ -1,7 +1,7 @@ |
| 1 | 1 |
# the following lines are in sorted order, FYI |
| 2 | 2 |
github.com/Azure/go-ansiterm 19f72df4d05d31cbe1c56bfc8045c96babff6c7e |
| 3 | 3 |
github.com/Microsoft/hcsshim v0.6.3 |
| 4 |
-github.com/Microsoft/go-winio v0.4.4 |
|
| 4 |
+github.com/Microsoft/go-winio v0.4.5 |
|
| 5 | 5 |
github.com/moby/buildkit da2b9dc7dab99e824b2b1067ad7d0523e32dd2d9 https://github.com/dmcgowan/buildkit.git |
| 6 | 6 |
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 |
| 7 | 7 |
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a |
| ... | ... |
@@ -68,10 +68,20 @@ func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
|
| 68 | 68 |
return &BackupStreamReader{r, 0}
|
| 69 | 69 |
} |
| 70 | 70 |
|
| 71 |
-// Next returns the next backup stream and prepares for calls to Write(). It skips the remainder of the current stream if |
|
| 71 |
+// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if |
|
| 72 | 72 |
// it was not completely read. |
| 73 | 73 |
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
| 74 | 74 |
if r.bytesLeft > 0 {
|
| 75 |
+ if s, ok := r.r.(io.Seeker); ok {
|
|
| 76 |
+ // Make sure Seek on io.SeekCurrent sometimes succeeds |
|
| 77 |
+ // before trying the actual seek. |
|
| 78 |
+ if _, err := s.Seek(0, io.SeekCurrent); err == nil {
|
|
| 79 |
+ if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
|
|
| 80 |
+ return nil, err |
|
| 81 |
+ } |
|
| 82 |
+ r.bytesLeft = 0 |
|
| 83 |
+ } |
|
| 84 |
+ } |
|
| 75 | 85 |
if _, err := io.Copy(ioutil.Discard, r); err != nil {
|
| 76 | 86 |
return nil, err |
| 77 | 87 |
} |
| ... | ... |
@@ -220,7 +230,7 @@ type BackupFileWriter struct {
|
| 220 | 220 |
ctx uintptr |
| 221 | 221 |
} |
| 222 | 222 |
|
| 223 |
-// NewBackupFileWrtier returns a new BackupFileWriter from a file handle. If includeSecurity is true, |
|
| 223 |
+// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true, |
|
| 224 | 224 |
// Write() will attempt to restore the security descriptor from the stream. |
| 225 | 225 |
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
| 226 | 226 |
w := &BackupFileWriter{f, includeSecurity, 0}
|
| ... | ... |
@@ -36,6 +36,7 @@ const ( |
| 36 | 36 |
hdrSecurityDescriptor = "sd" |
| 37 | 37 |
hdrRawSecurityDescriptor = "rawsd" |
| 38 | 38 |
hdrMountPoint = "mountpoint" |
| 39 |
+ hdrEaPrefix = "xattr." |
|
| 39 | 40 |
) |
| 40 | 41 |
|
| 41 | 42 |
func writeZeroes(w io.Writer, count int64) error {
|
| ... | ... |
@@ -118,6 +119,21 @@ func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *ta |
| 118 | 118 |
func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
|
| 119 | 119 |
name = filepath.ToSlash(name) |
| 120 | 120 |
hdr := BasicInfoHeader(name, size, fileInfo) |
| 121 |
+ |
|
| 122 |
+ // If r can be seeked, then this function is two-pass: pass 1 collects the |
|
| 123 |
+ // tar header data, and pass 2 copies the data stream. If r cannot be |
|
| 124 |
+ // seeked, then some header data (in particular EAs) will be silently lost. |
|
| 125 |
+ var ( |
|
| 126 |
+ restartPos int64 |
|
| 127 |
+ err error |
|
| 128 |
+ ) |
|
| 129 |
+ sr, readTwice := r.(io.Seeker) |
|
| 130 |
+ if readTwice {
|
|
| 131 |
+ if restartPos, err = sr.Seek(0, io.SeekCurrent); err != nil {
|
|
| 132 |
+ readTwice = false |
|
| 133 |
+ } |
|
| 134 |
+ } |
|
| 135 |
+ |
|
| 121 | 136 |
br := winio.NewBackupStreamReader(r) |
| 122 | 137 |
var dataHdr *winio.BackupHeader |
| 123 | 138 |
for dataHdr == nil {
|
| ... | ... |
@@ -131,7 +147,9 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size |
| 131 | 131 |
switch bhdr.Id {
|
| 132 | 132 |
case winio.BackupData: |
| 133 | 133 |
hdr.Mode |= c_ISREG |
| 134 |
- dataHdr = bhdr |
|
| 134 |
+ if !readTwice {
|
|
| 135 |
+ dataHdr = bhdr |
|
| 136 |
+ } |
|
| 135 | 137 |
case winio.BackupSecurity: |
| 136 | 138 |
sd, err := ioutil.ReadAll(br) |
| 137 | 139 |
if err != nil {
|
| ... | ... |
@@ -151,18 +169,54 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size |
| 151 | 151 |
hdr.Winheaders[hdrMountPoint] = "1" |
| 152 | 152 |
} |
| 153 | 153 |
hdr.Linkname = rp.Target |
| 154 |
- case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: |
|
| 154 |
+ |
|
| 155 |
+ case winio.BackupEaData: |
|
| 156 |
+ eab, err := ioutil.ReadAll(br) |
|
| 157 |
+ if err != nil {
|
|
| 158 |
+ return err |
|
| 159 |
+ } |
|
| 160 |
+ eas, err := winio.DecodeExtendedAttributes(eab) |
|
| 161 |
+ if err != nil {
|
|
| 162 |
+ return err |
|
| 163 |
+ } |
|
| 164 |
+ for _, ea := range eas {
|
|
| 165 |
+ // Use base64 encoding for the binary value. Note that there |
|
| 166 |
+ // is no way to encode the EA's flags, since their use doesn't |
|
| 167 |
+ // make any sense for persisted EAs. |
|
| 168 |
+ hdr.Winheaders[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value) |
|
| 169 |
+ } |
|
| 170 |
+ |
|
| 171 |
+ case winio.BackupAlternateData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: |
|
| 155 | 172 |
// ignore these streams |
| 156 | 173 |
default: |
| 157 | 174 |
return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
|
| 158 | 175 |
} |
| 159 | 176 |
} |
| 160 | 177 |
|
| 161 |
- err := t.WriteHeader(hdr) |
|
| 178 |
+ err = t.WriteHeader(hdr) |
|
| 162 | 179 |
if err != nil {
|
| 163 | 180 |
return err |
| 164 | 181 |
} |
| 165 | 182 |
|
| 183 |
+ if readTwice {
|
|
| 184 |
+ // Get back to the data stream. |
|
| 185 |
+ if _, err = sr.Seek(restartPos, io.SeekStart); err != nil {
|
|
| 186 |
+ return err |
|
| 187 |
+ } |
|
| 188 |
+ for dataHdr == nil {
|
|
| 189 |
+ bhdr, err := br.Next() |
|
| 190 |
+ if err == io.EOF {
|
|
| 191 |
+ break |
|
| 192 |
+ } |
|
| 193 |
+ if err != nil {
|
|
| 194 |
+ return err |
|
| 195 |
+ } |
|
| 196 |
+ if bhdr.Id == winio.BackupData {
|
|
| 197 |
+ dataHdr = bhdr |
|
| 198 |
+ } |
|
| 199 |
+ } |
|
| 200 |
+ } |
|
| 201 |
+ |
|
| 166 | 202 |
if dataHdr != nil {
|
| 167 | 203 |
// A data stream was found. Copy the data. |
| 168 | 204 |
if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
|
| ... | ... |
@@ -293,6 +347,38 @@ func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) ( |
| 293 | 293 |
return nil, err |
| 294 | 294 |
} |
| 295 | 295 |
} |
| 296 |
+ var eas []winio.ExtendedAttribute |
|
| 297 |
+ for k, v := range hdr.Winheaders {
|
|
| 298 |
+ if !strings.HasPrefix(k, hdrEaPrefix) {
|
|
| 299 |
+ continue |
|
| 300 |
+ } |
|
| 301 |
+ data, err := base64.StdEncoding.DecodeString(v) |
|
| 302 |
+ if err != nil {
|
|
| 303 |
+ return nil, err |
|
| 304 |
+ } |
|
| 305 |
+ eas = append(eas, winio.ExtendedAttribute{
|
|
| 306 |
+ Name: k[len(hdrEaPrefix):], |
|
| 307 |
+ Value: data, |
|
| 308 |
+ }) |
|
| 309 |
+ } |
|
| 310 |
+ if len(eas) != 0 {
|
|
| 311 |
+ eadata, err := winio.EncodeExtendedAttributes(eas) |
|
| 312 |
+ if err != nil {
|
|
| 313 |
+ return nil, err |
|
| 314 |
+ } |
|
| 315 |
+ bhdr := winio.BackupHeader{
|
|
| 316 |
+ Id: winio.BackupEaData, |
|
| 317 |
+ Size: int64(len(eadata)), |
|
| 318 |
+ } |
|
| 319 |
+ err = bw.WriteHeader(&bhdr) |
|
| 320 |
+ if err != nil {
|
|
| 321 |
+ return nil, err |
|
| 322 |
+ } |
|
| 323 |
+ _, err = bw.Write(eadata) |
|
| 324 |
+ if err != nil {
|
|
| 325 |
+ return nil, err |
|
| 326 |
+ } |
|
| 327 |
+ } |
|
| 296 | 328 |
if hdr.Typeflag == tar.TypeSymlink {
|
| 297 | 329 |
_, isMountPoint := hdr.Winheaders[hdrMountPoint] |
| 298 | 330 |
rp := winio.ReparsePoint{
|
| 299 | 331 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,137 @@ |
| 0 |
+package winio |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "encoding/binary" |
|
| 5 |
+ "errors" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+type fileFullEaInformation struct {
|
|
| 9 |
+ NextEntryOffset uint32 |
|
| 10 |
+ Flags uint8 |
|
| 11 |
+ NameLength uint8 |
|
| 12 |
+ ValueLength uint16 |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+var ( |
|
| 16 |
+ fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
|
|
| 17 |
+ |
|
| 18 |
+ errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
|
|
| 19 |
+ errEaNameTooLarge = errors.New("extended attribute name too large")
|
|
| 20 |
+ errEaValueTooLarge = errors.New("extended attribute value too large")
|
|
| 21 |
+) |
|
| 22 |
+ |
|
| 23 |
+// ExtendedAttribute represents a single Windows EA. |
|
| 24 |
+type ExtendedAttribute struct {
|
|
| 25 |
+ Name string |
|
| 26 |
+ Value []byte |
|
| 27 |
+ Flags uint8 |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
|
| 31 |
+ var info fileFullEaInformation |
|
| 32 |
+ err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) |
|
| 33 |
+ if err != nil {
|
|
| 34 |
+ err = errInvalidEaBuffer |
|
| 35 |
+ return |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ nameOffset := fileFullEaInformationSize |
|
| 39 |
+ nameLen := int(info.NameLength) |
|
| 40 |
+ valueOffset := nameOffset + int(info.NameLength) + 1 |
|
| 41 |
+ valueLen := int(info.ValueLength) |
|
| 42 |
+ nextOffset := int(info.NextEntryOffset) |
|
| 43 |
+ if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
|
|
| 44 |
+ err = errInvalidEaBuffer |
|
| 45 |
+ return |
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ ea.Name = string(b[nameOffset : nameOffset+nameLen]) |
|
| 49 |
+ ea.Value = b[valueOffset : valueOffset+valueLen] |
|
| 50 |
+ ea.Flags = info.Flags |
|
| 51 |
+ if info.NextEntryOffset != 0 {
|
|
| 52 |
+ nb = b[info.NextEntryOffset:] |
|
| 53 |
+ } |
|
| 54 |
+ return |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION |
|
| 58 |
+// buffer retrieved from BackupRead, ZwQueryEaFile, etc. |
|
| 59 |
+func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
|
| 60 |
+ for len(b) != 0 {
|
|
| 61 |
+ ea, nb, err := parseEa(b) |
|
| 62 |
+ if err != nil {
|
|
| 63 |
+ return nil, err |
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ eas = append(eas, ea) |
|
| 67 |
+ b = nb |
|
| 68 |
+ } |
|
| 69 |
+ return |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
|
| 73 |
+ if int(uint8(len(ea.Name))) != len(ea.Name) {
|
|
| 74 |
+ return errEaNameTooLarge |
|
| 75 |
+ } |
|
| 76 |
+ if int(uint16(len(ea.Value))) != len(ea.Value) {
|
|
| 77 |
+ return errEaValueTooLarge |
|
| 78 |
+ } |
|
| 79 |
+ entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) |
|
| 80 |
+ withPadding := (entrySize + 3) &^ 3 |
|
| 81 |
+ nextOffset := uint32(0) |
|
| 82 |
+ if !last {
|
|
| 83 |
+ nextOffset = withPadding |
|
| 84 |
+ } |
|
| 85 |
+ info := fileFullEaInformation{
|
|
| 86 |
+ NextEntryOffset: nextOffset, |
|
| 87 |
+ Flags: ea.Flags, |
|
| 88 |
+ NameLength: uint8(len(ea.Name)), |
|
| 89 |
+ ValueLength: uint16(len(ea.Value)), |
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ err := binary.Write(buf, binary.LittleEndian, &info) |
|
| 93 |
+ if err != nil {
|
|
| 94 |
+ return err |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ _, err = buf.Write([]byte(ea.Name)) |
|
| 98 |
+ if err != nil {
|
|
| 99 |
+ return err |
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ err = buf.WriteByte(0) |
|
| 103 |
+ if err != nil {
|
|
| 104 |
+ return err |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ _, err = buf.Write(ea.Value) |
|
| 108 |
+ if err != nil {
|
|
| 109 |
+ return err |
|
| 110 |
+ } |
|
| 111 |
+ |
|
| 112 |
+ _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
|
|
| 113 |
+ if err != nil {
|
|
| 114 |
+ return err |
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ return nil |
|
| 118 |
+} |
|
| 119 |
+ |
|
| 120 |
+// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION |
|
| 121 |
+// buffer for use with BackupWrite, ZwSetEaFile, etc. |
|
| 122 |
+func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
|
| 123 |
+ var buf bytes.Buffer |
|
| 124 |
+ for i := range eas {
|
|
| 125 |
+ last := false |
|
| 126 |
+ if i == len(eas)-1 {
|
|
| 127 |
+ last = true |
|
| 128 |
+ } |
|
| 129 |
+ |
|
| 130 |
+ err := writeEa(&buf, &eas[i], last) |
|
| 131 |
+ if err != nil {
|
|
| 132 |
+ return nil, err |
|
| 133 |
+ } |
|
| 134 |
+ } |
|
| 135 |
+ return buf.Bytes(), nil |
|
| 136 |
+} |
| ... | ... |
@@ -78,6 +78,7 @@ func initIo() {
|
| 78 | 78 |
type win32File struct {
|
| 79 | 79 |
handle syscall.Handle |
| 80 | 80 |
wg sync.WaitGroup |
| 81 |
+ wgLock sync.RWMutex |
|
| 81 | 82 |
closing atomicBool |
| 82 | 83 |
readDeadline deadlineHandler |
| 83 | 84 |
writeDeadline deadlineHandler |
| ... | ... |
@@ -114,14 +115,18 @@ func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
| 114 | 114 |
|
| 115 | 115 |
// closeHandle closes the resources associated with a Win32 handle |
| 116 | 116 |
func (f *win32File) closeHandle() {
|
| 117 |
+ f.wgLock.Lock() |
|
| 117 | 118 |
// Atomically set that we are closing, releasing the resources only once. |
| 118 | 119 |
if !f.closing.swap(true) {
|
| 120 |
+ f.wgLock.Unlock() |
|
| 119 | 121 |
// cancel all IO and wait for it to complete |
| 120 | 122 |
cancelIoEx(f.handle, nil) |
| 121 | 123 |
f.wg.Wait() |
| 122 | 124 |
// at this point, no new IO can start |
| 123 | 125 |
syscall.Close(f.handle) |
| 124 | 126 |
f.handle = 0 |
| 127 |
+ } else {
|
|
| 128 |
+ f.wgLock.Unlock() |
|
| 125 | 129 |
} |
| 126 | 130 |
} |
| 127 | 131 |
|
| ... | ... |
@@ -134,10 +139,13 @@ func (f *win32File) Close() error {
|
| 134 | 134 |
// prepareIo prepares for a new IO operation. |
| 135 | 135 |
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. |
| 136 | 136 |
func (f *win32File) prepareIo() (*ioOperation, error) {
|
| 137 |
+ f.wgLock.RLock() |
|
| 137 | 138 |
if f.closing.isSet() {
|
| 139 |
+ f.wgLock.RUnlock() |
|
| 138 | 140 |
return nil, ErrFileClosed |
| 139 | 141 |
} |
| 140 | 142 |
f.wg.Add(1) |
| 143 |
+ f.wgLock.RUnlock() |
|
| 141 | 144 |
c := &ioOperation{}
|
| 142 | 145 |
c.ch = make(chan ioResult) |
| 143 | 146 |
return c, nil |
| ... | ... |
@@ -265,9 +265,9 @@ func (l *win32PipeListener) listenerRoutine() {
|
| 265 | 265 |
if err == nil {
|
| 266 | 266 |
// Wait for the client to connect. |
| 267 | 267 |
ch := make(chan error) |
| 268 |
- go func() {
|
|
| 268 |
+ go func(p *win32File) {
|
|
| 269 | 269 |
ch <- connectPipe(p) |
| 270 |
- }() |
|
| 270 |
+ }(p) |
|
| 271 | 271 |
select {
|
| 272 | 272 |
case err = <-ch: |
| 273 | 273 |
if err != nil {
|