1// +build windows 2 3package backuptar 4 5import ( 6 "archive/tar" 7 "encoding/base64" 8 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "syscall" 16 "time" 17 18 "github.com/Microsoft/go-winio" 19) 20 21const ( 22 c_ISUID = 04000 // Set uid 23 c_ISGID = 02000 // Set gid 24 c_ISVTX = 01000 // Save text (sticky bit) 25 c_ISDIR = 040000 // Directory 26 c_ISFIFO = 010000 // FIFO 27 c_ISREG = 0100000 // Regular file 28 c_ISLNK = 0120000 // Symbolic link 29 c_ISBLK = 060000 // Block special file 30 c_ISCHR = 020000 // Character special file 31 c_ISSOCK = 0140000 // Socket 32) 33 34const ( 35 hdrFileAttributes = "MSWINDOWS.fileattr" 36 hdrSecurityDescriptor = "MSWINDOWS.sd" 37 hdrRawSecurityDescriptor = "MSWINDOWS.rawsd" 38 hdrMountPoint = "MSWINDOWS.mountpoint" 39 hdrEaPrefix = "MSWINDOWS.xattr." 40 41 hdrCreationTime = "LIBARCHIVE.creationtime" 42) 43 44func writeZeroes(w io.Writer, count int64) error { 45 buf := make([]byte, 8192) 46 c := len(buf) 47 for i := int64(0); i < count; i += int64(c) { 48 if int64(c) > count-i { 49 c = int(count - i) 50 } 51 _, err := w.Write(buf[:c]) 52 if err != nil { 53 return err 54 } 55 } 56 return nil 57} 58 59func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { 60 curOffset := int64(0) 61 for { 62 bhdr, err := br.Next() 63 if err == io.EOF { 64 err = io.ErrUnexpectedEOF 65 } 66 if err != nil { 67 return err 68 } 69 if bhdr.Id != winio.BackupSparseBlock { 70 return fmt.Errorf("unexpected stream %d", bhdr.Id) 71 } 72 73 // archive/tar does not support writing sparse files 74 // so just write zeroes to catch up to the current offset. 75 err = writeZeroes(t, bhdr.Offset-curOffset) 76 if bhdr.Size == 0 { 77 break 78 } 79 n, err := io.Copy(t, br) 80 if err != nil { 81 return err 82 } 83 curOffset = bhdr.Offset + n 84 } 85 return nil 86} 87 88// BasicInfoHeader creates a tar header from basic file information. 89func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header { 90 hdr := &tar.Header{ 91 Format: tar.FormatPAX, 92 Name: filepath.ToSlash(name), 93 Size: size, 94 Typeflag: tar.TypeReg, 95 ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()), 96 ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()), 97 AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()), 98 PAXRecords: make(map[string]string), 99 } 100 hdr.PAXRecords[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes) 101 hdr.PAXRecords[hdrCreationTime] = formatPAXTime(time.Unix(0, fileInfo.CreationTime.Nanoseconds())) 102 103 if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 { 104 hdr.Mode |= c_ISDIR 105 hdr.Size = 0 106 hdr.Typeflag = tar.TypeDir 107 } 108 return hdr 109} 110 111// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream. 112// 113// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS. 114// 115// The additional Win32 metadata is: 116// 117// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value 118// 119// MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format 120// 121// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink) 122func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error { 123 name = filepath.ToSlash(name) 124 hdr := BasicInfoHeader(name, size, fileInfo) 125 126 // If r can be seeked, then this function is two-pass: pass 1 collects the 127 // tar header data, and pass 2 copies the data stream. If r cannot be 128 // seeked, then some header data (in particular EAs) will be silently lost. 129 var ( 130 restartPos int64 131 err error 132 ) 133 sr, readTwice := r.(io.Seeker) 134 if readTwice { 135 if restartPos, err = sr.Seek(0, io.SeekCurrent); err != nil { 136 readTwice = false 137 } 138 } 139 140 br := winio.NewBackupStreamReader(r) 141 var dataHdr *winio.BackupHeader 142 for dataHdr == nil { 143 bhdr, err := br.Next() 144 if err == io.EOF { 145 break 146 } 147 if err != nil { 148 return err 149 } 150 switch bhdr.Id { 151 case winio.BackupData: 152 hdr.Mode |= c_ISREG 153 if !readTwice { 154 dataHdr = bhdr 155 } 156 case winio.BackupSecurity: 157 sd, err := ioutil.ReadAll(br) 158 if err != nil { 159 return err 160 } 161 hdr.PAXRecords[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd) 162 163 case winio.BackupReparseData: 164 hdr.Mode |= c_ISLNK 165 hdr.Typeflag = tar.TypeSymlink 166 reparseBuffer, err := ioutil.ReadAll(br) 167 rp, err := winio.DecodeReparsePoint(reparseBuffer) 168 if err != nil { 169 return err 170 } 171 if rp.IsMountPoint { 172 hdr.PAXRecords[hdrMountPoint] = "1" 173 } 174 hdr.Linkname = rp.Target 175 176 case winio.BackupEaData: 177 eab, err := ioutil.ReadAll(br) 178 if err != nil { 179 return err 180 } 181 eas, err := winio.DecodeExtendedAttributes(eab) 182 if err != nil { 183 return err 184 } 185 for _, ea := range eas { 186 // Use base64 encoding for the binary value. Note that there 187 // is no way to encode the EA's flags, since their use doesn't 188 // make any sense for persisted EAs. 189 hdr.PAXRecords[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value) 190 } 191 192 case winio.BackupAlternateData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: 193 // ignore these streams 194 default: 195 return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id) 196 } 197 } 198 199 err = t.WriteHeader(hdr) 200 if err != nil { 201 return err 202 } 203 204 if readTwice { 205 // Get back to the data stream. 206 if _, err = sr.Seek(restartPos, io.SeekStart); err != nil { 207 return err 208 } 209 for dataHdr == nil { 210 bhdr, err := br.Next() 211 if err == io.EOF { 212 break 213 } 214 if err != nil { 215 return err 216 } 217 if bhdr.Id == winio.BackupData { 218 dataHdr = bhdr 219 } 220 } 221 } 222 223 if dataHdr != nil { 224 // A data stream was found. Copy the data. 225 if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 { 226 if size != dataHdr.Size { 227 return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size) 228 } 229 _, err = io.Copy(t, br) 230 if err != nil { 231 return err 232 } 233 } else { 234 err = copySparse(t, br) 235 if err != nil { 236 return err 237 } 238 } 239 } 240 241 // Look for streams after the data stream. The only ones we handle are alternate data streams. 242 // Other streams may have metadata that could be serialized, but the tar header has already 243 // been written. In practice, this means that we don't get EA or TXF metadata. 244 for { 245 bhdr, err := br.Next() 246 if err == io.EOF { 247 break 248 } 249 if err != nil { 250 return err 251 } 252 switch bhdr.Id { 253 case winio.BackupAlternateData: 254 altName := bhdr.Name 255 if strings.HasSuffix(altName, ":$DATA") { 256 altName = altName[:len(altName)-len(":$DATA")] 257 } 258 if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 { 259 hdr = &tar.Header{ 260 Format: hdr.Format, 261 Name: name + altName, 262 Mode: hdr.Mode, 263 Typeflag: tar.TypeReg, 264 Size: bhdr.Size, 265 ModTime: hdr.ModTime, 266 AccessTime: hdr.AccessTime, 267 ChangeTime: hdr.ChangeTime, 268 } 269 err = t.WriteHeader(hdr) 270 if err != nil { 271 return err 272 } 273 _, err = io.Copy(t, br) 274 if err != nil { 275 return err 276 } 277 278 } else { 279 // Unsupported for now, since the size of the alternate stream is not present 280 // in the backup stream until after the data has been read. 281 return errors.New("tar of sparse alternate data streams is unsupported") 282 } 283 case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData: 284 // ignore these streams 285 default: 286 return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id) 287 } 288 } 289 return nil 290} 291 292// FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by 293// WriteTarFileFromBackupStream. 294func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) { 295 name = hdr.Name 296 if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA { 297 size = hdr.Size 298 } 299 fileInfo = &winio.FileBasicInfo{ 300 LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()), 301 LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()), 302 ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()), 303 // Default to ModTime, we'll pull hdrCreationTime below if present 304 CreationTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()), 305 } 306 if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok { 307 attr, err := strconv.ParseUint(attrStr, 10, 32) 308 if err != nil { 309 return "", 0, nil, err 310 } 311 fileInfo.FileAttributes = uint32(attr) 312 } else { 313 if hdr.Typeflag == tar.TypeDir { 314 fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY 315 } 316 } 317 if creationTimeStr, ok := hdr.PAXRecords[hdrCreationTime]; ok { 318 creationTime, err := parsePAXTime(creationTimeStr) 319 if err != nil { 320 return "", 0, nil, err 321 } 322 fileInfo.CreationTime = syscall.NsecToFiletime(creationTime.UnixNano()) 323 } 324 return 325} 326 327// WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple 328// tar file entries in order to collect all the alternate data streams for the file, it returns the next 329// tar file that was not processed, or io.EOF is there are no more. 330func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) { 331 bw := winio.NewBackupStreamWriter(w) 332 var sd []byte 333 var err error 334 // Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written 335 // by this library will have raw binary for the security descriptor. 336 if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok { 337 sd, err = winio.SddlToSecurityDescriptor(sddl) 338 if err != nil { 339 return nil, err 340 } 341 } 342 if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok { 343 sd, err = base64.StdEncoding.DecodeString(sdraw) 344 if err != nil { 345 return nil, err 346 } 347 } 348 if len(sd) != 0 { 349 bhdr := winio.BackupHeader{ 350 Id: winio.BackupSecurity, 351 Size: int64(len(sd)), 352 } 353 err := bw.WriteHeader(&bhdr) 354 if err != nil { 355 return nil, err 356 } 357 _, err = bw.Write(sd) 358 if err != nil { 359 return nil, err 360 } 361 } 362 var eas []winio.ExtendedAttribute 363 for k, v := range hdr.PAXRecords { 364 if !strings.HasPrefix(k, hdrEaPrefix) { 365 continue 366 } 367 data, err := base64.StdEncoding.DecodeString(v) 368 if err != nil { 369 return nil, err 370 } 371 eas = append(eas, winio.ExtendedAttribute{ 372 Name: k[len(hdrEaPrefix):], 373 Value: data, 374 }) 375 } 376 if len(eas) != 0 { 377 eadata, err := winio.EncodeExtendedAttributes(eas) 378 if err != nil { 379 return nil, err 380 } 381 bhdr := winio.BackupHeader{ 382 Id: winio.BackupEaData, 383 Size: int64(len(eadata)), 384 } 385 err = bw.WriteHeader(&bhdr) 386 if err != nil { 387 return nil, err 388 } 389 _, err = bw.Write(eadata) 390 if err != nil { 391 return nil, err 392 } 393 } 394 if hdr.Typeflag == tar.TypeSymlink { 395 _, isMountPoint := hdr.PAXRecords[hdrMountPoint] 396 rp := winio.ReparsePoint{ 397 Target: filepath.FromSlash(hdr.Linkname), 398 IsMountPoint: isMountPoint, 399 } 400 reparse := winio.EncodeReparsePoint(&rp) 401 bhdr := winio.BackupHeader{ 402 Id: winio.BackupReparseData, 403 Size: int64(len(reparse)), 404 } 405 err := bw.WriteHeader(&bhdr) 406 if err != nil { 407 return nil, err 408 } 409 _, err = bw.Write(reparse) 410 if err != nil { 411 return nil, err 412 } 413 } 414 if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA { 415 bhdr := winio.BackupHeader{ 416 Id: winio.BackupData, 417 Size: hdr.Size, 418 } 419 err := bw.WriteHeader(&bhdr) 420 if err != nil { 421 return nil, err 422 } 423 _, err = io.Copy(bw, t) 424 if err != nil { 425 return nil, err 426 } 427 } 428 // Copy all the alternate data streams and return the next non-ADS header. 429 for { 430 ahdr, err := t.Next() 431 if err != nil { 432 return nil, err 433 } 434 if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") { 435 return ahdr, nil 436 } 437 bhdr := winio.BackupHeader{ 438 Id: winio.BackupAlternateData, 439 Size: ahdr.Size, 440 Name: ahdr.Name[len(hdr.Name):] + ":$DATA", 441 } 442 err = bw.WriteHeader(&bhdr) 443 if err != nil { 444 return nil, err 445 } 446 _, err = io.Copy(bw, t) 447 if err != nil { 448 return nil, err 449 } 450 } 451} 452