Merge pull request #16 from Microsoft/fixing_wim

Fix various WIM bugs
This commit is contained in:
John Starks
2016-04-06 17:10:02 -07:00
4 changed files with 320 additions and 93 deletions
+1
View File
@@ -0,0 +1 @@
*.exe
+20 -13
View File
@@ -21,7 +21,7 @@ const (
maxBlockSize = 32768 maxBlockSize = 32768
windowSize = 32768 windowSize = 32768
treePathLenCount = 17 maxTreePathLen = 16
e8filesize = 12000000 e8filesize = 12000000
maxe8offset = 0x3fffffff maxe8offset = 0x3fffffff
@@ -97,8 +97,8 @@ func (f *decompressor) feed() bool {
// getBits retrieves the next n bits from the byte stream. n // getBits retrieves the next n bits from the byte stream. n
// must be <= 16. It sets f.err on error. // must be <= 16. It sets f.err on error.
func (f *decompressor) getBits(n byte) uint16 { func (f *decompressor) getBits(n byte) uint16 {
if f.nbits < 16 { if f.nbits < n {
if !f.feed() && n > f.nbits { if !f.feed() {
f.err = io.ErrUnexpectedEOF f.err = io.ErrUnexpectedEOF
} }
} }
@@ -115,12 +115,12 @@ type huffman struct {
} }
// buildTable builds a huffman decoding table from a slice of code lengths, // buildTable builds a huffman decoding table from a slice of code lengths,
// one per code, in order. Each code length must be less than treePathLenCount. // one per code, in order. Each code length must be <= maxTreePathLen.
// See https://en.wikipedia.org/wiki/Canonical_Huffman_code. // See https://en.wikipedia.org/wiki/Canonical_Huffman_code.
func buildTable(codelens []byte) *huffman { func buildTable(codelens []byte) *huffman {
// Determine the number of codes of each length, and the // Determine the number of codes of each length, and the
// maximum length. // maximum length.
var count [treePathLenCount]uint var count [maxTreePathLen + 1]uint
var max byte var max byte
for _, cl := range codelens { for _, cl := range codelens {
count[cl]++ count[cl]++
@@ -134,7 +134,7 @@ func buildTable(codelens []byte) *huffman {
} }
// Determine the first code of each length. // Determine the first code of each length.
var first [treePathLenCount]uint var first [maxTreePathLen + 1]uint
code := uint(0) code := uint(0)
for i := byte(1); i <= max; i++ { for i := byte(1); i <= max; i++ {
code <<= 1 code <<= 1
@@ -178,7 +178,7 @@ func (f *decompressor) getCode(h *huffman) uint16 {
f.err = errCorrupt f.err = errCorrupt
return 0 return 0
} }
if f.nbits < 16 { if f.nbits < maxTreePathLen {
f.feed() f.feed()
} }
// For codes with length < h.maxbits, it doesn't matter // For codes with length < h.maxbits, it doesn't matter
@@ -309,14 +309,21 @@ func (f *decompressor) readBlockHeader() (byte, uint16, error) {
case verbatimBlock, alignedOffsetBlock: case verbatimBlock, alignedOffsetBlock:
// The caller will read the huffman trees. // The caller will read the huffman trees.
case uncompressedBlock: case uncompressedBlock:
// Not sure if this can happen... if f.nbits > 16 {
if f.nbits > 16 || f.nbits == 0 { panic("impossible: more than one 16-bit word remains")
return 0, 0, errCorrupt
} }
// Drop the remaining bits in the current 16-bit word. // Drop the remaining bits in the current 16-bit word
f.nbits = 0 // If there are no bits left, discard a full 16-bit word.
f.c = 0 n := f.nbits
if n == 0 {
n = 16
}
f.getBits(n)
if f.err != nil {
return 0, 0, f.err
}
// Read the LRU values for the next block. // Read the LRU values for the next block.
var lru [12]byte var lru [12]byte
+51
View File
@@ -0,0 +1,51 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/Microsoft/go-winio/wim"
)
func main() {
flag.Parse()
f, err := os.Open(flag.Arg(0))
if err != nil {
panic(err)
}
w, err := wim.NewReader(f)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n%#v\n", w.Image[0], w.Image[0].Windows)
dir, err := w.Image[0].Open()
if err != nil {
panic(err)
}
err = recur(dir)
if err != nil {
panic(err)
}
}
func recur(d *wim.File) error {
files, err := d.Readdir()
if err != nil {
return fmt.Errorf("%s: %s", d.Name, err)
}
for _, f := range files {
if f.IsDir() {
err = recur(f)
if err != nil {
return fmt.Errorf("%s: %s", f.Name, err)
}
}
}
return nil
}
+245 -77
View File
@@ -9,14 +9,55 @@ import (
"bytes" "bytes"
"crypto/sha1" "crypto/sha1"
"encoding/binary" "encoding/binary"
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"syscall" "strconv"
"time"
"unicode/utf16" "unicode/utf16"
) )
// File attribute constants from Windows.
const (
FILE_ATTRIBUTE_READONLY = 0x00000001
FILE_ATTRIBUTE_HIDDEN = 0x00000002
FILE_ATTRIBUTE_SYSTEM = 0x00000004
FILE_ATTRIBUTE_DIRECTORY = 0x00000010
FILE_ATTRIBUTE_ARCHIVE = 0x00000020
FILE_ATTRIBUTE_DEVICE = 0x00000040
FILE_ATTRIBUTE_NORMAL = 0x00000080
FILE_ATTRIBUTE_TEMPORARY = 0x00000100
FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
FILE_ATTRIBUTE_COMPRESSED = 0x00000800
FILE_ATTRIBUTE_OFFLINE = 0x00001000
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000
FILE_ATTRIBUTE_ENCRYPTED = 0x00004000
FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000
FILE_ATTRIBUTE_VIRTUAL = 0x00010000
FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000
FILE_ATTRIBUTE_EA = 0x00040000
)
// Windows processor architectures.
const (
PROCESSOR_ARCHITECTURE_INTEL = 0
PROCESSOR_ARCHITECTURE_MIPS = 1
PROCESSOR_ARCHITECTURE_ALPHA = 2
PROCESSOR_ARCHITECTURE_PPC = 3
PROCESSOR_ARCHITECTURE_SHX = 4
PROCESSOR_ARCHITECTURE_ARM = 5
PROCESSOR_ARCHITECTURE_IA64 = 6
PROCESSOR_ARCHITECTURE_ALPHA64 = 7
PROCESSOR_ARCHITECTURE_MSIL = 8
PROCESSOR_ARCHITECTURE_AMD64 = 9
PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10
PROCESSOR_ARCHITECTURE_NEUTRAL = 11
PROCESSOR_ARCHITECTURE_ARM64 = 12
)
var wimImageTag = [...]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0} var wimImageTag = [...]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0}
type guid struct { type guid struct {
@@ -128,9 +169,9 @@ type direntry struct {
SecurityID uint32 SecurityID uint32
SubdirOffset int64 SubdirOffset int64
Unused1, Unused2 int64 Unused1, Unused2 int64
CreationTime syscall.Filetime CreationTime Filetime
LastAccessTime syscall.Filetime LastAccessTime Filetime
LastWriteTime syscall.Filetime LastWriteTime Filetime
Hash SHA1Hash Hash SHA1Hash
Padding uint32 Padding uint32
ReparseHardLink int64 ReparseHardLink int64
@@ -150,15 +191,97 @@ type streamentry struct {
const streamentrySize = 38 const streamentrySize = 38
// Filetime represents a Windows time.
type Filetime struct {
LowDateTime uint32
HighDateTime uint32
}
// Time returns the time as time.Time.
func (ft *Filetime) Time() time.Time {
// 100-nanosecond intervals since January 1, 1601
nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime)
// change starting time to the Epoch (00:00:00 UTC, January 1, 1970)
nsec -= 116444736000000000
// convert into nanoseconds
nsec *= 100
return time.Unix(0, nsec)
}
// UnmarshalXML unmarshals the time from a WIM XML blob.
func (ft *Filetime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type time struct {
Low string `xml:"LOWPART"`
High string `xml:"HIGHPART"`
}
var t time
err := d.DecodeElement(&t, &start)
if err != nil {
return err
}
low, err := strconv.ParseUint(t.Low, 0, 32)
if err != nil {
return err
}
high, err := strconv.ParseUint(t.High, 0, 32)
if err != nil {
return err
}
ft.LowDateTime = uint32(low)
ft.HighDateTime = uint32(high)
return nil
}
type info struct {
Image []ImageInfo `xml:"IMAGE"`
}
// ImageInfo contains information about the image.
type ImageInfo struct {
Name string `xml:"NAME"`
Index int `xml:"INDEX,attr"`
CreationTime Filetime `xml:"CREATIONTIME"`
ModTime Filetime `xml:"LASTMODIFICATIONTIME"`
Windows *WindowsInfo `xml:"WINDOWS"`
}
// WindowsInfo contains information about the Windows installation in the image.
type WindowsInfo struct {
Arch byte `xml:"ARCH"`
ProductName string `xml:"PRODUCTNAME"`
EditionID string `xml:"EDITIONID"`
InstallationType string `xml:"INSTALLATIONTYPE"`
ProductType string `xml:"PRODUCTTYPE"`
Languages []string `xml:"LANGUAGES>LANGUAGE"`
DefaultLanguage string `xml:"LANGUAGES>DEFAULT"`
Version Version `xml:"VERSION"`
SystemRoot string `xml:"SYSTEMROOT"`
}
// Version represents a Windows build version.
type Version struct {
Major int `xml:"MAJOR"`
Minor int `xml:"MINOR"`
Build int `xml:"BUILD"`
SPBuild int `xml:"SPBUILD"`
SPLevel int `xml:"SPLEVEL"`
}
// ParseError is returned when the WIM cannot be parsed. // ParseError is returned when the WIM cannot be parsed.
type ParseError struct { type ParseError struct {
Oper string Oper string
Path string
Err error Err error
} }
func (e *ParseError) Error() string { func (e *ParseError) Error() string {
if e.Path == "" {
return "WIM parse error at " + e.Oper + ": " + e.Err.Error() return "WIM parse error at " + e.Oper + ": " + e.Err.Error()
} }
return fmt.Sprintf("WIM parse error: %s %s: %s", e.Oper, e.Path, e.Err.Error())
}
// Reader provides functions to read a WIM file. // Reader provides functions to read a WIM file.
type Reader struct { type Reader struct {
@@ -166,6 +289,7 @@ type Reader struct {
r io.ReaderAt r io.ReaderAt
fileData map[SHA1Hash]resourceDescriptor fileData map[SHA1Hash]resourceDescriptor
XMLInfo string // The XML information about the WIM.
Image []*Image // The WIM's images. Image []*Image // The WIM's images.
} }
@@ -175,6 +299,8 @@ type Image struct {
offset resourceDescriptor offset resourceDescriptor
sds [][]byte sds [][]byte
rootOffset int64 rootOffset int64
ImageInfo
} }
// StreamHeader contains alternate data stream metadata. // StreamHeader contains alternate data stream metadata.
@@ -197,15 +323,14 @@ type FileHeader struct {
ShortName string ShortName string
Attributes uint32 Attributes uint32
SecurityDescriptor []byte SecurityDescriptor []byte
CreationTime syscall.Filetime CreationTime Filetime
LastAccessTime syscall.Filetime LastAccessTime Filetime
LastWriteTime syscall.Filetime LastWriteTime Filetime
Hash SHA1Hash Hash SHA1Hash
Size int64 Size int64
LinkID int64 LinkID int64
ReparseTag uint32 ReparseTag uint32
ReparseReserved uint32 ReparseReserved uint32
ReparseStream *Stream
} }
// File represents a file or directory in a WIM image. // File represents a file or directory in a WIM image.
@@ -227,7 +352,7 @@ func NewReader(f io.ReaderAt) (*Reader, error) {
} }
if r.hdr.ImageTag != wimImageTag { if r.hdr.ImageTag != wimImageTag {
return nil, &ParseError{"image tag", errors.New("not a WIM file")} return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")}
} }
if r.hdr.Flags&^supportedHdrFlags != 0 { if r.hdr.Flags&^supportedHdrFlags != 0 {
@@ -246,8 +371,30 @@ func NewReader(f io.ReaderAt) (*Reader, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
xmlinfo, err := r.readXML()
if err != nil {
return nil, err
}
var info info
err = xml.Unmarshal([]byte(xmlinfo), &info)
if err != nil {
return nil, &ParseError{Oper: "XML info", Err: err}
}
for i, img := range images {
for _, imgInfo := range info.Image {
if imgInfo.Index == i+1 {
img.ImageInfo = imgInfo
break
}
}
}
r.fileData = fileData r.fileData = fileData
r.Image = images r.Image = images
r.XMLInfo = xmlinfo
return r, nil return r, nil
} }
@@ -281,8 +428,7 @@ func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) {
return ioutil.ReadAll(rsrc) return ioutil.ReadAll(rsrc)
} }
// ReadXML reads the XML metadata from a WIM. func (r *Reader) readXML() (string, error) {
func (r *Reader) ReadXML() (string, error) {
if r.hdr.XMLData.CompressedSize() == 0 { if r.hdr.XMLData.CompressedSize() == 0 {
return "", nil return "", nil
} }
@@ -295,12 +441,12 @@ func (r *Reader) ReadXML() (string, error) {
XMLData := make([]uint16, r.hdr.XMLData.OriginalSize/2) XMLData := make([]uint16, r.hdr.XMLData.OriginalSize/2)
err = binary.Read(rsrc, binary.LittleEndian, XMLData) err = binary.Read(rsrc, binary.LittleEndian, XMLData)
if err != nil { if err != nil {
return "", &ParseError{"XML data", err} return "", &ParseError{Oper: "XML data", Err: err}
} }
// The BOM will always indicate little-endian UTF-16. // The BOM will always indicate little-endian UTF-16.
if XMLData[0] != 0xfeff { if XMLData[0] != 0xfeff {
return "", &ParseError{"XML data", errors.New("invalid BOM")} return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")}
} }
return string(utf16.Decode(XMLData[1:])), nil return string(utf16.Decode(XMLData[1:])), nil
} }
@@ -311,39 +457,39 @@ func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resource
offsetTable, err := r.readResource(res) offsetTable, err := r.readResource(res)
if err != nil { if err != nil {
return nil, nil, &ParseError{"offset table", err} return nil, nil, &ParseError{Oper: "offset table", Err: err}
} }
br := bytes.NewReader(offsetTable) br := bytes.NewReader(offsetTable)
for { for i := 0; ; i++ {
var res streamDescriptor var res streamDescriptor
err := binary.Read(br, binary.LittleEndian, &res) err := binary.Read(br, binary.LittleEndian, &res)
if err == io.EOF { if err == io.EOF {
break break
} }
if err != nil { if err != nil {
return nil, nil, &ParseError{"offset table", err} return nil, nil, &ParseError{Oper: "offset table", Err: err}
} }
if res.Flags()&^supportedResFlags != 0 { if res.Flags()&^supportedResFlags != 0 {
return nil, nil, &ParseError{"offset table", errors.New("unsupported resource flag")} return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")}
} }
// Validation for ad-hoc testing // Validation for ad-hoc testing
if validate { if validate {
sec, err := r.resourceReader(&res.resourceDescriptor) sec, err := r.resourceReader(&res.resourceDescriptor)
if err != nil { if err != nil {
return nil, nil, err panic(fmt.Sprint(i, err))
} }
hash := sha1.New() hash := sha1.New()
_, err = io.Copy(hash, sec) _, err = io.Copy(hash, sec)
sec.Close() sec.Close()
if err != nil { if err != nil {
return nil, nil, err panic(fmt.Sprint(i, err))
} }
var cmphash SHA1Hash var cmphash SHA1Hash
copy(cmphash[:], hash.Sum(nil)) copy(cmphash[:], hash.Sum(nil))
if cmphash != res.Hash { if cmphash != res.Hash {
return nil, nil, errors.New("hash mismatch") panic(fmt.Sprint(i, "hash mismatch"))
} }
} }
@@ -359,7 +505,7 @@ func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resource
} }
if len(images) != int(r.hdr.ImageCount) { if len(images) != int(r.hdr.ImageCount) {
return nil, nil, &ParseError{"offset table", errors.New("mismatched image count")} return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")}
} }
return fileData, images, nil return fileData, images, nil
@@ -369,7 +515,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64,
var secBlock securityblockDisk var secBlock securityblockDisk
err = binary.Read(rsrc, binary.LittleEndian, &secBlock) err = binary.Read(rsrc, binary.LittleEndian, &secBlock)
if err != nil { if err != nil {
err = &ParseError{"security table", err} err = &ParseError{Oper: "security table", Err: err}
return return
} }
@@ -378,7 +524,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64,
secSizes := make([]int64, secBlock.NumEntries) secSizes := make([]int64, secBlock.NumEntries)
err = binary.Read(rsrc, binary.LittleEndian, &secSizes) err = binary.Read(rsrc, binary.LittleEndian, &secSizes)
if err != nil { if err != nil {
err = &ParseError{"security table sizes", err} err = &ParseError{Oper: "security table sizes", Err: err}
return return
} }
@@ -389,7 +535,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64,
sd := make([]byte, size&0xffffffff) sd := make([]byte, size&0xffffffff)
_, err = io.ReadFull(rsrc, sd) _, err = io.ReadFull(rsrc, sd)
if err != nil { if err != nil {
err = &ParseError{"security descriptor", err} err = &ParseError{Oper: "security descriptor", Err: err}
return return
} }
n += int64(len(sd)) n += int64(len(sd))
@@ -398,7 +544,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64,
secsize := int64((secBlock.TotalLength + 7) &^ 7) secsize := int64((secBlock.TotalLength + 7) &^ 7)
if n > secsize { if n > secsize {
err = &ParseError{"security descriptor", errors.New("security descriptor table too small")} err = &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")}
return return
} }
@@ -432,7 +578,7 @@ func (img *Image) Open() (*File, error) {
return nil, err return nil, err
} }
if len(f) != 1 { if len(f) != 1 {
return nil, &ParseError{"root directory", errors.New("expected exactly 1 root directory entry")} return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")}
} }
return f[0], err return f[0], err
} }
@@ -457,7 +603,7 @@ func (img *Image) readdir(rsrc io.Reader) ([]*File, error) {
func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) { func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
lengthBuf, err := r.Peek(8) lengthBuf, err := r.Peek(8)
if err != nil { if err != nil {
return nil, &ParseError{"directory length check", err} return nil, &ParseError{Oper: "directory length check", Err: err}
} }
left := int(binary.LittleEndian.Uint64(lengthBuf)) left := int(binary.LittleEndian.Uint64(lengthBuf))
@@ -466,24 +612,46 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
} }
if left < direntrySize { if left < direntrySize {
return nil, &ParseError{"directory entry", errors.New("size too short")} return nil, &ParseError{Oper: "directory entry", Err: errors.New("size too short")}
} }
var dentry direntry var dentry direntry
err = binary.Read(r, binary.LittleEndian, &dentry) err = binary.Read(r, binary.LittleEndian, &dentry)
if err != nil { if err != nil {
return nil, &ParseError{"directory entry", err} return nil, &ParseError{Oper: "directory entry", Err: err}
} }
left -= direntrySize left -= direntrySize
namesLen := int(dentry.FileNameLength + 2 + dentry.ShortNameLength)
if left < namesLen {
return nil, &ParseError{Oper: "directory entry", Err: errors.New("size too short for names")}
}
names := make([]uint16, namesLen/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, &ParseError{Oper: "file name", Err: err}
}
left -= namesLen
var name, shortName string
if dentry.FileNameLength > 0 {
name = string(utf16.Decode(names[:dentry.FileNameLength/2]))
}
if dentry.ShortNameLength > 0 {
shortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:]))
}
var offset resourceDescriptor var offset resourceDescriptor
zerohash := SHA1Hash{} zerohash := SHA1Hash{}
if dentry.Hash != zerohash { if dentry.Hash != zerohash {
var ok bool var ok bool
offset, ok = img.wim.fileData[dentry.Hash] offset, ok = img.wim.fileData[dentry.Hash]
if !ok { if !ok {
return nil, &ParseError{"directory entry", fmt.Errorf("could not find file data matching hash %v", dentry.Hash)} return nil, &ParseError{Oper: "directory entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %#v", dentry)}
} }
} }
@@ -495,6 +663,8 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
LastWriteTime: dentry.LastWriteTime, LastWriteTime: dentry.LastWriteTime,
Hash: dentry.Hash, Hash: dentry.Hash,
Size: offset.OriginalSize, Size: offset.OriginalSize,
Name: name,
ShortName: shortName,
}, },
offset: offset, offset: offset,
@@ -502,38 +672,28 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
subdirOffset: dentry.SubdirOffset, subdirOffset: dentry.SubdirOffset,
} }
if dentry.Attributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { isDir := false
if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT == 0 {
f.LinkID = dentry.ReparseHardLink f.LinkID = dentry.ReparseHardLink
if dentry.Attributes&FILE_ATTRIBUTE_DIRECTORY != 0 {
isDir = true
}
} else { } else {
f.ReparseTag = uint32(dentry.ReparseHardLink) f.ReparseTag = uint32(dentry.ReparseHardLink)
f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32) f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32)
} }
if isDir && f.subdirOffset == 0 {
return nil, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")}
} else if !isDir && f.subdirOffset != 0 {
return nil, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")}
}
if dentry.SecurityID != 0xffffffff { if dentry.SecurityID != 0xffffffff {
f.SecurityDescriptor = img.sds[dentry.SecurityID] f.SecurityDescriptor = img.sds[dentry.SecurityID]
} }
namesLen := int(dentry.FileNameLength + 2 + dentry.ShortNameLength)
if left < namesLen {
return nil, &ParseError{"directory entry", errors.New("size too short for names")}
}
names := make([]uint16, namesLen/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, &ParseError{"file name", err}
}
left -= namesLen
if dentry.FileNameLength > 0 {
f.Name = string(utf16.Decode(names[:dentry.FileNameLength/2]))
}
if dentry.ShortNameLength > 0 {
f.ShortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:]))
}
_, err = r.Discard(left) _, err = r.Discard(left)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -546,19 +706,20 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !(s.Name == "" && s.Size == 0) { // The first unnamed stream should be treated as the file stream.
if dentry.Attributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && s.Name == "" { if i == 0 && s.Name == "" {
f.ReparseStream = s f.Hash = s.Hash
} else { f.Size = s.Size
f.offset = s.offset
} else if s.Name != "" {
streams = append(streams, s) streams = append(streams, s)
} }
} }
}
f.Streams = streams f.Streams = streams
} }
if dentry.Attributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.ReparseStream == nil { if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 {
return nil, &ParseError{"directory entry", errors.New("reparse point is missing reparse stream")} return nil, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("reparse point is missing reparse stream")}
} }
return f, nil return f, nil
@@ -567,28 +728,41 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
func (img *Image) readNextStream(r *bufio.Reader) (*Stream, error) { func (img *Image) readNextStream(r *bufio.Reader) (*Stream, error) {
lengthBuf, err := r.Peek(8) lengthBuf, err := r.Peek(8)
if err != nil { if err != nil {
return nil, &ParseError{"stream length check", err} return nil, &ParseError{Oper: "stream length check", Err: err}
} }
left := int(binary.LittleEndian.Uint64(lengthBuf)) left := int(binary.LittleEndian.Uint64(lengthBuf))
if left < streamentrySize { if left < streamentrySize {
return nil, &ParseError{"stream entry", errors.New("size too short")} return nil, &ParseError{Oper: "stream entry", Err: errors.New("size too short")}
} }
var sentry streamentry var sentry streamentry
err = binary.Read(r, binary.LittleEndian, &sentry) err = binary.Read(r, binary.LittleEndian, &sentry)
if err != nil { if err != nil {
return nil, &ParseError{"stream entry", err} return nil, &ParseError{Oper: "stream entry", Err: err}
} }
left -= streamentrySize left -= streamentrySize
if left < int(sentry.NameLength) {
return nil, &ParseError{Oper: "stream entry", Err: errors.New("size too short for name")}
}
names := make([]uint16, sentry.NameLength/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, &ParseError{Oper: "file name", Err: err}
}
left -= int(sentry.NameLength)
name := string(utf16.Decode(names))
var offset resourceDescriptor var offset resourceDescriptor
if sentry.Hash != (SHA1Hash{}) { if sentry.Hash != (SHA1Hash{}) {
var ok bool var ok bool
offset, ok = img.wim.fileData[sentry.Hash] offset, ok = img.wim.fileData[sentry.Hash]
if !ok { if !ok {
return nil, &ParseError{"stream entry", fmt.Errorf("could not find file data matching hash %v", sentry.Hash)} return nil, &ParseError{Oper: "stream entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash)}
} }
} }
@@ -596,24 +770,12 @@ func (img *Image) readNextStream(r *bufio.Reader) (*Stream, error) {
StreamHeader: StreamHeader{ StreamHeader: StreamHeader{
Hash: sentry.Hash, Hash: sentry.Hash,
Size: offset.OriginalSize, Size: offset.OriginalSize,
Name: name,
}, },
wim: img.wim, wim: img.wim,
offset: offset, offset: offset,
} }
if left < int(sentry.NameLength) {
return nil, &ParseError{"stream entry", errors.New("size too short for name")}
}
names := make([]uint16, sentry.NameLength/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, &ParseError{"file name", err}
}
left -= int(sentry.NameLength)
s.Name = string(utf16.Decode(names))
_, err = r.Discard(left) _, err = r.Discard(left)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -634,7 +796,7 @@ func (f *File) Open() (io.ReadCloser, error) {
// Readdir reads the directory entries. // Readdir reads the directory entries.
func (f *File) Readdir() ([]*File, error) { func (f *File) Readdir() ([]*File, error) {
if f.Attributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 { if !f.IsDir() {
return nil, errors.New("not a directory") return nil, errors.New("not a directory")
} }
rsrc, err := f.img.wim.resourceReaderWithOffset(&f.img.offset, f.subdirOffset) rsrc, err := f.img.wim.resourceReaderWithOffset(&f.img.offset, f.subdirOffset)
@@ -644,3 +806,9 @@ func (f *File) Readdir() ([]*File, error) {
defer rsrc.Close() defer rsrc.Close()
return f.img.readdir(rsrc) return f.img.readdir(rsrc)
} }
// IsDir returns whether the given file is a directory. It returns false when it
// is a directory reparse point.
func (f *FileHeader) IsDir() bool {
return f.Attributes&(FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_DIRECTORY
}