Browse Source

make.tcl and scriptwrap fixes

master
Julian Noble 6 months ago
parent
commit
6b7cfa1843
  1. 5
      .fossil-settings/ignore-glob
  2. 10
      .gitignore
  3. 636
      bin/getzig.cmd
  4. 973
      bin/runtime.cmd
  5. 1338
      bin/tclargs.cmd
  6. 630
      getpunk.cmd
  7. 4
      src/lib/app-punk/pkgIndex.tcl
  8. 2
      src/lib/app-punkshell/pkgIndex.tcl
  9. 296
      src/lib/app-punkshell/punkshell.tcl
  10. 5
      src/lib/app-shellspy/pkgIndex.tcl
  11. 2449
      src/lib/app-shellspy/shellspy.tcl
  12. 7
      src/make.tcl
  13. 13
      src/modules/flagfilter-999999.0a1.0.tm
  14. 3
      src/modules/flagfilter-buildversion.txt
  15. 5
      src/modules/punk/char-999999.0a1.0.tm
  16. 621
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
  17. 317
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell1.cmd
  18. 346
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell2.cmd
  19. 240
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell3.cmd
  20. 680
      src/modules/punk/mix/templates/utility/scriptappwrappers/multishell4.cmd
  21. 2
      src/modules/punk/repl-999999.0a1.0.tm
  22. 44
      src/modules/punk/zip-999999.0a1.0.tm
  23. 2
      src/modules/shellrun-0.1.1.tm
  24. 93
      src/project_layouts/custom/_project/punk.basic/src/make.tcl
  25. 1
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config
  26. 131
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/zzzload-0.1.0.tm
  27. 93
      src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl
  28. 1
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/include_modules.config
  29. 131
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/zzzload-0.1.0.tm
  30. 93
      src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl
  31. 6
      src/runtime/mapvfs.config
  32. 2
      src/scriptapps/getpunk_wrap.toml
  33. 1
      src/scriptapps/getzig.ps1
  34. 18
      src/scriptapps/getzig_original.polyglot
  35. 2
      src/scriptapps/getzig_wrap.toml
  36. 68
      src/scriptapps/runtime.bash
  37. 296
      src/scriptapps/runtime.ps1
  38. 4
      src/scriptapps/runtime_wrap.toml
  39. 9
      src/scriptapps/tclargs.tcl
  40. 15
      src/scriptapps/tclargs_wrap.toml
  41. 2048
      src/vendormodules/www-2.8.tm
  42. 83
      src/vendormodules/www/digest-2.1.tm
  43. 1551
      src/vendormodules/www/http2-1.1.tm
  44. 13
      src/vendormodules/www/license.terms
  45. 826
      src/vendormodules/www/proxypac-2.1.tm
  46. 156
      src/vendormodules/www/socks-1.0.tm
  47. 306
      src/vendormodules/www/websocket-1.1.tm
  48. 5
      src/vfs/_config/punk_main.tcl
  49. 14
      src/vfs/_vfscommon.vfs/doc/bogus.tcl
  50. 4
      src/vfs/_vfscommon.vfs/lib/app-punk/pkgIndex.tcl
  51. 2
      src/vfs/_vfscommon.vfs/lib/app-punkshell/pkgIndex.tcl
  52. 296
      src/vfs/_vfscommon.vfs/lib/app-punkshell/punkshell.tcl
  53. 5
      src/vfs/_vfscommon.vfs/lib/app-shellspy/pkgIndex.tcl
  54. 2449
      src/vfs/_vfscommon.vfs/lib/app-shellspy/shellspy.tcl
  55. 2718
      src/vfs/_vfscommon.vfs/modules/flagfilter-0.3.1.tm
  56. 5
      src/vfs/_vfscommon.vfs/modules/punk/char-0.1.0.tm
  57. 636
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd
  58. 317
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell1.cmd
  59. 346
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell2.cmd
  60. 240
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell3.cmd
  61. 680
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell4.cmd
  62. 2
      src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.2.tm
  63. 44
      src/vfs/_vfscommon.vfs/modules/punk/zip-0.1.1.tm
  64. 2
      src/vfs/_vfscommon.vfs/modules/shellrun-0.1.1.tm
  65. 2048
      src/vfs/_vfscommon.vfs/modules/www-2.8.tm
  66. 83
      src/vfs/_vfscommon.vfs/modules/www/digest-2.1.tm
  67. 1551
      src/vfs/_vfscommon.vfs/modules/www/http2-1.1.tm
  68. 13
      src/vfs/_vfscommon.vfs/modules/www/license.terms
  69. 826
      src/vfs/_vfscommon.vfs/modules/www/proxypac-2.1.tm
  70. 156
      src/vfs/_vfscommon.vfs/modules/www/socks-1.0.tm
  71. 306
      src/vfs/_vfscommon.vfs/modules/www/websocket-1.1.tm
  72. 381
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/critcl-rt.tcl
  73. 1
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/license.terms
  74. BIN
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/linux-x86_64/tcllibc.so
  75. 2
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/pkgIndex.tcl
  76. 21
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/teapot.txt
  77. 381
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/critcl-rt.tcl
  78. 1
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/license.terms
  79. BIN
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/linux-x86_64/tcllibc.so
  80. 2
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/pkgIndex.tcl
  81. 21
      src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/teapot.txt
  82. 16
      src/vfs/punk9linux.vfs/lib_tcl9/tcltls/pkgIndex.tcl
  83. BIN
      src/vfs/punk9linux.vfs/lib_tcl9/tcltls/tcltls.so
  84. 398
      src/vfs/punk9linux.vfs/lib_tcl9/tcltls/tls.tcl
  85. BIN
      src/vfs/punk9linux.vfs/lib_tcl9/tdom/libtcl9tdom0.9.6.so
  86. BIN
      src/vfs/punk9linux.vfs/lib_tcl9/tdom/libtdomstub.a
  87. 12
      src/vfs/punk9linux.vfs/lib_tcl9/tdom/pkgIndex.tcl
  88. 1101
      src/vfs/punk9linux.vfs/lib_tcl9/tdom/tdom.tcl
  89. BIN
      src/vfs/punk9linux.vfs/lib_tcl9/thread3.0.2/libtcl9thread3.0.2.so
  90. 55
      src/vfs/punk9linux.vfs/lib_tcl9/thread3.0.2/pkgIndex.tcl
  91. BIN
      src/vfs/punk9linux.vfs/lib_tcl9/thread3.0.2/thread3.0.2/libtcl9thread3.0.2.so
  92. 55
      src/vfs/punk9linux.vfs/lib_tcl9/thread3.0.2/thread3.0.2/pkgIndex.tcl
  93. BIN
      src/vfs/punk9linux.vfs/modules_tcl9/Thread-3.0b1.tm
  94. BIN
      src/vfs/punk9linux.vfs/modules_tcl9/Thread/platform/linux_x86_64_tcl9-3.0b1.tm
  95. 16
      src/vfs/punk9win_for_tkruntime.vfs/lib_tcl9/tcltls1.7.23/pkgIndex.tcl
  96. BIN
      src/vfs/punk9win_for_tkruntime.vfs/lib_tcl9/tcltls1.7.23/tcltls.dll
  97. 398
      src/vfs/punk9win_for_tkruntime.vfs/lib_tcl9/tcltls1.7.23/tls.tcl

5
.fossil-settings/ignore-glob

@ -26,8 +26,9 @@ doc
test*
#Built tclkits (if any)
punk*.exe
tcl*.exe
#punk*.exe
#tcl*.exe
*.exe
#miscellaneous editor files etc
*.swp

10
.gitignore vendored

@ -1,5 +1,7 @@
/bin/
#/bin/
/bin/*
!/bin/*.cmd
/lib/
#The directories for compiled/built Tcl modules and libraries
/modules/
@ -12,6 +14,7 @@
#Temporary files e.g from tests
/tmp/
**/_old.*
/logs/
**/_aside/
scratch*
@ -38,8 +41,9 @@ scratch*
!/src/runtime/mapvfs.config
#Built tclkits (if any)
punk*.exe
tcl*.exe
#punk*.exe
#tcl*.exe
*.exe
#ignore fossil database files (but keep .fossil-settings and .fossil-custom in repository even if fossil not being used at your site)
_FOSSIL_

636
bin/getzig.cmd

@ -1,4 +1,4 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
@ -16,6 +16,70 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@rem -------------------------------------------------------------------------------------------------------------------------------
@rem return from endlocal macro - courtesy of jeb
@rem This allows return of values containing special characters from subroutines
@rem https://stackoverflow.com/questions/3262287/make-an-environment-variable-survive-endlocal/8257951#8257951
@rem -------------------------------------------------------------------------------------------------------------------------------
@setlocal DisableDelayedExpansion
@echo off
%= 2 blank lines after next are required =%
set LF=^
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
%= I use EDE for EnableDelayeExpansion and DDE for DisableDelayedExpansion =%
set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
setlocal EnableDelayedExpansion%\n%
%= Take all variable names into the varName array =%%\n%
set varName_count=0%\n%
for %%C in (!args!) do set "varName[!varName_count!]=%%~C" ^& set /a varName_count+=1%\n%
%= Build one variable with a list of set statements for each variable delimited by newlines =%%\n%
%= The lists looks like --> set result1=myContent\n"set result1=myContent1"\nset result2=content2\nset result2=content2\n =%%\n%
%= Each result exists two times, the first for the case returning to DDE, the second for EDE =%%\n%
%= The correct line will be detected by the (missing) enclosing quotes =%%\n%
set "retContent=1!LF!"%\n%
for /L %%n in (0 1 !varName_count!) do (%\n%
for /F "delims=" %%C in ("!varName[%%n]!") DO (%\n%
set "content=!%%C!"%\n%
set "retContent=!retContent!"set !varName[%%n]!=!content!"!LF!"%\n%
if defined content (%\n%
%= This complex block is only for replacing '!' with '^!' =%%\n%
%= First replacing '"'->'""q' '^'->'^^' =%%\n%
set ^"content_EDE=!content:"=""q!"%\n%
set "content_EDE=!content_EDE:^=^^!"%\n%
%= Now it's poosible to use CALL SET and replace '!'->'""e!' =%%\n%
call set "content_EDE=%%content_EDE:^!=""e^!%%"%\n%
%= Now it's possible to replace '""e' to '^', this is effectivly '!' -> '^!' =%%\n%
set "content_EDE=!content_EDE:""e=^!"%\n%
%= Now restore the quotes =%%\n%
set ^"content_EDE=!content_EDE:""q="!"%\n%
) ELSE set "content_EDE="%\n%
set "retContent=!retContent!set "!varName[%%n]!=!content_EDE!"!LF!"%\n%
)%\n%
)%\n%
%= Now return all variables from retContent over the barrier =%%\n%
for /F "delims=" %%V in ("!retContent!") DO (%\n%
%= Only the first line can contain a single 1 =%%\n%
if "%%V"=="1" (%\n%
%= We need to call endlocal twice, as there is one more setlocal in the macro itself =%%\n%
endlocal%\n%
endlocal%\n%
) ELSE (%\n%
%= This is true in EDE =%%\n%
if "!"=="" (%\n%
if %%V==%%~V (%\n%
%%V !%\n%
)%\n%
) ELSE IF not %%V==%%~V (%\n%
%%~V%\n%
)%\n%
)%\n%
)%\n%
) else set args="
@rem -------------------------------------------------------------------------------------------------------------------------------
@SETLOCAL EnableExtensions EnableDelayedExpansion
@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: dev scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@ -24,7 +88,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________"
@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@ -32,7 +95,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM Supporting more explicit oses than those listed may also require script padding adjustment
: <<nextshell_start>>
@SET "nextshellpath[win32___________]=pwsh____________________________________________________________"
@SET "nextshellpath[win32___________]=pwsh -nop -nol -ExecutionPolicy bypass -c_______________________"
@SET "nextshelltype[win32___________]=pwsh____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env bash_______________________________________________"
@SET "nextshelltype[dragonflybsd____]=bash____________"
@ -51,8 +114,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: <<asadmin_start>>
@SET "asadmin=0"
: <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@REM @ECHO selected_shelltype %selected_shelltype%
@CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed
@ -73,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- deck scriptwrap.checkfile filepath
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@ -83,12 +144,13 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@SET "winpath=%~dp0"
@SET "winpath=%~dp0" %= e.g c:\punkshell\bin\ %=
@SET "fname=%~nx0"
@SET "scriptrootname=%~dp0%~n0" %= e.g c:\punkshell\bin\runtime (full path without extension) unavailable after shift, so store it =%
@REM @ECHO fname %fname%
@REM @ECHO winpath %winpath%
@REM @ECHO commandlineascalled %0
@ -123,17 +185,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ -178,25 +229,101 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=pwsh
)
@set argCount=30
@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
@set tmpfile_base=%TEMP%\punkbatch_params
@call :getUniqueFile %tmpfile_base% ".txt" paramfile
@echo %paramfile%
%= NOTE when we loop like this using the percent-n args, we lose unquoted separators such as comma and semicolon %=
@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
@rem outer loop required to redirect all rem lines at once to file
@for %%x in (1) do @(
@for /L %%f in (1,1,%argCount%) do @(
@set "argnum=%%~nf"
@set "a1=%%1"
@rem @set "argname=%%!argnum!"
@rem @echo argname: !argname!
@call :rem_output !argnum! !a1!
@shift
)
) > %paramfile%
@echo off
@set "newcommandline= "
@(set target=cmd_pwsh)
@if "%target%"=="cmd_pwsh" (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
@REM @echo ######### %%L
@rem call :buildcmdline newcommandline param "{" "}"
@rem call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell ... -c %=
call :buildcmdline newcommandline param %= cmd.exe /c powershell ... -f %=
@rem @echo .
)
) ELSE (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
call :buildcmdline newcommandline param
)
)
@REM padding
SETLOCAL EnableDelayedExpansion
@echo off
@IF EXIST %paramfile% (
@DEL /F /Q %paramfile%
)
@IF EXIST %paramfile% (
echo failed to delete %paramfile%
cat %paramfile%
)
@REM @SET "squoted_args="
@REM @for %%a in (%*) do @(
@REM set "v=%%a"
@REM set "v=!v:'=''!"
@REM SET "squoted_args=!squoted_args!'!v!' "
@REM )
@REM @SET "squoted_args=%squoted_args:~0,-1%"
@REM @ECHO %squoted_args%
@IF "!selected_shelltype_trimmed!"=="pwsh" (
REM pwsh vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
REM when run without cmd.exe - pwsh will receive the semicolon (for cmd.exe unquoted semicolon and comma are separators that aren't seen in positional arguments)
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted 2>NUL; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted
cmd /c pwsh -nop -nol -ExecutionPolicy bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
REM TODO prompt user with option to call script to install pwsh using winget
REM powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist%
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!selected_shelltype_trimmed!"=="powershell" (
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist%
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
IF "!selected_shelltype_trimmed!"=="wslbash" (
@ -211,7 +338,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
@ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
%selected_shellpath_trimmed% "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@ -222,9 +349,145 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
@REM padding
@REM padding
@REM padding
%= ---------------------------------------------------------------------- =%
@rem courtesy of dbenham
:: Example usage
@rem call :getUniqueFile "d:\test\myFile" ".txt" myFile
@rem echo myFile="%myFile%"
:getUniqueFile baseName extension rtnVar
setlocal
:getUniqueFileLoop
for /f "skip=1" %%A in ('wmic os get localDateTime') do for %%B in (%%A) do set "rtn=%~1_%%B%~2"
if exist "%rtn%" (
goto :getUniqueFileLoop
) else (
2>nul >nul (9>"%rtn%" timeout /nobreak 1) || goto :getUniqueFileLoop
)
endlocal & set "%~3=%rtn%"
exit /b
%= ---------------------------------------------------------------------- =%
@REM padding
:buildcmdline cmdlinevar paramvar wrapA wrapB
%= quoting for cmd.exe /c pwsh -nop !args! =%
@SETLOCAL EnableDelayedExpansion
@REM @echo =====================
set "pval=!%~2:*#=!"
set "pval=!pval:~0,-2!"
@REM set "pval=!pval:~0,-1!"
set "wrapa=%~3"
set "wrapb=%~4"
@call :strlen pval slen
@rem @echo strlen: !slen!
if "!slen!"=="0" (
goto :eof
)
@set /A n = !slen! - 1
@(set str=)
@set "dq=""
@set "bang=^!"
@(set carat=^^)
@for /l %%i in (0,1,!n!) do @(
(set c=!pval:~%%i,1!)
if "!c!"=="|" (
set "ch=^^!pval:~%%i,1!"
) ELSE IF "!c!"=="(" (
set "ch=^("
) ELSE if "!c!"==")" (
set "ch=^)"
) ELSE if "!c!"=="&" (
set "ch=^^&"
) ELSE if "!c!"=="!dq!" (
set "ch=^""
) ELSE if "!c!"=="!bang!" (
@rem - double caret - first for initial parsing, second to ensure remains escaped during delayed expansion phase
@rem - REVIEW
set "ch=^^!bang!"
) ELSE if "!c!"=="^carat" (
set "ch=^^^^"
) ELSE if "!c!"=="'" (
set "ch=''"
) ELSE (
set "ch=!c!"
)
@rem @echo - !ch!
set "str=!str!!ch!"
)
echo +++++ %~1 = !%1! !str!
set "%~1=!%1! !wrapa!!str!!wrapb!"
@rem old method of return - failes to preserve exclamation marks
@rem for /f "delims=" %%A in (""!str!"") do endlocal & set "%~1=!%1! '%%~A'"
@rem macro method of endlocal return - preserving !val!
@echo off
%endlocal% %~1
@exit /b
:rem_output
@rem ---------------------------------------------
@rem rem_output is called for each n in the number of args we process - as we don't have a non-destructive way to count args whilst accepting special chars
@rem we therefore need a way to stop processing on the last received arg so we don't write argCount entries to param.txt if less are received
@rem see 'disappearing quotes' technique
@rem https://stackoverflow.com/questions/4643376/how-to-split-double-quoted-line-into-multiple-lines-in-windows-batch-file/4645113#4645113
@rem and
@rem https://groups.google.com/g/alt.msdos.batch.nt/c/J71F17Bve9Y (sponge belly)
@echo off
setlocal enableextensions disabledelayedexpansion
set "param1=%~2"
rem do must not be indented
for %%^" in ("") ^
do if not defined param1 set %%~"param1=%2%%~"
if not defined param1 goto :eof
endlocal
@rem ---------------------------------------------
@PROMPT @
@echo on
rem %1 #%2#
@exit /b
@REM courtesy of: https://stackoverflow.com/users/463115/jeb
:strlen stringVar returnVar
@(
setlocal EnableDelayedExpansion
@SET "rtrn=%~2"
(set^ tmp=!%~1!)
@rem @echo jjjjj !tmp!
@if defined tmp (
set "len=1"
@for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do @(
@if "!tmp:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "tmp=!tmp:~%%P!"
)
)
) ELSE (
set len=0
)
)
@(
endlocal
@IF "%~2" neq "" (
@SET "%rtrn%=%len%"
) ELSE (
@ECHO :strlen result: %len%
)
exit /b
)
:getWslPath
@SETLOCAL
@SET "_path=%~p1"
@ -280,7 +543,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@ -295,13 +558,12 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -327,6 +589,10 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringContains
@REM usage: @CALL:stringContains string needle returnvarname
@SETLOCAL
@ -348,7 +614,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
:stringToUpper strvar returnvar
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@ -384,6 +650,11 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@ -442,12 +713,104 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
# -- i.e it is a polyglot file.
# -- The specific layout including some lines that appear just as comments is quite sensitive to change.
# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline.
# -- e.g ./filename.polypunk.cmd in sh or bash
# -- e.g tclsh filename.cmd
# -- e.g ./scriptname.cmd in sh or zsh or bash
# -- e.g tclsh scriptname.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
#---------------------------------------------------------------------
#divert to configured nextshell
package require platform
set plat_full [platform::generic]
set plat [lindex [split $plat_full -] 0]
#may be old tcl - not assuming readFile available
set fd [open [info script] r]
set scriptdata [read $fd]
close $fd
set scriptdata [string map [list \r\n \n] $scriptdata]
set in_data 0
set nextshellpath ""
set nextshelltype ""
puts stderr "PLAT: $plat"
foreach ln [split $scriptdata \n] {
if {[string trim $ln] eq ""} {continue}
if {!$in_data} {
if {[string match ": <<nextshell_start>>*" $ln]} {
set in_data 1
}
} else {
if {[string match "*@SET*nextshellpath?${plat}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshellpath [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match "*@SET*nextshelltype?${plat}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshelltype [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match ": <<nextshell_end>>*" $ln]} {
break
}
}
}
if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
if {$nextshelltype in "pwsh powershell"} {
set scrname [file rootname [info script]].ps1
set arglist [list]
foreach a $::argv {
set a "'$a'"
lappend arglist $a
}
} else {
set scrname [info script]
set arglist $::argv
}
puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
#todo - handle /usr/bin/env
#todo - exitcode
if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
set nextshell_words [list $nextshellpath]
} else {
set nextshell_words $nextshellpath
}
set ns_firstword [lindex $nextshellpath 0]
if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
set ns_firstword [string range $ns_firstword 1 end-1]
}
if {[string match {/*/env} $ns_firstword] && $::tcl_platform(platform) ne "windows"} {
set exec_part $nextshellpath
} else {
set epath [auto_execok $ns_firstword]
if {$epath eq ""} {
error "unable to find executable path for first word '$ns_firstword' of nextshellpath '$nextshellpath'"
} else {
set exec_part [list {*}$epath {*}[lrange $nextshellpath 1 end]]
}
}
catch {exec {*}$exec_part $scrname {*}$arglist <@stdin >@stdout 2>@stderr} emsg eopts
if {[dict exists $eopts -errorcode]} {
set ecode [dict get $eopts -errorcode]
if {[lindex $ecode 0] eq "CHILDSTATUS"} {
exit [lindex $ecode 2]
} else {
puts stderr "error calling next shell $nextshelltype :"
puts stderr $emsg
exit 1
}
} else {
exit 0
}
}
#---------------------------------------------------------------------
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
@ -481,7 +844,7 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as perl or bash"
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
#</tcl-payload>
#<tcl-pre-launch-subprocess>
@ -512,21 +875,26 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "script: `echo $0 | sed 's/^-//'`"
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#csh/tcsh diversion \
test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit )
#other non-bash diversion \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0
#review \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit
# sh/bash \
shift && set -- "${@:1:$#-1}"
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
# echo "script: `echo $0 | sed 's/^-//'`"
# use oldschool backticks and sed - lowest common denominator \
# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# zsh diversion \
# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
# \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# \
echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
# non-bash-like diversion \
if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
# sh/bash (or zsh?) \
shift && set -- "${@:1:$((${#@}-1))}"
# \
#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
#------------------------------------------------------
# \
echo "args: $@"
# ------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
then
@ -541,20 +909,30 @@ if false==false # else {
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
if [[ "$OSTYPE" == "linux"* ]]; then
plat=$(uname -s) #platform/system
if [[ "$plat" = "Linux"* ]]; then
os="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
elif [[ "$OSTYPE" == "freebsd"* ]]; then
elif [[ "$plat" == "FreeBSD"* ]]; then
os="freebsd"
elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then
elif [[ "$plat" == "DragonFly"* ]]; then
os="dragonflybsd"
elif [[ "$OSTYPE" == "netbsd"* ]]; then
elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$OSTYPE" == "win32" ]]; then
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
elif [[ "$plat" = "MINGW32"* ]]; then
os="win32"
elif [[ "$plat" = "MINGW64"* ]]; then
os="win32"
elif [[ "$OSTYPE" == "msys" ]]; then
elif [[ "$plat" = "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
echo MSYS
#win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#bash reports $OSTYPE msys
os="win32"
#review - need ps/sed/awk to determine shell?
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
@ -564,37 +942,50 @@ elif [[ "$OSTYPE" == "msys" ]]; then
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
else
#os="$OSTYPE"
os="other"
fi
echo ostype: $OSTYPE
shellconfigline=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#echo $shellconfigline;
if [[ $shellconfigline == *"nextshelltype"* ]]; then
echo "found config for os $os"
split1="${shellconfigline#*=}" #remove everything through the first '='
#echo "split1: $split1"
pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match
pathraw="${pathraw//\"/}" #remove quote
nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end)
#echo "nextshellpath: $nextshellpath"
split2="${split1#*=}"
#echo "split2: $split2"
split2="${split2//\"/}"
nextshelltype="${split2/%_*/}"
echo "nextshelltype: $nextshelltype"
## This is the sort of sed that will not work across implementations
## shellconfiglines=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#awk tested on linux & freebsd
shellconfiglines=$( awk '/^:.*<<nextshell_start>>.*$/,/^:.*<<nextshell_end>>.*$/' "$0" | grep $os)
# echo $shellconfiglines;
# readarray requires bash 4.0
if [[ "$ps_shellname" == "bash" ]]; then
readarray -t arr_oslines <<<"$shellconfiglines"
elif [[ "$ps_shellname" == "zsh" ]]; then
arr_oslines=("${(f)shellconfiglines}")
else
echo "unable to find config for os $os"
echo "shellconfigline: $shellconfigline"
nextshellpath=""
nextshelltype=""
#fallback - doesn't seem to work in zsh - untested in early bash
IFS=$'\n' arr_oslines=($shellconfiglines)
fi
nextshellpath=""
nextshelltype=""
for ln in "${arr_oslines[@]}"; do
# echo "---- $ln"
if [[ "$ln" == *"nextshellpath"* ]]; then
splitln="${ln#*=}" #remove everything through the first '='
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
echo "nextshelltype: $nextshelltype"
fi
done
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
#echo bash launching subshell of type $nextshelltype $nextshellpath on "$0"
#/usr/bin/env tclsh "$0" "$@"
echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
@ -767,12 +1158,14 @@ function GetDynamicParamDictionary {
return $DynParamDictionary
}
}
# Example usage:
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# [Parameter(Mandatory)][string] $myargument,
# [Parameter(ValueFromRemainingArguments)] $opts
# )
#}
#function psmain {
@ -783,10 +1176,15 @@ function GetDynamicParamDictionary {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# 'myargument' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# 'opts' {
# write-warning "Unused parameters: $($PSBoundParameters.$_)"
# }
# Default {
# write-warning "Unhandled parameter -> [$($_)]"
# }
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
@ -794,24 +1192,24 @@ function GetDynamicParamDictionary {
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# Write-Host "myargument value is: $myargument"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
"Script Name : {0}" -f $scriptname | write-host
"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
$thisfileContent = Get-Content $scriptname -Raw
$startTag = ": <<asadmin_start>>"
$endTag = ": <<asadmin_end>>"
$fileContent = Get-Content $scriptname -Raw
$pattern = "(?s)$startTag(.*?)$endTag"
$matches = [regex]::Matches($fileContent,$pattern)
$admininfo = $matches[0].Groups[1].Value
$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
$match = [regex]::Match($thisfileContent,$pattern)
$asadmin = 0
if ($matches.count) {
if ($match.Success) {
$admininfo = $match.Groups[1].Value
$asadmin = $admininfo.Contains("asadmin=1")
if ($asadmin) {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
@ -829,6 +1227,67 @@ if ($matches.count) {
}
}
}
#
$startTag = ": <<nextshell_start>>"
$endTag = ": <<nextshell_end>>"
$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
$match = [regex]::Match($thisfileContent,$pattern)
if ($match.Success) {
$plat = [System.Environment]::OSVersion.Platform
if ($plat -eq "Unix") {
$runtime_ident = [System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier
switch ($runtime_ident.split("-")[0]) {
"freebsd" {
# untested
$os = "freebsd"
}
"linux" {
$os = "linux"
}
"osx" {
# osx-x64 or osx-arm64 ?
$os = "macosx"
}
default {
#openbsd, netbsd ?
$os = "other"
}
}
} else {
$os = "win32"
}
$matchedlines = $match.Groups[1].Value
$nextshell_type = ""
$nextshell_path = ""
ForEach ($line in $($matchedlines -split "\r?\n")) {
$m = [regex]::Match($line,".*nextshelltype\[${os}[_]+\]=([^_]*)[_]*")
if ($m.Success) {
$nextshell_type = $m.Groups[1].Value
}
$m = [regex]::Match($line,".*nextshellpath\[${os}[_]+\]=([^_]*)[_]*")
if ($m.Success) {
$nextshell_path = $m.Groups[1].Value
}
if ($nextshell_type -ne "" -and $nextshell_path -ne "") {
break
}
}
if (-not (("pwsh", "powershell", "") -contains $nextshell_type)) {
#nextshell diversion exists for this platform
write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $scriptname"
# $arguments = @($($MyInvocation.MyCommand.Path))
# $arguments += $args
# NOTE - this gives incorrect argument quoting e.g wrong number of arguments received by launched process for arguments: a "b c"
# $process = (Start-Process -FilePath $nextshell_path -ArgumentList $arguments -NoNewWindow -Wait)
# Exit $process.ExitCode
& $nextshell_path $scriptname $args
exit $LASTEXITCODE
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#<powershell-payload>
@ -841,6 +1300,7 @@ if ($matches.count) {
#$outbase = Join-Path -Path $PSScriptRoot -ChildPath "../.."
$outbase = $PSScriptRoot
$outbase = Resolve-Path -Path $outbase
Write-host "Base folder: $outbase"
$toolsfolder = Join-Path -Path $outbase -ChildPath "tools"
if (-not(Test-Path -Path $toolsfolder -PathType Container)) {
#create folder - (can include missing intermediaries)

973
bin/runtime.cmd

File diff suppressed because it is too large Load Diff

1338
bin/tclargs.cmd

File diff suppressed because it is too large Load Diff

630
getpunk.cmd

@ -1,4 +1,4 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
@ -16,6 +16,70 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@rem -------------------------------------------------------------------------------------------------------------------------------
@rem return from endlocal macro - courtesy of jeb
@rem This allows return of values containing special characters from subroutines
@rem https://stackoverflow.com/questions/3262287/make-an-environment-variable-survive-endlocal/8257951#8257951
@rem -------------------------------------------------------------------------------------------------------------------------------
@setlocal DisableDelayedExpansion
@echo off
%= 2 blank lines after next are required =%
set LF=^
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
%= I use EDE for EnableDelayeExpansion and DDE for DisableDelayedExpansion =%
set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
setlocal EnableDelayedExpansion%\n%
%= Take all variable names into the varName array =%%\n%
set varName_count=0%\n%
for %%C in (!args!) do set "varName[!varName_count!]=%%~C" ^& set /a varName_count+=1%\n%
%= Build one variable with a list of set statements for each variable delimited by newlines =%%\n%
%= The lists looks like --> set result1=myContent\n"set result1=myContent1"\nset result2=content2\nset result2=content2\n =%%\n%
%= Each result exists two times, the first for the case returning to DDE, the second for EDE =%%\n%
%= The correct line will be detected by the (missing) enclosing quotes =%%\n%
set "retContent=1!LF!"%\n%
for /L %%n in (0 1 !varName_count!) do (%\n%
for /F "delims=" %%C in ("!varName[%%n]!") DO (%\n%
set "content=!%%C!"%\n%
set "retContent=!retContent!"set !varName[%%n]!=!content!"!LF!"%\n%
if defined content (%\n%
%= This complex block is only for replacing '!' with '^!' =%%\n%
%= First replacing '"'->'""q' '^'->'^^' =%%\n%
set ^"content_EDE=!content:"=""q!"%\n%
set "content_EDE=!content_EDE:^=^^!"%\n%
%= Now it's poosible to use CALL SET and replace '!'->'""e!' =%%\n%
call set "content_EDE=%%content_EDE:^!=""e^!%%"%\n%
%= Now it's possible to replace '""e' to '^', this is effectivly '!' -> '^!' =%%\n%
set "content_EDE=!content_EDE:""e=^!"%\n%
%= Now restore the quotes =%%\n%
set ^"content_EDE=!content_EDE:""q="!"%\n%
) ELSE set "content_EDE="%\n%
set "retContent=!retContent!set "!varName[%%n]!=!content_EDE!"!LF!"%\n%
)%\n%
)%\n%
%= Now return all variables from retContent over the barrier =%%\n%
for /F "delims=" %%V in ("!retContent!") DO (%\n%
%= Only the first line can contain a single 1 =%%\n%
if "%%V"=="1" (%\n%
%= We need to call endlocal twice, as there is one more setlocal in the macro itself =%%\n%
endlocal%\n%
endlocal%\n%
) ELSE (%\n%
%= This is true in EDE =%%\n%
if "!"=="" (%\n%
if %%V==%%~V (%\n%
%%V !%\n%
)%\n%
) ELSE IF not %%V==%%~V (%\n%
%%~V%\n%
)%\n%
)%\n%
)%\n%
) else set args="
@rem -------------------------------------------------------------------------------------------------------------------------------
@SETLOCAL EnableExtensions EnableDelayedExpansion
@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: dev scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@ -24,7 +88,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________"
@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@ -32,7 +95,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM Supporting more explicit oses than those listed may also require script padding adjustment
: <<nextshell_start>>
@SET "nextshellpath[win32___________]=powershell______________________________________________________"
@SET "nextshellpath[win32___________]=powershell -nop -nol -ExecutionPolicy ByPass -File______________"
@SET "nextshelltype[win32___________]=powershell______"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env bash_______________________________________________"
@SET "nextshelltype[dragonflybsd____]=bash____________"
@ -51,8 +114,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: <<asadmin_start>>
@SET "asadmin=0"
: <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@REM @ECHO selected_shelltype %selected_shelltype%
@CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed
@ -73,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- deck scriptwrap.checkfile filepath
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@ -83,12 +144,13 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@SET "winpath=%~dp0"
@SET "winpath=%~dp0" %= e.g c:\punkshell\bin\ %=
@SET "fname=%~nx0"
@SET "scriptrootname=%~dp0%~n0" %= e.g c:\punkshell\bin\runtime (full path without extension) unavailable after shift, so store it =%
@REM @ECHO fname %fname%
@REM @ECHO winpath %winpath%
@REM @ECHO commandlineascalled %0
@ -123,17 +185,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ -178,6 +229,77 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=pwsh
)
@set argCount=30
@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
@set tmpfile_base=%TEMP%\punkbatch_params
@call :getUniqueFile %tmpfile_base% ".txt" paramfile
@echo %paramfile%
%= NOTE when we loop like this using the percent-n args, we lose unquoted separators such as comma and semicolon %=
@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
@rem outer loop required to redirect all rem lines at once to file
@for %%x in (1) do @(
@for /L %%f in (1,1,%argCount%) do @(
@set "argnum=%%~nf"
@set "a1=%%1"
@rem @set "argname=%%!argnum!"
@rem @echo argname: !argname!
@call :rem_output !argnum! !a1!
@shift
)
) > %paramfile%
@echo off
@set "newcommandline= "
@(set target=cmd_pwsh)
@if "%target%"=="cmd_pwsh" (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
@REM @echo ######### %%L
@rem call :buildcmdline newcommandline param "{" "}"
call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell %=
@rem @echo .
)
) ELSE (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
call :buildcmdline newcommandline param
)
)
@REM padding
SETLOCAL EnableDelayedExpansion
@echo off
@IF EXIST %paramfile% (
@DEL /F /Q %paramfile%
)
@IF EXIST %paramfile% (
echo failed to delete %paramfile%
cat %paramfile%
)
@REM @SET "squoted_args="
@REM @for %%a in (%*) do @(
@REM set "v=%%a"
@REM set "v=!v:'=''!"
@REM SET "squoted_args=!squoted_args!'!v!' "
@REM )
@REM @SET "squoted_args=%squoted_args:~0,-1%"
@REM @ECHO %squoted_args%
@IF "!selected_shelltype_trimmed!"=="pwsh" (
REM pwsh vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
@ -186,17 +308,20 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted
cmd /c pwsh -nop -nol -ExecutionPolicy bypass -c "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
REM TODO prompt user with option to call script to install pwsh using winget
REM powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist%
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!selected_shelltype_trimmed!"=="powershell" (
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist%
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
IF "!selected_shelltype_trimmed!"=="wslbash" (
@ -211,7 +336,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
@ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
%selected_shellpath_trimmed% "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@ -222,9 +347,144 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
@REM padding
@REM padding
@REM padding
%= ---------------------------------------------------------------------- =%
@rem courtesy of dbenham
:: Example usage
@rem call :getUniqueFile "d:\test\myFile" ".txt" myFile
@rem echo myFile="%myFile%"
:getUniqueFile baseName extension rtnVar
setlocal
:getUniqueFileLoop
for /f "skip=1" %%A in ('wmic os get localDateTime') do for %%B in (%%A) do set "rtn=%~1_%%B%~2"
if exist "%rtn%" (
goto :getUniqueFileLoop
) else (
2>nul >nul (9>"%rtn%" timeout /nobreak 1) || goto :getUniqueFileLoop
)
endlocal & set "%~3=%rtn%"
exit /b
%= ---------------------------------------------------------------------- =%
:buildcmdline cmdlinevar paramvar wrapA wrapB
%= quoting for cmd.exe /c pwsh -nop !args! =%
@SETLOCAL EnableDelayedExpansion
@REM @echo =====================
set "pval=!%~2:*#=!"
set "pval=!pval:~0,-2!"
@REM set "pval=!pval:~0,-1!"
set "wrapa=%~3"
set "wrapb=%~4"
@call :strlen pval slen
@rem @echo strlen: !slen!
if "!slen!"=="0" (
goto :eof
)
@set /A n = !slen! - 1
@(set str=)
@set "dq=""
@set "bang=^!"
@(set carat=^^)
@for /l %%i in (0,1,!n!) do @(
(set c=!pval:~%%i,1!)
if "!c!"=="|" (
set "ch=^^!pval:~%%i,1!"
) ELSE IF "!c!"=="(" (
set "ch=^("
) ELSE if "!c!"==")" (
set "ch=^)"
) ELSE if "!c!"=="&" (
set "ch=^^&"
) ELSE if "!c!"=="!dq!" (
set "ch=^""
) ELSE if "!c!"=="!bang!" (
@rem - double caret - first for initial parsing, second to ensure remains escaped during delayed expansion phase
@rem - REVIEW
set "ch=^^!bang!"
) ELSE if "!c!"=="^carat" (
set "ch=^^^^"
) ELSE if "!c!"=="'" (
set "ch=''"
) ELSE (
set "ch=!c!"
)
@rem @echo - !ch!
set "str=!str!!ch!"
)
echo +++++ %~1 = !%1! !str!
set "%~1=!%1! !wrapa!!str!!wrapb!"
@rem old method of return - failes to preserve exclamation marks
@rem for /f "delims=" %%A in (""!str!"") do endlocal & set "%~1=!%1! '%%~A'"
@rem macro method of endlocal return - preserving !val!
@echo off
%endlocal% %~1
@exit /b
:rem_output
@rem ---------------------------------------------
@rem rem_output is called for each n in the number of args we process - as we don't have a non-destructive way to count args whilst accepting special chars
@rem we therefore need a way to stop processing on the last received arg so we don't write argCount entries to param.txt if less are received
@rem see 'disappearing quotes' technique
@rem https://stackoverflow.com/questions/4643376/how-to-split-double-quoted-line-into-multiple-lines-in-windows-batch-file/4645113#4645113
@rem and
@rem https://groups.google.com/g/alt.msdos.batch.nt/c/J71F17Bve9Y (sponge belly)
@echo off
setlocal enableextensions disabledelayedexpansion
set "param1=%~2"
rem do must not be indented
for %%^" in ("") ^
do if not defined param1 set %%~"param1=%2%%~"
if not defined param1 goto :eof
endlocal
@rem ---------------------------------------------
@PROMPT @
@echo on
rem %1 #%2#
@exit /b
@REM courtesy of: https://stackoverflow.com/users/463115/jeb
:strlen stringVar returnVar
@(
setlocal EnableDelayedExpansion
@SET "rtrn=%~2"
(set^ tmp=!%~1!)
@rem @echo jjjjj !tmp!
@if defined tmp (
set "len=1"
@for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do @(
@if "!tmp:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "tmp=!tmp:~%%P!"
)
)
) ELSE (
set len=0
)
)
@(
endlocal
@IF "%~2" neq "" (
@SET "%rtrn%=%len%"
) ELSE (
@ECHO :strlen result: %len%
)
exit /b
)
:getWslPath
@SETLOCAL
@SET "_path=%~p1"
@ -280,7 +540,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@ -295,13 +555,12 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -327,6 +586,10 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringContains
@REM usage: @CALL:stringContains string needle returnvarname
@SETLOCAL
@ -348,7 +611,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
:stringToUpper strvar returnvar
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@ -384,6 +647,9 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@ -442,12 +708,104 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
# -- i.e it is a polyglot file.
# -- The specific layout including some lines that appear just as comments is quite sensitive to change.
# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline.
# -- e.g ./filename.polypunk.cmd in sh or bash
# -- e.g tclsh filename.cmd
# -- e.g ./scriptname.cmd in sh or zsh or bash
# -- e.g tclsh scriptname.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
#---------------------------------------------------------------------
#divert to configured nextshell
package require platform
set plat_full [platform::generic]
set plat [lindex [split $plat_full -] 0]
#may be old tcl - not assuming readFile available
set fd [open [info script] r]
set scriptdata [read $fd]
close $fd
set scriptdata [string map [list \r\n \n] $scriptdata]
set in_data 0
set nextshellpath ""
set nextshelltype ""
puts stderr "PLAT: $plat"
foreach ln [split $scriptdata \n] {
if {[string trim $ln] eq ""} {continue}
if {!$in_data} {
if {[string match ": <<nextshell_start>>*" $ln]} {
set in_data 1
}
} else {
if {[string match "*@SET*nextshellpath?${plat}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshellpath [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match "*@SET*nextshelltype?${plat}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshelltype [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match ": <<nextshell_end>>*" $ln]} {
break
}
}
}
if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
if {$nextshelltype in "pwsh powershell"} {
set scrname [file rootname [info script]].ps1
set arglist [list]
foreach a $::argv {
set a "'$a'"
lappend arglist $a
}
} else {
set scrname [info script]
set arglist $::argv
}
puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
#todo - handle /usr/bin/env
#todo - exitcode
if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
set nextshell_words [list $nextshellpath]
} else {
set nextshell_words $nextshellpath
}
set ns_firstword [lindex $nextshellpath 0]
if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
set ns_firstword [string range $ns_firstword 1 end-1]
}
if {[string match {/*/env} $ns_firstword] && $::tcl_platform(platform) ne "windows"} {
set exec_part $nextshellpath
} else {
set epath [auto_execok $ns_firstword]
if {$epath eq ""} {
error "unable to find executable path for first word '$ns_firstword' of nextshellpath '$nextshellpath'"
} else {
set exec_part [list {*}$epath {*}[lrange $nextshellpath 1 end]]
}
}
catch {exec {*}$exec_part $scrname {*}$arglist <@stdin >@stdout 2>@stderr} emsg eopts
if {[dict exists $eopts -errorcode]} {
set ecode [dict get $eopts -errorcode]
if {[lindex $ecode 0] eq "CHILDSTATUS"} {
exit [lindex $ecode 2]
} else {
puts stderr "error calling next shell $nextshelltype :"
puts stderr $emsg
exit 1
}
} else {
exit 0
}
}
#---------------------------------------------------------------------
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
@ -481,7 +839,7 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as perl or bash"
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
#</tcl-payload>
#<tcl-pre-launch-subprocess>
@ -512,21 +870,26 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "script: `echo $0 | sed 's/^-//'`"
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#csh/tcsh diversion \
test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit )
#other non-bash diversion \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0
#review \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit
# sh/bash \
shift && set -- "${@:1:$#-1}"
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
# echo "script: `echo $0 | sed 's/^-//'`"
# use oldschool backticks and sed - lowest common denominator \
# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# zsh diversion \
# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
# \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# \
echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
# non-bash-like diversion \
if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
# sh/bash (or zsh?) \
shift && set -- "${@:1:$((${#@}-1))}"
# \
#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
#------------------------------------------------------
# \
echo "args: $@"
# ------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
then
@ -541,20 +904,30 @@ if false==false # else {
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
if [[ "$OSTYPE" == "linux"* ]]; then
plat=$(uname -s) #platform/system
if [[ "$plat" = "Linux"* ]]; then
os="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
elif [[ "$OSTYPE" == "freebsd"* ]]; then
elif [[ "$plat" == "FreeBSD"* ]]; then
os="freebsd"
elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then
elif [[ "$plat" == "DragonFly"* ]]; then
os="dragonflybsd"
elif [[ "$OSTYPE" == "netbsd"* ]]; then
elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$OSTYPE" == "win32" ]]; then
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
elif [[ "$plat" = "MINGW32"* ]]; then
os="win32"
elif [[ "$plat" = "MINGW64"* ]]; then
os="win32"
elif [[ "$plat" = "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$OSTYPE" == "msys" ]]; then
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
echo MSYS
#win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#bash reports $OSTYPE msys
os="win32"
#review - need ps/sed/awk to determine shell?
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
@ -564,37 +937,50 @@ elif [[ "$OSTYPE" == "msys" ]]; then
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
else
#os="$OSTYPE"
os="other"
fi
echo ostype: $OSTYPE
shellconfigline=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#echo $shellconfigline;
if [[ $shellconfigline == *"nextshelltype"* ]]; then
echo "found config for os $os"
split1="${shellconfigline#*=}" #remove everything through the first '='
#echo "split1: $split1"
pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match
pathraw="${pathraw//\"/}" #remove quote
nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end)
#echo "nextshellpath: $nextshellpath"
split2="${split1#*=}"
#echo "split2: $split2"
split2="${split2//\"/}"
nextshelltype="${split2/%_*/}"
echo "nextshelltype: $nextshelltype"
## This is the sort of sed that will not work across implementations
## shellconfiglines=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#awk tested on linux & freebsd
shellconfiglines=$( awk '/^:.*<<nextshell_start>>.*$/,/^:.*<<nextshell_end>>.*$/' "$0" | grep $os)
# echo $shellconfiglines;
# readarray requires bash 4.0
if [[ "$ps_shellname" == "bash" ]]; then
readarray -t arr_oslines <<<"$shellconfiglines"
elif [[ "$ps_shellname" == "zsh" ]]; then
arr_oslines=("${(f)shellconfiglines}")
else
echo "unable to find config for os $os"
echo "shellconfigline: $shellconfigline"
nextshellpath=""
nextshelltype=""
#fallback - doesn't seem to work in zsh - untested in early bash
IFS=$'\n' arr_oslines=($shellconfiglines)
fi
nextshellpath=""
nextshelltype=""
for ln in "${arr_oslines[@]}"; do
# echo "---- $ln"
if [[ "$ln" == *"nextshellpath"* ]]; then
splitln="${ln#*=}" #remove everything through the first '='
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
echo "nextshelltype: $nextshelltype"
fi
done
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
#echo bash launching subshell of type $nextshelltype $nextshellpath on "$0"
#/usr/bin/env tclsh "$0" "$@"
echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
@ -842,12 +1228,14 @@ function GetDynamicParamDictionary {
return $DynParamDictionary
}
}
# Example usage:
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# [Parameter(Mandatory)][string] $myargument,
# [Parameter(ValueFromRemainingArguments)] $opts
# )
#}
#function psmain {
@ -858,10 +1246,15 @@ function GetDynamicParamDictionary {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# 'myargument' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# 'opts' {
# write-warning "Unused parameters: $($PSBoundParameters.$_)"
# }
# Default {
# write-warning "Unhandled parameter -> [$($_)]"
# }
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
@ -869,24 +1262,24 @@ function GetDynamicParamDictionary {
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# Write-Host "myargument value is: $myargument"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
"Script Name : {0}" -f $scriptname | write-host
"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
$thisfileContent = Get-Content $scriptname -Raw
$startTag = ": <<asadmin_start>>"
$endTag = ": <<asadmin_end>>"
$fileContent = Get-Content $scriptname -Raw
$pattern = "(?s)$startTag(.*?)$endTag"
$matches = [regex]::Matches($fileContent,$pattern)
$admininfo = $matches[0].Groups[1].Value
$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
$match = [regex]::Match($thisfileContent,$pattern)
$asadmin = 0
if ($matches.count) {
if ($match.Success) {
$admininfo = $match.Groups[1].Value
$asadmin = $admininfo.Contains("asadmin=1")
if ($asadmin) {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
@ -904,6 +1297,67 @@ if ($matches.count) {
}
}
}
#
$startTag = ": <<nextshell_start>>"
$endTag = ": <<nextshell_end>>"
$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
$match = [regex]::Match($thisfileContent,$pattern)
if ($match.Success) {
$plat = [System.Environment]::OSVersion.Platform
if ($plat -eq "Unix") {
$runtime_ident = [System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier
switch ($runtime_ident.split("-")[0]) {
"freebsd" {
# untested
$os = "freebsd"
}
"linux" {
$os = "linux"
}
"osx" {
# osx-x64 or osx-arm64 ?
$os = "macosx"
}
default {
#openbsd, netbsd ?
$os = "other"
}
}
} else {
$os = "win32"
}
$matchedlines = $match.Groups[1].Value
$nextshell_type = ""
$nextshell_path = ""
ForEach ($line in $($matchedlines -split "\r?\n")) {
$m = [regex]::Match($line,".*nextshelltype\[${os}[_]+\]=([^_]*)[_]*")
if ($m.Success) {
$nextshell_type = $m.Groups[1].Value
}
$m = [regex]::Match($line,".*nextshellpath\[${os}[_]+\]=([^_]*)[_]*")
if ($m.Success) {
$nextshell_path = $m.Groups[1].Value
}
if ($nextshell_type -ne "" -and $nextshell_path -ne "") {
break
}
}
if (-not (("pwsh", "powershell", "") -contains $nextshell_type)) {
#nextshell diversion exists for this platform
write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $scriptname"
# $arguments = @($($MyInvocation.MyCommand.Path))
# $arguments += $args
# NOTE - this gives incorrect argument quoting e.g wrong number of arguments received by launched process for arguments: a "b c"
# $process = (Start-Process -FilePath $nextshell_path -ArgumentList $arguments -NoNewWindow -Wait)
# Exit $process.ExitCode
& $nextshell_path $scriptname $args
exit $LASTEXITCODE
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#<powershell-payload>

4
src/lib/app-punk/pkgIndex.tcl

@ -1,3 +1 @@
package ifneeded app-punk 1.0 [list source [file join $dir repl.tcl]]
package ifneeded app-punk 1.0 [list source [file join $dir repl.tcl]]

2
src/lib/app-punkshell/pkgIndex.tcl

@ -0,0 +1,2 @@
package ifneeded app-punkshell 1.0 [list source [file join $dir punkshell.tcl]]

296
src/lib/app-punkshell/punkshell.tcl

@ -0,0 +1,296 @@
package provide app-punkshell 1.0
package require Thread
package require punk::args
package require shellfilter
package require punk::ansi
package require punk::packagepreference
punk::packagepreference::install
namespace eval punkshell {
variable chanstack_stderr_redir
variable chanstack_stdout_redir
proc clock_sec {} {
return [expr {[clock millis]/1000.0}]
}
set do_log 0
if {$do_log} {
set debug_syslog_server 127.0.0.1:514
#set debug_syslog_server 172.16.6.42:51500
set error_syslog_server 127.0.0.1:514
set data_syslog_server 127.0.0.1:514
} else {
set debug_syslog_server ""
set error_syslog_server ""
set data_syslog_server ""
}
#-------------------------------------------------------------------------
##don't write to stdout/stderr before you've redirected them to a log using shellfilter functions
## puts to stdout/stderr will comingle with command's output if performed before the channel stacks are configured.
#chan configure stdin -buffering line
#chan configure stdout -buffering none
#chan configure stderr -buffering none
#redir on the shellfilter stack with no log or syslog specified acts to suppress output of stdout & stderr.
#todo - fix shellfilter code to make this noop more efficient (avoid creating corresponding logging thread and filter?)
#JMN
#set redirconfig {-settings {-syslog 127.0.0.1:514 -file ""}}
set redirconfig {}
#lassign [shellfilter::redir_output_to_log "SUPPRESS" {*}$redirconfig] chanstack_stdout_redir chanstack_stderr_redir
#shellfilter::log::write $punkshell_status_log "shellfilter::redir_output_to_log SUPPRESS DONE [clock_sec]"
set stdout_log ""
set stderr_log ""
#set stdout_log [file normalize ~]/punkshell-stdout.txt
#set stderr_log [file normalize ~]/punkshell-stderr.txt
set stdout_log "[pwd]/punkshell_out.log"
set stderr_log "[pwd]/punkshell_err.log"
set errdeviceinfo [shellfilter::stack::new punkshellerr -settings [list -tag "punkshellerr" -buffering none -raw 1 -syslog $data_syslog_server -file $stderr_log]]
set outdeviceinfo [shellfilter::stack::new punkshellout -settings [list -tag "punkshellout" -buffering none -raw 1 -syslog $data_syslog_server -file $stdout_log]]
#set commandlog [dict get $outdeviceinfo localchan]
#puts $commandlog "HELLO $commandlog"
#flush $commandlog
proc do_script {scriptname args} {
#ideally we don't want to launch an external process to run the script
#variable punkshell_status_log
#shellfilter::log::write $punkshell_status_log "do_script got scriptname:'$scriptname' replwhen:$replwhen args:'$args'"
set exepath [file dirname [file join [info nameofexecutable] __dummy__]]
set exedir [file dirname $exepath]
set scriptpath [file normalize $scriptname]
if {![file exists $scriptpath]} {
puts stderr "Failed to find script: '$scriptpath'"
error "bad scriptpath '$scriptpath'"
}
set script [string map [list %a% $args %s% $scriptpath] {
set normscript %s%
#save values
set prevscript [info script]
set prevglobal [dict create]
foreach g [list ::argv ::argc ::argv0] {
if {[info exists $g]} {
dict set prevglobal $g [set $g]
}
}
#setup and run
set ::argv [list %a%]
set ::argc [llength $::argv]
set ::argv0 $normscript
info script $normscript
source $normscript
#restore values
info script $prevscript
dict with prevglobal {}
}]
dict set params -tclscript 1 ;#don't give callback a chance to omit/break this
dict set params -teehandle punkshell
#dict set params -teehandle punksh
dict set params -inbuffering none
dict set params -outbuffering none
dict set params -readprocesstranslation crlf
dict set params -outtranslation lf
set id_err [shellfilter::stack::add stderr ansiwrap -action sink-locked -settings {-colour {red bold}}]
set exitinfo [shellfilter::run $script {*}$params]
shellfilter::stack::remove stderr $id_err
if {[dict exists $exitinfo errorInfo]} {
#strip out the irrelevant info from the errorInfo - we don't want info beyond 'invoked from within' as this is just plumbing related to the script sourcing
set stacktrace [string map [list \r\n \n] [dict get $exitinfo errorInfo]]
set output ""
set tracelines [split $stacktrace \n]
foreach ln $tracelines {
if {[string match "*invoked from within*" $ln]} {
break
}
append output $ln \n
}
set output [string trimright $output \n]
dict set exitinfo errorInfo $output
}
return $exitinfo
}
proc do_tclkit {kitname replwhen args} {
set script [string map [list %a% $args %k% $kitname] {
#::tcl::tm::add %m%
set kit %k%
set kitpath [file normalize $kit]
set kitmount $kitpath.0
#save values
set prevscript [info script]
set prevglobal [dict create]
foreach g [list ::argv ::argc ::argv0] {
if {[info exists $g]} {
dict set prevglobal $g [set $g]
}
}
#setup and run
set ::argv [list %a%]
set ::argc [llength $::argv]
set ::argv0 $kitmount
#puts stderr "setting 'info script' $kitmount/main.tcl"
info script $kitmount/main.tcl
#info script dir must match argv0 for kit main.tcl to return 'starkit' from 'starkit::startup'
if {![catch {
package require vfs
package require vfs::mk4
} errMsg]} {
vfs::mk4::Mount $kitpath $kitmount
lappend ::auto_path $kitmount/lib
if {[file exists "$kitmount/modules"]} {
tcl::tm::add "$kitmount/modules"
}
#puts stderr "sourcing $kitmount/main.tcl"
#puts stderr "$kitmount/main.tcl exists: [file exists $kitmount/main.tcl]"
#puts stderr "argv : $::argv"
#puts stderr "argv0: $::argv0"
#puts stderr "autopath: $::auto_path"
#puts stdout "starkit::startup [starkit::startup]"
#usually main.tcl will just be something like: package require app-XXX
#it will usually do nothing if starkit::startup returned 'sourced'
source $kitmount/main.tcl
} else {
puts stderr "Unable to load vfs::mk4 for tclkit mounting"
}
#restore values
info script $prevscript
dict with prevglobal {}
}]
set repl_lines ""
append repl_lines {package require punk::repl} \n
append repl_lines {repl::init -safe 0} \n
append repl_lines {repl::start stdin} \n
#test
#set replwhen "repl_last"
if {$replwhen eq "repl_first"} {
#we need to cooperate with the repl to get the script to run on exit
namespace eval ::repl {}
set ::repl::post_script $script
set script "$repl_lines"
} elseif {$replwhen eq "repl_last"} {
append script $repl_lines
} else {
#just the script
}
dict set params -tclscript 1 ;#don't give callback a chance to omit/break this
dict set params -teehandle punkshell
dict set params -inbuffering none
dict set params -outbuffering none
dict set params -readprocesstranslation crlf
dict set params -outtranslation lf
set id_err [shellfilter::stack::add stderr ansiwrap -action sink-locked -settings {-colour {red bold}}]
set exitinfo [shellfilter::run $script {*}$params]
shellfilter::stack::remove stderr $id_err
if {[dict exists $exitinfo errorInfo]} {
#strip out the irrelevant info from the errorInfo - we don't want info beyond 'invoked from within' as this is just plumbing related to the script sourcing
set stacktrace [string map [list \r\n \n] [dict get $exitinfo errorInfo]]
set output ""
set tracelines [split $stacktrace \n]
foreach ln $tracelines {
if {[string match "*invoked from within*" $ln]} {
break
}
append output $ln \n
}
set output [string trimright $output \n]
dict set exitinfo errorInfo $output
}
return $exitinfo
}
punk::args::define {
@id -id ::punkshell
@cmd -name punkshell
@leaders -min 0 -max 0
@opts
-debug -type none
@values -min 1 -max -1
script_or_kit -type string
arg -type any -optional 1 -multiple 1
}
set argd [punk::args::parse $::argv withid ::punkshell]
lassign [dict values $argd] leaders opts values received
set script_or_kit [dict get $values script_or_kit]
if {[dict exists $received arg]} {
set arglist [dict get $values arg]
} else {
set arglist [list]
}
set exitinfo [dict create]
switch -glob -nocase -- $script_or_kit {
lib:* {
#scriptlib
puts stderr "lib:* todo"
}
*.tcl {
set exitinfo [punkshell::do_script $script_or_kit {*}$arglist]
}
*.kit {
set exitinfo [punkshell::do_tclkit $script_or_kit "no_repl" {*}$arglist]
}
default {
puts stderr "unrecognised script extension"
}
}
catch {
shellfilter::stack::remove stderr $chanstack_stderr_redir
shellfilter::stack::remove stdout $chanstack_stdout_redir
}
shellfilter::stack::delete punkshellout
shellfilter::stack::delete punkshellerr
set free_info [shellthread::manager::shutdown_free_threads]
foreach tid [thread::names] {
thread::release $tid
}
if {[dict size $exitinfo] == 0} {
puts stderr "No result"
exit 2
}
if {[dict exists $exitinfo errorInfo]} {
set einf [dict get $exitinfo errorInfo]
puts stderr "errorCode: [dict get $exitinfo errorCode]"
if {[catch {
punk::ansi::ansiwrap yellow bold $einf
} msg]} {
set msg $einf
}
puts stderr $msg
flush stderr
exit 1
} else {
puts -nonewline stdout [dict get $exitinfo result]
exit 0
}
}

5
src/lib/app-shellspy/pkgIndex.tcl

@ -1,3 +1,2 @@
package ifneeded app-shellspy 1.0 [list source [file join $dir shellspy.tcl]]
package ifneeded app-shellspy 1.0 [list source [file join $dir shellspy.tcl]]

2449
src/lib/app-shellspy/shellspy.tcl

File diff suppressed because it is too large Load Diff

7
src/make.tcl

@ -3028,8 +3028,9 @@ foreach vfstail $vfs_tails {
kit {
if {!$have_sdx} {
puts stderr "no sdx available to unwrap $targetkit"
lappend failed_kits [list kit $targetkit reason "sdx_executable_unavailable"]
#don't add to failed_kits here
#extraction fail for one type doesn't mean we have fully failed yet
#lappend failed_kits [list kit $targetkit reason "sdx_executable_unavailable"]
#$vfs_event targetset_end FAILED
#$vfs_event destroy
#$vfs_installer destroy
@ -3074,8 +3075,10 @@ foreach vfstail $vfs_tails {
if {!$extraction_done} {
#TODO: if not extracted - use a default tcl_library for patchlevel and platform?
puts stderr "WARNING: No extraction done from runtime $runtime_fullname"
puts stderr "--------------------------------------------"
puts stderr "\x1b\31mWARNING: No extraction done from runtime $runtime_fullname\x1b\[m"
puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!"
puts stderr "--------------------------------------------"
file mkdir $targetvfs
}

13
src/modules/flagfilter-0.3.tm → src/modules/flagfilter-999999.0a1.0.tm

@ -1,8 +1,3 @@
#package provide flagfilter [namespace eval flagfilter {list [variable version 0.2.3]$version}]
#package provide [set ::pkg flagfilter-0.2.3] [namespace eval [lindex [split $pkg -] 0] {list [variable version [lindex [split $pkg -] 1][set ::pkg {}]]$version}]
#
#package provide [lindex [set pkg {flagfilter 0.2.3}] 0] [namespace eval [lindex $pkg 0] {list [variable version [lindex $pkg 1][set pkg {}]]$version}]
package provide [lassign {flagfilter 0.3} pkg ver]$pkg [namespace eval $pkg[set pkg {}] {list [variable version $::ver[set ::ver {}]]$version}]
#Note: this is ugly.. particularly when trying to classify flags that are not fully specified i.e raw passthrough.
# - we can't know if a flag -x --x etc is expecting a parameter or not.
@ -2185,6 +2180,7 @@ namespace eval flagfilter {
set raise_dispatch_error_instead_of_return ""
set dict_dispatch_results [list dispatchstatuslist [list] dispatchresultlist [list] dispatchstatus "ok"]
#todo - only dispatch if no unallocated args (must get tail_processor to allocate known flags to 'global')
if {[llength $dispatch]} {
set dispatchstatuslist [list]
set dispatchresultlist [list]
@ -2334,7 +2330,10 @@ namespace eval flagfilter {
set commandline [concat $command $matched_in_order $extraflags]
}
}
dict set dispatchrecord asdispatched $commandline
set dispatchresult ""
set dispatcherror ""
@ -2711,6 +2710,8 @@ namespace eval flagfilter {
}
package provide [lassign {flagfilter 999999.0a1.0} pkg ver]$pkg [namespace eval $pkg[set pkg {}] {list [variable version $::ver[set ::ver {}]]$version}]

3
src/modules/flagfilter-buildversion.txt

@ -0,0 +1,3 @@
0.3.1
#First line must be a tm version number
#all other lines are ignored.

5
src/modules/punk/char-999999.0a1.0.tm

@ -186,8 +186,9 @@ tcl::namespace::eval punk::char {
set r [list "" {*}[split $lowbits ""] $ridx {*}$charlist]
$t add_row $r
}
puts stderr $t
$t print
set result [$t print]
$t destroy
return $result
}
#just the 7-bit ascii. use [page ascii] for the 8-bit layout

621
src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd

@ -1,4 +1,4 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
@ -16,6 +16,70 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@rem -------------------------------------------------------------------------------------------------------------------------------
@rem return from endlocal macro - courtesy of jeb
@rem This allows return of values containing special characters from subroutines
@rem https://stackoverflow.com/questions/3262287/make-an-environment-variable-survive-endlocal/8257951#8257951
@rem -------------------------------------------------------------------------------------------------------------------------------
@setlocal DisableDelayedExpansion
@echo off
%= 2 blank lines after next are required =%
set LF=^
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
%= I use EDE for EnableDelayeExpansion and DDE for DisableDelayedExpansion =%
set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
setlocal EnableDelayedExpansion%\n%
%= Take all variable names into the varName array =%%\n%
set varName_count=0%\n%
for %%C in (!args!) do set "varName[!varName_count!]=%%~C" ^& set /a varName_count+=1%\n%
%= Build one variable with a list of set statements for each variable delimited by newlines =%%\n%
%= The lists looks like --> set result1=myContent\n"set result1=myContent1"\nset result2=content2\nset result2=content2\n =%%\n%
%= Each result exists two times, the first for the case returning to DDE, the second for EDE =%%\n%
%= The correct line will be detected by the (missing) enclosing quotes =%%\n%
set "retContent=1!LF!"%\n%
for /L %%n in (0 1 !varName_count!) do (%\n%
for /F "delims=" %%C in ("!varName[%%n]!") DO (%\n%
set "content=!%%C!"%\n%
set "retContent=!retContent!"set !varName[%%n]!=!content!"!LF!"%\n%
if defined content (%\n%
%= This complex block is only for replacing '!' with '^!' =%%\n%
%= First replacing '"'->'""q' '^'->'^^' =%%\n%
set ^"content_EDE=!content:"=""q!"%\n%
set "content_EDE=!content_EDE:^=^^!"%\n%
%= Now it's poosible to use CALL SET and replace '!'->'""e!' =%%\n%
call set "content_EDE=%%content_EDE:^!=""e^!%%"%\n%
%= Now it's possible to replace '""e' to '^', this is effectivly '!' -> '^!' =%%\n%
set "content_EDE=!content_EDE:""e=^!"%\n%
%= Now restore the quotes =%%\n%
set ^"content_EDE=!content_EDE:""q="!"%\n%
) ELSE set "content_EDE="%\n%
set "retContent=!retContent!set "!varName[%%n]!=!content_EDE!"!LF!"%\n%
)%\n%
)%\n%
%= Now return all variables from retContent over the barrier =%%\n%
for /F "delims=" %%V in ("!retContent!") DO (%\n%
%= Only the first line can contain a single 1 =%%\n%
if "%%V"=="1" (%\n%
%= We need to call endlocal twice, as there is one more setlocal in the macro itself =%%\n%
endlocal%\n%
endlocal%\n%
) ELSE (%\n%
%= This is true in EDE =%%\n%
if "!"=="" (%\n%
if %%V==%%~V (%\n%
%%V !%\n%
)%\n%
) ELSE IF not %%V==%%~V (%\n%
%%~V%\n%
)%\n%
)%\n%
)%\n%
) else set args="
@rem -------------------------------------------------------------------------------------------------------------------------------
@SETLOCAL EnableExtensions EnableDelayedExpansion
@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: dev scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@ -24,7 +88,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________"
@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@ -51,8 +114,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: <<asadmin_start>>
@SET "asadmin=0"
: <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@REM @ECHO selected_shelltype %selected_shelltype%
@CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed
@ -73,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- deck scriptwrap.checkfile filepath
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@ -83,12 +144,13 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@SET "winpath=%~dp0"
@SET "winpath=%~dp0" %= e.g c:\punkshell\bin\ %=
@SET "fname=%~nx0"
@SET "scriptrootname=%~dp0%~n0" %= e.g c:\punkshell\bin\runtime (full path without extension) unavailable after shift, so store it =%
@REM @ECHO fname %fname%
@REM @ECHO winpath %winpath%
@REM @ECHO commandlineascalled %0
@ -123,17 +185,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ -178,25 +229,101 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=pwsh
)
@set argCount=30
@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
@set tmpfile_base=%TEMP%\punkbatch_params
@call :getUniqueFile %tmpfile_base% ".txt" paramfile
@echo %paramfile%
%= NOTE when we loop like this using the percent-n args, we lose unquoted separators such as comma and semicolon %=
@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
@rem outer loop required to redirect all rem lines at once to file
@for %%x in (1) do @(
@for /L %%f in (1,1,%argCount%) do @(
@set "argnum=%%~nf"
@set "a1=%%1"
@rem @set "argname=%%!argnum!"
@rem @echo argname: !argname!
@call :rem_output !argnum! !a1!
@shift
)
) > %paramfile%
@echo off
@set "newcommandline= "
@(set target=cmd_pwsh)
@if "%target%"=="cmd_pwsh" (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
@REM @echo ######### %%L
@rem call :buildcmdline newcommandline param "{" "}"
@rem call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell ... -c %=
call :buildcmdline newcommandline param %= cmd.exe /c powershell ... -f %=
@rem @echo .
)
) ELSE (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
call :buildcmdline newcommandline param
)
)
@REM padding
SETLOCAL EnableDelayedExpansion
@echo off
@IF EXIST %paramfile% (
@DEL /F /Q %paramfile%
)
@IF EXIST %paramfile% (
echo failed to delete %paramfile%
cat %paramfile%
)
@REM @SET "squoted_args="
@REM @for %%a in (%*) do @(
@REM set "v=%%a"
@REM set "v=!v:'=''!"
@REM SET "squoted_args=!squoted_args!'!v!' "
@REM )
@REM @SET "squoted_args=%squoted_args:~0,-1%"
@REM @ECHO %squoted_args%
@IF "!selected_shelltype_trimmed!"=="pwsh" (
REM pwsh vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
REM when run without cmd.exe - pwsh will receive the semicolon (for cmd.exe unquoted semicolon and comma are separators that aren't seen in positional arguments)
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted 2>NUL; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted
cmd /c pwsh -nop -nol -ExecutionPolicy bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
REM TODO prompt user with option to call script to install pwsh using winget
REM powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist%
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!selected_shelltype_trimmed!"=="powershell" (
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist%
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
IF "!selected_shelltype_trimmed!"=="wslbash" (
@ -211,7 +338,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
@ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
%selected_shellpath_trimmed% "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@ -222,9 +349,145 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
@REM padding
@REM padding
@REM padding
%= ---------------------------------------------------------------------- =%
@rem courtesy of dbenham
:: Example usage
@rem call :getUniqueFile "d:\test\myFile" ".txt" myFile
@rem echo myFile="%myFile%"
:getUniqueFile baseName extension rtnVar
setlocal
:getUniqueFileLoop
for /f "skip=1" %%A in ('wmic os get localDateTime') do for %%B in (%%A) do set "rtn=%~1_%%B%~2"
if exist "%rtn%" (
goto :getUniqueFileLoop
) else (
2>nul >nul (9>"%rtn%" timeout /nobreak 1) || goto :getUniqueFileLoop
)
endlocal & set "%~3=%rtn%"
exit /b
%= ---------------------------------------------------------------------- =%
@REM padding
:buildcmdline cmdlinevar paramvar wrapA wrapB
%= quoting for cmd.exe /c pwsh -nop !args! =%
@SETLOCAL EnableDelayedExpansion
@REM @echo =====================
set "pval=!%~2:*#=!"
set "pval=!pval:~0,-2!"
@REM set "pval=!pval:~0,-1!"
set "wrapa=%~3"
set "wrapb=%~4"
@call :strlen pval slen
@rem @echo strlen: !slen!
if "!slen!"=="0" (
goto :eof
)
@set /A n = !slen! - 1
@(set str=)
@set "dq=""
@set "bang=^!"
@(set carat=^^)
@for /l %%i in (0,1,!n!) do @(
(set c=!pval:~%%i,1!)
if "!c!"=="|" (
set "ch=^^!pval:~%%i,1!"
) ELSE IF "!c!"=="(" (
set "ch=^("
) ELSE if "!c!"==")" (
set "ch=^)"
) ELSE if "!c!"=="&" (
set "ch=^^&"
) ELSE if "!c!"=="!dq!" (
set "ch=^""
) ELSE if "!c!"=="!bang!" (
@rem - double caret - first for initial parsing, second to ensure remains escaped during delayed expansion phase
@rem - REVIEW
set "ch=^^!bang!"
) ELSE if "!c!"=="^carat" (
set "ch=^^^^"
) ELSE if "!c!"=="'" (
set "ch=''"
) ELSE (
set "ch=!c!"
)
@rem @echo - !ch!
set "str=!str!!ch!"
)
echo +++++ %~1 = !%1! !str!
set "%~1=!%1! !wrapa!!str!!wrapb!"
@rem old method of return - failes to preserve exclamation marks
@rem for /f "delims=" %%A in (""!str!"") do endlocal & set "%~1=!%1! '%%~A'"
@rem macro method of endlocal return - preserving !val!
@echo off
%endlocal% %~1
@exit /b
:rem_output
@rem ---------------------------------------------
@rem rem_output is called for each n in the number of args we process - as we don't have a non-destructive way to count args whilst accepting special chars
@rem we therefore need a way to stop processing on the last received arg so we don't write argCount entries to param.txt if less are received
@rem see 'disappearing quotes' technique
@rem https://stackoverflow.com/questions/4643376/how-to-split-double-quoted-line-into-multiple-lines-in-windows-batch-file/4645113#4645113
@rem and
@rem https://groups.google.com/g/alt.msdos.batch.nt/c/J71F17Bve9Y (sponge belly)
@echo off
setlocal enableextensions disabledelayedexpansion
set "param1=%~2"
rem do must not be indented
for %%^" in ("") ^
do if not defined param1 set %%~"param1=%2%%~"
if not defined param1 goto :eof
endlocal
@rem ---------------------------------------------
@PROMPT @
@echo on
rem %1 #%2#
@exit /b
@REM courtesy of: https://stackoverflow.com/users/463115/jeb
:strlen stringVar returnVar
@(
setlocal EnableDelayedExpansion
@SET "rtrn=%~2"
(set^ tmp=!%~1!)
@rem @echo jjjjj !tmp!
@if defined tmp (
set "len=1"
@for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do @(
@if "!tmp:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "tmp=!tmp:~%%P!"
)
)
) ELSE (
set len=0
)
)
@(
endlocal
@IF "%~2" neq "" (
@SET "%rtrn%=%len%"
) ELSE (
@ECHO :strlen result: %len%
)
exit /b
)
:getWslPath
@SETLOCAL
@SET "_path=%~p1"
@ -280,7 +543,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@ -295,13 +558,12 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -327,6 +589,10 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringContains
@REM usage: @CALL:stringContains string needle returnvarname
@SETLOCAL
@ -348,7 +614,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
:stringToUpper strvar returnvar
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@ -384,6 +650,11 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@ -442,12 +713,104 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
# -- i.e it is a polyglot file.
# -- The specific layout including some lines that appear just as comments is quite sensitive to change.
# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline.
# -- e.g ./filename.polypunk.cmd in sh or bash
# -- e.g tclsh filename.cmd
# -- e.g ./scriptname.cmd in sh or zsh or bash
# -- e.g tclsh scriptname.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
#---------------------------------------------------------------------
#divert to configured nextshell
package require platform
set plat_full [platform::generic]
set plat [lindex [split $plat_full -] 0]
#may be old tcl - not assuming readFile available
set fd [open [info script] r]
set scriptdata [read $fd]
close $fd
set scriptdata [string map [list \r\n \n] $scriptdata]
set in_data 0
set nextshellpath ""
set nextshelltype ""
puts stderr "PLAT: $plat"
foreach ln [split $scriptdata \n] {
if {[string trim $ln] eq ""} {continue}
if {!$in_data} {
if {[string match ": <<nextshell_start>>*" $ln]} {
set in_data 1
}
} else {
if {[string match "*@SET*nextshellpath?${plat}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshellpath [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match "*@SET*nextshelltype?${plat}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshelltype [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match ": <<nextshell_end>>*" $ln]} {
break
}
}
}
if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
if {$nextshelltype in "pwsh powershell"} {
set scrname [file rootname [info script]].ps1
set arglist [list]
foreach a $::argv {
set a "'$a'"
lappend arglist $a
}
} else {
set scrname [info script]
set arglist $::argv
}
puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
#todo - handle /usr/bin/env
#todo - exitcode
if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
set nextshell_words [list $nextshellpath]
} else {
set nextshell_words $nextshellpath
}
set ns_firstword [lindex $nextshellpath 0]
if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
set ns_firstword [string range $ns_firstword 1 end-1]
}
if {[string match {/*/env} $ns_firstword] && $::tcl_platform(platform) ne "windows"} {
set exec_part $nextshellpath
} else {
set epath [auto_execok $ns_firstword]
if {$epath eq ""} {
error "unable to find executable path for first word '$ns_firstword' of nextshellpath '$nextshellpath'"
} else {
set exec_part [list {*}$epath {*}[lrange $nextshellpath 1 end]]
}
}
catch {exec {*}$exec_part $scrname {*}$arglist <@stdin >@stdout 2>@stderr} emsg eopts
if {[dict exists $eopts -errorcode]} {
set ecode [dict get $eopts -errorcode]
if {[lindex $ecode 0] eq "CHILDSTATUS"} {
exit [lindex $ecode 2]
} else {
puts stderr "error calling next shell $nextshelltype :"
puts stderr $emsg
exit 1
}
} else {
exit 0
}
}
#---------------------------------------------------------------------
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
@ -481,7 +844,7 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as perl or bash"
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
#</tcl-payload>
#<tcl-pre-launch-subprocess>
@ -512,21 +875,26 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "script: `echo $0 | sed 's/^-//'`"
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#csh/tcsh diversion \
test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit )
#other non-bash diversion \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0
#review \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit
# sh/bash \
shift && set -- "${@:1:$#-1}"
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
# echo "script: `echo $0 | sed 's/^-//'`"
# use oldschool backticks and sed - lowest common denominator \
# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# zsh diversion \
# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
# \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# \
echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
# non-bash-like diversion \
if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
# sh/bash (or zsh?) \
shift && set -- "${@:1:$((${#@}-1))}"
# \
#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
#------------------------------------------------------
# \
echo "args: $@"
# ------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
then
@ -541,20 +909,30 @@ if false==false # else {
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
if [[ "$OSTYPE" == "linux"* ]]; then
plat=$(uname -s) #platform/system
if [[ "$plat" = "Linux"* ]]; then
os="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
elif [[ "$OSTYPE" == "freebsd"* ]]; then
elif [[ "$plat" == "FreeBSD"* ]]; then
os="freebsd"
elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then
elif [[ "$plat" == "DragonFly"* ]]; then
os="dragonflybsd"
elif [[ "$OSTYPE" == "netbsd"* ]]; then
elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$OSTYPE" == "win32" ]]; then
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
elif [[ "$plat" = "MINGW32"* ]]; then
os="win32"
elif [[ "$plat" = "MINGW64"* ]]; then
os="win32"
elif [[ "$OSTYPE" == "msys" ]]; then
elif [[ "$plat" = "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
echo MSYS
#win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#bash reports $OSTYPE msys
os="win32"
#review - need ps/sed/awk to determine shell?
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
@ -564,37 +942,50 @@ elif [[ "$OSTYPE" == "msys" ]]; then
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
else
#os="$OSTYPE"
os="other"
fi
echo ostype: $OSTYPE
shellconfigline=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#echo $shellconfigline;
if [[ $shellconfigline == *"nextshelltype"* ]]; then
echo "found config for os $os"
split1="${shellconfigline#*=}" #remove everything through the first '='
#echo "split1: $split1"
pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match
pathraw="${pathraw//\"/}" #remove quote
nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end)
#echo "nextshellpath: $nextshellpath"
split2="${split1#*=}"
#echo "split2: $split2"
split2="${split2//\"/}"
nextshelltype="${split2/%_*/}"
echo "nextshelltype: $nextshelltype"
## This is the sort of sed that will not work across implementations
## shellconfiglines=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#awk tested on linux & freebsd
shellconfiglines=$( awk '/^:.*<<nextshell_start>>.*$/,/^:.*<<nextshell_end>>.*$/' "$0" | grep $os)
# echo $shellconfiglines;
# readarray requires bash 4.0
if [[ "$ps_shellname" == "bash" ]]; then
readarray -t arr_oslines <<<"$shellconfiglines"
elif [[ "$ps_shellname" == "zsh" ]]; then
arr_oslines=("${(f)shellconfiglines}")
else
echo "unable to find config for os $os"
echo "shellconfigline: $shellconfigline"
nextshellpath=""
nextshelltype=""
#fallback - doesn't seem to work in zsh - untested in early bash
IFS=$'\n' arr_oslines=($shellconfiglines)
fi
nextshellpath=""
nextshelltype=""
for ln in "${arr_oslines[@]}"; do
# echo "---- $ln"
if [[ "$ln" == *"nextshellpath"* ]]; then
splitln="${ln#*=}" #remove everything through the first '='
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
echo "nextshelltype: $nextshelltype"
fi
done
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
#echo bash launching subshell of type $nextshelltype $nextshellpath on "$0"
#/usr/bin/env tclsh "$0" "$@"
echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
@ -792,18 +1183,18 @@ function GetDynamicParamDictionary {
#}
#psmain @args
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
"Script Name : {0}" -f $scriptname | write-host
"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
$thisfileContent = Get-Content $scriptname -Raw
$startTag = ": <<asadmin_start>>"
$endTag = ": <<asadmin_end>>"
$fileContent = Get-Content $scriptname -Raw
$pattern = "(?s)$startTag(.*?)$endTag"
$matches = [regex]::Matches($fileContent,$pattern)
$admininfo = $matches[0].Groups[1].Value
$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
$match = [regex]::Match($thisfileContent,$pattern)
$asadmin = 0
if ($matches.count) {
if ($match.Success) {
$admininfo = $match.Groups[1].Value
$asadmin = $admininfo.Contains("asadmin=1")
if ($asadmin) {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
@ -821,10 +1212,72 @@ if ($matches.count) {
}
}
}
#
$startTag = ": <<nextshell_start>>"
$endTag = ": <<nextshell_end>>"
$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
$match = [regex]::Match($thisfileContent,$pattern)
if ($match.Success) {
$plat = [System.Environment]::OSVersion.Platform
if ($plat -eq "Unix") {
$runtime_ident = [System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier
switch ($runtime_ident.split("-")[0]) {
"freebsd" {
# untested
$os = "freebsd"
}
"linux" {
$os = "linux"
}
"osx" {
# osx-x64 or osx-arm64 ?
$os = "macosx"
}
default {
#openbsd, netbsd ?
$os = "other"
}
}
} else {
$os = "win32"
}
$matchedlines = $match.Groups[1].Value
$nextshell_type = ""
$nextshell_path = ""
ForEach ($line in $($matchedlines -split "\r?\n")) {
$m = [regex]::Match($line,".*nextshelltype\[${os}[_]+\]=([^_]*)[_]*")
if ($m.Success) {
$nextshell_type = $m.Groups[1].Value
}
$m = [regex]::Match($line,".*nextshellpath\[${os}[_]+\]=([^_]*)[_]*")
if ($m.Success) {
$nextshell_path = $m.Groups[1].Value
}
if ($nextshell_type -ne "" -and $nextshell_path -ne "") {
break
}
}
if (-not (("pwsh", "powershell", "") -contains $nextshell_type)) {
#nextshell diversion exists for this platform
write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $scriptname"
# $arguments = @($($MyInvocation.MyCommand.Path))
# $arguments += $args
# NOTE - this gives incorrect argument quoting e.g wrong number of arguments received by launched process for arguments: a "b c"
# $process = (Start-Process -FilePath $nextshell_path -ArgumentList $arguments -NoNewWindow -Wait)
# Exit $process.ExitCode
& $nextshell_path $scriptname $args
exit $LASTEXITCODE
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#<powershell-payload>
Write-Error "No powershell code for this script. Try another program such as perl, tcl or bash"
Write-Error "No powershell code for this script. Try another program such as tcl or bash`n"
"powershell args : {0}" -f ($args -join ", ") | write-host
#</powershell-payload>
#<powershell-pre-launch-subprocess>

317
src/modules/punk/mix/templates/utility/scriptappwrappers/multishell1.cmd

@ -1,5 +1,5 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
@ -16,41 +16,41 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: dev scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM Call with sh, bash, perl, or tclsh. (powershell untested on unix)
@REM Due to lack of shebang (#! line) Unix-like systems will hopefully default to a flavour of sh that can divert to bash if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________"
@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set
@REM If more than 32 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM Supporting more explicit oses than those listed may also require script padding adjustment
: <nextshell>
@SET "nextshellpath[win32___________]=tclsh___________________________"
: <<nextshell_start>>
@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[other___________]=tcl_____________"
: </nextshell>
: <<nextshell_end>>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
: <<asadmin_start>>
@SET "asadmin=0"
: </asadmin>
: <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@ -143,7 +143,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@ECHO Launching script in new window due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -175,43 +175,57 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "%selected_shelltype_trimmed%"=="powershell" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=pwsh
)
@SET "squoted_args="
@for %%a in (%*) do @(
set "v=%%a"
set "v=!v:'=''!"
SET "squoted_args=!squoted_args!'!v!' "
)
@SET "squoted_args=%squoted_args:~0,-1%"
@ECHO %squoted_args%
@IF "!selected_shelltype_trimmed!"=="pwsh" (
REM pwsh vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted 2>NUL; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %squoted_args%
SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; %~dp0%~n0.ps1" %arglist%
REM TODO prompt user with option to call script to install pwsh using winget
REM powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %squoted_args%
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "%selected_shelltype_trimmed%"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
IF "!selected_shelltype_trimmed!"=="powershell" (
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %squoted_args%
SET task_exitcode=!errorlevel!
) ELSE (
REM perl or tcl or sh or bash
IF NOT "x%keyRemoved%"=="x%validshelltypes%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
IF "!selected_shelltype_trimmed!"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
REM perl or tcl or sh or bash
IF NOT "x%keyRemoved%"=="x%validshelltypes%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
@ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
GOTO :exit_multishell
)
)
)
)
@ -342,7 +356,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
:stringToUpper <strvar> <returnvar>
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@ -383,14 +397,15 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 31 underscores from the end of a string using string substitution
@SET trimstring=%trimstring%###
@SET trimstring=%trimstring:________________###=###%
@SET trimstring=%trimstring:________###=###%
@SET trimstring=%trimstring:____###=###%
@SET trimstring=%trimstring:__###=###%
@SET trimstring=%trimstring:_###=###%
@SET trimstring=%trimstring:###=%
@REM trim up to 63 underscores from the end of a string using string substitution
@SET "trimstring=%trimstring%###"
@SET "trimstring=%trimstring:________________________________###=###%"
@SET "trimstring=%trimstring:________________###=###%"
@SET "trimstring=%trimstring:________###=###%"
@SET "trimstring=%trimstring:____###=###%"
@SET "trimstring=%trimstring:__###=###%"
@SET "trimstring=%trimstring:_###=###%"
@SET "trimstring=%trimstring:###=%"
@SET "result=!trimstring!"
@ENDLOCAL & (
@IF "%~2" neq "" (
@ -439,7 +454,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
# -- e.g tclsh filename.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]]
@ -473,6 +488,9 @@ namespace eval ::punk::multishell {
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as perl or bash"
#</tcl-payload>
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
@ -502,8 +520,20 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
#Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations
#echo "script: `echo $0 | sed 's/^-//'`"
# csh/tcsh/sh/bash use oldschool backticks and sed - lowest common denominator \
#echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#csh/tcsh diversion \
test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit )
#other non-bash diversion \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0
#review \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit
# sh/bash \
shift && set -- "${@:1:$#-1}"
echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
#------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
@ -518,10 +548,113 @@ if false==false # else {
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
plat=$(uname -s) #platform/system
if [[ "$plat" = "Linux"* ]]; then
os="linux"
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
elif [[ "$plat" == "FreeBSD"* ]]; then
os="freebsd"
elif [[ "$plat" == "DragonFly"* ]]; then
os="dragonflybsd"
elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
elif [[ "$plat" = "MINGW32"* ]]; then
os="win32"
elif [[ "$plat" = "MINGW64"* ]]; then
os="win32"
elif [[ "$plat" = "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
echo MSYS
#win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#bash reports $OSTYPE msys
os="win32"
#review - need ps/sed/awk to determine shell?
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#use 'command -v' (shell builtin preferred over external which)
shellpath=`command -v $interp`
shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
else
#os="$OSTYPE"
os="other"
fi
echo ostype: $OSTYPE
## This is the sort of sed that will not work across implementations
## shellconfiglines=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#awk tested on linux & freebsd
shellconfiglines=$( awk '/^:.*<<nextshell_start>>.*$/,/^:.*<<nextshell_end>>.*$/' "$0" | grep $os)
#echo $shellconfiglines;
readarray -t arr_oslines <<<"$shellconfiglines"
nextshellpath=""
nextshelltype=""
for ln in "${arr_oslines[@]}"; do
if [[ "$ln" == *"nextshellpath"* ]]; then
splitln="${ln#*=}" #remove everything through the first '='
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
fi
done
#if [[ $shellconfigline == *"nextshelltype"* ]]; then
# #echo "found config for os $os"
# split1="${shellconfigline#*=}" #remove everything through the first '='
# #echo "split1: $split1"
# pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match
# pathraw="${pathraw//\"/}" #remove quote
# nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end)
# #echo "nextshellpath: $nextshellpath"
# split2="${split1#*=}"
# #echo "split2: $split2"
# split2="${split2//\"/}"
# nextshelltype="${split2/%_*/}"
# echo "nextshelltype: $nextshelltype"
#else
# echo "unable to find config for os $os"
# echo "shellconfigline: $shellconfigline"
# nextshellpath=""
# nextshelltype=""
#fi
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
#echo bash launching subshell of type $nextshelltype $nextshellpath on "$0"
#/usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
#echo "sh/bash reporting exitcode: ${exitcode}"
exit $exitcode
#-- override exitcode example
#exit 66
else
#already in bash - don't launch another process or we would loop
#echo "bash payload"
:
fi
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
#printf "start of bash or sh code"
#<shell-payload>
echo "No bash code for this script. Try another program such as perl or tcl" >&2
#</shell-payload>
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
@ -531,8 +664,8 @@ exitcode=0
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#/usr/bin/env tclsh "$0" "$@"
#exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
@ -558,8 +691,18 @@ exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
use Cwd qw(abs_path);
my $scriptname = abs_path($0);
#print "perl $scriptname\n";
my $os = "$^O";
if ($os eq "MSWin32") {
$os = "win32";
} elsif ($os eq "darwin") {
$os = "macosx";
}
print "os $os\n";
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
@ -571,13 +714,15 @@ my $exit_code = 0;
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-payload>
print STDERR "No perl code for this script. Try another program such as tcl or bash";
#</perl-payload>
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
@ -585,7 +730,7 @@ foreach my $a(@ARGV) {
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
@ -648,12 +793,14 @@ function GetDynamicParamDictionary {
return $DynParamDictionary
}
}
# Example usage:
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# [Parameter(Mandatory)][string] $myargument,
# [Parameter(ValueFromRemainingArguments)] $opts
# )
#}
#function psmain {
@ -664,10 +811,15 @@ function GetDynamicParamDictionary {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# 'myargument' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# 'opts' {
# write-warning "Unused parameters: $($PSBoundParameters.$_)"
# }
# Default {
# write-warning "Unhandled parameter -> [$($_)]"
# }
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
@ -675,17 +827,46 @@ function GetDynamicParamDictionary {
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# Write-Host "myargument value is: $myargument"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
$startTag = ": <<asadmin_start>>"
$endTag = ": <<asadmin_end>>"
$fileContent = Get-Content $scriptname -Raw
$pattern = "(?s)$startTag(.*?)$endTag"
$matches = [regex]::Matches($fileContent,$pattern)
$admininfo = $matches[0].Groups[1].Value
$asadmin = 0
if ($matches.count) {
$asadmin = $admininfo.Contains("asadmin=1")
if ($asadmin) {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
# If not elevated, relaunch with elevated privileges
# -Wait e.g for starting a service or other operations which remainder of script may depend on
$arguments = @("-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass")
$arguments += @("-File", $($MyInvocation.MyCommand.Path))
$arguments += $args
if ($PSVersionTable.PSEdition -eq 'Core') {
Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -Wait -Verb RunAs
} else {
Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Wait -Verb RunAs
}
Exit # Exit the current non-elevated process
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#<powershell-payload>
Write-Error "No powershell code for this script. Try another program such as perl, tcl or bash"
#</powershell-payload>
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
@ -693,7 +874,7 @@ function GetDynamicParamDictionary {
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
tclsh $scriptname $args
#tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---

346
src/modules/punk/mix/templates/utility/scriptappwrappers/multishell2.cmd

@ -1,41 +1,65 @@
: "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. (close sqote for unix shells) ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl \
: "[Hide @ECHO; Hide ); Hide (;Hide echo; Hide @REM]#not necessary but can help avoid errs in testing"
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH'
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section.
: shebang line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@GOTO :skip_perl_pod_start ^;
=begin excludeperl
: skip_perl_pod_start
: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \
: {
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT. shebang #! line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: pmix scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set
@REM If more than 32 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM Supporting more explicit oses than those listed may also require script padding adjustment
: <nextshell>
@SET "nextshell=13"
@SET "nextshellpath[win32___________]=tclsh___________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[other___________]=tcl_____________"
: </nextshell>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
@SET "asadmin=0"
: </asadmin>
@REM nextshell set to index for validshells .eg 10 for pwsh
@REM @ECHO nextshell is %nextshell%
@SET "selected=!shells[%nextshell%]!"
@REM @ECHO selected %selected%
@CALL SET "keyRemoved=%%validshells:'!selected!'=%%"
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@REM @ECHO selected_shelltype %selected_shelltype%
@CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed
@REM @ECHO selected_shelltype_trimmed %selected_shelltype_trimmed%
@SET "selected_shellpath=%nextshellpath[win32___________]%"
@CALL :stringTrimTrailingUnderscores %selected_shellpath% selected_shellpath_trimmed
@CALL SET "keyRemoved=%%validshelltypes:!selected_shelltype!=%%"
@REM @ECHO keyremoved %keyRemoved%
@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@ -49,16 +73,16 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- pmix scriptwrap.checkfile <filepath>
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using pmix scriptwrap.checkfile is still recommended.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended.
@REM -- Alternatively, as you should do anyway - test the final script on windows
@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations.
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and pmix scriptwrap.checkfile doesn't check all such boundaries.
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@ -89,22 +113,36 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%*
@IF "%1"=="PUNK-ELEVATED" (
@SET "qstrippedargs=args%arglist%"
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
GOTO :gotPrivileges
)
@IF !asadmin!==1 (
net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@GOTO skip_privileges
:getPrivileges
@IF '%1'=='PUNK-ELEVATED' (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0.cmd", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -113,7 +151,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "%1"=="PUNK-ELEVATED" (
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
@ -124,7 +162,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >nul || goto different
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -134,10 +172,10 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >NUL
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
@IF "%selected_shelltype_trimmed%"=="powershell" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
@ -145,7 +183,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
@ -153,24 +192,31 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!shells[%nextshell%]!"=="bash" (
IF "%selected_shelltype_trimmed%"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
REM perl or tcl or sh or bash
IF NOT "x%keyRemoved%"=="x%validshelltypes%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
!shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@ -179,7 +225,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET "name=%~nx1"
@SET "drive=%~d1"
@SET "rtrn=%~2"
@SET "result=/mnt/%drive:~0,1%%_path:\=/%%name%"
@REM Although drive letters on windows are normally upper case wslbash seems to expect lower case drive letters
@CALL :stringToLower %drive ldrive
@SET "result=/mnt/%ldrive:~0,1%%_path:\=/%%name%"
@ENDLOCAL & (
@if "%~2" neq "" (
SET "%rtrn%=%result%"
@ -227,6 +275,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -245,6 +294,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -289,7 +340,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
@SETLOCAL
@SET "rtrn=%~2"
@ -307,7 +359,47 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
)
@EXIT /B
:stringToLower
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "retstring=%~1"
@FOR %%A in (a b c d e f g h i j k l m n o p q r s t u v w x y z) DO @(
@SET "retstring=!retstring:%%A=%%A!"
)
@SET "result=!retstring!"
@ENDLOCAL & (
@IF "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO stringToLower %string% result: %result%
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 31 underscores from the end of a string using string substitution
@SET trimstring=%trimstring%###
@SET trimstring=%trimstring:________________###=###%
@SET trimstring=%trimstring:________###=###%
@SET trimstring=%trimstring:____###=###%
@SET trimstring=%trimstring:__###=###%
@SET trimstring=%trimstring:_###=###%
@SET trimstring=%trimstring:###=%
@SET "result=!trimstring!"
@ENDLOCAL & (
@IF "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO stringTrimTrailingUnderscores %string% result: %result%
)
)
@EXIT /B
:isNumeric
@SETLOCAL
@SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i"
@ -328,6 +420,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
:endlib
: \
@REM padding
@REM padding
@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell
@GOTO :exit_multishell
# }
@ -348,9 +442,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${argv0}/__]]
set last_script_root [file dirname [file normalize ${::argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
if {[info exists argv0] &&
if {[info exists ::argv0] &&
$last_script eq $last_script_root
} {
set ::punk::multishell::is_main($last_script) 1 ;#run as executable/script - likely desirable to launch application and return an exitcode
@ -365,7 +459,7 @@ namespace eval ::punk::multishell {
if {![info exists ::punk::multishell::is_main($script_name)]} {
#e.g a .dll or something else unanticipated
puts stderr "Warning punk::multishell didn't recognize info script result: $script_name - will treat as if sourced and return instead of exiting"
puts stderr "Info: script_root: [file dirname [file normalize ${argv0}/__]]"
puts stderr "Info: script_root: [file dirname [file normalize ${::argv0}/__]]"
return 0
}
return [set ::punk::multishell::is_main($script_name)]
@ -380,10 +474,16 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
#</tcl-payload>
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subprocess>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
@ -414,33 +514,33 @@ if false==false # else {
# -- leave as is if all that is required is launching the Tcl payload"
# --
# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default
# -- adjust @call line above ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate
# -- adjust the %nextshell% value above
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-launch-tcl>
exitcode=0 ;#default assumption
#<shell-launch-subprocess>
#-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#echo "tcl exitcode: ${exitcode}"
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-tcl>
#</shell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#<shell-post-launch-subprocess>
#</shell-post-launch-subprocess>
#printf "sh/bash done \n"
@ -448,7 +548,57 @@ exitcode=$?
#------------------------------------------------------
fi
exit ${exitcode}
# end hide sh/bash block from Tcl
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# This comment with closing brace should stay in place whether if commented or not }
#------------------------------------------------------
# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above
@ -460,9 +610,76 @@ if 0 {
# -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy
# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above
# -- custom script should generally go below the begin_powershell_payload line
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
function GetScriptName { $myInvocation.ScriptName }
$scriptname = getScriptName
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
@ -470,22 +687,22 @@ $scriptname = getScriptName
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-launch-tcl>
#<powershell-launch-subprocess>
tclsh $scriptname $args
#</powershell-launch-tcl>
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
@ -498,7 +715,7 @@ $1 = @'
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "%1"=="PUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
@ -509,6 +726,7 @@ $1 = @'
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>


240
src/modules/punk/mix/templates/utility/scriptappwrappers/multishell3.cmd

@ -1,34 +1,29 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. (close sqote for unix shells) ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl \
: "[Hide @ECHO; Hide ); Hide (;Hide echo; Hide @REM]#not necessary but can help avoid errs in testing"
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH'
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section.
: shebang line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@GOTO :skip_perl_pod_start ^;
=begin excludeperl
: skip_perl_pod_start
: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \
: {
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT. shebang #! line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM e.g from within a running punkshell: pmix scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh' (^14^) 'perl'"
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "shells[14]=perl"
: <nextshell>
@SET "nextshell=13"
: </nextshell>
@ -54,16 +49,16 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- pmix scriptwrap.checkfile <filepath>
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using pmix scriptwrap.checkfile is still recommended.
@REM -- Alternatively, as you should do anyway - test the final script on windows
@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations.
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REM -- It is unknown what versions of cmd interpreters behave this way - and pmix scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@ -94,40 +89,22 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
)
@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%*
@SET "qstrippedargs=args%arglist%"
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@IF "%1"=="PUNK-ELEVATED" (
GOTO :gotPrivileges
)
@IF !asadmin!==1 (
net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@IF '%1'=='PUNK-ELEVATED' (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0.cmd", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -136,7 +113,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@IF "%1"=="PUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
@ -147,7 +124,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
fc "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -157,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
COPY "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
@ -168,8 +145,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
@ -180,26 +156,21 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
IF "!shells[%nextshell%]!"=="bash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
!shells[%nextshell%]! "%~dp0%fname%" %arglist%
SET task_exitcode=!errorlevel!
!shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@ -256,7 +227,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -275,8 +245,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -412,15 +380,9 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subproces>
#<tcl-payload>
#</tcl-payload>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subproces>
# -- --- --- --- --- --- --- --- --- --- --- ---
@ -452,33 +414,33 @@ if false==false # else {
# -- leave as is if all that is required is launching the Tcl payload"
# --
# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default
# -- adjust the %nextshell% value above
# -- adjust @call line above ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<shell-launch-subprocess>
#<shell-launch-tcl>
exitcode=0 ;#default assumption
#-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#echo "tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-subprocess>
#</shell-launch-tcl>
# -- --- --- --- --- --- --- ---
#<shell-post-launch-subprocess>
#</shell-post-launch-subproces>
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#printf "sh/bash done \n"
@ -486,57 +448,7 @@ exitcode=$?
#------------------------------------------------------
fi
exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# end hide sh/bash block from Tcl
# This comment with closing brace should stay in place whether if commented or not }
#------------------------------------------------------
# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above
@ -548,76 +460,9 @@ if 0 {
# -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy
# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above
# -- custom script should generally go below the begin_powershell_payload line
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
function GetScriptName { $myInvocation.ScriptName }
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
$scriptname = getScriptName
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
@ -625,22 +470,22 @@ function GetDynamicParamDictionary {
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
#<powershell-launch-tcl>
tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
#</powershell-launch-tcl>
# -- --- --- --- --- --- --- ---
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
@ -653,7 +498,7 @@ $1 = @'
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
@IF "%1"=="PUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
@ -664,7 +509,6 @@ $1 = @'
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>


680
src/modules/punk/mix/templates/utility/scriptappwrappers/multishell4.cmd

@ -0,0 +1,680 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH'
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section.
: shebang line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@GOTO :skip_perl_pod_start ^;
=begin excludeperl
: skip_perl_pod_start
: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \
: {
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh' (^14^) 'perl'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "shells[14]=perl"
: <nextshell>
@SET "nextshell=13"
: </nextshell>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
@SET "asadmin=0"
: </asadmin>
@REM nextshell set to index for validshells .eg 10 for pwsh
@REM @ECHO nextshell is %nextshell%
@SET "selected=!shells[%nextshell%]!"
@REM @ECHO selected %selected%
@CALL SET "keyRemoved=%%validshells:'!selected!'=%%"
@REM @ECHO keyremoved %keyRemoved%
@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@REM -- cmd/batch file section (ignored on unix but should be left in place)
@REM -- This section intended mainly to launch the next shell (and to escalate privileges if necessary)
@REM -- Avoid customising this if you are not familiar with batch scripting. cmd/batch script can be useful, but is probably the least expressive language and most error prone.
@REM -- For example - as this file needs to use unix-style lf line-endings - the label scanner is susceptible to the 512Byte boundary issue: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888
@REM -- This label issue can be triggered/abused in files with crlf line endings too - but it is less likely to happen accidentaly.
@REm -- See also: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133
@REM ############################################################################################################################
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended.
@REM -- Alternatively, as you should do anyway - test the final script on windows
@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations.
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@SET "winpath=%~dp0"
@SET "fname=%~nx0"
@REM @ECHO fname %fname%
@REM @ECHO winpath %winpath%
@REM @ECHO commandlineascalled %0
@REM @ECHO commandlineresolved %~f0
@CALL :getNormalizedScriptTail nftail
@REM @ECHO normalizedscripttail %nftail%
@CALL :getFileTail %0 clinetail
@REM @ECHO clinetail %clinetail%
@CALL :stringToUpper %~nx0 capscripttail
@REM @ECHO capscriptname: %capscripttail%
@IF "%nftail%"=="%capscripttail%" (
@ECHO forcing asadmin=1 due to file name on filesystem being uppercase
@SET "asadmin=1"
) else (
@CALL :stringToUpper %clinetail% capcmdlinetail
@REM @ECHO capcmdlinetail !capcmdlinetail!
IF "%clinetail%"=="!capcmdlinetail!" (
@ECHO forcing asadmin=1 due to cmdline scriptname in uppercase
@set "asadmin=1"
)
)
@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%*
@SET "qstrippedargs=args%arglist%"
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
GOTO :gotPrivileges
)
@IF !asadmin!==1 (
net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
:gotPrivileges
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
:skip_privileges
@SET need_ps1=0
@REM we want the ps1 to exist even if the nextshell isn't powershell
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@GOTO :pscontinue
:different
@REM @ECHO "files differ"
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; %~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!shells[%nextshell%]!"=="bash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
!shells[%nextshell%]! "%~dp0%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@SETLOCAL
@SET "_path=%~p1"
@SET "name=%~nx1"
@SET "drive=%~d1"
@SET "rtrn=%~2"
@SET "result=/mnt/%drive:~0,1%%_path:\=/%%name%"
@ENDLOCAL & (
@if "%~2" neq "" (
SET "%rtrn%=%result%"
) ELSE (
ECHO %result%
)
)
@EXIT /B
:getFileTail
@REM return tail of file without any normalization e.g c:/punkshell/bin/Punk.cmd returns Punk.cmd even if file is punk.cmd
@REM we can't use things such as %~nx1 as it can change capitalisation
@REM This function is designed explicitly to preserve capitalisation
@REM accepts full paths with either / or \ as delimiters - or
@SETLOCAL
@SET "rtrn=%~2"
@SET "arg=%~1"
@REM @SET "result=%_arg:*/=%"
@REM @SET "result=%~1"
@SET LF=^
: The above 2 empty lines are important. Don't remove
@CALL :stringContains "!arg!" "\" hasBackSlash
@IF "!hasBackslash!"=="true" (
@for %%A in ("!LF!") do @(
@FOR /F %%B in ("!arg:\=%%~A!") do @set "result=%%B"
)
) ELSE (
@CALL :stringContains "!arg!" "/" hasForwardSlash
@IF "!hasForwardSlash!"=="true" (
@FOR %%A in ("!LF!") do @(
@FOR /F %%B in ("!arg:/=%%~A!") do @set "result=%%B"
)
) ELSE (
@set "result=%arg%"
)
)
@ENDLOCAL & (
@if "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO %result%
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@SET "rtrn=%~1"
@ENDLOCAL & (
@IF "%~1" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO %result%
)
)
@EXIT /B
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@IF "%hasBackslash%-%hasForwardslash%"=="false-false" (
@SET "P=%cd%%~1"
@CALL :getNormalizedFileTailFromPath "!P!" ftail2
@SET "result=!ftail2!"
) else (
@IF EXIST "%~1" (
@SET "result=%~nx1"
) else (
@ECHO error getNormalizedFileTailFromPath file not found: %~1
@EXIT /B 1
)
)
@SET "rtrn=%~2"
@ENDLOCAL & (
@IF "%~2" neq "" (
SET "%rtrn%=%result%"
) ELSE (
@ECHO getNormalizedFileTailFromPath %1 result: %result%
)
)
@EXIT /B
:stringContains
@REM usage: @CALL:stringContains string needle returnvarname
@SETLOCAL
@SET "rtrn=%~3"
@SET "string=%~1"
@SET "needle=%~2"
@IF "!string:%needle%=!"=="!string!" @(
@SET "result=false"
) ELSE (
@SET "result=true"
)
@ENDLOCAL & (
@IF "%~3" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO stringContains %string% %needle% result: %result%
)
)
@EXIT /B
:stringToUpper
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "capstring=%~1"
@FOR %%A in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO @(
@SET "capstring=!capstring:%%A=%%A!"
)
@SET "result=!capstring!"
@ENDLOCAL & (
@IF "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO stringToUpper %string% result: %result%
)
)
@EXIT /B
:isNumeric
@SETLOCAL
@SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i"
@IF defined notnumeric (
@SET "result=false"
) else (
@SET "result=true"
)
@SET "rtrn=%~2"
@ENDLOCAL & (
@IF "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO %result%
)
)
@EXIT /B
:endlib
: \
@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell
@GOTO :exit_multishell
# }
# -*- tcl -*-
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- tcl script section
# -- This is a punk multishell file
# -- Primary payload target is Tcl, with sh,bash,powershell as helpers
# -- but it may equally be used with any of these being the primary script.
# -- It is tuned to run when called as a batch file, a tcl script a sh/bash script or a pwsh/powershell script
# -- i.e it is a polyglot file.
# -- The specific layout including some lines that appear just as comments is quite sensitive to change.
# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline.
# -- e.g ./filename.polypunk.cmd in sh or bash
# -- e.g tclsh filename.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
if {[info exists argv0] &&
$last_script eq $last_script_root
} {
set ::punk::multishell::is_main($last_script) 1 ;#run as executable/script - likely desirable to launch application and return an exitcode
} else {
set ::punk::multishell::is_main($last_script) 0 ;#sourced - likely to be being used as a library - no launch, no exit. Can use return.
}
if {"::punk::multishell::is_main" ni [info commands ::punk::multishell::is_main]} {
proc ::punk::multishell::is_main {{script_name {}}} {
if {$script_name eq ""} {
set script_name [file dirname [file normalize [info script]/--]]
}
if {![info exists ::punk::multishell::is_main($script_name)]} {
#e.g a .dll or something else unanticipated
puts stderr "Warning punk::multishell didn't recognize info script result: $script_name - will treat as if sourced and return instead of exiting"
puts stderr "Info: script_root: [file dirname [file normalize ${argv0}/__]]"
return 0
}
return [set ::punk::multishell::is_main($script_name)]
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload
#puts "script : [info script]"
#puts "argcount : $::argc"
#puts "argvalues: $::argv"
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subproces>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subproces>
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
# -- If the multishell script is modified to have Tcl below the Tcl Payload section,
# -- then Tcl bracket balancing needs to be carefully managed in the shell and powershell sections below.
# -- Only the # in front of the two relevant if statements below needs to be removed to enable Tcl below
# -- but the sh/bash 'then' and 'fi' would also need to be uncommented.
# -- This facility left in place for experiments on whether configuration payloads etc can be appended
# -- to tail of file - possibly binary with ctrl-z char - but utility is dependent on which other interpreters/shells
# -- can be made to ignore/cope with such data.
if {[::punk::multishell::is_main]} {
exit 0
} else {
return
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# sh/bash \
shift && set -- "${@:1:$#-1}"
#------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
then
: #
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- sh/bash script section
# -- leave as is if all that is required is launching the Tcl payload"
# --
# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default
# -- adjust the %nextshell% value above
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-launch-subprocess>
#-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-post-launch-subprocess>
#</shell-post-launch-subproces>
#printf "sh/bash done \n"
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload
#------------------------------------------------------
fi
exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# This comment with closing brace should stay in place whether if commented or not }
#------------------------------------------------------
# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above
if 0 {
: end heredoc1 - end hide from powershell \
'@
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- powershell/pwsh section
# -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy
# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above
# -- custom script should generally go below the begin_powershell_payload line
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
function GetScriptName { $myInvocation.ScriptName }
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
'
: comment end hide powershell-block from Tcl \
# This comment with closing brace should stay in place whether 'if' commented or not }
: multishell doubled-up cmd exit label - return exitcode
:exit_multishell
:exit_multishell
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
: comment end heredoc2 \
'@
<#
# id:tailblock0
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>

# </ctrl-z>
# -- unreachable by tcl directly if ctrl-z character is in the <ctrl-z> section above. (but file can be read and split on \x1A)
# -- Potential for zip and/or base64 contents, but we can't stop pwsh parser from slurping in the data
# -- so for example a plain text tar archive could cause problems depending on the content.
# -- final line in file must be the powershell multiline comment terminator or other data it can handle.
# -- e.g plain # comment lines will work too
# -- (for example a powershell digital signature is a # commented block of data at the end of the file)
#>

2
src/modules/punk/repl-999999.0a1.0.tm

@ -2418,6 +2418,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
#if {[lindex $command 0] eq "runx"} {}
#temporary hack.
#todo - use happy path return options for non-primary result (like www package) ?
if {
[string equal -length [string length "d/ "] "d/ " $commandstr] || \
[string equal "d/\n" $commandstr] || \

44
src/modules/punk/zip-999999.0a1.0.tm

@ -283,9 +283,47 @@ tcl::namespace::eval punk::zip {
#if there is an external preamble - extract that. (if there is also an internal preamble - ignore and consider part of the archive-data)
#Otherwise extract an internal preamble.
#if neither -
#if neither -?
#review - reconsider auto-determination of internal vs external preamble
proc extract_preamble {infile outfile_preamble {outfile_zip ""}} {
punk::args::define {
@id -id ::punk::zip::extract_preamble
@cmd -name punk::zip::extract_preamble -help\
"Split a zipfs based executable or library into its constituent
binary and zip parts.
Note that the binary preamble might be either 'within' the zip offsets,
or simply catenated prior to an unadjusted zip.
Some build processes may have 'adjusted' the zip offsets to make the zip cover the entire file
('file based' offset) whilst the more modern approach is to simply concatenate the binary and the zip
('archive based' offset). An archive-based offset is simpler and more reliably points to the proper
split location. It also allows 'zipfs info //zipfs:/app' to return the correct offset information.
Either way, extract_preamble can usually separate them, but in the unusual case that there is both an
external preamble and a preamble within the zip, only the external preamble will be split, with the
internal one remaining in the zip.
The inverse of this process would be to extract the .zip file created by this split to a folder,
e.g extracted_zip_folder (adjusting contents as required) and then to run:
zipfs mkimg newbinaryname.exe extracted_zip_folder <prefix> \"\" <extracted_preamble_or_alternative exe>
"
@values -min 2 -max 3
infile -type file -optional 0 -help\
"Name of existing tcl executable or shared lib with attached zipfs filesystem"
outfile_preamble -optional 0 -type file -help\
"Name of output file for binary preamble to be extracted to.
If this file already exists, an error will be raised"
outfile_zip -default "" -type file -help\
"Name of output file for zip data to be extracted to.
If this file already exists, an error will be raised"
}
proc extract_preamble {args} {
set argd [punk::args::parse $args withid ::punk::zip::extract_preamble]
lassign [dict values $argd] leaders opts values received
set infile [dict get $values infile]
set outfile_preamble [dict get $values outfile_preamble]
set outfile_zip [dict get $values outfile_zip]
set inzip [open $infile r]
fconfigure $inzip -encoding iso8859-1 -translation binary
if {[file exists $outfile_preamble]} {
@ -346,7 +384,7 @@ tcl::namespace::eval punk::zip {
#we could scan from top (ugly) - and with binary prefixes we could get false positives in the data that look like PK\3\4 headers
#we could either work out the format for all possible executables that could be appended (across all platforms) and understand where they end?
#or we just look for the topmost PK\3\4 header pointed to by a CDR record - and assume the CDR is complete
#step one - read all the CD records and find the highest pointed to local file record (which isn't necessarily the first - but should get us above most if not all of the zip data)
#we can't assume they're ordered in any particular way - so we in theory have to look at them all.
set baseoffset "unknown"

2
src/modules/shellrun-0.1.1.tm

@ -427,7 +427,7 @@ namespace eval shellrun {
cmdarg -type any -multiple 1 -optional 1
}]
proc runerr {args} {
set argd [punk::args::parse $args withid ::shellrun::runout]
set argd [punk::args::parse $args withid ::shellrun::runerr]
lassign [dict values $argd] leaders opts values received
if {[dict exists $received "-nonewline"]} {

93
src/project_layouts/custom/_project/punk.basic/src/make.tcl

@ -195,8 +195,8 @@ namespace eval ::punkboot::lib {
}
x86_64 {
if {$tcl_platform(wordSize) == 4} {
# See Example <1> at the top of this file.
set cpu ix86
# See Example <1> at the top of this file.
set cpu ix86
}
}
ppc -
@ -216,26 +216,26 @@ namespace eval ::punkboot::lib {
switch -glob -- $plat {
windows {
if {$tcl_platform(platform) == "unix"} {
set plat cygwin
set plat cygwin
} else {
set plat win32
set plat win32
}
if {$cpu eq "amd64"} {
# Do not check wordSize, win32-x64 is an IL32P64 platform.
set cpu x86_64
# Do not check wordSize, win32-x64 is an IL32P64 platform.
set cpu x86_64
}
}
sunos {
set plat solaris
if {[string match "ix86" $cpu]} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
} elseif {![string match "ia64*" $cpu]} {
# sparc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
# sparc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
darwin {
@ -243,24 +243,24 @@ namespace eval ::punkboot::lib {
# Correctly identify the cpu when running as a 64bit
# process on a machine with a 32bit kernel
if {$cpu eq "ix86"} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
}
}
aix {
set cpu powerpc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
append cpu 64
}
}
hp-ux {
set plat hpux
if {![string match "ia64*" $cpu]} {
set cpu parisc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
set cpu parisc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
osf1 {
@ -2296,8 +2296,23 @@ if {$::punkboot::command eq "bin"} {
}
#find runtimes
set rtfolder $sourcefolder/runtime
#set rtfolder $sourcefolder/runtime
#AAA
switch -glob -- $this_platform_generic {
macosx-* {
#assuming universal binaries x86_64 and arm
set rt_os_arch macosx
}
default {
set rt_os_arch $this_platform_generic
}
}
set rtfolder $binfolder/runtime/$rt_os_arch
set rt_sourcefolder $sourcefolder/runtime ;#where our config lives
#review - when building kits for other platforms - it's unlikely runtime will be marked as executable - we should probably process all files in runtime folder except those with certain extensions
set rtfolder_files [glob -nocomplain -dir $rtfolder -types {f} -tail *]
set exclusions {.config .md .ico .txt .doc .pdf .htm .html} ;#we don't encourage other files in runtime folder aside from mapvfs.config - but lets ignore some common possibilities
lappend exclusions .zip .7z .pea .bz2 .tar .gz .tgz .z .xz ;#don't allow archives to directly be treated as runtimes - tolerate presence but require user to unpack or rename if they're to be used as runtimes
@ -2311,7 +2326,7 @@ foreach f $rtfolder_files {
}
if {![llength $runtimes]} {
puts stderr "No executable runtimes found in $rtfolder - unable to build any .vfs folders into executables."
puts stderr "Add runtimes to $sourcefolder/runtime if required"
puts stderr "Add runtimes to $rtfolder if required"
#todo - don't exit - it is valid to use runtime of - to just build a .kit/.zipkit ?
exit 0
}
@ -2350,7 +2365,7 @@ if {$sdxpath eq ""} {
#build a dict keyed on runtime executable name.
#If no mapfile (or no mapfile entry for that runtime) - the runtime will be paired with a matching .vfs folder in src folder. e.g punk.exe to src/punk.vfs
#If vfs folders or runtime executables which are explicitly listed in the mapfile don't exist - warn on stderr - but continue. if such nonexistants found; prompt user for whether to continue or abort.
set mapfile $rtfolder/mapvfs.config
set mapfile $rt_sourcefolder/mapvfs.config
set runtime_vfs_map [dict create]
set vfs_runtime_map [dict create]
if {[file exists $mapfile]} {
@ -2891,8 +2906,8 @@ foreach vfstail $vfs_tails {
set extract_kit_type ""
while {!$extraction_done && [llength $extraction_trylist]} {
set extract_kit_type [lpop extraction_trylist 0]
switch -- $extract_kit_type {
set extract_kit_try [lpop extraction_trylist 0]
switch -- $extract_kit_try {
zip - zipcat {
#for a zipkit - we need to extract the existing vfs from the runtime
#zipfs mkimg replaces the entire zipped vfs in the runtime - so we need the original data to be part of our targetvfs.
@ -2972,12 +2987,12 @@ foreach vfstail $vfs_tails {
zipfile::decode::unzip $archiveinfo $extractedzipfolder
}]} {
set extraction_done 1
set extract_kit_type $extract_kit_try
#todo - verify that init.tcl etc are present?
merge_over $extractedzipfolder $targetvfs
}
#todo - verify that init.tcl etc are present?
merge_over $extractedzipfolder $targetvfs
}
}
cookit - cookfs {
#upx easy enough to deal with if we have/install a upx executable (avail on windows via scoop, freshports for freebsd and presumably various for linux)
@ -3006,17 +3021,20 @@ foreach vfstail $vfs_tails {
#copy from mounted runtime's vfs to the filesystem vfs
merge_over $rtmountpoint $targetvfs
set extraction_done 1
set extract_kit_type $extract_kit_try
}
}
}
kit {
if {!$have_sdx} {
puts stderr "no sdx available to wrap $targetkit"
lappend failed_kits [list kit $targetkit reason "sdx_executable_unavailable"]
$vfs_event targetset_end FAILED
$vfs_event destroy
$vfs_installer destroy
continue
puts stderr "no sdx available to unwrap $targetkit"
#don't add to failed_kits here
#extraction fail for one type doesn't mean we have fully failed yet
#lappend failed_kits [list kit $targetkit reason "sdx_executable_unavailable"]
#$vfs_event targetset_end FAILED
#$vfs_event destroy
#$vfs_installer destroy
continue ;#to next extraction attempt
}
set raw_runtime $buildfolder/raw_$runtime_fullname
@ -3046,6 +3064,7 @@ foreach vfstail $vfs_tails {
merge_over [file rootname $building_runtime].vfs $targetvfs
}
set extraction_done 1
set extract_kit_type $extract_kit_try
file copy -force $building_runtime $raw_runtime
}
cd $prev_cwd
@ -3056,8 +3075,10 @@ foreach vfstail $vfs_tails {
if {!$extraction_done} {
#TODO: if not extracted - use a default tcl_library for patchlevel and platform?
puts stderr "WARNING: No extraction done from runtime $runtime_fullname"
puts stderr "--------------------------------------------"
puts stderr "\x1b\31mWARNING: No extraction done from runtime $runtime_fullname\x1b\[m"
puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!"
puts stderr "--------------------------------------------"
file mkdir $targetvfs
}

1
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config

@ -99,5 +99,6 @@ set bootsupport_modules [list\
modules natsort\
modules oolib\
modules zipper\
modules zzzload\
]

131
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/zzzload-0.1.0.tm

@ -0,0 +1,131 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2023
#
# @@ Meta Begin
# Application zzzload 0.1.0
# Meta platform tcl
# Meta license BSD
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
package require Thread
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval zzzload {
variable loader_tid "" ;#thread id
proc stacktrace {} {
set stack "Stack trace:\n"
for {set i 1} {$i < [info level]} {incr i} {
set lvl [info level -$i]
set pname [lindex $lvl 0]
append stack [string repeat " " $i]$pname
if {![catch {info args $pname} pargs]} {
foreach value [lrange $lvl 1 end] arg $pargs {
if {$value eq ""} {
if {$arg != 0} {
info default $pname $arg value
}
}
append stack " $arg='$value'"
}
} else {
append stack " !unknown vars for $pname"
}
append stack \n
}
return $stack
}
proc pkg_require {pkgname args} {
variable loader_tid
if {[set ver [package provide twapi]] ne ""} {
#skip the whole shebazzle if it's already loaded
return $ver
}
if {$loader_tid eq ""} {
set loader_tid [thread::create -joinable -preserved]
}
if {![tsv::exists zzzload_pkg $pkgname]} {
#puts stderr "zzzload pkg_require $pkgname"
#puts [stacktrace]
tsv::set zzzload_pkg $pkgname "loading"
tsv::set zzzload_pkg_mutex $pkgname [thread::mutex create]
set cond [thread::cond create]
tsv::set zzzload_pkg_cond $pkgname $cond
thread::send -async $loader_tid [string map [list <pkg> $pkgname <cond> $cond] {
if {![catch {package require <pkg>} returnver]} {
tsv::set zzzload_pkg <pkg> $returnver
} else {
tsv::set zzzload_pkg <pkg> "failed"
}
thread::cond notify <cond>
}]
return "loading"
} else {
return [tsv::get zzzload_pkg $pkgname]
}
}
proc pkg_wait {pkgname} {
if {[set ver [package provide twapi]] ne ""} {
return $ver
}
set pkgstate [tsv::get zzzload_pkg $pkgname]
if {$pkgstate eq "loading"} {
set mutex [tsv::get zzzload_pkg_mutex $pkgname]
thread::mutex lock $mutex
set cond [tsv::get zzzload_pkg_cond $pkgname]
while {[tsv::get zzzload_pkg $pkgname] eq "loading"} {
thread::cond wait $cond $mutex 3000
}
set result [tsv::get zzzload_pkg $pkgname]
thread::mutex unlock $mutex
return $result
} else {
return $pkgstate
}
}
proc shutdown {} {
variable loader_tid
if {[thread::exists $loader_tid]} {
thread::release $loader_tid
thread::join $loader_tid
set loader_tid ""
}
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide zzzload [namespace eval zzzload {
variable version
set version 0.1.0
}]
return

93
src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl

@ -195,8 +195,8 @@ namespace eval ::punkboot::lib {
}
x86_64 {
if {$tcl_platform(wordSize) == 4} {
# See Example <1> at the top of this file.
set cpu ix86
# See Example <1> at the top of this file.
set cpu ix86
}
}
ppc -
@ -216,26 +216,26 @@ namespace eval ::punkboot::lib {
switch -glob -- $plat {
windows {
if {$tcl_platform(platform) == "unix"} {
set plat cygwin
set plat cygwin
} else {
set plat win32
set plat win32
}
if {$cpu eq "amd64"} {
# Do not check wordSize, win32-x64 is an IL32P64 platform.
set cpu x86_64
# Do not check wordSize, win32-x64 is an IL32P64 platform.
set cpu x86_64
}
}
sunos {
set plat solaris
if {[string match "ix86" $cpu]} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
} elseif {![string match "ia64*" $cpu]} {
# sparc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
# sparc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
darwin {
@ -243,24 +243,24 @@ namespace eval ::punkboot::lib {
# Correctly identify the cpu when running as a 64bit
# process on a machine with a 32bit kernel
if {$cpu eq "ix86"} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
}
}
aix {
set cpu powerpc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
append cpu 64
}
}
hp-ux {
set plat hpux
if {![string match "ia64*" $cpu]} {
set cpu parisc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
set cpu parisc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
osf1 {
@ -2296,8 +2296,23 @@ if {$::punkboot::command eq "bin"} {
}
#find runtimes
set rtfolder $sourcefolder/runtime
#set rtfolder $sourcefolder/runtime
#AAA
switch -glob -- $this_platform_generic {
macosx-* {
#assuming universal binaries x86_64 and arm
set rt_os_arch macosx
}
default {
set rt_os_arch $this_platform_generic
}
}
set rtfolder $binfolder/runtime/$rt_os_arch
set rt_sourcefolder $sourcefolder/runtime ;#where our config lives
#review - when building kits for other platforms - it's unlikely runtime will be marked as executable - we should probably process all files in runtime folder except those with certain extensions
set rtfolder_files [glob -nocomplain -dir $rtfolder -types {f} -tail *]
set exclusions {.config .md .ico .txt .doc .pdf .htm .html} ;#we don't encourage other files in runtime folder aside from mapvfs.config - but lets ignore some common possibilities
lappend exclusions .zip .7z .pea .bz2 .tar .gz .tgz .z .xz ;#don't allow archives to directly be treated as runtimes - tolerate presence but require user to unpack or rename if they're to be used as runtimes
@ -2311,7 +2326,7 @@ foreach f $rtfolder_files {
}
if {![llength $runtimes]} {
puts stderr "No executable runtimes found in $rtfolder - unable to build any .vfs folders into executables."
puts stderr "Add runtimes to $sourcefolder/runtime if required"
puts stderr "Add runtimes to $rtfolder if required"
#todo - don't exit - it is valid to use runtime of - to just build a .kit/.zipkit ?
exit 0
}
@ -2350,7 +2365,7 @@ if {$sdxpath eq ""} {
#build a dict keyed on runtime executable name.
#If no mapfile (or no mapfile entry for that runtime) - the runtime will be paired with a matching .vfs folder in src folder. e.g punk.exe to src/punk.vfs
#If vfs folders or runtime executables which are explicitly listed in the mapfile don't exist - warn on stderr - but continue. if such nonexistants found; prompt user for whether to continue or abort.
set mapfile $rtfolder/mapvfs.config
set mapfile $rt_sourcefolder/mapvfs.config
set runtime_vfs_map [dict create]
set vfs_runtime_map [dict create]
if {[file exists $mapfile]} {
@ -2891,8 +2906,8 @@ foreach vfstail $vfs_tails {
set extract_kit_type ""
while {!$extraction_done && [llength $extraction_trylist]} {
set extract_kit_type [lpop extraction_trylist 0]
switch -- $extract_kit_type {
set extract_kit_try [lpop extraction_trylist 0]
switch -- $extract_kit_try {
zip - zipcat {
#for a zipkit - we need to extract the existing vfs from the runtime
#zipfs mkimg replaces the entire zipped vfs in the runtime - so we need the original data to be part of our targetvfs.
@ -2972,12 +2987,12 @@ foreach vfstail $vfs_tails {
zipfile::decode::unzip $archiveinfo $extractedzipfolder
}]} {
set extraction_done 1
set extract_kit_type $extract_kit_try
#todo - verify that init.tcl etc are present?
merge_over $extractedzipfolder $targetvfs
}
#todo - verify that init.tcl etc are present?
merge_over $extractedzipfolder $targetvfs
}
}
cookit - cookfs {
#upx easy enough to deal with if we have/install a upx executable (avail on windows via scoop, freshports for freebsd and presumably various for linux)
@ -3006,17 +3021,20 @@ foreach vfstail $vfs_tails {
#copy from mounted runtime's vfs to the filesystem vfs
merge_over $rtmountpoint $targetvfs
set extraction_done 1
set extract_kit_type $extract_kit_try
}
}
}
kit {
if {!$have_sdx} {
puts stderr "no sdx available to wrap $targetkit"
lappend failed_kits [list kit $targetkit reason "sdx_executable_unavailable"]
$vfs_event targetset_end FAILED
$vfs_event destroy
$vfs_installer destroy
continue
puts stderr "no sdx available to unwrap $targetkit"
#don't add to failed_kits here
#extraction fail for one type doesn't mean we have fully failed yet
#lappend failed_kits [list kit $targetkit reason "sdx_executable_unavailable"]
#$vfs_event targetset_end FAILED
#$vfs_event destroy
#$vfs_installer destroy
continue ;#to next extraction attempt
}
set raw_runtime $buildfolder/raw_$runtime_fullname
@ -3046,6 +3064,7 @@ foreach vfstail $vfs_tails {
merge_over [file rootname $building_runtime].vfs $targetvfs
}
set extraction_done 1
set extract_kit_type $extract_kit_try
file copy -force $building_runtime $raw_runtime
}
cd $prev_cwd
@ -3056,8 +3075,10 @@ foreach vfstail $vfs_tails {
if {!$extraction_done} {
#TODO: if not extracted - use a default tcl_library for patchlevel and platform?
puts stderr "WARNING: No extraction done from runtime $runtime_fullname"
puts stderr "--------------------------------------------"
puts stderr "\x1b\31mWARNING: No extraction done from runtime $runtime_fullname\x1b\[m"
puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!"
puts stderr "--------------------------------------------"
file mkdir $targetvfs
}

1
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/include_modules.config

@ -99,5 +99,6 @@ set bootsupport_modules [list\
modules natsort\
modules oolib\
modules zipper\
modules zzzload\
]

131
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/zzzload-0.1.0.tm

@ -0,0 +1,131 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2023
#
# @@ Meta Begin
# Application zzzload 0.1.0
# Meta platform tcl
# Meta license BSD
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
package require Thread
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval zzzload {
variable loader_tid "" ;#thread id
proc stacktrace {} {
set stack "Stack trace:\n"
for {set i 1} {$i < [info level]} {incr i} {
set lvl [info level -$i]
set pname [lindex $lvl 0]
append stack [string repeat " " $i]$pname
if {![catch {info args $pname} pargs]} {
foreach value [lrange $lvl 1 end] arg $pargs {
if {$value eq ""} {
if {$arg != 0} {
info default $pname $arg value
}
}
append stack " $arg='$value'"
}
} else {
append stack " !unknown vars for $pname"
}
append stack \n
}
return $stack
}
proc pkg_require {pkgname args} {
variable loader_tid
if {[set ver [package provide twapi]] ne ""} {
#skip the whole shebazzle if it's already loaded
return $ver
}
if {$loader_tid eq ""} {
set loader_tid [thread::create -joinable -preserved]
}
if {![tsv::exists zzzload_pkg $pkgname]} {
#puts stderr "zzzload pkg_require $pkgname"
#puts [stacktrace]
tsv::set zzzload_pkg $pkgname "loading"
tsv::set zzzload_pkg_mutex $pkgname [thread::mutex create]
set cond [thread::cond create]
tsv::set zzzload_pkg_cond $pkgname $cond
thread::send -async $loader_tid [string map [list <pkg> $pkgname <cond> $cond] {
if {![catch {package require <pkg>} returnver]} {
tsv::set zzzload_pkg <pkg> $returnver
} else {
tsv::set zzzload_pkg <pkg> "failed"
}
thread::cond notify <cond>
}]
return "loading"
} else {
return [tsv::get zzzload_pkg $pkgname]
}
}
proc pkg_wait {pkgname} {
if {[set ver [package provide twapi]] ne ""} {
return $ver
}
set pkgstate [tsv::get zzzload_pkg $pkgname]
if {$pkgstate eq "loading"} {
set mutex [tsv::get zzzload_pkg_mutex $pkgname]
thread::mutex lock $mutex
set cond [tsv::get zzzload_pkg_cond $pkgname]
while {[tsv::get zzzload_pkg $pkgname] eq "loading"} {
thread::cond wait $cond $mutex 3000
}
set result [tsv::get zzzload_pkg $pkgname]
thread::mutex unlock $mutex
return $result
} else {
return $pkgstate
}
}
proc shutdown {} {
variable loader_tid
if {[thread::exists $loader_tid]} {
thread::release $loader_tid
thread::join $loader_tid
set loader_tid ""
}
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide zzzload [namespace eval zzzload {
variable version
set version 0.1.0
}]
return

93
src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl

@ -195,8 +195,8 @@ namespace eval ::punkboot::lib {
}
x86_64 {
if {$tcl_platform(wordSize) == 4} {
# See Example <1> at the top of this file.
set cpu ix86
# See Example <1> at the top of this file.
set cpu ix86
}
}
ppc -
@ -216,26 +216,26 @@ namespace eval ::punkboot::lib {
switch -glob -- $plat {
windows {
if {$tcl_platform(platform) == "unix"} {
set plat cygwin
set plat cygwin
} else {
set plat win32
set plat win32
}
if {$cpu eq "amd64"} {
# Do not check wordSize, win32-x64 is an IL32P64 platform.
set cpu x86_64
# Do not check wordSize, win32-x64 is an IL32P64 platform.
set cpu x86_64
}
}
sunos {
set plat solaris
if {[string match "ix86" $cpu]} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
} elseif {![string match "ia64*" $cpu]} {
# sparc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
# sparc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
darwin {
@ -243,24 +243,24 @@ namespace eval ::punkboot::lib {
# Correctly identify the cpu when running as a 64bit
# process on a machine with a 32bit kernel
if {$cpu eq "ix86"} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
}
}
aix {
set cpu powerpc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
append cpu 64
}
}
hp-ux {
set plat hpux
if {![string match "ia64*" $cpu]} {
set cpu parisc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
set cpu parisc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
osf1 {
@ -2296,8 +2296,23 @@ if {$::punkboot::command eq "bin"} {
}
#find runtimes
set rtfolder $sourcefolder/runtime
#set rtfolder $sourcefolder/runtime
#AAA
switch -glob -- $this_platform_generic {
macosx-* {
#assuming universal binaries x86_64 and arm
set rt_os_arch macosx
}
default {
set rt_os_arch $this_platform_generic
}
}
set rtfolder $binfolder/runtime/$rt_os_arch
set rt_sourcefolder $sourcefolder/runtime ;#where our config lives
#review - when building kits for other platforms - it's unlikely runtime will be marked as executable - we should probably process all files in runtime folder except those with certain extensions
set rtfolder_files [glob -nocomplain -dir $rtfolder -types {f} -tail *]
set exclusions {.config .md .ico .txt .doc .pdf .htm .html} ;#we don't encourage other files in runtime folder aside from mapvfs.config - but lets ignore some common possibilities
lappend exclusions .zip .7z .pea .bz2 .tar .gz .tgz .z .xz ;#don't allow archives to directly be treated as runtimes - tolerate presence but require user to unpack or rename if they're to be used as runtimes
@ -2311,7 +2326,7 @@ foreach f $rtfolder_files {
}
if {![llength $runtimes]} {
puts stderr "No executable runtimes found in $rtfolder - unable to build any .vfs folders into executables."
puts stderr "Add runtimes to $sourcefolder/runtime if required"
puts stderr "Add runtimes to $rtfolder if required"
#todo - don't exit - it is valid to use runtime of - to just build a .kit/.zipkit ?
exit 0
}
@ -2350,7 +2365,7 @@ if {$sdxpath eq ""} {
#build a dict keyed on runtime executable name.
#If no mapfile (or no mapfile entry for that runtime) - the runtime will be paired with a matching .vfs folder in src folder. e.g punk.exe to src/punk.vfs
#If vfs folders or runtime executables which are explicitly listed in the mapfile don't exist - warn on stderr - but continue. if such nonexistants found; prompt user for whether to continue or abort.
set mapfile $rtfolder/mapvfs.config
set mapfile $rt_sourcefolder/mapvfs.config
set runtime_vfs_map [dict create]
set vfs_runtime_map [dict create]
if {[file exists $mapfile]} {
@ -2891,8 +2906,8 @@ foreach vfstail $vfs_tails {
set extract_kit_type ""
while {!$extraction_done && [llength $extraction_trylist]} {
set extract_kit_type [lpop extraction_trylist 0]
switch -- $extract_kit_type {
set extract_kit_try [lpop extraction_trylist 0]
switch -- $extract_kit_try {
zip - zipcat {
#for a zipkit - we need to extract the existing vfs from the runtime
#zipfs mkimg replaces the entire zipped vfs in the runtime - so we need the original data to be part of our targetvfs.
@ -2972,12 +2987,12 @@ foreach vfstail $vfs_tails {
zipfile::decode::unzip $archiveinfo $extractedzipfolder
}]} {
set extraction_done 1
set extract_kit_type $extract_kit_try
#todo - verify that init.tcl etc are present?
merge_over $extractedzipfolder $targetvfs
}
#todo - verify that init.tcl etc are present?
merge_over $extractedzipfolder $targetvfs
}
}
cookit - cookfs {
#upx easy enough to deal with if we have/install a upx executable (avail on windows via scoop, freshports for freebsd and presumably various for linux)
@ -3006,17 +3021,20 @@ foreach vfstail $vfs_tails {
#copy from mounted runtime's vfs to the filesystem vfs
merge_over $rtmountpoint $targetvfs
set extraction_done 1
set extract_kit_type $extract_kit_try
}
}
}
kit {
if {!$have_sdx} {
puts stderr "no sdx available to wrap $targetkit"
lappend failed_kits [list kit $targetkit reason "sdx_executable_unavailable"]
$vfs_event targetset_end FAILED
$vfs_event destroy
$vfs_installer destroy
continue
puts stderr "no sdx available to unwrap $targetkit"
#don't add to failed_kits here
#extraction fail for one type doesn't mean we have fully failed yet
#lappend failed_kits [list kit $targetkit reason "sdx_executable_unavailable"]
#$vfs_event targetset_end FAILED
#$vfs_event destroy
#$vfs_installer destroy
continue ;#to next extraction attempt
}
set raw_runtime $buildfolder/raw_$runtime_fullname
@ -3046,6 +3064,7 @@ foreach vfstail $vfs_tails {
merge_over [file rootname $building_runtime].vfs $targetvfs
}
set extraction_done 1
set extract_kit_type $extract_kit_try
file copy -force $building_runtime $raw_runtime
}
cd $prev_cwd
@ -3056,8 +3075,10 @@ foreach vfstail $vfs_tails {
if {!$extraction_done} {
#TODO: if not extracted - use a default tcl_library for patchlevel and platform?
puts stderr "WARNING: No extraction done from runtime $runtime_fullname"
puts stderr "--------------------------------------------"
puts stderr "\x1b\31mWARNING: No extraction done from runtime $runtime_fullname\x1b\[m"
puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!"
puts stderr "--------------------------------------------"
file mkdir $targetvfs
}

6
src/runtime/mapvfs.config

@ -45,7 +45,7 @@
#made with Bawt (2025-08)
#tclkit
#tclkit902.exe {punk9win_for_tkruntime.vfs punk902k kit}
#tclkit902.exe {punk9win_for_tkruntime.vfs punk902kit kit}
#static build - with tk dll and tk lib added to zip
tclsh902z.exe {punk9win_for_tkruntime.vfs punk902z zip}
@ -55,8 +55,8 @@ tclsh902z.exe {punk9win_for_tkruntime.vfs punk902z zip}
#-----------------------------------------
#AAA
#tclsh901t.exe {punk9win.vfs punk901t zipcat}
#tclsh901k.exe {mkzipfix.vfs punktest zip}
tclsh901t.exe {punk9win.vfs punk901t zipcat}
#tclsh901k.exe {mkzipfix.vfs punktest zip}

2
src/scriptapps/getpunk_wrap.toml

@ -15,6 +15,6 @@
#valid nextshelltype entries are: tcl perl powershell bash.
#nextshellpath entries must be 64 characters or less.
win32.nextshellpath="powershell"
win32.nextshellpath="powershell -nop -nol -ExecutionPolicy ByPass -File"
win32.nextshelltype="powershell"
win32.outputfile="getpunk.cmd"

1
src/scriptapps/getzig.ps1

@ -7,6 +7,7 @@
#$outbase = Join-Path -Path $PSScriptRoot -ChildPath "../.."
$outbase = $PSScriptRoot
$outbase = Resolve-Path -Path $outbase
Write-host "Base folder: $outbase"
$toolsfolder = Join-Path -Path $outbase -ChildPath "tools"
if (-not(Test-Path -Path $toolsfolder -PathType Container)) {
#create folder - (can include missing intermediaries)

18
src/scriptapps/getzig_original.polyglot

@ -0,0 +1,18 @@
#!/bin/sh
echo `# <#`
mkdir -p ./zig
wget https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz -O ./zig/zig-linux-x86_64-0.10.1.tar.xz
tar -xf ./zig/zig-linux-x86_64-0.10.1.tar.xz -C ./zig --strip-components=1
rm ./zig/zig-linux-x86_64-0.10.1.tar.xz
echo "Zig installed."
./zig/zig version
exit
#> > $null
Invoke-WebRequest -Uri "https://ziglang.org/download/0.10.1/zig-windows-x86_64-0.10.1.zip" -OutFile ".\zig-windows-x86_64-0.10.1.zip"
Expand-Archive -Path ".\zig-windows-x86_64-0.10.1.zip" -DestinationPath ".\" -Force
Remove-Item -Path " .\zig-windows-x86_64-0.10.1.zip"
Rename-Item -Path ".\zig-windows-x86_64-0.10.1" -NewName ".\zig"
Write-Host "Zig installed."
./zig/zig.exe version

2
src/scriptapps/getzig_wrap.toml

@ -16,6 +16,6 @@
#valid nextshelltype entries are: tcl perl pwsh powershell bash.
#nextshellpath entries must be 64 characters or less.
win32.nextshellpath="pwsh"
win32.nextshellpath="pwsh -nop -nol -ExecutionPolicy bypass -c"
win32.nextshelltype="pwsh"
win32.outputfile="getzig.cmd"

68
src/scriptapps/runtime.bash

@ -12,44 +12,67 @@ scriptroot="${basename%.*}" #e.g "fetchruntime"
url_kitbase="https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master"
runtime_available=0
if [[ "$OSTYPE" == "linux"* ]]; then
arch=$(uname -i)
if [[ "$arch" == "x86_64"* ]]; then
#$OSTYPE varies in capitalization across for example zsh and bash
#uname probably a more consistent bet
arch=$(uname -m) #machine/architecture
plat=$(uname -s) #platform/system
#even though most of the platform prongs are very similar,
#we keep the code separate so it can be tweaked easily for unexpected differences
if [[ "$plat" = "Linux"* ]]; then
if [[ "$arch" = "x86_64"* ]]; then
url="${url_kitbase}/linux-x86_64/tclkit-902-Linux64-intel-dyn"
archdir="${scriptdir}/runtime/linux-x86_64"
output="${archdir}/tclkit-902-Linux64-intel-dyn"
runtime_available=1
elif [[ "$arch" == "arm"* ]]; then
elif [[ "$arch" = "arm"* ]]; then
url="${url_kitbase}/linux-arm/tclkit-902-Linux64-arm-dyn"
archdir="${scriptdir}/runtime/linux-arm"
output="${archdir}/tclkit-902-Linux64-arm-dyn"
runtime_available=1
fi
if [[ "$runtime_available" -eq 1 ]]; then
echo "Please ensure libxFt.so.2 is available"
echo "e.g on Ubuntu: sudo apt-get install libxft2"
else
archdir="${scriptdir}/runtime/linux-$arch"
fi
os="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
elif [[ "$plat" = "Darwin"* ]]; then
os="macosx"
#assumed to be Mach-O 'universal binaries' for both x86-64 and arm? - REVIEW
url="${url_kitbase}/macosx/tclkit-902-Darwin64-dyn"
archdir="${scriptdir}/runtime/macosx/"
output="${archdir}/tclkit-902-Darwin64-dyn"
runtime_available=1
elif [[ "$OSTYPE" == "freebsd"* ]]; then
elif [[ "$plat" = "FreeBSD"* ]]; then
archdir="${scriptdir}/runtime/freebsd-amd64"
os="freebsd"
elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then
elif [[ "$plat" == "DragonFly"* ]]; then
archdir="${scriptdir}/runtime/dragonflybsd-$arch"
os="dragonflybsd"
elif [[ "$OSTYPE" == "netbsd"* ]]; then
elif [[ "$plat" == "NetBSD"* ]]; then
archdir="${scriptdir}/runtime/netbsd-$arch"
os="netbsd"
elif [[ "$OSTYPE" == "win32" ]]; then
elif [[ "$plat" == "OpenBSD"* ]]; then
archdir="${scriptdir}/runtime/openbsd-amd64"
os="openbsd"
elif [[ "$plat" == "MINGW32"* ]]; then
#REVIEW
os="win32"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64/"
output="${archdir}/tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "MINGW64"* ]]; then
#REVIEW
os="win32"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64/"
output="${archdir}/tcsh902z.exe"
output="${archdir}/tclsh902z.exe"
runtime_available=1
elif [[ "$plat" == "CYGWIN_NT"* ]]; then
os="win32"
url="${url_kitbase}/win32-x86_64/tclsh902z.exe"
archdir="${scriptdir}/runtime/win32-x86_64/"
output="${archdir}/tclsh902z.exe"
runtime_available=1
elif [[ "$OSTYPE" == "msys" ]]; then
elif [[ "$plat" == "MSYS_NT"* ]]; then
echo MSYS
os="win32"
#use 'command -v' (shell builtin preferred over external which)
@ -64,7 +87,7 @@ elif [[ "$OSTYPE" == "msys" ]]; then
output="${archdir}/tclsh902z.exe"
runtime_available=1
else
#os="$OSTYPE"
archdir="${scriptdir}/runtime/other"
os="other"
fi
@ -80,6 +103,10 @@ case "$1" in
if [[ $? -eq 0 ]]; then
echo "File downloaded to $output"
chmod +x $output
if [[ "$plat" == "Linux" ]]; then
echo "Please ensure libxFt.so.2 is available"
echo "e.g on Ubuntu: sudo apt-get install libxft2"
fi
else
echo "Error: Failed to download to $output"
fi
@ -88,24 +115,25 @@ case "$1" in
fi
;;
"list")
if [ -d $archdir ]; then
if [[ -d "$archdir" ]]; then
echo "$(ls $archdir -1 | wc -l) files in $archdir"
echo $(ls $archdir -1)
else
echo "No runtimes available in $archdir\n Use '$0 fetch' to install."
echo -e "No runtimes available in $archdir\n Use '$0 fetch' to install."
fi
;;
"run")
#todo - lookup active runtime for os-arch from .toml file
activeruntime=$(ls $archdir -1 | tail -n 1)
activeruntime_fullpath="$archdir/$activeruntime"
echo "using $activeruntime_fullpath"
#echo "using $activeruntime_fullpath"
shift
echo "args: $@"
#echo "args: $@"
$activeruntime_fullpath "$@"
;;
*)
echo "Usage: $0 {fetch|list|run}"
echo "received $@"
exit 1
;;
esac

296
src/scriptapps/runtime.ps1

@ -36,7 +36,7 @@ function GetDynamicParamDictionary {
}
function ParameterDefinitions {
param(
[Parameter(ValueFromRemainingArguments=$true)] $opts
[Parameter(ValueFromRemainingArguments=$true,Position = 1)][string[]] $opts
)
}
@ -44,15 +44,28 @@ function psmain {
[CmdletBinding()]
#Empty param block (extra params can be added)
param(
[Parameter(Mandatory=$false)][string] $action
[Parameter(Mandatory=$false, Position = 0)][string] $action = ""
)
dynamicparam {
if ($action -eq 'list') {
$parameterAttribute = [System.Management.Automation.ParameterAttribute]@{
ParameterSetName = "listruntime"
Mandatory = $false
}
$attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection.Add($parameterAttribute)
$dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new(
'remote', [switch], $attributeCollection
)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add('remote', $dynParam1)
return $paramDictionary
} elseif ($action -eq 'fetch') {
#GetDynamicParamDictionary ParameterDefinitions
$parameterAttribute = [System.Management.Automation.ParameterAttribute]@{
ParameterSetName = "fetchruntime"
Mandatory = $false
Position = 1
}
$attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection.Add($parameterAttribute)
@ -65,8 +78,39 @@ function psmain {
$paramDictionary.Add('runtime', $dynParam1)
return $paramDictionary
} elseif ($action -eq 'run') {
GetDynamicParamDictionary ParameterDefinitions
#GetDynamicParamDictionary ParameterDefinitions
$parameterAttribute = [System.Management.Automation.ParameterAttribute]@{
ParameterSetName = "runargs"
Mandatory = $false
ValueFromRemainingArguments = $true
}
$attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection.Add($parameterAttribute)
$dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new(
'opts', [string[]], $attributeCollection
)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add('opts', $dynParam1)
return $paramDictionary
} else {
#accept all args when action is unrecognised - so we can go to help anyway
$parameterAttribute = [System.Management.Automation.ParameterAttribute]@{
ParameterSetName = "invalidaction"
Mandatory = $false
ValueFromRemainingArguments = $true
}
$attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection.Add($parameterAttribute)
$dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new(
'opts', [string[]], $attributeCollection
)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add('opts', $dynParam1)
return $paramDictionary
}
}
process {
@ -74,7 +118,7 @@ function psmain {
#write-host "Bound Parameters:$($PSBoundParameters.Keys)"
switch ($PSBoundParameters.keys) {
'action' {
#write-host "got action " $PSBoundParameters.action
write-host "got action " $PSBoundParameters.action
Set-Variable -Name $_ -Value $PSBoundParameters."$_"
$known_actions = @("fetch", "list", "run")
if (-not($known_actions -contains $action)) {
@ -83,10 +127,10 @@ function psmain {
}
}
'opts' {
#write-warning "Unused parameters: $($PSBoundParameters.$_)"
# write-warning "Unused parameters: $($PSBoundParameters.$_)"
}
Default {
#write-warning "Unhandled parameter -> [$($_)]"
# write-warning "Unhandled parameter -> [$($_)]"
}
}
#foreach ($boundparam in $PSBoundParameters.Keys) {
@ -100,11 +144,15 @@ function psmain {
$outbase = Resolve-Path -Path $outbase
#expected script location is the bin folder of a punk project
$rtfolder = Join-Path -Path $outbase -ChildPath "runtime"
$archfolder = Join-Path -Path $rtfolder -ChildPath "win32-x86_64"
#Binary artifact server url. (git is not ideal for this - but will do for now - todo - use artifact system within gitea?)
$artifacturl = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master"
switch ($action) {
'fetch' {
$arch = "win32-x86_64"
$archfolder = Join-Path -Path $rtfolder -ChildPath "$arch"
$archurl = "$artifacturl/$arch"
$sha1url = "$archurl/sha1sums.txt"
$runtime = "tclsh902z.exe"
$archurl = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win32-x86_64"
foreach ($boundparam in $PSBoundParameters.Keys) {
write-host "fetchopt: $boundparam $($PSBoundParameters[$boundparam])"
}
@ -112,44 +160,138 @@ function psmain {
$runtime = $PSBoundParameters["runtime"]
}
$fileurl = "$archurl/$runtime"
$output = join-path $archfolder $runtime
$output = join-path -Path $archfolder -ChildPath $runtime
$sha1local = join-path -Path $archfolder -ChildPath "sha1sums.txt"
$container = split-path -Path $output -Parent
new-item -Path $container -ItemType Directory -force #create with intermediary folders if not already present
if (-not(Test-Path -Path $output -PathType Leaf)) {
Write-Host "Downloading from $fileurl ..."
try {
$response = Invoke-WebRequest -Uri $fileurl -OutFile $output -ErrorAction Stop
Write-Host "Runtime saved at $output"
try {
Write-Host "Fetching $sha1url"
Invoke-WebRequest -Uri $sha1url -OutFile $sha1local -ErrorAction Stop
Write-Host "sha1 saved at $sha1local"
} catch {
Write-Host "An error occurred while downloading ${sha1url}: $($_.Exception.Message)"
if ($_.Exception.Response) {
Write-Host "HTTP Status code: $($_.Exception.Response.StatusCode)"
}
}
if (Test-Path -Path $sha1local -PathType Leaf) {
$sha1Content = Get-Content -Path $sha1local
$stored_sha1 = ""
foreach ($line in $sha1Content) {
#all sha1sums have * (binary indicator) - review
$match = [regex]::Match($line,"(.*) [*]${runtime}$")
if ($match.Success) {
$stored_sha1 = $match.Groups[1].Value
Write-host "stored hash from sha1sums.txt: $storedhash"
break
}
}
if ($stored_sha1 -eq "") {
Write-Host "Unable to locate hash for $runtime in $sha1local - Aborting"
Write-Host "Please download and verify manually"
return
}
$need_download = $false
if (Test-Path -Path $output -PathType Leaf) {
Write-Host "Runtime already found at $output"
Write-Host "Checking sha1 checksum of local file versus sha1 of server file"
$file_sha1 = Get-FileHash -Path "$output" -Algorithm SHA1
if (${file_sha1}.Hash -ne $stored_sha1) {
Write-Host "$runtime on server has different sha1 hash - Download required"
$need_download = $true
}
} else {
Write-Host "$runtime not found locally - Download required"
$need_download = $true
}
catch {
Write-Host "An error occurred: $($_.Exception.Message)"
if ($_.Exception.Response) {
Write-Host "HTTP Status code: $($_.Exception.Response.StatusCode)"
if ($need_download) {
Write-Host "Downloading from $fileurl ..."
try {
Invoke-WebRequest -Uri $fileurl -OutFile "${output}.tmp" -ErrorAction Stop
Write-Host "Runtime saved at $output.tmp"
}
catch {
Write-Host "An error occurred while downloading $fileurl $($_.Exception.Message)"
if ($_.Exception.Response) {
Write-Host "HTTP Status code: $($_.Exception.Response.StatusCode)"
}
return
}
Write-Host "comparing sha1 checksum of downloaded file with data in sha1sums.txt"
Start-Sleep -Seconds 1 #REVIEW - give at least some time for windows to do its thing? (av filters?)
$newfile_sha1 = Get-FileHash -Path "${output}.tmp" -Algorithm SHA1
if (${newfile_sha1}.Hash -eq $stored_sha1) {
Write-Host "sha1 checksum ok"
Move-Item -Path "${output}.tmp" -Destination "${output}" -Force
Write-Host "Runtime is available at ${output}"
} else {
Write-Host "WARNING! sha1 of downloaded file at $output.tmp does not match stored sha1 from sha1sums.txt"
return
}
} else {
Write-Host "Local copy of runtime at $output seems to match sha1 checksum of file on server."
Write-Host "No download required"
}
} else {
Write-Host "Runtime already found at $output"
Write-Host "Unable to consult local copy of sha1sums.txt at $sha1local"
if (Test-Path -Path $output -PathType Leaf) {
Write-Host "A runtime is available at $output - but we failed to retrieve the list of sha1sums from the server"
Write-Host "Unable to check for updated version at this time."
} else {
Write-Host "Please retry - or manually download a runtime from $archurl and verify checksums"
}
}
}
'run' {
#select first (or configured default) runtime and launch, passing arguments
$arch = "win32-x86_64"
$archfolder = Join-Path -Path $rtfolder -ChildPath "$arch"
if (-not(Test-Path -Path $archfolder -PathType Container)) {
write-host "No runtimes seem to be installed for win32-x86_64`nPlease use 'runtime.cmd fetch' to install"
} else {
$dircontents = (get-childItem -Path $archfolder -File | Sort-Object Name)
if ($dircontents.Count -gt 0) {
#write-host "run.."
#write-host "num params: $($PSBoundParameters.opts.count)"
#foreach ($boundparam in $PSBoundParameters.opts) {
# write-host $boundparam
#}
write-host "num params: $($PSBoundParameters.opts.count)"
#todo - use 'active' runtime - need to lookup (PSToml?)
#when no 'active' runtime for this os-arch - use last item (sorted in dictionary order)
$active = $dircontents[-1]
#write-host "using: $active"
Start-Process -FilePath $active -ArgumentList $PSBoundParameters.opts -NoNewWindow -Wait
$active = $dircontents[-1].FullName
write-host "using: $active"
if ($PSBoundParameters.opts.Length -gt 0) {
$optsType = $PSBoundParameters.opts.GetType() #method can only be called if .opts is not null
write-host "type of opts: $($optsType.FullName)"
foreach ($boundparam in $PSBoundParameters.opts) {
write-host $boundparam
}
Write-Host "opts: $($PSBoundParameters.opts)"
Write-Host "args: $args"
Write-HOst "argscount: $($args.Count)"
$arglist = @()
foreach ($o in $PSBoundParameters.opts) {
$oquoted = $o -replace '"', "`\`""
#$oquoted = $oquoted -replace "'", "`'"
if ($oquoted -match "\s") {
$oquoted = "`"$oquoted`""
}
$arglist += @($oquoted)
}
$arglist = $arglist.TrimEnd(' ')
write-host "arglist: $arglist"
#$arglist = $PSBoundParameters.opts
Start-Process -FilePath $active -ArgumentList $arglist -NoNewWindow -Wait
} else {
#powershell 5.1 and earlier can't accept an empty -ArgumentList value :/ !!
#$arglist = @()
#Start-Process -FilePath $active -ArgumentList $arglist -NoNewWindow -Wait
#Start-Process -FilePath $active -ArgumentList "" -NoNewWindow -Wait
Start-Process -FilePath $active -NoNewWindow -Wait
}
} else {
write-host "No files found in $archfolder"
write-host "No runtimes seem to be installed for win32-x86_64`nPlease use 'runtime.cmd fetch' to install."
@ -157,19 +299,104 @@ function psmain {
}
}
'list' {
if (test-path -Path $archfolder -Type Container) {
$dircontents = (get-childItem -Path $archfolder -File)
write-host "$(${dircontents}.count) files in $archfolder"
foreach ($f in $dircontents) {
write-host $f.Name
#todo - option to list for other os-arch
$arch = 'win32-x86_64'
$archfolder = Join-Path -Path $rtfolder -ChildPath "$arch"
$sha1local = join-path -Path $archfolder -ChildPath "sha1sums.txt"
$archurl = "$artifacturl/$arch"
$sha1url = "$archurl/sha1sums.txt"
if ( $PSBoundParameters.ContainsKey('remote') ) {
write-host "Checking for available remote runtimes for"
Write-Host "Fetching $sha1url"
Invoke-WebRequest -Uri $sha1url -OutFile $sha1local -ErrorAction Stop
Write-Host "sha1 saved at $sha1local"
$sha1Content = Get-Content -Path $sha1local
$remotedict = @{}
foreach ($line in $sha1Content) {
#all sha1sums have * (binary indicator) - review
$match = [regex]::Match($line,"(.*) [*](.*)$")
if ($match.Success) {
$server_sha1 = $match.Groups[1].Value
$server_rt = $match.Groups[2].Value
$remotedict[$server_rt] = $server_sha1
}
}
$localdict = @{}
if (test-path -Path $archfolder -Type Container) {
$dircontents = (get-childItem -Path $archfolder -File | Where-object {-not ($(".txt",".tm") -contains $_.Extension) })
foreach ($f in $dircontents) {
$local_sha1 = Get-FileHash -Path $(${f}.FullName) -Algorithm SHA1
$localdict[$f.Name] = ${local_sha1}.Hash
}
}
Write-host "-----------------------------------------------------------------------"
Write-host "Runtimes for $arch"
Write-host "Local $archfolder"
Write-host "Remote $archurl"
Write-host "-----------------------------------------------------------------------"
Write-host "Local Remote"
Write-host "-----------------------------------------------------------------------"
# 12345678910234567892023456789302345
$G = "`e[32m" #Green
$Y = "`e[33m" #Yellow
$R = "`e[31m" #Red
$RST = "`e[m"
foreach ($key in $localdict.Keys) {
$local_sha1 = $($localdict[$key])
if ($remotedict.ContainsKey($key)) {
if ($local_sha1 -eq $remotedict[$key]) {
$rhs = "Same version"
$C = $G
} else {
$rhs = "UPDATE AVAILABLE"
$C = $Y
}
} else {
$C = $R
$rhs = "(not listed on server)"
}
#ansi problems from cmd.exe not in windows terminal - review
$C = ""
$RST = ""
$lhs = "$key".PadRight(35, ' ')
write-host -nonewline "${C}${lhs}${RST}"
write-host $rhs
}
$lhs_missing = "-".PadRight(35, ' ')
foreach ($key in $remotedict.Keys) {
if (-not ($localdict.ContainsKey($key))) {
write-host -nonewline $lhs_missing
write-host $key
}
}
Write-host "-----------------------------------------------------------------------"
} else {
write-host "No runtimes seem to be installed for win32-x86_64 in $archfolder`nPlase use 'runtime.cmd fetch' to install."
if (test-path -Path $archfolder -Type Container) {
Write-host "-----------------------------------------------------------------------"
Write-Host "Local runtimes for $arch"
$dircontents = (get-childItem -Path $archfolder -File | Where-object {-not ($(".txt",".tm") -contains $_.Extension) })
write-host "$(${dircontents}.count) files in $archfolder"
Write-host "-----------------------------------------------------------------------"
foreach ($f in $dircontents) {
write-host $f.Name
}
Write-host "-----------------------------------------------------------------------"
Write-host "Use: 'list -remote' to compare local runtimes with those available on the artifact server"
} else {
write-host "No runtimes seem to be installed for win32-x86_64 in $archfolder`nPlease use 'runtime.cmd fetch' to install."
}
}
}
default {
$actions = @("fetch", "list", "run")
write-host "Available actions: $actions"
write-host "received"
foreach ($boundparam in $PSBoundParameters.opts) {
write-host $boundparam
}
}
}
@ -177,8 +404,9 @@ function psmain {
}
}
#write-host (psmain @args)
$returnvalue = psmain @args
#$returnvalue = psmain @args
#Write-Host "Function Returned $returnvalue" -ForegroundColor Cyan
return $returnvalue
#return $returnvalue
psmain @args | out-null
exit 0

4
src/scriptapps/runtime_wrap.toml

@ -15,6 +15,8 @@
#valid nextshelltype entries are: tcl perl powershell bash.
#nextshellpath entries must be 64 characters or less.
win32.nextshellpath="powershell"
#don't use -c for launching - or in old powershell, arguments such as "a b" will become 2 arguments a b
#do use -File (even though pwsh doesn't require it)
win32.nextshellpath="powershell -nop -nol -ExecutionPolicy bypass -File"
win32.nextshelltype="powershell"
win32.outputfile="runtime.cmd"

9
src/scriptapps/tclargs.tcl

@ -0,0 +1,9 @@
puts stdout "::argc"
puts stdout $::argc
puts stdout "::argv"
puts stdout "$::argv"
puts stdout -----------------------
foreach a $::argv {
puts stdout $a
}
puts stdout -done-

15
src/scriptapps/tclargs_wrap.toml

@ -0,0 +1,15 @@
[application]
template="punk.multishell.cmd"
as_admin=false
scripts=[
"tclargs.tcl",
]
default_outputfile="tclargs.cmd"
default_nextshellpath="tclsh"
default_nextshelltype="tcl"
win32.nextshellpath="tclsh"
win32.nextshelltype="tcl"
win32.outputfile="tclargs.cmd"

2048
src/vendormodules/www-2.8.tm

File diff suppressed because it is too large Load Diff

83
src/vendormodules/www/digest-2.1.tm

@ -0,0 +1,83 @@
namespace eval www::digest {
variable noncecount
}
# HTTP/1.1 401 Unauthorized
# WWW-Authenticate: Digest
# realm="testrealm@host.com",
# qop="auth,auth-int",
# nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
# opaque="5ccc069c403ebaf9f0171e9517f40e41"
proc www::digest::md5 {str} {
package require md5
return [string tolower [::md5::md5 -hex $str]]
}
proc www::digest::sha256 {str} {
package require sha256
return [::sha2::sha256 -hex $str]
}
proc www::digest::digest {challenge username password method uri {body ""}} {
variable noncecount
if {[dict exists $challenge algorithm]} {
set algorithm [dict get $challenge algorithm]
} else {
set algorithm MD5
}
switch $algorithm {
MD5 - MD5-sess {set hash md5}
SHA-256 - SHA-256-sess {set hash sha256}
default {
error "unsupported algorithm: $algorithm"
}
}
set interlude [dict get $challenge nonce]
set keys {username realm nonce uri response}
if {[dict exists $challenge qop]} {
set qops [split [dict get $challenge qop] ,]
if {"auth" in $qops} {
set qop auth
} elseif {"auth-int" in $qops} {
set qop auth-int
} else {
error "unsupported qop: [join $qops {, }]"
}
set nonce [dict get $challenge nonce]
# Generate a random cnonce
set cnonce [format %08x [expr {int(rand() * 0x100000000)}]]
set nc [format %08X [incr noncecount($nonce)]]
append interlude : $nc : $cnonce : $qop
lappend keys qop nc cnonce
if {[dict exists $challenge algorithm]} {lappend keys algorithm}
if {[dict exists $challenge opaque]} {lappend keys opaque}
} else {
set qop auth
}
foreach n $keys {
dict set rc $n \
[if {[dict exists $challenge $n]} {dict get $challenge $n}]
}
dict set rc username $username
dict set rc uri $uri
if {[dict exists $rc qop]} {
dict set rc qop $qop
dict set rc cnonce $cnonce
dict set rc nc $nc
}
set A1 [$hash $username:[dict get $challenge realm]:$password]
if {[string match {*-sess} $algorithm]} {append A1 : $nonce : $cnonce}
set A2 [$hash $method:$uri]
if {$qop eq "auth-int"} {append A2 : $body}
dict set rc response [$hash $A1:$interlude:$A2]
set authlist {}
dict for {key val} $rc {
if {$key ni {qop nc}} {
lappend authlist [format {%s="%s"} $key $val]
} else {
lappend authlist $key=$val
}
}
return "Digest [join $authlist ,]"
}

1551
src/vendormodules/www/http2-1.1.tm

File diff suppressed because it is too large Load Diff

13
src/vendormodules/www/license.terms

@ -0,0 +1,13 @@
Copyright (c) 2021, Schelte Bron
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

826
src/vendormodules/www/proxypac-2.1.tm

@ -0,0 +1,826 @@
#!/usr/bin/tclsh
# This library can be used together with www 2.0+ to use a proxy based on a
# Proxy Auto Configure (pac) file:
# package require proxypac
# www configure -proxyfilter {proxypac <pacurl>}
# Example: http://pac.webdefence.global.blackspider.com:8082/proxy.pac
package require www
namespace eval www::proxypac {
variable oldpac {}
namespace export proxypac
proc proxypac {pacurl url host} {
variable oldpac
if {[string equal -length [string length $url] $pacurl $url]} {
# The pac url itself must be reachable directly
return DIRECT
}
try {
if {$pacurl ne $oldpac} {
set data [www get $pacurl]
set oldpac $pacurl
parse $data
}
set proxies [execute FindProxyForURL $url $host]
return [lmap proxy [split $proxies {;}] {
if {[string is space $proxy]} continue
string trim $proxy
}]
} on error {err opts} {
www::log "Failed to auto-configure proxy: $err"
# In case of any error, use a direct connection
return [list DIRECT]
}
}
proc validip {ipchars} {
set valid [lmap n [split $ipchars .] {
expr {[string is digit -strict $n] && $n < 256}
}]
return [expr {[join $valid ""] eq "1111"}]
}
proc resolve {host} {
if {[catch {package require dns}]} return
set tok [dns::resolve $host]
dns::wait $tok
set result [lindex [dns::address $tok] 0]
dns::cleanup $tok
return $result
}
}
if {[catch {package require duktape::oo 0.11}]} {
proc www::proxypac::parse {data} {
set code [convert [string map [list \r\n \n] $data]]
proxypacrun eval $code
}
proc www::proxypac::execute {args} {
proxypacrun eval $args
}
proc www::proxypac::convert {data} {
variable tokenlist
set p 0
set re {/[/=*]?|[*]/|[+][+=]?|[*][*]?=?|-=?|<[<=]?|>>>|>[>=]?|%=?|&&?|[|][|]?|!=?=?|={1,3}|[][(),.{}"';\n?~^]|[ \t]+}
set tokenlist [lmap n [regexp -all -indices -inline $re $data] {
lassign $n x1 x2
set str [string range $data $p [expr {$x1 - 1}]]
set sep [string range $data $x1 $x2]
set p [expr {$x2 + 1}]
list $str $sep
}]
set code [lmap line [block] {
set tabs [string length [lindex [regexp -inline ^\t* $line] 0]]
set indent [string repeat \t [expr {$tabs / 2}]]
append indent [string repeat " " [expr {$tabs % 2}]]
regsub ^\t* $line $indent
}]
return [join $code \n]
}
proc www::proxypac::peek {{trim 1}} {
variable tokenlist
variable count
if {[incr count] > 20} {
fail "endless loop"
}
if {[llength $tokenlist] == 0} return
lassign [lindex $tokenlist 0] str tag
if {![string is space $tag] || !$trim} {
return [lindex $tokenlist 0]
} elseif {$str ne ""} {
if {[lindex $tokenlist 1 0] ne ""} {
return [lindex $tokenlist 0]
}
lset tokenlist 1 0 $str
}
set tokenlist [lrange $tokenlist 1 end]
tailcall peek
}
proc www::proxypac::poke {str tag} {
variable tokenlist
lset tokenlist 0 [list $str $tag]
}
proc www::proxypac::next {{trim 1}} {
variable tokenlist
variable count 0
set tokenlist [lrange $tokenlist 1 end]
tailcall peek $trim
}
proc www::proxypac::end {} {
variable tokenlist
return [expr {[llength $tokenlist] == 0}]
}
proc www::proxypac::code {} {
lassign [peek] str tag
if {$str eq "" && $tag eq "\{"} {
next
lappend rc {*}[block]
lassign [peek] str tag
if {$tag ne "\}"} {
fail "expected \}"
}
next
} else {
lappend rc {*}[statement]
}
return $rc
}
proc www::proxypac::block {} {
while {![end]} {
lassign [peek] str tag
switch $str {
{} {
if {$tag in {// /*}} {
comment
}
}
default {
set block [statement]
lappend rc {*}$block
}
}
lassign [peek] str tag
if {$tag eq "\}"} {
break
}
}
return $rc
}
proc www::proxypac::comment {} {
variable tokenlist
variable count 0
lassign [peek] str tag
if {$tag eq "//"} {
set end \n
} else {
set end "*/"
}
set nl [lsearch -exact -index 1 $tokenlist $end]
if {$nl < 0} {set nl end}
set tokenlist [lreplace $tokenlist 0 $nl]
}
proc www::proxypac::statement {} {
lassign [peek] str tag
switch $str {
function {
if {![string is space $tag]} {
fail "expected white space"
}
set rc [function]
}
if {
set rc [ifelse]
}
return {
set rc [jsreturn]
}
var {
if {![string is space $tag]} {
fail "expected white space"
}
set rc [var]
}
for {
if {$tag ne "("} {
fail "expected ("
}
set rc [forloop]
}
default {
if {![regexp {^[\w$]+$} $str]} {
fail "unsupported JavaScript command: $str"
} elseif {$tag eq "="} {
set rc [assignment $str]
} elseif {$tag eq "("} {
set rc [list [funccall $str]]
} else {
fail "unsupported JavaScript command: $str (tag = $tag)"
}
}
}
lassign [peek] str tag
if {$tag eq ";"} {
lassign [next] str tag
}
return $rc
}
proc www::proxypac::jsreturn {} {
lassign [peek] str tag
if {$str eq "" && $tag in {; \n}} {
return [list return]
} else {
poke "" $tag
return [list "return [expression]"]
}
}
proc www::proxypac::expression {{top 1}} {
lassign [peek] str tag
set rc {}
set unary {}
set strcat 0
while 1 {
if {$str eq "" && $tag in {+ - ! ~}} {
append unary $tag
lassign [next] str tag
continue
}
switch -regexp $str {
{^$} {
set op [lindex $rc end]
if {$op eq "=="} {
lset rc end eq
} elseif {$op eq "!="} {
lset rc end ne
}
if {$tag in {\" '}} {
set quote $tag
set strvar ""
while 1 {
lassign [next 0] str tag
if {$tag eq $quote} {
append strvar $str
break
} else {
append strvar $str $tag
}
}
lappend rc [format {{%s}} $strvar]
lassign [next] str tag
if {$str ne ""} {
fail "invalid expression"
}
set strcat 1
} elseif {$tag in "("} {
next
lappend rc [format (%s) [expression 0]]
lassign [peek] str tag
if {$tag ne ")"} {
fail "expected )"
}
next
}
}
{^[\w$]+$} {
if {$tag eq "("} {
lappend rc [format {[%s]} [funccall $str]]
} elseif {$tag eq "\["} {
lappend rc [arrayelem $str]
} elseif {[string is double $str]} {
lappend rc $str
} elseif {[string tolower $str] in {true false}} {
lappend rc $str
} else {
lappend rc [format {$%s} $str]
}
}
default {
fail "expected expression"
}
}
lassign [peek] str tag
while {$tag eq "."} {
lset rc end [method [lindex $rc end]]
lassign [peek] str tag
}
if {$unary ne ""} {
lset rc end $unary[lindex $rc end]
set unary {}
}
switch $tag {
+ - - - * - ** - / - % -
== - != - > - < - >= - <= - ? - : -
& - | - ^ - << - >> - && - || {
lappend rc $tag
}
=== {
lappend rc ==
}
!== {
lappend rc !=
}
>>> {
lappend rc >>
}
default {
break
}
}
lassign [next] str tag
}
if {!$top} {
return [join $rc " "]
} elseif {[llength $rc] == 1} {
set rc [lindex $rc 0]
if {[string match {{*}} $rc]} {
return [list [string range $rc 1 end-1]]
} else {
return $rc
}
} elseif {!$strcat} {
return [format {[expr {%s}]} [join $rc " "]]
}
set cat {}
set expr {}
set rest [lassign $rc arg]
set strcat [string match {{*}} $arg]
if {$strcat} {
lappend cat $arg
} else {
lappend expr $arg
}
foreach {op arg} $rest {
if {$op ne "+" || !$strcat && ![string match {{*}} $arg]} {
lappend expr $op $arg
} else {
if {[llength $expr]} {
if {[llength $expr] > 1} {
lappend cat [format {[expr {%s}]} [join $expr]]
} else {
lappend cat [lindex $expr 0]
}
}
set expr {}
if {[string match {{*}} $arg]} {
set strcat 1
lappend cat $arg
} else {
lappend expr $arg
}
}
}
if {[llength $expr]} {
if {[llength $expr] > 1} {
lappend cat [format {[expr {%s}]} [join $expr]]
} else {
lappend cat [lindex $expr 0]
}
}
return [format {[string cat %s]} [join $cat]]
}
proc www::proxypac::function {} {
lassign [next] name tag
if {$tag ne "("} {
fail "expected open parenthesis"
}
set arglist {}
lassign [next] str tag
if {$str ne ""} {
while 1 {
lappend arglist $str
if {$tag eq ")"} break
if {$tag ne ","} {
fail "expected , or )"
}
lassign [next] str tag
}
} elseif {$tag ne ")"} {
fail "expected )"
}
lappend rc "proc $name [list $arglist] \{"
lassign [next] str tag
lappend rc {*}[indent [code]]
lappend rc "\}"
return $rc
}
proc www::proxypac::funccall {name} {
set cmd $name
lassign [next] str tag
if {$str ne "" || $tag ne ")"} {
while 1 {
append cmd " " [expression]
lassign [peek] str tag
if {$tag eq ")"} break
if {$tag ne ","} {
fail "expected , or )"
}
next
}
}
next
return $cmd
}
proc www::proxypac::ifelse {} {
lassign [peek] str tag
if {$tag ne "("} {
fail "expected ("
}
next
lappend rc [format "if {%s} \{" [expression 0]]
lassign [next] str tag
lappend rc {*}[indent [code]]
lassign [peek] str tag
if {$str eq "else"} {
lappend rc {\} else \{}
lassign [next] str tag
lappend rc {*}[indent [code]]
}
lappend rc "\}"
return $rc
}
proc www::proxypac::forloop {} {
lassign [peek] str tag
if {$tag ne "("} {
fail "expected ("
}
lassign [next] name tag
if {$name eq "var" && [string is space $tag]} {
lassign [next] name tag
}
if {![regexp {^[\w$]+$} $name]} {
fail "expected identifier"
}
if {$tag eq "="} {
} elseif {[string is space $tag]} {
lassign [next] str tag
if {$str ni {in of} || ![string is space $tag]} {
fail "expected 'in' or 'of'"
}
if {$str eq "in"} {
set op keys
} else {
set op values
}
lassign [next] str tag
lappend rc [format "foreach %s \[dict %s $%s\] \{" $name $op $str]
if {$tag ne ")"} {
fail "expected )"
}
next
lappend rc {*}[indent [code]]
lappend rc "\}"
}
return $rc
}
proc www::proxypac::method {obj} {
lassign [next] method tag
set cmd [format {%s %s} $method $obj]
if {$tag eq "("} {
lassign [next] str tag
if {$str ne "" || $tag ne ")"} {
while 1 {
append cmd " " [expression]
lassign [peek] str tag
if {$tag eq ")"} break
if {$tag ne ","} {
fail "expected , or )"
}
next
}
}
next
}
return [format {[%s]} $cmd]
}
proc www::proxypac::assignment {name} {
lassign [next] str tag
switch $str {
new {
if {![string is space $tag]} {
fail "expected white space"
}
lassign [next] str tag
switch $str {
Array {
if {$tag ne "("} {
fail "expected ("
}
set cmd "dict create"
lassign [next] str tag
set index 0
if {$str ne "" || $tag ne ")"} {
while 1 {
append cmd " " $index " " [expression]
incr index
lassign [peek] str tag
next
if {$tag eq ","} continue
if {$tag eq ")"} break
fail "expected , or )"
}
} else {
next
}
return [list [format {set %s [%s]} $name $cmd]]
}
default {
fail "$str objects are not supported"
}
}
}
{} {
if {$tag eq "\["} {
set cmd list
lassign [next] str tag
if {$str ne "" || $tag ne "]"} {
while 1 {
append cmd " " [expression]
lassign [peek] str tag
next
if {$tag eq ","} continue
if {$tag eq "\]"} break
fail "expected , or \]"
}
}
return [list [format {set %s [%s]} $name $cmd]]
}
}
}
return [list [format {set %s %s} $name [expression]]]
}
proc www::proxypac::var {} {
lassign [next] str tag
if {![regexp {^[\w$]+$} $str]} {
fail "expected identifier"
}
if {$tag in {; \n}} return
return [assignment $str]
}
proc www::proxypac::arrayelem {name} {
next
set sub [expression]
lassign [peek] str tag
if {$tag ne "\]"} {
fail "expected \]"
}
next
return [format {[dict get $%s %s]} $name $sub]
}
proc www::proxypac::indent {list} {
return [lmap line $list {format \t%s $line}]
}
proc www::proxypac::fail {str} {
error $str
}
namespace eval www::proxypac {
interp create [namespace current]::proxypacrun
proxypacrun alias resolve [namespace which resolve]
proxypacrun alias validip [namespace which validip]
proxypacrun eval {
proc substring {str start {end 0}} {
if {[llength [info level 0]] < 4} {
set end [string length $str]
}
if {$start < $end} {
return [string range $str $start [expr {$end - 1}]]
} else {
return [string range $str $end [expr {$start - 1}]]
}
}
proc toLowerCase {str} {
return [string tolower $str]
}
rename split tclsplit
proc split {str {separator ""} {limit 2147483647}} {
if {[llength [info level 0]] == 1} {
set list [list $str]
} elseif {$separator eq ""} {
set list [tclsplit $str ""]
} else {
set list {}
set p 0
while {[set x [string first $separator $str $p]] >= 0} {
lappend list [string range $str $p [expr {$x - 1}]]
set p [expr {$x + [string length $separator]}]
}
lappend list [string range $str $p end]
}
set rc {}
set num 0
foreach n $list {
if {$num >= $limit} break
dict set rc $num $n
incr num
}
return $rc
}
}
proc jsfunction {name type args body} {
proxypacrun alias $name \
apply [list $args $body [namespace current]]
# proxypacrun eval [list proc $name $args $body]
}
}
} else {
namespace eval www::proxypac {
duktape::oo::Duktape create js
proc parse {data} {
js eval $data
}
proc execute {args} {
js call {*}$args
}
proc jsfunction {name type args body} {
js tcl-function $name $type $args $body
}
}
}
namespace eval www::proxypac {
variable ipaddress ""
jsfunction isPlainHostName boolean {host} {
return [expr {[string first . $host] < 0}]
}
jsfunction dnsDomainIs boolean {host domain} {
set x [string first . $host]
return [expr {$x >= 0 && [string range $host $x end] eq $domain}]
}
jsfunction localHostOrDomainIs boolean {host hostdom} {
return \
[expr {$host eq $hostdom || $host eq [lindex [split $host .] 0]}]
}
jsfunction isValidIpAddress boolean {ipchars} {
return [validip $ipchars]
}
jsfunction isResolvable boolean {host} {
return [expr {[resolve $host] ne ""}]
}
jsfunction isInNet boolean {host pattern mask} {
if {![validip $host]} {
set host [resolve $host]
if {$host eq ""} {return 0}
}
foreach ip1 [split $host .] ip2 [split $pattern .] m [split $mask .] {
if {($ip1 & $m) != ($ip2 & $m)} {return 0}
}
return 1
}
jsfunction dnsResolve string {host} {
return [resolve $host]
}
jsfunction convert_addr integer {ipaddr} {
binary scan [binary format c4 [split $ipaddr .]] Iu addr
return $addr
}
jsfunction myIpAddress string {} {
variable ipaddress
if {$ipaddress eq ""} {
try {
set fd ""
set fd [socket -server dummy -myaddr [info hostname] 0]
set ipaddress [lindex [fconfigure $fd -sockname] 0]
} on error {} {
set ipaddress 127.0.0.1
} finally {
if {$fd ne ""} {close $fd}
}
}
return $ipaddress
}
jsfunction dnsDomainLevels integer {host} {
return [regexp {[.]} $host]
}
jsfunction shExpMatch boolean {str shexp} {
return [string match $shexp $str]
}
jsfunction weekdayRange boolean {wd1 {wd2 ""} {gmt ""}} {
set weekdays {SUN MON TUE WED THU FRI SAT}
if {$wd2 eq "GMT"} {
set gmt 1
set match [list $wd1]
} else {
set gmt [expr {$gmt eq "GMT"}]
set d1 [lsearch -exact $weekdays $wd1]
set d2 [lsearch -exact $weekdays $wd2]
if {$d1 < $d2} {
set match [lrange $weekdays $d1 $d2]
} else {
set match [list $wd1 $wd2]
}
}
set wd0 [clock format [clock seconds] -gmt $gmt -format %a]
return [expr {[string toupper $wd0] in $match}]
}
jsfunction dateRange boolean {args} {
set gmt [expr {[lindex $args end] eq "GMT"}]
set len [expr {[llength $args] - $gmt}]
if {$len < 1} {return 0}
set now [clock seconds]
if {$len == 1} {
set arg [lindex $args 0]
if {![string is integer -strict $arg]} {
set mon [clock format $now -format %b -gmt $gmt]
return [expr {$arg eq [string toupper $mon]}]
} elseif {$arg < 32} {
set day [clock format $now -format %e -gmt $gmt]
return [expr {$arg == $day}]
} else {
set year [clock format $now -format %Y -gmt $gmt]
return [expr {$arg == $year}]
}
}
lassign [clock format $now -format {%Y %b} -gmt $gmt] year month
set d1 [list $year JAN 1 0 0 0]
set d2 [list $year DEC 31 23 59 59]
set middle [expr {$len / 2}]
for {set i 0} {$i < $middle} {incr i} {
set arg [lindex $args $i]
if {![string is integer -strict $arg]} {
lset d1 1 $arg
} elseif {$arg < 32} {
lset d1 2 $arg
if {$len <= 2} {
lset d1 1 $month
lset d2 1 $month
}
} else {
lset d1 0 $arg
}
}
for {set i $middle} {$i < $len} {incr i} {
set arg [lindex $args $i]
if {![string is integer -strict $arg]} {
lset d2 1 $arg
} elseif {$arg < 32} {
lset d2 2 $arg
} else {
lset d2 0 $arg
}
}
set time1 [clock scan [join $d1 :] -format %Y:%b:%d:%T -gmt $gmt]
set time2 [clock scan [join $d2 :] -format %Y:%b:%d:%T -gmt $gmt]
if {$time1 < $time2} {
return [expr {$now >= $time1 && $now <= $time2}]
} else {
return [expr {$now >= $time2 && $now <= $time1}]
}
}
jsfunction timeRange boolean {args} {
set gmt [expr {[lindex $args end] eq "GMT"}]
set len [expr {[llength $args] - $gmt}]
if {$len < 1} {
return 0
} elseif {$len > 6 || $len == 3 || $len == 5} {
return -code error "timeRange: bad number of arguments"
}
set t1 {0 0 0}
set t2 {23 59 59}
set n [expr {($len + 1) / 2}]
for {set i1 0; set i2 [expr {$len / 2}]} {$i1 < $n} {incr i1; incr i2} {
lset t1 $i1 [lindex $args $i1]
if {$i2 < $len} {
lset t2 $i1 [lindex $args $i2]
}
}
set time1 [clock scan [join $t1 :] -format %T -gmt $gmt]
set time2 [clock scan [join $t2 :] -format %T -gmt $gmt]
set now [clock seconds]
if {$time1 < $time2} {
return [expr {$now >= $time1 && $now <= $time2}]
} else {
return [expr {$now >= $time2 && $now <= $time1}]
}
}
jsfunction alert undefined {} {}
}
namespace import www::proxypac::*

156
src/vendormodules/www/socks-1.0.tm

@ -0,0 +1,156 @@
# SOCKS V4a: http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol
# SOCKS V5: RFC 1928
namespace eval www::socks {
variable username guest password guest
namespace ensemble create -map {socks4 {init socks4} socks5 {init socks5}}
}
proc www::socks::command {sock data {count 2} {timeout 2000}} {
if {$data ne ""} {
puts -nonewline $sock $data
flush $sock
}
set coro [info coroutine]
if {[llength $coro]} {
set id [after $timeout [list $coro timeout]]
fileevent $sock readable [list $coro data]
} else {
fconfigure $sock -blocking 1
set id {}
}
set resp {}
set len 0
while {![eof $sock]} {
append resp [read $sock [expr {$count - $len}]]
set len [string length $resp]
if {$len >= $count} {
after cancel $id
return $resp
}
if {[llength $coro] == 0} continue
set event [yield]
if {$event eq "data"} continue
if {$event eq "timeout"} break
}
throw {SOCKS PROTOCOL ERROR} "did not get expected response from proxy"
}
proc www::socks::init {version sock host port} {
# Make sure this is running in a coroutine
if {[llength [info coroutine]] == 0} {
return [coroutine $sock init $version $sock $host $port]
}
dict set cfg -translation [fconfigure $sock -translation]
dict set cfg -blocking [fconfigure $sock -blocking]
dict set event readable [fileevent $sock readable]
dict set event writable [fileevent $sock writable]
fileevent $sock writable {}
fconfigure $sock -translation binary -blocking 0
if {[catch {$version $sock $host $port} result opts]} {
variable lasterror $result
}
fconfigure $sock {*}$cfg
dict for {ev cmd} $event {
fileevent $sock $ev $cmd
}
return -options [dict incr opts -level] $result
}
proc www::socks::socks4 {sock host port} {
variable username
set ip4 [split $host .]
if {[llength $ip4] == 4 && [string is digit -strict [lindex $ip4 end]]} {
set data [binary format ccSc4a*x 4 1 $port $ip4 $username]
} else {
# SOCKS4a
set data [binary format ccSx3ca*xa*x 4 1 $port 1 $username $host]
}
binary scan [command $sock $data 8] cucuSc4 vn cd dstport dstip
if {$vn != 0} {
throw {SOCKS CONNECT VERSION} \
"unsupported socks connection version: $vn"
}
if {$cd != 90} {
throw [list SOCKS CONNECT [format ERROR%02X $cd]] \
"socks connection failed with error code $cd"
}
return [join $dstip .]:$dstport
}
proc www::socks::socks5 {sock host port} {
fconfigure $sock -translation binary -blocking 0
# Authenticate
set methods [list 0 2]
set data [binary format ccc* 5 [llength $methods] $methods]
binary scan [command $sock $data 2] cucu version method
if {$method == 0} {
# No authentication required
} elseif {$method == 1} {
# GSS-API RFC 1961
# Not implemented
throw {SOCKS AUTH UNKNOWN} "unsupported authentication method: $method"
} elseif {$method == 2} {
# Username/password RFC 1929
authenticate $sock
} else {
throw {SOCKS AUTH NOTACCEPTED} "no acceptable authentication methods"
}
# Connect
set data [binary format ccc 5 1 0]
set ip4 [split $host .]
if {[llength $ip4] == 1 && [llength [set ip6 [split $host :]]] >= 3} {
# IPv6 address
set x [lsearch -exact $ip6 {}]
if {$x >= 0} {
set ip6 [lsearch -inline -exact -all -not $ip6 {}]
set insert [lrepeat [expr {8 - [llength $ip6]}] 0]
set ip6 [linsert $ip6 $x {*}$insert]
}
append data [binary format cS8S 4 $ip6 $port]
} elseif {[llength $ip4] == 4 && [string is digit -strict [lindex $ip4 end]]} {
# IPv4 address
append data [binary format cc4S 1 $ip4 $port]
} else {
# hostname
append data [binary format cca*S 3 [string length $host] $host $port]
}
binary scan [command $sock $data 4 10000] ccxc version reply atyp
if {$reply != 0} {
throw [list SOCKS CONNECT [format ERROR%02X $reply]] \
"socks connection failed with error code $reply"
}
switch $atyp {
1 {
binary scan [command $sock {} 6] c4S dstip dstport
return [join $dstip .]:$dstport
}
3 {
binary scan [command $sock {} 1] c len
binary scan [command $sock {} [expr {$len + 2}]] a${len}S dsthost dstport
return $dsthost:$dstport
}
4 {
binary scan [command $sock {} 18] S8S dstip dstport
return format {[%s]:$d} [join $dstip :] $dstport
}
}
}
proc www::socks::authenticate {sock} {
variable username
variable password
set data [binary format cca*ca* 1 \
[string length $username] $username [string length $password] $password]
binary scan [command $sock 2] cucu version status
if {$version != 1} {
throw {SOCKS AUTH RFC1929 VERSION} \
"unsupported username/password authentication version: $version"
}
if {$status != 0} {
throw {SOCKS AUTH RFC1929 STATUS} \
"username/password authentication failed: $status"
}
}

306
src/vendormodules/www/websocket-1.1.tm

@ -0,0 +1,306 @@
# Helper library for adding websocket support to www
package require www 2.7
proc www::websocket {args} {
set opts {-upgrade {WebSocket www::WebSocket}}
set args [getopt arg $args {
-timeout:milliseconds {dict set opts -timeout $arg}
-auth:data {dict set opts -auth $arg}
-digest:cred {dict set opts -digest $arg}
-maxredir:cnt {dict set opts -maxredir $arg}
}]
if {[llength $args] < 1 || [llength $args] > 3} {
throw {WWW WEBSOCKET ARGS} {wrong # args:\
should be "www::websocket url ?protocols? ?extensions?"}
}
lassign $args url protocols extensions
try {
set hdrs [WebSocket headers]
if {[llength $protocols]} {
lappend hdrs Sec-WebSocket-Protocol [join $protocols {, }]
}
if {[dict size $extensions]} {
set ext [join [lmap name [dict keys $extensions] {
set list [list $name]
if {[dict exists $extensions $name parameters]} {
lappend $list [dict get $extensions $name parameters]
}
join $list {; }
}] {, }]
lappend hdrs Sec-WebSocket-Extensions $ext
}
www get {*}$opts -headers $hdrs $url
} on ok {result info} {
if {[dict get $info status code] != 101} {
# The only correct response for a successful websocket connection
# is 101 Switching Protocols. Even 200 OK is not good.
set code [dict get $info status code]
set codegrp [string replace $code 1 2 XX]
set reason [dict get $info status reason]
dict set info -code 1
dict set info -errorcode [list WWW CODE $codegrp $code $reason]
return -options [dict incr info -level] $result
}
set websock [dict get $info websocket]
set hdrs [dict get $info headers]
set protocol [if {[dict exists $hdrs sec-websocket-protocol]} {
dict get $hdrs sec-websocket-protocol
}]
if {[dict exists $hdrs sec-websocket-extensions]} {
set ext [header [$hdrs sec-websocket-extensions] *]
set mixins [lmap value [lreverse $ext] {
set list [lmap n [split $value {;}] {string trim $n}]
set params [lassign $list name]
dict set parameters $name $params
dict get $extensions $name implementation
}]
oo::objdefine $websock \
mixin www::WSExtension {*}$mixins www::WebSocket
# Inform the extensions of their parameters, if any
$websock parameters $parameters
}
# Return the websocket object command (and the negotiated protocol)
return protocol $protocol [dict get $info websocket]
}
}
namespace ensemble configure www \
-subcommands [linsert [namespace ensemble configure www -subcommands] end websocket]
oo::class create www::WebSocket {
method Startup {headers} {
my variable fd
variable callback {}
# This socket cannot be used for future connections
release [self]
fconfigure $fd -translation binary -buffering none -blocking 0
# Return the websocket object to the caller
my Result websocket [self]
my Return [my PopRequest]
}
method Read {} {
my variable fd
return [read $fd]
}
method Write {data} {
my variable fd
puts -nonewline $fd $data
}
method Handler {} {
my variable fd callback
fileevent $fd readable [list [info coroutine] data]
set data ""
set payload ""
while {![eof $fd]} {
yield
append data [my Read]
if {[binary scan $data B4Xcucu flags code len] != 3} continue
if {$len < 126} {
set pos 2
} elseif {$len == 126} {
if {[binary scan $data x2Su len] != 1} continue
set pos 4
} elseif {$len == 127} {
if {[binary scan $data x2Wu len] != 1} continue
set pos 10
} else {
# Error: Messages from server to client should not be masked
my close 1002
}
if {[string length $data] < $pos + $len} continue
set code [expr {$code & 0xf}]
set payload [string range $data $pos [expr {$pos + $len - 1}]]
set data [string range $data [expr {$pos + $len}] end]
if {$code == 0} {
append message $payload
} else {
set opcode $code
# Control frames MAY be injected in the middle of a
# fragmented message. (RFC6455 5.4)
# Control frames are identified by opcodes where the most
# significant bit of the opcode is 1. (RFC6455 5.5)
if {$code < 8} {set message $payload}
}
if {![string index $flags 0]} continue
if {$opcode < 8} {
my Receive $opcode $message $flags
} else {
my Receive $opcode $payload $flags
}
}
if {[dict exists $callback close]} {
# 1006 is designated for use in applications expecting a status
# code to indicate that the connection was closed abnormally,
# e.g., without sending or receiving a Close control frame.
{*}[dict get $callback close] close 1006 "eof on connection"
}
my destroy
}
# Methods that can be overridden by extensions
method Read {} {
my variable fd
return [read $fd]
}
method Write {data} {
my variable fd
puts -nonewline $fd $data
}
method Receive {opcode data flags} {
my variable callback
switch $opcode {
1 {
if {[dict exists $callback text]} {
set str [encoding convertfrom utf-8 $data]
{*}[dict get $callback text] text $str
} else {
my close 1003
}
}
2 {
if {[dict exists $callback binary]} {
{*}[dict get $callback binary] binary $data
} else {
my close 1003
}
}
8 {
if {[dict exists $callback close]} {
if {[binary scan $data Sua* code reason] != 2} {
set code 1005
set reason ""
}
{*}[dict get $callback close] close $code $reason
set callback {}
}
}
9 {
if {[dict exists $callback ping]} {
{*}[dict get $callback ping] ping $data
} else {
my pong $data
}
}
10 {
if {[dict exists $callback pong]} {
{*}[dict get $callback pong] pong $data
}
}
}
}
method Transmit {opcode data {flags 1}} {
binary scan $data cu* bytes
# The requirement to use a strong source of entropy makes no sense
# So we'll just use Tcl's simple linear congruential generator
set key [expr {int(rand() * 0x100000000)}]
binary scan [binary format I $key] cu* mask
set length [llength $bytes]
# Apply the mask
set i 0
set bytes [lmap n $bytes {
set m [lindex $mask [expr {$i & 3}]]
incr i
expr {$n ^ $m}
}]
set type \
[expr {$opcode | "0b[string reverse [format %04s $flags]]0000"}]
set data [binary format c $type]
if {$length < 126} {
append data [binary format c [expr {$length | 0x80}]]
} elseif {$length < 65536} {
append data [binary format cS [expr {126 | 0x80}] $length]
} else {
append data [binary format cW [expr {127 | 0x80}] $length]
}
append data [binary format c*c* $mask $bytes]
my Write $data
}
# Public methods
method callback {types prefix} {
variable callback
set running [dict size $callback]
if {$prefix ne ""} {
foreach type $types {
dict set callback $type $prefix
}
} elseif {[llength $types]} {
set callback [dict remove $callback {*}$types]
} else {
set callback {}
}
if {[dict size $callback]} {
if {!$running} {coroutine websockcoro my Handler}
} else {
if {$running} {rename websockcoro ""}
}
}
method text {str} {
my Transmit 1 [encoding convertto utf-8 $str]
}
method binary {data} {
my Transmit 2 $data
}
method close {{code 1005} {reason ""}} {
# 1005 is designated for use in applications expecting a status code
# to indicate that no status code was actually present.
set payload [if {$code != 1005} {
binary format Sa* $code [encoding convertto utf-8 $reason]
}]
my Transmit 8 $payload
# The client SHOULD wait for the server to close the connection but
# MAY close the connection at any time after sending and receiving
# a Close message, e.g., if it has not received a TCP Close from
# the server in a reasonable time period.
# my destroy
}
method ping {{data ""}} {
my Transmit 9 $data
}
method pong {{data ""}} {
my Transmit 10 $data
}
}
oo::class create www::WSExtension {
method parameters {parameters} {
dict for {mixin params} $parameters {
nextto $mixin $params
}
}
}
oo::objdefine www::WebSocket {
method key {} {
# Generate a websocket key containing base64-encoded random bytes
# This key is only intended to prevent a caching proxy from
# re-sending a previous WebSocket conversation, and does not
# provide any authentication, privacy or integrity.
# It is therefor not necessary to check the returned hash.
for {set i 0} {$i < 12} {incr i} {
lappend bytes [expr {int(rand() * 256)}]
}
return [binary encode base64 [binary format c* $bytes]]
}
method headers {} {
return [list Sec-WebSocket-Key [my key] Sec-WebSocket-Version 13]
}
}
www register ws 80
www register wss 443 www::encrypt 1

5
src/vfs/_config/punk_main.tcl

@ -876,11 +876,12 @@ apply { args {
}
set ::tcl_interactive 1
set ::tclsh(dorepl) 1
} elseif {[llength $arglist]} {
} elseif {[lindex $arglist 0] eq "shellspy"} {
#pass through to shellspy commandline processor
#puts stdout "main.tcl launching app-shellspy"
package require app-shellspy
} elseif {[llength $arglist]} {
package require app-punkshell
} else {
#punk shell
#todo logger ?

14
src/vfs/_vfscommon.vfs/doc/bogus.tcl

@ -8,9 +8,17 @@ package require Tcl 8.5-
# # ## ### ##### ######## ############# #####################
namespace eval ::bogus {
proc test {args} {
puts stderr "bogus-should-not-load-$args"
}
proc test {args} {
puts stderr "bogus-should-not-load-$args"
}
proc about {} {
set msg ""
append msg "tclkits do not scan all directories in the base of their vfs for pkgIndex.tcl files"
append msg "This is because the base of the vfs is not in the ::auto_path by default."
append msg "zipfs based tcl will add the root of the vfs (mountpoint //zipfs:/app) to the ::auto_path,"
append msg "not just //zipfs:/app/lib. This means every directory in the root of the zipfs vfs is scanned"
append msg "for pkgIndex.tcl files."
}
}
# # ## ### ##### ######## ############# #####################

4
src/vfs/_vfscommon.vfs/lib/app-punk/pkgIndex.tcl

@ -1,3 +1 @@
package ifneeded app-punk 1.0 [list source [file join $dir repl.tcl]]
package ifneeded app-punk 1.0 [list source [file join $dir repl.tcl]]

2
src/vfs/_vfscommon.vfs/lib/app-punkshell/pkgIndex.tcl

@ -0,0 +1,2 @@
package ifneeded app-punkshell 1.0 [list source [file join $dir punkshell.tcl]]

296
src/vfs/_vfscommon.vfs/lib/app-punkshell/punkshell.tcl

@ -0,0 +1,296 @@
package provide app-punkshell 1.0
package require Thread
package require punk::args
package require shellfilter
package require punk::ansi
package require punk::packagepreference
punk::packagepreference::install
namespace eval punkshell {
variable chanstack_stderr_redir
variable chanstack_stdout_redir
proc clock_sec {} {
return [expr {[clock millis]/1000.0}]
}
set do_log 0
if {$do_log} {
set debug_syslog_server 127.0.0.1:514
#set debug_syslog_server 172.16.6.42:51500
set error_syslog_server 127.0.0.1:514
set data_syslog_server 127.0.0.1:514
} else {
set debug_syslog_server ""
set error_syslog_server ""
set data_syslog_server ""
}
#-------------------------------------------------------------------------
##don't write to stdout/stderr before you've redirected them to a log using shellfilter functions
## puts to stdout/stderr will comingle with command's output if performed before the channel stacks are configured.
#chan configure stdin -buffering line
#chan configure stdout -buffering none
#chan configure stderr -buffering none
#redir on the shellfilter stack with no log or syslog specified acts to suppress output of stdout & stderr.
#todo - fix shellfilter code to make this noop more efficient (avoid creating corresponding logging thread and filter?)
#JMN
#set redirconfig {-settings {-syslog 127.0.0.1:514 -file ""}}
set redirconfig {}
#lassign [shellfilter::redir_output_to_log "SUPPRESS" {*}$redirconfig] chanstack_stdout_redir chanstack_stderr_redir
#shellfilter::log::write $punkshell_status_log "shellfilter::redir_output_to_log SUPPRESS DONE [clock_sec]"
set stdout_log ""
set stderr_log ""
#set stdout_log [file normalize ~]/punkshell-stdout.txt
#set stderr_log [file normalize ~]/punkshell-stderr.txt
set stdout_log "[pwd]/punkshell_out.log"
set stderr_log "[pwd]/punkshell_err.log"
set errdeviceinfo [shellfilter::stack::new punkshellerr -settings [list -tag "punkshellerr" -buffering none -raw 1 -syslog $data_syslog_server -file $stderr_log]]
set outdeviceinfo [shellfilter::stack::new punkshellout -settings [list -tag "punkshellout" -buffering none -raw 1 -syslog $data_syslog_server -file $stdout_log]]
#set commandlog [dict get $outdeviceinfo localchan]
#puts $commandlog "HELLO $commandlog"
#flush $commandlog
proc do_script {scriptname args} {
#ideally we don't want to launch an external process to run the script
#variable punkshell_status_log
#shellfilter::log::write $punkshell_status_log "do_script got scriptname:'$scriptname' replwhen:$replwhen args:'$args'"
set exepath [file dirname [file join [info nameofexecutable] __dummy__]]
set exedir [file dirname $exepath]
set scriptpath [file normalize $scriptname]
if {![file exists $scriptpath]} {
puts stderr "Failed to find script: '$scriptpath'"
error "bad scriptpath '$scriptpath'"
}
set script [string map [list %a% $args %s% $scriptpath] {
set normscript %s%
#save values
set prevscript [info script]
set prevglobal [dict create]
foreach g [list ::argv ::argc ::argv0] {
if {[info exists $g]} {
dict set prevglobal $g [set $g]
}
}
#setup and run
set ::argv [list %a%]
set ::argc [llength $::argv]
set ::argv0 $normscript
info script $normscript
source $normscript
#restore values
info script $prevscript
dict with prevglobal {}
}]
dict set params -tclscript 1 ;#don't give callback a chance to omit/break this
dict set params -teehandle punkshell
#dict set params -teehandle punksh
dict set params -inbuffering none
dict set params -outbuffering none
dict set params -readprocesstranslation crlf
dict set params -outtranslation lf
set id_err [shellfilter::stack::add stderr ansiwrap -action sink-locked -settings {-colour {red bold}}]
set exitinfo [shellfilter::run $script {*}$params]
shellfilter::stack::remove stderr $id_err
if {[dict exists $exitinfo errorInfo]} {
#strip out the irrelevant info from the errorInfo - we don't want info beyond 'invoked from within' as this is just plumbing related to the script sourcing
set stacktrace [string map [list \r\n \n] [dict get $exitinfo errorInfo]]
set output ""
set tracelines [split $stacktrace \n]
foreach ln $tracelines {
if {[string match "*invoked from within*" $ln]} {
break
}
append output $ln \n
}
set output [string trimright $output \n]
dict set exitinfo errorInfo $output
}
return $exitinfo
}
proc do_tclkit {kitname replwhen args} {
set script [string map [list %a% $args %k% $kitname] {
#::tcl::tm::add %m%
set kit %k%
set kitpath [file normalize $kit]
set kitmount $kitpath.0
#save values
set prevscript [info script]
set prevglobal [dict create]
foreach g [list ::argv ::argc ::argv0] {
if {[info exists $g]} {
dict set prevglobal $g [set $g]
}
}
#setup and run
set ::argv [list %a%]
set ::argc [llength $::argv]
set ::argv0 $kitmount
#puts stderr "setting 'info script' $kitmount/main.tcl"
info script $kitmount/main.tcl
#info script dir must match argv0 for kit main.tcl to return 'starkit' from 'starkit::startup'
if {![catch {
package require vfs
package require vfs::mk4
} errMsg]} {
vfs::mk4::Mount $kitpath $kitmount
lappend ::auto_path $kitmount/lib
if {[file exists "$kitmount/modules"]} {
tcl::tm::add "$kitmount/modules"
}
#puts stderr "sourcing $kitmount/main.tcl"
#puts stderr "$kitmount/main.tcl exists: [file exists $kitmount/main.tcl]"
#puts stderr "argv : $::argv"
#puts stderr "argv0: $::argv0"
#puts stderr "autopath: $::auto_path"
#puts stdout "starkit::startup [starkit::startup]"
#usually main.tcl will just be something like: package require app-XXX
#it will usually do nothing if starkit::startup returned 'sourced'
source $kitmount/main.tcl
} else {
puts stderr "Unable to load vfs::mk4 for tclkit mounting"
}
#restore values
info script $prevscript
dict with prevglobal {}
}]
set repl_lines ""
append repl_lines {package require punk::repl} \n
append repl_lines {repl::init -safe 0} \n
append repl_lines {repl::start stdin} \n
#test
#set replwhen "repl_last"
if {$replwhen eq "repl_first"} {
#we need to cooperate with the repl to get the script to run on exit
namespace eval ::repl {}
set ::repl::post_script $script
set script "$repl_lines"
} elseif {$replwhen eq "repl_last"} {
append script $repl_lines
} else {
#just the script
}
dict set params -tclscript 1 ;#don't give callback a chance to omit/break this
dict set params -teehandle punkshell
dict set params -inbuffering none
dict set params -outbuffering none
dict set params -readprocesstranslation crlf
dict set params -outtranslation lf
set id_err [shellfilter::stack::add stderr ansiwrap -action sink-locked -settings {-colour {red bold}}]
set exitinfo [shellfilter::run $script {*}$params]
shellfilter::stack::remove stderr $id_err
if {[dict exists $exitinfo errorInfo]} {
#strip out the irrelevant info from the errorInfo - we don't want info beyond 'invoked from within' as this is just plumbing related to the script sourcing
set stacktrace [string map [list \r\n \n] [dict get $exitinfo errorInfo]]
set output ""
set tracelines [split $stacktrace \n]
foreach ln $tracelines {
if {[string match "*invoked from within*" $ln]} {
break
}
append output $ln \n
}
set output [string trimright $output \n]
dict set exitinfo errorInfo $output
}
return $exitinfo
}
punk::args::define {
@id -id ::punkshell
@cmd -name punkshell
@leaders -min 0 -max 0
@opts
-debug -type none
@values -min 1 -max -1
script_or_kit -type string
arg -type any -optional 1 -multiple 1
}
set argd [punk::args::parse $::argv withid ::punkshell]
lassign [dict values $argd] leaders opts values received
set script_or_kit [dict get $values script_or_kit]
if {[dict exists $received arg]} {
set arglist [dict get $values arg]
} else {
set arglist [list]
}
set exitinfo [dict create]
switch -glob -nocase -- $script_or_kit {
lib:* {
#scriptlib
puts stderr "lib:* todo"
}
*.tcl {
set exitinfo [punkshell::do_script $script_or_kit {*}$arglist]
}
*.kit {
set exitinfo [punkshell::do_tclkit $script_or_kit "no_repl" {*}$arglist]
}
default {
puts stderr "unrecognised script extension"
}
}
catch {
shellfilter::stack::remove stderr $chanstack_stderr_redir
shellfilter::stack::remove stdout $chanstack_stdout_redir
}
shellfilter::stack::delete punkshellout
shellfilter::stack::delete punkshellerr
set free_info [shellthread::manager::shutdown_free_threads]
foreach tid [thread::names] {
thread::release $tid
}
if {[dict size $exitinfo] == 0} {
puts stderr "No result"
exit 2
}
if {[dict exists $exitinfo errorInfo]} {
set einf [dict get $exitinfo errorInfo]
puts stderr "errorCode: [dict get $exitinfo errorCode]"
if {[catch {
punk::ansi::ansiwrap yellow bold $einf
} msg]} {
set msg $einf
}
puts stderr $msg
flush stderr
exit 1
} else {
puts -nonewline stdout [dict get $exitinfo result]
exit 0
}
}

5
src/vfs/_vfscommon.vfs/lib/app-shellspy/pkgIndex.tcl

@ -1,3 +1,2 @@
package ifneeded app-shellspy 1.0 [list source [file join $dir shellspy.tcl]]
package ifneeded app-shellspy 1.0 [list source [file join $dir shellspy.tcl]]

2449
src/vfs/_vfscommon.vfs/lib/app-shellspy/shellspy.tcl

File diff suppressed because it is too large Load Diff

2718
src/vfs/_vfscommon.vfs/modules/flagfilter-0.3.1.tm

File diff suppressed because it is too large Load Diff

5
src/vfs/_vfscommon.vfs/modules/punk/char-0.1.0.tm

@ -186,8 +186,9 @@ tcl::namespace::eval punk::char {
set r [list "" {*}[split $lowbits ""] $ridx {*}$charlist]
$t add_row $r
}
puts stderr $t
$t print
set result [$t print]
$t destroy
return $result
}
#just the 7-bit ascii. use [page ascii] for the 8-bit layout

636
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd

@ -1,4 +1,4 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
@ -16,6 +16,70 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@rem -------------------------------------------------------------------------------------------------------------------------------
@rem return from endlocal macro - courtesy of jeb
@rem This allows return of values containing special characters from subroutines
@rem https://stackoverflow.com/questions/3262287/make-an-environment-variable-survive-endlocal/8257951#8257951
@rem -------------------------------------------------------------------------------------------------------------------------------
@setlocal DisableDelayedExpansion
@echo off
%= 2 blank lines after next are required =%
set LF=^
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
%= I use EDE for EnableDelayeExpansion and DDE for DisableDelayedExpansion =%
set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n%
setlocal EnableDelayedExpansion%\n%
%= Take all variable names into the varName array =%%\n%
set varName_count=0%\n%
for %%C in (!args!) do set "varName[!varName_count!]=%%~C" ^& set /a varName_count+=1%\n%
%= Build one variable with a list of set statements for each variable delimited by newlines =%%\n%
%= The lists looks like --> set result1=myContent\n"set result1=myContent1"\nset result2=content2\nset result2=content2\n =%%\n%
%= Each result exists two times, the first for the case returning to DDE, the second for EDE =%%\n%
%= The correct line will be detected by the (missing) enclosing quotes =%%\n%
set "retContent=1!LF!"%\n%
for /L %%n in (0 1 !varName_count!) do (%\n%
for /F "delims=" %%C in ("!varName[%%n]!") DO (%\n%
set "content=!%%C!"%\n%
set "retContent=!retContent!"set !varName[%%n]!=!content!"!LF!"%\n%
if defined content (%\n%
%= This complex block is only for replacing '!' with '^!' =%%\n%
%= First replacing '"'->'""q' '^'->'^^' =%%\n%
set ^"content_EDE=!content:"=""q!"%\n%
set "content_EDE=!content_EDE:^=^^!"%\n%
%= Now it's poosible to use CALL SET and replace '!'->'""e!' =%%\n%
call set "content_EDE=%%content_EDE:^!=""e^!%%"%\n%
%= Now it's possible to replace '""e' to '^', this is effectivly '!' -> '^!' =%%\n%
set "content_EDE=!content_EDE:""e=^!"%\n%
%= Now restore the quotes =%%\n%
set ^"content_EDE=!content_EDE:""q="!"%\n%
) ELSE set "content_EDE="%\n%
set "retContent=!retContent!set "!varName[%%n]!=!content_EDE!"!LF!"%\n%
)%\n%
)%\n%
%= Now return all variables from retContent over the barrier =%%\n%
for /F "delims=" %%V in ("!retContent!") DO (%\n%
%= Only the first line can contain a single 1 =%%\n%
if "%%V"=="1" (%\n%
%= We need to call endlocal twice, as there is one more setlocal in the macro itself =%%\n%
endlocal%\n%
endlocal%\n%
) ELSE (%\n%
%= This is true in EDE =%%\n%
if "!"=="" (%\n%
if %%V==%%~V (%\n%
%%V !%\n%
)%\n%
) ELSE IF not %%V==%%~V (%\n%
%%~V%\n%
)%\n%
)%\n%
)%\n%
) else set args="
@rem -------------------------------------------------------------------------------------------------------------------------------
@SETLOCAL EnableExtensions EnableDelayedExpansion
@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: dev scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@ -24,7 +88,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________"
@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@ -51,8 +114,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: <<asadmin_start>>
@SET "asadmin=0"
: <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@REM @ECHO selected_shelltype %selected_shelltype%
@CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed
@ -73,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- deck scriptwrap.checkfile filepath
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@ -83,12 +144,13 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@SET "winpath=%~dp0"
@SET "winpath=%~dp0" %= e.g c:\punkshell\bin\ %=
@SET "fname=%~nx0"
@SET "scriptrootname=%~dp0%~n0" %= e.g c:\punkshell\bin\runtime (full path without extension) unavailable after shift, so store it =%
@REM @ECHO fname %fname%
@REM @ECHO winpath %winpath%
@REM @ECHO commandlineascalled %0
@ -123,17 +185,6 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ -178,25 +229,101 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=pwsh
)
@set argCount=30
@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe
@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon
@rem Set argCount higher if desired, but there is a small amount of additional looping overhead.
@set tmpfile_base=%TEMP%\punkbatch_params
@call :getUniqueFile %tmpfile_base% ".txt" paramfile
@echo %paramfile%
%= NOTE when we loop like this using the percent-n args, we lose unquoted separators such as comma and semicolon %=
@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124
@rem outer loop required to redirect all rem lines at once to file
@for %%x in (1) do @(
@for /L %%f in (1,1,%argCount%) do @(
@set "argnum=%%~nf"
@set "a1=%%1"
@rem @set "argname=%%!argnum!"
@rem @echo argname: !argname!
@call :rem_output !argnum! !a1!
@shift
)
) > %paramfile%
@echo off
@set "newcommandline= "
@(set target=cmd_pwsh)
@if "%target%"=="cmd_pwsh" (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
@REM @echo ######### %%L
@rem call :buildcmdline newcommandline param "{" "}"
@rem call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell ... -c %=
call :buildcmdline newcommandline param %= cmd.exe /c powershell ... -f %=
@rem @echo .
)
) ELSE (
@for /F "delims=" %%L in (%paramfile%) do @(
SETLOCAL DisableDelayedExpansion
set "param=%%L"
call :buildcmdline newcommandline param
)
)
@REM padding
SETLOCAL EnableDelayedExpansion
@echo off
@IF EXIST %paramfile% (
@DEL /F /Q %paramfile%
)
@IF EXIST %paramfile% (
echo failed to delete %paramfile%
cat %paramfile%
)
@REM @SET "squoted_args="
@REM @for %%a in (%*) do @(
@REM set "v=%%a"
@REM set "v=!v:'=''!"
@REM SET "squoted_args=!squoted_args!'!v!' "
@REM )
@REM @SET "squoted_args=%squoted_args:~0,-1%"
@REM @ECHO %squoted_args%
@IF "!selected_shelltype_trimmed!"=="pwsh" (
REM pwsh vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
REM when run without cmd.exe - pwsh will receive the semicolon (for cmd.exe unquoted semicolon and comma are separators that aren't seen in positional arguments)
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted 2>NUL; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist%
@rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted
cmd /c pwsh -nop -nol -ExecutionPolicy bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
REM TODO prompt user with option to call script to install pwsh using winget
REM powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist%
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!selected_shelltype_trimmed!"=="powershell" (
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist%
@rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist%
cmd /c powershell -nop -nol -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline!
SET task_exitcode=!errorlevel!
) ELSE (
IF "!selected_shelltype_trimmed!"=="wslbash" (
@ -211,7 +338,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
@ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
%selected_shellpath_trimmed% "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@ -222,9 +349,145 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
@REM padding
@REM padding
@REM padding
%= ---------------------------------------------------------------------- =%
@rem courtesy of dbenham
:: Example usage
@rem call :getUniqueFile "d:\test\myFile" ".txt" myFile
@rem echo myFile="%myFile%"
:getUniqueFile baseName extension rtnVar
setlocal
:getUniqueFileLoop
for /f "skip=1" %%A in ('wmic os get localDateTime') do for %%B in (%%A) do set "rtn=%~1_%%B%~2"
if exist "%rtn%" (
goto :getUniqueFileLoop
) else (
2>nul >nul (9>"%rtn%" timeout /nobreak 1) || goto :getUniqueFileLoop
)
endlocal & set "%~3=%rtn%"
exit /b
%= ---------------------------------------------------------------------- =%
@REM padding
:buildcmdline cmdlinevar paramvar wrapA wrapB
%= quoting for cmd.exe /c pwsh -nop !args! =%
@SETLOCAL EnableDelayedExpansion
@REM @echo =====================
set "pval=!%~2:*#=!"
set "pval=!pval:~0,-2!"
@REM set "pval=!pval:~0,-1!"
set "wrapa=%~3"
set "wrapb=%~4"
@call :strlen pval slen
@rem @echo strlen: !slen!
if "!slen!"=="0" (
goto :eof
)
@set /A n = !slen! - 1
@(set str=)
@set "dq=""
@set "bang=^!"
@(set carat=^^)
@for /l %%i in (0,1,!n!) do @(
(set c=!pval:~%%i,1!)
if "!c!"=="|" (
set "ch=^^!pval:~%%i,1!"
) ELSE IF "!c!"=="(" (
set "ch=^("
) ELSE if "!c!"==")" (
set "ch=^)"
) ELSE if "!c!"=="&" (
set "ch=^^&"
) ELSE if "!c!"=="!dq!" (
set "ch=^""
) ELSE if "!c!"=="!bang!" (
@rem - double caret - first for initial parsing, second to ensure remains escaped during delayed expansion phase
@rem - REVIEW
set "ch=^^!bang!"
) ELSE if "!c!"=="^carat" (
set "ch=^^^^"
) ELSE if "!c!"=="'" (
set "ch=''"
) ELSE (
set "ch=!c!"
)
@rem @echo - !ch!
set "str=!str!!ch!"
)
echo +++++ %~1 = !%1! !str!
set "%~1=!%1! !wrapa!!str!!wrapb!"
@rem old method of return - failes to preserve exclamation marks
@rem for /f "delims=" %%A in (""!str!"") do endlocal & set "%~1=!%1! '%%~A'"
@rem macro method of endlocal return - preserving !val!
@echo off
%endlocal% %~1
@exit /b
:rem_output
@rem ---------------------------------------------
@rem rem_output is called for each n in the number of args we process - as we don't have a non-destructive way to count args whilst accepting special chars
@rem we therefore need a way to stop processing on the last received arg so we don't write argCount entries to param.txt if less are received
@rem see 'disappearing quotes' technique
@rem https://stackoverflow.com/questions/4643376/how-to-split-double-quoted-line-into-multiple-lines-in-windows-batch-file/4645113#4645113
@rem and
@rem https://groups.google.com/g/alt.msdos.batch.nt/c/J71F17Bve9Y (sponge belly)
@echo off
setlocal enableextensions disabledelayedexpansion
set "param1=%~2"
rem do must not be indented
for %%^" in ("") ^
do if not defined param1 set %%~"param1=%2%%~"
if not defined param1 goto :eof
endlocal
@rem ---------------------------------------------
@PROMPT @
@echo on
rem %1 #%2#
@exit /b
@REM courtesy of: https://stackoverflow.com/users/463115/jeb
:strlen stringVar returnVar
@(
setlocal EnableDelayedExpansion
@SET "rtrn=%~2"
(set^ tmp=!%~1!)
@rem @echo jjjjj !tmp!
@if defined tmp (
set "len=1"
@for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do @(
@if "!tmp:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "tmp=!tmp:~%%P!"
)
)
) ELSE (
set len=0
)
)
@(
endlocal
@IF "%~2" neq "" (
@SET "%rtrn%=%len%"
) ELSE (
@ECHO :strlen result: %len%
)
exit /b
)
:getWslPath
@SETLOCAL
@SET "_path=%~p1"
@ -280,7 +543,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@ -295,13 +558,12 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -327,6 +589,10 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringContains
@REM usage: @CALL:stringContains string needle returnvarname
@SETLOCAL
@ -348,7 +614,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
:stringToUpper strvar returnvar
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@ -384,6 +650,11 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@ -442,12 +713,104 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
# -- i.e it is a polyglot file.
# -- The specific layout including some lines that appear just as comments is quite sensitive to change.
# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline.
# -- e.g ./filename.polypunk.cmd in sh or bash
# -- e.g tclsh filename.cmd
# -- e.g ./scriptname.cmd in sh or zsh or bash
# -- e.g tclsh scriptname.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
#---------------------------------------------------------------------
#divert to configured nextshell
package require platform
set plat_full [platform::generic]
set plat [lindex [split $plat_full -] 0]
#may be old tcl - not assuming readFile available
set fd [open [info script] r]
set scriptdata [read $fd]
close $fd
set scriptdata [string map [list \r\n \n] $scriptdata]
set in_data 0
set nextshellpath ""
set nextshelltype ""
puts stderr "PLAT: $plat"
foreach ln [split $scriptdata \n] {
if {[string trim $ln] eq ""} {continue}
if {!$in_data} {
if {[string match ": <<nextshell_start>>*" $ln]} {
set in_data 1
}
} else {
if {[string match "*@SET*nextshellpath?${plat}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshellpath [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match "*@SET*nextshelltype?${plat}_*" $ln]} {
set lineparts [split $ln =]
set tail [lindex $lineparts 1]
set nextshelltype [string trimright $tail {_"}]
if {$nextshellpath ne "" && $nextshelltype ne ""} {
break
}
} elseif {[string match ": <<nextshell_end>>*" $ln]} {
break
}
}
}
if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} {
if {$nextshelltype in "pwsh powershell"} {
set scrname [file rootname [info script]].ps1
set arglist [list]
foreach a $::argv {
set a "'$a'"
lappend arglist $a
}
} else {
set scrname [info script]
set arglist $::argv
}
puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist"
#todo - handle /usr/bin/env
#todo - exitcode
if {[llength $nextshellpath] == 1 && [string index $nextshellpath 0] eq {"} && [string index $nextshellpath end] eq {"}} {
set nextshell_words [list $nextshellpath]
} else {
set nextshell_words $nextshellpath
}
set ns_firstword [lindex $nextshellpath 0]
if {[string index $ns_firstword 0] eq {"} && [string index $ns_firstword end] eq {"}} {
set ns_firstword [string range $ns_firstword 1 end-1]
}
if {[string match {/*/env} $ns_firstword] && $::tcl_platform(platform) ne "windows"} {
set exec_part $nextshellpath
} else {
set epath [auto_execok $ns_firstword]
if {$epath eq ""} {
error "unable to find executable path for first word '$ns_firstword' of nextshellpath '$nextshellpath'"
} else {
set exec_part [list {*}$epath {*}[lrange $nextshellpath 1 end]]
}
}
catch {exec {*}$exec_part $scrname {*}$arglist <@stdin >@stdout 2>@stderr} emsg eopts
if {[dict exists $eopts -errorcode]} {
set ecode [dict get $eopts -errorcode]
if {[lindex $ecode 0] eq "CHILDSTATUS"} {
exit [lindex $ecode 2]
} else {
puts stderr "error calling next shell $nextshelltype :"
puts stderr $emsg
exit 1
}
} else {
exit 0
}
}
#---------------------------------------------------------------------
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
@ -481,7 +844,7 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as perl or bash"
puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl"
#</tcl-payload>
#<tcl-pre-launch-subprocess>
@ -512,21 +875,26 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "script: `echo $0 | sed 's/^-//'`"
# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \
echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#csh/tcsh diversion \
test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit )
#other non-bash diversion \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0
#review \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit
# sh/bash \
shift && set -- "${@:1:$#-1}"
# Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \
echo "var0: $0 @: $@"
# echo "script: `echo $0 | sed 's/^-//'`"
# use oldschool backticks and sed - lowest common denominator \
# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# zsh diversion \
# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi
# \
ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
# \
echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}"
# non-bash-like diversion \
if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi
# sh/bash (or zsh?) \
shift && set -- "${@:1:$((${#@}-1))}"
# \
#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
#------------------------------------------------------
# \
echo "args: $@"
# ------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
then
@ -541,20 +909,30 @@ if false==false # else {
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
if [[ "$OSTYPE" == "linux"* ]]; then
plat=$(uname -s) #platform/system
if [[ "$plat" = "Linux"* ]]; then
os="linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
elif [[ "$OSTYPE" == "freebsd"* ]]; then
elif [[ "$plat" == "FreeBSD"* ]]; then
os="freebsd"
elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then
elif [[ "$plat" == "DragonFly"* ]]; then
os="dragonflybsd"
elif [[ "$OSTYPE" == "netbsd"* ]]; then
elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$OSTYPE" == "win32" ]]; then
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
elif [[ "$plat" = "MINGW32"* ]]; then
os="win32"
elif [[ "$plat" = "MINGW64"* ]]; then
os="win32"
elif [[ "$OSTYPE" == "msys" ]]; then
elif [[ "$plat" = "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
echo MSYS
#win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#bash reports $OSTYPE msys
os="win32"
#review - need ps/sed/awk to determine shell?
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
@ -564,37 +942,50 @@ elif [[ "$OSTYPE" == "msys" ]]; then
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
else
#os="$OSTYPE"
os="other"
fi
echo ostype: $OSTYPE
shellconfigline=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#echo $shellconfigline;
if [[ $shellconfigline == *"nextshelltype"* ]]; then
echo "found config for os $os"
split1="${shellconfigline#*=}" #remove everything through the first '='
#echo "split1: $split1"
pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match
pathraw="${pathraw//\"/}" #remove quote
nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end)
#echo "nextshellpath: $nextshellpath"
split2="${split1#*=}"
#echo "split2: $split2"
split2="${split2//\"/}"
nextshelltype="${split2/%_*/}"
echo "nextshelltype: $nextshelltype"
## This is the sort of sed that will not work across implementations
## shellconfiglines=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#awk tested on linux & freebsd
shellconfiglines=$( awk '/^:.*<<nextshell_start>>.*$/,/^:.*<<nextshell_end>>.*$/' "$0" | grep $os)
# echo $shellconfiglines;
# readarray requires bash 4.0
if [[ "$ps_shellname" == "bash" ]]; then
readarray -t arr_oslines <<<"$shellconfiglines"
elif [[ "$ps_shellname" == "zsh" ]]; then
arr_oslines=("${(f)shellconfiglines}")
else
echo "unable to find config for os $os"
echo "shellconfigline: $shellconfigline"
nextshellpath=""
nextshelltype=""
#fallback - doesn't seem to work in zsh - untested in early bash
IFS=$'\n' arr_oslines=($shellconfiglines)
fi
nextshellpath=""
nextshelltype=""
for ln in "${arr_oslines[@]}"; do
# echo "---- $ln"
if [[ "$ln" == *"nextshellpath"* ]]; then
splitln="${ln#*=}" #remove everything through the first '='
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
echo "nextshelltype: $nextshelltype"
fi
done
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
#echo bash launching subshell of type $nextshelltype $nextshellpath on "$0"
#/usr/bin/env tclsh "$0" "$@"
echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@"
#e.g /usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
@ -752,12 +1143,14 @@ function GetDynamicParamDictionary {
return $DynParamDictionary
}
}
# Example usage:
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# [Parameter(Mandatory)][string] $myargument,
# [Parameter(ValueFromRemainingArguments)] $opts
# )
#}
#function psmain {
@ -768,10 +1161,15 @@ function GetDynamicParamDictionary {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# 'myargument' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# 'opts' {
# write-warning "Unused parameters: $($PSBoundParameters.$_)"
# }
# Default {
# write-warning "Unhandled parameter -> [$($_)]"
# }
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
@ -779,24 +1177,24 @@ function GetDynamicParamDictionary {
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# Write-Host "myargument value is: $myargument"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
"Script Name : {0}" -f $scriptname | write-host
"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
$thisfileContent = Get-Content $scriptname -Raw
$startTag = ": <<asadmin_start>>"
$endTag = ": <<asadmin_end>>"
$fileContent = Get-Content $scriptname -Raw
$pattern = "(?s)$startTag(.*?)$endTag"
$matches = [regex]::Matches($fileContent,$pattern)
$admininfo = $matches[0].Groups[1].Value
$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
$match = [regex]::Match($thisfileContent,$pattern)
$asadmin = 0
if ($matches.count) {
if ($match.Success) {
$admininfo = $match.Groups[1].Value
$asadmin = $admininfo.Contains("asadmin=1")
if ($asadmin) {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
@ -814,10 +1212,72 @@ if ($matches.count) {
}
}
}
#
$startTag = ": <<nextshell_start>>"
$endTag = ": <<nextshell_end>>"
$pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag"
$match = [regex]::Match($thisfileContent,$pattern)
if ($match.Success) {
$plat = [System.Environment]::OSVersion.Platform
if ($plat -eq "Unix") {
$runtime_ident = [System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier
switch ($runtime_ident.split("-")[0]) {
"freebsd" {
# untested
$os = "freebsd"
}
"linux" {
$os = "linux"
}
"osx" {
# osx-x64 or osx-arm64 ?
$os = "macosx"
}
default {
#openbsd, netbsd ?
$os = "other"
}
}
} else {
$os = "win32"
}
$matchedlines = $match.Groups[1].Value
$nextshell_type = ""
$nextshell_path = ""
ForEach ($line in $($matchedlines -split "\r?\n")) {
$m = [regex]::Match($line,".*nextshelltype\[${os}[_]+\]=([^_]*)[_]*")
if ($m.Success) {
$nextshell_type = $m.Groups[1].Value
}
$m = [regex]::Match($line,".*nextshellpath\[${os}[_]+\]=([^_]*)[_]*")
if ($m.Success) {
$nextshell_path = $m.Groups[1].Value
}
if ($nextshell_type -ne "" -and $nextshell_path -ne "") {
break
}
}
if (-not (("pwsh", "powershell", "") -contains $nextshell_type)) {
#nextshell diversion exists for this platform
write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $scriptname"
# $arguments = @($($MyInvocation.MyCommand.Path))
# $arguments += $args
# NOTE - this gives incorrect argument quoting e.g wrong number of arguments received by launched process for arguments: a "b c"
# $process = (Start-Process -FilePath $nextshell_path -ArgumentList $arguments -NoNewWindow -Wait)
# Exit $process.ExitCode
& $nextshell_path $scriptname $args
exit $LASTEXITCODE
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#<powershell-payload>
Write-Error "No powershell code for this script. Try another program such as perl, tcl or bash"
Write-Error "No powershell code for this script. Try another program such as tcl or bash`n"
"powershell args : {0}" -f ($args -join ", ") | write-host
#</powershell-payload>
#<powershell-pre-launch-subprocess>

317
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell1.cmd

@ -1,5 +1,5 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
@ -16,41 +16,41 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: dev scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM Call with sh, bash, perl, or tclsh. (powershell untested on unix)
@REM Due to lack of shebang (#! line) Unix-like systems will hopefully default to a flavour of sh that can divert to bash if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________"
@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set
@REM If more than 32 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM Supporting more explicit oses than those listed may also require script padding adjustment
: <nextshell>
@SET "nextshellpath[win32___________]=tclsh___________________________"
: <<nextshell_start>>
@SET "nextshellpath[win32___________]=tclsh___________________________________________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________________________________________"
@SET "nextshelltype[other___________]=tcl_____________"
: </nextshell>
: <<nextshell_end>>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
: <<asadmin_start>>
@SET "asadmin=0"
: </asadmin>
: <<asadmin_end>>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@ -143,7 +143,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@ECHO Launching script in new window due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -175,43 +175,57 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "%selected_shelltype_trimmed%"=="powershell" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
@IF "!selected_shelltype_trimmed!"=="none" (
SET selected_shelltype_trimmed=pwsh
)
@SET "squoted_args="
@for %%a in (%*) do @(
set "v=%%a"
set "v=!v:'=''!"
SET "squoted_args=!squoted_args!'!v!' "
)
@SET "squoted_args=%squoted_args:~0,-1%"
@ECHO %squoted_args%
@IF "!selected_shelltype_trimmed!"=="pwsh" (
REM pwsh vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted 2>NUL; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %squoted_args%
SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; %~dp0%~n0.ps1" %arglist%
REM TODO prompt user with option to call script to install pwsh using winget
REM powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %squoted_args%
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "%selected_shelltype_trimmed%"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
IF "!selected_shelltype_trimmed!"=="powershell" (
powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %squoted_args%
SET task_exitcode=!errorlevel!
) ELSE (
REM perl or tcl or sh or bash
IF NOT "x%keyRemoved%"=="x%validshelltypes%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
IF "!selected_shelltype_trimmed!"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
REM perl or tcl or sh or bash
IF NOT "x%keyRemoved%"=="x%validshelltypes%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
@ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!"
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
GOTO :exit_multishell
)
)
)
)
@ -342,7 +356,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
:stringToUpper <strvar> <returnvar>
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@ -383,14 +397,15 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 31 underscores from the end of a string using string substitution
@SET trimstring=%trimstring%###
@SET trimstring=%trimstring:________________###=###%
@SET trimstring=%trimstring:________###=###%
@SET trimstring=%trimstring:____###=###%
@SET trimstring=%trimstring:__###=###%
@SET trimstring=%trimstring:_###=###%
@SET trimstring=%trimstring:###=%
@REM trim up to 63 underscores from the end of a string using string substitution
@SET "trimstring=%trimstring%###"
@SET "trimstring=%trimstring:________________________________###=###%"
@SET "trimstring=%trimstring:________________###=###%"
@SET "trimstring=%trimstring:________###=###%"
@SET "trimstring=%trimstring:____###=###%"
@SET "trimstring=%trimstring:__###=###%"
@SET "trimstring=%trimstring:_###=###%"
@SET "trimstring=%trimstring:###=%"
@SET "result=!trimstring!"
@ENDLOCAL & (
@IF "%~2" neq "" (
@ -439,7 +454,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
# -- e.g tclsh filename.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${::argv0}/__]]
@ -473,6 +488,9 @@ namespace eval ::punk::multishell {
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
puts stderr "No tcl code for this script. Try another program such as perl or bash"
#</tcl-payload>
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
@ -502,8 +520,20 @@ if {[::punk::multishell::is_main]} {
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
#Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations
#echo "script: `echo $0 | sed 's/^-//'`"
# csh/tcsh/sh/bash use oldschool backticks and sed - lowest common denominator \
#echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#csh/tcsh diversion \
test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit )
#other non-bash diversion \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0
#review \
test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit
# sh/bash \
shift && set -- "${@:1:$#-1}"
echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'`
#------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
@ -518,10 +548,113 @@ if false==false # else {
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
plat=$(uname -s) #platform/system
if [[ "$plat" = "Linux"* ]]; then
os="linux"
elif [[ "$plat" == "Darwin"* ]]; then
os="macosx"
elif [[ "$plat" == "FreeBSD"* ]]; then
os="freebsd"
elif [[ "$plat" == "DragonFly"* ]]; then
os="dragonflybsd"
elif [[ "$plat" == "NetBSD"* ]]; then
os="netbsd"
elif [[ "$plat" == "OpenBSD"* ]]; then
os="openbsd"
elif [[ "$plat" = "MINGW32"* ]]; then
os="win32"
elif [[ "$plat" = "MINGW64"* ]]; then
os="win32"
elif [[ "$plat" = "CYGWIN_NT"* ]]; then
os="win32"
elif [[ "$plat" == "MSYS_NT"* ]]; then
#review..
echo MSYS
#win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix'
#bash reports $OSTYPE msys
os="win32"
#review - need ps/sed/awk to determine shell?
interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'`
#use 'command -v' (shell builtin preferred over external which)
shellpath=`command -v $interp`
shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname
#"c:/windows/system32/" is quite likely in the path ahead of msys,git etc.
#This breaks calls to various unix utils such as sed etc (wsl related?)
export PATH="$shellfolder${PATH:+:${PATH}}"
elif [[ "$OSTYPE" == "win32" ]]; then
os="win32"
else
#os="$OSTYPE"
os="other"
fi
echo ostype: $OSTYPE
## This is the sort of sed that will not work across implementations
## shellconfiglines=$( sed -n "/: <<nextshell_start>>/{:a;n;/: <<nextshell_end>>/q;p;ba}" "$0" | grep $os)
#awk tested on linux & freebsd
shellconfiglines=$( awk '/^:.*<<nextshell_start>>.*$/,/^:.*<<nextshell_end>>.*$/' "$0" | grep $os)
#echo $shellconfiglines;
readarray -t arr_oslines <<<"$shellconfiglines"
nextshellpath=""
nextshelltype=""
for ln in "${arr_oslines[@]}"; do
if [[ "$ln" == *"nextshellpath"* ]]; then
splitln="${ln#*=}" #remove everything through the first '='
pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match
#remove trailing underscores (% means must match at end)
nextshellpath="${pathraw/%_*/}"
echo "nextshellpath: $nextshellpath"
elif [[ "$ln" == *"nextshelltype"* ]]; then
splitln="${ln#*=}"
typeraw="${splitln%%\"*}"
nextshelltype="${typeraw/%_*/}"
fi
done
#if [[ $shellconfigline == *"nextshelltype"* ]]; then
# #echo "found config for os $os"
# split1="${shellconfigline#*=}" #remove everything through the first '='
# #echo "split1: $split1"
# pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match
# pathraw="${pathraw//\"/}" #remove quote
# nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end)
# #echo "nextshellpath: $nextshellpath"
# split2="${split1#*=}"
# #echo "split2: $split2"
# split2="${split2//\"/}"
# nextshelltype="${split2/%_*/}"
# echo "nextshelltype: $nextshelltype"
#else
# echo "unable to find config for os $os"
# echo "shellconfigline: $shellconfigline"
# nextshellpath=""
# nextshelltype=""
#fi
exitcode=0
#-- sh/bash launches nextscript here instead of shebang line at top
if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then
#echo bash launching subshell of type $nextshelltype $nextshellpath on "$0"
#/usr/bin/env tclsh "$0" "$@"
${nextshellpath} "$0" "$@"
exitcode=$?
#echo "sh/bash reporting exitcode: ${exitcode}"
exit $exitcode
#-- override exitcode example
#exit 66
else
#already in bash - don't launch another process or we would loop
#echo "bash payload"
:
fi
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
#printf "start of bash or sh code"
#<shell-payload>
echo "No bash code for this script. Try another program such as perl or tcl" >&2
#</shell-payload>
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
@ -531,8 +664,8 @@ exitcode=0
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#/usr/bin/env tclsh "$0" "$@"
#exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
@ -558,8 +691,18 @@ exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
use Cwd qw(abs_path);
my $scriptname = abs_path($0);
#print "perl $scriptname\n";
my $os = "$^O";
if ($os eq "MSWin32") {
$os = "win32";
} elsif ($os eq "darwin") {
$os = "macosx";
}
print "os $os\n";
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
@ -571,13 +714,15 @@ my $exit_code = 0;
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-payload>
print STDERR "No perl code for this script. Try another program such as tcl or bash";
#</perl-payload>
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
@ -585,7 +730,7 @@ foreach my $a(@ARGV) {
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
@ -648,12 +793,14 @@ function GetDynamicParamDictionary {
return $DynParamDictionary
}
}
# Example usage:
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# [Parameter(Mandatory)][string] $myargument,
# [Parameter(ValueFromRemainingArguments)] $opts
# )
#}
#function psmain {
@ -664,10 +811,15 @@ function GetDynamicParamDictionary {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# 'myargument' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# 'opts' {
# write-warning "Unused parameters: $($PSBoundParameters.$_)"
# }
# Default {
# write-warning "Unhandled parameter -> [$($_)]"
# }
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
@ -675,17 +827,46 @@ function GetDynamicParamDictionary {
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# Write-Host "myargument value is: $myargument"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
$startTag = ": <<asadmin_start>>"
$endTag = ": <<asadmin_end>>"
$fileContent = Get-Content $scriptname -Raw
$pattern = "(?s)$startTag(.*?)$endTag"
$matches = [regex]::Matches($fileContent,$pattern)
$admininfo = $matches[0].Groups[1].Value
$asadmin = 0
if ($matches.count) {
$asadmin = $admininfo.Contains("asadmin=1")
if ($asadmin) {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
# If not elevated, relaunch with elevated privileges
# -Wait e.g for starting a service or other operations which remainder of script may depend on
$arguments = @("-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass")
$arguments += @("-File", $($MyInvocation.MyCommand.Path))
$arguments += $args
if ($PSVersionTable.PSEdition -eq 'Core') {
Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -Wait -Verb RunAs
} else {
Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Wait -Verb RunAs
}
Exit # Exit the current non-elevated process
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#<powershell-payload>
Write-Error "No powershell code for this script. Try another program such as perl, tcl or bash"
#</powershell-payload>
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
@ -693,7 +874,7 @@ function GetDynamicParamDictionary {
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
tclsh $scriptname $args
#tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---

346
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell2.cmd

@ -1,41 +1,65 @@
: "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. (close sqote for unix shells) ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl \
: "[Hide @ECHO; Hide ); Hide (;Hide echo; Hide @REM]#not necessary but can help avoid errs in testing"
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH'
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section.
: shebang line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@GOTO :skip_perl_pod_start ^;
=begin excludeperl
: skip_perl_pod_start
: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \
: {
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT. shebang #! line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: pmix scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________"
@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set
@REM If more than 32 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM Supporting more explicit oses than those listed may also require script padding adjustment
: <nextshell>
@SET "nextshell=13"
@SET "nextshellpath[win32___________]=tclsh___________________________"
@SET "nextshelltype[win32___________]=tcl_____________"
@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________"
@SET "nextshelltype[dragonflybsd____]=tcl_____________"
@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[freebsd_________]=tcl_____________"
@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[netbsd__________]=tcl_____________"
@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[linux___________]=tcl_____________"
@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[macosx__________]=tcl_____________"
@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________"
@SET "nextshelltype[other___________]=tcl_____________"
: </nextshell>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
@SET "asadmin=0"
: </asadmin>
@REM nextshell set to index for validshells .eg 10 for pwsh
@REM @ECHO nextshell is %nextshell%
@SET "selected=!shells[%nextshell%]!"
@REM @ECHO selected %selected%
@CALL SET "keyRemoved=%%validshells:'!selected!'=%%"
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM @SET "selected_shelltype=%nextshelltype[win32___________]%"
@SET "selected_shelltype=%nextshelltype[win32___________]%"
@REM @ECHO selected_shelltype %selected_shelltype%
@CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed
@REM @ECHO selected_shelltype_trimmed %selected_shelltype_trimmed%
@SET "selected_shellpath=%nextshellpath[win32___________]%"
@CALL :stringTrimTrailingUnderscores %selected_shellpath% selected_shellpath_trimmed
@CALL SET "keyRemoved=%%validshelltypes:!selected_shelltype!=%%"
@REM @ECHO keyremoved %keyRemoved%
@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@ -49,16 +73,16 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- pmix scriptwrap.checkfile <filepath>
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using pmix scriptwrap.checkfile is still recommended.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended.
@REM -- Alternatively, as you should do anyway - test the final script on windows
@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations.
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and pmix scriptwrap.checkfile doesn't check all such boundaries.
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@ -89,22 +113,36 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%*
@IF "%1"=="PUNK-ELEVATED" (
@SET "qstrippedargs=args%arglist%"
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
GOTO :gotPrivileges
)
@IF !asadmin!==1 (
net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@REM padding
@GOTO skip_privileges
:getPrivileges
@IF '%1'=='PUNK-ELEVATED' (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0.cmd", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -113,7 +151,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "%1"=="PUNK-ELEVATED" (
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
@ -124,7 +162,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >nul || goto different
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -134,10 +172,10 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >NUL
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
@IF "%selected_shelltype_trimmed%"=="powershell" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
@ -145,7 +183,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
@ -153,24 +192,31 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!shells[%nextshell%]!"=="bash" (
IF "%selected_shelltype_trimmed%"=="wslbash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
%selected_shellpath_trimmed% "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
REM perl or tcl or sh or bash
IF NOT "x%keyRemoved%"=="x%validshelltypes%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
!shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode
%selected_shellpath_trimmed% "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call;
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@ -179,7 +225,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@SET "name=%~nx1"
@SET "drive=%~d1"
@SET "rtrn=%~2"
@SET "result=/mnt/%drive:~0,1%%_path:\=/%%name%"
@REM Although drive letters on windows are normally upper case wslbash seems to expect lower case drive letters
@CALL :stringToLower %drive ldrive
@SET "result=/mnt/%ldrive:~0,1%%_path:\=/%%name%"
@ENDLOCAL & (
@if "%~2" neq "" (
SET "%rtrn%=%result%"
@ -227,6 +275,7 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -245,6 +294,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -289,7 +340,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringToUpper
@SETLOCAL
@SET "rtrn=%~2"
@ -307,7 +359,47 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
)
)
@EXIT /B
:stringToLower
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "retstring=%~1"
@FOR %%A in (a b c d e f g h i j k l m n o p q r s t u v w x y z) DO @(
@SET "retstring=!retstring:%%A=%%A!"
)
@SET "result=!retstring!"
@ENDLOCAL & (
@IF "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO stringToLower %string% result: %result%
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:stringTrimTrailingUnderscores
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "trimstring=%~1"
@REM trim up to 31 underscores from the end of a string using string substitution
@SET trimstring=%trimstring%###
@SET trimstring=%trimstring:________________###=###%
@SET trimstring=%trimstring:________###=###%
@SET trimstring=%trimstring:____###=###%
@SET trimstring=%trimstring:__###=###%
@SET trimstring=%trimstring:_###=###%
@SET trimstring=%trimstring:###=%
@SET "result=!trimstring!"
@ENDLOCAL & (
@IF "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO stringTrimTrailingUnderscores %string% result: %result%
)
)
@EXIT /B
:isNumeric
@SETLOCAL
@SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i"
@ -328,6 +420,8 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
:endlib
: \
@REM padding
@REM padding
@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell
@GOTO :exit_multishell
# }
@ -348,9 +442,9 @@ set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershe
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${argv0}/__]]
set last_script_root [file dirname [file normalize ${::argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
if {[info exists argv0] &&
if {[info exists ::argv0] &&
$last_script eq $last_script_root
} {
set ::punk::multishell::is_main($last_script) 1 ;#run as executable/script - likely desirable to launch application and return an exitcode
@ -365,7 +459,7 @@ namespace eval ::punk::multishell {
if {![info exists ::punk::multishell::is_main($script_name)]} {
#e.g a .dll or something else unanticipated
puts stderr "Warning punk::multishell didn't recognize info script result: $script_name - will treat as if sourced and return instead of exiting"
puts stderr "Info: script_root: [file dirname [file normalize ${argv0}/__]]"
puts stderr "Info: script_root: [file dirname [file normalize ${::argv0}/__]]"
return 0
}
return [set ::punk::multishell::is_main($script_name)]
@ -380,10 +474,16 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-payload>
#</tcl-payload>
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subprocess>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
@ -414,33 +514,33 @@ if false==false # else {
# -- leave as is if all that is required is launching the Tcl payload"
# --
# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default
# -- adjust @call line above ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate
# -- adjust the %nextshell% value above
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-launch-tcl>
exitcode=0 ;#default assumption
#<shell-launch-subprocess>
#-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#echo "tcl exitcode: ${exitcode}"
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-tcl>
#</shell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#<shell-post-launch-subprocess>
#</shell-post-launch-subprocess>
#printf "sh/bash done \n"
@ -448,7 +548,57 @@ exitcode=$?
#------------------------------------------------------
fi
exit ${exitcode}
# end hide sh/bash block from Tcl
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# This comment with closing brace should stay in place whether if commented or not }
#------------------------------------------------------
# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above
@ -460,9 +610,76 @@ if 0 {
# -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy
# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above
# -- custom script should generally go below the begin_powershell_payload line
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
function GetScriptName { $myInvocation.ScriptName }
$scriptname = getScriptName
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
@ -470,22 +687,22 @@ $scriptname = getScriptName
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-launch-tcl>
#<powershell-launch-subprocess>
tclsh $scriptname $args
#</powershell-launch-tcl>
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
@ -498,7 +715,7 @@ $1 = @'
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "%1"=="PUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
@ -509,6 +726,7 @@ $1 = @'
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>


240
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell3.cmd

@ -1,34 +1,29 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: "[rename set s;proc Hide x {proc $x args {}};Hide :]" "\$(function : {<#pwsh#>})" ^
set -- "$@" "a=[list shebangless punk MULTISHELL tclsh sh bash cmd pwsh powershell;proc Hide x {proc $x args {}}; Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. (close sqote for unix shells) ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl \
: "[Hide @ECHO; Hide ); Hide (;Hide echo; Hide @REM]#not necessary but can help avoid errs in testing"
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH'
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section.
: shebang line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@GOTO :skip_perl_pod_start ^;
=begin excludeperl
: skip_perl_pod_start
: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \
: {
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT. shebang #! line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM e.g from within a running punkshell: pmix scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh' (^14^) 'perl'"
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "shells[14]=perl"
: <nextshell>
@SET "nextshell=13"
: </nextshell>
@ -54,16 +49,16 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- pmix scriptwrap.checkfile <filepath>
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using pmix scriptwrap.checkfile is still recommended.
@REM -- Alternatively, as you should do anyway - test the final script on windows
@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations.
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REM -- It is unknown what versions of cmd interpreters behave this way - and pmix scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@ -94,40 +89,22 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
)
@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%*
@SET "qstrippedargs=args%arglist%"
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@IF "%1"=="PUNK-ELEVATED" (
GOTO :gotPrivileges
)
@IF !asadmin!==1 (
net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@IF '%1'=='PUNK-ELEVATED' (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0.cmd", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
@ -136,7 +113,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@IF "%1"=="PUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
@ -147,7 +124,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
fc "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@ -157,7 +134,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
COPY "%~dp0%~n0.cmd" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
@ -168,8 +145,7 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
@ -180,26 +156,21 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
IF "!shells[%nextshell%]!"=="bash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
!shells[%nextshell%]! "!wslpath!%fname%" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
!shells[%nextshell%]! "%~dp0%fname%" %arglist%
SET task_exitcode=!errorlevel!
!shells[%nextshell%]! "%~dp0%fname%" %arglist% & SET task_exitcode=!errorlevel!
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@ -256,7 +227,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@ -275,8 +245,6 @@ set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@ -412,15 +380,9 @@ namespace eval ::punk::multishell {
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subproces>
#<tcl-payload>
#</tcl-payload>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subproces>
# -- --- --- --- --- --- --- --- --- --- --- ---
@ -452,33 +414,33 @@ if false==false # else {
# -- leave as is if all that is required is launching the Tcl payload"
# --
# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default
# -- adjust the %nextshell% value above
# -- adjust @call line above ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<shell-launch-subprocess>
#<shell-launch-tcl>
exitcode=0 ;#default assumption
#-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#echo "tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-subprocess>
#</shell-launch-tcl>
# -- --- --- --- --- --- --- ---
#<shell-post-launch-subprocess>
#</shell-post-launch-subproces>
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#printf "sh/bash done \n"
@ -486,57 +448,7 @@ exitcode=$?
#------------------------------------------------------
fi
exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# end hide sh/bash block from Tcl
# This comment with closing brace should stay in place whether if commented or not }
#------------------------------------------------------
# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above
@ -548,76 +460,9 @@ if 0 {
# -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy
# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above
# -- custom script should generally go below the begin_powershell_payload line
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
function GetScriptName { $myInvocation.ScriptName }
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
$scriptname = getScriptName
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
@ -625,22 +470,22 @@ function GetDynamicParamDictionary {
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
#<powershell-launch-tcl>
tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
#</powershell-launch-tcl>
# -- --- --- --- --- --- --- ---
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
@ -653,7 +498,7 @@ $1 = @'
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
@IF "%1"=="PUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
@ -664,7 +509,6 @@ $1 = @'
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>


680
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/utility/scriptappwrappers/multishell4.cmd

@ -0,0 +1,680 @@
: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^
set -- "$@" "a=[Hide <#;Hide set;s 1 list]"; set -- : "$@";$1 = @'
: heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \
: .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \
: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" +
: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH'
: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section.
: shebang line is not required on unix or windows and will reduce functionality and/or portability.
: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care.
@GOTO :skip_perl_pod_start ^;
=begin excludeperl
: skip_perl_pod_start
: Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \
: {
@REM ############################################################################################################################
@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, sh and/or powershelll (powershell.exe or pwsh.exe)
@REM It should remain portable between unix-like OSes & windows if the proper structure is maintained.
@REM ############################################################################################################################
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@SETLOCAL EnableExtensions EnableDelayedExpansion
@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh' (^14^) 'perl'"
@SET "shells[10]=pwsh"
@SET "shells[11]=sh"
@set "shells[12]=bash"
@SET "shells[13]=tclsh"
@SET "shells[14]=perl"
: <nextshell>
@SET "nextshell=13"
: </nextshell>
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
: <asadmin>
@SET "asadmin=0"
: </asadmin>
@REM nextshell set to index for validshells .eg 10 for pwsh
@REM @ECHO nextshell is %nextshell%
@SET "selected=!shells[%nextshell%]!"
@REM @ECHO selected %selected%
@CALL SET "keyRemoved=%%validshells:'!selected!'=%%"
@REM @ECHO keyremoved %keyRemoved%
@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@REM -- cmd/batch file section (ignored on unix but should be left in place)
@REM -- This section intended mainly to launch the next shell (and to escalate privileges if necessary)
@REM -- Avoid customising this if you are not familiar with batch scripting. cmd/batch script can be useful, but is probably the least expressive language and most error prone.
@REM -- For example - as this file needs to use unix-style lf line-endings - the label scanner is susceptible to the 512Byte boundary issue: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888
@REM -- This label issue can be triggered/abused in files with crlf line endings too - but it is less likely to happen accidentaly.
@REm -- See also: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133
@REM ############################################################################################################################
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended.
@REM -- Alternatively, as you should do anyway - test the final script on windows
@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations.
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
@REM ############################################################################################################################
@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections
@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ###
@SET "winpath=%~dp0"
@SET "fname=%~nx0"
@REM @ECHO fname %fname%
@REM @ECHO winpath %winpath%
@REM @ECHO commandlineascalled %0
@REM @ECHO commandlineresolved %~f0
@CALL :getNormalizedScriptTail nftail
@REM @ECHO normalizedscripttail %nftail%
@CALL :getFileTail %0 clinetail
@REM @ECHO clinetail %clinetail%
@CALL :stringToUpper %~nx0 capscripttail
@REM @ECHO capscriptname: %capscripttail%
@IF "%nftail%"=="%capscripttail%" (
@ECHO forcing asadmin=1 due to file name on filesystem being uppercase
@SET "asadmin=1"
) else (
@CALL :stringToUpper %clinetail% capcmdlinetail
@REM @ECHO capcmdlinetail !capcmdlinetail!
IF "%clinetail%"=="!capcmdlinetail!" (
@ECHO forcing asadmin=1 due to cmdline scriptname in uppercase
@set "asadmin=1"
)
)
@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs"
@SET arglist=%*
@SET "qstrippedargs=args%arglist%"
@SET "qstrippedargs=%qstrippedargs:"=%"
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
GOTO :gotPrivileges
)
@IF !asadmin!==1 (
net file 1>NUL 2>NUL
@IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges )
)
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@REM
@GOTO skip_privileges
:getPrivileges
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges )
@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%"
@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%"
@ECHO Next >> "%vbsGetPrivileges%"
@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%"
@ECHO Launching script in new windows due to administrator elevation
@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
@EXIT /B
:gotPrivileges
@REM setlocal & pushd .
@PUSHD .
@cd /d %~dp0
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (
@DEL "%vbsGetPrivileges%" 1>nul 2>nul
@SET arglist=%arglist:~14%
)
:skip_privileges
@SET need_ps1=0
@REM we want the ps1 to exist even if the nextshell isn't powershell
@if not exist "%~dp0%~n0.ps1" (
@SET need_ps1=1
) ELSE (
fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different
@REM @ECHO "files same"
@SET need_ps1=0
)
@GOTO :pscontinue
:different
@REM @ECHO "files differ"
@SET need_ps1=1
:pscontinue
@IF !need_ps1!==1 (
COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL
)
@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /?
@IF "!shells[%nextshell%]!"=="pwsh" (
REM pws vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time
REM test availability of preferred option of powershell7+ pwsh
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; write-host "statusmessage: pwsh-found" >NUL
SET pwshtest_exitcode=!errorlevel!
REM ECHO pwshtest_exitcode !pwshtest_exitcode!
REM fallback to powershell if pwsh failed
IF !pwshtest_exitcode!==0 (
pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM CALL powershell -nop -nol -c write-host powershell-found
REM powershell -nop -nol -file "%~dp0%~n0.ps1" %*
powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; %~dp0%~n0.ps1" %arglist%
SET task_exitcode=!errorlevel!
)
) ELSE (
IF "!shells[%nextshell%]!"=="bash" (
CALL :getWslPath %winpath% wslpath
REM ECHO wslfullpath "!wslpath!%fname%"
!shells[%nextshell%]! "!wslpath!%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
REM probably tclsh or sh
IF NOT "x%keyRemoved%"=="x%validshells%" (
REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl
REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx
!shells[%nextshell%]! "%~dp0%fname%" %arglist%
SET task_exitcode=!errorlevel!
) ELSE (
ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells%
SET task_exitcode=66
@REM boundary padding
@REM boundary padding
GOTO :exit_multishell
)
)
)
@REM batch file library functions
@REM boundary padding
@GOTO :endlib
:getWslPath
@SETLOCAL
@SET "_path=%~p1"
@SET "name=%~nx1"
@SET "drive=%~d1"
@SET "rtrn=%~2"
@SET "result=/mnt/%drive:~0,1%%_path:\=/%%name%"
@ENDLOCAL & (
@if "%~2" neq "" (
SET "%rtrn%=%result%"
) ELSE (
ECHO %result%
)
)
@EXIT /B
:getFileTail
@REM return tail of file without any normalization e.g c:/punkshell/bin/Punk.cmd returns Punk.cmd even if file is punk.cmd
@REM we can't use things such as %~nx1 as it can change capitalisation
@REM This function is designed explicitly to preserve capitalisation
@REM accepts full paths with either / or \ as delimiters - or
@SETLOCAL
@SET "rtrn=%~2"
@SET "arg=%~1"
@REM @SET "result=%_arg:*/=%"
@REM @SET "result=%~1"
@SET LF=^
: The above 2 empty lines are important. Don't remove
@CALL :stringContains "!arg!" "\" hasBackSlash
@IF "!hasBackslash!"=="true" (
@for %%A in ("!LF!") do @(
@FOR /F %%B in ("!arg:\=%%~A!") do @set "result=%%B"
)
) ELSE (
@CALL :stringContains "!arg!" "/" hasForwardSlash
@IF "!hasForwardSlash!"=="true" (
@FOR %%A in ("!LF!") do @(
@FOR /F %%B in ("!arg:/=%%~A!") do @set "result=%%B"
)
) ELSE (
@set "result=%arg%"
)
)
@ENDLOCAL & (
@if "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO %result%
)
)
@EXIT /B
@REM boundary padding
@REM boundary padding
:getNormalizedScriptTail
@SETLOCAL
@SET "result=%~nx0"
@SET "rtrn=%~1"
@ENDLOCAL & (
@IF "%~1" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO %result%
)
)
@EXIT /B
:getNormalizedFileTailFromPath
@REM warn via echo, and do not set return variable if path not found
@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized'
@REM boundary padding
@REM boundary padding
@REM boundary padding
@REM boundary padding
@SETLOCAL
@CALL :stringContains %~1 "\" hasBackSlash
@CALL :stringContains %~1 "/" hasForwardSlash
@IF "%hasBackslash%-%hasForwardslash%"=="false-false" (
@SET "P=%cd%%~1"
@CALL :getNormalizedFileTailFromPath "!P!" ftail2
@SET "result=!ftail2!"
) else (
@IF EXIST "%~1" (
@SET "result=%~nx1"
) else (
@ECHO error getNormalizedFileTailFromPath file not found: %~1
@EXIT /B 1
)
)
@SET "rtrn=%~2"
@ENDLOCAL & (
@IF "%~2" neq "" (
SET "%rtrn%=%result%"
) ELSE (
@ECHO getNormalizedFileTailFromPath %1 result: %result%
)
)
@EXIT /B
:stringContains
@REM usage: @CALL:stringContains string needle returnvarname
@SETLOCAL
@SET "rtrn=%~3"
@SET "string=%~1"
@SET "needle=%~2"
@IF "!string:%needle%=!"=="!string!" @(
@SET "result=false"
) ELSE (
@SET "result=true"
)
@ENDLOCAL & (
@IF "%~3" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO stringContains %string% %needle% result: %result%
)
)
@EXIT /B
:stringToUpper
@SETLOCAL
@SET "rtrn=%~2"
@SET "string=%~1"
@SET "capstring=%~1"
@FOR %%A in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO @(
@SET "capstring=!capstring:%%A=%%A!"
)
@SET "result=!capstring!"
@ENDLOCAL & (
@IF "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO stringToUpper %string% result: %result%
)
)
@EXIT /B
:isNumeric
@SETLOCAL
@SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i"
@IF defined notnumeric (
@SET "result=false"
) else (
@SET "result=true"
)
@SET "rtrn=%~2"
@ENDLOCAL & (
@IF "%~2" neq "" (
@SET "%rtrn%=%result%"
) ELSE (
@ECHO %result%
)
)
@EXIT /B
:endlib
: \
@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell
@GOTO :exit_multishell
# }
# -*- tcl -*-
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- tcl script section
# -- This is a punk multishell file
# -- Primary payload target is Tcl, with sh,bash,powershell as helpers
# -- but it may equally be used with any of these being the primary script.
# -- It is tuned to run when called as a batch file, a tcl script a sh/bash script or a pwsh/powershell script
# -- i.e it is a polyglot file.
# -- The specific layout including some lines that appear just as comments is quite sensitive to change.
# -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline.
# -- e.g ./filename.polypunk.cmd in sh or bash
# -- e.g tclsh filename.cmd
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore
Hide :exit_multishell;Hide {<#};Hide '@
namespace eval ::punk::multishell {
set last_script_root [file dirname [file normalize ${argv0}/__]]
set last_script [file dirname [file normalize [info script]/__]]
if {[info exists argv0] &&
$last_script eq $last_script_root
} {
set ::punk::multishell::is_main($last_script) 1 ;#run as executable/script - likely desirable to launch application and return an exitcode
} else {
set ::punk::multishell::is_main($last_script) 0 ;#sourced - likely to be being used as a library - no launch, no exit. Can use return.
}
if {"::punk::multishell::is_main" ni [info commands ::punk::multishell::is_main]} {
proc ::punk::multishell::is_main {{script_name {}}} {
if {$script_name eq ""} {
set script_name [file dirname [file normalize [info script]/--]]
}
if {![info exists ::punk::multishell::is_main($script_name)]} {
#e.g a .dll or something else unanticipated
puts stderr "Warning punk::multishell didn't recognize info script result: $script_name - will treat as if sourced and return instead of exiting"
puts stderr "Info: script_root: [file dirname [file normalize ${argv0}/__]]"
return 0
}
return [set ::punk::multishell::is_main($script_name)]
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload
#puts "script : [info script]"
#puts "argcount : $::argc"
#puts "argvalues: $::argv"
#puts "argv0 : $::argv0"
# -- --- --- --- --- --- --- --- --- --- --- ---
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subproces>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subproces>
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- Best practice is to always return or exit above, or just by leaving the below defaults in place.
# -- If the multishell script is modified to have Tcl below the Tcl Payload section,
# -- then Tcl bracket balancing needs to be carefully managed in the shell and powershell sections below.
# -- Only the # in front of the two relevant if statements below needs to be removed to enable Tcl below
# -- but the sh/bash 'then' and 'fi' would also need to be uncommented.
# -- This facility left in place for experiments on whether configuration payloads etc can be appended
# -- to tail of file - possibly binary with ctrl-z char - but utility is dependent on which other interpreters/shells
# -- can be made to ignore/cope with such data.
if {[::punk::multishell::is_main]} {
exit 0
} else {
return
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload
# end hide from unix shells \
HEREDOC1B_HIDE_FROM_BASH_AND_SH
# sh/bash \
shift && set -- "${@:1:$#-1}"
#------------------------------------------------------
# -- This if block only needed if Tcl didn't exit or return above.
if false==false # else {
then
: #
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- sh/bash script section
# -- leave as is if all that is required is launching the Tcl payload"
# --
# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default
# -- adjust the %nextshell% value above
# -- if sh/bash scripting needs to run on windows too.
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload
exitcode=0
#printf "start of bash or sh code"
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-launch-subprocess>
#-- sh/bash launches Tcl here instead of shebang line at top
#-- use exec to use exitcode (if any) directly from the tcl script
#exec /usr/bin/env tclsh "$0" "$@"
#-- alternative - can run sh/bash script after the tcl call.
/usr/bin/env tclsh "$0" "$@"
exitcode=$?
#echo "sh/bash reporting tcl exitcode: ${exitcode}"
#-- override exitcode example
#exit 66
#</shell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-post-launch-subprocess>
#</shell-post-launch-subproces>
#printf "sh/bash done \n"
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload
#------------------------------------------------------
fi
exit ${exitcode}
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- Perl script section
# -- leave the script below as is, if all that is required is launching the Tcl payload"
# --
# -- Note that perl script isn't called by default when simply running this script by name
# -- adjust the nextshell value at the top of the script to point to perl
# --
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
=cut
#!/user/bin/perl
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload
my $exit_code = 0;
#use ExtUtils::Installed;
#my $installed = ExtUtils::Installed->new();
#my @modules = $installed->modules();
#print "Modules:\n";
#foreach my $m (@modules) {
# print "$m\n";
#}
# -- --- ---
my $scriptname = $0;
print "perl $scriptname\n";
my $i =1;
foreach my $a(@ARGV) {
print "Arg # $i: $a\n";
}
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload
exit $exit_code;
__END__
# end hide sh/bash/perl block from Tcl
# This comment with closing brace should stay in place whether if commented or not }
#------------------------------------------------------
# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above
if 0 {
: end heredoc1 - end hide from powershell \
'@
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
# -- powershell/pwsh section
# -- Do not edit if current file is the .ps1
# -- Edit the corresponding .cmd and it will autocopy
# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above
# -- custom script should generally go below the begin_powershell_payload line
# ## ### ### ### ### ### ### ### ### ### ### ### ### ###
function GetScriptName { $myInvocation.ScriptName }
$scriptname = GetScriptName
function GetDynamicParamDictionary {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[string] $CommandName
)
begin {
# Get a list of params that should be ignored (they're common to all advanced functions)
$CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) |
Get-Member -MemberType Properties |
Select-Object -ExpandProperty Name
}
process {
# Create the dictionary that this scriptblock will return:
$DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Convert to object array and get rid of Common params:
(Get-Command $CommandName | select -exp Parameters).GetEnumerator() |
Where-Object { $CommonParameterNames -notcontains $_.Key } |
ForEach-Object {
$DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
$_.Key,
$_.Value.ParameterType,
$_.Value.Attributes
)
$DynParamDictionary.Add($_.Key, $DynamicParameter)
}
# Return the dynamic parameters
return $DynParamDictionary
}
}
# GetDynamicParamDictionary
# - This can make it easier to share a single set of param definitions between functions
# - sample usage
#function ParameterDefinitions {
# param(
# [Parameter(Mandatory)][string] $myargument
# )
#}
#function psmain {
# [CmdletBinding()]
# param()
# dynamicparam { GetDynamicParamDictionary ParameterDefinitions }
# process {
# #called once with $PSBoundParameters dictionary
# #can be used to validate arguments, or set a simpler variable name for access
# switch ($PSBoundParameters.keys) {
# 'myargumentname' {
# Set-Variable -Name $_ -Value $PSBoundParameters."$_"
# }
# #...
# }
# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) {
# #...
# }
# }
# end {
# #Main function logic
# Write-Host "myargumentname value is: $myargumentname"
# #myotherfunction @PSBoundParameters
# }
#}
#psmain @args
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload
#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host
#"Script Name : {0}" -f $scriptname | write-host
#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
Exit $LASTEXITCODE
# heredoc2 for powershell to ignore block below
$1 = @'
'
: comment end hide powershell-block from Tcl \
# This comment with closing brace should stay in place whether 'if' commented or not }
: multishell doubled-up cmd exit label - return exitcode
:exit_multishell
:exit_multishell
: \
@REM @ECHO exitcode: !task_exitcode!
: \
@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit)
: \
@EXIT /B !task_exitcode!
# cmd has exited
: comment end heredoc2 \
'@
<#
# id:tailblock0
# -- powershell multiline comment
#>
<#
no script engine should try to run me
# id:tailblock1
# <ctrl-z>

# </ctrl-z>
# -- unreachable by tcl directly if ctrl-z character is in the <ctrl-z> section above. (but file can be read and split on \x1A)
# -- Potential for zip and/or base64 contents, but we can't stop pwsh parser from slurping in the data
# -- so for example a plain text tar archive could cause problems depending on the content.
# -- final line in file must be the powershell multiline comment terminator or other data it can handle.
# -- e.g plain # comment lines will work too
# -- (for example a powershell digital signature is a # commented block of data at the end of the file)
#>

2
src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.2.tm

@ -2418,6 +2418,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
#if {[lindex $command 0] eq "runx"} {}
#temporary hack.
#todo - use happy path return options for non-primary result (like www package) ?
if {
[string equal -length [string length "d/ "] "d/ " $commandstr] || \
[string equal "d/\n" $commandstr] || \

44
src/vfs/_vfscommon.vfs/modules/punk/zip-0.1.1.tm

@ -283,9 +283,47 @@ tcl::namespace::eval punk::zip {
#if there is an external preamble - extract that. (if there is also an internal preamble - ignore and consider part of the archive-data)
#Otherwise extract an internal preamble.
#if neither -
#if neither -?
#review - reconsider auto-determination of internal vs external preamble
proc extract_preamble {infile outfile_preamble {outfile_zip ""}} {
punk::args::define {
@id -id ::punk::zip::extract_preamble
@cmd -name punk::zip::extract_preamble -help\
"Split a zipfs based executable or library into its constituent
binary and zip parts.
Note that the binary preamble might be either 'within' the zip offsets,
or simply catenated prior to an unadjusted zip.
Some build processes may have 'adjusted' the zip offsets to make the zip cover the entire file
('file based' offset) whilst the more modern approach is to simply concatenate the binary and the zip
('archive based' offset). An archive-based offset is simpler and more reliably points to the proper
split location. It also allows 'zipfs info //zipfs:/app' to return the correct offset information.
Either way, extract_preamble can usually separate them, but in the unusual case that there is both an
external preamble and a preamble within the zip, only the external preamble will be split, with the
internal one remaining in the zip.
The inverse of this process would be to extract the .zip file created by this split to a folder,
e.g extracted_zip_folder (adjusting contents as required) and then to run:
zipfs mkimg newbinaryname.exe extracted_zip_folder <prefix> \"\" <extracted_preamble_or_alternative exe>
"
@values -min 2 -max 3
infile -type file -optional 0 -help\
"Name of existing tcl executable or shared lib with attached zipfs filesystem"
outfile_preamble -optional 0 -type file -help\
"Name of output file for binary preamble to be extracted to.
If this file already exists, an error will be raised"
outfile_zip -default "" -type file -help\
"Name of output file for zip data to be extracted to.
If this file already exists, an error will be raised"
}
proc extract_preamble {args} {
set argd [punk::args::parse $args withid ::punk::zip::extract_preamble]
lassign [dict values $argd] leaders opts values received
set infile [dict get $values infile]
set outfile_preamble [dict get $values outfile_preamble]
set outfile_zip [dict get $values outfile_zip]
set inzip [open $infile r]
fconfigure $inzip -encoding iso8859-1 -translation binary
if {[file exists $outfile_preamble]} {
@ -346,7 +384,7 @@ tcl::namespace::eval punk::zip {
#we could scan from top (ugly) - and with binary prefixes we could get false positives in the data that look like PK\3\4 headers
#we could either work out the format for all possible executables that could be appended (across all platforms) and understand where they end?
#or we just look for the topmost PK\3\4 header pointed to by a CDR record - and assume the CDR is complete
#step one - read all the CD records and find the highest pointed to local file record (which isn't necessarily the first - but should get us above most if not all of the zip data)
#we can't assume they're ordered in any particular way - so we in theory have to look at them all.
set baseoffset "unknown"

2
src/vfs/_vfscommon.vfs/modules/shellrun-0.1.1.tm

@ -427,7 +427,7 @@ namespace eval shellrun {
cmdarg -type any -multiple 1 -optional 1
}]
proc runerr {args} {
set argd [punk::args::parse $args withid ::shellrun::runout]
set argd [punk::args::parse $args withid ::shellrun::runerr]
lassign [dict values $argd] leaders opts values received
if {[dict exists $received "-nonewline"]} {

2048
src/vfs/_vfscommon.vfs/modules/www-2.8.tm

File diff suppressed because it is too large Load Diff

83
src/vfs/_vfscommon.vfs/modules/www/digest-2.1.tm

@ -0,0 +1,83 @@
namespace eval www::digest {
variable noncecount
}
# HTTP/1.1 401 Unauthorized
# WWW-Authenticate: Digest
# realm="testrealm@host.com",
# qop="auth,auth-int",
# nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
# opaque="5ccc069c403ebaf9f0171e9517f40e41"
proc www::digest::md5 {str} {
package require md5
return [string tolower [::md5::md5 -hex $str]]
}
proc www::digest::sha256 {str} {
package require sha256
return [::sha2::sha256 -hex $str]
}
proc www::digest::digest {challenge username password method uri {body ""}} {
variable noncecount
if {[dict exists $challenge algorithm]} {
set algorithm [dict get $challenge algorithm]
} else {
set algorithm MD5
}
switch $algorithm {
MD5 - MD5-sess {set hash md5}
SHA-256 - SHA-256-sess {set hash sha256}
default {
error "unsupported algorithm: $algorithm"
}
}
set interlude [dict get $challenge nonce]
set keys {username realm nonce uri response}
if {[dict exists $challenge qop]} {
set qops [split [dict get $challenge qop] ,]
if {"auth" in $qops} {
set qop auth
} elseif {"auth-int" in $qops} {
set qop auth-int
} else {
error "unsupported qop: [join $qops {, }]"
}
set nonce [dict get $challenge nonce]
# Generate a random cnonce
set cnonce [format %08x [expr {int(rand() * 0x100000000)}]]
set nc [format %08X [incr noncecount($nonce)]]
append interlude : $nc : $cnonce : $qop
lappend keys qop nc cnonce
if {[dict exists $challenge algorithm]} {lappend keys algorithm}
if {[dict exists $challenge opaque]} {lappend keys opaque}
} else {
set qop auth
}
foreach n $keys {
dict set rc $n \
[if {[dict exists $challenge $n]} {dict get $challenge $n}]
}
dict set rc username $username
dict set rc uri $uri
if {[dict exists $rc qop]} {
dict set rc qop $qop
dict set rc cnonce $cnonce
dict set rc nc $nc
}
set A1 [$hash $username:[dict get $challenge realm]:$password]
if {[string match {*-sess} $algorithm]} {append A1 : $nonce : $cnonce}
set A2 [$hash $method:$uri]
if {$qop eq "auth-int"} {append A2 : $body}
dict set rc response [$hash $A1:$interlude:$A2]
set authlist {}
dict for {key val} $rc {
if {$key ni {qop nc}} {
lappend authlist [format {%s="%s"} $key $val]
} else {
lappend authlist $key=$val
}
}
return "Digest [join $authlist ,]"
}

1551
src/vfs/_vfscommon.vfs/modules/www/http2-1.1.tm

File diff suppressed because it is too large Load Diff

13
src/vfs/_vfscommon.vfs/modules/www/license.terms

@ -0,0 +1,13 @@
Copyright (c) 2021, Schelte Bron
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

826
src/vfs/_vfscommon.vfs/modules/www/proxypac-2.1.tm

@ -0,0 +1,826 @@
#!/usr/bin/tclsh
# This library can be used together with www 2.0+ to use a proxy based on a
# Proxy Auto Configure (pac) file:
# package require proxypac
# www configure -proxyfilter {proxypac <pacurl>}
# Example: http://pac.webdefence.global.blackspider.com:8082/proxy.pac
package require www
namespace eval www::proxypac {
variable oldpac {}
namespace export proxypac
proc proxypac {pacurl url host} {
variable oldpac
if {[string equal -length [string length $url] $pacurl $url]} {
# The pac url itself must be reachable directly
return DIRECT
}
try {
if {$pacurl ne $oldpac} {
set data [www get $pacurl]
set oldpac $pacurl
parse $data
}
set proxies [execute FindProxyForURL $url $host]
return [lmap proxy [split $proxies {;}] {
if {[string is space $proxy]} continue
string trim $proxy
}]
} on error {err opts} {
www::log "Failed to auto-configure proxy: $err"
# In case of any error, use a direct connection
return [list DIRECT]
}
}
proc validip {ipchars} {
set valid [lmap n [split $ipchars .] {
expr {[string is digit -strict $n] && $n < 256}
}]
return [expr {[join $valid ""] eq "1111"}]
}
proc resolve {host} {
if {[catch {package require dns}]} return
set tok [dns::resolve $host]
dns::wait $tok
set result [lindex [dns::address $tok] 0]
dns::cleanup $tok
return $result
}
}
if {[catch {package require duktape::oo 0.11}]} {
proc www::proxypac::parse {data} {
set code [convert [string map [list \r\n \n] $data]]
proxypacrun eval $code
}
proc www::proxypac::execute {args} {
proxypacrun eval $args
}
proc www::proxypac::convert {data} {
variable tokenlist
set p 0
set re {/[/=*]?|[*]/|[+][+=]?|[*][*]?=?|-=?|<[<=]?|>>>|>[>=]?|%=?|&&?|[|][|]?|!=?=?|={1,3}|[][(),.{}"';\n?~^]|[ \t]+}
set tokenlist [lmap n [regexp -all -indices -inline $re $data] {
lassign $n x1 x2
set str [string range $data $p [expr {$x1 - 1}]]
set sep [string range $data $x1 $x2]
set p [expr {$x2 + 1}]
list $str $sep
}]
set code [lmap line [block] {
set tabs [string length [lindex [regexp -inline ^\t* $line] 0]]
set indent [string repeat \t [expr {$tabs / 2}]]
append indent [string repeat " " [expr {$tabs % 2}]]
regsub ^\t* $line $indent
}]
return [join $code \n]
}
proc www::proxypac::peek {{trim 1}} {
variable tokenlist
variable count
if {[incr count] > 20} {
fail "endless loop"
}
if {[llength $tokenlist] == 0} return
lassign [lindex $tokenlist 0] str tag
if {![string is space $tag] || !$trim} {
return [lindex $tokenlist 0]
} elseif {$str ne ""} {
if {[lindex $tokenlist 1 0] ne ""} {
return [lindex $tokenlist 0]
}
lset tokenlist 1 0 $str
}
set tokenlist [lrange $tokenlist 1 end]
tailcall peek
}
proc www::proxypac::poke {str tag} {
variable tokenlist
lset tokenlist 0 [list $str $tag]
}
proc www::proxypac::next {{trim 1}} {
variable tokenlist
variable count 0
set tokenlist [lrange $tokenlist 1 end]
tailcall peek $trim
}
proc www::proxypac::end {} {
variable tokenlist
return [expr {[llength $tokenlist] == 0}]
}
proc www::proxypac::code {} {
lassign [peek] str tag
if {$str eq "" && $tag eq "\{"} {
next
lappend rc {*}[block]
lassign [peek] str tag
if {$tag ne "\}"} {
fail "expected \}"
}
next
} else {
lappend rc {*}[statement]
}
return $rc
}
proc www::proxypac::block {} {
while {![end]} {
lassign [peek] str tag
switch $str {
{} {
if {$tag in {// /*}} {
comment
}
}
default {
set block [statement]
lappend rc {*}$block
}
}
lassign [peek] str tag
if {$tag eq "\}"} {
break
}
}
return $rc
}
proc www::proxypac::comment {} {
variable tokenlist
variable count 0
lassign [peek] str tag
if {$tag eq "//"} {
set end \n
} else {
set end "*/"
}
set nl [lsearch -exact -index 1 $tokenlist $end]
if {$nl < 0} {set nl end}
set tokenlist [lreplace $tokenlist 0 $nl]
}
proc www::proxypac::statement {} {
lassign [peek] str tag
switch $str {
function {
if {![string is space $tag]} {
fail "expected white space"
}
set rc [function]
}
if {
set rc [ifelse]
}
return {
set rc [jsreturn]
}
var {
if {![string is space $tag]} {
fail "expected white space"
}
set rc [var]
}
for {
if {$tag ne "("} {
fail "expected ("
}
set rc [forloop]
}
default {
if {![regexp {^[\w$]+$} $str]} {
fail "unsupported JavaScript command: $str"
} elseif {$tag eq "="} {
set rc [assignment $str]
} elseif {$tag eq "("} {
set rc [list [funccall $str]]
} else {
fail "unsupported JavaScript command: $str (tag = $tag)"
}
}
}
lassign [peek] str tag
if {$tag eq ";"} {
lassign [next] str tag
}
return $rc
}
proc www::proxypac::jsreturn {} {
lassign [peek] str tag
if {$str eq "" && $tag in {; \n}} {
return [list return]
} else {
poke "" $tag
return [list "return [expression]"]
}
}
proc www::proxypac::expression {{top 1}} {
lassign [peek] str tag
set rc {}
set unary {}
set strcat 0
while 1 {
if {$str eq "" && $tag in {+ - ! ~}} {
append unary $tag
lassign [next] str tag
continue
}
switch -regexp $str {
{^$} {
set op [lindex $rc end]
if {$op eq "=="} {
lset rc end eq
} elseif {$op eq "!="} {
lset rc end ne
}
if {$tag in {\" '}} {
set quote $tag
set strvar ""
while 1 {
lassign [next 0] str tag
if {$tag eq $quote} {
append strvar $str
break
} else {
append strvar $str $tag
}
}
lappend rc [format {{%s}} $strvar]
lassign [next] str tag
if {$str ne ""} {
fail "invalid expression"
}
set strcat 1
} elseif {$tag in "("} {
next
lappend rc [format (%s) [expression 0]]
lassign [peek] str tag
if {$tag ne ")"} {
fail "expected )"
}
next
}
}
{^[\w$]+$} {
if {$tag eq "("} {
lappend rc [format {[%s]} [funccall $str]]
} elseif {$tag eq "\["} {
lappend rc [arrayelem $str]
} elseif {[string is double $str]} {
lappend rc $str
} elseif {[string tolower $str] in {true false}} {
lappend rc $str
} else {
lappend rc [format {$%s} $str]
}
}
default {
fail "expected expression"
}
}
lassign [peek] str tag
while {$tag eq "."} {
lset rc end [method [lindex $rc end]]
lassign [peek] str tag
}
if {$unary ne ""} {
lset rc end $unary[lindex $rc end]
set unary {}
}
switch $tag {
+ - - - * - ** - / - % -
== - != - > - < - >= - <= - ? - : -
& - | - ^ - << - >> - && - || {
lappend rc $tag
}
=== {
lappend rc ==
}
!== {
lappend rc !=
}
>>> {
lappend rc >>
}
default {
break
}
}
lassign [next] str tag
}
if {!$top} {
return [join $rc " "]
} elseif {[llength $rc] == 1} {
set rc [lindex $rc 0]
if {[string match {{*}} $rc]} {
return [list [string range $rc 1 end-1]]
} else {
return $rc
}
} elseif {!$strcat} {
return [format {[expr {%s}]} [join $rc " "]]
}
set cat {}
set expr {}
set rest [lassign $rc arg]
set strcat [string match {{*}} $arg]
if {$strcat} {
lappend cat $arg
} else {
lappend expr $arg
}
foreach {op arg} $rest {
if {$op ne "+" || !$strcat && ![string match {{*}} $arg]} {
lappend expr $op $arg
} else {
if {[llength $expr]} {
if {[llength $expr] > 1} {
lappend cat [format {[expr {%s}]} [join $expr]]
} else {
lappend cat [lindex $expr 0]
}
}
set expr {}
if {[string match {{*}} $arg]} {
set strcat 1
lappend cat $arg
} else {
lappend expr $arg
}
}
}
if {[llength $expr]} {
if {[llength $expr] > 1} {
lappend cat [format {[expr {%s}]} [join $expr]]
} else {
lappend cat [lindex $expr 0]
}
}
return [format {[string cat %s]} [join $cat]]
}
proc www::proxypac::function {} {
lassign [next] name tag
if {$tag ne "("} {
fail "expected open parenthesis"
}
set arglist {}
lassign [next] str tag
if {$str ne ""} {
while 1 {
lappend arglist $str
if {$tag eq ")"} break
if {$tag ne ","} {
fail "expected , or )"
}
lassign [next] str tag
}
} elseif {$tag ne ")"} {
fail "expected )"
}
lappend rc "proc $name [list $arglist] \{"
lassign [next] str tag
lappend rc {*}[indent [code]]
lappend rc "\}"
return $rc
}
proc www::proxypac::funccall {name} {
set cmd $name
lassign [next] str tag
if {$str ne "" || $tag ne ")"} {
while 1 {
append cmd " " [expression]
lassign [peek] str tag
if {$tag eq ")"} break
if {$tag ne ","} {
fail "expected , or )"
}
next
}
}
next
return $cmd
}
proc www::proxypac::ifelse {} {
lassign [peek] str tag
if {$tag ne "("} {
fail "expected ("
}
next
lappend rc [format "if {%s} \{" [expression 0]]
lassign [next] str tag
lappend rc {*}[indent [code]]
lassign [peek] str tag
if {$str eq "else"} {
lappend rc {\} else \{}
lassign [next] str tag
lappend rc {*}[indent [code]]
}
lappend rc "\}"
return $rc
}
proc www::proxypac::forloop {} {
lassign [peek] str tag
if {$tag ne "("} {
fail "expected ("
}
lassign [next] name tag
if {$name eq "var" && [string is space $tag]} {
lassign [next] name tag
}
if {![regexp {^[\w$]+$} $name]} {
fail "expected identifier"
}
if {$tag eq "="} {
} elseif {[string is space $tag]} {
lassign [next] str tag
if {$str ni {in of} || ![string is space $tag]} {
fail "expected 'in' or 'of'"
}
if {$str eq "in"} {
set op keys
} else {
set op values
}
lassign [next] str tag
lappend rc [format "foreach %s \[dict %s $%s\] \{" $name $op $str]
if {$tag ne ")"} {
fail "expected )"
}
next
lappend rc {*}[indent [code]]
lappend rc "\}"
}
return $rc
}
proc www::proxypac::method {obj} {
lassign [next] method tag
set cmd [format {%s %s} $method $obj]
if {$tag eq "("} {
lassign [next] str tag
if {$str ne "" || $tag ne ")"} {
while 1 {
append cmd " " [expression]
lassign [peek] str tag
if {$tag eq ")"} break
if {$tag ne ","} {
fail "expected , or )"
}
next
}
}
next
}
return [format {[%s]} $cmd]
}
proc www::proxypac::assignment {name} {
lassign [next] str tag
switch $str {
new {
if {![string is space $tag]} {
fail "expected white space"
}
lassign [next] str tag
switch $str {
Array {
if {$tag ne "("} {
fail "expected ("
}
set cmd "dict create"
lassign [next] str tag
set index 0
if {$str ne "" || $tag ne ")"} {
while 1 {
append cmd " " $index " " [expression]
incr index
lassign [peek] str tag
next
if {$tag eq ","} continue
if {$tag eq ")"} break
fail "expected , or )"
}
} else {
next
}
return [list [format {set %s [%s]} $name $cmd]]
}
default {
fail "$str objects are not supported"
}
}
}
{} {
if {$tag eq "\["} {
set cmd list
lassign [next] str tag
if {$str ne "" || $tag ne "]"} {
while 1 {
append cmd " " [expression]
lassign [peek] str tag
next
if {$tag eq ","} continue
if {$tag eq "\]"} break
fail "expected , or \]"
}
}
return [list [format {set %s [%s]} $name $cmd]]
}
}
}
return [list [format {set %s %s} $name [expression]]]
}
proc www::proxypac::var {} {
lassign [next] str tag
if {![regexp {^[\w$]+$} $str]} {
fail "expected identifier"
}
if {$tag in {; \n}} return
return [assignment $str]
}
proc www::proxypac::arrayelem {name} {
next
set sub [expression]
lassign [peek] str tag
if {$tag ne "\]"} {
fail "expected \]"
}
next
return [format {[dict get $%s %s]} $name $sub]
}
proc www::proxypac::indent {list} {
return [lmap line $list {format \t%s $line}]
}
proc www::proxypac::fail {str} {
error $str
}
namespace eval www::proxypac {
interp create [namespace current]::proxypacrun
proxypacrun alias resolve [namespace which resolve]
proxypacrun alias validip [namespace which validip]
proxypacrun eval {
proc substring {str start {end 0}} {
if {[llength [info level 0]] < 4} {
set end [string length $str]
}
if {$start < $end} {
return [string range $str $start [expr {$end - 1}]]
} else {
return [string range $str $end [expr {$start - 1}]]
}
}
proc toLowerCase {str} {
return [string tolower $str]
}
rename split tclsplit
proc split {str {separator ""} {limit 2147483647}} {
if {[llength [info level 0]] == 1} {
set list [list $str]
} elseif {$separator eq ""} {
set list [tclsplit $str ""]
} else {
set list {}
set p 0
while {[set x [string first $separator $str $p]] >= 0} {
lappend list [string range $str $p [expr {$x - 1}]]
set p [expr {$x + [string length $separator]}]
}
lappend list [string range $str $p end]
}
set rc {}
set num 0
foreach n $list {
if {$num >= $limit} break
dict set rc $num $n
incr num
}
return $rc
}
}
proc jsfunction {name type args body} {
proxypacrun alias $name \
apply [list $args $body [namespace current]]
# proxypacrun eval [list proc $name $args $body]
}
}
} else {
namespace eval www::proxypac {
duktape::oo::Duktape create js
proc parse {data} {
js eval $data
}
proc execute {args} {
js call {*}$args
}
proc jsfunction {name type args body} {
js tcl-function $name $type $args $body
}
}
}
namespace eval www::proxypac {
variable ipaddress ""
jsfunction isPlainHostName boolean {host} {
return [expr {[string first . $host] < 0}]
}
jsfunction dnsDomainIs boolean {host domain} {
set x [string first . $host]
return [expr {$x >= 0 && [string range $host $x end] eq $domain}]
}
jsfunction localHostOrDomainIs boolean {host hostdom} {
return \
[expr {$host eq $hostdom || $host eq [lindex [split $host .] 0]}]
}
jsfunction isValidIpAddress boolean {ipchars} {
return [validip $ipchars]
}
jsfunction isResolvable boolean {host} {
return [expr {[resolve $host] ne ""}]
}
jsfunction isInNet boolean {host pattern mask} {
if {![validip $host]} {
set host [resolve $host]
if {$host eq ""} {return 0}
}
foreach ip1 [split $host .] ip2 [split $pattern .] m [split $mask .] {
if {($ip1 & $m) != ($ip2 & $m)} {return 0}
}
return 1
}
jsfunction dnsResolve string {host} {
return [resolve $host]
}
jsfunction convert_addr integer {ipaddr} {
binary scan [binary format c4 [split $ipaddr .]] Iu addr
return $addr
}
jsfunction myIpAddress string {} {
variable ipaddress
if {$ipaddress eq ""} {
try {
set fd ""
set fd [socket -server dummy -myaddr [info hostname] 0]
set ipaddress [lindex [fconfigure $fd -sockname] 0]
} on error {} {
set ipaddress 127.0.0.1
} finally {
if {$fd ne ""} {close $fd}
}
}
return $ipaddress
}
jsfunction dnsDomainLevels integer {host} {
return [regexp {[.]} $host]
}
jsfunction shExpMatch boolean {str shexp} {
return [string match $shexp $str]
}
jsfunction weekdayRange boolean {wd1 {wd2 ""} {gmt ""}} {
set weekdays {SUN MON TUE WED THU FRI SAT}
if {$wd2 eq "GMT"} {
set gmt 1
set match [list $wd1]
} else {
set gmt [expr {$gmt eq "GMT"}]
set d1 [lsearch -exact $weekdays $wd1]
set d2 [lsearch -exact $weekdays $wd2]
if {$d1 < $d2} {
set match [lrange $weekdays $d1 $d2]
} else {
set match [list $wd1 $wd2]
}
}
set wd0 [clock format [clock seconds] -gmt $gmt -format %a]
return [expr {[string toupper $wd0] in $match}]
}
jsfunction dateRange boolean {args} {
set gmt [expr {[lindex $args end] eq "GMT"}]
set len [expr {[llength $args] - $gmt}]
if {$len < 1} {return 0}
set now [clock seconds]
if {$len == 1} {
set arg [lindex $args 0]
if {![string is integer -strict $arg]} {
set mon [clock format $now -format %b -gmt $gmt]
return [expr {$arg eq [string toupper $mon]}]
} elseif {$arg < 32} {
set day [clock format $now -format %e -gmt $gmt]
return [expr {$arg == $day}]
} else {
set year [clock format $now -format %Y -gmt $gmt]
return [expr {$arg == $year}]
}
}
lassign [clock format $now -format {%Y %b} -gmt $gmt] year month
set d1 [list $year JAN 1 0 0 0]
set d2 [list $year DEC 31 23 59 59]
set middle [expr {$len / 2}]
for {set i 0} {$i < $middle} {incr i} {
set arg [lindex $args $i]
if {![string is integer -strict $arg]} {
lset d1 1 $arg
} elseif {$arg < 32} {
lset d1 2 $arg
if {$len <= 2} {
lset d1 1 $month
lset d2 1 $month
}
} else {
lset d1 0 $arg
}
}
for {set i $middle} {$i < $len} {incr i} {
set arg [lindex $args $i]
if {![string is integer -strict $arg]} {
lset d2 1 $arg
} elseif {$arg < 32} {
lset d2 2 $arg
} else {
lset d2 0 $arg
}
}
set time1 [clock scan [join $d1 :] -format %Y:%b:%d:%T -gmt $gmt]
set time2 [clock scan [join $d2 :] -format %Y:%b:%d:%T -gmt $gmt]
if {$time1 < $time2} {
return [expr {$now >= $time1 && $now <= $time2}]
} else {
return [expr {$now >= $time2 && $now <= $time1}]
}
}
jsfunction timeRange boolean {args} {
set gmt [expr {[lindex $args end] eq "GMT"}]
set len [expr {[llength $args] - $gmt}]
if {$len < 1} {
return 0
} elseif {$len > 6 || $len == 3 || $len == 5} {
return -code error "timeRange: bad number of arguments"
}
set t1 {0 0 0}
set t2 {23 59 59}
set n [expr {($len + 1) / 2}]
for {set i1 0; set i2 [expr {$len / 2}]} {$i1 < $n} {incr i1; incr i2} {
lset t1 $i1 [lindex $args $i1]
if {$i2 < $len} {
lset t2 $i1 [lindex $args $i2]
}
}
set time1 [clock scan [join $t1 :] -format %T -gmt $gmt]
set time2 [clock scan [join $t2 :] -format %T -gmt $gmt]
set now [clock seconds]
if {$time1 < $time2} {
return [expr {$now >= $time1 && $now <= $time2}]
} else {
return [expr {$now >= $time2 && $now <= $time1}]
}
}
jsfunction alert undefined {} {}
}
namespace import www::proxypac::*

156
src/vfs/_vfscommon.vfs/modules/www/socks-1.0.tm

@ -0,0 +1,156 @@
# SOCKS V4a: http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol
# SOCKS V5: RFC 1928
namespace eval www::socks {
variable username guest password guest
namespace ensemble create -map {socks4 {init socks4} socks5 {init socks5}}
}
proc www::socks::command {sock data {count 2} {timeout 2000}} {
if {$data ne ""} {
puts -nonewline $sock $data
flush $sock
}
set coro [info coroutine]
if {[llength $coro]} {
set id [after $timeout [list $coro timeout]]
fileevent $sock readable [list $coro data]
} else {
fconfigure $sock -blocking 1
set id {}
}
set resp {}
set len 0
while {![eof $sock]} {
append resp [read $sock [expr {$count - $len}]]
set len [string length $resp]
if {$len >= $count} {
after cancel $id
return $resp
}
if {[llength $coro] == 0} continue
set event [yield]
if {$event eq "data"} continue
if {$event eq "timeout"} break
}
throw {SOCKS PROTOCOL ERROR} "did not get expected response from proxy"
}
proc www::socks::init {version sock host port} {
# Make sure this is running in a coroutine
if {[llength [info coroutine]] == 0} {
return [coroutine $sock init $version $sock $host $port]
}
dict set cfg -translation [fconfigure $sock -translation]
dict set cfg -blocking [fconfigure $sock -blocking]
dict set event readable [fileevent $sock readable]
dict set event writable [fileevent $sock writable]
fileevent $sock writable {}
fconfigure $sock -translation binary -blocking 0
if {[catch {$version $sock $host $port} result opts]} {
variable lasterror $result
}
fconfigure $sock {*}$cfg
dict for {ev cmd} $event {
fileevent $sock $ev $cmd
}
return -options [dict incr opts -level] $result
}
proc www::socks::socks4 {sock host port} {
variable username
set ip4 [split $host .]
if {[llength $ip4] == 4 && [string is digit -strict [lindex $ip4 end]]} {
set data [binary format ccSc4a*x 4 1 $port $ip4 $username]
} else {
# SOCKS4a
set data [binary format ccSx3ca*xa*x 4 1 $port 1 $username $host]
}
binary scan [command $sock $data 8] cucuSc4 vn cd dstport dstip
if {$vn != 0} {
throw {SOCKS CONNECT VERSION} \
"unsupported socks connection version: $vn"
}
if {$cd != 90} {
throw [list SOCKS CONNECT [format ERROR%02X $cd]] \
"socks connection failed with error code $cd"
}
return [join $dstip .]:$dstport
}
proc www::socks::socks5 {sock host port} {
fconfigure $sock -translation binary -blocking 0
# Authenticate
set methods [list 0 2]
set data [binary format ccc* 5 [llength $methods] $methods]
binary scan [command $sock $data 2] cucu version method
if {$method == 0} {
# No authentication required
} elseif {$method == 1} {
# GSS-API RFC 1961
# Not implemented
throw {SOCKS AUTH UNKNOWN} "unsupported authentication method: $method"
} elseif {$method == 2} {
# Username/password RFC 1929
authenticate $sock
} else {
throw {SOCKS AUTH NOTACCEPTED} "no acceptable authentication methods"
}
# Connect
set data [binary format ccc 5 1 0]
set ip4 [split $host .]
if {[llength $ip4] == 1 && [llength [set ip6 [split $host :]]] >= 3} {
# IPv6 address
set x [lsearch -exact $ip6 {}]
if {$x >= 0} {
set ip6 [lsearch -inline -exact -all -not $ip6 {}]
set insert [lrepeat [expr {8 - [llength $ip6]}] 0]
set ip6 [linsert $ip6 $x {*}$insert]
}
append data [binary format cS8S 4 $ip6 $port]
} elseif {[llength $ip4] == 4 && [string is digit -strict [lindex $ip4 end]]} {
# IPv4 address
append data [binary format cc4S 1 $ip4 $port]
} else {
# hostname
append data [binary format cca*S 3 [string length $host] $host $port]
}
binary scan [command $sock $data 4 10000] ccxc version reply atyp
if {$reply != 0} {
throw [list SOCKS CONNECT [format ERROR%02X $reply]] \
"socks connection failed with error code $reply"
}
switch $atyp {
1 {
binary scan [command $sock {} 6] c4S dstip dstport
return [join $dstip .]:$dstport
}
3 {
binary scan [command $sock {} 1] c len
binary scan [command $sock {} [expr {$len + 2}]] a${len}S dsthost dstport
return $dsthost:$dstport
}
4 {
binary scan [command $sock {} 18] S8S dstip dstport
return format {[%s]:$d} [join $dstip :] $dstport
}
}
}
proc www::socks::authenticate {sock} {
variable username
variable password
set data [binary format cca*ca* 1 \
[string length $username] $username [string length $password] $password]
binary scan [command $sock 2] cucu version status
if {$version != 1} {
throw {SOCKS AUTH RFC1929 VERSION} \
"unsupported username/password authentication version: $version"
}
if {$status != 0} {
throw {SOCKS AUTH RFC1929 STATUS} \
"username/password authentication failed: $status"
}
}

306
src/vfs/_vfscommon.vfs/modules/www/websocket-1.1.tm

@ -0,0 +1,306 @@
# Helper library for adding websocket support to www
package require www 2.7
proc www::websocket {args} {
set opts {-upgrade {WebSocket www::WebSocket}}
set args [getopt arg $args {
-timeout:milliseconds {dict set opts -timeout $arg}
-auth:data {dict set opts -auth $arg}
-digest:cred {dict set opts -digest $arg}
-maxredir:cnt {dict set opts -maxredir $arg}
}]
if {[llength $args] < 1 || [llength $args] > 3} {
throw {WWW WEBSOCKET ARGS} {wrong # args:\
should be "www::websocket url ?protocols? ?extensions?"}
}
lassign $args url protocols extensions
try {
set hdrs [WebSocket headers]
if {[llength $protocols]} {
lappend hdrs Sec-WebSocket-Protocol [join $protocols {, }]
}
if {[dict size $extensions]} {
set ext [join [lmap name [dict keys $extensions] {
set list [list $name]
if {[dict exists $extensions $name parameters]} {
lappend $list [dict get $extensions $name parameters]
}
join $list {; }
}] {, }]
lappend hdrs Sec-WebSocket-Extensions $ext
}
www get {*}$opts -headers $hdrs $url
} on ok {result info} {
if {[dict get $info status code] != 101} {
# The only correct response for a successful websocket connection
# is 101 Switching Protocols. Even 200 OK is not good.
set code [dict get $info status code]
set codegrp [string replace $code 1 2 XX]
set reason [dict get $info status reason]
dict set info -code 1
dict set info -errorcode [list WWW CODE $codegrp $code $reason]
return -options [dict incr info -level] $result
}
set websock [dict get $info websocket]
set hdrs [dict get $info headers]
set protocol [if {[dict exists $hdrs sec-websocket-protocol]} {
dict get $hdrs sec-websocket-protocol
}]
if {[dict exists $hdrs sec-websocket-extensions]} {
set ext [header [$hdrs sec-websocket-extensions] *]
set mixins [lmap value [lreverse $ext] {
set list [lmap n [split $value {;}] {string trim $n}]
set params [lassign $list name]
dict set parameters $name $params
dict get $extensions $name implementation
}]
oo::objdefine $websock \
mixin www::WSExtension {*}$mixins www::WebSocket
# Inform the extensions of their parameters, if any
$websock parameters $parameters
}
# Return the websocket object command (and the negotiated protocol)
return protocol $protocol [dict get $info websocket]
}
}
namespace ensemble configure www \
-subcommands [linsert [namespace ensemble configure www -subcommands] end websocket]
oo::class create www::WebSocket {
method Startup {headers} {
my variable fd
variable callback {}
# This socket cannot be used for future connections
release [self]
fconfigure $fd -translation binary -buffering none -blocking 0
# Return the websocket object to the caller
my Result websocket [self]
my Return [my PopRequest]
}
method Read {} {
my variable fd
return [read $fd]
}
method Write {data} {
my variable fd
puts -nonewline $fd $data
}
method Handler {} {
my variable fd callback
fileevent $fd readable [list [info coroutine] data]
set data ""
set payload ""
while {![eof $fd]} {
yield
append data [my Read]
if {[binary scan $data B4Xcucu flags code len] != 3} continue
if {$len < 126} {
set pos 2
} elseif {$len == 126} {
if {[binary scan $data x2Su len] != 1} continue
set pos 4
} elseif {$len == 127} {
if {[binary scan $data x2Wu len] != 1} continue
set pos 10
} else {
# Error: Messages from server to client should not be masked
my close 1002
}
if {[string length $data] < $pos + $len} continue
set code [expr {$code & 0xf}]
set payload [string range $data $pos [expr {$pos + $len - 1}]]
set data [string range $data [expr {$pos + $len}] end]
if {$code == 0} {
append message $payload
} else {
set opcode $code
# Control frames MAY be injected in the middle of a
# fragmented message. (RFC6455 5.4)
# Control frames are identified by opcodes where the most
# significant bit of the opcode is 1. (RFC6455 5.5)
if {$code < 8} {set message $payload}
}
if {![string index $flags 0]} continue
if {$opcode < 8} {
my Receive $opcode $message $flags
} else {
my Receive $opcode $payload $flags
}
}
if {[dict exists $callback close]} {
# 1006 is designated for use in applications expecting a status
# code to indicate that the connection was closed abnormally,
# e.g., without sending or receiving a Close control frame.
{*}[dict get $callback close] close 1006 "eof on connection"
}
my destroy
}
# Methods that can be overridden by extensions
method Read {} {
my variable fd
return [read $fd]
}
method Write {data} {
my variable fd
puts -nonewline $fd $data
}
method Receive {opcode data flags} {
my variable callback
switch $opcode {
1 {
if {[dict exists $callback text]} {
set str [encoding convertfrom utf-8 $data]
{*}[dict get $callback text] text $str
} else {
my close 1003
}
}
2 {
if {[dict exists $callback binary]} {
{*}[dict get $callback binary] binary $data
} else {
my close 1003
}
}
8 {
if {[dict exists $callback close]} {
if {[binary scan $data Sua* code reason] != 2} {
set code 1005
set reason ""
}
{*}[dict get $callback close] close $code $reason
set callback {}
}
}
9 {
if {[dict exists $callback ping]} {
{*}[dict get $callback ping] ping $data
} else {
my pong $data
}
}
10 {
if {[dict exists $callback pong]} {
{*}[dict get $callback pong] pong $data
}
}
}
}
method Transmit {opcode data {flags 1}} {
binary scan $data cu* bytes
# The requirement to use a strong source of entropy makes no sense
# So we'll just use Tcl's simple linear congruential generator
set key [expr {int(rand() * 0x100000000)}]
binary scan [binary format I $key] cu* mask
set length [llength $bytes]
# Apply the mask
set i 0
set bytes [lmap n $bytes {
set m [lindex $mask [expr {$i & 3}]]
incr i
expr {$n ^ $m}
}]
set type \
[expr {$opcode | "0b[string reverse [format %04s $flags]]0000"}]
set data [binary format c $type]
if {$length < 126} {
append data [binary format c [expr {$length | 0x80}]]
} elseif {$length < 65536} {
append data [binary format cS [expr {126 | 0x80}] $length]
} else {
append data [binary format cW [expr {127 | 0x80}] $length]
}
append data [binary format c*c* $mask $bytes]
my Write $data
}
# Public methods
method callback {types prefix} {
variable callback
set running [dict size $callback]
if {$prefix ne ""} {
foreach type $types {
dict set callback $type $prefix
}
} elseif {[llength $types]} {
set callback [dict remove $callback {*}$types]
} else {
set callback {}
}
if {[dict size $callback]} {
if {!$running} {coroutine websockcoro my Handler}
} else {
if {$running} {rename websockcoro ""}
}
}
method text {str} {
my Transmit 1 [encoding convertto utf-8 $str]
}
method binary {data} {
my Transmit 2 $data
}
method close {{code 1005} {reason ""}} {
# 1005 is designated for use in applications expecting a status code
# to indicate that no status code was actually present.
set payload [if {$code != 1005} {
binary format Sa* $code [encoding convertto utf-8 $reason]
}]
my Transmit 8 $payload
# The client SHOULD wait for the server to close the connection but
# MAY close the connection at any time after sending and receiving
# a Close message, e.g., if it has not received a TCP Close from
# the server in a reasonable time period.
# my destroy
}
method ping {{data ""}} {
my Transmit 9 $data
}
method pong {{data ""}} {
my Transmit 10 $data
}
}
oo::class create www::WSExtension {
method parameters {parameters} {
dict for {mixin params} $parameters {
nextto $mixin $params
}
}
}
oo::objdefine www::WebSocket {
method key {} {
# Generate a websocket key containing base64-encoded random bytes
# This key is only intended to prevent a caching proxy from
# re-sending a previous WebSocket conversation, and does not
# provide any authentication, privacy or integrity.
# It is therefor not necessary to check the returned hash.
for {set i 0} {$i < 12} {incr i} {
lappend bytes [expr {int(rand() * 256)}]
}
return [binary encode base64 [binary format c* $bytes]]
}
method headers {} {
return [list Sec-WebSocket-Key [my key] Sec-WebSocket-Version 13]
}
}
www register ws 80
www register wss 443 www::encrypt 1

381
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/critcl-rt.tcl

@ -0,0 +1,381 @@
#
# Critcl - build C extensions on-the-fly
#
# Copyright (c) 2001-2007 Jean-Claude Wippler
# Copyright (c) 2002-2007 Steve Landers
#
# See http://wiki.tcl.tk/critcl
#
# This is the Critcl runtime that loads the appropriate
# shared library when a package is requested
#
namespace eval ::critcl::runtime {}
proc ::critcl::runtime::loadlib {dir package version libname initfun tsrc mapping args} {
# XXX At least parts of this can be done by the package generator,
# XXX like listing the Tcl files to source. The glob here allows
# XXX code-injection after-the-fact, by simply adding a .tcl in
# XXX the proper place.
set path [file join $dir [MapPlatform $mapping]]
set ext [info sharedlibextension]
set lib [file join $path $libname$ext]
set provide [list]
# Now the runtime equivalent of a series of 'preFetch' commands.
if {[llength $args]} {
set preload [file join $path preload$ext]
foreach p $args {
set prelib [file join $path $p$ext]
if {[file readable $preload] && [file readable $prelib]} {
lappend provide [list load $preload];# XXX Move this out of the loop, do only once.
lappend provide [list ::critcl::runtime::preload $prelib]
}
}
}
lappend provide [list load $lib $initfun]
foreach t $tsrc {
lappend loadcmd "::critcl::runtime::Fetch \$dir [list $t]"
}
lappend provide "package provide $package $version"
package ifneeded $package $version [join $provide "\n"]
return
}
proc ::critcl::runtime::preFetch {path ext dll} {
set preload [file join $path preload$ext]
if {![file readable $preload]} return
set prelib [file join $path $dll$ext]
if {![file readable $prelib]} return
load $preload ; # Defines next command.
::critcl::runtime::preload $prelib
return
}
proc ::critcl::runtime::Fetch {dir t} {
# The 'Ignore' disables compile & run functionality.
# Background: If the regular critcl package is already loaded, and
# this prebuilt package uses its defining .tcl file also as a
# 'tsources' then critcl might try to collect data and build it
# because of the calls to its API, despite the necessary binaries
# already being present, just not in the critcl cache. That is
# redundant in the best case, and fails in the worst case (no
# compiler), preventing the use o a perfectly fine package. The
# 'ignore' call now tells critcl that it should ignore any calls
# made to it by the sourced files, and thus avoids that trouble.
# The other case, the regular critcl package getting loaded after
# this prebuilt package is irrelevant. At that point the tsources
# were already run, and used the dummy procedures defined in the
# critcl-rt.tcl, which ignore the calls by definition.
set t [file join $dir tcl $t]
::critcl::Ignore $t
uplevel #0 [list source $t]
return
}
proc ::critcl::runtime::precopy {dll} {
# This command is only used on Windows when preloading out of a
# VFS that doesn't support direct loading (usually, a Starkit)
# - we preserve the dll name so that dependencies are satisfied
# - The critcl::runtime::preload command is defined in the supporting
# "preload" package, implemented in "critcl/lib/critcl/critcl_c/preload.c"
global env
if {[info exists env(TEMP)]} {
set dir $env(TEMP)
} elseif {[info exists env(TMP)]} {
set dir $env(TMP)
} elseif {[file exists $env(HOME)]} {
set dir $env(HOME)
} else {
set dir .
}
set dir [file join $dir TCL[pid]]
set i 0
while {[file exists $dir]} {
append dir [incr i]
}
set new [file join $dir [file tail $dll]]
file mkdir $dir
file copy $dll $new
return $new
}
proc ::critcl::runtime::MapPlatform {{mapping {}}} {
# A sibling of critcl::platform that applies the platform mapping
set platform [::platform::generic]
set version $::tcl_platform(osVersion)
if {[string match "macosx-*" $platform]} {
# "normalize" the osVersion to match OSX release numbers
set v [split $version .]
set v1 [lindex $v 0]
set v2 [lindex $v 1]
incr v1 -4
set version 10.$v1.$v2
} else {
# Strip trailing non-version info
regsub -- {-.*$} $version {} version
}
foreach {config map} $mapping {
if {![string match $config $platform]} continue
set minver [lindex $map 1]
if {[package vcompare $version $minver] < 0} continue
set platform [lindex $map 0]
break
}
return $platform
}
# Dummy implementation of the critcl package, if not present
if {![llength [info commands ::critcl::Ignore]]} {
namespace eval ::critcl {}
proc ::critcl::Ignore {args} {
namespace eval ::critcl::v {}
set ::critcl::v::ignore([file normalize [lindex $args 0]]) .
}
}
if {![llength [info commands ::critcl::api]]} {
namespace eval ::critcl {}
proc ::critcl::api {args} {}
}
if {![llength [info commands ::critcl::at]]} {
namespace eval ::critcl {}
proc ::critcl::at {args} {}
}
if {![llength [info commands ::critcl::cache]]} {
namespace eval ::critcl {}
proc ::critcl::cache {args} {}
}
if {![llength [info commands ::critcl::ccode]]} {
namespace eval ::critcl {}
proc ::critcl::ccode {args} {}
}
if {![llength [info commands ::critcl::ccommand]]} {
namespace eval ::critcl {}
proc ::critcl::ccommand {args} {}
}
if {![llength [info commands ::critcl::cdata]]} {
namespace eval ::critcl {}
proc ::critcl::cdata {args} {}
}
if {![llength [info commands ::critcl::cdefines]]} {
namespace eval ::critcl {}
proc ::critcl::cdefines {args} {}
}
if {![llength [info commands ::critcl::cflags]]} {
namespace eval ::critcl {}
proc ::critcl::cflags {args} {}
}
if {![llength [info commands ::critcl::cheaders]]} {
namespace eval ::critcl {}
proc ::critcl::cheaders {args} {}
}
if {![llength [info commands ::critcl::check]]} {
namespace eval ::critcl {}
proc ::critcl::check {args} {return 0}
}
if {![llength [info commands ::critcl::cinit]]} {
namespace eval ::critcl {}
proc ::critcl::cinit {args} {}
}
if {![llength [info commands ::critcl::clibraries]]} {
namespace eval ::critcl {}
proc ::critcl::clibraries {args} {}
}
if {![llength [info commands ::critcl::compiled]]} {
namespace eval ::critcl {}
proc ::critcl::compiled {args} {return 1}
}
if {![llength [info commands ::critcl::compiling]]} {
namespace eval ::critcl {}
proc ::critcl::compiling {args} {return 0}
}
if {![llength [info commands ::critcl::config]]} {
namespace eval ::critcl {}
proc ::critcl::config {args} {}
}
if {![llength [info commands ::critcl::cproc]]} {
namespace eval ::critcl {}
proc ::critcl::cproc {args} {}
}
if {![llength [info commands ::critcl::csources]]} {
namespace eval ::critcl {}
proc ::critcl::csources {args} {}
}
if {![llength [info commands ::critcl::debug]]} {
namespace eval ::critcl {}
proc ::critcl::debug {args} {}
}
if {![llength [info commands ::critcl::done]]} {
namespace eval ::critcl {}
proc ::critcl::done {args} {return 1}
}
if {![llength [info commands ::critcl::failed]]} {
namespace eval ::critcl {}
proc ::critcl::failed {args} {return 0}
}
if {![llength [info commands ::critcl::framework]]} {
namespace eval ::critcl {}
proc ::critcl::framework {args} {}
}
if {![llength [info commands ::critcl::include]]} {
namespace eval ::critcl {}
proc ::critcl::include {args} {}
}
if {![llength [info commands ::critcl::ldflags]]} {
namespace eval ::critcl {}
proc ::critcl::ldflags {args} {}
}
if {![llength [info commands ::critcl::license]]} {
namespace eval ::critcl {}
proc ::critcl::license {args} {}
}
if {![llength [info commands ::critcl::load]]} {
namespace eval ::critcl {}
proc ::critcl::load {args} {return 1}
}
if {![llength [info commands ::critcl::make]]} {
namespace eval ::critcl {}
proc ::critcl::make {args} {}
}
if {![llength [info commands ::critcl::meta]]} {
namespace eval ::critcl {}
proc ::critcl::meta {args} {}
}
if {![llength [info commands ::critcl::platform]]} {
namespace eval ::critcl {}
proc ::critcl::platform {args} {}
}
if {![llength [info commands ::critcl::preload]]} {
namespace eval ::critcl {}
proc ::critcl::preload {args} {}
}
if {![llength [info commands ::critcl::source]]} {
namespace eval ::critcl {}
proc ::critcl::source {args} {}
}
if {![llength [info commands ::critcl::tcl]]} {
namespace eval ::critcl {}
proc ::critcl::tcl {args} {}
}
if {![llength [info commands ::critcl::tk]]} {
namespace eval ::critcl {}
proc ::critcl::tk {args} {}
}
if {![llength [info commands ::critcl::tsources]]} {
namespace eval ::critcl {}
proc ::critcl::tsources {args} {}
}
if {![llength [info commands ::critcl::userconfig]]} {
namespace eval ::critcl {}
proc ::critcl::userconfig {args} {}
}
# Define a clone of platform::generic, if needed
if {![llength [info commands ::platform::generic]]} {
namespace eval ::platform {}
proc ::platform::generic {} {
global tcl_platform
set plat [string tolower [lindex $tcl_platform(os) 0]]
set cpu $tcl_platform(machine)
switch -glob -- $cpu {
sun4* {
set cpu sparc
}
intel -
ia32* -
i*86* {
set cpu ix86
}
x86_64 {
if {$tcl_platform(wordSize) == 4} {
# See Example <1> at the top of this file.
set cpu ix86
}
}
ppc -
"Power*" {
set cpu powerpc
}
"arm*" {
set cpu arm
}
ia64 {
if {$tcl_platform(wordSize) == 4} {
append cpu _32
}
}
}
switch -glob -- $plat {
windows {
if {$tcl_platform(platform) == "unix"} {
set plat cygwin
} else {
set plat win32
}
if {$cpu eq "amd64"} {
# Do not check wordSize, win32-x64 is an IL32P64 platform.
set cpu x86_64
}
}
sunos {
set plat solaris
if {[string match "ix86" $cpu]} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
} elseif {![string match "ia64*" $cpu]} {
# sparc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
darwin {
set plat macosx
# Correctly identify the cpu when running as a 64bit
# process on a machine with a 32bit kernel
if {$cpu eq "ix86"} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
}
}
aix {
set cpu powerpc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
hp-ux {
set plat hpux
if {![string match "ia64*" $cpu]} {
set cpu parisc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
osf1 {
set plat tru64
}
default {
set plat [lindex [split $plat _-] 0]
}
}
return "${plat}-${cpu}"
}
}

1
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/license.terms

@ -0,0 +1 @@
<<Undefined>>

BIN
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/linux-x86_64/tcllibc.so

Binary file not shown.

2
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/pkgIndex.tcl

@ -0,0 +1,2 @@
if {![package vsatisfies [package provide Tcl] 9.0]} {return}
package ifneeded tcllibc 2.0 "[list proc __critcl_load__ {dir} { ; source [file join $dir critcl-rt.tcl] ; set path [file join $dir [::critcl::runtime::MapPlatform]] ; set ext [info sharedlibextension] ; set lib [file join $path "tcllibc$ext"] ; load $lib Tcllibc ; package provide tcllibc 2.0 ; catch {rename __critcl_load__ {}}}] ; [list __critcl_load__ $dir]"

21
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc/teapot.txt

@ -0,0 +1,21 @@
Package tcllibc 2.0
Meta platform linux-glibc2.22-x86_64
Meta build::date 2025-08-20
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::date critcl
Meta license BSD licensed.
Meta author {Andreas Kupries}
Meta require {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9}
Meta require {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9}
Meta require {Tcl 8.5 9} {Tcl 8.5 9}
Meta entrytclcommand {eval "[list proc __critcl_load__ {dir} { ; source [file join $dir critcl-rt.tcl] ; set path [file join $dir [::critcl::runtime::MapPlatform]] ; set ext [info sharedlibextension] ; set lib [file join $path "tcllibc$ext"] ; load $lib Tcllibc ; package provide tcllibc 2.0 ; catch {rename __critcl_load__ {}}}] ; [list __critcl_load__ $dir]"}
Meta included critcl-rt.tcl linux-x86_64/tcllibc.so

381
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/critcl-rt.tcl

@ -0,0 +1,381 @@
#
# Critcl - build C extensions on-the-fly
#
# Copyright (c) 2001-2007 Jean-Claude Wippler
# Copyright (c) 2002-2007 Steve Landers
#
# See http://wiki.tcl.tk/critcl
#
# This is the Critcl runtime that loads the appropriate
# shared library when a package is requested
#
namespace eval ::critcl::runtime {}
proc ::critcl::runtime::loadlib {dir package version libname initfun tsrc mapping args} {
# XXX At least parts of this can be done by the package generator,
# XXX like listing the Tcl files to source. The glob here allows
# XXX code-injection after-the-fact, by simply adding a .tcl in
# XXX the proper place.
set path [file join $dir [MapPlatform $mapping]]
set ext [info sharedlibextension]
set lib [file join $path $libname$ext]
set provide [list]
# Now the runtime equivalent of a series of 'preFetch' commands.
if {[llength $args]} {
set preload [file join $path preload$ext]
foreach p $args {
set prelib [file join $path $p$ext]
if {[file readable $preload] && [file readable $prelib]} {
lappend provide [list load $preload];# XXX Move this out of the loop, do only once.
lappend provide [list ::critcl::runtime::preload $prelib]
}
}
}
lappend provide [list load $lib $initfun]
foreach t $tsrc {
lappend loadcmd "::critcl::runtime::Fetch \$dir [list $t]"
}
lappend provide "package provide $package $version"
package ifneeded $package $version [join $provide "\n"]
return
}
proc ::critcl::runtime::preFetch {path ext dll} {
set preload [file join $path preload$ext]
if {![file readable $preload]} return
set prelib [file join $path $dll$ext]
if {![file readable $prelib]} return
load $preload ; # Defines next command.
::critcl::runtime::preload $prelib
return
}
proc ::critcl::runtime::Fetch {dir t} {
# The 'Ignore' disables compile & run functionality.
# Background: If the regular critcl package is already loaded, and
# this prebuilt package uses its defining .tcl file also as a
# 'tsources' then critcl might try to collect data and build it
# because of the calls to its API, despite the necessary binaries
# already being present, just not in the critcl cache. That is
# redundant in the best case, and fails in the worst case (no
# compiler), preventing the use o a perfectly fine package. The
# 'ignore' call now tells critcl that it should ignore any calls
# made to it by the sourced files, and thus avoids that trouble.
# The other case, the regular critcl package getting loaded after
# this prebuilt package is irrelevant. At that point the tsources
# were already run, and used the dummy procedures defined in the
# critcl-rt.tcl, which ignore the calls by definition.
set t [file join $dir tcl $t]
::critcl::Ignore $t
uplevel #0 [list source $t]
return
}
proc ::critcl::runtime::precopy {dll} {
# This command is only used on Windows when preloading out of a
# VFS that doesn't support direct loading (usually, a Starkit)
# - we preserve the dll name so that dependencies are satisfied
# - The critcl::runtime::preload command is defined in the supporting
# "preload" package, implemented in "critcl/lib/critcl/critcl_c/preload.c"
global env
if {[info exists env(TEMP)]} {
set dir $env(TEMP)
} elseif {[info exists env(TMP)]} {
set dir $env(TMP)
} elseif {[file exists $env(HOME)]} {
set dir $env(HOME)
} else {
set dir .
}
set dir [file join $dir TCL[pid]]
set i 0
while {[file exists $dir]} {
append dir [incr i]
}
set new [file join $dir [file tail $dll]]
file mkdir $dir
file copy $dll $new
return $new
}
proc ::critcl::runtime::MapPlatform {{mapping {}}} {
# A sibling of critcl::platform that applies the platform mapping
set platform [::platform::generic]
set version $::tcl_platform(osVersion)
if {[string match "macosx-*" $platform]} {
# "normalize" the osVersion to match OSX release numbers
set v [split $version .]
set v1 [lindex $v 0]
set v2 [lindex $v 1]
incr v1 -4
set version 10.$v1.$v2
} else {
# Strip trailing non-version info
regsub -- {-.*$} $version {} version
}
foreach {config map} $mapping {
if {![string match $config $platform]} continue
set minver [lindex $map 1]
if {[package vcompare $version $minver] < 0} continue
set platform [lindex $map 0]
break
}
return $platform
}
# Dummy implementation of the critcl package, if not present
if {![llength [info commands ::critcl::Ignore]]} {
namespace eval ::critcl {}
proc ::critcl::Ignore {args} {
namespace eval ::critcl::v {}
set ::critcl::v::ignore([file normalize [lindex $args 0]]) .
}
}
if {![llength [info commands ::critcl::api]]} {
namespace eval ::critcl {}
proc ::critcl::api {args} {}
}
if {![llength [info commands ::critcl::at]]} {
namespace eval ::critcl {}
proc ::critcl::at {args} {}
}
if {![llength [info commands ::critcl::cache]]} {
namespace eval ::critcl {}
proc ::critcl::cache {args} {}
}
if {![llength [info commands ::critcl::ccode]]} {
namespace eval ::critcl {}
proc ::critcl::ccode {args} {}
}
if {![llength [info commands ::critcl::ccommand]]} {
namespace eval ::critcl {}
proc ::critcl::ccommand {args} {}
}
if {![llength [info commands ::critcl::cdata]]} {
namespace eval ::critcl {}
proc ::critcl::cdata {args} {}
}
if {![llength [info commands ::critcl::cdefines]]} {
namespace eval ::critcl {}
proc ::critcl::cdefines {args} {}
}
if {![llength [info commands ::critcl::cflags]]} {
namespace eval ::critcl {}
proc ::critcl::cflags {args} {}
}
if {![llength [info commands ::critcl::cheaders]]} {
namespace eval ::critcl {}
proc ::critcl::cheaders {args} {}
}
if {![llength [info commands ::critcl::check]]} {
namespace eval ::critcl {}
proc ::critcl::check {args} {return 0}
}
if {![llength [info commands ::critcl::cinit]]} {
namespace eval ::critcl {}
proc ::critcl::cinit {args} {}
}
if {![llength [info commands ::critcl::clibraries]]} {
namespace eval ::critcl {}
proc ::critcl::clibraries {args} {}
}
if {![llength [info commands ::critcl::compiled]]} {
namespace eval ::critcl {}
proc ::critcl::compiled {args} {return 1}
}
if {![llength [info commands ::critcl::compiling]]} {
namespace eval ::critcl {}
proc ::critcl::compiling {args} {return 0}
}
if {![llength [info commands ::critcl::config]]} {
namespace eval ::critcl {}
proc ::critcl::config {args} {}
}
if {![llength [info commands ::critcl::cproc]]} {
namespace eval ::critcl {}
proc ::critcl::cproc {args} {}
}
if {![llength [info commands ::critcl::csources]]} {
namespace eval ::critcl {}
proc ::critcl::csources {args} {}
}
if {![llength [info commands ::critcl::debug]]} {
namespace eval ::critcl {}
proc ::critcl::debug {args} {}
}
if {![llength [info commands ::critcl::done]]} {
namespace eval ::critcl {}
proc ::critcl::done {args} {return 1}
}
if {![llength [info commands ::critcl::failed]]} {
namespace eval ::critcl {}
proc ::critcl::failed {args} {return 0}
}
if {![llength [info commands ::critcl::framework]]} {
namespace eval ::critcl {}
proc ::critcl::framework {args} {}
}
if {![llength [info commands ::critcl::include]]} {
namespace eval ::critcl {}
proc ::critcl::include {args} {}
}
if {![llength [info commands ::critcl::ldflags]]} {
namespace eval ::critcl {}
proc ::critcl::ldflags {args} {}
}
if {![llength [info commands ::critcl::license]]} {
namespace eval ::critcl {}
proc ::critcl::license {args} {}
}
if {![llength [info commands ::critcl::load]]} {
namespace eval ::critcl {}
proc ::critcl::load {args} {return 1}
}
if {![llength [info commands ::critcl::make]]} {
namespace eval ::critcl {}
proc ::critcl::make {args} {}
}
if {![llength [info commands ::critcl::meta]]} {
namespace eval ::critcl {}
proc ::critcl::meta {args} {}
}
if {![llength [info commands ::critcl::platform]]} {
namespace eval ::critcl {}
proc ::critcl::platform {args} {}
}
if {![llength [info commands ::critcl::preload]]} {
namespace eval ::critcl {}
proc ::critcl::preload {args} {}
}
if {![llength [info commands ::critcl::source]]} {
namespace eval ::critcl {}
proc ::critcl::source {args} {}
}
if {![llength [info commands ::critcl::tcl]]} {
namespace eval ::critcl {}
proc ::critcl::tcl {args} {}
}
if {![llength [info commands ::critcl::tk]]} {
namespace eval ::critcl {}
proc ::critcl::tk {args} {}
}
if {![llength [info commands ::critcl::tsources]]} {
namespace eval ::critcl {}
proc ::critcl::tsources {args} {}
}
if {![llength [info commands ::critcl::userconfig]]} {
namespace eval ::critcl {}
proc ::critcl::userconfig {args} {}
}
# Define a clone of platform::generic, if needed
if {![llength [info commands ::platform::generic]]} {
namespace eval ::platform {}
proc ::platform::generic {} {
global tcl_platform
set plat [string tolower [lindex $tcl_platform(os) 0]]
set cpu $tcl_platform(machine)
switch -glob -- $cpu {
sun4* {
set cpu sparc
}
intel -
ia32* -
i*86* {
set cpu ix86
}
x86_64 {
if {$tcl_platform(wordSize) == 4} {
# See Example <1> at the top of this file.
set cpu ix86
}
}
ppc -
"Power*" {
set cpu powerpc
}
"arm*" {
set cpu arm
}
ia64 {
if {$tcl_platform(wordSize) == 4} {
append cpu _32
}
}
}
switch -glob -- $plat {
windows {
if {$tcl_platform(platform) == "unix"} {
set plat cygwin
} else {
set plat win32
}
if {$cpu eq "amd64"} {
# Do not check wordSize, win32-x64 is an IL32P64 platform.
set cpu x86_64
}
}
sunos {
set plat solaris
if {[string match "ix86" $cpu]} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
} elseif {![string match "ia64*" $cpu]} {
# sparc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
darwin {
set plat macosx
# Correctly identify the cpu when running as a 64bit
# process on a machine with a 32bit kernel
if {$cpu eq "ix86"} {
if {$tcl_platform(wordSize) == 8} {
set cpu x86_64
}
}
}
aix {
set cpu powerpc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
hp-ux {
set plat hpux
if {![string match "ia64*" $cpu]} {
set cpu parisc
if {$tcl_platform(wordSize) == 8} {
append cpu 64
}
}
}
osf1 {
set plat tru64
}
default {
set plat [lindex [split $plat _-] 0]
}
}
return "${plat}-${cpu}"
}
}

1
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/license.terms

@ -0,0 +1 @@
<<Undefined>>

BIN
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/linux-x86_64/tcllibc.so

Binary file not shown.

2
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/pkgIndex.tcl

@ -0,0 +1,2 @@
if {![package vsatisfies [package provide Tcl] 9.0]} {return}
package ifneeded tcllibc 2.0 "[list proc __critcl_load__ {dir} { ; source [file join $dir critcl-rt.tcl] ; set path [file join $dir [::critcl::runtime::MapPlatform]] ; set ext [info sharedlibextension] ; set lib [file join $path "tcllibc$ext"] ; load $lib Tcllibc ; package provide tcllibc 2.0 ; catch {rename __critcl_load__ {}}}] ; [list __critcl_load__ $dir]"

21
src/vfs/punk9linux.vfs/lib_tcl9/tcllibc2.0/teapot.txt

@ -0,0 +1,21 @@
Package tcllibc 2.0
Meta platform linux-glibc2.22-x86_64
Meta build::date 2025-08-20
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::by {critcl 3.3} obermeier {critcl 3.3} obermeier
Meta generated::date critcl
Meta license BSD licensed.
Meta author {Andreas Kupries}
Meta require {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9}
Meta require {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9} {Tcl 8.5 9}
Meta require {Tcl 8.5 9} {Tcl 8.5 9}
Meta entrytclcommand {eval "[list proc __critcl_load__ {dir} { ; source [file join $dir critcl-rt.tcl] ; set path [file join $dir [::critcl::runtime::MapPlatform]] ; set ext [info sharedlibextension] ; set lib [file join $path "tcllibc$ext"] ; load $lib Tcllibc ; package provide tcllibc 2.0 ; catch {rename __critcl_load__ {}}}] ; [list __critcl_load__ $dir]"}
Meta included critcl-rt.tcl linux-x86_64/tcllibc.so

16
src/vfs/punk9linux.vfs/lib_tcl9/tcltls/pkgIndex.tcl

@ -0,0 +1,16 @@
if {[package vsatisfies [package present Tcl] 8.5-]} {
package ifneeded tls 1.7.23 [list apply {{dir} {
if {{shared} eq "static"} {
load {} Tls
} else {
load [file join $dir tcltls.so] Tls
}
set tlsTclInitScript [file join $dir tls.tcl]
if {[file exists $tlsTclInitScript]} {
source $tlsTclInitScript
}
}} $dir]
} elseif {[package vsatisfies [package present Tcl] 8.4]} {
package ifneeded tls 1.7.23 [list load [file join $dir tcltls.so] Tls]
}

BIN
src/vfs/punk9linux.vfs/lib_tcl9/tcltls/tcltls.so

Binary file not shown.

398
src/vfs/punk9linux.vfs/lib_tcl9/tcltls/tls.tcl

@ -0,0 +1,398 @@
#
# Copyright (C) 1997-2000 Matt Newman <matt@novadigm.com>
#
namespace eval tls {
variable logcmd tclLog
variable debug 0
# Default flags passed to tls::import
variable defaults {}
# Maps UID to Server Socket
variable srvmap
variable srvuid 0
# Over-ride this if you are using a different socket command
variable socketCmd
if {![info exists socketCmd]} {
set socketCmd [info command ::socket]
}
# This is the possible arguments to tls::socket and tls::init
# The format of this is a list of lists
## Each inner list contains the following elements
### Server (matched against "string match" for 0/1)
### Option name
### Variable to add the option to:
#### sopts: [socket] option
#### iopts: [tls::import] option
### How many arguments the following the option to consume
variable socketOptionRules {
{0 -async sopts 0}
{* -myaddr sopts 1}
{0 -myport sopts 1}
{* -type sopts 1}
{* -cadir iopts 1}
{* -cafile iopts 1}
{* -cert iopts 1}
{* -certfile iopts 1}
{* -cipher iopts 1}
{* -command iopts 1}
{* -dhparams iopts 1}
{* -key iopts 1}
{* -keyfile iopts 1}
{* -password iopts 1}
{* -request iopts 1}
{* -require iopts 1}
{* -autoservername discardOpts 1}
{* -servername iopts 1}
{* -ssl2 iopts 1}
{* -ssl3 iopts 1}
{* -tls1 iopts 1}
{* -tls1.1 iopts 1}
{* -tls1.2 iopts 1}
{* -tls1.3 iopts 1}
}
# tls::socket and tls::init options as a humane readable string
variable socketOptionsNoServer
variable socketOptionsServer
# Internal [switch] body to validate options
variable socketOptionsSwitchBody
}
proc tls::_initsocketoptions {} {
variable socketOptionRules
variable socketOptionsNoServer
variable socketOptionsServer
variable socketOptionsSwitchBody
# Do not re-run if we have already been initialized
if {[info exists socketOptionsSwitchBody]} {
return
}
# Create several structures from our list of options
## 1. options: a text representation of the valid options for the current
## server type
## 2. argSwitchBody: Switch body for processing arguments
set options(0) [list]
set options(1) [list]
set argSwitchBody [list]
foreach optionRule $socketOptionRules {
set ruleServer [lindex $optionRule 0]
set ruleOption [lindex $optionRule 1]
set ruleVarToUpdate [lindex $optionRule 2]
set ruleVarArgsToConsume [lindex $optionRule 3]
foreach server [list 0 1] {
if {![string match $ruleServer $server]} {
continue
}
lappend options($server) $ruleOption
}
switch -- $ruleVarArgsToConsume {
0 {
set argToExecute {
lappend @VAR@ $arg
set argsArray($arg) true
}
}
1 {
set argToExecute {
incr idx
if {$idx >= [llength $args]} {
return -code error "\"$arg\" option must be followed by value"
}
set argValue [lindex $args $idx]
lappend @VAR@ $arg $argValue
set argsArray($arg) $argValue
}
}
default {
return -code error "Internal argument construction error"
}
}
lappend argSwitchBody $ruleServer,$ruleOption [string map [list @VAR@ $ruleVarToUpdate] $argToExecute]
}
# Add in the final options
lappend argSwitchBody {*,-*} {return -code error "bad option \"$arg\": must be one of $options"}
lappend argSwitchBody default break
# Set the final variables
set socketOptionsNoServer [join $options(0) {, }]
set socketOptionsServer [join $options(1) {, }]
set socketOptionsSwitchBody $argSwitchBody
}
proc tls::initlib {dir dll} {
# Package index cd's into the package directory for loading.
# Irrelevant to unixoids, but for Windows this enables the OS to find
# the dependent DLL's in the CWD, where they may be.
set cwd [pwd]
catch {cd $dir}
if {[string equal $::tcl_platform(platform) "windows"] &&
![string equal [lindex [file system $dir] 0] "native"]} {
# If it is a wrapped executable running on windows, the openssl
# dlls must be copied out of the virtual filesystem to the disk
# where Windows will find them when resolving the dependency in
# the tls dll. We choose to make them siblings of the executable.
package require starkit
set dst [file nativename [file dirname $starkit::topdir]]
foreach sdll [glob -nocomplain -directory $dir -tails *eay32.dll] {
catch {file delete -force $dst/$sdll}
catch {file copy -force $dir/$sdll $dst/$sdll}
}
}
set res [catch {uplevel #0 [list load [file join [pwd] $dll]]} err]
catch {cd $cwd}
if {$res} {
namespace eval [namespace parent] {namespace delete tls}
return -code $res $err
}
rename tls::initlib {}
}
#
# Backwards compatibility, also used to set the default
# context options
#
proc tls::init {args} {
variable defaults
variable socketOptionsNoServer
variable socketOptionsServer
variable socketOptionsSwitchBody
tls::_initsocketoptions
# Technically a third option should be used here: Options that are valid
# only a both servers and non-servers
set server -1
set options $socketOptionsServer
# Validate arguments passed
set initialArgs $args
set argc [llength $args]
array set argsArray [list]
for {set idx 0} {$idx < $argc} {incr idx} {
set arg [lindex $args $idx]
switch -glob -- $server,$arg $socketOptionsSwitchBody
}
set defaults $initialArgs
}
#
# Helper function - behaves exactly as the native socket command.
#
proc tls::socket {args} {
variable socketCmd
variable defaults
variable socketOptionsNoServer
variable socketOptionsServer
variable socketOptionsSwitchBody
tls::_initsocketoptions
set idx [lsearch $args -server]
if {$idx != -1} {
set server 1
set callback [lindex $args [expr {$idx+1}]]
set args [lreplace $args $idx [expr {$idx+1}]]
set usage "wrong # args: should be \"tls::socket -server command ?options? port\""
set options $socketOptionsServer
} else {
set server 0
set usage "wrong # args: should be \"tls::socket ?options? host port\""
set options $socketOptionsNoServer
}
# Combine defaults with current options
set args [concat $defaults $args]
set argc [llength $args]
set sopts {}
set iopts [list -server $server]
array set argsArray [list]
for {set idx 0} {$idx < $argc} {incr idx} {
set arg [lindex $args $idx]
switch -glob -- $server,$arg $socketOptionsSwitchBody
}
if {$server} {
if {($idx + 1) != $argc} {
return -code error $usage
}
set uid [incr ::tls::srvuid]
set port [lindex $args [expr {$argc-1}]]
lappend sopts $port
#set sopts [linsert $sopts 0 -server $callback]
set sopts [linsert $sopts 0 -server [list tls::_accept $iopts $callback]]
#set sopts [linsert $sopts 0 -server [list tls::_accept $uid $callback]]
} else {
if {($idx + 2) != $argc} {
return -code error $usage
}
set host [lindex $args [expr {$argc-2}]]
set port [lindex $args [expr {$argc-1}]]
# If an "-autoservername" option is found, honor it
if {[info exists argsArray(-autoservername)] && $argsArray(-autoservername)} {
if {![info exists argsArray(-servername)]} {
set argsArray(-servername) $host
lappend iopts -servername $host
}
}
lappend sopts $host $port
}
#
# Create TCP/IP socket
#
set chan [eval $socketCmd $sopts]
if {!$server && [catch {
#
# Push SSL layer onto socket
#
eval [list tls::import] $chan $iopts
} err]} {
set info ${::errorInfo}
catch {close $chan}
return -code error -errorinfo $info $err
}
return $chan
}
# tls::_accept --
#
# This is the actual accept that TLS sockets use, which then calls
# the callback registered by tls::socket.
#
# Arguments:
# iopts tls::import opts
# callback server callback to invoke
# chan socket channel to accept/deny
# ipaddr calling IP address
# port calling port
#
# Results:
# Returns an error if the callback throws one.
#
proc tls::_accept { iopts callback chan ipaddr port } {
log 2 [list tls::_accept $iopts $callback $chan $ipaddr $port]
set chan [eval [list tls::import $chan] $iopts]
lappend callback $chan $ipaddr $port
if {[catch {
uplevel #0 $callback
} err]} {
log 1 "tls::_accept error: ${::errorInfo}"
close $chan
error $err $::errorInfo $::errorCode
} else {
log 2 "tls::_accept - called \"$callback\" succeeded"
}
}
#
# Sample callback for hooking: -
#
# error
# verify
# info
#
proc tls::callback {option args} {
variable debug
#log 2 [concat $option $args]
switch -- $option {
"error" {
foreach {chan msg} $args break
log 0 "TLS/$chan: error: $msg"
}
"verify" {
# poor man's lassign
foreach {chan depth cert rc err} $args break
array set c $cert
if {$rc != "1"} {
log 1 "TLS/$chan: verify/$depth: Bad Cert: $err (rc = $rc)"
} else {
log 2 "TLS/$chan: verify/$depth: $c(subject)"
}
if {$debug > 0} {
return 1; # FORCE OK
} else {
return $rc
}
}
"info" {
# poor man's lassign
foreach {chan major minor state msg} $args break
if {$msg != ""} {
append state ": $msg"
}
# For tracing
upvar #0 tls::$chan cb
set cb($major) $minor
log 2 "TLS/$chan: $major/$minor: $state"
}
default {
return -code error "bad option \"$option\":\
must be one of error, info, or verify"
}
}
}
proc tls::xhandshake {chan} {
upvar #0 tls::$chan cb
if {[info exists cb(handshake)] && \
$cb(handshake) == "done"} {
return 1
}
while {1} {
vwait tls::${chan}(handshake)
if {![info exists cb(handshake)]} {
return 0
}
if {$cb(handshake) == "done"} {
return 1
}
}
}
proc tls::password {} {
log 0 "TLS/Password: did you forget to set your passwd!"
# Return the worlds best kept secret password.
return "secret"
}
proc tls::log {level msg} {
variable debug
variable logcmd
if {$level > $debug || $logcmd == ""} {
return
}
set cmd $logcmd
lappend cmd $msg
uplevel #0 $cmd
}

BIN
src/vfs/punk9linux.vfs/lib_tcl9/tdom/libtcl9tdom0.9.6.so

Binary file not shown.

BIN
src/vfs/punk9linux.vfs/lib_tcl9/tdom/libtdomstub.a

Binary file not shown.

12
src/vfs/punk9linux.vfs/lib_tcl9/tdom/pkgIndex.tcl

@ -0,0 +1,12 @@
#
# Tcl package index file
#
if {[package vsatisfies [package provide Tcl] 9.0-]} {
package ifneeded tdom 0.9.6 \
"[list load [file join $dir libtcl9tdom0.9.6.so]];
[list source [file join $dir tdom.tcl]]"
} else {
package ifneeded tdom 0.9.6 \
"[list load [file join $dir libtdom0.9.6.so]];
[list source [file join $dir tdom.tcl]]"
}

1101
src/vfs/punk9linux.vfs/lib_tcl9/tdom/tdom.tcl

File diff suppressed because it is too large Load Diff

BIN
src/vfs/punk9linux.vfs/lib_tcl9/thread3.0.2/libtcl9thread3.0.2.so

Binary file not shown.

55
src/vfs/punk9linux.vfs/lib_tcl9/thread3.0.2/pkgIndex.tcl

@ -0,0 +1,55 @@
# -*- tcl -*-
# Tcl package index file, version 1.1
#
# Tcl 8.7 interps are only supported on 32-bit platforms.
# Lower than that is never supported. Bye!
if {![package vsatisfies [package provide Tcl] 9.0]
&& ((![package vsatisfies [package provide Tcl] 8.7])
|| ($::tcl_platform(pointerSize)!=4))} {
return
}
# All Tcl 8.7+ interps can [load] thread 3.0.2
#
# For interps that are not thread-enabled, we still call [package ifneeded].
# This is contrary to the usual convention, but is a good idea because we
# cannot imagine any other version of thread that might succeed in a
# thread-disabled interp. There's nothing to gain by yielding to other
# competing callers of [package ifneeded Thread]. On the other hand,
# deferring the error has the advantage that a script calling
# [package require Thread] in a thread-disabled interp gets an error message
# about a thread-disabled interp, instead of the message
# "can't find package thread".
package ifneeded [string tolower thread] 3.0.2 \
[list load [file join $dir libtcl9thread3.0.2.so] [string totitle thread]]
package ifneeded [string totitle thread] 3.0.2 \
[list package require -exact [string tolower thread] 3.0.2]
# package ttrace uses some support machinery.
# In Tcl 8.7+ interps; use [::apply]
package ifneeded ttrace 3.0.2 [list ::apply {{dir} {
if {[info exists ::env(TCL_THREAD_LIBRARY)] &&
[file readable $::env(TCL_THREAD_LIBRARY)/ttrace.tcl]} {
source $::env(TCL_THREAD_LIBRARY)/ttrace.tcl
} elseif {[file readable [file join $dir .. lib ttrace.tcl]]} {
source [file join $dir .. lib ttrace.tcl]
} elseif {[file readable [file join $dir ttrace.tcl]]} {
source [file join $dir ttrace.tcl]
} elseif {[file exists //zipfs:/lib/thread/ttrace.tcl] ||
![catch {zipfs mount [file join $dir libtcl9thread3.0.2.so] //zipfs:/lib/thread}]} {
source //zipfs:/lib/thread/ttrace.tcl
}
if {[namespace which ::ttrace::update] ne ""} {
::ttrace::update
}
}} $dir]
package ifneeded Ttrace 3.0.2 \
[list package require -exact ttrace 3.0.2]

BIN
src/vfs/punk9linux.vfs/lib_tcl9/thread3.0.2/thread3.0.2/libtcl9thread3.0.2.so

Binary file not shown.

55
src/vfs/punk9linux.vfs/lib_tcl9/thread3.0.2/thread3.0.2/pkgIndex.tcl

@ -0,0 +1,55 @@
# -*- tcl -*-
# Tcl package index file, version 1.1
#
# Tcl 8.7 interps are only supported on 32-bit platforms.
# Lower than that is never supported. Bye!
if {![package vsatisfies [package provide Tcl] 9.0]
&& ((![package vsatisfies [package provide Tcl] 8.7])
|| ($::tcl_platform(pointerSize)!=4))} {
return
}
# All Tcl 8.7+ interps can [load] thread 3.0.2
#
# For interps that are not thread-enabled, we still call [package ifneeded].
# This is contrary to the usual convention, but is a good idea because we
# cannot imagine any other version of thread that might succeed in a
# thread-disabled interp. There's nothing to gain by yielding to other
# competing callers of [package ifneeded Thread]. On the other hand,
# deferring the error has the advantage that a script calling
# [package require Thread] in a thread-disabled interp gets an error message
# about a thread-disabled interp, instead of the message
# "can't find package thread".
package ifneeded [string tolower thread] 3.0.2 \
[list load [file join $dir libtcl9thread3.0.2.so] [string totitle thread]]
package ifneeded [string totitle thread] 3.0.2 \
[list package require -exact [string tolower thread] 3.0.2]
# package ttrace uses some support machinery.
# In Tcl 8.7+ interps; use [::apply]
package ifneeded ttrace 3.0.2 [list ::apply {{dir} {
if {[info exists ::env(TCL_THREAD_LIBRARY)] &&
[file readable $::env(TCL_THREAD_LIBRARY)/ttrace.tcl]} {
source $::env(TCL_THREAD_LIBRARY)/ttrace.tcl
} elseif {[file readable [file join $dir .. lib ttrace.tcl]]} {
source [file join $dir .. lib ttrace.tcl]
} elseif {[file readable [file join $dir ttrace.tcl]]} {
source [file join $dir ttrace.tcl]
} elseif {[file exists //zipfs:/lib/thread/ttrace.tcl] ||
![catch {zipfs mount [file join $dir libtcl9thread3.0.2.so] //zipfs:/lib/thread}]} {
source //zipfs:/lib/thread/ttrace.tcl
}
if {[namespace which ::ttrace::update] ne ""} {
::ttrace::update
}
}} $dir]
package ifneeded Ttrace 3.0.2 \
[list package require -exact ttrace 3.0.2]

BIN
src/vfs/punk9linux.vfs/modules_tcl9/Thread-3.0b1.tm

Binary file not shown.

BIN
src/vfs/punk9linux.vfs/modules_tcl9/Thread/platform/linux_x86_64_tcl9-3.0b1.tm

Binary file not shown.

16
src/vfs/punk9win_for_tkruntime.vfs/lib_tcl9/tcltls1.7.23/pkgIndex.tcl

@ -0,0 +1,16 @@
if {[package vsatisfies [package present Tcl] 8.5-]} {
package ifneeded tls 1.7.23 [list apply {{dir} {
if {{shared} eq "static"} {
load {} Tls
} else {
load [file join $dir tcltls.dll] Tls
}
set tlsTclInitScript [file join $dir tls.tcl]
if {[file exists $tlsTclInitScript]} {
source $tlsTclInitScript
}
}} $dir]
} elseif {[package vsatisfies [package present Tcl] 8.4]} {
package ifneeded tls 1.7.23 [list load [file join $dir tcltls.dll] Tls]
}

BIN
src/vfs/punk9win_for_tkruntime.vfs/lib_tcl9/tcltls1.7.23/tcltls.dll

Binary file not shown.

398
src/vfs/punk9win_for_tkruntime.vfs/lib_tcl9/tcltls1.7.23/tls.tcl

@ -0,0 +1,398 @@
#
# Copyright (C) 1997-2000 Matt Newman <matt@novadigm.com>
#
namespace eval tls {
variable logcmd tclLog
variable debug 0
# Default flags passed to tls::import
variable defaults {}
# Maps UID to Server Socket
variable srvmap
variable srvuid 0
# Over-ride this if you are using a different socket command
variable socketCmd
if {![info exists socketCmd]} {
set socketCmd [info command ::socket]
}
# This is the possible arguments to tls::socket and tls::init
# The format of this is a list of lists
## Each inner list contains the following elements
### Server (matched against "string match" for 0/1)
### Option name
### Variable to add the option to:
#### sopts: [socket] option
#### iopts: [tls::import] option
### How many arguments the following the option to consume
variable socketOptionRules {
{0 -async sopts 0}
{* -myaddr sopts 1}
{0 -myport sopts 1}
{* -type sopts 1}
{* -cadir iopts 1}
{* -cafile iopts 1}
{* -cert iopts 1}
{* -certfile iopts 1}
{* -cipher iopts 1}
{* -command iopts 1}
{* -dhparams iopts 1}
{* -key iopts 1}
{* -keyfile iopts 1}
{* -password iopts 1}
{* -request iopts 1}
{* -require iopts 1}
{* -autoservername discardOpts 1}
{* -servername iopts 1}
{* -ssl2 iopts 1}
{* -ssl3 iopts 1}
{* -tls1 iopts 1}
{* -tls1.1 iopts 1}
{* -tls1.2 iopts 1}
{* -tls1.3 iopts 1}
}
# tls::socket and tls::init options as a humane readable string
variable socketOptionsNoServer
variable socketOptionsServer
# Internal [switch] body to validate options
variable socketOptionsSwitchBody
}
proc tls::_initsocketoptions {} {
variable socketOptionRules
variable socketOptionsNoServer
variable socketOptionsServer
variable socketOptionsSwitchBody
# Do not re-run if we have already been initialized
if {[info exists socketOptionsSwitchBody]} {
return
}
# Create several structures from our list of options
## 1. options: a text representation of the valid options for the current
## server type
## 2. argSwitchBody: Switch body for processing arguments
set options(0) [list]
set options(1) [list]
set argSwitchBody [list]
foreach optionRule $socketOptionRules {
set ruleServer [lindex $optionRule 0]
set ruleOption [lindex $optionRule 1]
set ruleVarToUpdate [lindex $optionRule 2]
set ruleVarArgsToConsume [lindex $optionRule 3]
foreach server [list 0 1] {
if {![string match $ruleServer $server]} {
continue
}
lappend options($server) $ruleOption
}
switch -- $ruleVarArgsToConsume {
0 {
set argToExecute {
lappend @VAR@ $arg
set argsArray($arg) true
}
}
1 {
set argToExecute {
incr idx
if {$idx >= [llength $args]} {
return -code error "\"$arg\" option must be followed by value"
}
set argValue [lindex $args $idx]
lappend @VAR@ $arg $argValue
set argsArray($arg) $argValue
}
}
default {
return -code error "Internal argument construction error"
}
}
lappend argSwitchBody $ruleServer,$ruleOption [string map [list @VAR@ $ruleVarToUpdate] $argToExecute]
}
# Add in the final options
lappend argSwitchBody {*,-*} {return -code error "bad option \"$arg\": must be one of $options"}
lappend argSwitchBody default break
# Set the final variables
set socketOptionsNoServer [join $options(0) {, }]
set socketOptionsServer [join $options(1) {, }]
set socketOptionsSwitchBody $argSwitchBody
}
proc tls::initlib {dir dll} {
# Package index cd's into the package directory for loading.
# Irrelevant to unixoids, but for Windows this enables the OS to find
# the dependent DLL's in the CWD, where they may be.
set cwd [pwd]
catch {cd $dir}
if {[string equal $::tcl_platform(platform) "windows"] &&
![string equal [lindex [file system $dir] 0] "native"]} {
# If it is a wrapped executable running on windows, the openssl
# dlls must be copied out of the virtual filesystem to the disk
# where Windows will find them when resolving the dependency in
# the tls dll. We choose to make them siblings of the executable.
package require starkit
set dst [file nativename [file dirname $starkit::topdir]]
foreach sdll [glob -nocomplain -directory $dir -tails *eay32.dll] {
catch {file delete -force $dst/$sdll}
catch {file copy -force $dir/$sdll $dst/$sdll}
}
}
set res [catch {uplevel #0 [list load [file join [pwd] $dll]]} err]
catch {cd $cwd}
if {$res} {
namespace eval [namespace parent] {namespace delete tls}
return -code $res $err
}
rename tls::initlib {}
}
#
# Backwards compatibility, also used to set the default
# context options
#
proc tls::init {args} {
variable defaults
variable socketOptionsNoServer
variable socketOptionsServer
variable socketOptionsSwitchBody
tls::_initsocketoptions
# Technically a third option should be used here: Options that are valid
# only a both servers and non-servers
set server -1
set options $socketOptionsServer
# Validate arguments passed
set initialArgs $args
set argc [llength $args]
array set argsArray [list]
for {set idx 0} {$idx < $argc} {incr idx} {
set arg [lindex $args $idx]
switch -glob -- $server,$arg $socketOptionsSwitchBody
}
set defaults $initialArgs
}
#
# Helper function - behaves exactly as the native socket command.
#
proc tls::socket {args} {
variable socketCmd
variable defaults
variable socketOptionsNoServer
variable socketOptionsServer
variable socketOptionsSwitchBody
tls::_initsocketoptions
set idx [lsearch $args -server]
if {$idx != -1} {
set server 1
set callback [lindex $args [expr {$idx+1}]]
set args [lreplace $args $idx [expr {$idx+1}]]
set usage "wrong # args: should be \"tls::socket -server command ?options? port\""
set options $socketOptionsServer
} else {
set server 0
set usage "wrong # args: should be \"tls::socket ?options? host port\""
set options $socketOptionsNoServer
}
# Combine defaults with current options
set args [concat $defaults $args]
set argc [llength $args]
set sopts {}
set iopts [list -server $server]
array set argsArray [list]
for {set idx 0} {$idx < $argc} {incr idx} {
set arg [lindex $args $idx]
switch -glob -- $server,$arg $socketOptionsSwitchBody
}
if {$server} {
if {($idx + 1) != $argc} {
return -code error $usage
}
set uid [incr ::tls::srvuid]
set port [lindex $args [expr {$argc-1}]]
lappend sopts $port
#set sopts [linsert $sopts 0 -server $callback]
set sopts [linsert $sopts 0 -server [list tls::_accept $iopts $callback]]
#set sopts [linsert $sopts 0 -server [list tls::_accept $uid $callback]]
} else {
if {($idx + 2) != $argc} {
return -code error $usage
}
set host [lindex $args [expr {$argc-2}]]
set port [lindex $args [expr {$argc-1}]]
# If an "-autoservername" option is found, honor it
if {[info exists argsArray(-autoservername)] && $argsArray(-autoservername)} {
if {![info exists argsArray(-servername)]} {
set argsArray(-servername) $host
lappend iopts -servername $host
}
}
lappend sopts $host $port
}
#
# Create TCP/IP socket
#
set chan [eval $socketCmd $sopts]
if {!$server && [catch {
#
# Push SSL layer onto socket
#
eval [list tls::import] $chan $iopts
} err]} {
set info ${::errorInfo}
catch {close $chan}
return -code error -errorinfo $info $err
}
return $chan
}
# tls::_accept --
#
# This is the actual accept that TLS sockets use, which then calls
# the callback registered by tls::socket.
#
# Arguments:
# iopts tls::import opts
# callback server callback to invoke
# chan socket channel to accept/deny
# ipaddr calling IP address
# port calling port
#
# Results:
# Returns an error if the callback throws one.
#
proc tls::_accept { iopts callback chan ipaddr port } {
log 2 [list tls::_accept $iopts $callback $chan $ipaddr $port]
set chan [eval [list tls::import $chan] $iopts]
lappend callback $chan $ipaddr $port
if {[catch {
uplevel #0 $callback
} err]} {
log 1 "tls::_accept error: ${::errorInfo}"
close $chan
error $err $::errorInfo $::errorCode
} else {
log 2 "tls::_accept - called \"$callback\" succeeded"
}
}
#
# Sample callback for hooking: -
#
# error
# verify
# info
#
proc tls::callback {option args} {
variable debug
#log 2 [concat $option $args]
switch -- $option {
"error" {
foreach {chan msg} $args break
log 0 "TLS/$chan: error: $msg"
}
"verify" {
# poor man's lassign
foreach {chan depth cert rc err} $args break
array set c $cert
if {$rc != "1"} {
log 1 "TLS/$chan: verify/$depth: Bad Cert: $err (rc = $rc)"
} else {
log 2 "TLS/$chan: verify/$depth: $c(subject)"
}
if {$debug > 0} {
return 1; # FORCE OK
} else {
return $rc
}
}
"info" {
# poor man's lassign
foreach {chan major minor state msg} $args break
if {$msg != ""} {
append state ": $msg"
}
# For tracing
upvar #0 tls::$chan cb
set cb($major) $minor
log 2 "TLS/$chan: $major/$minor: $state"
}
default {
return -code error "bad option \"$option\":\
must be one of error, info, or verify"
}
}
}
proc tls::xhandshake {chan} {
upvar #0 tls::$chan cb
if {[info exists cb(handshake)] && \
$cb(handshake) == "done"} {
return 1
}
while {1} {
vwait tls::${chan}(handshake)
if {![info exists cb(handshake)]} {
return 0
}
if {$cb(handshake) == "done"} {
return 1
}
}
}
proc tls::password {} {
log 0 "TLS/Password: did you forget to set your passwd!"
# Return the worlds best kept secret password.
return "secret"
}
proc tls::log {level msg} {
variable debug
variable logcmd
if {$level > $debug || $logcmd == ""} {
return
}
set cmd $logcmd
lappend cmd $msg
uplevel #0 $cmd
}
Loading…
Cancel
Save