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