github.com/containerd/Containerd@v1.4.13/archive/tar_windows.go (about) 1 // +build windows 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package archive 20 21 import ( 22 "archive/tar" 23 "bufio" 24 "context" 25 "encoding/base64" 26 "fmt" 27 "io" 28 "os" 29 "path" 30 "path/filepath" 31 "strconv" 32 "strings" 33 "syscall" 34 35 "github.com/Microsoft/go-winio" 36 "github.com/Microsoft/hcsshim" 37 "github.com/containerd/containerd/sys" 38 "github.com/pkg/errors" 39 ) 40 41 const ( 42 // MSWINDOWS pax vendor extensions 43 hdrMSWindowsPrefix = "MSWINDOWS." 44 45 hdrFileAttributes = hdrMSWindowsPrefix + "fileattr" 46 hdrSecurityDescriptor = hdrMSWindowsPrefix + "sd" 47 hdrRawSecurityDescriptor = hdrMSWindowsPrefix + "rawsd" 48 hdrMountPoint = hdrMSWindowsPrefix + "mountpoint" 49 hdrEaPrefix = hdrMSWindowsPrefix + "xattr." 50 51 // LIBARCHIVE pax vendor extensions 52 hdrLibArchivePrefix = "LIBARCHIVE." 53 54 hdrCreateTime = hdrLibArchivePrefix + "creationtime" 55 ) 56 57 var ( 58 // mutatedFiles is a list of files that are mutated by the import process 59 // and must be backed up and restored. 60 mutatedFiles = map[string]string{ 61 "UtilityVM/Files/EFI/Microsoft/Boot/BCD": "bcd.bak", 62 "UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG": "bcd.log.bak", 63 "UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG1": "bcd.log1.bak", 64 "UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG2": "bcd.log2.bak", 65 } 66 ) 67 68 // tarName returns platform-specific filepath 69 // to canonical posix-style path for tar archival. p is relative 70 // path. 71 func tarName(p string) (string, error) { 72 // windows: convert windows style relative path with backslashes 73 // into forward slashes. Since windows does not allow '/' or '\' 74 // in file names, it is mostly safe to replace however we must 75 // check just in case 76 if strings.Contains(p, "/") { 77 return "", fmt.Errorf("windows path contains forward slash: %s", p) 78 } 79 80 return strings.Replace(p, string(os.PathSeparator), "/", -1), nil 81 } 82 83 // chmodTarEntry is used to adjust the file permissions used in tar header based 84 // on the platform the archival is done. 85 func chmodTarEntry(perm os.FileMode) os.FileMode { 86 perm &= 0755 87 // Add the x bit: make everything +x from windows 88 perm |= 0111 89 90 return perm 91 } 92 93 func setHeaderForSpecialDevice(*tar.Header, string, os.FileInfo) error { 94 // do nothing. no notion of Rdev, Inode, Nlink in stat on Windows 95 return nil 96 } 97 98 func open(p string) (*os.File, error) { 99 // We use sys.OpenSequential to ensure we use sequential file 100 // access on Windows to avoid depleting the standby list. 101 return sys.OpenSequential(p) 102 } 103 104 func openFile(name string, flag int, perm os.FileMode) (*os.File, error) { 105 // Source is regular file. We use sys.OpenFileSequential to use sequential 106 // file access to avoid depleting the standby list on Windows. 107 return sys.OpenFileSequential(name, flag, perm) 108 } 109 110 func mkdir(path string, perm os.FileMode) error { 111 return os.Mkdir(path, perm) 112 } 113 114 func skipFile(hdr *tar.Header) bool { 115 // Windows does not support filenames with colons in them. Ignore 116 // these files. This is not a problem though (although it might 117 // appear that it is). Let's suppose a client is running docker pull. 118 // The daemon it points to is Windows. Would it make sense for the 119 // client to be doing a docker pull Ubuntu for example (which has files 120 // with colons in the name under /usr/share/man/man3)? No, absolutely 121 // not as it would really only make sense that they were pulling a 122 // Windows image. However, for development, it is necessary to be able 123 // to pull Linux images which are in the repository. 124 // 125 // TODO Windows. Once the registry is aware of what images are Windows- 126 // specific or Linux-specific, this warning should be changed to an error 127 // to cater for the situation where someone does manage to upload a Linux 128 // image but have it tagged as Windows inadvertently. 129 return strings.Contains(hdr.Name, ":") 130 } 131 132 // handleTarTypeBlockCharFifo is an OS-specific helper function used by 133 // createTarFile to handle the following types of header: Block; Char; Fifo 134 func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { 135 return nil 136 } 137 138 func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { 139 return nil 140 } 141 142 func getxattr(path, attr string) ([]byte, error) { 143 return nil, nil 144 } 145 146 func setxattr(path, key, value string) error { 147 // Return not support error, do not wrap underlying not supported 148 // since xattrs should not exist in windows diff archives 149 return errors.New("xattrs not supported on Windows") 150 } 151 152 // applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows 153 // layer using the hcsshim layer writer and backup streams. 154 // See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets 155 func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) { 156 home, id := filepath.Split(root) 157 info := hcsshim.DriverInfo{ 158 HomeDir: home, 159 } 160 161 w, err := hcsshim.NewLayerWriter(info, id, options.Parents) 162 if err != nil { 163 return 0, err 164 } 165 defer func() { 166 if err2 := w.Close(); err2 != nil { 167 // This error should not be discarded as a failure here 168 // could result in an invalid layer on disk 169 if err == nil { 170 err = err2 171 } 172 } 173 }() 174 175 buf := bufio.NewWriter(nil) 176 hdr, nextErr := tr.Next() 177 // Iterate through the files in the archive. 178 for { 179 select { 180 case <-ctx.Done(): 181 return 0, ctx.Err() 182 default: 183 } 184 185 if nextErr == io.EOF { 186 // end of tar archive 187 break 188 } 189 if nextErr != nil { 190 return 0, nextErr 191 } 192 193 // Note: path is used instead of filepath to prevent OS specific handling 194 // of the tar path 195 base := path.Base(hdr.Name) 196 if strings.HasPrefix(base, whiteoutPrefix) { 197 dir := path.Dir(hdr.Name) 198 originalBase := base[len(whiteoutPrefix):] 199 originalPath := path.Join(dir, originalBase) 200 if err := w.Remove(filepath.FromSlash(originalPath)); err != nil { 201 return 0, err 202 } 203 hdr, nextErr = tr.Next() 204 } else if hdr.Typeflag == tar.TypeLink { 205 err := w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname)) 206 if err != nil { 207 return 0, err 208 } 209 hdr, nextErr = tr.Next() 210 } else { 211 name, fileSize, fileInfo, err := fileInfoFromHeader(hdr) 212 if err != nil { 213 return 0, err 214 } 215 if err := w.Add(filepath.FromSlash(name), fileInfo); err != nil { 216 return 0, err 217 } 218 size += fileSize 219 hdr, nextErr = tarToBackupStreamWithMutatedFiles(buf, w, tr, hdr, root) 220 } 221 } 222 223 return 224 } 225 226 // fileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by 227 // WriteTarFileFromBackupStream. 228 func fileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) { 229 name = hdr.Name 230 if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA { 231 size = hdr.Size 232 } 233 fileInfo = &winio.FileBasicInfo{ 234 LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()), 235 LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()), 236 ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()), 237 238 // Default CreationTime to ModTime, updated below if MSWINDOWS.createtime exists 239 CreationTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()), 240 } 241 if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok { 242 attr, err := strconv.ParseUint(attrStr, 10, 32) 243 if err != nil { 244 return "", 0, nil, err 245 } 246 fileInfo.FileAttributes = uint32(attr) 247 } else { 248 if hdr.Typeflag == tar.TypeDir { 249 fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY 250 } 251 } 252 if createStr, ok := hdr.PAXRecords[hdrCreateTime]; ok { 253 createTime, err := parsePAXTime(createStr) 254 if err != nil { 255 return "", 0, nil, err 256 } 257 fileInfo.CreationTime = syscall.NsecToFiletime(createTime.UnixNano()) 258 } 259 return 260 } 261 262 // tarToBackupStreamWithMutatedFiles reads data from a tar stream and 263 // writes it to a backup stream, and also saves any files that will be mutated 264 // by the import layer process to a backup location. 265 func tarToBackupStreamWithMutatedFiles(buf *bufio.Writer, w io.Writer, t *tar.Reader, hdr *tar.Header, root string) (nextHdr *tar.Header, err error) { 266 var ( 267 bcdBackup *os.File 268 bcdBackupWriter *winio.BackupFileWriter 269 ) 270 if backupPath, ok := mutatedFiles[hdr.Name]; ok { 271 bcdBackup, err = os.Create(filepath.Join(root, backupPath)) 272 if err != nil { 273 return nil, err 274 } 275 defer func() { 276 cerr := bcdBackup.Close() 277 if err == nil { 278 err = cerr 279 } 280 }() 281 282 bcdBackupWriter = winio.NewBackupFileWriter(bcdBackup, false) 283 defer func() { 284 cerr := bcdBackupWriter.Close() 285 if err == nil { 286 err = cerr 287 } 288 }() 289 290 buf.Reset(io.MultiWriter(w, bcdBackupWriter)) 291 } else { 292 buf.Reset(w) 293 } 294 295 defer func() { 296 ferr := buf.Flush() 297 if err == nil { 298 err = ferr 299 } 300 }() 301 302 return writeBackupStreamFromTarFile(buf, t, hdr) 303 } 304 305 // writeBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple 306 // tar file entries in order to collect all the alternate data streams for the file, it returns the next 307 // tar file that was not processed, or io.EOF is there are no more. 308 func writeBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) { 309 bw := winio.NewBackupStreamWriter(w) 310 var sd []byte 311 var err error 312 // Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written 313 // by this library will have raw binary for the security descriptor. 314 if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok { 315 sd, err = winio.SddlToSecurityDescriptor(sddl) 316 if err != nil { 317 return nil, err 318 } 319 } 320 if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok { 321 sd, err = base64.StdEncoding.DecodeString(sdraw) 322 if err != nil { 323 return nil, err 324 } 325 } 326 if len(sd) != 0 { 327 bhdr := winio.BackupHeader{ 328 Id: winio.BackupSecurity, 329 Size: int64(len(sd)), 330 } 331 err := bw.WriteHeader(&bhdr) 332 if err != nil { 333 return nil, err 334 } 335 _, err = bw.Write(sd) 336 if err != nil { 337 return nil, err 338 } 339 } 340 var eas []winio.ExtendedAttribute 341 for k, v := range hdr.PAXRecords { 342 if !strings.HasPrefix(k, hdrEaPrefix) { 343 continue 344 } 345 data, err := base64.StdEncoding.DecodeString(v) 346 if err != nil { 347 return nil, err 348 } 349 eas = append(eas, winio.ExtendedAttribute{ 350 Name: k[len(hdrEaPrefix):], 351 Value: data, 352 }) 353 } 354 if len(eas) != 0 { 355 eadata, err := winio.EncodeExtendedAttributes(eas) 356 if err != nil { 357 return nil, err 358 } 359 bhdr := winio.BackupHeader{ 360 Id: winio.BackupEaData, 361 Size: int64(len(eadata)), 362 } 363 err = bw.WriteHeader(&bhdr) 364 if err != nil { 365 return nil, err 366 } 367 _, err = bw.Write(eadata) 368 if err != nil { 369 return nil, err 370 } 371 } 372 if hdr.Typeflag == tar.TypeSymlink { 373 _, isMountPoint := hdr.PAXRecords[hdrMountPoint] 374 rp := winio.ReparsePoint{ 375 Target: filepath.FromSlash(hdr.Linkname), 376 IsMountPoint: isMountPoint, 377 } 378 reparse := winio.EncodeReparsePoint(&rp) 379 bhdr := winio.BackupHeader{ 380 Id: winio.BackupReparseData, 381 Size: int64(len(reparse)), 382 } 383 err := bw.WriteHeader(&bhdr) 384 if err != nil { 385 return nil, err 386 } 387 _, err = bw.Write(reparse) 388 if err != nil { 389 return nil, err 390 } 391 } 392 393 buf := bufPool.Get().(*[]byte) 394 defer bufPool.Put(buf) 395 396 if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA { 397 bhdr := winio.BackupHeader{ 398 Id: winio.BackupData, 399 Size: hdr.Size, 400 } 401 err := bw.WriteHeader(&bhdr) 402 if err != nil { 403 return nil, err 404 } 405 _, err = io.CopyBuffer(bw, t, *buf) 406 if err != nil { 407 return nil, err 408 } 409 } 410 // Copy all the alternate data streams and return the next non-ADS header. 411 for { 412 ahdr, err := t.Next() 413 if err != nil { 414 return nil, err 415 } 416 if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") { 417 return ahdr, nil 418 } 419 bhdr := winio.BackupHeader{ 420 Id: winio.BackupAlternateData, 421 Size: ahdr.Size, 422 Name: ahdr.Name[len(hdr.Name):] + ":$DATA", 423 } 424 err = bw.WriteHeader(&bhdr) 425 if err != nil { 426 return nil, err 427 } 428 _, err = io.CopyBuffer(bw, t, *buf) 429 if err != nil { 430 return nil, err 431 } 432 } 433 } 434 435 func copyDirInfo(fi os.FileInfo, path string) error { 436 if err := os.Chmod(path, fi.Mode()); err != nil { 437 return errors.Wrapf(err, "failed to chmod %s", path) 438 } 439 return nil 440 } 441 442 func copyUpXAttrs(dst, src string) error { 443 return nil 444 }