完善 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:
@@ -0,0 +1,177 @@
|
||||
param(
|
||||
[switch]$Elevated,
|
||||
[string]$ResultFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||
|
||||
function Test-IsAdministrator {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
||||
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
function New-SmokeSource([string]$Kind) {
|
||||
switch ($Kind) {
|
||||
'mft' {
|
||||
@'
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"b612.me/wincmd/ntfs/mft"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r, n, err := mft.GetMFTFileReader(`C:\`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
got, err := io.ReadFull(r, buf)
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("mft_length=%d\n", n)
|
||||
fmt.Printf("mft_first_read=%d\n", got)
|
||||
fmt.Printf("mft_sig=%x\n", buf[:4])
|
||||
}
|
||||
'@
|
||||
}
|
||||
'usn' {
|
||||
@'
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"b612.me/wincmd/ntfs/usn"
|
||||
"b612.me/win32api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dir, err := os.MkdirTemp("", "wincmd-usn-admin-")
|
||||
if err != nil { panic(err) }
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
path := filepath.Join(dir, "admin-usn.txt")
|
||||
if err := os.WriteFile(path, []byte("admin smoke"), 0600); err != nil { panic(err) }
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil { panic(err) }
|
||||
defer f.Close()
|
||||
|
||||
var info syscall.ByHandleFileInformation
|
||||
if err := syscall.GetFileInformationByHandle(syscall.Handle(f.Fd()), &info); err != nil { panic(err) }
|
||||
|
||||
vol := filepath.VolumeName(path) + `\`
|
||||
vh, err := usn.CreateFile(`\\.\`+vol[:len(vol)-1], syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
|
||||
if err != nil { panic(err) }
|
||||
defer syscall.Close(vh)
|
||||
|
||||
id := win32api.DWORDLONG(uint64(info.FileIndexHigh)<<32 | uint64(info.FileIndexLow))
|
||||
fh, err := usn.OpenFileByIdWithfd(vh, id, syscall.O_RDONLY, win32api.FILE_ATTRIBUTE_NORMAL)
|
||||
if err != nil { panic(err) }
|
||||
defer syscall.Close(fh)
|
||||
|
||||
var info2 syscall.ByHandleFileInformation
|
||||
if err := syscall.GetFileInformationByHandle(fh, &info2); err != nil { panic(err) }
|
||||
|
||||
fmt.Printf("usn_id_ok=true\n")
|
||||
fmt.Printf("usn_idx_hi=%d\n", info2.FileIndexHigh)
|
||||
fmt.Printf("usn_idx_lo=%d\n", info2.FileIndexLow)
|
||||
}
|
||||
'@
|
||||
}
|
||||
default {
|
||||
throw "unknown smoke source kind: $Kind"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-GoSmoke([string]$Kind, [System.Collections.Generic.List[string]]$Lines) {
|
||||
$tmpGo = Join-Path $env:TEMP ("wincmd_ntfs_{0}_smoke.go" -f $Kind)
|
||||
try {
|
||||
Set-Content -Path $tmpGo -Value (New-SmokeSource $Kind) -Encoding utf8
|
||||
$output = & go run $tmpGo 2>&1 | Out-String
|
||||
$Lines.Add(("{0}_smoke_ok=true" -f $Kind))
|
||||
foreach ($line in ($output -split "`r?`n")) {
|
||||
if ($line -ne '') {
|
||||
$Lines.Add($line)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$Lines.Add(("{0}_smoke_ok=false" -f $Kind))
|
||||
$Lines.Add(("{0}_smoke_err={1}" -f $Kind, $_.Exception.Message))
|
||||
throw
|
||||
} finally {
|
||||
Remove-Item $tmpGo -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Write-Result([System.Collections.Generic.List[string]]$Lines, [string]$ResultFilePath) {
|
||||
if ($ResultFilePath) {
|
||||
Set-Content -Path $ResultFilePath -Value $Lines -Encoding utf8
|
||||
} else {
|
||||
foreach ($line in $Lines) {
|
||||
Write-Output $line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $Elevated -and -not (Test-IsAdministrator)) {
|
||||
if (-not $ResultFile) {
|
||||
$ResultFile = Join-Path $env:TEMP 'wincmd_ntfs_admin_smoke_result.txt'
|
||||
}
|
||||
Remove-Item $ResultFile -Force -ErrorAction SilentlyContinue
|
||||
$process = Start-Process pwsh.exe -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File',$PSCommandPath,'-Elevated','-ResultFile',$ResultFile -PassThru
|
||||
$process.WaitForExit()
|
||||
if (Test-Path $ResultFile) {
|
||||
Get-Content $ResultFile -Encoding utf8
|
||||
Remove-Item $ResultFile -Force -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
Write-Output 'result_file_missing'
|
||||
}
|
||||
exit $process.ExitCode
|
||||
}
|
||||
|
||||
Set-Location $repo
|
||||
$lines = New-Object 'System.Collections.Generic.List[string]'
|
||||
$lines.Add('admin=' + (Test-IsAdministrator))
|
||||
|
||||
$failed = $false
|
||||
try {
|
||||
& go test ./ntfs/mft -run '^$' *> $null
|
||||
$lines.Add('mft_pkg_ok=true')
|
||||
} catch {
|
||||
$failed = $true
|
||||
$lines.Add('mft_pkg_ok=false')
|
||||
$lines.Add('mft_pkg_err=' + $_.Exception.Message)
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-GoSmoke -Kind 'mft' -Lines $lines
|
||||
} catch {
|
||||
$failed = $true
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-GoSmoke -Kind 'usn' -Lines $lines
|
||||
} catch {
|
||||
$failed = $true
|
||||
}
|
||||
|
||||
Write-Result -Lines $lines -ResultFilePath $ResultFile
|
||||
|
||||
if ($failed) {
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
param(
|
||||
[string[]]$Packages = @('.', './ntfs/usn'),
|
||||
[switch]$KeepArtifacts
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$repo = Resolve-Path (Join-Path $PSScriptRoot '..')
|
||||
$tmpDir = Join-Path $repo '.tmp_test'
|
||||
New-Item -ItemType Directory -Force -Path $tmpDir | Out-Null
|
||||
|
||||
Set-Location $repo
|
||||
|
||||
foreach ($pkg in $Packages) {
|
||||
$name = ($pkg -replace '[^A-Za-z0-9_.-]', '_').Trim('_')
|
||||
if ([string]::IsNullOrWhiteSpace($name) -or $name -eq '.') {
|
||||
$name = 'root'
|
||||
}
|
||||
$exe = Join-Path $tmpDir ("$name.test.exe")
|
||||
|
||||
Write-Host "[build] $pkg -> $exe"
|
||||
go test $pkg -c -o $exe
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "go test -c failed for package $pkg"
|
||||
}
|
||||
|
||||
Write-Host "[run] $pkg"
|
||||
& $exe --% -test.v
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "test executable failed for package $pkg"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $KeepArtifacts) {
|
||||
Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Reference in New Issue
Block a user