mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
5.5 KiB
147 lines
5.5 KiB
#!/usr/bin/env pwsh |
|
# Crawls the nuget.org catalog and runs nugetfuzz.cs on every package id. |
|
# Resumable: page cursor + seen-id list live in ./crawl. Every call is recorded |
|
# in crawl/history.log; the full per-package log is kept only when the run failed. |
|
# |
|
# usage: ./nugetfuzz-all.ps1 [-MaxPages n] [-MaxPackages n] [-CacheCapMB n] |
|
# ponytail: sequential crawl; parallelize per-page if throughput matters |
|
|
|
[CmdletBinding()] |
|
param( |
|
[int]$MaxPages = 0, # 0 = all |
|
[int]$MaxPackages = 0, # 0 = unlimited |
|
[int]$CacheCapMB = 20480, |
|
[int]$TimeoutSeconds = 1800 |
|
) |
|
|
|
$ErrorActionPreference = 'Stop' |
|
Set-Location $PSScriptRoot |
|
|
|
$state = Join-Path $PSScriptRoot 'crawl' |
|
$logs = Join-Path $PSScriptRoot 'logs' |
|
$cache = Join-Path ([Environment]::GetFolderPath('UserProfile')) '.cache/nugetfuzz' |
|
# Findings from every package run land in one append-only ledger. Render the aggregate |
|
# at any time, while the sweep keeps running: |
|
# dotnet run nugetfuzz.cs -- --report crawl/findings.jsonl |
|
if (-not $env:NUGETFUZZ_LEDGER) { |
|
$env:NUGETFUZZ_LEDGER = Join-Path $state 'findings.jsonl' |
|
} |
|
# Required by the local OpenSSL configuration to validate the SHA-1 signed packages. |
|
if (-not $env:OPENSSL_ENABLE_SHA1_SIGNATURES) { |
|
$env:OPENSSL_ENABLE_SHA1_SIGNATURES = '1' |
|
} |
|
New-Item -ItemType Directory -Force -Path $state, $logs | Out-Null |
|
|
|
$seenFile = Join-Path $state 'seen-ids.txt' |
|
$cursorFile = Join-Path $state 'next-page.txt' |
|
$historyFile = Join-Path $state 'history.log' |
|
|
|
$seen = @{} |
|
if (Test-Path $seenFile) { |
|
foreach ($id in Get-Content $seenFile) { $seen[$id] = $true } |
|
} |
|
|
|
# Disk guard. Evicts least-recently-used <id>/<version> directories down to 80% of the |
|
# cap rather than wiping the cache wholesale: the cache doubles as the corpus decompdiff |
|
# runs against, and a wipe throws away packages that are expensive to re-download and |
|
# that other tools are pointed at. |
|
function Invoke-CachePrune { |
|
if (-not (Test-Path $cache)) { return } |
|
$dirSize = { param($d) (Get-ChildItem -LiteralPath $d -Recurse -File -EA SilentlyContinue |
|
| Measure-Object -Property Length -Sum).Sum / 1MB } |
|
$used = & $dirSize $cache |
|
if ($used -le $CacheCapMB) { return } |
|
$target = $CacheCapMB * 0.8 |
|
Write-Host ("cache {0:N0}MB over {1}MB, evicting least-recently-used down to {2:N0}MB" -f $used, $CacheCapMB, $target) |
|
# NTFS disables last-access-time updates by default, so on Windows this degrades to |
|
# least-recently-written, which for an extract-once cache means least recently added. |
|
$victims = Get-ChildItem -LiteralPath $cache -Directory -EA SilentlyContinue |
|
| ForEach-Object { Get-ChildItem -LiteralPath $_.FullName -Directory -EA SilentlyContinue } |
|
| Sort-Object LastAccessTime |
|
foreach ($dir in $victims) { |
|
if ($used -le $target) { break } |
|
$used -= & $dirSize $dir.FullName |
|
Remove-Item -LiteralPath $dir.FullName -Recurse -Force -EA SilentlyContinue |
|
$parent = $dir.Parent.FullName |
|
if (-not (Get-ChildItem -LiteralPath $parent -Force -EA SilentlyContinue)) { |
|
Remove-Item -LiteralPath $parent -Force -EA SilentlyContinue |
|
} |
|
} |
|
Write-Host ("cache now ~{0:N0}MB" -f $used) |
|
} |
|
|
|
try { |
|
$pages = (Invoke-RestMethod 'https://api.nuget.org/v3/catalog0/index.json').items |
|
| Sort-Object commitTimeStamp | ForEach-Object { $_.'@id' } |
|
} |
|
catch { |
|
Write-Error "failed to fetch catalog index: $_" |
|
exit 1 |
|
} |
|
|
|
$start = if (Test-Path $cursorFile) { [int](Get-Content $cursorFile -Raw).Trim() } else { 0 } |
|
$i = 0 |
|
$donePages = 0 |
|
$donePkgs = 0 |
|
:pages foreach ($page in $pages) { |
|
$i++ |
|
if ($i -le $start) { continue } |
|
if ($MaxPages -gt 0 -and $donePages -ge $MaxPages) { break } |
|
|
|
try { |
|
$ids = (Invoke-RestMethod $page).items |
|
| Where-Object { $_.'@type' -eq 'nuget:PackageDetails' } |
|
| ForEach-Object { $_.'nuget:id'.ToLowerInvariant() } |
|
| Sort-Object -Unique |
|
} |
|
catch { |
|
Write-Warning "failed to fetch $page, stopping (resume with cursor)" |
|
break |
|
} |
|
|
|
foreach ($id in $ids) { |
|
if ($seen[$id]) { continue } |
|
if ($MaxPackages -gt 0 -and $donePkgs -ge $MaxPackages) { break pages } |
|
$seen[$id] = $true |
|
Add-Content -LiteralPath $seenFile -Value $id |
|
|
|
$log = Join-Path $logs "$id.log" |
|
$errLog = "$log.err" |
|
$p = Start-Process dotnet -ArgumentList 'run', 'nugetfuzz.cs', '--', $id ` |
|
-WorkingDirectory $PSScriptRoot -NoNewWindow -PassThru ` |
|
-RedirectStandardOutput $log -RedirectStandardError $errLog |
|
if ($p.WaitForExit($TimeoutSeconds * 1000)) { |
|
$rc = $p.ExitCode |
|
} |
|
else { |
|
$p.Kill($true) |
|
$rc = 124 |
|
} |
|
# Start-Process cannot merge the two streams into one file; fold them afterwards. |
|
if ((Get-Item -LiteralPath $errLog -EA SilentlyContinue).Length) { |
|
Get-Content -LiteralPath $errLog -Raw | Add-Content -LiteralPath $log |
|
} |
|
Remove-Item -LiteralPath $errLog -Force -EA SilentlyContinue |
|
|
|
$summary = Select-String -LiteralPath $log -Pattern '\d+ assemblies.*' -EA SilentlyContinue |
|
| Select-Object -Last 1 | ForEach-Object { $_.Matches[0].Value } |
|
Add-Content -LiteralPath $historyFile -Value "$(Get-Date -Format o) $id exit=$rc $(if ($summary) { $summary } else { 'no-summary' })" |
|
if ($rc -eq 0) { |
|
Remove-Item -LiteralPath $log -Force -EA SilentlyContinue |
|
} |
|
else { |
|
Write-Host "FAILED ($rc): $id -> $log" |
|
} |
|
$donePkgs++ |
|
|
|
Invoke-CachePrune |
|
} |
|
Set-Content -LiteralPath $cursorFile -Value $i |
|
$donePages++ |
|
Write-Host "--- page $i done ($donePkgs packages this run) ---" |
|
} |
|
Write-Host "run complete: $donePkgs packages processed, failures kept in $logs" |
|
# Reaching here means the sweep ran to completion; a real failure throws (ErrorActionPreference |
|
# is Stop) and exits non-zero on its own. Without this the exit code trails whatever the last |
|
# cmdlet happened to set. |
|
exit 0
|
|
|