diff --git a/go.mod b/go.mod index 40ce9cc18f..1f5f5b1a19 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 github.com/360EntSecGroup-Skylar/excelize v1.4.0 github.com/LeeEirc/terminalparser v0.0.0-20240205084113-fbf78c8480f2 - github.com/Microsoft/go-winio v0.6.2 github.com/aliyun/alibaba-cloud-sdk-go v1.61.684 github.com/anacrolix/torrent v1.57.0 github.com/aws/aws-sdk-go-v2 v1.41.5 @@ -113,7 +112,7 @@ require ( k8s.io/cri-api v0.28.15 k8s.io/klog/v2 v2.90.1 moul.io/http2curl/v2 v2.3.0 - yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023537-f41e01f2eee3 + yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260812062623-2b79dfea19bd yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005 yunion.io/x/jsonutils v1.0.1-0.20260715075349-615cfb44ff7c yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91 @@ -141,6 +140,7 @@ require ( github.com/DataDog/datadog-go/v5 v5.0.2 // indirect github.com/DataDog/go-tuf v0.3.0--fix-localmeta-fork // indirect github.com/DataDog/sketches-go v1.2.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/RoaringBitmap/roaring v1.2.3 // indirect github.com/StackExchange/wmi v1.2.1 // indirect diff --git a/go.sum b/go.sum index 0adba24151..695617da1e 100644 --- a/go.sum +++ b/go.sum @@ -1754,8 +1754,8 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023537-f41e01f2eee3 h1:QBX44ByHjCYbstpxDejNPI+GqJmcsJIE8nTQCGfxUsw= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023537-f41e01f2eee3/go.mod h1:QgrcekfkD3fELNYipZnlUfhR8dPI8CCUsuzC2SkP3YE= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260812062623-2b79dfea19bd h1:rtRzbOZP24DqQ0u0SYfrSdYWLX6Taov0AwkVQyv95p0= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260812062623-2b79dfea19bd/go.mod h1:QgrcekfkD3fELNYipZnlUfhR8dPI8CCUsuzC2SkP3YE= yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005 h1:3sWwcjGXGjG9mLBWa7AyLq+QSi0udTAx21pfVQRFMBE= yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws= yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634= diff --git a/pkg/util/isoutils/isoutils.go b/pkg/util/isoutils/isoutils.go index 4667ac642a..6febec9665 100644 --- a/pkg/util/isoutils/isoutils.go +++ b/pkg/util/isoutils/isoutils.go @@ -186,8 +186,8 @@ func DetectOSFromISO(r io.Reader) (*ISOInfo, error) { return nil, err } - // ========== 识别Windows系列 ========== - if reader.FileExists("sources/install.wim") { + // ========== 识别Windows系列(install.wim 或 install.esd) ========== + if reader.FileExists("sources/install.wim") || reader.FileExists("sources/install.esd") { return DetectWindowsEdition(reader) } diff --git a/pkg/util/isoutils/wim_xml.go b/pkg/util/isoutils/wim_xml.go new file mode 100644 index 0000000000..ce2830f37b --- /dev/null +++ b/pkg/util/isoutils/wim_xml.go @@ -0,0 +1,211 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package isoutils + +import ( + "encoding/binary" + "encoding/xml" + "fmt" + "io" + "unicode/utf16" + + "yunion.io/x/log" + "yunion.io/x/pkg/util/imagetools" +) + +var wimImageTag = [8]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0} + +const ( + wimResFlagCompressed = 0x04 +) + +// wimResourceDesc matches the on-disk WIM resource descriptor (24 bytes). +type wimResourceDesc struct { + FlagsAndCompressedSize uint64 + Offset int64 + OriginalSize int64 +} + +func (r wimResourceDesc) flags() byte { + return byte(r.FlagsAndCompressedSize >> 56) +} + +func (r wimResourceDesc) compressedSize() int64 { + return int64(r.FlagsAndCompressedSize & 0xffffffffffffff) +} + +// wimHeaderDisk is the on-disk WIM header (208 bytes / 0xd0). +type wimHeaderDisk struct { + ImageTag [8]byte + Size uint32 + Version uint32 + Flags uint32 + CompressionSize uint32 + WIMGuid [16]byte + PartNumber uint16 + TotalParts uint16 + ImageCount uint32 + OffsetTable wimResourceDesc + XMLData wimResourceDesc + BootMetadata wimResourceDesc + BootIndex uint32 + Padding uint32 + Integrity wimResourceDesc + Unused [60]byte +} + +type wimXMLInfo struct { + Image []wimXMLImage `xml:"IMAGE"` +} + +type wimXMLImage struct { + Name string `xml:"NAME"` + Index int `xml:"INDEX,attr"` + Windows *wimWindowsInfo `xml:"WINDOWS"` +} + +type wimWindowsInfo struct { + Arch byte `xml:"ARCH"` + ProductName string `xml:"PRODUCTNAME"` + EditionID string `xml:"EDITIONID"` + ProductType string `xml:"PRODUCTTYPE"` + DefaultLanguage string `xml:"LANGUAGES>DEFAULT"` + Version wimXMLVersion `xml:"VERSION"` +} + +type wimXMLVersion struct { + Major int `xml:"MAJOR"` + Minor int `xml:"MINOR"` + Build int `xml:"BUILD"` +} + +// parseWimXmlMetadata reads WIM/ESD header + uncompressed XML metadata and maps Windows version. +// Content compression (LZMS/XPRESS) is ignored; only the XML blob is required for edition detection. +func parseWimXmlMetadata(r io.ReaderAt) (*ISOInfo, error) { + var hdr wimHeaderDisk + if err := binary.Read(io.NewSectionReader(r, 0, int64(binary.Size(hdr))), binary.LittleEndian, &hdr); err != nil { + return nil, fmt.Errorf("read WIM header: %w", err) + } + if hdr.ImageTag != wimImageTag { + return nil, fmt.Errorf("not a WIM/ESD file") + } + if hdr.XMLData.compressedSize() == 0 || hdr.XMLData.OriginalSize == 0 { + return nil, fmt.Errorf("WIM/ESD has no XML metadata") + } + if hdr.XMLData.flags()&wimResFlagCompressed != 0 { + return nil, fmt.Errorf("compressed WIM XML metadata is not supported") + } + + xmlBytes := make([]byte, hdr.XMLData.OriginalSize) + if _, err := r.ReadAt(xmlBytes, hdr.XMLData.Offset); err != nil { + return nil, fmt.Errorf("read WIM XML metadata: %w", err) + } + + xmlStr, err := decodeWimUTF16XML(xmlBytes) + if err != nil { + return nil, err + } + + var info wimXMLInfo + if err := xml.Unmarshal([]byte(xmlStr), &info); err != nil { + return nil, fmt.Errorf("parse WIM XML: %w", err) + } + + for _, image := range info.Image { + if image.Windows == nil { + continue + } + result := mapWindowsVersion(image.Windows) + ver := fmt.Sprintf("%d.%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor, image.Windows.Version.Build) + log.Debugf("识别到 %s 版本: %s -> %s", result.Distro, ver, result.Version) + return result, nil + } + return nil, fmt.Errorf("no WINDOWS metadata found in WIM/ESD XML") +} + +func decodeWimUTF16XML(data []byte) (string, error) { + if len(data) < 2 || len(data)%2 != 0 { + return "", fmt.Errorf("invalid WIM XML encoding") + } + u16 := make([]uint16, len(data)/2) + for i := 0; i < len(u16); i++ { + u16[i] = binary.LittleEndian.Uint16(data[i*2:]) + } + // BOM is little-endian UTF-16 (0xFEFF) + if u16[0] != 0xfeff { + return "", fmt.Errorf("invalid WIM XML BOM") + } + return string(utf16.Decode(u16[1:])), nil +} + +func mapWindowsVersion(win *wimWindowsInfo) *ISOInfo { + result := &ISOInfo{ + Distro: imagetools.OS_DIST_WINDOWS, + Language: win.DefaultLanguage, + } + switch win.Arch { + case 9: + result.Arch = "x86_64" + case 12: + result.Arch = "arm64" + case 0: + result.Arch = "x86" + } + + majMin := fmt.Sprintf("%d.%d", win.Version.Major, win.Version.Minor) + switch majMin { + case "6.0": + result.Version = "Windows Vista" + case "6.1": + result.Version = "Windows 7" + case "6.2": + result.Version = "Windows 8" + case "6.3": + result.Version = "Windows 8.1" + case "10.0": + if win.Version.Build >= 27500 { + result.Version = "Windows 12" + } else if win.Version.Build >= 22000 { + result.Version = "Windows 11" + } else { + result.Version = "Windows 10" + } + } + + if win.ProductType == "ServerNT" { + result.Distro = imagetools.OS_DIST_WINDOWS_SERVER + switch majMin { + case "6.0": + result.Version = "Windows Server 2008" + case "6.1": + result.Version = "Windows Server 2008 R2" + case "6.2": + result.Version = "Windows Server 2012" + case "6.3": + result.Version = "Windows Server 2012 R2" + case "10.0": + if win.Version.Build >= 26040 { + result.Version = "Windows Server 2025" + } else if win.Version.Build >= 20348 { + result.Version = "Windows Server 2022" + } else if win.Version.Build >= 17763 { + result.Version = "Windows Server 2019" + } else if win.Version.Build >= 14393 { + result.Version = "Windows Server 2016" + } + } + } + return result +} diff --git a/pkg/util/isoutils/wim_xml_test.go b/pkg/util/isoutils/wim_xml_test.go new file mode 100644 index 0000000000..330da10a8f --- /dev/null +++ b/pkg/util/isoutils/wim_xml_test.go @@ -0,0 +1,197 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package isoutils + +import ( + "bytes" + "encoding/binary" + "testing" + "unicode/utf16" + + "yunion.io/x/pkg/util/imagetools" +) + +func encodeWimUTF16XML(s string) []byte { + u16 := utf16.Encode([]rune(s)) + out := make([]byte, 2+len(u16)*2) + binary.LittleEndian.PutUint16(out[0:], 0xfeff) // BOM + for i, v := range u16 { + binary.LittleEndian.PutUint16(out[2+i*2:], v) + } + return out +} + +func buildMinimalWimWithXML(xmlASCII string) []byte { + xmlData := encodeWimUTF16XML(xmlASCII) + hdrSize := binary.Size(wimHeaderDisk{}) + offset := int64(hdrSize) + + var hdr wimHeaderDisk + hdr.ImageTag = wimImageTag + hdr.Size = uint32(hdrSize) + hdr.Version = 0x10d00 + hdr.PartNumber = 1 + hdr.TotalParts = 1 + hdr.ImageCount = 1 + // uncompressed XML resource: flags=0, compressed size = original size + hdr.XMLData = wimResourceDesc{ + FlagsAndCompressedSize: uint64(len(xmlData)), + Offset: offset, + OriginalSize: int64(len(xmlData)), + } + + buf := &bytes.Buffer{} + _ = binary.Write(buf, binary.LittleEndian, &hdr) + buf.Write(xmlData) + return buf.Bytes() +} + +func TestParseWimXmlMetadataWindows11(t *testing.T) { + xml := ` + + + Windows 11 Pro + + 9 + Microsoft® Windows® Operating System + Professional + WinNT + + zh-CN + zh-CN + + + 10 + 0 + 22631 + + + +` + data := buildMinimalWimWithXML(xml) + info, err := parseWimXmlMetadata(bytes.NewReader(data)) + if err != nil { + t.Fatalf("parseWimXmlMetadata: %v", err) + } + if info.Distro != imagetools.OS_DIST_WINDOWS { + t.Fatalf("distro: got %s want %s", info.Distro, imagetools.OS_DIST_WINDOWS) + } + if info.Version != "Windows 11" { + t.Fatalf("version: got %s want Windows 11", info.Version) + } + if info.Arch != "x86_64" { + t.Fatalf("arch: got %s want x86_64", info.Arch) + } + if info.Language != "zh-CN" { + t.Fatalf("language: got %s want zh-CN", info.Language) + } +} + +func TestParseWimXmlMetadataWindowsServer2022(t *testing.T) { + xml := ` + + + Windows Server 2022 SERVERSTANDARD + + 9 + ServerNT + + en-US + + + 10 + 0 + 20348 + + + +` + data := buildMinimalWimWithXML(xml) + info, err := parseWimXmlMetadata(bytes.NewReader(data)) + if err != nil { + t.Fatalf("parseWimXmlMetadata: %v", err) + } + if info.Distro != imagetools.OS_DIST_WINDOWS_SERVER { + t.Fatalf("distro: got %s want %s", info.Distro, imagetools.OS_DIST_WINDOWS_SERVER) + } + if info.Version != "Windows Server 2022" { + t.Fatalf("version: got %s want Windows Server 2022", info.Version) + } +} + +func TestParseWimXmlMetadataRejectsCompressedXML(t *testing.T) { + xmlData := encodeWimUTF16XML(``) + hdrSize := binary.Size(wimHeaderDisk{}) + var hdr wimHeaderDisk + hdr.ImageTag = wimImageTag + hdr.Size = uint32(hdrSize) + hdr.PartNumber = 1 + hdr.TotalParts = 1 + hdr.XMLData = wimResourceDesc{ + FlagsAndCompressedSize: uint64(wimResFlagCompressed)<<56 | uint64(len(xmlData)), + Offset: int64(hdrSize), + OriginalSize: int64(len(xmlData)), + } + buf := &bytes.Buffer{} + _ = binary.Write(buf, binary.LittleEndian, &hdr) + buf.Write(xmlData) + + _, err := parseWimXmlMetadata(bytes.NewReader(buf.Bytes())) + if err == nil { + t.Fatal("expected error for compressed XML") + } +} + +func TestMapWindowsVersion(t *testing.T) { + cases := []struct { + name string + win wimWindowsInfo + distro string + version string + }{ + { + name: "win10", + win: wimWindowsInfo{ + Arch: 9, ProductType: "WinNT", + Version: wimXMLVersion{Major: 10, Minor: 0, Build: 19045}, + }, + distro: imagetools.OS_DIST_WINDOWS, version: "Windows 10", + }, + { + name: "win7", + win: wimWindowsInfo{ + Arch: 9, ProductType: "WinNT", + Version: wimXMLVersion{Major: 6, Minor: 1, Build: 7601}, + }, + distro: imagetools.OS_DIST_WINDOWS, version: "Windows 7", + }, + { + name: "server2019", + win: wimWindowsInfo{ + Arch: 9, ProductType: "ServerNT", + Version: wimXMLVersion{Major: 10, Minor: 0, Build: 17763}, + }, + distro: imagetools.OS_DIST_WINDOWS_SERVER, version: "Windows Server 2019", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + info := mapWindowsVersion(&c.win) + if info.Distro != c.distro || info.Version != c.version { + t.Fatalf("got %s/%s want %s/%s", info.Distro, info.Version, c.distro, c.version) + } + }) + } +} diff --git a/pkg/util/isoutils/winiso_unix.go b/pkg/util/isoutils/winiso_unix.go index ce10f23316..a0478a8474 100644 --- a/pkg/util/isoutils/winiso_unix.go +++ b/pkg/util/isoutils/winiso_unix.go @@ -19,80 +19,30 @@ package isoutils import ( "fmt" - - "github.com/Microsoft/go-winio/wim" - - "yunion.io/x/log" - "yunion.io/x/pkg/util/imagetools" ) -// ========== 7. 保留Windows版本识别函数(适配新结构) ========== +// DetectWindowsEdition reads sources/install.wim or sources/install.esd XML metadata +// to determine Windows edition/version. Prefer .wim, fall back to .esd. func DetectWindowsEdition(r *ISOFileReader) (*ISOInfo, error) { - wimFile, err := r.GetFile("sources/install.wim") - if err != nil { - return nil, err - } - wim, err := wim.NewReader(wimFile.NewReader()) - if err != nil { - return nil, err - } - result := &ISOInfo{} - for _, image := range wim.Image { - version := fmt.Sprintf("%d.%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor, image.Windows.Version.Build) - if image.Windows != nil { - if image.Windows.Arch == 9 { - result.Arch = "x86_64" - } else if image.Windows.Arch == 12 { - result.Arch = "arm64" - } else if image.Windows.Arch == 0 { - result.Arch = "x86" - } - result.Distro = imagetools.OS_DIST_WINDOWS - result.Language = image.Windows.DefaultLanguage - switch fmt.Sprintf("%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor) { - case "6.0": - result.Version = "Windows Vista" - case "6.1": - result.Version = "Windows 7" - case "6.2": - result.Version = "Windows 8" - case "6.3": - result.Version = "Windows 8.1" - case "10.0": - if image.Windows.Version.Build >= 27500 { - result.Version = "Windows 12" - } else if image.Windows.Version.Build >= 22000 { - result.Version = "Windows 11" - } else { - result.Version = "Windows 10" - } - } - if image.Windows.ProductType == "ServerNT" { - result.Distro = imagetools.OS_DIST_WINDOWS_SERVER - switch fmt.Sprintf("%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor) { - case "6.0": - result.Version = "Windows Server 2008" - case "6.1": - result.Version = "Windows Server 2008 R2" - case "6.2": - result.Version = "Windows Server 2012" - case "6.3": - result.Version = "Windows Server 2012 R2" - case "10.0": - if image.Windows.Version.Build >= 26040 { - result.Version = "Windows Server 2025" - } else if image.Windows.Version.Build >= 20348 { - result.Version = "Windows Server 2022" - } else if image.Windows.Version.Build >= 17763 { - result.Version = "Windows Server 2019" - } else if image.Windows.Version.Build >= 14393 { - result.Version = "Windows Server 2016" - } - } - } - log.Debugf("识别到 %s 版本: %s -> %s", result.Distro, version, result.Version) - break + var lastErr error + for _, path := range []string{"sources/install.wim", "sources/install.esd"} { + if !r.FileExists(path) { + continue } + f, err := r.GetFile(path) + if err != nil { + lastErr = fmt.Errorf("open %s: %w", path, err) + continue + } + info, err := parseWimXmlMetadata(f.NewReader()) + if err != nil { + lastErr = fmt.Errorf("parse %s: %w", path, err) + continue + } + return info, nil } - return result, nil + if lastErr != nil { + return nil, lastErr + } + return nil, fmt.Errorf("sources/install.wim or sources/install.esd not found") } diff --git a/vendor/github.com/Microsoft/go-winio/wim/decompress.go b/vendor/github.com/Microsoft/go-winio/wim/decompress.go deleted file mode 100644 index 993a413187..0000000000 --- a/vendor/github.com/Microsoft/go-winio/wim/decompress.go +++ /dev/null @@ -1,139 +0,0 @@ -//go:build windows || linux -// +build windows linux - -package wim - -import ( - "encoding/binary" - "io" - - "github.com/Microsoft/go-winio/wim/lzx" -) - -const chunkSize = 32768 // Compressed resource chunk size - -type compressedReader struct { - r *io.SectionReader - d io.ReadCloser - chunks []int64 - curChunk int - originalSize int64 -} - -func newCompressedReader(r *io.SectionReader, originalSize int64, offset int64) (*compressedReader, error) { - nchunks := (originalSize + chunkSize - 1) / chunkSize - var base int64 - chunks := make([]int64, nchunks) - if originalSize <= 0xffffffff { - // 32-bit chunk offsets - base = (nchunks - 1) * 4 - chunks32 := make([]uint32, nchunks-1) - err := binary.Read(r, binary.LittleEndian, chunks32) - if err != nil { - return nil, err - } - for i, n := range chunks32 { - chunks[i+1] = int64(n) - } - } else { - // 64-bit chunk offsets - base = (nchunks - 1) * 8 - err := binary.Read(r, binary.LittleEndian, chunks[1:]) - if err != nil { - return nil, err - } - } - - for i, c := range chunks { - chunks[i] = c + base - } - - cr := &compressedReader{ - r: r, - chunks: chunks, - originalSize: originalSize, - } - - err := cr.reset(int(offset / chunkSize)) - if err != nil { - return nil, err - } - - suboff := offset % chunkSize - if suboff != 0 { - _, err := io.CopyN(io.Discard, cr.d, suboff) - if err != nil { - return nil, err - } - } - return cr, nil -} - -func (r *compressedReader) chunkOffset(n int) int64 { - if n == len(r.chunks) { - return r.r.Size() - } - return r.chunks[n] -} - -func (r *compressedReader) chunkSize(n int) int { - return int(r.chunkOffset(n+1) - r.chunkOffset(n)) -} - -func (r *compressedReader) uncompressedSize(n int) int { - if n < len(r.chunks)-1 { - return chunkSize - } - size := int(r.originalSize % chunkSize) - if size == 0 { - size = chunkSize - } - return size -} - -func (r *compressedReader) reset(n int) error { - if n >= len(r.chunks) { - return io.EOF - } - if r.d != nil { - r.d.Close() - } - r.curChunk = n - size := r.chunkSize(n) - uncompressedSize := r.uncompressedSize(n) - section := io.NewSectionReader(r.r, r.chunkOffset(n), int64(size)) - if size != uncompressedSize { - d, err := lzx.NewReader(section, uncompressedSize) - if err != nil { - return err - } - r.d = d - } else { - r.d = io.NopCloser(section) - } - - return nil -} - -func (r *compressedReader) Read(b []byte) (int, error) { - for { - n, err := r.d.Read(b) - if err != io.EOF { //nolint:errorlint - return n, err - } - - err = r.reset(r.curChunk + 1) - if err != nil { - return n, err - } - } -} - -func (r *compressedReader) Close() error { - var err error - if r.d != nil { - err = r.d.Close() - r.d = nil - } - return err -} diff --git a/vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go b/vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go deleted file mode 100644 index e5db82602c..0000000000 --- a/vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go +++ /dev/null @@ -1,598 +0,0 @@ -// Package lzx implements a decompressor for the the WIM variant of the -// LZX compression algorithm. -// -// The LZX algorithm is an earlier variant of LZX DELTA, which is documented -// at https://msdn.microsoft.com/en-us/library/cc483133(v=exchg.80).aspx. -package lzx - -import ( - "bytes" - "encoding/binary" - "errors" - "io" -) - -const ( - maincodecount = 496 - maincodesplit = 256 - lencodecount = 249 - lenshift = 9 - codemask = 0x1ff - tablebits = 9 - tablesize = 1 << tablebits - - maxBlockSize = 32768 - windowSize = 32768 - - maxTreePathLen = 16 - - e8filesize = 12000000 - maxe8offset = 0x3fffffff - - verbatimBlock = 1 - alignedOffsetBlock = 2 - uncompressedBlock = 3 -) - -var footerBits = [...]byte{ - 0, 0, 0, 0, 1, 1, 2, 2, - 3, 3, 4, 4, 5, 5, 6, 6, - 7, 7, 8, 8, 9, 9, 10, 10, - 11, 11, 12, 12, 13, 13, 14, -} - -var basePosition = [...]uint16{ - 0, 1, 2, 3, 4, 6, 8, 12, - 16, 24, 32, 48, 64, 96, 128, 192, - 256, 384, 512, 768, 1024, 1536, 2048, 3072, - 4096, 6144, 8192, 12288, 16384, 24576, 32768, -} - -var ( - errCorrupt = errors.New("LZX data corrupt") -) - -// Reader is an interface used by the decompressor to access -// the input stream. If the provided io.Reader does not implement -// Reader, then a bufio.Reader is used. -type Reader interface { - io.Reader - io.ByteReader -} - -type decompressor struct { - r io.Reader - err error - unaligned bool - nbits byte - c uint32 - lru [3]uint16 - uncompressed int - windowReader *bytes.Reader - mainlens [maincodecount]byte - lenlens [lencodecount]byte - window [windowSize]byte - b []byte - bv int - bo int -} - -//go:noinline -func (f *decompressor) fail(err error) { - if f.err == nil { - f.err = err - } - f.bo = 0 - f.bv = 0 -} - -func (f *decompressor) ensureAtLeast(n int) error { - if f.bv-f.bo >= n { - return nil - } - - if f.err != nil { - return f.err - } - - if f.bv != f.bo { - copy(f.b[:f.bv-f.bo], f.b[f.bo:f.bv]) - } - n, err := io.ReadAtLeast(f.r, f.b[f.bv-f.bo:], n) - if err != nil { - if err == io.EOF { //nolint:errorlint - err = io.ErrUnexpectedEOF - } else { - f.fail(err) - } - return err - } - f.bv = f.bv - f.bo + n - f.bo = 0 - return nil -} - -// feed retrieves another 16-bit word from the stream and consumes -// it into f.c. It returns false if there are no more bytes available. -// Otherwise, on error, it sets f.err. -func (f *decompressor) feed() bool { - err := f.ensureAtLeast(2) - if err == io.ErrUnexpectedEOF { //nolint:errorlint // returns io.ErrUnexpectedEOF by contract - return false - } - f.c |= (uint32(f.b[f.bo+1])<<8 | uint32(f.b[f.bo])) << (16 - f.nbits) - f.nbits += 16 - f.bo += 2 - return true -} - -// getBits retrieves the next n bits from the byte stream. n -// must be <= 16. It sets f.err on error. -func (f *decompressor) getBits(n byte) uint16 { - if f.nbits < n { - if !f.feed() { - f.fail(io.ErrUnexpectedEOF) - } - } - c := uint16(f.c >> (32 - n)) - f.c <<= n - f.nbits -= n - return c -} - -type huffman struct { - extra [][]uint16 - maxbits byte - table [tablesize]uint16 -} - -// buildTable builds a huffman decoding table from a slice of code lengths, -// one per code, in order. Each code length must be <= maxTreePathLen. -// See https://en.wikipedia.org/wiki/Canonical_Huffman_code. -func buildTable(codelens []byte) *huffman { - // Determine the number of codes of each length, and the - // maximum length. - var count [maxTreePathLen + 1]uint - var max byte - for _, cl := range codelens { - count[cl]++ - if max < cl { - max = cl - } - } - - if max == 0 { - return &huffman{} - } - - // Determine the first code of each length. - var first [maxTreePathLen + 1]uint - code := uint(0) - for i := byte(1); i <= max; i++ { - code <<= 1 - first[i] = code - code += count[i] - } - - if code != 1< tablebits, split long codes into additional tables - // of suffixes of max-tablebits length. - h := &huffman{maxbits: max} - if max > tablebits { - core := first[tablebits+1] / 2 // Number of codes that fit without extra tables - nextra := 1<> (cl - tablebits) - suffix := code & (1<<(cl-tablebits) - 1) - extendedCode := suffix << (max - cl) - for j := uint(0); j < 1<<(max-cl); j++ { - h.extra[h.table[prefix]][extendedCode+j] = v - } - } - } - } - - return h -} - -// getCode retrieves the next code using the provided -// huffman tree. It sets f.err on error. -func (f *decompressor) getCode(h *huffman) uint16 { - if h.maxbits > 0 { - if f.nbits < maxTreePathLen { - f.feed() - } - - // For codes with length < tablebits, it doesn't matter - // what the remainder of the bits used for table lookup - // are, since entries with all possible suffixes were - // added to the table. - c := h.table[f.c>>(32-tablebits)] - if !(c >= 1<>(32-(h.maxbits-tablebits))] - } - - n := byte(c >> lenshift) - if f.nbits >= n { - // Only consume the length of the code, not the maximum - // code length. - f.c <<= n - f.nbits -= n - return c & codemask - } - - f.fail(io.ErrUnexpectedEOF) - return 0 - } - - // This is an empty tree. It should not be used. - f.fail(errCorrupt) - return 0 -} - -// readTree updates the huffman tree path lengths in lens by -// reading and decoding lengths from the byte stream. lens -// should be prepopulated with the previous block's tree's path -// lengths. For the first block, lens should be zero. -func (f *decompressor) readTree(lens []byte) error { - // Get the pre-tree for the main tree. - var pretreeLen [20]byte - for i := range pretreeLen { - pretreeLen[i] = byte(f.getBits(4)) - } - if f.err != nil { - return f.err - } - h := buildTable(pretreeLen[:]) - - // The lengths are encoded as a series of huffman codes - // encoded by the pre-tree. - for i := 0; i < len(lens); { - c := byte(f.getCode(h)) - if f.err != nil { - return f.err - } - switch { - case c <= 16: // length is delta from previous length - lens[i] = (lens[i] + 17 - c) % 17 - i++ - case c == 17: // next n + 4 lengths are zero - zeroes := int(f.getBits(4)) + 4 - if i+zeroes > len(lens) { - return errCorrupt - } - for j := 0; j < zeroes; j++ { - lens[i+j] = 0 - } - i += zeroes - case c == 18: // next n + 20 lengths are zero - zeroes := int(f.getBits(5)) + 20 - if i+zeroes > len(lens) { - return errCorrupt - } - for j := 0; j < zeroes; j++ { - lens[i+j] = 0 - } - i += zeroes - case c == 19: // next n + 4 lengths all have the same value - same := int(f.getBits(1)) + 4 - if i+same > len(lens) { - return errCorrupt - } - c = byte(f.getCode(h)) - if c > 16 { - return errCorrupt - } - l := (lens[i] + 17 - c) % 17 - for j := 0; j < same; j++ { - lens[i+j] = l - } - i += same - default: - return errCorrupt - } - } - - if f.err != nil { - return f.err - } - return nil -} - -func (f *decompressor) readBlockHeader() (byte, uint16, error) { - // If the previous block was an unaligned uncompressed block, restore - // 2-byte alignment. - if f.unaligned { - err := f.ensureAtLeast(1) - if err != nil { - return 0, 0, err - } - f.bo++ - f.unaligned = false - } - - blockType := f.getBits(3) - full := f.getBits(1) - var blockSize uint16 - if full != 0 { - blockSize = maxBlockSize - } else { - blockSize = f.getBits(16) - if blockSize > maxBlockSize { - return 0, 0, errCorrupt - } - } - - if f.err != nil { - return 0, 0, f.err - } - - switch blockType { - case verbatimBlock, alignedOffsetBlock: - // The caller will read the huffman trees. - case uncompressedBlock: - if f.nbits > 16 { - panic("impossible: more than one 16-bit word remains") - } - - // Drop the remaining bits in the current 16-bit word - // If there are no bits left, discard a full 16-bit word. - n := f.nbits - if n == 0 { - n = 16 - } - - f.getBits(n) - - // Read the LRU values for the next block. - err := f.ensureAtLeast(12) - if err != nil { - return 0, 0, err - } - - f.lru[0] = uint16(binary.LittleEndian.Uint32(f.b[f.bo : f.bo+4])) - f.lru[1] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+4 : f.bo+8])) - f.lru[2] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+8 : f.bo+12])) - f.bo += 12 - - default: - return 0, 0, errCorrupt - } - - return byte(blockType), blockSize, nil -} - -// readTrees reads the two or three huffman trees for the current block. -// readAligned specifies whether to read the aligned offset tree. -func (f *decompressor) readTrees(readAligned bool) (main *huffman, length *huffman, aligned *huffman, err error) { - // Aligned offset blocks start with a small aligned offset tree. - if readAligned { - var alignedLen [8]byte - for i := range alignedLen { - alignedLen[i] = byte(f.getBits(3)) - } - aligned = buildTable(alignedLen[:]) - if aligned == nil { - return main, length, aligned, errors.New("corrupt") - } - } - - // The main tree is encoded in two parts. - err = f.readTree(f.mainlens[:maincodesplit]) - if err != nil { - return main, length, aligned, err - } - err = f.readTree(f.mainlens[maincodesplit:]) - if err != nil { - return main, length, aligned, err - } - - main = buildTable(f.mainlens[:]) - if main == nil { - return main, length, aligned, errors.New("corrupt") - } - - // The length tree is encoding in a single part. - err = f.readTree(f.lenlens[:]) - if err != nil { - return main, length, aligned, err - } - - length = buildTable(f.lenlens[:]) - if length == nil { - return main, length, aligned, errors.New("corrupt") - } - - return main, length, aligned, f.err -} - -// readCompressedBlock decodes a compressed block, writing into the window -// starting at start and ending at end, and using the provided huffman trees. -func (f *decompressor) readCompressedBlock(start, end uint16, hmain, hlength, haligned *huffman) (int, error) { - i := start - for i < end { - main := f.getCode(hmain) - if f.err != nil { - break - } - if main < 256 { - // Literal byte. - f.window[i] = byte(main) - i++ - continue - } - - // This is a match backward in the window. Determine - // the offset and dlength. - matchlen := (main - 256) % 8 - slot := (main - 256) / 8 - - // The length is either the low bits of the code, - // or if this is 7, is encoded with the length tree. - if matchlen == 7 { - matchlen += f.getCode(hlength) - } - matchlen += 2 - - var matchoffset uint16 - if slot < 3 { //nolint:nestif // todo: simplify nested complexity - // The offset is one of the LRU values. - matchoffset = f.lru[slot] - f.lru[slot] = f.lru[0] - f.lru[0] = matchoffset - } else { - // The offset is encoded as a combination of the - // slot and more bits from the bit stream. - offsetbits := footerBits[slot] - var verbatimbits, alignedbits uint16 - if offsetbits > 0 { - if haligned != nil && offsetbits >= 3 { - // This is an aligned offset block. Combine - // the bits written verbatim with the aligned - // offset tree code. - verbatimbits = f.getBits(offsetbits-3) * 8 - alignedbits = f.getCode(haligned) - } else { - // There are no aligned offset bits to read, - // only verbatim bits. - verbatimbits = f.getBits(offsetbits) - alignedbits = 0 - } - } - matchoffset = basePosition[slot] + verbatimbits + alignedbits - 2 - // Update the LRU cache. - f.lru[2] = f.lru[1] - f.lru[1] = f.lru[0] - f.lru[0] = matchoffset - } - - if !(matchoffset <= i && matchlen <= end-i) { - f.fail(errCorrupt) - break - } - copyend := i + matchlen - for ; i < copyend; i++ { - f.window[i] = f.window[i-matchoffset] - } - } - return int(i - start), f.err -} - -// readBlock decodes the current block and returns the number of uncompressed bytes. -func (f *decompressor) readBlock(start uint16) (int, error) { - blockType, size, err := f.readBlockHeader() - if err != nil { - return 0, err - } - - if blockType == uncompressedBlock { - if size%2 == 1 { - // Remember to realign the byte stream at the next block. - f.unaligned = true - } - copied := 0 - if f.bo < f.bv { - copied = int(size) - s := int(start) - if copied > f.bv-f.bo { - copied = f.bv - f.bo - } - copy(f.window[s:s+copied], f.b[f.bo:f.bo+copied]) - f.bo += copied - } - n, err := io.ReadFull(f.r, f.window[start+uint16(copied):start+size]) - return copied + n, err - } - - hmain, hlength, haligned, err := f.readTrees(blockType == alignedOffsetBlock) - if err != nil { - return 0, err - } - - return f.readCompressedBlock(start, start+size, hmain, hlength, haligned) -} - -// decodeE8 reverses the 0xe8 x86 instruction encoding that was performed -// to the uncompressed data before it was compressed. -func decodeE8(b []byte, off int64) { - if off > maxe8offset || len(b) < 10 { - return - } - for i := 0; i < len(b)-10; i++ { - if b[i] == 0xe8 { - currentPtr := int32(off) + int32(i) - abs := int32(binary.LittleEndian.Uint32(b[i+1 : i+5])) - if abs >= -currentPtr && abs < e8filesize { - var rel int32 - if abs >= 0 { - rel = abs - currentPtr - } else { - rel = abs + e8filesize - } - binary.LittleEndian.PutUint32(b[i+1:i+5], uint32(rel)) - } - i += 4 - } - } -} - -func (f *decompressor) Read(b []byte) (int, error) { - // Read and uncompress everything. - if f.windowReader == nil { - n := 0 - for n < f.uncompressed { - k, err := f.readBlock(uint16(n)) - if err != nil { - return 0, err - } - n += k - } - decodeE8(f.window[:f.uncompressed], 0) - f.windowReader = bytes.NewReader(f.window[:f.uncompressed]) - } - - // Just read directly from the window. - return f.windowReader.Read(b) -} - -func (*decompressor) Close() error { - return nil -} - -// NewReader returns a new io.ReadCloser that decompresses a -// WIM LZX stream until uncompressedSize bytes have been returned. -func NewReader(r io.Reader, uncompressedSize int) (io.ReadCloser, error) { - if uncompressedSize > windowSize { - return nil, errors.New("uncompressed size is limited to 32KB") - } - f := &decompressor{ - lru: [3]uint16{1, 1, 1}, - uncompressed: uncompressedSize, - b: make([]byte, 4096), - r: r, - } - return f, nil -} diff --git a/vendor/github.com/Microsoft/go-winio/wim/wim.go b/vendor/github.com/Microsoft/go-winio/wim/wim.go deleted file mode 100644 index 8f272f3da8..0000000000 --- a/vendor/github.com/Microsoft/go-winio/wim/wim.go +++ /dev/null @@ -1,898 +0,0 @@ -//go:build windows || linux -// +build windows linux - -// Package wim implements a WIM file parser. -// -// WIM files are used to distribute Windows file system and container images. -// They are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/dd861280.aspx. -package wim - -import ( - "bytes" - "crypto/sha1" //nolint:gosec // not used for secure application - "encoding/binary" - "encoding/xml" - "errors" - "fmt" - "io" - "strconv" - "sync" - "time" - "unicode/utf16" -) - -// File attribute constants from Windows. -// -//nolint:revive // var-naming: ALL_CAPS -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. -// -//nolint:revive // var-naming: ALL_CAPS -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} - -// todo: replace this with pkg/guid.GUID (and add tests to make sure nothing breaks) - -type guid struct { - Data1 uint32 - Data2 uint16 - Data3 uint16 - Data4 [8]byte -} - -func (g guid) String() string { - 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]) -} - -type resourceDescriptor struct { - FlagsAndCompressedSize uint64 - Offset int64 - OriginalSize int64 -} - -type resFlag byte - -//nolint:deadcode,varcheck // need unused variables for iota to work -const ( - resFlagFree resFlag = 1 << iota - resFlagMetadata - resFlagCompressed - resFlagSpanned -) - -const validate = false - -const supportedResFlags = resFlagMetadata | resFlagCompressed - -func (r *resourceDescriptor) Flags() resFlag { - return resFlag(r.FlagsAndCompressedSize >> 56) -} - -func (r *resourceDescriptor) CompressedSize() int64 { - return int64(r.FlagsAndCompressedSize & 0xffffffffffffff) -} - -func (r *resourceDescriptor) String() string { - s := fmt.Sprintf("%d bytes at %d", r.CompressedSize(), r.Offset) - if r.Flags()&4 != 0 { - s += fmt.Sprintf(" (uncompresses to %d)", r.OriginalSize) - } - return s -} - -// SHA1Hash contains the SHA1 hash of a file or stream. -type SHA1Hash [20]byte - -type streamDescriptor struct { - resourceDescriptor - PartNumber uint16 - RefCount uint32 - Hash SHA1Hash -} - -type hdrFlag uint32 - -//nolint:deadcode,varcheck // need unused variables for iota to work -const ( - hdrFlagReserved hdrFlag = 1 << iota - hdrFlagCompressed - hdrFlagReadOnly - hdrFlagSpanned - hdrFlagResourceOnly - hdrFlagMetadataOnly - hdrFlagWriteInProgress - hdrFlagRpFix -) - -//nolint:deadcode,varcheck // need unused variables for iota to work -const ( - hdrFlagCompressReserved hdrFlag = 1 << (iota + 16) - hdrFlagCompressXpress - hdrFlagCompressLzx -) - -const supportedHdrFlags = hdrFlagRpFix | hdrFlagReadOnly | hdrFlagCompressed | hdrFlagCompressLzx - -type wimHeader struct { - ImageTag [8]byte - Size uint32 - Version uint32 - Flags hdrFlag - CompressionSize uint32 - WIMGuid guid - PartNumber uint16 - TotalParts uint16 - ImageCount uint32 - OffsetTable resourceDescriptor - XMLData resourceDescriptor - BootMetadata resourceDescriptor - BootIndex uint32 - Padding uint32 - Integrity resourceDescriptor - Unused [60]byte -} - -type securityblockDisk struct { - TotalLength uint32 - NumEntries uint32 -} - -const securityblockDiskSize = 8 - -type direntry struct { - Attributes uint32 - SecurityID uint32 - SubdirOffset int64 - Unused1, Unused2 int64 - CreationTime Filetime - LastAccessTime Filetime - LastWriteTime Filetime - Hash SHA1Hash - Padding uint32 - ReparseHardLink int64 - StreamCount uint16 - ShortNameLength uint16 - FileNameLength uint16 -} - -var direntrySize = int64(binary.Size(direntry{}) + 8) // includes an 8-byte length prefix - -type streamentry struct { - Unused int64 - Hash SHA1Hash - NameLength int16 -} - -var streamentrySize = int64(binary.Size(streamentry{}) + 8) // includes an 8-byte length prefix - -// 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 unmarshalls 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. -type ParseError struct { - Oper string - Path string - Err error -} - -func (e *ParseError) Error() string { - if e.Path == "" { - 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()) -} - -func (e *ParseError) Unwrap() error { return e.Err } - -// Reader provides functions to read a WIM file. -type Reader struct { - hdr wimHeader - r io.ReaderAt - fileData map[SHA1Hash]resourceDescriptor - - XMLInfo string // The XML information about the WIM. - Image []*Image // The WIM's images. -} - -// Image represents an image within a WIM file. -type Image struct { - wim *Reader - offset resourceDescriptor - sds [][]byte - rootOffset int64 - r io.ReadCloser - curOffset int64 - m sync.Mutex - - ImageInfo -} - -// StreamHeader contains alternate data stream metadata. -type StreamHeader struct { - Name string - Hash SHA1Hash - Size int64 -} - -// Stream represents an alternate data stream or reparse point data stream. -type Stream struct { - StreamHeader - wim *Reader - offset resourceDescriptor -} - -// FileHeader contains file metadata. -type FileHeader struct { - Name string - ShortName string - Attributes uint32 - SecurityDescriptor []byte - CreationTime Filetime - LastAccessTime Filetime - LastWriteTime Filetime - Hash SHA1Hash - Size int64 - LinkID int64 - ReparseTag uint32 - ReparseReserved uint32 -} - -// File represents a file or directory in a WIM image. -type File struct { - FileHeader - Streams []*Stream - offset resourceDescriptor - img *Image - subdirOffset int64 -} - -// NewReader returns a Reader that can be used to read WIM file data. -func NewReader(f io.ReaderAt) (*Reader, error) { - r := &Reader{r: f} - section := io.NewSectionReader(f, 0, 0xffff) - err := binary.Read(section, binary.LittleEndian, &r.hdr) - if err != nil { - return nil, err - } - - if r.hdr.ImageTag != wimImageTag { - return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")} - } - - if r.hdr.Flags&^supportedHdrFlags != 0 { - return nil, fmt.Errorf("unsupported WIM flags %x", r.hdr.Flags&^supportedHdrFlags) - } - - if r.hdr.CompressionSize != 0x8000 { - return nil, fmt.Errorf("unsupported compression size %d", r.hdr.CompressionSize) - } - - if r.hdr.TotalParts != 1 { - return nil, errors.New("multi-part WIM not supported") - } - - fileData, images, err := r.readOffsetTable(&r.hdr.OffsetTable) - if err != nil { - return nil, err - } - - xmlinfo, err := r.readXML() - if err != nil { - return nil, err - } - - var inf info - err = xml.Unmarshal([]byte(xmlinfo), &inf) - if err != nil { - return nil, &ParseError{Oper: "XML info", Err: err} - } - - for i, img := range images { - for _, imgInfo := range inf.Image { - if imgInfo.Index == i+1 { - img.ImageInfo = imgInfo - break - } - } - } - - r.fileData = fileData - r.Image = images - r.XMLInfo = xmlinfo - return r, nil -} - -// Close releases resources associated with the Reader. -func (r *Reader) Close() error { - for _, img := range r.Image { - img.reset() - } - return nil -} - -func (r *Reader) resourceReader(hdr *resourceDescriptor) (io.ReadCloser, error) { - return r.resourceReaderWithOffset(hdr, 0) -} - -func (r *Reader) resourceReaderWithOffset(hdr *resourceDescriptor, offset int64) (io.ReadCloser, error) { - var sr io.ReadCloser - section := io.NewSectionReader(r.r, hdr.Offset, hdr.CompressedSize()) - if hdr.Flags()&resFlagCompressed == 0 { - _, _ = section.Seek(offset, 0) - sr = io.NopCloser(section) - } else { - cr, err := newCompressedReader(section, hdr.OriginalSize, offset) - if err != nil { - return nil, err - } - sr = cr - } - - return sr, nil -} - -func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) { - rsrc, err := r.resourceReader(hdr) - if err != nil { - return nil, err - } - defer rsrc.Close() - return io.ReadAll(rsrc) -} - -func (r *Reader) readXML() (string, error) { - if r.hdr.XMLData.CompressedSize() == 0 { - return "", nil - } - rsrc, err := r.resourceReader(&r.hdr.XMLData) - if err != nil { - return "", err - } - defer rsrc.Close() - - xmlData := make([]uint16, r.hdr.XMLData.OriginalSize/2) - err = binary.Read(rsrc, binary.LittleEndian, xmlData) - if err != nil { - return "", &ParseError{Oper: "XML data", Err: err} - } - - // The BOM will always indicate little-endian UTF-16. - if xmlData[0] != 0xfeff { - return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")} - } - return string(utf16.Decode(xmlData[1:])), nil -} - -func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resourceDescriptor, []*Image, error) { - fileData := make(map[SHA1Hash]resourceDescriptor) - var images []*Image - - offsetTable, err := r.readResource(res) - if err != nil { - return nil, nil, &ParseError{Oper: "offset table", Err: err} - } - - br := bytes.NewReader(offsetTable) - for i := 0; ; i++ { - var res streamDescriptor - err := binary.Read(br, binary.LittleEndian, &res) - if err == io.EOF { //nolint:errorlint - break - } - if err != nil { - return nil, nil, &ParseError{Oper: "offset table", Err: err} - } - if res.Flags()&^supportedResFlags != 0 { - return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")} - } - - // Validation for ad-hoc testing - if validate { - sec, err := r.resourceReader(&res.resourceDescriptor) - if err != nil { - panic(fmt.Sprint(i, err)) - } - hash := sha1.New() //nolint:gosec // not used for secure application - _, err = io.Copy(hash, sec) - sec.Close() - if err != nil { - panic(fmt.Sprint(i, err)) - } - var cmphash SHA1Hash - copy(cmphash[:], hash.Sum(nil)) - if cmphash != res.Hash { - panic(fmt.Sprint(i, "hash mismatch")) - } - } - - if res.Flags()&resFlagMetadata != 0 { - image := &Image{ - wim: r, - offset: res.resourceDescriptor, - } - images = append(images, image) - } else { - fileData[res.Hash] = res.resourceDescriptor - } - } - - if len(images) != int(r.hdr.ImageCount) { - return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")} - } - - return fileData, images, nil -} - -func (*Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, err error) { - var secBlock securityblockDisk - err = binary.Read(rsrc, binary.LittleEndian, &secBlock) - if err != nil { - return sds, 0, &ParseError{Oper: "security table", Err: err} - } - - n += securityblockDiskSize - - secSizes := make([]int64, secBlock.NumEntries) - err = binary.Read(rsrc, binary.LittleEndian, &secSizes) - if err != nil { - return sds, n, &ParseError{Oper: "security table sizes", Err: err} - } - - n += int64(secBlock.NumEntries * 8) - - sds = make([][]byte, secBlock.NumEntries) - for i, size := range secSizes { - sd := make([]byte, size&0xffffffff) - _, err = io.ReadFull(rsrc, sd) - if err != nil { - return sds, n, &ParseError{Oper: "security descriptor", Err: err} - } - n += int64(len(sd)) - sds[i] = sd - } - - secsize := int64((secBlock.TotalLength + 7) &^ 7) - if n > secsize { - return sds, n, &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")} - } - - _, err = io.CopyN(io.Discard, rsrc, secsize-n) - if err != nil { - return sds, n, err - } - - n = secsize - return sds, n, nil -} - -// Open parses the image and returns the root directory. -func (img *Image) Open() (*File, error) { - if img.sds == nil { - rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, img.rootOffset) - if err != nil { - return nil, err - } - sds, n, err := img.wim.readSecurityDescriptors(rsrc) - if err != nil { - rsrc.Close() - return nil, err - } - img.sds = sds - img.r = rsrc - img.rootOffset = n - img.curOffset = n - } - - f, err := img.readdir(img.rootOffset) - if err != nil { - return nil, err - } - if len(f) != 1 { - return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")} - } - return f[0], err -} - -func (img *Image) reset() { - if img.r != nil { - img.r.Close() - img.r = nil - } - img.curOffset = -1 -} - -func (img *Image) readdir(offset int64) ([]*File, error) { - img.m.Lock() - defer img.m.Unlock() - - if offset < img.curOffset || offset > img.curOffset+chunkSize { - // Reset to seek backward or to seek forward very far. - img.reset() - } - if img.r == nil { - rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, offset) - if err != nil { - return nil, err - } - img.r = rsrc - img.curOffset = offset - } - if offset > img.curOffset { - _, err := io.CopyN(io.Discard, img.r, offset-img.curOffset) - if err != nil { - img.reset() - if err == io.EOF { //nolint:errorlint - err = io.ErrUnexpectedEOF - } - return nil, err - } - } - - var entries []*File - for { - e, n, err := img.readNextEntry(img.r) - img.curOffset += n - if err == io.EOF { //nolint:errorlint - break - } - if err != nil { - img.reset() - return nil, err - } - entries = append(entries, e) - } - return entries, nil -} - -func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) { - var length int64 - err := binary.Read(r, binary.LittleEndian, &length) - if err != nil { - return nil, 0, &ParseError{Oper: "directory length check", Err: err} - } - - if length == 0 { - return nil, 8, io.EOF - } - - left := length - if left < direntrySize { - return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short")} - } - - var dentry direntry - err = binary.Read(r, binary.LittleEndian, &dentry) - if err != nil { - return nil, 0, &ParseError{Oper: "directory entry", Err: err} - } - - left -= direntrySize - - namesLen := int64(dentry.FileNameLength + 2 + dentry.ShortNameLength) - if left < namesLen { - return nil, 0, &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, 0, &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 - zerohash := SHA1Hash{} - if dentry.Hash != zerohash { - var ok bool - offset, ok = img.wim.fileData[dentry.Hash] - if !ok { - return nil, 0, &ParseError{ - Oper: "directory entry", - Path: name, - Err: fmt.Errorf("could not find file data matching hash %#v", dentry), - } - } - } - - f := &File{ - FileHeader: FileHeader{ - Attributes: dentry.Attributes, - CreationTime: dentry.CreationTime, - LastAccessTime: dentry.LastAccessTime, - LastWriteTime: dentry.LastWriteTime, - Hash: dentry.Hash, - Size: offset.OriginalSize, - Name: name, - ShortName: shortName, - }, - - offset: offset, - img: img, - subdirOffset: dentry.SubdirOffset, - } - - isDir := false - - if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT == 0 { - f.LinkID = dentry.ReparseHardLink - if dentry.Attributes&FILE_ATTRIBUTE_DIRECTORY != 0 { - isDir = true - } - } else { - f.ReparseTag = uint32(dentry.ReparseHardLink) - f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32) - } - - if isDir && f.subdirOffset == 0 { - return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")} - } else if !isDir && f.subdirOffset != 0 { - return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")} - } - - if dentry.SecurityID != 0xffffffff { - f.SecurityDescriptor = img.sds[dentry.SecurityID] - } - - _, err = io.CopyN(io.Discard, r, left) - if err != nil { - if err == io.EOF { //nolint:errorlint - err = io.ErrUnexpectedEOF - } - return nil, 0, err - } - - if dentry.StreamCount > 0 { - var streams []*Stream - for i := uint16(0); i < dentry.StreamCount; i++ { - s, n, err := img.readNextStream(r) - length += n - if err != nil { - return nil, 0, err - } - // The first unnamed stream should be treated as the file stream. - if i == 0 && s.Name == "" { - f.Hash = s.Hash - f.Size = s.Size - f.offset = s.offset - } else if s.Name != "" { - streams = append(streams, s) - } - } - f.Streams = streams - } - - if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 { - return nil, 0, &ParseError{ - Oper: "directory entry", - Path: name, - Err: errors.New("reparse point is missing reparse stream"), - } - } - - return f, length, nil -} - -func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) { - var length int64 - err := binary.Read(r, binary.LittleEndian, &length) - if err != nil { - if err == io.EOF { //nolint:errorlint - err = io.ErrUnexpectedEOF - } - return nil, 0, &ParseError{Oper: "stream length check", Err: err} - } - - left := length - if left < streamentrySize { - return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short")} - } - - var sentry streamentry - err = binary.Read(r, binary.LittleEndian, &sentry) - if err != nil { - return nil, 0, &ParseError{Oper: "stream entry", Err: err} - } - - left -= streamentrySize - - if left < int64(sentry.NameLength) { - return nil, 0, &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, 0, &ParseError{Oper: "file name", Err: err} - } - - left -= int64(sentry.NameLength) - name := string(utf16.Decode(names)) - - var offset resourceDescriptor - if sentry.Hash != (SHA1Hash{}) { - var ok bool - offset, ok = img.wim.fileData[sentry.Hash] - if !ok { - return nil, 0, &ParseError{ - Oper: "stream entry", - Path: name, - Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash), - } - } - } - - s := &Stream{ - StreamHeader: StreamHeader{ - Hash: sentry.Hash, - Size: offset.OriginalSize, - Name: name, - }, - wim: img.wim, - offset: offset, - } - - _, err = io.CopyN(io.Discard, r, left) - if err != nil { - if err == io.EOF { //nolint:errorlint - err = io.ErrUnexpectedEOF - } - return nil, 0, err - } - - return s, length, nil -} - -// Open returns an io.ReadCloser that can be used to read the stream's contents. -func (s *Stream) Open() (io.ReadCloser, error) { - return s.wim.resourceReader(&s.offset) -} - -// Open returns an io.ReadCloser that can be used to read the file's contents. -func (f *File) Open() (io.ReadCloser, error) { - return f.img.wim.resourceReader(&f.offset) -} - -// Readdir reads the directory entries. -func (f *File) Readdir() ([]*File, error) { - if !f.IsDir() { - return nil, errors.New("not a directory") - } - return f.img.readdir(f.subdirOffset) -} - -// 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 -} diff --git a/vendor/modules.txt b/vendor/modules.txt index ce372e8d91..3878596729 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -98,8 +98,6 @@ github.com/Microsoft/go-winio/internal/stringbuffer github.com/Microsoft/go-winio/pkg/bindfilter github.com/Microsoft/go-winio/pkg/guid github.com/Microsoft/go-winio/vhd -github.com/Microsoft/go-winio/wim -github.com/Microsoft/go-winio/wim/lzx # github.com/Microsoft/hcsshim v0.11.4 ## explicit; go 1.18 github.com/Microsoft/hcsshim @@ -2547,7 +2545,7 @@ sigs.k8s.io/structured-merge-diff/v4/value # sigs.k8s.io/yaml v1.3.0 ## explicit; go 1.12 sigs.k8s.io/yaml -# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023537-f41e01f2eee3 +# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260812062623-2b79dfea19bd ## explicit; go 1.24 yunion.io/x/cloudmux/pkg/apis yunion.io/x/cloudmux/pkg/apis/billing diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/monitor.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/monitor.go index 0e69156c15..b35fdd7c63 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/monitor.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/monitor.go @@ -376,12 +376,12 @@ func (self *SAliyunClient) GetEcsMetrics(opts *cloudprovider.MetricListOptions) } case cloudprovider.VM_METRIC_TYPE_PROCESS_NUMBER: metricTags = map[string]string{ - "process.count_processname": "", + "process.number": "", } tagKey = cloudprovider.METRIC_TAG_PROCESS_NAME case cloudprovider.VM_METRIC_TYPE_NET_TCP_CONNECTION: metricTags = map[string]string{ - "network.tcp.connection_state": "", + "net_tcpconnection": "", } tagKey = cloudprovider.METRIC_TAG_STATE default: diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/notice.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/notice.go index 6656e288b6..4b39afa47e 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/notice.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/notice.go @@ -34,7 +34,17 @@ import ( const aliyunNoticeRssURL = "https://www.aliyun.com/rss/notice/zh.xml" -var htmlTagRe = regexp.MustCompile(`<[^>]*>`) +var ( + // 仅匹配真实 HTML 标签,避免把版本比较里的 <= / >= 当成标签删掉 + htmlTagRe = regexp.MustCompile(`(?i)]*>`) + htmlBreakTagRe = regexp.MustCompile(`(?i)`) + htmlLinkRe = regexp.MustCompile(`(?i)]*href\s*=\s*["']([^"']+)["'][^>]*>([\s\S]*?)`) + htmlLiTagRe = regexp.MustCompile(`(?i)]*>`) + htmlTdThTagRe = regexp.MustCompile(`(?i)`) + htmlBlockTagRe = regexp.MustCompile(`(?i)]*>`) + htmlMultiLineRe = regexp.MustCompile(`\n{3,}`) + htmlSpaceRe = regexp.MustCompile(`[^\S\n]{2,}`) +) type SNotice struct { title string @@ -137,16 +147,45 @@ func stripHTML(s string) string { if len(s) == 0 { return "" } + // 阿里云 RSS 常把   误用作标签内属性分隔符,需先还原成空格再解析标签 + s = strings.ReplaceAll(s, " ", " ") + s = strings.ReplaceAll(s, " ", " ") s = html.UnescapeString(s) - s = htmlTagRe.ReplaceAllString(s, "") s = strings.ReplaceAll(s, "\u00a0", " ") + s = strings.ReplaceAll(s, "\r\n", "\n") + s = strings.ReplaceAll(s, "\r", "\n") + + // 保留超链接:文案 (url);文案与 url 相同时只保留 url + s = htmlLinkRe.ReplaceAllStringFunc(s, func(m string) string { + parts := htmlLinkRe.FindStringSubmatch(m) + if len(parts) < 3 { + return m + } + url := strings.TrimSpace(parts[1]) + text := strings.TrimSpace(htmlTagRe.ReplaceAllString(parts[2], "")) + text = strings.Join(strings.Fields(text), " ") + if len(text) == 0 || text == url { + return url + } + return fmt.Sprintf("%s (%s)", text, url) + }) + + s = htmlBreakTagRe.ReplaceAllString(s, "\n") + s = htmlLiTagRe.ReplaceAllString(s, "\n- ") + s = htmlTdThTagRe.ReplaceAllString(s, " | ") + s = htmlBlockTagRe.ReplaceAllString(s, "\n") + s = htmlTagRe.ReplaceAllString(s, "") + lines := strings.Split(s, "\n") - parts := []string{} + parts := make([]string, 0, len(lines)) for _, line := range lines { line = strings.TrimSpace(line) - if len(line) > 0 { - parts = append(parts, line) - } + line = strings.Trim(line, "|") + line = strings.TrimSpace(line) + line = htmlSpaceRe.ReplaceAllString(line, " ") + parts = append(parts, line) } - return strings.Join(parts, "\n") + s = strings.Join(parts, "\n") + s = htmlMultiLineRe.ReplaceAllString(s, "\n\n") + return strings.TrimSpace(s) }