- 新增自启动幂等配置、统一错误语义、进程等待和进程树终止能力 - 增强服务生命周期管理,支持等待状态、重启、幂等创建和配置更新 - 新增 NTFS 卷索引、文件 ID 解析、文件遍历、USN 变更监听和 bookmark 持久化 - 修复 NTFS boot sector、fragment、MFT、USN 解析边界和路径重建问题 - 补充权限、进程、服务、NTFS 解析和工作流回归测试 - 增加 Windows 测试脚本和管理员 NTFS smoke 验证脚本 - 升级 Go 兼容版本到 1.18,并更新 stario、win32api 及相关间接依赖
178 lines
4.6 KiB
PowerShell
178 lines
4.6 KiB
PowerShell
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
|
|
}
|