Documentation
¶
Overview ¶
Package wim writes Windows Imaging (.wim) files.
It 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.
The package writes; it does not read. Enough of the format is parsed internally to place what it emits, and the tests parse the output back, but there is no reader API.
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.
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 ImageInfo
- type Options
- 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 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 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 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(path 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.
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. |