Documentation
¶
Overview ¶
Package wim reads and writes Windows Imaging (.wim) files.
Writing captures a file tree into a WIM: walking the tree, deduplicating streams by SHA-1, and emitting the dentry tree, the security table, the blob table, and the XML — with no external imaging tool involved. Resources are stored uncompressed or LZX-compressed; the codec lives in the lzx subpackage and is usable on its own.
Reading goes the other way and presents an image as an io/fs.FS:
rd, err := wim.Open(f, size) im, err := rd.Boot() data, err := fs.ReadFile(im.FS(), "windows/system32/ntoskrnl.exe")
so anything already written against a filesystem works against a WIM unchanged. Reads are served from the chunks they touch rather than by expanding the image, which is what makes listing one cheap and reading one file out of a large one cheaper still.
Two properties of the format are load-bearing and easy to get wrong, and this package refuses rather than lets you discover them at boot. A loader takes the metadata resource's compression from the WIM header rather than from that resource's own flag, so an image declaring a codec must compress its metadata too — the mismatch is legal, verifies clean under other tools, and produces an image Windows will not boot. And every dentry needs a security ID: an image whose dentries carry -1, which is what a POSIX capture naturally produces, is rejected at mount, which is why Options.Security has no default.
Both of those constrain what is written. The reader is deliberately more permissive: it takes each resource's storage from that resource's own flag, so it reads images a loader would refuse. A WIM this package reads is therefore not necessarily one Windows will boot — writing it here is what makes it that.
Index ¶
- Constants
- Variables
- func Capture(ctx context.Context, dst io.WriteSeeker, src fs.FS, info ImageInfo, ...) error
- func CaptureDir(ctx context.Context, dir, out string, info ImageInfo, opts Options) error
- type Arch
- type Compression
- type Image
- type ImageInfo
- type Options
- type Reader
- type SecurityFunc
- type Version
- type WindowsInfo
- type Writer
Examples ¶
Constants ¶
const DefaultChunkSize = 32768
DefaultChunkSize is the uncompressed size of one chunk of a compressed resource, and the value every WIM of this version in the wild uses. It is also the LZX window, which is why the codec cannot code a larger one.
Variables ¶
var ( // ErrNotWIM reports input that is not a WIM: too short, or without the signature. ErrNotWIM = errors.New("wim: not a WIM file") // ErrUnsupported reports a well-formed WIM in a form this package does not read — another // codec, a solid or split image — as opposed to a damaged one. ErrUnsupported = errors.New("wim: unsupported WIM") // ErrCorrupt reports a WIM whose structure contradicts itself. Every bound this package // checks reports through it, so a caller can tell a damaged image from an unreadable one. ErrCorrupt = errors.New("wim: corrupt WIM") )
The three error kinds a reader distinguishes. They are kept apart because they mean different things to a caller: the input is not a WIM at all, it is a WIM this package declines to read, or it is a WIM whose contents contradict themselves. Collapsing them would make "we cannot read this" indistinguishable from "this file is damaged".
var ErrChunkSize = errors.New("wim: ChunkSize must be a power of two no larger than the LZX window")
ErrChunkSize reports a chunk size the codec cannot code against.
It is refused because the failure is otherwise silent and looks like success: the LZX window is one chunk, so a chunk larger than the window is declined by the codec and stored raw, and a resource of those can still come out a few bytes shorter than its input — earning the compressed flag while holding almost entirely uncompressed data. The image is well formed, reads back correctly, and is neither compressed nor readable by anything expecting the standard chunk size.
var ErrNoSecurity = errors.New("wim: Options.Security is required (an image with no security table is rejected by Windows)")
ErrNoSecurity reports a capture with no security descriptors. An image whose dentries carry no security ID is what a POSIX filesystem naturally yields — there is no NT security data to read — and it is what Windows rejects at mount, so it is refused at capture time rather than discovered when the image is used.
Functions ¶
func Capture ¶
func Capture(ctx context.Context, dst io.WriteSeeker, src fs.FS, info ImageInfo, opts Options) error
Capture writes src to dst as a single-image WIM. It is NewWriter + AddImage + Close.
Example ¶
Capture an in-memory tree, which is all a Writer needs — any fs.FS will do.
package main
import (
"context"
"fmt"
"log"
"os"
"testing/fstest"
"github.com/nradchenko/go-wim"
)
// A security descriptor is opaque to this package; a real caller supplies one its target
// Windows accepts. This is a minimal self-relative descriptor so the examples run.
var descriptor = []byte{
0x01, 0x00, 0x00, 0x80,
0x14, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00,
}
func main() {
src := fstest.MapFS{
"boot.ini": &fstest.MapFile{Data: []byte("[boot loader]\n")},
"system32/kernel.dll": &fstest.MapFile{Data: []byte("MZ...")},
}
out, err := os.CreateTemp("", "example-*.wim")
if err != nil {
log.Fatal(err)
}
defer os.Remove(out.Name())
defer out.Close()
err = wim.Capture(context.Background(), out, src,
wim.ImageInfo{Name: "Image"},
wim.Options{
Security: wim.UniformSecurity(descriptor),
// Deduplicated by content, so one descriptor for every file costs one entry.
Compression: wim.CompressLZX,
},
)
if err != nil {
log.Fatal(err)
}
fmt.Println("captured")
}
Output: captured
func CaptureDir ¶
CaptureDir captures the directory dir into a new WIM file at out, replacing it if it exists. A failed capture leaves no partial file behind.
Example ¶
Capture a directory into a bootable single-image WIM.
package main
import (
"context"
"log"
"github.com/nradchenko/go-wim"
)
// A security descriptor is opaque to this package; a real caller supplies one its target
// Windows accepts. This is a minimal self-relative descriptor so the examples run.
var descriptor = []byte{
0x01, 0x00, 0x00, 0x80,
0x14, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00,
}
func main() {
err := wim.CaptureDir(context.Background(), "tree", "out.wim",
wim.ImageInfo{
Name: "Image",
Boot: true,
Windows: &wim.WindowsInfo{
Arch: wim.ArchIntel,
Version: wim.Version{Major: 5, Minor: 1, Build: 2600},
},
},
wim.Options{Security: wim.UniformSecurity(descriptor)},
)
if err != nil {
log.Fatal(err)
}
}
Output:
Types ¶
type Arch ¶
type Arch int
Arch is the processor architecture recorded in <WINDOWS><ARCH>, using Windows' own PROCESSOR_ARCHITECTURE numbering.
type Compression ¶
type Compression int
Compression selects the codec a WIM's file resources are stored with.
const ( // CompressLZX stores resources as LZX-compressed chunks. It is the codec WIMs of this // version carry, and the only one this package implements; XPRESS is not written. CompressLZX Compression = iota // CompressNone stores every resource uncompressed. Useful for tests and for inspecting // a capture; a whole PE image stored this way is legal but large. CompressNone )
type Image ¶ added in v0.2.0
type Image struct {
// contains filtered or unexported fields
}
Image is one image inside a WIM: its identity, and its file tree.
The tree is parsed on first use rather than at Open: listing a WIM's images should not cost the decode of every image's metadata, and most callers want one image out of the WIM.
func (*Image) FS ¶ added in v0.2.0
FS presents the image's file tree as a read-only fs.FS, implementing fs.ReadDirFS and fs.StatFS as well, so a caller walks, stats and reads it exactly as it would any other filesystem.
Paths are matched exactly, including case. A WIM stores names in the case the capture saw, and Windows would match them case-insensitively; that is a property of the filesystem the image came from rather than of the format, and folding it in here would make Open succeed on paths this tree does not contain. A caller wanting Windows' own matching wraps this.
FS never fails: an image whose metadata cannot be parsed reports that from the first operation on it, rather than making every caller handle an error from a method that has nothing else to go wrong.
type ImageInfo ¶
type ImageInfo struct {
// Name is the image's <NAME>. It is what wimlib and DISM show as the image's identity.
Name string
// Description is the image's optional <DESCRIPTION>.
Description string
// Boot marks this image bootable: it becomes the WIM's boot index, and the header's
// boot-metadata resource points at this image's metadata. A loader reads the image the
// boot index names.
Boot bool
// Windows, when non-nil, adds the <WINDOWS> block describing the Windows installation
// the image holds.
Windows *WindowsInfo
}
ImageInfo describes an image being captured.
type Options ¶
type Options struct {
// Compression is the codec for file resources. Defaults to CompressLZX.
Compression Compression
// ChunkSize is the uncompressed size of one chunk of a compressed resource. Defaults to
// DefaultChunkSize, which is what real images carry; setting anything else is for tests.
// It must be a power of two no larger than the LZX window, which is what the codec can
// code a chunk against (see ErrChunkSize).
ChunkSize int
// Security assigns a security descriptor to each captured file. It has no default: see
// ErrNoSecurity.
Security SecurityFunc
// Timestamp, when non-zero, is recorded as every dentry's creation, access, and write
// time in place of the source tree's own times. Set it for a byte-reproducible capture
// of a tree whose timestamps vary.
Timestamp time.Time
// GUID is the WIM's identifier. A zero GUID is derived from the image's own content,
// keeping a capture reproducible; wimlib randomises it.
GUID [16]byte
// Concurrency bounds the chunk compressions in flight. Zero means one per CPU. Chunks are
// independent by construction — each resets the window, the trees and the recent offsets —
// so this is the cheapest speed available, and the output is byte-identical whatever the
// value: results are placed by chunk index, never in completion order.
Concurrency int
}
Options configure a Writer. Security is required; the rest default to LZX, 32768-byte chunks, and timestamps taken from the captured tree.
type Reader ¶ added in v0.2.0
type Reader struct {
// contains filtered or unexported fields
}
Reader reads a WIM: its images, and through them their file trees.
A Reader is safe for concurrent use, and so must be the io.ReaderAt it was opened over — a tree walk fans out across files, and each read seeks independently.
func Open ¶ added in v0.2.0
Open reads the WIM held in r, whose length is size.
r must support random access and be safe for concurrent use: the format is offset-indexed — the blob table, the metadata resource and every chunk of every file are found by offset — so reading one file never costs a scan of the image, and several may be read at once.
func OpenBytes ¶ added in v0.2.0
OpenBytes reads a WIM already held in memory. It is the convenience form of Open for a WIM pulled out of an archive or an image, where the bytes are in hand already.
func (*Reader) Boot ¶ added in v0.2.0
Boot returns the image the header marks bootable, or the sole image of a WIM that marks none.
A bootable image is named twice — by index, and by a copy of its metadata resource header that a loader reads directly — and the two are cross-checked here. On disagreement the image is reported as corrupt rather than one of the two being preferred silently: the header's copy is what a loader would boot and the index is what every other tool would show, so the image is two different images depending on who reads it. Only Boot fails, so an image in that state can still be listed and inspected through Image.
type SecurityFunc ¶
SecurityFunc returns the self-relative NT security descriptor to record for the captured file at path — slash-separated and relative to the capture root, with "." for the root itself. It must return one for every file: a dentry left without a security ID carries -1, which is the wimlib-on-Linux shape Windows rejects at mount, and a capture holding even a few of them verifies clean and fails at boot. Returning nil is therefore an error, not a way to skip a file.
The writer deduplicates descriptors by content, so returning the same bytes for every file costs exactly one security-table entry.
Example ¶
Give different files different descriptors.
package main
import (
"io/fs"
"github.com/nradchenko/go-wim"
)
// A security descriptor is opaque to this package; a real caller supplies one its target
// Windows accepts. This is a minimal self-relative descriptor so the examples run.
var descriptor = []byte{
0x01, 0x00, 0x00, 0x80,
0x14, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00,
}
func main() {
security := func(_ string, d fs.DirEntry) []byte {
if d.IsDir() {
return directoryDescriptor
}
return descriptor
}
_ = wim.Options{Security: security}
}
var directoryDescriptor = descriptor
Output:
func UniformSecurity ¶
func UniformSecurity(sd []byte) SecurityFunc
UniformSecurity returns a SecurityFunc that assigns sd to every captured file — the shape a PE build wants, where one system descriptor covers the whole image.
type Version ¶
Version is a Windows build version, recorded in <WINDOWS><VERSION>, in the four parts that element carries: 5.1.2600.5512 is one such version.
type WindowsInfo ¶
WindowsInfo is an image's <WINDOWS> block. Zero fields are omitted from the XML.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer builds a WIM into dst. Resources are written as they are captured and the header, blob table, and XML are finalised by Close, so dst must be seekable — the header carries offsets that are only known once every image has been written.
A Writer holds one blob table across every image it writes, which is the whole reason it is a type rather than a function: identical files, within an image or across images, are stored once and referenced by SHA-1.
func NewWriter ¶
func NewWriter(dst io.WriteSeeker, opts Options) *Writer
NewWriter returns a Writer that builds a WIM into dst. Nothing is written until the first AddImage; Close must be called to produce a valid file.
func (*Writer) AddImage ¶
AddImage captures the tree rooted at src as a new image described by info, appending its file resources and metadata resource to the WIM. Files are deduplicated against everything already written. Symlinks in src are an error rather than being followed, so a capture cannot silently duplicate a file or escape its root.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package lzx implements the WIM variant of the LZX compression algorithm, in both directions: a compressor for building WIMs and a decompressor the round-trip tests check it against.
|
Package lzx implements the WIM variant of the LZX compression algorithm, in both directions: a compressor for building WIMs and a decompressor the round-trip tests check it against. |