@ -1506,11 +1506,12 @@ metadata_summary() {
#operator help (the 'help' action; the no-args case shows show_usage only).
#Keep in sync with the ps1 payload's Show-PunkRuntimeHelp/Show-PunkRuntimeUsage.
show_usage() {
echo " Usage: $0 {fetch|list|use|run|platforms|help} "
echo " Usage: $0 {fetch|list|use|run|info| platforms|help} "
echo " fetch ?<name>? ?-platform <p>? download+verify a runtime from punkbin "
echo " list ?-remote? ?-platform <p>? installed (or server) runtimes + metadata "
echo " use <name> ?-platform <p>? select active / materialize -r<N> artifact "
echo " run ?args...? launch the active local-platform runtime "
echo " info <name-or-path> ?-platform <p>? identity report: embedded record vs sidecar toml "
echo " platforms ?-remote? local platform folders / platforms the server serves "
echo " help full help (options, env vars, examples) "
}
@ -1549,6 +1550,17 @@ show_help() {
echo " Resolution: PUNK_ACTIVE_RUNTIME env > active.toml > sole "
echo " installed candidate. Takes no -platform (foreign binaries are "
echo " not runnable here). "
echo " info <name-or-path> ?-platform <p>? "
echo " Identity report for a runtime file (G-117 schema v1): the record "
echo " EMBEDDED in its attached image and the sidecar toml side by side, "
echo " with disagreements flagged and the file's sha1 checked against "
echo " the sidecar. The embedded read uses a zip central-directory read "
echo " of the exe-appended archive (unzip) - the target is NEVER "
echo " executed, so it is safe on stray/untrusted binaries; without "
echo " unzip the printed fallback is a cooperative probe that EXECUTES "
echo " the target (only for binaries you already trust). Works for a "
echo " bare renamed copy with no sidecar (embedded-only report); "
echo " pre-v1 artifacts without an embedded record report sidecar-only. "
echo " platforms ?-remote? "
echo " List local platform folders under bin/runtime/. With -remote, "
echo " list the platforms the artifact server serves - read from the "
@ -2000,6 +2012,96 @@ case "$action" in
echo " Canonical platform names: 'help platforms' in the punk shell "
fi
;;
" info " )
#G-117: identity report - the sidecar toml and the record EMBEDDED in the
#artifact's attached image, side by side, disagreements flagged. Embedded
#read = zip central-directory read of the exe-appended archive via unzip
#(the target is NEVER executed - identifying an untrusted stray binary by
#running it is exactly what this action avoids). Hosts without unzip get
#the cooperative-probe fallback COMMAND printed instead (that one EXECUTES
#the target - only for binaries already trusted).
iname=" ${1:-} "
if [[ - z " $ i n a m e " ] ] ; t h e n
echo " usage: $0 info <runtime-name-or-path> ?-platform <p>? "
exit 1
fi
if [[ - f " $ i n a m e " ] ] ; t h e n
tfile=" $iname "
elif [[ -f " $archdir/$iname " ]]; then
tfile=" $archdir/$iname "
else
echo " punk-runtime info: no such file: '$iname' (checked as a path and in $archdir) "
exit 1
fi
tdir=$(dirname " $tfile " ); tbase=$(basename " $tfile " )
sidecar=" $tdir/$(rootname_of " $tbase" ).toml "
embedded=" "
haveunzip=0
if command - v u n z i p > / d e v / n u l l 2 > & 1 ; t h e n
haveunzip=1
embedded=$(unzip -p " $tfile " punkbin-artifact.toml 2 > /dev/null || true)
fi
localsha1=$(lcase " $(sha1_of " $tfile" ) " )
echo " ----------------------------------------------------------------------- "
echo " punk-runtime info: $tbase "
echo " file: $tfile "
[[ -n " $localsha1 " ]] && echo " sha1: $localsha1 (computed from the file) "
if [[ - f " $ s i d e c a r " ] ] ; t h e n
echo " sidecar: $sidecar "
else
echo " sidecar: (none beside the file - stray/renamed copy?) "
fi
if [[ $ h a v e u n z i p - e q 0 ] ] ; t h e n
echo " embedded: (cannot read - no 'unzip' on this host) "
echo " fallback - a cooperative probe that EXECUTES the target (only run "
echo " this against a binary you already trust): "
echo " echo 'catch {puts [read [open //zipfs:/app/punkbin-artifact.toml]]};exit' | $tfile "
elif [[ -z " $embedded " ]]; then
echo " embedded: (no punkbin-artifact.toml in the attached image - pre-v1 "
echo " artifact, or not a punkshell family runtime) "
fi
echo " ----------------------------------------------------------------------- "
if [[ - n " $ e m b e d d e d " | | - f " $ s i d e c a r " ] ] ; t h e n
printf ' %-20s % -36s %s\n' " field " " embedded " " sidecar "
disagreements=" "
for k in schema name class variant working_name revision target build_id origin packager project project_url license build_host_platform tcl_patchlevel sha1 size built; do
ev=" " ; sv=" "
if [[ - n " $ e m b e d d e d " ] ] ; t h e n
ev=$(printf '%s\n' " $embedded " | sed -n 's/^[ [:space:]]*'" $k " '[[:space:]]*=[[:space:]]*" \(.*\) " .*/\1/p' | head -n 1)
[[ -z " $ev " ]] && ev=$(printf '%s\n' " $embedded " | sed -n 's/^[ [:space:]]*'" $k " '[[:space:]]*=[[:space:]]*\([0-9][0-9]*\| true\| false\).*/\1/p' | head -n 1)
fi
if [[ - f " $ s i d e c a r " ] ] ; t h e n
sv=$(sed -n 's/^[ [:space:]]*'" $k " '[[:space:]]*=[[:space:]]*" \(.*\) " .*/\1/p' " $sidecar " | head -n 1)
[[ -z " $sv " ]] && sv=$(sed -n 's/^[ [:space:]]*'" $k " '[[:space:]]*=[[:space:]]*\([0-9][0-9]*\| true\| false\).*/\1/p' " $sidecar " | head -n 1)
fi
[[ -z " $ev " && -z " $sv " ]] && continue
printf ' %-20s % -36s %s\n' " $k " " ${ev:--} " " ${sv:--} "
#schema absent on one side is pre-v1 tolerance, not a disagreement
if [[ - n " $ e v " & & - n " $ s v " & & " $ e v " ! = " $ s v " ] ] ; t h e n
disagreements=" $disagreements $k "
fi
done
echo " ----------------------------------------------------------------------- "
if [[ - n " $ d i s a g r e e m e n t s " ] ] ; t h e n
echo " !DISAGREE embedded-vs-sidecar on:$disagreements "
echo " (the sidecar + sha1sums are the integrity authority; a mismatched pair "
echo " means this sidecar belongs to a different assembly than this binary) "
elif [[ -n " $embedded " && -f " $sidecar " ]]; then
echo " embedded and sidecar records agree on all shared fields "
fi
if [[ - f " $ s i d e c a r " & & - n " $ l o c a l s h a 1 " ] ] ; t h e n
scsha1=$(lcase " $(sed -n 's/ ^ [[:space:]]*sha1[[:space:]]*=[[:space:]]* " \(.*\)" .*/\1/p' " $sidecar" | head -n 1) " )
if [[ - n " $ s c s h a 1 " ] ] ; t h e n
if [[ " $ s c s h a 1 " == " $localsha1 " ]]; then
echo " sha1: file matches the sidecar record "
else
echo " !SHA1-MISMATCH: file does not match the sidecar record (modified or "
echo " different assembly; for a materialized working copy compare build_id) "
fi
fi
fi
fi
;;
" help " )
show_help
;;
@ -2401,11 +2503,12 @@ function Get-PunkRuntimeCandidates {
#operator help (the 'help' action; the no-args case shows the Usage block only).
#Keep in sync with the bash payload's show_help/show_usage.
function Show-PunkRuntimeUsage {
write-host " Usage: punk-runtime.cmd {fetch|list|use|run|platforms|help} "
write-host " Usage: punk-runtime.cmd {fetch|list|use|run|info| platforms|help} "
write-host " fetch ?<name>? ?-platform <p>? download+verify a runtime from punkbin "
write-host " list ?-remote? ?-platform <p>? installed (or server) runtimes + metadata "
write-host " use <name> ?-platform <p>? select active / materialize -r<N> artifact "
write-host " run ?args...? launch the active local-platform runtime "
write-host " info <name-or-path> ?-platform <p>? identity report: embedded record vs sidecar toml "
write-host " platforms ?-remote? local platform folders / platforms the server serves "
write-host " help full help (options, env vars, examples) "
}
@ -2444,6 +2547,16 @@ function Show-PunkRuntimeHelp {
write-host " Resolution: PUNK_ACTIVE_RUNTIME env > active.toml > sole "
write-host " installed candidate. Takes no -platform (foreign binaries are "
write-host " not runnable here). "
write-host " info <name-or-path> ?-platform <p>? "
write-host " Identity report for a runtime file (G-117 schema v1): the record "
write-host " EMBEDDED in its attached image and the sidecar toml side by side, "
write-host " with disagreements flagged and the file's sha1 checked against "
write-host " the sidecar. The embedded read uses a zip central-directory read "
write-host " of the exe-appended archive (.NET System.IO.Compression) - the "
write-host " target is NEVER executed, so it is safe on stray/untrusted "
write-host " binaries. Works for a bare renamed copy with no sidecar "
write-host " (embedded-only report); pre-v1 artifacts without an embedded "
write-host " record report sidecar-only. "
write-host " platforms ?-remote? "
write-host " List local platform folders under bin/runtime/. With -remote, "
write-host " list the platforms the artifact server serves - read from the "
@ -2588,6 +2701,102 @@ function Get-PunkRuntimeMetadataSummary {
}
return " [ " + ($parts -join " " ) + " ] "
}
#G-117 record parsing (schema-tolerant): quoted-string / bare integer / true| false
#value lines only; '#' comments and unknown fields are ignored by construction, and
#an absent schema line simply yields no 'schema' key (pre-v1 records keep working).
function Get-PunkRuntimeRecordFields {
param([string[]] $recordlines)
$fields = @{}
foreach ($line in $recordlines) {
$m = [regex]::Match($line, '^\ s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*" (.*) " \s*$')
if ($m.Success) {
if (-not $ f i e l d s . C o n t a i n s K e y ( $ m . G r o u p s [ 1 ] . V a l u e ) ) { $ f i e l d s [ $ m . G r o u p s [ 1 ] . V a l u e ] = $ m . G r o u p s [ 2 ] . V a l u e }
continue
}
$m = [regex]::Match($line, '^\ s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([0-9]+| true| false)\s*$')
if ($m.Success) {
if (-not $ f i e l d s . C o n t a i n s K e y ( $ m . G r o u p s [ 1 ] . V a l u e ) ) { $ f i e l d s [ $ m . G r o u p s [ 1 ] . V a l u e ] = $ m . G r o u p s [ 2 ] . V a l u e }
}
}
return $fields
}
#G-117 embedded record: zip CENTRAL-DIRECTORY read of the exe-appended archive via
#.NET System.IO.Compression (offsets resolve from the end-of-central-directory
#record, so the prepended executable data is no obstacle). The target is NEVER
#executed - identifying an untrusted stray binary by running it is exactly what an
#identity mechanism should avoid; the cooperative probe (piping a reader script
#into the EXECUTED target) is the documented fallback for hosts without a zip
#reader, with that execution caveat. Returns the record text, or $null when the
#archive has no punkbin-artifact.toml (pre-v1 artifact / not a family runtime).
function Get-PunkRuntimeEmbeddedRecord {
param([string] $Path)
#.NET's ZipArchive does NOT apply the self-extractor offset fixup Info-ZIP
#applies: an exe-appended archive's central-directory offsets are relative to
#the ZIP START, not the file start, and OpenRead on the exe throws. Locate the
#end-of-central-directory record ourselves, compute the prepend length (actual
#CD position minus recorded offset), copy the pure-zip tail to a temp file and
#let .NET read that (deflate/stored handling for free). Works for plain zips
#too (delta 0). No zip64 handling - family images are far below those limits.
try { Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop } catch { }
$fullpath = (Resolve-Path -LiteralPath $Path).ProviderPath
$tmpzip = $null
$zip = $null
try {
$fs = [System.IO.File]::OpenRead($fullpath)
try {
$flen = $fs.Length
if ($flen - l t 2 2 ) { r e t u r n $ n u l l }
#EOCD signature scan in the trailing 64KB+22 (max comment length)
$scanlen = [Math]::Min($flen, 65557 + 22)
$fs.Seek($flen - $scanlen, [System.IO.SeekOrigin]::Begin) | Out-Null
$buf = New-Object byte[] $scanlen
$read = 0
while ($read -lt $scanlen) {
$n = $fs.Read($buf, $read, $scanlen - $read)
if ($n - l e 0 ) { b r e a k }
$read += $n
}
$eocdrel = -1
for ($i = $read - 22; $i -ge 0; $i--) {
if ($buf[$i] - e q 0 x 5 0 - a n d $ b u f [ $ i + 1 ] - e q 0 x 4 b - a n d $ b u f [ $ i + 2 ] - e q 0 x 0 5 - a n d $ b u f [ $ i + 3 ] - e q 0 x 0 6 ) {
$eocdrel = $i
break
}
}
if ($eocdrel - l t 0 ) { r e t u r n $ n u l l }
$eocdabs = ($flen - $scanlen) + $eocdrel
$cdsize = [System.BitConverter]::ToUInt32($buf, $eocdrel + 12)
$cdoffset = [System.BitConverter]::ToUInt32($buf, $eocdrel + 16)
$zipstart = $eocdabs - $cdsize - $cdoffset
if ($zipstart - l t 0 ) { r e t u r n $ n u l l }
$tmpzip = [System.IO.Path]::GetTempFileName()
$out = [System.IO.File]::Open($tmpzip, [System.IO.FileMode]::Create)
try {
$fs.Seek($zipstart, [System.IO.SeekOrigin]::Begin) | Out-Null
$fs.CopyTo($out)
} finally {
$out.Dispose()
}
} finally {
$fs.Dispose()
}
$zip = [System.IO.Compression.ZipFile]::OpenRead($tmpzip)
foreach ($entry in $zip.Entries) {
if ($entry.FullName - e q ' p u n k b i n - a r t i f a c t . t o m l ' ) {
$sr = New-Object System.IO.StreamReader($entry.Open())
try { return $sr.ReadToEnd() } finally { $sr.Dispose() }
}
}
return $null
} catch {
return $null
} finally {
if ($zip) { $ z i p . D i s p o s e ( ) }
if ($tmpzip - a n d ( T e s t - P a t h - L i t e r a l P a t h $ t m p z i p ) ) {
Remove-Item -LiteralPath $tmpzip -Force -ErrorAction SilentlyContinue
}
}
}
function psmain {
[CmdletBinding()]
@ -2622,7 +2831,7 @@ function psmain {
$paramDictionary.Add('remote', $dynParam1)
$paramDictionary.Add('platform', $dynParam2)
return $paramDictionary
} elseif ($action -eq 'fetch' -or $action -eq 'use') {
} elseif ($action -eq 'fetch' -or $action -eq 'use' -or $action -eq 'info' ) {
#GetDynamicParamDictionary ParameterDefinitions
$parameterAttribute = [System.Management.Automation.ParameterAttribute]@{
ParameterSetName = " fetchruntime "
@ -2695,7 +2904,7 @@ function psmain {
'action' {
write-host " got action " $PSBoundParameters.action
Set-Variable -Name $_ -Value $PSBoundParameters." $_ "
$known_actions = @(" fetch " , " list " , " use " , " run " , " platforms " , " help " )
$known_actions = @(" fetch " , " list " , " use " , " run " , " info " , " platforms" , " help " )
if (-not($known_actions - c o n t a i n s $ a c t i o n ) ) {
write-host " action '$action' not understood. Known_actions: $known_actions "
exit 1
@ -3221,6 +3430,85 @@ function psmain {
}
}
}
'info' {
#G-117: identity report - the sidecar toml and the record EMBEDDED in
#the artifact's attached image, side by side, disagreements flagged.
#Embedded read = zip central-directory read (the target is NEVER
#executed; see Get-PunkRuntimeEmbeddedRecord). Keep output in parity
#with the bash payload's " info " action.
$arch = Resolve-PunkRuntimePlatform $PSBoundParameters[" platform " ]
$archfolder = Join-Path -Path $rtfolder -ChildPath " $arch "
$iname = " "
if ( $ P S B o u n d P a r a m e t e r s [ " r u n t i m e " ] . L e n g t h ) { $ i n a m e = $ P S B o u n d P a r a m e t e r s [ " r u n t i m e " ] }
if (-not $ i n a m e ) {
write-host " usage: punk-runtime.cmd info <runtime-name-or-path> ?-platform <p>? "
exit 1
}
$tfile = " "
if (Test-Path - P a t h $ i n a m e - P a t h T y p e L e a f ) {
$tfile = (Resolve-Path -LiteralPath $iname).ProviderPath
} elseif (Test-Path -Path (Join-Path -Path $archfolder -ChildPath $iname) -PathType Leaf) {
$tfile = (Resolve-Path -LiteralPath (Join-Path -Path $archfolder -ChildPath $iname)).ProviderPath
} else {
write-host " punk-runtime info: no such file: '$iname' (checked as a path and in $archfolder) "
exit 1
}
$tbase = Split-Path -Leaf $tfile
$tdir = Split-Path -Parent $tfile
$sidecarfile = Join-Path -Path $tdir -ChildPath ((Get-PunkRuntimeRootName $tbase) + " .toml " )
$embedtext = Get-PunkRuntimeEmbeddedRecord $tfile
$localsha1 = Get-PunkFileSha1 -Path $tfile
write-host " ----------------------------------------------------------------------- "
write-host " punk-runtime info: $tbase "
write-host " file: $tfile "
write-host " sha1: $localsha1 (computed from the file) "
$havesidecar = Test-Path -Path $sidecarfile -PathType Leaf
if ($havesidecar) {
write-host " sidecar: $sidecarfile "
} else {
write-host " sidecar: (none beside the file - stray/renamed copy?) "
}
if ($null - e q $ e m b e d t e x t ) {
write-host " embedded: (no punkbin-artifact.toml in the attached image - pre-v1 "
write-host " artifact, or not a punkshell family runtime) "
}
write-host " ----------------------------------------------------------------------- "
if (($null - n e $ e m b e d t e x t ) - o r $ h a v e s i d e c a r ) {
$efields = @{}
if ($null - n e $ e m b e d t e x t ) { $ e f i e l d s = G e t - P u n k R u n t i m e R e c o r d F i e l d s ( $ e m b e d t e x t - s p l i t " ` r ? ` n " ) }
$sfields = @{}
if ($havesidecar) { $ s f i e l d s = G e t - P u n k R u n t i m e R e c o r d F i e l d s ( [ s t r i n g [ ] ] ( G e t - C o n t e n t - P a t h $ s i d e c a r f i l e ) ) }
write-host (" {0,-20} {1,-36} {2} " -f " field " , " embedded " , " sidecar " )
$disagreements = @()
foreach ($k in @('schema','name','class','variant','working_name','revision','target','build_id','origin','packager','project','project_url','license','build_host_platform','tcl_patchlevel','sha1','size','built')) {
$ev = " " ; $sv = " "
if ($efields.ContainsKey($k)) { $ e v = $ e f i e l d s [ $ k ] }
if ($sfields.ContainsKey($k)) { $ s v = $ s f i e l d s [ $ k ] }
if (($ev - e q " " ) - a n d ( $ s v - e q " " ) ) { c o n t i n u e }
$evd = " - " ; if ($ev -ne " " ) { $evd = $ev }
$svd = " - " ; if ($sv -ne " " ) { $svd = $sv }
write-host (" {0,-20} {1,-36} {2} " -f $k, $evd, $svd)
#schema absent on one side is pre-v1 tolerance, not a disagreement
if (($ev - n e " " ) - a n d ( $ s v - n e " " ) - a n d ( $ e v - c n e $ s v ) ) { $ d i s a g r e e m e n t s + = $ k }
}
write-host " ----------------------------------------------------------------------- "
if ($disagreements.Count) {
write-host " !DISAGREE embedded-vs-sidecar on: $($disagreements -join ' ') "
write-host " (the sidecar + sha1sums are the integrity authority; a mismatched pair "
write-host " means this sidecar belongs to a different assembly than this binary) "
} elseif (($null -ne $embedtext) -and $havesidecar) {
write-host " embedded and sidecar records agree on all shared fields "
}
if ($havesidecar - a n d $ s f i e l d s . C o n t a i n s K e y ( ' s h a 1 ' ) ) {
if ($sfields['sha1'] - e q $ l o c a l s h a 1 ) {
write-host " sha1: file matches the sidecar record "
} else {
write-host " !SHA1-MISMATCH: file does not match the sidecar record (modified or "
write-host " different assembly; for a materialized working copy compare build_id) "
}
}
}
}
'platforms' {
#enumerate platform folders: local (bin/runtime/*) and, with
#-remote, the server's platforms.txt discovery manifest (raw-file