wincmd/ntfs/fragment/reader.go
starainrt 7e6cc73106
完善 Windows 运维封装与 NTFS 索引解析
- 新增自启动幂等配置、统一错误语义、进程等待和进程树终止能力
- 增强服务生命周期管理,支持等待状态、重启、幂等创建和配置更新
- 新增 NTFS 卷索引、文件 ID 解析、文件遍历、USN 变更监听和 bookmark 持久化
- 修复 NTFS boot sector、fragment、MFT、USN 解析边界和路径重建问题
- 补充权限、进程、服务、NTFS 解析和工作流回归测试
- 增加 Windows 测试脚本和管理员 NTFS smoke 验证脚本
- 升级 Go 兼容版本到 1.18,并更新 stario、win32api 及相关间接依赖
2026-06-09 15:59:31 +08:00

95 lines
2.9 KiB
Go

/*
Package fragment contains a Reader which can read Fragments which may be scattered around a volume (and perhaps even
not in sequence). Typically these could be translated from MFT attribute DataRuns. To convert MFT attribute DataRuns
to Fragments for use in the fragment Reader, use mft.DataRunsToFragments().
# Implementation notes
When the fragment Reader is near the end of a fragment and a Read() call requests more data than what is left in
the current fragment, the Reader will exhaust only the current fragment and return that data (which could be less
than len(p)). A next Read() call will then seek to the next fragment and continue reading there. When the last
fragment is exhausted by a Read(), it will return the remaining bytes read and a nil error. Any subsequent Read()
calls after that will return 0, io.EOF.
When accessing a new fragment, the Reader will seek using the absolute Length in the fragment from the start
of the contained io.ReadSeeker (using io.SeekStart).
*/
package fragment
import (
"fmt"
"io"
)
// Fragment contains an absolute Offset in bytes from the start of a volume and a Length of the fragment, also in bytes.
type Fragment struct {
Offset int64
Length int64
}
// A fragment Reader will read data from the fragments in order. When one fragment is depleted, it will seek to the
// position of the next fragment and continue reading from there, until all fragments have been exhausted. When the last
// fragment has been exhaused, each subsequent Read() will return io.EOF.
type Reader struct {
src io.ReadSeeker
closer io.Closer
fragments []Fragment
idx int
remaining int64
}
// NewReader initializes a new Reader from the io.ReaderSeeker and fragments and returns a pointer to. Note that
// fragments may not be sequential in order, so the io.ReadSeeker should support seeking backwards (or rather, from the
// start).
func NewReader(src io.ReadSeeker, fragments []Fragment) *Reader {
r := &Reader{src: src, fragments: fragments, idx: -1, remaining: 0}
if closer, ok := src.(io.Closer); ok {
r.closer = closer
}
return r
}
func (r *Reader) Read(p []byte) (n int, err error) {
if r.idx >= len(r.fragments) {
return 0, io.EOF
}
if len(p) == 0 {
return 0, nil
}
if r.remaining == 0 {
r.idx++
if r.idx >= len(r.fragments) {
return 0, io.EOF
}
next := r.fragments[r.idx]
r.remaining = next.Length
seeked, err := r.src.Seek(next.Offset, io.SeekStart)
if err != nil {
return 0, fmt.Errorf("unable to seek to next offset %d: %v", next.Offset, err)
}
if seeked != next.Offset {
return 0, fmt.Errorf("wanted to seek to %d but reached %d", next.Offset, seeked)
}
}
target := p
if int64(len(p)) > r.remaining {
target = p[:r.remaining]
}
n, err = io.ReadFull(r.src, target)
r.remaining -= int64(n)
return n, err
}
func (r *Reader) Close() error {
if r.closer == nil {
return nil
}
err := r.closer.Close()
r.closer = nil
return err
}