1// Package wim implements a WIM file parser. 2// 3// WIM files are used to distribute Windows file system and container images. 4// They are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/dd861280.aspx. 5package wim 6 7import ( 8 "bytes" 9 "crypto/sha1" 10 "encoding/binary" 11 "encoding/xml" 12 "errors" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "strconv" 17 "sync" 18 "time" 19 "unicode/utf16" 20) 21 22// File attribute constants from Windows. 23const ( 24 FILE_ATTRIBUTE_READONLY = 0x00000001 25 FILE_ATTRIBUTE_HIDDEN = 0x00000002 26 FILE_ATTRIBUTE_SYSTEM = 0x00000004 27 FILE_ATTRIBUTE_DIRECTORY = 0x00000010 28 FILE_ATTRIBUTE_ARCHIVE = 0x00000020 29 FILE_ATTRIBUTE_DEVICE = 0x00000040 30 FILE_ATTRIBUTE_NORMAL = 0x00000080 31 FILE_ATTRIBUTE_TEMPORARY = 0x00000100 32 FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200 33 FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400 34 FILE_ATTRIBUTE_COMPRESSED = 0x00000800 35 FILE_ATTRIBUTE_OFFLINE = 0x00001000 36 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000 37 FILE_ATTRIBUTE_ENCRYPTED = 0x00004000 38 FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000 39 FILE_ATTRIBUTE_VIRTUAL = 0x00010000 40 FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000 41 FILE_ATTRIBUTE_EA = 0x00040000 42) 43 44// Windows processor architectures. 45const ( 46 PROCESSOR_ARCHITECTURE_INTEL = 0 47 PROCESSOR_ARCHITECTURE_MIPS = 1 48 PROCESSOR_ARCHITECTURE_ALPHA = 2 49 PROCESSOR_ARCHITECTURE_PPC = 3 50 PROCESSOR_ARCHITECTURE_SHX = 4 51 PROCESSOR_ARCHITECTURE_ARM = 5 52 PROCESSOR_ARCHITECTURE_IA64 = 6 53 PROCESSOR_ARCHITECTURE_ALPHA64 = 7 54 PROCESSOR_ARCHITECTURE_MSIL = 8 55 PROCESSOR_ARCHITECTURE_AMD64 = 9 56 PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10 57 PROCESSOR_ARCHITECTURE_NEUTRAL = 11 58 PROCESSOR_ARCHITECTURE_ARM64 = 12 59) 60 61var wimImageTag = [...]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0} 62 63type guid struct { 64 Data1 uint32 65 Data2 uint16 66 Data3 uint16 67 Data4 [8]byte 68} 69 70func (g guid) String() string { 71 return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", g.Data1, g.Data2, g.Data3, g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]) 72} 73 74type resourceDescriptor struct { 75 FlagsAndCompressedSize uint64 76 Offset int64 77 OriginalSize int64 78} 79 80type resFlag byte 81 82const ( 83 resFlagFree resFlag = 1 << iota 84 resFlagMetadata 85 resFlagCompressed 86 resFlagSpanned 87) 88 89const validate = false 90 91const supportedResFlags = resFlagMetadata | resFlagCompressed 92 93func (r *resourceDescriptor) Flags() resFlag { 94 return resFlag(r.FlagsAndCompressedSize >> 56) 95} 96 97func (r *resourceDescriptor) CompressedSize() int64 { 98 return int64(r.FlagsAndCompressedSize & 0xffffffffffffff) 99} 100 101func (r *resourceDescriptor) String() string { 102 s := fmt.Sprintf("%d bytes at %d", r.CompressedSize(), r.Offset) 103 if r.Flags()&4 != 0 { 104 s += fmt.Sprintf(" (uncompresses to %d)", r.OriginalSize) 105 } 106 return s 107} 108 109// SHA1Hash contains the SHA1 hash of a file or stream. 110type SHA1Hash [20]byte 111 112type streamDescriptor struct { 113 resourceDescriptor 114 PartNumber uint16 115 RefCount uint32 116 Hash SHA1Hash 117} 118 119type hdrFlag uint32 120 121const ( 122 hdrFlagReserved hdrFlag = 1 << iota 123 hdrFlagCompressed 124 hdrFlagReadOnly 125 hdrFlagSpanned 126 hdrFlagResourceOnly 127 hdrFlagMetadataOnly 128 hdrFlagWriteInProgress 129 hdrFlagRpFix 130) 131 132const ( 133 hdrFlagCompressReserved hdrFlag = 1 << (iota + 16) 134 hdrFlagCompressXpress 135 hdrFlagCompressLzx 136) 137 138const supportedHdrFlags = hdrFlagRpFix | hdrFlagReadOnly | hdrFlagCompressed | hdrFlagCompressLzx 139 140type wimHeader struct { 141 ImageTag [8]byte 142 Size uint32 143 Version uint32 144 Flags hdrFlag 145 CompressionSize uint32 146 WIMGuid guid 147 PartNumber uint16 148 TotalParts uint16 149 ImageCount uint32 150 OffsetTable resourceDescriptor 151 XMLData resourceDescriptor 152 BootMetadata resourceDescriptor 153 BootIndex uint32 154 Padding uint32 155 Integrity resourceDescriptor 156 Unused [60]byte 157} 158 159type securityblockDisk struct { 160 TotalLength uint32 161 NumEntries uint32 162} 163 164const securityblockDiskSize = 8 165 166type direntry struct { 167 Attributes uint32 168 SecurityID uint32 169 SubdirOffset int64 170 Unused1, Unused2 int64 171 CreationTime Filetime 172 LastAccessTime Filetime 173 LastWriteTime Filetime 174 Hash SHA1Hash 175 Padding uint32 176 ReparseHardLink int64 177 StreamCount uint16 178 ShortNameLength uint16 179 FileNameLength uint16 180} 181 182var direntrySize = int64(binary.Size(direntry{}) + 8) // includes an 8-byte length prefix 183 184type streamentry struct { 185 Unused int64 186 Hash SHA1Hash 187 NameLength int16 188} 189 190var streamentrySize = int64(binary.Size(streamentry{}) + 8) // includes an 8-byte length prefix 191 192// Filetime represents a Windows time. 193type Filetime struct { 194 LowDateTime uint32 195 HighDateTime uint32 196} 197 198// Time returns the time as time.Time. 199func (ft *Filetime) Time() time.Time { 200 // 100-nanosecond intervals since January 1, 1601 201 nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) 202 // change starting time to the Epoch (00:00:00 UTC, January 1, 1970) 203 nsec -= 116444736000000000 204 // convert into nanoseconds 205 nsec *= 100 206 return time.Unix(0, nsec) 207} 208 209// UnmarshalXML unmarshals the time from a WIM XML blob. 210func (ft *Filetime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 211 type time struct { 212 Low string `xml:"LOWPART"` 213 High string `xml:"HIGHPART"` 214 } 215 var t time 216 err := d.DecodeElement(&t, &start) 217 if err != nil { 218 return err 219 } 220 221 low, err := strconv.ParseUint(t.Low, 0, 32) 222 if err != nil { 223 return err 224 } 225 high, err := strconv.ParseUint(t.High, 0, 32) 226 if err != nil { 227 return err 228 } 229 230 ft.LowDateTime = uint32(low) 231 ft.HighDateTime = uint32(high) 232 return nil 233} 234 235type info struct { 236 Image []ImageInfo `xml:"IMAGE"` 237} 238 239// ImageInfo contains information about the image. 240type ImageInfo struct { 241 Name string `xml:"NAME"` 242 Index int `xml:"INDEX,attr"` 243 CreationTime Filetime `xml:"CREATIONTIME"` 244 ModTime Filetime `xml:"LASTMODIFICATIONTIME"` 245 Windows *WindowsInfo `xml:"WINDOWS"` 246} 247 248// WindowsInfo contains information about the Windows installation in the image. 249type WindowsInfo struct { 250 Arch byte `xml:"ARCH"` 251 ProductName string `xml:"PRODUCTNAME"` 252 EditionID string `xml:"EDITIONID"` 253 InstallationType string `xml:"INSTALLATIONTYPE"` 254 ProductType string `xml:"PRODUCTTYPE"` 255 Languages []string `xml:"LANGUAGES>LANGUAGE"` 256 DefaultLanguage string `xml:"LANGUAGES>DEFAULT"` 257 Version Version `xml:"VERSION"` 258 SystemRoot string `xml:"SYSTEMROOT"` 259} 260 261// Version represents a Windows build version. 262type Version struct { 263 Major int `xml:"MAJOR"` 264 Minor int `xml:"MINOR"` 265 Build int `xml:"BUILD"` 266 SPBuild int `xml:"SPBUILD"` 267 SPLevel int `xml:"SPLEVEL"` 268} 269 270// ParseError is returned when the WIM cannot be parsed. 271type ParseError struct { 272 Oper string 273 Path string 274 Err error 275} 276 277func (e *ParseError) Error() string { 278 if e.Path == "" { 279 return "WIM parse error at " + e.Oper + ": " + e.Err.Error() 280 } 281 return fmt.Sprintf("WIM parse error: %s %s: %s", e.Oper, e.Path, e.Err.Error()) 282} 283 284// Reader provides functions to read a WIM file. 285type Reader struct { 286 hdr wimHeader 287 r io.ReaderAt 288 fileData map[SHA1Hash]resourceDescriptor 289 290 XMLInfo string // The XML information about the WIM. 291 Image []*Image // The WIM's images. 292} 293 294// Image represents an image within a WIM file. 295type Image struct { 296 wim *Reader 297 offset resourceDescriptor 298 sds [][]byte 299 rootOffset int64 300 r io.ReadCloser 301 curOffset int64 302 m sync.Mutex 303 304 ImageInfo 305} 306 307// StreamHeader contains alternate data stream metadata. 308type StreamHeader struct { 309 Name string 310 Hash SHA1Hash 311 Size int64 312} 313 314// Stream represents an alternate data stream or reparse point data stream. 315type Stream struct { 316 StreamHeader 317 wim *Reader 318 offset resourceDescriptor 319} 320 321// FileHeader contains file metadata. 322type FileHeader struct { 323 Name string 324 ShortName string 325 Attributes uint32 326 SecurityDescriptor []byte 327 CreationTime Filetime 328 LastAccessTime Filetime 329 LastWriteTime Filetime 330 Hash SHA1Hash 331 Size int64 332 LinkID int64 333 ReparseTag uint32 334 ReparseReserved uint32 335} 336 337// File represents a file or directory in a WIM image. 338type File struct { 339 FileHeader 340 Streams []*Stream 341 offset resourceDescriptor 342 img *Image 343 subdirOffset int64 344} 345 346// NewReader returns a Reader that can be used to read WIM file data. 347func NewReader(f io.ReaderAt) (*Reader, error) { 348 r := &Reader{r: f} 349 section := io.NewSectionReader(f, 0, 0xffff) 350 err := binary.Read(section, binary.LittleEndian, &r.hdr) 351 if err != nil { 352 return nil, err 353 } 354 355 if r.hdr.ImageTag != wimImageTag { 356 return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")} 357 } 358 359 if r.hdr.Flags&^supportedHdrFlags != 0 { 360 return nil, fmt.Errorf("unsupported WIM flags %x", r.hdr.Flags&^supportedHdrFlags) 361 } 362 363 if r.hdr.CompressionSize != 0x8000 { 364 return nil, fmt.Errorf("unsupported compression size %d", r.hdr.CompressionSize) 365 } 366 367 if r.hdr.TotalParts != 1 { 368 return nil, errors.New("multi-part WIM not supported") 369 } 370 371 fileData, images, err := r.readOffsetTable(&r.hdr.OffsetTable) 372 if err != nil { 373 return nil, err 374 } 375 376 xmlinfo, err := r.readXML() 377 if err != nil { 378 return nil, err 379 } 380 381 var info info 382 err = xml.Unmarshal([]byte(xmlinfo), &info) 383 if err != nil { 384 return nil, &ParseError{Oper: "XML info", Err: err} 385 } 386 387 for i, img := range images { 388 for _, imgInfo := range info.Image { 389 if imgInfo.Index == i+1 { 390 img.ImageInfo = imgInfo 391 break 392 } 393 } 394 } 395 396 r.fileData = fileData 397 r.Image = images 398 r.XMLInfo = xmlinfo 399 return r, nil 400} 401 402// Close releases resources associated with the Reader. 403func (r *Reader) Close() error { 404 for _, img := range r.Image { 405 img.reset() 406 } 407 return nil 408} 409 410func (r *Reader) resourceReader(hdr *resourceDescriptor) (io.ReadCloser, error) { 411 return r.resourceReaderWithOffset(hdr, 0) 412} 413 414func (r *Reader) resourceReaderWithOffset(hdr *resourceDescriptor, offset int64) (io.ReadCloser, error) { 415 var sr io.ReadCloser 416 section := io.NewSectionReader(r.r, hdr.Offset, hdr.CompressedSize()) 417 if hdr.Flags()&resFlagCompressed == 0 { 418 section.Seek(offset, 0) 419 sr = ioutil.NopCloser(section) 420 } else { 421 cr, err := newCompressedReader(section, hdr.OriginalSize, offset) 422 if err != nil { 423 return nil, err 424 } 425 sr = cr 426 } 427 428 return sr, nil 429} 430 431func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) { 432 rsrc, err := r.resourceReader(hdr) 433 if err != nil { 434 return nil, err 435 } 436 defer rsrc.Close() 437 return ioutil.ReadAll(rsrc) 438} 439 440func (r *Reader) readXML() (string, error) { 441 if r.hdr.XMLData.CompressedSize() == 0 { 442 return "", nil 443 } 444 rsrc, err := r.resourceReader(&r.hdr.XMLData) 445 if err != nil { 446 return "", err 447 } 448 defer rsrc.Close() 449 450 XMLData := make([]uint16, r.hdr.XMLData.OriginalSize/2) 451 err = binary.Read(rsrc, binary.LittleEndian, XMLData) 452 if err != nil { 453 return "", &ParseError{Oper: "XML data", Err: err} 454 } 455 456 // The BOM will always indicate little-endian UTF-16. 457 if XMLData[0] != 0xfeff { 458 return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")} 459 } 460 return string(utf16.Decode(XMLData[1:])), nil 461} 462 463func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resourceDescriptor, []*Image, error) { 464 fileData := make(map[SHA1Hash]resourceDescriptor) 465 var images []*Image 466 467 offsetTable, err := r.readResource(res) 468 if err != nil { 469 return nil, nil, &ParseError{Oper: "offset table", Err: err} 470 } 471 472 br := bytes.NewReader(offsetTable) 473 for i := 0; ; i++ { 474 var res streamDescriptor 475 err := binary.Read(br, binary.LittleEndian, &res) 476 if err == io.EOF { 477 break 478 } 479 if err != nil { 480 return nil, nil, &ParseError{Oper: "offset table", Err: err} 481 } 482 if res.Flags()&^supportedResFlags != 0 { 483 return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")} 484 } 485 486 // Validation for ad-hoc testing 487 if validate { 488 sec, err := r.resourceReader(&res.resourceDescriptor) 489 if err != nil { 490 panic(fmt.Sprint(i, err)) 491 } 492 hash := sha1.New() 493 _, err = io.Copy(hash, sec) 494 sec.Close() 495 if err != nil { 496 panic(fmt.Sprint(i, err)) 497 } 498 var cmphash SHA1Hash 499 copy(cmphash[:], hash.Sum(nil)) 500 if cmphash != res.Hash { 501 panic(fmt.Sprint(i, "hash mismatch")) 502 } 503 } 504 505 if res.Flags()&resFlagMetadata != 0 { 506 image := &Image{ 507 wim: r, 508 offset: res.resourceDescriptor, 509 } 510 images = append(images, image) 511 } else { 512 fileData[res.Hash] = res.resourceDescriptor 513 } 514 } 515 516 if len(images) != int(r.hdr.ImageCount) { 517 return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")} 518 } 519 520 return fileData, images, nil 521} 522 523func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, err error) { 524 var secBlock securityblockDisk 525 err = binary.Read(rsrc, binary.LittleEndian, &secBlock) 526 if err != nil { 527 err = &ParseError{Oper: "security table", Err: err} 528 return 529 } 530 531 n += securityblockDiskSize 532 533 secSizes := make([]int64, secBlock.NumEntries) 534 err = binary.Read(rsrc, binary.LittleEndian, &secSizes) 535 if err != nil { 536 err = &ParseError{Oper: "security table sizes", Err: err} 537 return 538 } 539 540 n += int64(secBlock.NumEntries * 8) 541 542 sds = make([][]byte, secBlock.NumEntries) 543 for i, size := range secSizes { 544 sd := make([]byte, size&0xffffffff) 545 _, err = io.ReadFull(rsrc, sd) 546 if err != nil { 547 err = &ParseError{Oper: "security descriptor", Err: err} 548 return 549 } 550 n += int64(len(sd)) 551 sds[i] = sd 552 } 553 554 secsize := int64((secBlock.TotalLength + 7) &^ 7) 555 if n > secsize { 556 err = &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")} 557 return 558 } 559 560 _, err = io.CopyN(ioutil.Discard, rsrc, secsize-n) 561 if err != nil { 562 return 563 } 564 565 n = secsize 566 return 567} 568 569// Open parses the image and returns the root directory. 570func (img *Image) Open() (*File, error) { 571 if img.sds == nil { 572 rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, img.rootOffset) 573 if err != nil { 574 return nil, err 575 } 576 sds, n, err := img.wim.readSecurityDescriptors(rsrc) 577 if err != nil { 578 rsrc.Close() 579 return nil, err 580 } 581 img.sds = sds 582 img.r = rsrc 583 img.rootOffset = n 584 img.curOffset = n 585 } 586 587 f, err := img.readdir(img.rootOffset) 588 if err != nil { 589 return nil, err 590 } 591 if len(f) != 1 { 592 return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")} 593 } 594 return f[0], err 595} 596 597func (img *Image) reset() { 598 if img.r != nil { 599 img.r.Close() 600 img.r = nil 601 } 602 img.curOffset = -1 603} 604 605func (img *Image) readdir(offset int64) ([]*File, error) { 606 img.m.Lock() 607 defer img.m.Unlock() 608 609 if offset < img.curOffset || offset > img.curOffset+chunkSize { 610 // Reset to seek backward or to seek forward very far. 611 img.reset() 612 } 613 if img.r == nil { 614 rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, offset) 615 if err != nil { 616 return nil, err 617 } 618 img.r = rsrc 619 img.curOffset = offset 620 } 621 if offset > img.curOffset { 622 _, err := io.CopyN(ioutil.Discard, img.r, offset-img.curOffset) 623 if err != nil { 624 img.reset() 625 if err == io.EOF { 626 err = io.ErrUnexpectedEOF 627 } 628 return nil, err 629 } 630 } 631 632 var entries []*File 633 for { 634 e, n, err := img.readNextEntry(img.r) 635 img.curOffset += n 636 if err == io.EOF { 637 break 638 } 639 if err != nil { 640 img.reset() 641 return nil, err 642 } 643 entries = append(entries, e) 644 } 645 return entries, nil 646} 647 648func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) { 649 var length int64 650 err := binary.Read(r, binary.LittleEndian, &length) 651 if err != nil { 652 return nil, 0, &ParseError{Oper: "directory length check", Err: err} 653 } 654 655 if length == 0 { 656 return nil, 8, io.EOF 657 } 658 659 left := length 660 if left < direntrySize { 661 return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short")} 662 } 663 664 var dentry direntry 665 err = binary.Read(r, binary.LittleEndian, &dentry) 666 if err != nil { 667 return nil, 0, &ParseError{Oper: "directory entry", Err: err} 668 } 669 670 left -= direntrySize 671 672 namesLen := int64(dentry.FileNameLength + 2 + dentry.ShortNameLength) 673 if left < namesLen { 674 return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short for names")} 675 } 676 677 names := make([]uint16, namesLen/2) 678 err = binary.Read(r, binary.LittleEndian, names) 679 if err != nil { 680 return nil, 0, &ParseError{Oper: "file name", Err: err} 681 } 682 683 left -= namesLen 684 685 var name, shortName string 686 if dentry.FileNameLength > 0 { 687 name = string(utf16.Decode(names[:dentry.FileNameLength/2])) 688 } 689 690 if dentry.ShortNameLength > 0 { 691 shortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:])) 692 } 693 694 var offset resourceDescriptor 695 zerohash := SHA1Hash{} 696 if dentry.Hash != zerohash { 697 var ok bool 698 offset, ok = img.wim.fileData[dentry.Hash] 699 if !ok { 700 return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %#v", dentry)} 701 } 702 } 703 704 f := &File{ 705 FileHeader: FileHeader{ 706 Attributes: dentry.Attributes, 707 CreationTime: dentry.CreationTime, 708 LastAccessTime: dentry.LastAccessTime, 709 LastWriteTime: dentry.LastWriteTime, 710 Hash: dentry.Hash, 711 Size: offset.OriginalSize, 712 Name: name, 713 ShortName: shortName, 714 }, 715 716 offset: offset, 717 img: img, 718 subdirOffset: dentry.SubdirOffset, 719 } 720 721 isDir := false 722 723 if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT == 0 { 724 f.LinkID = dentry.ReparseHardLink 725 if dentry.Attributes&FILE_ATTRIBUTE_DIRECTORY != 0 { 726 isDir = true 727 } 728 } else { 729 f.ReparseTag = uint32(dentry.ReparseHardLink) 730 f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32) 731 } 732 733 if isDir && f.subdirOffset == 0 { 734 return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")} 735 } else if !isDir && f.subdirOffset != 0 { 736 return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")} 737 } 738 739 if dentry.SecurityID != 0xffffffff { 740 f.SecurityDescriptor = img.sds[dentry.SecurityID] 741 } 742 743 _, err = io.CopyN(ioutil.Discard, r, left) 744 if err != nil { 745 if err == io.EOF { 746 err = io.ErrUnexpectedEOF 747 } 748 return nil, 0, err 749 } 750 751 if dentry.StreamCount > 0 { 752 var streams []*Stream 753 for i := uint16(0); i < dentry.StreamCount; i++ { 754 s, n, err := img.readNextStream(r) 755 length += n 756 if err != nil { 757 return nil, 0, err 758 } 759 // The first unnamed stream should be treated as the file stream. 760 if i == 0 && s.Name == "" { 761 f.Hash = s.Hash 762 f.Size = s.Size 763 f.offset = s.offset 764 } else if s.Name != "" { 765 streams = append(streams, s) 766 } 767 } 768 f.Streams = streams 769 } 770 771 if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 { 772 return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("reparse point is missing reparse stream")} 773 } 774 775 return f, length, nil 776} 777 778func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) { 779 var length int64 780 err := binary.Read(r, binary.LittleEndian, &length) 781 if err != nil { 782 if err == io.EOF { 783 err = io.ErrUnexpectedEOF 784 } 785 return nil, 0, &ParseError{Oper: "stream length check", Err: err} 786 } 787 788 left := length 789 if left < streamentrySize { 790 return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short")} 791 } 792 793 var sentry streamentry 794 err = binary.Read(r, binary.LittleEndian, &sentry) 795 if err != nil { 796 return nil, 0, &ParseError{Oper: "stream entry", Err: err} 797 } 798 799 left -= streamentrySize 800 801 if left < int64(sentry.NameLength) { 802 return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short for name")} 803 } 804 805 names := make([]uint16, sentry.NameLength/2) 806 err = binary.Read(r, binary.LittleEndian, names) 807 if err != nil { 808 return nil, 0, &ParseError{Oper: "file name", Err: err} 809 } 810 811 left -= int64(sentry.NameLength) 812 name := string(utf16.Decode(names)) 813 814 var offset resourceDescriptor 815 if sentry.Hash != (SHA1Hash{}) { 816 var ok bool 817 offset, ok = img.wim.fileData[sentry.Hash] 818 if !ok { 819 return nil, 0, &ParseError{Oper: "stream entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash)} 820 } 821 } 822 823 s := &Stream{ 824 StreamHeader: StreamHeader{ 825 Hash: sentry.Hash, 826 Size: offset.OriginalSize, 827 Name: name, 828 }, 829 wim: img.wim, 830 offset: offset, 831 } 832 833 _, err = io.CopyN(ioutil.Discard, r, left) 834 if err != nil { 835 if err == io.EOF { 836 err = io.ErrUnexpectedEOF 837 } 838 return nil, 0, err 839 } 840 841 return s, length, nil 842} 843 844// Open returns an io.ReadCloser that can be used to read the stream's contents. 845func (s *Stream) Open() (io.ReadCloser, error) { 846 return s.wim.resourceReader(&s.offset) 847} 848 849// Open returns an io.ReadCloser that can be used to read the file's contents. 850func (f *File) Open() (io.ReadCloser, error) { 851 return f.img.wim.resourceReader(&f.offset) 852} 853 854// Readdir reads the directory entries. 855func (f *File) Readdir() ([]*File, error) { 856 if !f.IsDir() { 857 return nil, errors.New("not a directory") 858 } 859 return f.img.readdir(f.subdirOffset) 860} 861 862// IsDir returns whether the given file is a directory. It returns false when it 863// is a directory reparse point. 864func (f *FileHeader) IsDir() bool { 865 return f.Attributes&(FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_DIRECTORY 866} 867