完善 Windows 运维封装与 NTFS 索引解析
- 新增自启动幂等配置、统一错误语义、进程等待和进程树终止能力 - 增强服务生命周期管理,支持等待状态、重启、幂等创建和配置更新 - 新增 NTFS 卷索引、文件 ID 解析、文件遍历、USN 变更监听和 bookmark 持久化 - 修复 NTFS boot sector、fragment、MFT、USN 解析边界和路径重建问题 - 补充权限、进程、服务、NTFS 解析和工作流回归测试 - 增加 Windows 测试脚本和管理员 NTFS smoke 验证脚本 - 升级 Go 兼容版本到 1.18,并更新 stario、win32api 及相关间接依赖
This commit is contained in:
+74
-48
@@ -15,13 +15,17 @@ const supportedOemId = "NTFS "
|
||||
const isWin = runtime.GOOS == "windows"
|
||||
|
||||
func GetMFTFileBytes(volume string) ([]byte, error) {
|
||||
reader, length, err := GetMFTFile(volume)
|
||||
reader, length, err := GetMFTFileReader(volume)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
bfio := bytes.NewBuffer(buf)
|
||||
defer reader.Close()
|
||||
|
||||
bfio := bytes.NewBuffer(make([]byte, 0, length))
|
||||
written, err := copyBytes(bfio, reader, length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if written != length {
|
||||
return nil, fmt.Errorf("Write Not Ok,Should %d got %d", length, written)
|
||||
}
|
||||
@@ -29,16 +33,21 @@ func GetMFTFileBytes(volume string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func DumpMFTFile(volume, filepath string, fn func(int64, int64, float64)) error {
|
||||
reader, length, err := GetMFTFile(volume)
|
||||
reader, length, err := GetMFTFileReader(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
out, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
written, err := copyFiles(out, reader, length, fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if written != length {
|
||||
return fmt.Errorf("Write Not Ok,Should %d got %d", length, written)
|
||||
}
|
||||
@@ -46,69 +55,98 @@ func DumpMFTFile(volume, filepath string, fn func(int64, int64, float64)) error
|
||||
}
|
||||
|
||||
func GetMFTFile(volume string) (io.Reader, int64, error) {
|
||||
reader, length, err := GetMFTFileReader(volume)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return reader, length, nil
|
||||
}
|
||||
|
||||
func GetMFTFileReader(volume string) (io.ReadCloser, int64, error) {
|
||||
reader, length, _, err := openMFTFile(volume)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return reader, length, nil
|
||||
}
|
||||
|
||||
func openMFTFile(volume string) (io.ReadCloser, int64, int64, error) {
|
||||
if isWin {
|
||||
volume = `\\.\` + volume[:len(volume)-1]
|
||||
}
|
||||
in, err := os.Open(volume)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
success := false
|
||||
defer func() {
|
||||
if !success {
|
||||
in.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
bootSectorData := make([]byte, 512)
|
||||
_, err = io.ReadFull(in, bootSectorData)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("Unable to read boot sector: %v\n", err)
|
||||
return nil, 0, 0, fmt.Errorf("Unable to read boot sector: %v", err)
|
||||
}
|
||||
|
||||
bootSector, err := bootsect.Parse(bootSectorData)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("Unable to parse boot sector data: %v\n", err)
|
||||
return nil, 0, 0, fmt.Errorf("Unable to parse boot sector data: %v", err)
|
||||
}
|
||||
|
||||
if bootSector.OemId != supportedOemId {
|
||||
return nil, 0, fmt.Errorf("Unknown OemId (file system type) %q (expected %q)\n", bootSector.OemId, supportedOemId)
|
||||
return nil, 0, 0, fmt.Errorf("Unknown OemId (file system type) %q (expected %q)", bootSector.OemId, supportedOemId)
|
||||
}
|
||||
|
||||
bytesPerCluster := bootSector.BytesPerSector * bootSector.SectorsPerCluster
|
||||
if bytesPerCluster <= 0 {
|
||||
return nil, 0, 0, fmt.Errorf("Invalid bytes per cluster %d", bytesPerCluster)
|
||||
}
|
||||
mftPosInBytes := int64(bootSector.MftClusterNumber) * int64(bytesPerCluster)
|
||||
|
||||
_, err = in.Seek(mftPosInBytes, 0)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("Unable to seek to MFT position: %v\n", err)
|
||||
return nil, 0, 0, fmt.Errorf("Unable to seek to MFT position: %v", err)
|
||||
}
|
||||
|
||||
mftSizeInBytes := bootSector.FileRecordSegmentSizeInBytes
|
||||
if mftSizeInBytes <= 0 {
|
||||
return nil, 0, 0, fmt.Errorf("Invalid MFT record size %d", mftSizeInBytes)
|
||||
}
|
||||
mftData := make([]byte, mftSizeInBytes)
|
||||
_, err = io.ReadFull(in, mftData)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("Unable to read $MFT record: %v\n", err)
|
||||
return nil, 0, 0, fmt.Errorf("Unable to read $MFT record: %v", err)
|
||||
}
|
||||
|
||||
record, err := ParseRecord(mftData)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("Unable to parse $MFT record: %v\n", err)
|
||||
return nil, 0, 0, fmt.Errorf("Unable to parse $MFT record: %v", err)
|
||||
}
|
||||
|
||||
dataAttributes := record.FindAttributes(AttributeTypeData)
|
||||
if len(dataAttributes) == 0 {
|
||||
return nil, 0, fmt.Errorf("No $DATA attribute found in $MFT record\n")
|
||||
return nil, 0, 0, fmt.Errorf("No $DATA attribute found in $MFT record")
|
||||
}
|
||||
|
||||
if len(dataAttributes) > 1 {
|
||||
return nil, 0, fmt.Errorf("More than 1 $DATA attribute found in $MFT record\n")
|
||||
return nil, 0, 0, fmt.Errorf("More than 1 $DATA attribute found in $MFT record")
|
||||
}
|
||||
|
||||
dataAttribute := dataAttributes[0]
|
||||
if dataAttribute.Resident {
|
||||
return nil, 0, fmt.Errorf("Don't know how to handle resident $DATA attribute in $MFT record\n")
|
||||
return nil, 0, 0, fmt.Errorf("Don't know how to handle resident $DATA attribute in $MFT record")
|
||||
}
|
||||
|
||||
dataRuns, err := ParseDataRuns(dataAttribute.Data)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("Unable to parse dataruns in $MFT $DATA record: %v\n", err)
|
||||
return nil, 0, 0, fmt.Errorf("Unable to parse dataruns in $MFT $DATA record: %v", err)
|
||||
}
|
||||
|
||||
if len(dataRuns) == 0 {
|
||||
return nil, 0, fmt.Errorf("No dataruns found in $MFT $DATA record\n")
|
||||
return nil, 0, 0, fmt.Errorf("No dataruns found in $MFT $DATA record")
|
||||
}
|
||||
|
||||
fragments := DataRunsToFragments(dataRuns, bytesPerCluster)
|
||||
@@ -117,47 +155,24 @@ func GetMFTFile(volume string) (io.Reader, int64, error) {
|
||||
totalLength += int64(frag.Length)
|
||||
}
|
||||
|
||||
return fragment.NewReader(in, fragments), totalLength, nil
|
||||
success = true
|
||||
return fragment.NewReader(in, fragments), totalLength, int64(mftSizeInBytes), nil
|
||||
}
|
||||
|
||||
func copyBytes(dst io.Writer, src io.Reader, totalLength int64) (written int64, err error) {
|
||||
buf := make([]byte, 1024*1024)
|
||||
|
||||
// Below copied from io.copyBuffer (https://golang.org/src/io/io.go?s=12796:12856#L380)
|
||||
for {
|
||||
|
||||
nr, er := src.Read(buf)
|
||||
if nr > 0 {
|
||||
nw, ew := dst.Write(buf[0:nr])
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
}
|
||||
if er != nil {
|
||||
if er != io.EOF {
|
||||
err = er
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return written, err
|
||||
return copyWithProgress(dst, src, totalLength, nil)
|
||||
}
|
||||
|
||||
func copyFiles(dst io.Writer, src io.Reader, totalLength int64, fn func(int64, int64, float64)) (written int64, err error) {
|
||||
return copyWithProgress(dst, src, totalLength, fn)
|
||||
}
|
||||
|
||||
func copyWithProgress(dst io.Writer, src io.Reader, totalLength int64, fn func(int64, int64, float64)) (written int64, err error) {
|
||||
buf := make([]byte, 1024*1024)
|
||||
onePercent := float64(written) / float64(totalLength) * float64(100.0)
|
||||
|
||||
// Below copied from io.copyBuffer (https://golang.org/src/io/io.go?s=12796:12856#L380)
|
||||
for {
|
||||
fn(written, totalLength, onePercent)
|
||||
reportCopyProgress(fn, written, totalLength)
|
||||
nr, er := src.Read(buf)
|
||||
if nr > 0 {
|
||||
nw, ew := dst.Write(buf[0:nr])
|
||||
@@ -180,6 +195,17 @@ func copyFiles(dst io.Writer, src io.Reader, totalLength int64, fn func(int64, i
|
||||
break
|
||||
}
|
||||
}
|
||||
fn(written, totalLength, onePercent)
|
||||
reportCopyProgress(fn, written, totalLength)
|
||||
return written, err
|
||||
}
|
||||
|
||||
func reportCopyProgress(fn func(int64, int64, float64), written int64, totalLength int64) {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
if totalLength <= 0 {
|
||||
fn(written, totalLength, 100)
|
||||
return
|
||||
}
|
||||
fn(written, totalLength, float64(written)/float64(totalLength)*100)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user