@poe-platform/safe-bash 0.1.630 → 0.1.631
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/office-package/compression.d.ts +19 -0
- package/dist/office-package/compression.js +186 -0
- package/dist/office-package/runtime.d.ts +11 -0
- package/dist/office-package/runtime.js +28 -0
- package/dist/safe-bash/chunks/{bz2-B2RXT4EX.js → bz2-ACU4EOVE.js} +2 -2
- package/dist/safe-bash/chunks/chunk-2E4KBFO6.js +239 -0
- package/dist/safe-bash/chunks/chunk-2E4KBFO6.js.map +7 -0
- package/dist/safe-bash/chunks/chunk-3E5M2WEL.js +33 -0
- package/dist/safe-bash/chunks/chunk-3E5M2WEL.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-KQ6J7JHQ.js → chunk-3LD67SER.js} +12 -10
- package/dist/safe-bash/chunks/{chunk-KQ6J7JHQ.js.map → chunk-3LD67SER.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-6LSSEOG7.js → chunk-6HNK7Q66.js} +11 -7
- package/dist/safe-bash/chunks/{chunk-6LSSEOG7.js.map → chunk-6HNK7Q66.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-AD7UAP5B.js → chunk-7EEWZY5S.js} +10 -8
- package/dist/safe-bash/chunks/{chunk-AD7UAP5B.js.map → chunk-7EEWZY5S.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-RNKTWFEM.js → chunk-CDBQXRWL.js} +3 -3
- package/dist/safe-bash/chunks/{chunk-UYX72YHY.js → chunk-CHHAB5SS.js} +10 -74
- package/dist/safe-bash/chunks/{chunk-UYX72YHY.js.map → chunk-CHHAB5SS.js.map} +3 -3
- package/dist/safe-bash/chunks/{chunk-6YSBXTTJ.js → chunk-EI4X5ETX.js} +12 -10
- package/dist/safe-bash/chunks/{chunk-6YSBXTTJ.js.map → chunk-EI4X5ETX.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-KZ7QCYNT.js → chunk-GIBLUW2U.js} +13 -9
- package/dist/safe-bash/chunks/{chunk-KZ7QCYNT.js.map → chunk-GIBLUW2U.js.map} +1 -1
- package/dist/safe-bash/chunks/chunk-GPV7ZFYS.js +1528 -0
- package/dist/safe-bash/chunks/chunk-GPV7ZFYS.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-N5CYTGBN.js → chunk-H3E67KTZ.js} +13 -9
- package/dist/safe-bash/chunks/{chunk-N5CYTGBN.js.map → chunk-H3E67KTZ.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-AFH57PPL.js → chunk-IEKESMO4.js} +3 -3
- package/dist/safe-bash/chunks/{chunk-DPJWEGIX.js → chunk-JLBIUYT3.js} +2 -2
- package/dist/safe-bash/chunks/chunk-JZDO5MC3.js +77 -0
- package/dist/safe-bash/chunks/chunk-JZDO5MC3.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-GFZKQIPL.js → chunk-LA6BX562.js} +8 -6
- package/dist/safe-bash/chunks/{chunk-GFZKQIPL.js.map → chunk-LA6BX562.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-PVJJFCGA.js → chunk-M3GPCYSP.js} +2 -2
- package/dist/safe-bash/chunks/{chunk-FWDMJTQO.js → chunk-NDCZ3UBP.js} +13 -9
- package/dist/safe-bash/chunks/{chunk-FWDMJTQO.js.map → chunk-NDCZ3UBP.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-2KL6W6NB.js → chunk-O2EVBRKF.js} +1 -2
- package/dist/safe-bash/chunks/chunk-OFT77XIB.js +389 -0
- package/dist/safe-bash/chunks/chunk-OFT77XIB.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-PCLOK32J.js → chunk-PC6BEIAF.js} +13 -9
- package/dist/safe-bash/chunks/{chunk-PCLOK32J.js.map → chunk-PC6BEIAF.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-3M5F7LD5.js → chunk-QWKP7ZU7.js} +13 -9
- package/dist/safe-bash/chunks/{chunk-3M5F7LD5.js.map → chunk-QWKP7ZU7.js.map} +1 -1
- package/dist/safe-bash/chunks/chunk-THXJ6CLF.js +53 -0
- package/dist/safe-bash/chunks/chunk-THXJ6CLF.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-K7W6Q6FN.js → chunk-V73A742N.js} +58 -16
- package/dist/safe-bash/chunks/chunk-V73A742N.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-4BYUV5PA.js → chunk-VN3ZGME3.js} +12 -8
- package/dist/safe-bash/chunks/{chunk-4BYUV5PA.js.map → chunk-VN3ZGME3.js.map} +1 -1
- package/dist/safe-bash/chunks/{chunk-3BZ5NQ73.js → chunk-WECHUK3Y.js} +369 -36
- package/dist/safe-bash/chunks/chunk-WECHUK3Y.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-X55AMTNM.js → chunk-WIOEUR7D.js} +14 -10
- package/dist/safe-bash/chunks/{chunk-X55AMTNM.js.map → chunk-WIOEUR7D.js.map} +1 -1
- package/dist/safe-bash/chunks/chunk-WPIOO4NX.js +443 -0
- package/dist/safe-bash/chunks/chunk-WPIOO4NX.js.map +7 -0
- package/dist/safe-bash/chunks/{chunk-C7QNJ3D4.js → chunk-WYTI64IU.js} +2 -2
- package/dist/safe-bash/chunks/{xz-AVXFDXW4.js → xz-U4QDTPZL.js} +2 -2
- package/dist/safe-bash/chunks/{zstd-R6RPHJYO.js → zstd-OG5RPYXO.js} +2 -2
- package/dist/safe-bash/commands/archive/zip/crc.d.ts +2 -0
- package/dist/safe-bash/commands/archive/zip/crc.d.ts.map +1 -0
- package/dist/safe-bash/commands/archive/zip/crc.js +7 -0
- package/dist/safe-bash/commands/archive/zip/crc.js.map +1 -0
- package/dist/safe-bash/commands/archive/zip/crypto.d.ts +4 -0
- package/dist/safe-bash/commands/archive/zip/crypto.d.ts.map +1 -0
- package/dist/safe-bash/commands/archive/zip/crypto.js +34 -0
- package/dist/safe-bash/commands/archive/zip/crypto.js.map +1 -0
- package/dist/safe-bash/commands/archive/zip/help.d.ts +3 -0
- package/dist/safe-bash/commands/archive/zip/help.d.ts.map +1 -0
- package/dist/safe-bash/commands/archive/zip/help.js +87 -0
- package/dist/safe-bash/commands/archive/zip/help.js.map +1 -0
- package/dist/safe-bash/commands/archive/zip/move.d.ts +12 -0
- package/dist/safe-bash/commands/archive/zip/move.d.ts.map +1 -0
- package/dist/safe-bash/commands/archive/zip/move.js +60 -0
- package/dist/safe-bash/commands/archive/zip/move.js.map +1 -0
- package/dist/safe-bash/commands/archive/zip/options.js +5 -5
- package/dist/safe-bash/commands/archive/zip/options.js.map +1 -1
- package/dist/safe-bash/commands/archive/zip-format.d.ts +1 -1
- package/dist/safe-bash/commands/archive/zip-format.d.ts.map +1 -1
- package/dist/safe-bash/commands/archive/zip-format.js +14 -17
- package/dist/safe-bash/commands/archive/zip-format.js.map +1 -1
- package/dist/safe-bash/commands/archive/zip.d.ts.map +1 -1
- package/dist/safe-bash/commands/archive/zip.js +45 -15
- package/dist/safe-bash/commands/archive/zip.js.map +1 -1
- package/dist/safe-bash/commands/bytes/compression/bounded-codec.d.ts +2 -0
- package/dist/safe-bash/commands/bytes/compression/bounded-codec.d.ts.map +1 -1
- package/dist/safe-bash/commands/bytes/compression/bounded-codec.js +4 -0
- package/dist/safe-bash/commands/bytes/compression/bounded-codec.js.map +1 -1
- package/dist/safe-bash/commands/bytes/compression/codec.d.ts +4 -24
- package/dist/safe-bash/commands/bytes/compression/codec.d.ts.map +1 -1
- package/dist/safe-bash/commands/bytes/compression/codec.js +12 -168
- package/dist/safe-bash/commands/bytes/compression/codec.js.map +1 -1
- package/dist/safe-bash/commands/cmp.js +1 -1
- package/dist/safe-bash/commands/cmp.js.map +1 -1
- package/dist/safe-bash/commands/csplit/index.browser.js +10 -7
- package/dist/safe-bash/commands/docx/index.browser.js +54 -0
- package/dist/safe-bash/commands/docx/index.browser.js.map +7 -0
- package/dist/safe-bash/commands/docx/index.d.ts +25 -0
- package/dist/safe-bash/commands/docx/index.d.ts.map +1 -0
- package/dist/safe-bash/commands/docx/index.js +42 -0
- package/dist/safe-bash/commands/docx/index.js.map +1 -0
- package/dist/safe-bash/commands/docx/io.d.ts +15 -0
- package/dist/safe-bash/commands/docx/io.d.ts.map +1 -0
- package/dist/safe-bash/commands/docx/io.js +16 -0
- package/dist/safe-bash/commands/docx/io.js.map +1 -0
- package/dist/safe-bash/commands/factor/index.browser.js +7 -5
- package/dist/safe-bash/commands/filesystem.d.ts.map +1 -1
- package/dist/safe-bash/commands/filesystem.js +9 -1
- package/dist/safe-bash/commands/filesystem.js.map +1 -1
- package/dist/safe-bash/commands/getopt/index.browser.js +7 -5
- package/dist/safe-bash/commands/hexdump/index.browser.js +8 -6
- package/dist/safe-bash/commands/iconv/index.browser.js +8 -6
- package/dist/safe-bash/commands/line-endings/index.browser.js +10 -7
- package/dist/safe-bash/commands/llm/index.browser.js +10 -8
- package/dist/safe-bash/commands/llm/providers/index.browser.js +3 -3
- package/dist/safe-bash/commands/network/index.browser.js +11 -8
- package/dist/safe-bash/commands/node/index.browser.js +8 -6
- package/dist/safe-bash/commands/op/index.browser.js +3 -3
- package/dist/safe-bash/commands/op/index.js +0 -1
- package/dist/safe-bash/commands/op/index.js.map +2 -2
- package/dist/safe-bash/commands/pptx/index.d.ts +29 -0
- package/dist/safe-bash/commands/pptx/index.d.ts.map +1 -0
- package/dist/safe-bash/commands/pptx/index.js +153 -0
- package/dist/safe-bash/commands/pptx/index.js.map +1 -0
- package/dist/safe-bash/commands/pr/index.browser.js +8 -6
- package/dist/safe-bash/commands/predicates.js +1 -1
- package/dist/safe-bash/commands/python/execution.d.ts +3 -0
- package/dist/safe-bash/commands/python/execution.d.ts.map +1 -0
- package/dist/safe-bash/commands/python/execution.js +193 -0
- package/dist/safe-bash/commands/python/execution.js.map +1 -0
- package/dist/safe-bash/commands/python/index.browser.js +21 -0
- package/dist/safe-bash/commands/python/index.browser.js.map +7 -0
- package/dist/safe-bash/commands/python/index.d.ts +49 -0
- package/dist/safe-bash/commands/python/index.d.ts.map +1 -0
- package/dist/safe-bash/commands/python/index.js +317 -0
- package/dist/safe-bash/commands/python/index.js.map +1 -0
- package/dist/safe-bash/commands/python/installation.d.ts +9 -0
- package/dist/safe-bash/commands/python/installation.d.ts.map +1 -0
- package/dist/safe-bash/commands/python/installation.js +80 -0
- package/dist/safe-bash/commands/python/installation.js.map +1 -0
- package/dist/safe-bash/commands/python/invocation.d.ts +10 -0
- package/dist/safe-bash/commands/python/invocation.d.ts.map +1 -0
- package/dist/safe-bash/commands/python/invocation.js +81 -0
- package/dist/safe-bash/commands/python/invocation.js.map +1 -0
- package/dist/safe-bash/commands/python/node.d.ts +12 -0
- package/dist/safe-bash/commands/python/node.d.ts.map +1 -0
- package/dist/safe-bash/commands/python/node.js +33 -0
- package/dist/safe-bash/commands/python/node.js.map +1 -0
- package/dist/safe-bash/commands/python/provisioning-runtime.d.ts +5 -0
- package/dist/safe-bash/commands/python/provisioning-runtime.d.ts.map +1 -0
- package/dist/safe-bash/commands/python/provisioning-runtime.js +179 -0
- package/dist/safe-bash/commands/python/provisioning-runtime.js.map +1 -0
- package/dist/safe-bash/commands/python/provisioning.d.ts +49 -0
- package/dist/safe-bash/commands/python/provisioning.d.ts.map +1 -0
- package/dist/safe-bash/commands/python/provisioning.js +320 -0
- package/dist/safe-bash/commands/python/provisioning.js.map +1 -0
- package/dist/safe-bash/commands/python/reply.d.ts +3 -0
- package/dist/safe-bash/commands/python/reply.d.ts.map +1 -0
- package/dist/safe-bash/commands/python/reply.js +103 -0
- package/dist/safe-bash/commands/python/reply.js.map +1 -0
- package/dist/safe-bash/commands/python/worker.browser.js +736 -0
- package/dist/safe-bash/commands/python/worker.browser.js.map +7 -0
- package/dist/safe-bash/commands/python/worker.d.ts +47 -0
- package/dist/safe-bash/commands/python/worker.d.ts.map +1 -0
- package/dist/safe-bash/commands/python/worker.js +373 -0
- package/dist/safe-bash/commands/python/worker.js.map +1 -0
- package/dist/safe-bash/commands/tsort/index.browser.js +8 -6
- package/dist/safe-bash/commands/xml/index.browser.js +9 -7
- package/dist/safe-bash/commands/yq/index.browser.js +10 -8
- package/dist/safe-bash/commands/yq/index.d.ts.map +1 -1
- package/dist/safe-bash/commands/yq/index.js +32 -4
- package/dist/safe-bash/commands/yq/index.js.map +1 -1
- package/dist/safe-bash/commands/yq/parser.d.ts.map +1 -1
- package/dist/safe-bash/commands/yq/parser.js +21 -5
- package/dist/safe-bash/commands/yq/parser.js.map +1 -1
- package/dist/safe-bash/contracts/command.d.ts +2 -0
- package/dist/safe-bash/contracts/command.d.ts.map +1 -1
- package/dist/safe-bash/contracts/command.js +6 -2
- package/dist/safe-bash/contracts/command.js.map +1 -1
- package/dist/safe-bash/contracts/filesystem-descriptor.d.ts +13 -0
- package/dist/safe-bash/contracts/filesystem-descriptor.d.ts.map +1 -0
- package/dist/safe-bash/contracts/filesystem-descriptor.js +189 -0
- package/dist/safe-bash/contracts/filesystem-descriptor.js.map +1 -0
- package/dist/safe-bash/contracts/filesystem-output-budget.d.ts +15 -0
- package/dist/safe-bash/contracts/filesystem-output-budget.d.ts.map +1 -0
- package/dist/safe-bash/contracts/filesystem-output-budget.js +69 -0
- package/dist/safe-bash/contracts/filesystem-output-budget.js.map +1 -0
- package/dist/safe-bash/contracts/filesystem-output.d.ts +11 -5
- package/dist/safe-bash/contracts/filesystem-output.d.ts.map +1 -1
- package/dist/safe-bash/contracts/filesystem-output.js +229 -20
- package/dist/safe-bash/contracts/filesystem-output.js.map +1 -1
- package/dist/safe-bash/contracts/io.d.ts +29 -0
- package/dist/safe-bash/contracts/io.d.ts.map +1 -1
- package/dist/safe-bash/contracts/io.js +317 -37
- package/dist/safe-bash/contracts/io.js.map +1 -1
- package/dist/safe-bash/core.browser.js +5502 -2210
- package/dist/safe-bash/core.browser.js.map +4 -4
- package/dist/safe-bash/core.d.ts +1 -0
- package/dist/safe-bash/core.d.ts.map +1 -1
- package/dist/safe-bash/core.js +1 -0
- package/dist/safe-bash/core.js.map +1 -1
- package/dist/safe-bash/opt-in/commands/cmp/compare.js +95 -0
- package/dist/safe-bash/opt-in/commands/cmp/index.d.ts +6 -0
- package/dist/safe-bash/opt-in/commands/cmp/index.js +99 -0
- package/dist/safe-bash/opt-in/commands/cmp/io.js +218 -0
- package/dist/safe-bash/opt-in/commands/cmp/options.d.ts +24 -0
- package/dist/safe-bash/opt-in/commands/cmp/options.js +195 -0
- package/dist/safe-bash/opt-in/commands/dd/conversions.js +152 -0
- package/dist/safe-bash/opt-in/commands/dd/index.d.ts +18 -0
- package/dist/safe-bash/opt-in/commands/dd/index.js +561 -0
- package/dist/safe-bash/opt-in/commands/dd/io.d.ts +31 -0
- package/dist/safe-bash/opt-in/commands/dd/io.js +244 -0
- package/dist/safe-bash/opt-in/commands/dd/options.js +184 -0
- package/dist/safe-bash/opt-in/commands/dd/report.js +69 -0
- package/dist/safe-bash/opt-in/commands/install/arguments.js +224 -0
- package/dist/safe-bash/opt-in/commands/install/index.d.ts +6 -0
- package/dist/safe-bash/opt-in/commands/install/index.js +678 -0
- package/dist/safe-bash/opt-in/commands/install/mode.js +71 -0
- package/dist/safe-bash/opt-in/commands/install/options.d.ts +36 -0
- package/dist/safe-bash/opt-in/commands/install/options.js +27 -0
- package/dist/safe-bash/opt-in/commands/shuf/args.js +179 -0
- package/dist/safe-bash/opt-in/commands/shuf/index.d.ts +6 -0
- package/dist/safe-bash/opt-in/commands/shuf/index.js +20 -0
- package/dist/safe-bash/opt-in/commands/shuf/input.js +93 -0
- package/dist/safe-bash/opt-in/commands/shuf/options.d.ts +9 -0
- package/dist/safe-bash/opt-in/commands/shuf/options.js +8 -0
- package/dist/safe-bash/opt-in/commands/shuf/random.js +114 -0
- package/dist/safe-bash/opt-in/commands/shuf/shuf.d.ts +3 -0
- package/dist/safe-bash/opt-in/commands/shuf/shuf.js +230 -0
- package/dist/safe-bash/opt-in/commands/shuf/usage.js +24 -0
- package/dist/safe-bash/opt-in/commands/truncate/arguments.js +186 -0
- package/dist/safe-bash/opt-in/commands/truncate/index.d.ts +9 -0
- package/dist/safe-bash/opt-in/commands/truncate/index.js +244 -0
- package/dist/safe-bash/opt-in/commands/yes/index.d.ts +11 -0
- package/dist/safe-bash/opt-in/commands/yes/index.js +167 -0
- package/dist/safe-bash/opt-in/commands/yq/arguments.js +177 -0
- package/dist/safe-bash/opt-in/commands/yq/evaluate.js +564 -0
- package/dist/safe-bash/opt-in/commands/yq/expression.js +183 -0
- package/dist/safe-bash/opt-in/commands/yq/inplace.js +65 -0
- package/dist/safe-bash/opt-in/commands/yq/mike.d.ts +9 -0
- package/dist/safe-bash/opt-in/commands/yq/mike.js +192 -0
- package/dist/safe-bash/opt-in/commands/yq/native-encoder.js +165 -0
- package/dist/safe-bash/opt-in/commands/yq/native-work.d.ts +44 -0
- package/dist/safe-bash/opt-in/commands/yq/native-work.js +188 -0
- package/dist/safe-bash/opt-in/commands/yq/nodes.js +621 -0
- package/dist/safe-bash/opt-in/entrypoints/arrays.d.ts +1 -0
- package/dist/safe-bash/opt-in/entrypoints/arrays.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/cmp.d.ts +2 -0
- package/dist/safe-bash/opt-in/entrypoints/cmp.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/dd.d.ts +2 -0
- package/dist/safe-bash/opt-in/entrypoints/dd.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/devices.d.ts +2 -0
- package/dist/safe-bash/opt-in/entrypoints/devices.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/install.d.ts +2 -0
- package/dist/safe-bash/opt-in/entrypoints/install.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/jobs.d.ts +1 -0
- package/dist/safe-bash/opt-in/entrypoints/jobs.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/mapfile.d.ts +1 -0
- package/dist/safe-bash/opt-in/entrypoints/mapfile.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/read.d.ts +2 -0
- package/dist/safe-bash/opt-in/entrypoints/read.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/shuf.d.ts +2 -0
- package/dist/safe-bash/opt-in/entrypoints/shuf.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/trap.d.ts +2 -0
- package/dist/safe-bash/opt-in/entrypoints/trap.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/truncate.d.ts +2 -0
- package/dist/safe-bash/opt-in/entrypoints/truncate.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/yes.d.ts +2 -0
- package/dist/safe-bash/opt-in/entrypoints/yes.js +1 -0
- package/dist/safe-bash/opt-in/entrypoints/yq.d.ts +3 -0
- package/dist/safe-bash/opt-in/entrypoints/yq.js +1 -0
- package/dist/safe-bash/opt-in/fs/devices/index.d.ts +8 -0
- package/dist/safe-bash/opt-in/fs/devices/index.js +292 -0
- package/dist/safe-bash/opt-in/optional.d.ts +28 -0
- package/dist/safe-bash/opt-in/optional.js +14 -0
- package/dist/safe-bash/opt-in/shell/extensions/arrays/index.d.ts +2 -0
- package/dist/safe-bash/opt-in/shell/extensions/arrays/index.js +4 -0
- package/dist/safe-bash/opt-in/shell/extensions/jobs/index.d.ts +2 -0
- package/dist/safe-bash/opt-in/shell/extensions/jobs/index.js +291 -0
- package/dist/safe-bash/opt-in/shell/extensions/jobs/state.js +374 -0
- package/dist/safe-bash/opt-in/shell/extensions/mapfile/index.d.ts +24 -0
- package/dist/safe-bash/opt-in/shell/extensions/mapfile/index.js +338 -0
- package/dist/safe-bash/opt-in/shell/extensions/read/index.d.ts +5 -0
- package/dist/safe-bash/opt-in/shell/extensions/read/index.js +351 -0
- package/dist/safe-bash/opt-in/shell/extensions/trap/index.d.ts +9 -0
- package/dist/safe-bash/opt-in/shell/extensions/trap/index.js +210 -0
- package/dist/safe-bash/optional-host.d.ts +5 -0
- package/dist/safe-bash/optional-host.d.ts.map +1 -0
- package/dist/safe-bash/optional-host.js +3 -0
- package/dist/safe-bash/optional-host.js.map +1 -0
- package/dist/safe-bash/shell/arrays/bindings.d.ts +15 -5
- package/dist/safe-bash/shell/arrays/bindings.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/bindings.js +131 -23
- package/dist/safe-bash/shell/arrays/bindings.js.map +1 -1
- package/dist/safe-bash/shell/arrays/ledger.d.ts +1 -0
- package/dist/safe-bash/shell/arrays/ledger.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/ledger.js +19 -0
- package/dist/safe-bash/shell/arrays/ledger.js.map +1 -1
- package/dist/safe-bash/shell/arrays/state.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/state.js +3 -1
- package/dist/safe-bash/shell/arrays/state.js.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.d.ts +4 -1
- package/dist/safe-bash/shell/arrays/syntax.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.js +3 -3
- package/dist/safe-bash/shell/arrays/syntax.js.map +1 -1
- package/dist/safe-bash/shell/conditional.js +1 -1
- package/dist/safe-bash/shell/descriptors.d.ts +31 -0
- package/dist/safe-bash/shell/descriptors.d.ts.map +1 -0
- package/dist/safe-bash/shell/descriptors.js +234 -0
- package/dist/safe-bash/shell/descriptors.js.map +1 -0
- package/dist/safe-bash/shell/diagnostic-name-ranges.d.ts +2 -0
- package/dist/safe-bash/shell/diagnostic-name-ranges.d.ts.map +1 -0
- package/dist/safe-bash/shell/diagnostic-name-ranges.js +717 -0
- package/dist/safe-bash/shell/diagnostic-name-ranges.js.map +1 -0
- package/dist/safe-bash/shell/diagnostic-name.d.ts +3 -0
- package/dist/safe-bash/shell/diagnostic-name.d.ts.map +1 -0
- package/dist/safe-bash/shell/diagnostic-name.js +89 -0
- package/dist/safe-bash/shell/diagnostic-name.js.map +1 -0
- package/dist/safe-bash/shell/display.js +1 -1
- package/dist/safe-bash/shell/display.js.map +1 -1
- package/dist/safe-bash/shell/extensions.d.ts +188 -0
- package/dist/safe-bash/shell/extensions.d.ts.map +1 -0
- package/dist/safe-bash/shell/extensions.js +141 -0
- package/dist/safe-bash/shell/extensions.js.map +1 -0
- package/dist/safe-bash/shell/input.d.ts +66 -14
- package/dist/safe-bash/shell/input.d.ts.map +1 -1
- package/dist/safe-bash/shell/input.js +947 -173
- package/dist/safe-bash/shell/input.js.map +1 -1
- package/dist/safe-bash/shell/parser.d.ts +38 -6
- package/dist/safe-bash/shell/parser.d.ts.map +1 -1
- package/dist/safe-bash/shell/parser.js +339 -52
- package/dist/safe-bash/shell/parser.js.map +1 -1
- package/dist/safe-bash/shell/runtime.d.ts +92 -20
- package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
- package/dist/safe-bash/shell/runtime.js +2547 -689
- package/dist/safe-bash/shell/runtime.js.map +1 -1
- package/dist/safe-bash/shell/shell.d.ts.map +1 -1
- package/dist/safe-bash/shell/shell.js +52 -21
- package/dist/safe-bash/shell/shell.js.map +1 -1
- package/dist/safe-bash/shell/types.d.ts +4 -0
- package/dist/safe-bash/shell/types.d.ts.map +1 -1
- package/dist/safe-bash/shell/types.js.map +1 -1
- package/package.json +92 -4
- package/dist/safe-bash/chunks/chunk-3BZ5NQ73.js.map +0 -7
- package/dist/safe-bash/chunks/chunk-4DBWBEU7.js +0 -194
- package/dist/safe-bash/chunks/chunk-4DBWBEU7.js.map +0 -7
- package/dist/safe-bash/chunks/chunk-CXMIIXJA.js +0 -178
- package/dist/safe-bash/chunks/chunk-CXMIIXJA.js.map +0 -7
- package/dist/safe-bash/chunks/chunk-K7W6Q6FN.js.map +0 -7
- /package/dist/safe-bash/chunks/{bz2-B2RXT4EX.js.map → bz2-ACU4EOVE.js.map} +0 -0
- /package/dist/safe-bash/chunks/{chunk-RNKTWFEM.js.map → chunk-CDBQXRWL.js.map} +0 -0
- /package/dist/safe-bash/chunks/{chunk-AFH57PPL.js.map → chunk-IEKESMO4.js.map} +0 -0
- /package/dist/safe-bash/chunks/{chunk-DPJWEGIX.js.map → chunk-JLBIUYT3.js.map} +0 -0
- /package/dist/safe-bash/chunks/{chunk-PVJJFCGA.js.map → chunk-M3GPCYSP.js.map} +0 -0
- /package/dist/safe-bash/chunks/{chunk-2KL6W6NB.js.map → chunk-O2EVBRKF.js.map} +0 -0
- /package/dist/safe-bash/chunks/{chunk-C7QNJ3D4.js.map → chunk-WYTI64IU.js.map} +0 -0
- /package/dist/safe-bash/chunks/{xz-AVXFDXW4.js.map → xz-U4QDTPZL.js.map} +0 -0
- /package/dist/safe-bash/chunks/{zstd-R6RPHJYO.js.map → zstd-OG5RPYXO.js.map} +0 -0
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { BackgroundJobs, BackgroundResources, backgroundResources, backgroundPrune, backgroundDestination, forwardBackgroundDestination } from "./background-jobs.js";
|
|
2
1
|
import { PublicDiagnostic, publicDiagnosticMessage } from "../diagnostics.js";
|
|
3
2
|
import { writeDiagnostic } from "../escaping.js";
|
|
4
3
|
import { cancelTurn, monotonicNow, registerYieldCheckpoint, scheduleTurn, yieldTurn } from "../contracts/yield.js";
|
|
5
|
-
import { ACCESS_MODES, FsError, composeMiddleware, createBytePipe, pipeBytes, resolvePath,
|
|
4
|
+
import { ACCESS_MODES, FsError, composeMiddleware, createBytePipe, pipeBytes, resolvePath, validateExitCode, writeText, } from "../contracts/index.js";
|
|
6
5
|
import { concatShellValues, shellValueByteLength, shellValueBytes, shellValueFromBytes, shellValueText } from "../contracts/value.js";
|
|
7
6
|
import { createCommandArguments, getCommandArguments } from "../contracts/command.js";
|
|
8
7
|
import { ValueArena } from "./value-state.js";
|
|
9
|
-
import { parseArraySubscript, compoundEntryWords, HereDocumentSyntaxError, hereDocumentWords, parseShellInputUnit, parseShellUnit } from "./parser.js";
|
|
10
|
-
import { SourceLineIndex } from "./source-line-index.js";
|
|
8
|
+
import { parseArraySubscript, compoundEntryWords, HereDocumentSyntaxError, functionReprintedLines, hereDocumentWords, parseCompoundArrayValue, parseShellInputUnit, parseShellUnit } from "./parser.js";
|
|
11
9
|
import { ShellLimitError, ShellSyntaxError } from "./types.js";
|
|
10
|
+
import { forkExtensions } from "./extensions.js";
|
|
11
|
+
import { prepareBytesInput, prepareFileInput, ShellInput } from "./input.js";
|
|
12
|
+
import { observeDescriptor, PipeDescriptorFrame, pipeObservation } from "./descriptors.js";
|
|
13
|
+
import { SourceLineIndex } from "./source-line-index.js";
|
|
12
14
|
import { scopeFileSystem } from "@poe-platform/safe-fs/core";
|
|
13
|
-
import { fileInput, ShellInput } from "./input.js";
|
|
14
15
|
import { evaluateArithmetic, prepareArithmetic } from "./arithmetic.js";
|
|
15
16
|
import { defaultMaxParseUnits, ParseBudget } from "./parse-budget.js";
|
|
16
17
|
import { BraceExpansionFailure, expandBraces } from "./brace-expansion.js";
|
|
@@ -19,11 +20,12 @@ import { compilePattern, compilePatternBoundaries, matchesPattern } from "./patt
|
|
|
19
20
|
import { nextCodePointOffset, scanString, stringCheckpoint } from "./string-operations.js";
|
|
20
21
|
import { selectMenu } from "./select-menu.js";
|
|
21
22
|
import { byteLocale } from "./locale.js";
|
|
23
|
+
import { diagnosticCommandName } from "./diagnostic-name.js";
|
|
22
24
|
import { trimParameter } from "./parameter-trim.js";
|
|
23
25
|
import { ownedShellSource } from "./source-value.js";
|
|
24
26
|
import { functionDisplay } from "./display.js";
|
|
25
27
|
import { ConditionalUnsupported, evaluateConditional } from "./conditional.js";
|
|
26
|
-
import { invocationScope, throwCleanupFailures } from "./cleanup.js";
|
|
28
|
+
import { invocationScope, throwCleanupFailures, InvocationScope } from "./cleanup.js";
|
|
27
29
|
import { bindFileOutputBudget, openFileOutput } from "../contracts/filesystem-output.js";
|
|
28
30
|
import { outputFailure } from "../contracts/io.js";
|
|
29
31
|
import { executionCommands } from "../commands/execution.js";
|
|
@@ -63,9 +65,10 @@ export const defaultLimits = {
|
|
|
63
65
|
};
|
|
64
66
|
const shellBuiltinNames = new Set([
|
|
65
67
|
":", "true", "false", "pwd", "cd", "set", "shift", "export", "local", "unset", "read", "declare", "mapfile", "readarray",
|
|
66
|
-
"
|
|
68
|
+
"exit", "return", "break", "continue", "command", "builtin", "type", "readonly", "echo", "printf", "test", "[", ".", "source", "eval", "getopts", "let", "pushd", "dirs", "popd", "shopt",
|
|
67
69
|
]);
|
|
68
70
|
const implementedBuiltins = new Set([...shellBuiltinNames].filter(name => !["echo", "printf", "test", "["].includes(name)));
|
|
71
|
+
const extensionExitFailures = new WeakMap();
|
|
69
72
|
const specialBuiltinNames = new Set([":", ".", "break", "continue", "eval", "exit", "export", "readonly", "return", "set", "shift", "unset"]);
|
|
70
73
|
const zeroPositionKey = "-1";
|
|
71
74
|
const unsupportedSetOptionNames = new Set([
|
|
@@ -73,6 +76,20 @@ const unsupportedSetOptionNames = new Set([
|
|
|
73
76
|
"ignoreeof", "interactive-comments", "keyword", "monitor", "noclobber", "noexec", "noglob", "nolog",
|
|
74
77
|
"notify", "onecmd", "physical", "posix", "privileged", "verbose", "vi", "xtrace",
|
|
75
78
|
]);
|
|
79
|
+
function commandSpelling(command) {
|
|
80
|
+
if (command.kind === "arithmetic")
|
|
81
|
+
return `((${command.source}))`;
|
|
82
|
+
if (command.kind === "conditional")
|
|
83
|
+
return `[[${command.source}]]`;
|
|
84
|
+
const redirects = command.redirects.map(redirect => {
|
|
85
|
+
const target = redirect.target.spelling ?? redirect.target.plain ?? "";
|
|
86
|
+
if (redirect.operator === ">&" || redirect.operator === "<&")
|
|
87
|
+
return `${redirect.descriptor}${redirect.operator}${target}`;
|
|
88
|
+
const implicit = redirect.operator.startsWith("<") ? 0 : 1;
|
|
89
|
+
return `${redirect.descriptor === implicit ? "" : redirect.descriptor}${redirect.operator} ${target}`;
|
|
90
|
+
});
|
|
91
|
+
return [...command.words.map(word => word.spelling ?? word.plain ?? ""), ...redirects].join(" ");
|
|
92
|
+
}
|
|
76
93
|
export function resolveLimits(...limits) {
|
|
77
94
|
const result = Object.assign({}, defaultLimits, ...limits);
|
|
78
95
|
for (const [key, value] of Object.entries(result)) {
|
|
@@ -82,8 +99,6 @@ export function resolveLimits(...limits) {
|
|
|
82
99
|
}
|
|
83
100
|
return result;
|
|
84
101
|
}
|
|
85
|
-
const runtimeFileSystems = new WeakMap();
|
|
86
|
-
const inputScopes = new WeakMap();
|
|
87
102
|
const budgetedSinks = new WeakMap();
|
|
88
103
|
async function sortExpansionStrings(values, work) {
|
|
89
104
|
const compare = async (left, right) => {
|
|
@@ -119,6 +134,75 @@ async function sortExpansionStrings(values, work) {
|
|
|
119
134
|
await sift(0, end);
|
|
120
135
|
}
|
|
121
136
|
}
|
|
137
|
+
class ExecutionCleanup {
|
|
138
|
+
budget;
|
|
139
|
+
controller = new AbortController();
|
|
140
|
+
failures = [];
|
|
141
|
+
#pending = [];
|
|
142
|
+
#closed = false;
|
|
143
|
+
#drain;
|
|
144
|
+
#failure;
|
|
145
|
+
constructor(budget) {
|
|
146
|
+
this.budget = budget;
|
|
147
|
+
}
|
|
148
|
+
register(cleanup) {
|
|
149
|
+
this.budget.signal.throwIfAborted();
|
|
150
|
+
if (this.#failure)
|
|
151
|
+
throw this.#failure.reason;
|
|
152
|
+
if (this.#closed)
|
|
153
|
+
throw new TypeError("Execution cleanup enrollment is closed");
|
|
154
|
+
if (typeof cleanup !== "function")
|
|
155
|
+
throw new TypeError("Execution cleanup must be callable");
|
|
156
|
+
this.budget.cpuCheckpoint();
|
|
157
|
+
const allocation = this.budget.values.scope();
|
|
158
|
+
try {
|
|
159
|
+
allocation.reserve(96, 1);
|
|
160
|
+
this.#pending.push({ cleanup, allocation });
|
|
161
|
+
}
|
|
162
|
+
catch (reason) {
|
|
163
|
+
allocation.close();
|
|
164
|
+
throw reason;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
abort(reason) {
|
|
168
|
+
this.#failure ??= { reason };
|
|
169
|
+
if (this.#pending.length || this.#drain)
|
|
170
|
+
this.controller.abort(reason);
|
|
171
|
+
}
|
|
172
|
+
drain() {
|
|
173
|
+
return this.#drain ??= Promise.resolve().then(async () => {
|
|
174
|
+
const retained = [];
|
|
175
|
+
try {
|
|
176
|
+
while (this.#pending.length) {
|
|
177
|
+
const pending = this.#pending.splice(0);
|
|
178
|
+
const results = await Promise.allSettled(pending.map(async (entry) => {
|
|
179
|
+
let failed = false;
|
|
180
|
+
try {
|
|
181
|
+
await entry.cleanup();
|
|
182
|
+
}
|
|
183
|
+
catch (reason) {
|
|
184
|
+
failed = true;
|
|
185
|
+
retained.push(entry.allocation);
|
|
186
|
+
throw reason;
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
if (!failed)
|
|
190
|
+
entry.allocation.close();
|
|
191
|
+
}
|
|
192
|
+
}));
|
|
193
|
+
for (const result of results)
|
|
194
|
+
if (result.status === "rejected")
|
|
195
|
+
this.failures.push(result.reason);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
this.#closed = true;
|
|
200
|
+
for (const allocation of retained)
|
|
201
|
+
allocation.close();
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
122
206
|
export class Budget {
|
|
123
207
|
limits;
|
|
124
208
|
onInternalError;
|
|
@@ -126,6 +210,7 @@ export class Budget {
|
|
|
126
210
|
pathLookup = new PathLookup();
|
|
127
211
|
parsing;
|
|
128
212
|
values;
|
|
213
|
+
executionCleanup = new ExecutionCleanup(this);
|
|
129
214
|
commands = 0;
|
|
130
215
|
iterations = 0;
|
|
131
216
|
bytes = 0;
|
|
@@ -212,6 +297,27 @@ export class Budget {
|
|
|
212
297
|
this.fail("maxSourceBytes");
|
|
213
298
|
this.sourceBytes += bytes;
|
|
214
299
|
}
|
|
300
|
+
async writeCounted(chunk, write, signal = this.signal) {
|
|
301
|
+
signal.throwIfAborted();
|
|
302
|
+
if (!(chunk instanceof Uint8Array))
|
|
303
|
+
throw new TypeError("Shell output must be Uint8Array");
|
|
304
|
+
const reserved = chunk.byteLength;
|
|
305
|
+
if (reserved > this.limits.maxOutputBytes - this.bytes)
|
|
306
|
+
this.fail("maxOutputBytes");
|
|
307
|
+
this.bytes += reserved;
|
|
308
|
+
try {
|
|
309
|
+
const count = await write();
|
|
310
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > reserved)
|
|
311
|
+
throw new FsError("EIO", { syscall: "write", message: "invalid byte count" });
|
|
312
|
+
this.bytes -= reserved - count;
|
|
313
|
+
signal.throwIfAborted();
|
|
314
|
+
return count;
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
signal.throwIfAborted();
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
215
321
|
sink(sink, signal = this.signal) {
|
|
216
322
|
const ownership = budgetedSinks.get(sink);
|
|
217
323
|
if (ownership?.budget === this && ownership.write === sink.write)
|
|
@@ -252,7 +358,6 @@ export class Budget {
|
|
|
252
358
|
await interruptible(sink.write(chunk), signal);
|
|
253
359
|
},
|
|
254
360
|
};
|
|
255
|
-
forwardBackgroundDestination(output, sink);
|
|
256
361
|
budgetedSinks.set(output, { budget: this, write: output.write, ...(ownership?.write === sink.write && ownership.file ? { file: ownership.file } : {}) });
|
|
257
362
|
return output;
|
|
258
363
|
}
|
|
@@ -319,8 +424,138 @@ export class Capture {
|
|
|
319
424
|
const typedSavedVariables = new WeakMap();
|
|
320
425
|
const valueScope = Symbol("shell value allocation scope");
|
|
321
426
|
const invokedValues = new WeakMap();
|
|
322
|
-
|
|
323
|
-
|
|
427
|
+
const functionDiagnostics = new WeakMap();
|
|
428
|
+
const childIdentities = new WeakMap();
|
|
429
|
+
class DescriptorLifetime {
|
|
430
|
+
finalize;
|
|
431
|
+
#references = 1;
|
|
432
|
+
#ownerReleased = false;
|
|
433
|
+
#closing;
|
|
434
|
+
constructor(finalize) {
|
|
435
|
+
this.finalize = finalize;
|
|
436
|
+
}
|
|
437
|
+
acquire() {
|
|
438
|
+
if (!this.#references)
|
|
439
|
+
throw new FsError("EBADF");
|
|
440
|
+
this.#references++;
|
|
441
|
+
let released = false;
|
|
442
|
+
return () => {
|
|
443
|
+
if (released)
|
|
444
|
+
return this.#closing ?? Promise.resolve();
|
|
445
|
+
released = true;
|
|
446
|
+
return this.#release();
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
release() {
|
|
450
|
+
if (this.#ownerReleased)
|
|
451
|
+
return this.#closing ?? Promise.resolve();
|
|
452
|
+
this.#ownerReleased = true;
|
|
453
|
+
return this.#release();
|
|
454
|
+
}
|
|
455
|
+
#release() {
|
|
456
|
+
if (--this.#references)
|
|
457
|
+
return Promise.resolve();
|
|
458
|
+
return this.#closing ??= Promise.resolve().then(this.finalize);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
class PreparedDescriptorFrame {
|
|
462
|
+
references;
|
|
463
|
+
budget;
|
|
464
|
+
#bindings = new Map();
|
|
465
|
+
#work = Promise.resolve();
|
|
466
|
+
#closing;
|
|
467
|
+
#retireCleanup;
|
|
468
|
+
constructor(references, budget) {
|
|
469
|
+
this.references = references;
|
|
470
|
+
this.budget = budget;
|
|
471
|
+
this.#retireCleanup = references.scope.register(() => this.close());
|
|
472
|
+
}
|
|
473
|
+
acquire(descriptors) {
|
|
474
|
+
if (this.#closing)
|
|
475
|
+
throw new FsError("EBADF");
|
|
476
|
+
for (const [number, descriptor] of descriptors) {
|
|
477
|
+
if (descriptor.closed || !descriptor.lifetime)
|
|
478
|
+
continue;
|
|
479
|
+
const allocation = this.budget.values.scope();
|
|
480
|
+
try {
|
|
481
|
+
allocation.reserve(64, 1);
|
|
482
|
+
const release = descriptor.lifetime.acquire();
|
|
483
|
+
this.#bindings.set(number, { lifetime: descriptor.lifetime, async release() {
|
|
484
|
+
try {
|
|
485
|
+
await release();
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
allocation.close();
|
|
489
|
+
}
|
|
490
|
+
} });
|
|
491
|
+
}
|
|
492
|
+
catch (reason) {
|
|
493
|
+
allocation.close();
|
|
494
|
+
throw reason;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
reconcile(descriptors) {
|
|
499
|
+
if (this.#closing)
|
|
500
|
+
return Promise.reject(new FsError("EBADF"));
|
|
501
|
+
const previous = this.#bindings;
|
|
502
|
+
this.#bindings = new Map();
|
|
503
|
+
const failures = [];
|
|
504
|
+
const inherited = new Set([...previous.values()].map(binding => binding.lifetime));
|
|
505
|
+
const surviving = new Map([...descriptors].filter(([, descriptor]) => descriptor.lifetime && inherited.has(descriptor.lifetime)));
|
|
506
|
+
try {
|
|
507
|
+
this.acquire(surviving);
|
|
508
|
+
}
|
|
509
|
+
catch (reason) {
|
|
510
|
+
failures.push(reason);
|
|
511
|
+
}
|
|
512
|
+
const activePipes = new Set([...descriptors.values()].filter(descriptor => !descriptor.closed).map(descriptor => descriptor.pipe));
|
|
513
|
+
this.#work = this.#work.then(async () => {
|
|
514
|
+
const retired = await Promise.allSettled([
|
|
515
|
+
...[...previous.values()].map(binding => binding.release()),
|
|
516
|
+
...[...this.references.references].filter(reference => !activePipes.has(reference)).map(reference => reference.close()),
|
|
517
|
+
]);
|
|
518
|
+
failures.push(...retired.filter(result => result.status === "rejected").map(result => result.reason));
|
|
519
|
+
throwCleanupFailures(failures);
|
|
520
|
+
});
|
|
521
|
+
return this.#work;
|
|
522
|
+
}
|
|
523
|
+
close() {
|
|
524
|
+
return this.#closing ??= Promise.resolve().then(async () => {
|
|
525
|
+
const failures = [];
|
|
526
|
+
try {
|
|
527
|
+
await this.#work;
|
|
528
|
+
}
|
|
529
|
+
catch (reason) {
|
|
530
|
+
failures.push(reason);
|
|
531
|
+
}
|
|
532
|
+
const retired = await Promise.allSettled([...this.#bindings.values()].map(binding => binding.release()));
|
|
533
|
+
this.#bindings.clear();
|
|
534
|
+
failures.push(...retired.filter(result => result.status === "rejected").map(result => result.reason));
|
|
535
|
+
throwCleanupFailures(failures);
|
|
536
|
+
this.#retireCleanup();
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function isolateIO(io, references) {
|
|
541
|
+
const descriptors = new Map();
|
|
542
|
+
for (const [number, descriptor] of io.descriptors ?? []) {
|
|
543
|
+
if (number === 0 && descriptor.input !== io.stdin || number === 1 && descriptor.output !== io.stdout || number === 2 && descriptor.output !== io.stderr)
|
|
544
|
+
continue;
|
|
545
|
+
if (descriptor.closed || !descriptor.pipe)
|
|
546
|
+
descriptors.set(number, { ...descriptor });
|
|
547
|
+
else {
|
|
548
|
+
const pipe = references.acquire(descriptor.pipe);
|
|
549
|
+
descriptors.set(number, { ...descriptor, pipe });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (!descriptors.has(0))
|
|
553
|
+
descriptors.set(0, { input: io.stdin, ...(io.stdinIsDefault === undefined ? {} : { stdinIsDefault: io.stdinIsDefault }) });
|
|
554
|
+
if (!descriptors.has(1))
|
|
555
|
+
descriptors.set(1, { output: io.stdout });
|
|
556
|
+
if (!descriptors.has(2))
|
|
557
|
+
descriptors.set(2, { output: io.stderr });
|
|
558
|
+
return { ...io, nameExpansionContext: undefined, descriptors, ...(io.assignmentDiagnosticContext === undefined ? {} : { assignmentDiagnosticContext: { ...io.assignmentDiagnosticContext } }) };
|
|
324
559
|
}
|
|
325
560
|
function activeIO(io) {
|
|
326
561
|
const input = io.descriptors?.get(0);
|
|
@@ -349,16 +584,20 @@ class Flow extends Error {
|
|
|
349
584
|
kind;
|
|
350
585
|
status;
|
|
351
586
|
levels;
|
|
352
|
-
|
|
587
|
+
previousStatus;
|
|
588
|
+
expansionFailure;
|
|
589
|
+
constructor(kind, status, levels = 1, previousStatus, expansionFailure = false) {
|
|
353
590
|
super(kind);
|
|
354
591
|
this.kind = kind;
|
|
355
592
|
this.status = status;
|
|
356
593
|
this.levels = levels;
|
|
594
|
+
this.previousStatus = previousStatus;
|
|
595
|
+
this.expansionFailure = expansionFailure;
|
|
357
596
|
}
|
|
358
597
|
}
|
|
359
598
|
const completedFlows = new WeakSet();
|
|
360
|
-
function completedExit(status, kind = "exit", levels = 1) {
|
|
361
|
-
const flow = new Flow(kind, status, levels);
|
|
599
|
+
function completedExit(status, kind = "exit", levels = 1, previousStatus, expansionFailure = false) {
|
|
600
|
+
const flow = new Flow(kind, status, levels, previousStatus, expansionFailure);
|
|
362
601
|
completedFlows.add(flow);
|
|
363
602
|
return flow;
|
|
364
603
|
}
|
|
@@ -389,6 +628,15 @@ class CommandFailure extends Error {
|
|
|
389
628
|
}
|
|
390
629
|
class FatalCommandFailure extends CommandFailure {
|
|
391
630
|
}
|
|
631
|
+
class DiscardCommandFailure extends CommandFailure {
|
|
632
|
+
variableName;
|
|
633
|
+
origin;
|
|
634
|
+
constructor(variableName, status, origin) {
|
|
635
|
+
super(`${variableName}: readonly variable`, status);
|
|
636
|
+
this.variableName = variableName;
|
|
637
|
+
this.origin = origin;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
392
640
|
class ParameterExpansionFailure extends ExpansionFailure {
|
|
393
641
|
}
|
|
394
642
|
class NounsetFailure extends ExpansionFailure {
|
|
@@ -400,6 +648,8 @@ class NounsetDiagnosticFailure extends Flow {
|
|
|
400
648
|
this.reason = reason;
|
|
401
649
|
}
|
|
402
650
|
}
|
|
651
|
+
class ExtensionCheckpointFailure extends NounsetDiagnosticFailure {
|
|
652
|
+
}
|
|
403
653
|
class PipelineClosed extends Error {
|
|
404
654
|
code = "EPIPE";
|
|
405
655
|
constructor() { super("Pipeline consumer exited"); }
|
|
@@ -429,7 +679,6 @@ function signalSink(sink, signal) {
|
|
|
429
679
|
} } : {}),
|
|
430
680
|
async write(chunk) { signal.throwIfAborted(); await interruptible(write(chunk), signal); },
|
|
431
681
|
};
|
|
432
|
-
forwardBackgroundDestination(output, sink);
|
|
433
682
|
if (owned)
|
|
434
683
|
budgetedSinks.set(output, { ...owned, write: output.write });
|
|
435
684
|
return output;
|
|
@@ -446,7 +695,6 @@ function bindCommandIO(context) {
|
|
|
446
695
|
async function cloneState(state, signal, scope, inheritLocals = true) {
|
|
447
696
|
const destination = await snapshotState(state, () => ({
|
|
448
697
|
...state,
|
|
449
|
-
...(state.backgroundJobs ? { backgroundJobs: new BackgroundJobs(state.backgroundJobs.execution) } : {}),
|
|
450
698
|
variables: Object.assign(Object.create(null), state.variables),
|
|
451
699
|
exported: new Set(state.exported), functions: new Map(state.functions), positional: [...state.positional],
|
|
452
700
|
readonlyVariables: new Set(state.readonlyVariables),
|
|
@@ -506,6 +754,20 @@ async function cloneState(state, signal, scope, inheritLocals = true) {
|
|
|
506
754
|
function cloneGetoptsBinding(state) {
|
|
507
755
|
return { cursor: state.getopts ? cloneGetoptsState(state.getopts.cursor) : createGetoptsState(), integer: state.getopts?.integer ?? false };
|
|
508
756
|
}
|
|
757
|
+
function shellCharacterWidth(bytes, offset, byteCount) {
|
|
758
|
+
if (byteCount)
|
|
759
|
+
return 1;
|
|
760
|
+
const first = bytes[offset];
|
|
761
|
+
const length = first >= 0xc2 && first <= 0xdf ? 2 : first >= 0xe0 && first <= 0xef ? 3 : first >= 0xf0 && first <= 0xf4 ? 4 : 1;
|
|
762
|
+
if (offset + length > bytes.length)
|
|
763
|
+
return 1;
|
|
764
|
+
for (let index = 1; index < length; index++) {
|
|
765
|
+
const byte = bytes[offset + index];
|
|
766
|
+
if (byte < 0x80 || byte > 0xbf || index === 1 && (first === 0xe0 && byte < 0xa0 || first === 0xed && byte > 0x9f || first === 0xf0 && byte < 0x90 || first === 0xf4 && byte > 0x8f))
|
|
767
|
+
return 1;
|
|
768
|
+
}
|
|
769
|
+
return length;
|
|
770
|
+
}
|
|
509
771
|
function saveVariable(state, name) {
|
|
510
772
|
const monitor = stateMonitor(state);
|
|
511
773
|
const value = monitor?.values.get(name, state.variables[name] ?? "");
|
|
@@ -1120,6 +1382,7 @@ class InvocationCancellationOwner {
|
|
|
1120
1382
|
}
|
|
1121
1383
|
}
|
|
1122
1384
|
const mapfileCallbackStates = new WeakSet();
|
|
1385
|
+
const runtimeFileSystems = new WeakMap();
|
|
1123
1386
|
export class Runtime {
|
|
1124
1387
|
fs;
|
|
1125
1388
|
commands;
|
|
@@ -1136,6 +1399,7 @@ export class Runtime {
|
|
|
1136
1399
|
cancellationMaxDepth;
|
|
1137
1400
|
outcomeFrame;
|
|
1138
1401
|
inputProfile;
|
|
1402
|
+
sourceFs;
|
|
1139
1403
|
constructor(fs, commands, middleware, budget, signal = budget.signal, fileWrites = new Map(), outputFiles = new Map(), commandSignal = signal, cancellation, cancellationState, cancellationOwner, cancellationDepth, cancellationMaxDepth, outcomeFrame = undefined, inputProfile = fs) {
|
|
1140
1404
|
this.fs = fs;
|
|
1141
1405
|
this.commands = commands;
|
|
@@ -1152,8 +1416,9 @@ export class Runtime {
|
|
|
1152
1416
|
this.cancellationMaxDepth = cancellationMaxDepth;
|
|
1153
1417
|
this.outcomeFrame = outcomeFrame;
|
|
1154
1418
|
this.inputProfile = inputProfile;
|
|
1419
|
+
this.sourceFs = runtimeFileSystems.get(fs) ?? fs;
|
|
1155
1420
|
this.fs = scopeFileSystem(fs, () => budget.fileSystemOperation(), signal, () => budget.fileSystemCleanupOperation());
|
|
1156
|
-
runtimeFileSystems.set(this.fs,
|
|
1421
|
+
runtimeFileSystems.set(this.fs, this.sourceFs);
|
|
1157
1422
|
const checkpoint = () => budget.cpuCheckpoint();
|
|
1158
1423
|
registerYieldCheckpoint(signal, checkpoint);
|
|
1159
1424
|
registerYieldCheckpoint(commandSignal, checkpoint);
|
|
@@ -1399,8 +1664,18 @@ export class Runtime {
|
|
|
1399
1664
|
if (this.outcomeFrame)
|
|
1400
1665
|
this.outcomeFrame.report = undefined;
|
|
1401
1666
|
}
|
|
1402
|
-
diagnostic(io, text) {
|
|
1403
|
-
|
|
1667
|
+
async diagnostic(io, text) {
|
|
1668
|
+
const prefix = `${io.scriptName ?? "shell"}: line ${io.diagnosticLine ?? 1}: `;
|
|
1669
|
+
if (typeof text === "string")
|
|
1670
|
+
return writeDiagnostic(io.stderr, `${prefix}${text}\n`, this.signal);
|
|
1671
|
+
const allocation = this.budget.values.scope();
|
|
1672
|
+
try {
|
|
1673
|
+
const value = concatShellValues([prefix, text, "\n"], allocation);
|
|
1674
|
+
await io.stderr.write(shellValueBytes(value, allocation));
|
|
1675
|
+
}
|
|
1676
|
+
finally {
|
|
1677
|
+
allocation.close();
|
|
1678
|
+
}
|
|
1404
1679
|
}
|
|
1405
1680
|
writeVariable(state, name, value, origin = "assignment") {
|
|
1406
1681
|
if (arrayStore(state)?.get(name))
|
|
@@ -1665,6 +1940,13 @@ export class Runtime {
|
|
|
1665
1940
|
}
|
|
1666
1941
|
}
|
|
1667
1942
|
}
|
|
1943
|
+
assertArrayWritable(state, name, origin = "assignment") {
|
|
1944
|
+
if (!state.readonlyVariables?.has(name))
|
|
1945
|
+
return;
|
|
1946
|
+
if (state.extensions?.syntax.indexedDeclarations?.includes("readonly"))
|
|
1947
|
+
throw new DiscardCommandFailure(name, 1, origin);
|
|
1948
|
+
throw new ArrayFailure("readonly binding");
|
|
1949
|
+
}
|
|
1668
1950
|
async arrayZero(state, name, expand, append = false, freeze = false) {
|
|
1669
1951
|
const store = requireArrays(state);
|
|
1670
1952
|
const operation = ArrayOwner.create(store.owner.ledger, store.owner);
|
|
@@ -1682,8 +1964,7 @@ export class Runtime {
|
|
|
1682
1964
|
}
|
|
1683
1965
|
const expanded = await expand();
|
|
1684
1966
|
this.signal.throwIfAborted();
|
|
1685
|
-
|
|
1686
|
-
throw new ArrayFailure("readonly binding");
|
|
1967
|
+
this.assertArrayWritable(state, name);
|
|
1687
1968
|
if (!watch.valid())
|
|
1688
1969
|
throw new ArrayFailure("stale binding");
|
|
1689
1970
|
const current = store.get(name);
|
|
@@ -1691,10 +1972,10 @@ export class Runtime {
|
|
|
1691
1972
|
throw new ArrayFailure("stale binding");
|
|
1692
1973
|
staged = await current.copy(this.signal);
|
|
1693
1974
|
const index = staged.associative ? (await staged.keyIndex("0", operation, this.signal, true)) : 0;
|
|
1694
|
-
const previous = current.
|
|
1975
|
+
const previous = current.getValue(index) ?? "";
|
|
1695
1976
|
const value = !append ? expanded : typeof previous === "string" && typeof expanded === "string"
|
|
1696
1977
|
? await this.arrayJoin(operation, [previous, expanded], "") : concatShellValues([previous, expanded], valueAllocation);
|
|
1697
|
-
const token = await
|
|
1978
|
+
const token = await textToken(staged.owner, value, this.signal);
|
|
1698
1979
|
try {
|
|
1699
1980
|
staged.insert(index, token);
|
|
1700
1981
|
}
|
|
@@ -1704,8 +1985,7 @@ export class Runtime {
|
|
|
1704
1985
|
}
|
|
1705
1986
|
const supersede = await stateMonitor(state).prepareTypedPublication(name, operation, this.signal);
|
|
1706
1987
|
this.signal.throwIfAborted();
|
|
1707
|
-
|
|
1708
|
-
throw new ArrayFailure("readonly binding");
|
|
1988
|
+
this.assertArrayWritable(state, name);
|
|
1709
1989
|
if (!watch.valid())
|
|
1710
1990
|
throw new ArrayFailure("stale binding");
|
|
1711
1991
|
let released;
|
|
@@ -1733,18 +2013,26 @@ export class Runtime {
|
|
|
1733
2013
|
}
|
|
1734
2014
|
}
|
|
1735
2015
|
async arrayJoin(owner, values, separator) {
|
|
2016
|
+
const raw = typeof separator !== "string" || values.some(value => typeof value !== "string");
|
|
1736
2017
|
let bytes = 0;
|
|
1737
2018
|
for (const value of values) {
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
2019
|
+
const length = typeof value === "string" ? value.length : shellValueByteLength(value);
|
|
2020
|
+
owner.reserve({ work: length + 1 }).release();
|
|
2021
|
+
bytes = exactSum(bytes, shellValueByteLength(value));
|
|
2022
|
+
await owner.ledger.checkpoint(this.signal, length + 1);
|
|
1741
2023
|
}
|
|
1742
|
-
bytes = exactSum(bytes, Math.max(0, values.length - 1) *
|
|
1743
|
-
owner.reserve({ metadata: exactSum(96, values.length * 32), payload: bytes, allocatedSlots: values.length, work: values.length * 3 + 7 });
|
|
2024
|
+
bytes = exactSum(bytes, Math.max(0, values.length - 1) * shellValueByteLength(separator));
|
|
2025
|
+
owner.reserve({ metadata: exactSum(exactSum(96, values.length * 32), raw ? exactSum(bytes * 2, 64) : 0), payload: bytes, allocatedSlots: values.length * (raw ? 2 : 1), work: values.length * 3 + 7 });
|
|
1744
2026
|
this.signal.throwIfAborted();
|
|
1745
|
-
const
|
|
1746
|
-
|
|
1747
|
-
|
|
2027
|
+
const allocation = raw ? this.budget.values.scope() : undefined;
|
|
2028
|
+
try {
|
|
2029
|
+
const result = raw ? concatShellValues(values.flatMap((value, index) => index ? [separator, value] : [value]), allocation) : values.join(separator);
|
|
2030
|
+
await owner.ledger.checkpoint(this.signal);
|
|
2031
|
+
return result;
|
|
2032
|
+
}
|
|
2033
|
+
finally {
|
|
2034
|
+
allocation?.close();
|
|
2035
|
+
}
|
|
1748
2036
|
}
|
|
1749
2037
|
async arrayIndex(binding, index, state, io, owner, create = false) {
|
|
1750
2038
|
if (!binding?.associative) {
|
|
@@ -1765,16 +2053,14 @@ export class Runtime {
|
|
|
1765
2053
|
}
|
|
1766
2054
|
return binding.keyIndex(value, owner, this.signal, create);
|
|
1767
2055
|
}
|
|
1768
|
-
async arrayAssignment(assignment, state, io) {
|
|
2056
|
+
async arrayAssignment(assignment, state, io, declaration, origin = "assignment") {
|
|
1769
2057
|
const name = assignment.name;
|
|
1770
2058
|
this.signal.throwIfAborted();
|
|
1771
|
-
if (state.readonlyVariables?.has(name)) {
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
throw completedExit(1);
|
|
1775
|
-
}
|
|
1776
|
-
throw new ArrayFailure("readonly binding");
|
|
2059
|
+
if (state.readonlyVariables?.has(name) && arrayStore(state)?.get(name)?.associative) {
|
|
2060
|
+
await this.diagnostic(io, `${name}: readonly variable`);
|
|
2061
|
+
throw completedExit(1);
|
|
1777
2062
|
}
|
|
2063
|
+
this.assertArrayWritable(state, name, origin);
|
|
1778
2064
|
if (controlNames.has(name))
|
|
1779
2065
|
throw new ArrayFailure("control binding cannot be indexed");
|
|
1780
2066
|
if (state.exported.has(name))
|
|
@@ -1807,7 +2093,7 @@ export class Runtime {
|
|
|
1807
2093
|
else {
|
|
1808
2094
|
const quotedScalar = (part) => {
|
|
1809
2095
|
const selector = getArraySelector(part);
|
|
1810
|
-
return part.quoted && !(part.kind === "variable" && (part.name === "@" || selector
|
|
2096
|
+
return part.quoted && !(part.kind === "variable" && (part.name === "@" || selector && selector.kind !== "element" && selector.separator === "@"));
|
|
1811
2097
|
};
|
|
1812
2098
|
const certain = entry.value.parts.every(part => part.kind === "text" ? part.quoted || !/[*?[]/u.test(part.value) : quotedScalar(part));
|
|
1813
2099
|
const demanded = entry.value.parts.some(part => part.kind === "text" ? part.quoted || part.value.length > 0 : quotedScalar(part));
|
|
@@ -1824,11 +2110,17 @@ export class Runtime {
|
|
|
1824
2110
|
const supersede = await stateMonitor(state).prepareTypedPublication(name, operation, this.signal);
|
|
1825
2111
|
await this.prepareArrayObservers(state, operation);
|
|
1826
2112
|
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
2113
|
+
let frozenAttributes;
|
|
2114
|
+
if (declaration && !state.readonlyVariables?.has(name)) {
|
|
2115
|
+
store.owner.reserve({ metadata: state.readonlyVariables ? 32 : 96, slots: 1, work: 5 });
|
|
2116
|
+
if (!state.readonlyVariables)
|
|
2117
|
+
frozenAttributes = stateMonitor(state).prepareCollection(new Set(), "readonlyVariables");
|
|
2118
|
+
}
|
|
1827
2119
|
const prepared = await store.prepareName(name, operation, this.signal);
|
|
1828
2120
|
const preserve = assignment.kind === "element" || assignment.append;
|
|
1829
2121
|
staged = preserve && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner, current?.associative);
|
|
1830
2122
|
if (preserve && !current && state.variables[name] !== undefined) {
|
|
1831
|
-
const token = await
|
|
2123
|
+
const token = await textToken(staged.owner, stateMonitor(state)?.values.get(name, state.variables[name]) ?? state.variables[name], this.signal);
|
|
1832
2124
|
try {
|
|
1833
2125
|
staged.insert(0, token);
|
|
1834
2126
|
}
|
|
@@ -1859,7 +2151,7 @@ export class Runtime {
|
|
|
1859
2151
|
const fields = await this.valueWord(assignment.value, state, io, false);
|
|
1860
2152
|
let value = await join(fields);
|
|
1861
2153
|
if (assignment.append)
|
|
1862
|
-
value = await join([staged.
|
|
2154
|
+
value = await join([staged.getValue(index) ?? "", value]);
|
|
1863
2155
|
await insert(index, value);
|
|
1864
2156
|
}
|
|
1865
2157
|
else
|
|
@@ -1871,7 +2163,7 @@ export class Runtime {
|
|
|
1871
2163
|
if (expanded === original)
|
|
1872
2164
|
break;
|
|
1873
2165
|
expandedEntry = true;
|
|
1874
|
-
const values = await this.valueWord(expanded, state, io, true, false, false, false, undefined, false);
|
|
2166
|
+
const values = await this.valueWord(expanded, state, io, true, false, false, false, undefined, false, false);
|
|
1875
2167
|
for (const value of values) {
|
|
1876
2168
|
await insert(cursor, value);
|
|
1877
2169
|
cursor++;
|
|
@@ -1893,17 +2185,20 @@ export class Runtime {
|
|
|
1893
2185
|
}
|
|
1894
2186
|
}
|
|
1895
2187
|
this.signal.throwIfAborted();
|
|
1896
|
-
|
|
1897
|
-
throw new ArrayFailure("readonly binding");
|
|
2188
|
+
this.assertArrayWritable(state, name, origin);
|
|
1898
2189
|
if (!watch.valid())
|
|
1899
2190
|
throw new ArrayFailure("stale binding");
|
|
1900
2191
|
staged.assigned = true;
|
|
1901
|
-
if (!(assignment.kind === "compound" && assignment.append && writes === 0 && current?.assigned)) {
|
|
2192
|
+
if (declaration || !(assignment.kind === "compound" && assignment.append && writes === 0 && current?.assigned)) {
|
|
1902
2193
|
let released;
|
|
1903
2194
|
stateMonitor(state).publish(tickets, name, () => {
|
|
1904
2195
|
supersede();
|
|
1905
2196
|
delete state.variables[name];
|
|
1906
2197
|
released = store.publish(name, staged, tickets, prepared);
|
|
2198
|
+
if (declaration) {
|
|
2199
|
+
state.readonlyVariables ??= frozenAttributes;
|
|
2200
|
+
state.readonlyVariables.add(name);
|
|
2201
|
+
}
|
|
1907
2202
|
});
|
|
1908
2203
|
staged = undefined;
|
|
1909
2204
|
await released;
|
|
@@ -1921,87 +2216,861 @@ export class Runtime {
|
|
|
1921
2216
|
}
|
|
1922
2217
|
}
|
|
1923
2218
|
async run(script, state, io) {
|
|
1924
|
-
return (await this.runUnit(script, state, io)).exitCode;
|
|
2219
|
+
return this.finishShell(state, io, (await this.runUnit(script, state, io)).exitCode);
|
|
1925
2220
|
}
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2221
|
+
extensionContext(state, io, command = "", args = [], argumentValues = args) {
|
|
2222
|
+
const frame = state.extensions;
|
|
2223
|
+
let bindings;
|
|
2224
|
+
let input;
|
|
2225
|
+
const createBindings = this.extensionBindings.bind(this, state, io);
|
|
2226
|
+
const createInput = this.extensionInput.bind(this, state, io);
|
|
2227
|
+
const signal = AbortSignal.any([this.signal, this.budget.executionCleanup.controller.signal]);
|
|
2228
|
+
return {
|
|
2229
|
+
command, args, argumentValues, status: state.status, functionDepth: state.functionDepth, sourceDepth: state.sourceDepth ?? 0,
|
|
2230
|
+
stdin: io.stdin, stdout: io.stdout, stderr: io.stderr, signal, scope: frame,
|
|
2231
|
+
get bindings() { return bindings ??= createBindings(); },
|
|
2232
|
+
get input() { return input ??= createInput(); },
|
|
2233
|
+
variable: name => state.variables[name],
|
|
2234
|
+
accountSource: source => this.budget.source(shellValueByteLength(source)),
|
|
2235
|
+
diagnostic: message => this.diagnostic(io, message),
|
|
2236
|
+
evaluate: async (source, options) => {
|
|
2237
|
+
this.signal.throwIfAborted();
|
|
2238
|
+
if (state.depth >= this.budget.limits.maxSubstitutionDepth)
|
|
2239
|
+
this.budget.fail("maxSubstitutionDepth");
|
|
2240
|
+
this.budget.source(shellValueByteLength(source));
|
|
2241
|
+
const byteSource = typeof source !== "string";
|
|
2242
|
+
const text = byteSource ? Buffer.from(shellValueBytes(source)).toString("latin1") : source;
|
|
2243
|
+
const restoration = stateMonitor(state)?.restoration(true);
|
|
2244
|
+
try {
|
|
2245
|
+
state.depth++;
|
|
2246
|
+
return await this.runCurrentText(text, state, { ...io, diagnosticOffset: (io.diagnosticLine ?? 1) - 1 }, false, options?.name === undefined ? undefined : `${io.scriptName ?? "shell"}: ${options.name}`, byteSource, false);
|
|
2247
|
+
}
|
|
2248
|
+
finally {
|
|
2249
|
+
if (restoration)
|
|
2250
|
+
restoration.apply(() => { state.depth--; });
|
|
2251
|
+
else
|
|
2252
|
+
state.depth--;
|
|
2253
|
+
}
|
|
2254
|
+
},
|
|
2255
|
+
registerCleanup: cleanup => {
|
|
2256
|
+
let completion;
|
|
2257
|
+
const close = () => completion ??= Promise.resolve().then(cleanup);
|
|
2258
|
+
frame.cleanup.push(close);
|
|
2259
|
+
io[invocationScope].register(close);
|
|
2260
|
+
},
|
|
2261
|
+
registerExecutionCleanup: cleanup => {
|
|
2262
|
+
this.budget.executionCleanup.register(cleanup);
|
|
2263
|
+
return AbortSignal.any([this.commandSignal, this.budget.signal, this.budget.executionCleanup.controller.signal]);
|
|
2264
|
+
},
|
|
2265
|
+
interruptWait: status => {
|
|
2266
|
+
validateExitCode(status);
|
|
2267
|
+
return !signal.aborted && (frame.waiting?.(status) ?? false);
|
|
2268
|
+
},
|
|
2269
|
+
waitInterruptibly: async (operation) => {
|
|
2270
|
+
signal.throwIfAborted();
|
|
2271
|
+
const scope = io[invocationScope];
|
|
2272
|
+
scope.assertOpen();
|
|
2273
|
+
if (typeof operation !== "function")
|
|
2274
|
+
throw new TypeError("Cooperative wait requires a callable operation");
|
|
2275
|
+
if (frame.waiting)
|
|
2276
|
+
throw new Error("A cooperative wait is already active in this shell scope");
|
|
2277
|
+
const controller = new AbortController();
|
|
2278
|
+
const reason = Object.freeze({});
|
|
2279
|
+
let interruption;
|
|
2280
|
+
const interrupt = (status) => {
|
|
2281
|
+
if (interruption || signal.aborted || scope.signal.aborted)
|
|
2282
|
+
return false;
|
|
2283
|
+
interruption = { status };
|
|
2284
|
+
controller.abort(reason);
|
|
2285
|
+
return true;
|
|
2286
|
+
};
|
|
2287
|
+
const close = () => { if (frame.waiting === interrupt)
|
|
2288
|
+
delete frame.waiting; };
|
|
2289
|
+
scope.register(close);
|
|
2290
|
+
frame.waiting = interrupt;
|
|
2291
|
+
try {
|
|
2292
|
+
const value = await scope.run(() => operation(AbortSignal.any([signal, scope.signal, controller.signal])));
|
|
2293
|
+
signal.throwIfAborted();
|
|
2294
|
+
return { kind: "completed", value };
|
|
2295
|
+
}
|
|
2296
|
+
catch (error) {
|
|
2297
|
+
signal.throwIfAborted();
|
|
2298
|
+
if (interruption && error === reason)
|
|
2299
|
+
return { kind: "interrupted", status: interruption.status };
|
|
1934
2300
|
throw error;
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
}
|
|
2301
|
+
}
|
|
2302
|
+
finally {
|
|
2303
|
+
close();
|
|
2304
|
+
}
|
|
2305
|
+
},
|
|
2306
|
+
};
|
|
1941
2307
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
2308
|
+
extensionBindings(state, io) {
|
|
2309
|
+
const scope = io[invocationScope];
|
|
2310
|
+
scope.assertOpen();
|
|
2311
|
+
const allocation = this.budget.values.scope();
|
|
2312
|
+
const references = new Set();
|
|
2313
|
+
scope.register(async () => {
|
|
2314
|
+
try {
|
|
2315
|
+
await Promise.all([...references].map(close => close()));
|
|
2316
|
+
}
|
|
2317
|
+
finally {
|
|
2318
|
+
allocation.close();
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
const assertOpen = () => { this.signal.throwIfAborted(); scope.assertOpen(); allocation.assertOpen(); };
|
|
2322
|
+
const checkName = (name) => {
|
|
2323
|
+
assertOpen();
|
|
2324
|
+
if (typeof name !== "string" || !/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name))
|
|
2325
|
+
throw new TypeError("Invalid binding name");
|
|
2326
|
+
};
|
|
2327
|
+
const checkIndex = (index, maximum = 2147483647) => {
|
|
2328
|
+
if (!Number.isSafeInteger(index) || index < 0 || index > maximum)
|
|
2329
|
+
throw new RangeError(`Binding index outside 0..${maximum}`);
|
|
2330
|
+
};
|
|
2331
|
+
const retain = (value) => {
|
|
2332
|
+
if (value !== undefined && typeof value !== "string")
|
|
2333
|
+
allocation.hold(value);
|
|
2334
|
+
return value;
|
|
2335
|
+
};
|
|
2336
|
+
return Object.freeze({
|
|
2337
|
+
describe: (name) => {
|
|
2338
|
+
checkName(name);
|
|
2339
|
+
const kind = arrayStore(state)?.get(name) ? "indexed" : state.variables[name] === undefined ? "unset" : "scalar";
|
|
2340
|
+
return Object.freeze({ kind, readonly: state.readonlyVariables?.has(name) ?? false, exported: state.exported.has(name) });
|
|
2341
|
+
},
|
|
2342
|
+
get: (name, index = 0) => {
|
|
2343
|
+
checkName(name);
|
|
2344
|
+
checkIndex(index, 4294967295);
|
|
2345
|
+
const binding = arrayStore(state)?.get(name);
|
|
2346
|
+
const scalar = state.variables[name];
|
|
2347
|
+
return retain(binding ? binding.getValue(index) : index === 0 && scalar !== undefined ? stateMonitor(state)?.values.get(name, scalar) ?? scalar : undefined);
|
|
2348
|
+
},
|
|
2349
|
+
assign: async (name, value) => {
|
|
2350
|
+
checkName(name);
|
|
2351
|
+
await scope.run(() => this.assignVariable(state, name, value));
|
|
2352
|
+
assertOpen();
|
|
2353
|
+
},
|
|
2354
|
+
prepareReference: async (reference) => {
|
|
2355
|
+
assertOpen();
|
|
2356
|
+
let owned;
|
|
2357
|
+
let closed = false;
|
|
2358
|
+
let completion;
|
|
2359
|
+
const work = new Set();
|
|
2360
|
+
const close = () => {
|
|
2361
|
+
closed = true;
|
|
2362
|
+
return completion ??= Promise.resolve().then(async () => {
|
|
2363
|
+
await Promise.allSettled([...work]);
|
|
2364
|
+
try {
|
|
2365
|
+
owned?.close();
|
|
2366
|
+
}
|
|
2367
|
+
finally {
|
|
2368
|
+
references.delete(close);
|
|
2369
|
+
}
|
|
2370
|
+
});
|
|
2371
|
+
};
|
|
2372
|
+
references.add(close);
|
|
2373
|
+
const failure = (value, suffix, prefix = "") => {
|
|
2374
|
+
const diagnostic = concatShellValues([prefix, value, suffix], allocation);
|
|
2375
|
+
allocation.hold(diagnostic);
|
|
2376
|
+
return Object.freeze({ ok: false, diagnostic });
|
|
2377
|
+
};
|
|
1952
2378
|
try {
|
|
1953
|
-
|
|
2379
|
+
owned = this.budget.values.scope();
|
|
2380
|
+
owned.hold(reference);
|
|
2381
|
+
const text = shellValueText(reference);
|
|
2382
|
+
const match = /^([a-zA-Z_][a-zA-Z_0-9]*)(?:\[([^\[\]]+)\])?$/u.exec(text);
|
|
2383
|
+
if (!match) {
|
|
2384
|
+
const result = failure(reference, "': not a valid identifier", "`");
|
|
2385
|
+
await close();
|
|
2386
|
+
return result;
|
|
2387
|
+
}
|
|
2388
|
+
const name = match[1];
|
|
2389
|
+
const subscript = match[2];
|
|
2390
|
+
const reservation = owned.reserve(128 + text.length * 2, 1);
|
|
2391
|
+
const run = (action) => {
|
|
2392
|
+
try {
|
|
2393
|
+
assertOpen();
|
|
2394
|
+
if (closed)
|
|
2395
|
+
throw new Error("Binding reference is closed");
|
|
2396
|
+
if (work.size)
|
|
2397
|
+
throw new Error("Binding reference is busy");
|
|
2398
|
+
const pending = scope.run(() => Promise.resolve().then(() => {
|
|
2399
|
+
this.signal.throwIfAborted();
|
|
2400
|
+
return action();
|
|
2401
|
+
}));
|
|
2402
|
+
work.add(pending);
|
|
2403
|
+
void pending.then(() => { work.delete(pending); }, () => { work.delete(pending); });
|
|
2404
|
+
return pending;
|
|
2405
|
+
}
|
|
2406
|
+
catch (error) {
|
|
2407
|
+
return Promise.reject(error);
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
const handle = Object.freeze({
|
|
2411
|
+
unbindName: () => run(async () => {
|
|
2412
|
+
if (state.readonlyVariables?.has(text))
|
|
2413
|
+
return failure(reference, ": cannot unset: readonly variable");
|
|
2414
|
+
if (arrayStore(state)?.get(text))
|
|
2415
|
+
await this.unsetIndexed(state, text);
|
|
2416
|
+
else
|
|
2417
|
+
this.unsetVariable(state, text);
|
|
2418
|
+
return Object.freeze({ ok: true, value: undefined });
|
|
2419
|
+
}),
|
|
2420
|
+
assignInteger: (value) => run(async () => {
|
|
2421
|
+
if (!Number.isSafeInteger(value))
|
|
2422
|
+
throw new TypeError("Binding integer must be a safe integer");
|
|
2423
|
+
if (state.readonlyVariables?.has(name))
|
|
2424
|
+
return failure(name, ": readonly variable");
|
|
2425
|
+
if (subscript === undefined)
|
|
2426
|
+
await this.assignVariable(state, name, String(value));
|
|
2427
|
+
else {
|
|
2428
|
+
const index = literalIndex(subscript, name.length + 1);
|
|
2429
|
+
await this.arrayAssignment({
|
|
2430
|
+
kind: "element", name, index, append: false,
|
|
2431
|
+
value: { offset: 0, parts: [{ kind: "text", value: String(value), quoted: true }] },
|
|
2432
|
+
}, state, io);
|
|
2433
|
+
}
|
|
2434
|
+
return Object.freeze({ ok: true, value: undefined });
|
|
2435
|
+
}),
|
|
2436
|
+
close,
|
|
2437
|
+
});
|
|
2438
|
+
reservation.commit(handle);
|
|
2439
|
+
return Object.freeze({ ok: true, value: handle });
|
|
1954
2440
|
}
|
|
1955
2441
|
catch (error) {
|
|
1956
|
-
|
|
1957
|
-
|
|
2442
|
+
try {
|
|
2443
|
+
await close();
|
|
2444
|
+
}
|
|
2445
|
+
catch (cleanup) {
|
|
2446
|
+
scope.failures.push(cleanup);
|
|
2447
|
+
}
|
|
1958
2448
|
throw error;
|
|
1959
2449
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
const
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2450
|
+
},
|
|
2451
|
+
openIndexed: async (name, options = {}) => {
|
|
2452
|
+
checkName(name);
|
|
2453
|
+
const clear = options.clear;
|
|
2454
|
+
if (clear !== undefined && typeof clear !== "boolean")
|
|
2455
|
+
throw new TypeError("Invalid indexed writer options");
|
|
2456
|
+
return this.incrementalIndexed(state, io, name, clear === true);
|
|
2457
|
+
},
|
|
2458
|
+
prepare: async (name, options) => {
|
|
2459
|
+
checkName(name);
|
|
2460
|
+
if (options.kind !== "indexed" || options.clear !== undefined && typeof options.clear !== "boolean")
|
|
2461
|
+
throw new TypeError("Unsupported binding preparation");
|
|
2462
|
+
if (state.readonlyVariables?.has(name))
|
|
2463
|
+
throw new ArrayFailure("readonly binding");
|
|
2464
|
+
if (controlNames.has(name))
|
|
2465
|
+
throw new ArrayFailure("control binding cannot be indexed");
|
|
2466
|
+
if (state.exported.has(name))
|
|
2467
|
+
throw new ArrayFailure("exported binding cannot be indexed");
|
|
2468
|
+
const clear = options.clear === true;
|
|
2469
|
+
const store = requireArrays(state);
|
|
2470
|
+
let prepared;
|
|
2471
|
+
let preparing;
|
|
2472
|
+
let active;
|
|
2473
|
+
let closed = false;
|
|
2474
|
+
let completion;
|
|
2475
|
+
const close = () => {
|
|
2476
|
+
closed = true;
|
|
2477
|
+
return completion ??= (async () => {
|
|
2478
|
+
await Promise.allSettled([preparing, active]);
|
|
2479
|
+
await prepared?.close();
|
|
2480
|
+
})();
|
|
2481
|
+
};
|
|
2482
|
+
scope.register(close);
|
|
2483
|
+
const validate = () => {
|
|
2484
|
+
assertOpen();
|
|
2485
|
+
if (closed)
|
|
2486
|
+
throw new ArrayFailure("binding transaction is closed");
|
|
2487
|
+
if (state.readonlyVariables?.has(name))
|
|
2488
|
+
throw new ArrayFailure("readonly binding");
|
|
2489
|
+
if (state.exported.has(name))
|
|
2490
|
+
throw new ArrayFailure("exported binding cannot be indexed");
|
|
2491
|
+
prepared.validate();
|
|
2492
|
+
};
|
|
2493
|
+
const run = (action) => {
|
|
2494
|
+
try {
|
|
2495
|
+
validate();
|
|
2496
|
+
if (active)
|
|
2497
|
+
throw new ArrayFailure("binding transaction is busy");
|
|
2498
|
+
const work = scope.run(action);
|
|
2499
|
+
active = work;
|
|
2500
|
+
void work.then(() => { if (active === work)
|
|
2501
|
+
active = undefined; }, () => { if (active === work)
|
|
2502
|
+
active = undefined; });
|
|
2503
|
+
return work;
|
|
2504
|
+
}
|
|
2505
|
+
catch (error) {
|
|
2506
|
+
return Promise.reject(error);
|
|
2507
|
+
}
|
|
1978
2508
|
};
|
|
1979
|
-
const runtime = new Runtime(runtimeFileSystems.get(this.fs) ?? this.fs, this.commands, this.middleware, this.budget, AbortSignal.any([this.commandSignal, scope.signal]), this.fileWrites, this.outputFiles, this.commandSignal, this.cancellation, this.cancellationState, this.cancellationOwner, this.cancellationDepth, this.cancellationMaxDepth, undefined, this.inputProfile);
|
|
1980
2509
|
try {
|
|
1981
|
-
|
|
1982
|
-
|
|
2510
|
+
preparing = scope.run(async () => {
|
|
2511
|
+
prepared = await store.prepare(name, this.signal, !clear);
|
|
2512
|
+
validate();
|
|
2513
|
+
if (!clear && !store.get(name) && state.variables[name] !== undefined) {
|
|
2514
|
+
const value = stateMonitor(state).values.get(name, state.variables[name]);
|
|
2515
|
+
const token = await textToken(prepared.binding.owner, value, this.signal);
|
|
1983
2516
|
try {
|
|
1984
|
-
|
|
2517
|
+
validate();
|
|
2518
|
+
prepared.binding.insert(0, token);
|
|
1985
2519
|
}
|
|
1986
2520
|
catch (error) {
|
|
1987
|
-
|
|
1988
|
-
return error.status;
|
|
2521
|
+
token.release();
|
|
1989
2522
|
throw error;
|
|
1990
2523
|
}
|
|
1991
2524
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2525
|
+
validate();
|
|
2526
|
+
});
|
|
2527
|
+
await preparing;
|
|
2528
|
+
return Object.freeze({
|
|
2529
|
+
get: (index) => { validate(); checkIndex(index); return retain(prepared.binding.getValue(index)); },
|
|
2530
|
+
set: (index, value) => run(async () => {
|
|
2531
|
+
checkIndex(index);
|
|
2532
|
+
const token = await textToken(prepared.binding.owner, value, this.signal);
|
|
2533
|
+
try {
|
|
2534
|
+
validate();
|
|
2535
|
+
prepared.binding.insert(index, token);
|
|
2536
|
+
}
|
|
2537
|
+
catch (error) {
|
|
2538
|
+
token.release();
|
|
2539
|
+
throw error;
|
|
2540
|
+
}
|
|
2541
|
+
}),
|
|
2542
|
+
unset: (index) => run(async () => {
|
|
2543
|
+
checkIndex(index);
|
|
2544
|
+
const binding = prepared.binding;
|
|
2545
|
+
binding.values.get(index)?.slot.release();
|
|
2546
|
+
let maximum = -1;
|
|
2547
|
+
for (const key of binding.values.keys()) {
|
|
2548
|
+
maximum = Math.max(maximum, key);
|
|
2549
|
+
await binding.owner.ledger.checkpoint(this.signal, 2);
|
|
2550
|
+
validate();
|
|
2551
|
+
}
|
|
2552
|
+
binding.maximum = maximum;
|
|
2553
|
+
}),
|
|
2554
|
+
commit: () => run(async () => {
|
|
2555
|
+
await this.prepareArrayObservers(state, prepared.binding.owner);
|
|
2556
|
+
const supersede = await stateMonitor(state).prepareTypedPublication(name, prepared.binding.owner, this.signal);
|
|
2557
|
+
validate();
|
|
2558
|
+
let retirement;
|
|
2559
|
+
stateMonitor(state).publish(prepared.tickets, name, () => {
|
|
2560
|
+
supersede();
|
|
2561
|
+
delete state.variables[name];
|
|
2562
|
+
retirement = prepared.publish();
|
|
2563
|
+
});
|
|
2564
|
+
await retirement;
|
|
2565
|
+
this.signal.throwIfAborted();
|
|
2566
|
+
}),
|
|
2567
|
+
close,
|
|
1996
2568
|
});
|
|
1997
2569
|
}
|
|
1998
2570
|
catch (error) {
|
|
1999
|
-
|
|
2000
|
-
await scope.close();
|
|
2571
|
+
await close();
|
|
2001
2572
|
throw error;
|
|
2002
2573
|
}
|
|
2003
|
-
|
|
2004
|
-
|
|
2574
|
+
},
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
2577
|
+
async incrementalIndexed(state, io, name, clear) {
|
|
2578
|
+
const scope = io[invocationScope];
|
|
2579
|
+
scope.assertOpen();
|
|
2580
|
+
this.signal.throwIfAborted();
|
|
2581
|
+
if (state.readonlyVariables?.has(name))
|
|
2582
|
+
throw new ArrayFailure("readonly binding");
|
|
2583
|
+
if (controlNames.has(name))
|
|
2584
|
+
throw new ArrayFailure("control binding cannot be indexed");
|
|
2585
|
+
const store = requireArrays(state);
|
|
2586
|
+
const owner = store.owner;
|
|
2587
|
+
const monitor = stateMonitor(state);
|
|
2588
|
+
let lifetime;
|
|
2589
|
+
let holding;
|
|
2590
|
+
let identity;
|
|
2591
|
+
let admittedLocal;
|
|
2592
|
+
let preparing;
|
|
2593
|
+
let active;
|
|
2594
|
+
let closed = false;
|
|
2595
|
+
let completion;
|
|
2596
|
+
const close = () => {
|
|
2597
|
+
closed = true;
|
|
2598
|
+
return completion ??= (async () => {
|
|
2599
|
+
await Promise.allSettled([preparing, active]);
|
|
2600
|
+
try {
|
|
2601
|
+
await lifetime?.close();
|
|
2602
|
+
}
|
|
2603
|
+
finally {
|
|
2604
|
+
holding?.release();
|
|
2605
|
+
}
|
|
2606
|
+
})();
|
|
2607
|
+
};
|
|
2608
|
+
scope.register(close);
|
|
2609
|
+
const localIdentity = () => {
|
|
2610
|
+
for (let index = state.locals.length - 1; index >= 0; index--) {
|
|
2611
|
+
owner.reserve({ work: 2 }).release();
|
|
2612
|
+
const saved = state.locals[index].get(name);
|
|
2613
|
+
if (saved)
|
|
2614
|
+
return saved;
|
|
2615
|
+
}
|
|
2616
|
+
return undefined;
|
|
2617
|
+
};
|
|
2618
|
+
const validate = () => {
|
|
2619
|
+
this.signal.throwIfAborted();
|
|
2620
|
+
scope.assertOpen();
|
|
2621
|
+
if (closed)
|
|
2622
|
+
throw new ArrayFailure("indexed writer is closed");
|
|
2623
|
+
owner.assertOpen();
|
|
2624
|
+
if (arrayStore(state) !== store || store.owner !== owner || identity !== undefined && store.bindings.get(name) !== identity)
|
|
2625
|
+
throw new ArrayFailure("incremental target identity changed");
|
|
2626
|
+
if (localIdentity() !== admittedLocal)
|
|
2627
|
+
throw new ArrayFailure("incremental target local identity changed");
|
|
2628
|
+
};
|
|
2629
|
+
try {
|
|
2630
|
+
admittedLocal = localIdentity();
|
|
2631
|
+
preparing = scope.run(async () => {
|
|
2632
|
+
holding = owner.hold();
|
|
2633
|
+
lifetime = ArrayOwner.create(owner.ledger, owner);
|
|
2634
|
+
lifetime.reserve({ metadata: 192, work: 12 });
|
|
2635
|
+
await textToken(lifetime, name, this.signal);
|
|
2636
|
+
const watch = await store.watch(name, lifetime, this.signal);
|
|
2637
|
+
try {
|
|
2638
|
+
await this.prepareArrayObservers(state, lifetime);
|
|
2639
|
+
validate();
|
|
2640
|
+
if (state.readonlyVariables?.has(name))
|
|
2641
|
+
throw new ArrayFailure("readonly binding");
|
|
2642
|
+
if (!watch.valid())
|
|
2643
|
+
throw new ArrayFailure("binding changed during writer admission");
|
|
2644
|
+
if (clear || !store.get(name)) {
|
|
2645
|
+
const prepared = await store.prepare(name, this.signal, false);
|
|
2646
|
+
try {
|
|
2647
|
+
if (!clear && state.variables[name] !== undefined) {
|
|
2648
|
+
const value = monitor.values.get(name, state.variables[name]);
|
|
2649
|
+
const token = await textToken(prepared.binding.owner, value, this.signal);
|
|
2650
|
+
try {
|
|
2651
|
+
prepared.validate();
|
|
2652
|
+
prepared.binding.insert(0, token);
|
|
2653
|
+
}
|
|
2654
|
+
catch (error) {
|
|
2655
|
+
token.release();
|
|
2656
|
+
throw error;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
validate();
|
|
2660
|
+
if (state.readonlyVariables?.has(name))
|
|
2661
|
+
throw new ArrayFailure("readonly binding");
|
|
2662
|
+
if (!watch.valid())
|
|
2663
|
+
throw new ArrayFailure("binding changed during writer admission");
|
|
2664
|
+
prepared.validate();
|
|
2665
|
+
let retirement;
|
|
2666
|
+
monitor.publish(prepared.tickets, name, () => {
|
|
2667
|
+
delete state.variables[name];
|
|
2668
|
+
retirement = prepared.publish();
|
|
2669
|
+
});
|
|
2670
|
+
await retirement;
|
|
2671
|
+
}
|
|
2672
|
+
finally {
|
|
2673
|
+
await prepared.close();
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
identity = store.bindings.get(name);
|
|
2677
|
+
if (!identity)
|
|
2678
|
+
throw new ArrayFailure("incremental target is not indexed");
|
|
2679
|
+
validate();
|
|
2680
|
+
}
|
|
2681
|
+
finally {
|
|
2682
|
+
watch.close();
|
|
2683
|
+
}
|
|
2684
|
+
});
|
|
2685
|
+
await preparing;
|
|
2686
|
+
}
|
|
2687
|
+
catch (error) {
|
|
2688
|
+
try {
|
|
2689
|
+
await close();
|
|
2690
|
+
}
|
|
2691
|
+
catch (cleanup) {
|
|
2692
|
+
scope.failures.push(cleanup);
|
|
2693
|
+
}
|
|
2694
|
+
throw error;
|
|
2695
|
+
}
|
|
2696
|
+
return Object.freeze({
|
|
2697
|
+
set: (index, value) => {
|
|
2698
|
+
try {
|
|
2699
|
+
validate();
|
|
2700
|
+
if (!Number.isSafeInteger(index) || index < 0 || index > 4294967295)
|
|
2701
|
+
throw new ArrayFailure("index outside 0..4294967295");
|
|
2702
|
+
if (active)
|
|
2703
|
+
throw new ArrayFailure("indexed writer is busy");
|
|
2704
|
+
const work = scope.run(async () => {
|
|
2705
|
+
const current = store.get(name);
|
|
2706
|
+
const operation = ArrayOwner.create(owner.ledger, lifetime);
|
|
2707
|
+
let retained = false;
|
|
2708
|
+
let staged;
|
|
2709
|
+
let token;
|
|
2710
|
+
try {
|
|
2711
|
+
current.retain();
|
|
2712
|
+
retained = true;
|
|
2713
|
+
const watch = await store.watch(name, operation, this.signal);
|
|
2714
|
+
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
2715
|
+
await this.prepareArrayObservers(state, operation);
|
|
2716
|
+
token = await textToken(operation, value, this.signal);
|
|
2717
|
+
validate();
|
|
2718
|
+
if (!watch.valid() || store.get(name) !== current)
|
|
2719
|
+
throw new ArrayFailure("incremental target changed during write");
|
|
2720
|
+
if (current.references > 2)
|
|
2721
|
+
staged = await current.copy(this.signal);
|
|
2722
|
+
validate();
|
|
2723
|
+
if (!watch.valid() || store.get(name) !== current)
|
|
2724
|
+
throw new ArrayFailure("incremental target changed during write");
|
|
2725
|
+
const target = staged ?? current;
|
|
2726
|
+
let retirement;
|
|
2727
|
+
monitor.publish(tickets, name, () => {
|
|
2728
|
+
target.insert(index, token);
|
|
2729
|
+
token = undefined;
|
|
2730
|
+
if (staged)
|
|
2731
|
+
retirement = store.publish(name, staged, tickets);
|
|
2732
|
+
else
|
|
2733
|
+
store.revise(name, current, tickets);
|
|
2734
|
+
});
|
|
2735
|
+
staged = undefined;
|
|
2736
|
+
watch.close();
|
|
2737
|
+
await retirement;
|
|
2738
|
+
this.signal.throwIfAborted();
|
|
2739
|
+
}
|
|
2740
|
+
finally {
|
|
2741
|
+
token?.release();
|
|
2742
|
+
try {
|
|
2743
|
+
await staged?.release();
|
|
2744
|
+
}
|
|
2745
|
+
finally {
|
|
2746
|
+
try {
|
|
2747
|
+
if (retained)
|
|
2748
|
+
await current.release();
|
|
2749
|
+
}
|
|
2750
|
+
finally {
|
|
2751
|
+
await operation.close();
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
active = work;
|
|
2757
|
+
void work.then(() => { if (active === work)
|
|
2758
|
+
active = undefined; }, () => { if (active === work)
|
|
2759
|
+
active = undefined; });
|
|
2760
|
+
return work;
|
|
2761
|
+
}
|
|
2762
|
+
catch (error) {
|
|
2763
|
+
return Promise.reject(error);
|
|
2764
|
+
}
|
|
2765
|
+
},
|
|
2766
|
+
close,
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
extensionInput(state, io) {
|
|
2770
|
+
const scope = io[invocationScope];
|
|
2771
|
+
const assertOpen = () => { this.signal.throwIfAborted(); scope.assertOpen(); };
|
|
2772
|
+
assertOpen();
|
|
2773
|
+
const checkDescriptor = (descriptor) => {
|
|
2774
|
+
assertOpen();
|
|
2775
|
+
if (!Number.isSafeInteger(descriptor) || descriptor < 0)
|
|
2776
|
+
throw new RangeError("Invalid input descriptor");
|
|
2777
|
+
};
|
|
2778
|
+
return Object.freeze({ validateOpen: (descriptor) => {
|
|
2779
|
+
checkDescriptor(descriptor);
|
|
2780
|
+
const entry = io.descriptors?.get(descriptor);
|
|
2781
|
+
const input = entry?.input ?? (!io.descriptors && descriptor === 0 ? io.stdin : undefined);
|
|
2782
|
+
const output = entry?.output ?? (!io.descriptors ? descriptor === 1 ? io.stdout : descriptor === 2 ? io.stderr : undefined : undefined);
|
|
2783
|
+
if (entry?.closed || (!input || input === closedSource) && (!output || output === closedSink))
|
|
2784
|
+
throw new FsError("EBADF", { message: "Closed input descriptor" });
|
|
2785
|
+
}, observe: (descriptor) => {
|
|
2786
|
+
checkDescriptor(descriptor);
|
|
2787
|
+
const entry = io.descriptors?.get(descriptor);
|
|
2788
|
+
const input = entry?.input ?? (!io.descriptors && descriptor === 0 ? io.stdin : undefined);
|
|
2789
|
+
const output = entry?.output ?? (!io.descriptors ? descriptor === 1 ? io.stdout : descriptor === 2 ? io.stderr : undefined : undefined);
|
|
2790
|
+
if (entry?.closed || (!input || input === closedSource) && (!output || output === closedSink))
|
|
2791
|
+
throw new FsError("EBADF", { syscall: "observe" });
|
|
2792
|
+
const readable = input !== undefined && input !== closedSource;
|
|
2793
|
+
if (entry?.pipe)
|
|
2794
|
+
return observeDescriptor(pipeObservation(entry.pipe.endpoint, input instanceof ShellInput ? () => input.probeRead() : undefined), scope, this.budget, this.signal);
|
|
2795
|
+
const file = (input instanceof ShellInput ? input.descriptor : undefined) ?? entry?.file;
|
|
2796
|
+
const stat = file?.stat.bind(file);
|
|
2797
|
+
const method = file?.capabilities.readObservation === true ? file.probeRead : undefined;
|
|
2798
|
+
if (file?.capabilities.readObservation === true && typeof method !== "function")
|
|
2799
|
+
throw new FsError("ENOTSUP", { syscall: "probeRead" });
|
|
2800
|
+
const probe = method?.bind(file);
|
|
2801
|
+
return observeDescriptor({ readable, async probeRead(signal) {
|
|
2802
|
+
signal.throwIfAborted();
|
|
2803
|
+
const local = input instanceof ShellInput ? input.probeRead() : undefined;
|
|
2804
|
+
if (!file && local)
|
|
2805
|
+
return local;
|
|
2806
|
+
const timeout = local?.timeout ?? (stat && (await stat({ signal })).type === "file" ? "ignore" : "unknown");
|
|
2807
|
+
if (input instanceof ShellInput && input.bufferedBytes > 0)
|
|
2808
|
+
return { readiness: "ready", timeout };
|
|
2809
|
+
if (probe)
|
|
2810
|
+
return { readiness: await probe({ signal }), timeout };
|
|
2811
|
+
if (local)
|
|
2812
|
+
return local;
|
|
2813
|
+
return { readiness: timeout === "ignore" ? "ready" : "unknown", timeout };
|
|
2814
|
+
} }, scope, this.budget, this.signal);
|
|
2815
|
+
}, borrow: (descriptor) => {
|
|
2816
|
+
checkDescriptor(descriptor);
|
|
2817
|
+
const entry = io.descriptors?.get(descriptor);
|
|
2818
|
+
const source = entry?.closed ? undefined : entry?.input ?? (descriptor === 0 && !io.descriptors ? io.stdin : undefined);
|
|
2819
|
+
if (!source)
|
|
2820
|
+
throw new FsError("EBADF", { message: "Unreadable input descriptor" });
|
|
2821
|
+
if (!(source instanceof ShellInput))
|
|
2822
|
+
throw new FsError("ENOTSUP", { message: "Input descriptor has no enrolled shared cursor" });
|
|
2823
|
+
const resource = {};
|
|
2824
|
+
let closed = false;
|
|
2825
|
+
let completion;
|
|
2826
|
+
const release = () => {
|
|
2827
|
+
closed = true;
|
|
2828
|
+
return completion ??= Promise.resolve().then(() => resource.input?.close());
|
|
2829
|
+
};
|
|
2830
|
+
scope.register(release);
|
|
2831
|
+
const input = new ShellInput(source, this.budget, this.signal);
|
|
2832
|
+
resource.input = input;
|
|
2833
|
+
const stdinIsDefault = entry?.stdinIsDefault ?? (descriptor === 0 ? io.stdinIsDefault : undefined);
|
|
2834
|
+
return Object.freeze({
|
|
2835
|
+
...(stdinIsDefault === undefined ? {} : { stdinIsDefault }),
|
|
2836
|
+
read: async (raw, options = {}) => {
|
|
2837
|
+
assertOpen();
|
|
2838
|
+
if (closed)
|
|
2839
|
+
throw new Error("Input borrow is closed");
|
|
2840
|
+
const { count, delimiter, exact, timeoutMs } = options;
|
|
2841
|
+
if (typeof raw !== "boolean" || count !== undefined && (!Number.isSafeInteger(count) || count < 0) || delimiter !== undefined && (!Number.isInteger(delimiter) || delimiter < 0 || delimiter > 255) || exact !== undefined && typeof exact !== "boolean" || timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0))
|
|
2842
|
+
throw new TypeError("Invalid input read options");
|
|
2843
|
+
return scope.run(() => input.line(raw, { ...(count === undefined ? {} : { count }), ...(delimiter === undefined ? {} : { delimiter }), ...(exact === undefined ? {} : { exact }), ...(timeoutMs === undefined ? {} : { timeoutMs }), byteCount: byteLocale(state.variables) }));
|
|
2844
|
+
},
|
|
2845
|
+
record: async (options = {}) => {
|
|
2846
|
+
assertOpen();
|
|
2847
|
+
if (closed)
|
|
2848
|
+
throw new Error("Input borrow is closed");
|
|
2849
|
+
const { delimiter } = options;
|
|
2850
|
+
if (delimiter !== undefined && (!Number.isInteger(delimiter) || delimiter < 0 || delimiter > 255))
|
|
2851
|
+
throw new TypeError("Invalid input record options");
|
|
2852
|
+
return scope.run(() => input.record(delimiter === undefined ? {} : { delimiter }));
|
|
2853
|
+
},
|
|
2854
|
+
readiness: () => {
|
|
2855
|
+
assertOpen();
|
|
2856
|
+
if (closed)
|
|
2857
|
+
throw new Error("Input borrow is closed");
|
|
2858
|
+
return input.readiness();
|
|
2859
|
+
},
|
|
2860
|
+
release,
|
|
2861
|
+
});
|
|
2862
|
+
} });
|
|
2863
|
+
}
|
|
2864
|
+
async startExtensions(state, io) {
|
|
2865
|
+
const frame = state.extensions;
|
|
2866
|
+
if (!frame || frame.started)
|
|
2867
|
+
return;
|
|
2868
|
+
frame.started = true;
|
|
2869
|
+
for (const [name, builtin] of frame.builtins)
|
|
2870
|
+
if (shellBuiltinNames.has(name) && builtin.replace !== true)
|
|
2871
|
+
throw new TypeError(`Extension builtin conflicts with existing builtin: ${name}`);
|
|
2872
|
+
for (const entry of frame.entries)
|
|
2873
|
+
await entry.instance.start?.(this.extensionContext(state, io));
|
|
2874
|
+
}
|
|
2875
|
+
async extensionEvent(event, state, io, status, command = "") {
|
|
2876
|
+
if (!state.extensions)
|
|
2877
|
+
return false;
|
|
2878
|
+
this.signal.throwIfAborted();
|
|
2879
|
+
await this.startExtensions(state, io);
|
|
2880
|
+
const previous = state.status;
|
|
2881
|
+
state.status = status;
|
|
2882
|
+
state.extensions.eventDepth = (state.extensions.eventDepth ?? 0) + 1;
|
|
2883
|
+
try {
|
|
2884
|
+
for (const entry of state.extensions.entries) {
|
|
2885
|
+
this.signal.throwIfAborted();
|
|
2886
|
+
const result = await entry.instance.event?.(event, this.extensionContext(state, io, command));
|
|
2887
|
+
if (result !== undefined) {
|
|
2888
|
+
if (event !== "command" || !result || (result.action !== "skip" && result.action !== "return"))
|
|
2889
|
+
throw new TypeError("Invalid extension lifecycle control");
|
|
2890
|
+
if (result.action === "return")
|
|
2891
|
+
throw new Flow("return", validateExitCode(result.status), 1, status);
|
|
2892
|
+
return true;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
return false;
|
|
2896
|
+
}
|
|
2897
|
+
finally {
|
|
2898
|
+
state.extensions.eventDepth--;
|
|
2899
|
+
if (!this.signal.aborted)
|
|
2900
|
+
state.status = previous;
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
async extensionCheckpoint(point, state, io) {
|
|
2904
|
+
try {
|
|
2905
|
+
for (const checkpoint of state.extensions.checkpoints) {
|
|
2906
|
+
await io[invocationScope].run(() => Promise.resolve().then(async () => {
|
|
2907
|
+
const context = this.extensionContext(state, io);
|
|
2908
|
+
context.signal.throwIfAborted();
|
|
2909
|
+
await checkpoint(point, context);
|
|
2910
|
+
context.signal.throwIfAborted();
|
|
2911
|
+
}));
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
catch (reason) {
|
|
2915
|
+
this.signal.throwIfAborted();
|
|
2916
|
+
if (reason instanceof Flow)
|
|
2917
|
+
throw reason;
|
|
2918
|
+
throw new ExtensionCheckpointFailure(reason);
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
async beginShellExit(state, io, status) {
|
|
2922
|
+
const frame = state.extensions;
|
|
2923
|
+
if (!frame || frame.exiting)
|
|
2924
|
+
return status;
|
|
2925
|
+
if (frame.exitStatus !== undefined)
|
|
2926
|
+
return frame.exitStatus;
|
|
2927
|
+
this.signal.throwIfAborted();
|
|
2928
|
+
state = trackState(state, this.budget, io[invocationScope]);
|
|
2929
|
+
frame.exiting = true;
|
|
2930
|
+
try {
|
|
2931
|
+
try {
|
|
2932
|
+
await this.extensionEvent("exit", state, io, status);
|
|
2933
|
+
}
|
|
2934
|
+
catch (error) {
|
|
2935
|
+
if (error instanceof Flow && error.kind === "exit" && !(error instanceof ExtensionCheckpointFailure)) {
|
|
2936
|
+
if (!error.expansionFailure)
|
|
2937
|
+
status = error.status;
|
|
2938
|
+
}
|
|
2939
|
+
else
|
|
2940
|
+
throw error;
|
|
2941
|
+
}
|
|
2942
|
+
this.signal.throwIfAborted();
|
|
2943
|
+
}
|
|
2944
|
+
catch (reason) {
|
|
2945
|
+
extensionExitFailures.set(frame, { reason });
|
|
2946
|
+
}
|
|
2947
|
+
finally {
|
|
2948
|
+
frame.exiting = false;
|
|
2949
|
+
}
|
|
2950
|
+
frame.exitStatus = status;
|
|
2951
|
+
return status;
|
|
2952
|
+
}
|
|
2953
|
+
async finishShell(state, io, status) {
|
|
2954
|
+
const frame = state.extensions;
|
|
2955
|
+
if (!frame || frame.exiting)
|
|
2956
|
+
return status;
|
|
2957
|
+
let failure;
|
|
2958
|
+
try {
|
|
2959
|
+
status = await this.beginShellExit(state, io, status);
|
|
2960
|
+
failure = extensionExitFailures.get(frame);
|
|
2961
|
+
}
|
|
2962
|
+
catch (reason) {
|
|
2963
|
+
failure = { reason };
|
|
2964
|
+
}
|
|
2965
|
+
try {
|
|
2966
|
+
await this.releaseExtensions(state);
|
|
2967
|
+
}
|
|
2968
|
+
catch (cleanup) {
|
|
2969
|
+
this.signal.throwIfAborted();
|
|
2970
|
+
if (failure?.reason instanceof ExtensionCheckpointFailure)
|
|
2971
|
+
io[invocationScope].failures.push(cleanup);
|
|
2972
|
+
else {
|
|
2973
|
+
if (failure)
|
|
2974
|
+
throw new AggregateError([failure.reason, cleanup], "Shell completion and extension cleanup failed");
|
|
2975
|
+
throw cleanup;
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
this.signal.throwIfAborted();
|
|
2979
|
+
if (failure)
|
|
2980
|
+
throw failure.reason instanceof ExtensionCheckpointFailure && !state.isolated ? failure.reason.reason : failure.reason;
|
|
2981
|
+
return status;
|
|
2982
|
+
}
|
|
2983
|
+
async releaseExtensions(state) {
|
|
2984
|
+
const cleanup = await Promise.allSettled(state.extensions?.cleanup.map(close => close()) ?? []);
|
|
2985
|
+
this.signal.throwIfAborted();
|
|
2986
|
+
throwCleanupFailures(cleanup.filter(result => result.status === "rejected").map(result => result.reason));
|
|
2987
|
+
}
|
|
2988
|
+
async finishReturn(event, state, io, status, previous = status) {
|
|
2989
|
+
while (true) {
|
|
2990
|
+
try {
|
|
2991
|
+
await this.extensionEvent(event, state, io, previous);
|
|
2992
|
+
return status;
|
|
2993
|
+
}
|
|
2994
|
+
catch (error) {
|
|
2995
|
+
if (error instanceof ExtensionCheckpointFailure)
|
|
2996
|
+
throw error;
|
|
2997
|
+
if (error instanceof Flow && error.kind === "return") {
|
|
2998
|
+
status = error.status;
|
|
2999
|
+
previous = error.previousStatus ?? status;
|
|
3000
|
+
continue;
|
|
3001
|
+
}
|
|
3002
|
+
if (error instanceof Flow && error.kind === "exit")
|
|
3003
|
+
throw new Flow("exit", await this.beginShellExit(state, io, error.status), 1, error.previousStatus);
|
|
3004
|
+
throw error;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
async runUnit(script, state, io) {
|
|
3009
|
+
state = trackState(state, this.budget, io[invocationScope]);
|
|
3010
|
+
try {
|
|
3011
|
+
await this.startExtensions(state, io);
|
|
3012
|
+
return { exitCode: await this.inputUnit(script, state, io), terminated: false };
|
|
3013
|
+
}
|
|
3014
|
+
catch (error) {
|
|
3015
|
+
if (error instanceof NounsetDiagnosticFailure) {
|
|
3016
|
+
if (state.isolated)
|
|
3017
|
+
throw error;
|
|
3018
|
+
throw error.reason;
|
|
3019
|
+
}
|
|
3020
|
+
if (error instanceof Flow && error.kind === "exit")
|
|
3021
|
+
return { exitCode: await this.finishShell(state, io, error.status), terminated: true };
|
|
3022
|
+
throw error;
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
async inputUnit(script, state, io) {
|
|
3026
|
+
try {
|
|
3027
|
+
return await this.script(script, state, io);
|
|
3028
|
+
}
|
|
3029
|
+
catch (error) {
|
|
3030
|
+
if (!(error instanceof Flow) || error.kind !== "discard")
|
|
3031
|
+
throw error;
|
|
3032
|
+
this.signal.throwIfAborted();
|
|
3033
|
+
throwCleanupFailures(io[invocationScope].failures);
|
|
3034
|
+
state.status = error.status;
|
|
3035
|
+
return error.status;
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
async script(script, state, io) {
|
|
3039
|
+
if (state.extensions?.syntax.indexedDeclarations?.includes("readonly"))
|
|
3040
|
+
io.assignmentDiagnosticContext ??= { name: undefined };
|
|
3041
|
+
for (const list of script.lists) {
|
|
3042
|
+
if (list.terminator) {
|
|
3043
|
+
const hook = state.extensions?.listTerminators.get(list.terminator.operator);
|
|
3044
|
+
if (!hook)
|
|
3045
|
+
throw new TypeError("Missing captured shell list terminator handler");
|
|
3046
|
+
let admissionOpen = true;
|
|
3047
|
+
let preparation;
|
|
3048
|
+
let failure;
|
|
3049
|
+
let status = 0;
|
|
3050
|
+
try {
|
|
3051
|
+
status = validateExitCode(await hook.execute({
|
|
3052
|
+
...this.extensionContext(state, io),
|
|
3053
|
+
prepareChild: options => {
|
|
3054
|
+
if (!admissionOpen || preparation)
|
|
3055
|
+
return Promise.reject(this.commandSignal.aborted ? this.commandSignal.reason : new TypeError("Shell child preparation is single-use and dispatch-scoped"));
|
|
3056
|
+
preparation = this.prepareListChild(list, script, state, io, options);
|
|
3057
|
+
void preparation.catch(() => undefined);
|
|
3058
|
+
return preparation;
|
|
3059
|
+
},
|
|
3060
|
+
}));
|
|
3061
|
+
}
|
|
3062
|
+
catch (reason) {
|
|
3063
|
+
failure = { reason };
|
|
3064
|
+
}
|
|
3065
|
+
finally {
|
|
3066
|
+
admissionOpen = false;
|
|
3067
|
+
}
|
|
3068
|
+
await preparation?.catch(() => undefined);
|
|
3069
|
+
this.commandSignal.throwIfAborted();
|
|
3070
|
+
if (failure)
|
|
3071
|
+
throw failure.reason;
|
|
3072
|
+
state.status = status;
|
|
3073
|
+
continue;
|
|
2005
3074
|
}
|
|
2006
3075
|
for (let index = 0; index < list.pipelines.length; index++) {
|
|
2007
3076
|
const operator = list.operators[index - 1];
|
|
@@ -2024,11 +3093,108 @@ export class Runtime {
|
|
|
2024
3093
|
}
|
|
2025
3094
|
return script.lists.length ? state.status : 0;
|
|
2026
3095
|
}
|
|
3096
|
+
async prepareListChild(list, script, state, io, options) {
|
|
3097
|
+
this.signal.throwIfAborted();
|
|
3098
|
+
if (!options || !(options.signal instanceof AbortSignal) || typeof options.registerCleanup !== "function"
|
|
3099
|
+
|| options.stdin !== "inherit" && options.stdin !== "async-default")
|
|
3100
|
+
throw new TypeError("Invalid shell child preparation");
|
|
3101
|
+
const signal = AbortSignal.any([this.commandSignal, options.signal, this.budget.executionCleanup.controller.signal]);
|
|
3102
|
+
signal.throwIfAborted();
|
|
3103
|
+
const scope = new InvocationScope(signal);
|
|
3104
|
+
const references = new PipeDescriptorFrame(scope);
|
|
3105
|
+
const descriptors = new PreparedDescriptorFrame(references, this.budget);
|
|
3106
|
+
let child;
|
|
3107
|
+
let runtime;
|
|
3108
|
+
let execution;
|
|
3109
|
+
let closing;
|
|
3110
|
+
let closed = false;
|
|
3111
|
+
let ready;
|
|
3112
|
+
const acquisition = new Promise(resolve => { ready = resolve; });
|
|
3113
|
+
const close = () => {
|
|
3114
|
+
closed = true;
|
|
3115
|
+
return closing ??= Promise.resolve().then(async () => {
|
|
3116
|
+
await acquisition;
|
|
3117
|
+
await execution?.catch(() => undefined);
|
|
3118
|
+
await scope.close();
|
|
3119
|
+
if (child)
|
|
3120
|
+
stateMonitor(child)?.closeValues();
|
|
3121
|
+
throwCleanupFailures(scope.failures);
|
|
3122
|
+
});
|
|
3123
|
+
};
|
|
3124
|
+
try {
|
|
3125
|
+
options.registerCleanup(close);
|
|
3126
|
+
if (state.depth >= this.budget.limits.maxSubstitutionDepth)
|
|
3127
|
+
this.budget.fail("maxSubstitutionDepth");
|
|
3128
|
+
child = await cloneState(state, signal);
|
|
3129
|
+
if (closed)
|
|
3130
|
+
throw new TypeError("Shell child preparation is closed");
|
|
3131
|
+
signal.throwIfAborted();
|
|
3132
|
+
child.extensions = forkExtensions(state.extensions, "subshell");
|
|
3133
|
+
child.isolated = true;
|
|
3134
|
+
child.depth++;
|
|
3135
|
+
const allocation = this.budget.values.scope();
|
|
3136
|
+
scope.register(() => allocation.close());
|
|
3137
|
+
const childIO = isolateIO({ ...io, [invocationScope]: scope, [valueScope]: allocation }, references);
|
|
3138
|
+
descriptors.acquire(childIO.descriptors);
|
|
3139
|
+
Object.assign(childIO, { terminal: list.pipelines.length === 1 ? { target: list.pipelines[0], frame: descriptors } : undefined });
|
|
3140
|
+
if (options.stdin === "async-default") {
|
|
3141
|
+
const input = new ShellInput((async function* () { })(), this.budget, signal);
|
|
3142
|
+
scope.register(async () => {
|
|
3143
|
+
try {
|
|
3144
|
+
await input.close();
|
|
3145
|
+
}
|
|
3146
|
+
catch (reason) {
|
|
3147
|
+
if (!signal.aborted || !Object.is(reason, signal.reason))
|
|
3148
|
+
throw reason;
|
|
3149
|
+
}
|
|
3150
|
+
});
|
|
3151
|
+
Object.assign(childIO, { asyncDefaultInput: input });
|
|
3152
|
+
}
|
|
3153
|
+
runtime = new Runtime(this.sourceFs, this.commands, this.middleware, this.budget, signal, this.fileWrites, this.outputFiles, signal, this.cancellation, this.cancellationState, this.cancellationOwner, this.cancellationDepth, this.cancellationMaxDepth);
|
|
3154
|
+
const processId = (childIdentities.get(this.budget) ?? 1000) + 1;
|
|
3155
|
+
if (!Number.isSafeInteger(processId))
|
|
3156
|
+
throw new RangeError("Shell child identity exhausted");
|
|
3157
|
+
childIdentities.set(this.budget, processId);
|
|
3158
|
+
const childScript = { ...script, lists: [{ pipelines: list.pipelines, operators: list.operators }] };
|
|
3159
|
+
return Object.freeze({ processId, run: () => {
|
|
3160
|
+
if (closed || execution)
|
|
3161
|
+
return Promise.reject(new TypeError("Prepared shell child run is single-use"));
|
|
3162
|
+
execution = Promise.resolve().then(async () => {
|
|
3163
|
+
signal.throwIfAborted();
|
|
3164
|
+
try {
|
|
3165
|
+
return await runtime.run(childScript, child, childIO);
|
|
3166
|
+
}
|
|
3167
|
+
catch (reason) {
|
|
3168
|
+
if (reason instanceof Flow && reason.kind === "return")
|
|
3169
|
+
return runtime.finishShell(child, childIO, reason.status);
|
|
3170
|
+
if (reason instanceof NounsetDiagnosticFailure)
|
|
3171
|
+
throw reason.reason;
|
|
3172
|
+
throw reason;
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
return execution;
|
|
3176
|
+
} });
|
|
3177
|
+
}
|
|
3178
|
+
catch (reason) {
|
|
3179
|
+
ready();
|
|
3180
|
+
try {
|
|
3181
|
+
await close();
|
|
3182
|
+
}
|
|
3183
|
+
catch {
|
|
3184
|
+
this.commandSignal.throwIfAborted();
|
|
3185
|
+
}
|
|
3186
|
+
throw reason;
|
|
3187
|
+
}
|
|
3188
|
+
finally {
|
|
3189
|
+
ready();
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
2027
3192
|
async pipeline(pipeline, state, io) {
|
|
2028
3193
|
this.signal.throwIfAborted();
|
|
3194
|
+
const terminal = io.terminal?.target === pipeline ? io.terminal : undefined;
|
|
2029
3195
|
let status;
|
|
2030
3196
|
if (pipeline.commands.length === 1)
|
|
2031
|
-
status = await this.command(pipeline.commands[0], state, io, false, pipeline.negate);
|
|
3197
|
+
status = await this.command(pipeline.commands[0], state, terminal ? { ...io, terminal: { target: pipeline.commands[0], frame: terminal.frame } } : io, false, pipeline.negate);
|
|
2032
3198
|
else {
|
|
2033
3199
|
const release = this.budget.reservePipelineStages(pipeline.commands.length);
|
|
2034
3200
|
const retained = new Set();
|
|
@@ -2048,123 +3214,265 @@ export class Runtime {
|
|
|
2048
3214
|
const completed = new Set();
|
|
2049
3215
|
const closing = new Set();
|
|
2050
3216
|
let statuses;
|
|
2051
|
-
const stageLeases = [];
|
|
2052
3217
|
try {
|
|
3218
|
+
if (state.extensions && !state.extensions.eventDepth) {
|
|
3219
|
+
for (const command of pipeline.commands) {
|
|
3220
|
+
if (command.kind === "simple" || command.kind === "arithmetic" || command.kind === "conditional") {
|
|
3221
|
+
this.writeVariable(state, "BASH_COMMAND", commandSpelling(command));
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
2053
3225
|
for (let index = 1; index < pipeline.commands.length; index++)
|
|
2054
3226
|
pipes.push(createBytePipe({
|
|
2055
3227
|
highWaterMark: this.budget.limits.pipeHighWaterMark, signal: this.signal,
|
|
2056
3228
|
}));
|
|
2057
3229
|
for (let index = 0; index < pipeline.commands.length; index++)
|
|
2058
3230
|
controllers.push(new AbortController());
|
|
2059
|
-
|
|
2060
|
-
|
|
3231
|
+
let transferred;
|
|
3232
|
+
let pendingTransfers = pipeline.commands.length;
|
|
3233
|
+
const transfer = new Promise(resolve => { transferred = resolve; });
|
|
3234
|
+
const retireBaseline = terminal ? transfer.then(() => terminal.frame.reconcile(new Map())) : Promise.resolve();
|
|
3235
|
+
retain(retireBaseline);
|
|
3236
|
+
void retireBaseline.catch(() => undefined);
|
|
3237
|
+
let preparedStages = 0;
|
|
3238
|
+
let acceptPreparation;
|
|
3239
|
+
let rejectPreparation;
|
|
3240
|
+
const installation = state.extensions?.checkpoints.length ? new Promise((resolve, reject) => {
|
|
3241
|
+
acceptPreparation = () => { if (++preparedStages === pipeline.commands.length)
|
|
3242
|
+
resolve(); };
|
|
3243
|
+
rejectPreparation = reject;
|
|
3244
|
+
}).then(async () => {
|
|
3245
|
+
await retireBaseline;
|
|
3246
|
+
await this.extensionCheckpoint("child-job-install", state, io);
|
|
3247
|
+
}) : undefined;
|
|
3248
|
+
if (installation) {
|
|
3249
|
+
retain(installation);
|
|
3250
|
+
void installation.catch(() => undefined);
|
|
3251
|
+
}
|
|
2061
3252
|
const tasks = pipeline.commands.map(async (command, index) => {
|
|
2062
|
-
let
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
const prepared = prepareChildCancellation(this.cancellation, undefined, this.cancellationAdmission(childDepth, controls.length), controls);
|
|
2071
|
-
const owner = new InvocationCancellationOwner(io[invocationScope], prepared, this.cancellationState);
|
|
2072
|
-
let boundary;
|
|
3253
|
+
let admitted = false;
|
|
3254
|
+
const admit = () => {
|
|
3255
|
+
if (admitted)
|
|
3256
|
+
return;
|
|
3257
|
+
admitted = true;
|
|
3258
|
+
if (--pendingTransfers === 0)
|
|
3259
|
+
transferred();
|
|
3260
|
+
};
|
|
2073
3261
|
try {
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
const input = new ShellInput(incoming?.readable ?? io.stdin, this.budget, signal);
|
|
2084
|
-
const pipeOutput = outgoing && { [outputFailure]: outgoing.abort, ownedOutput: outgoing.writable.ownedOutput, write: async (chunk) => {
|
|
2085
|
-
try {
|
|
2086
|
-
await outgoing.writable.write(chunk);
|
|
2087
|
-
if (chunk.byteLength)
|
|
2088
|
-
written.add(index);
|
|
2089
|
-
}
|
|
2090
|
-
catch (error) {
|
|
2091
|
-
if (errorCode(error) === "EPIPE") {
|
|
2092
|
-
const closed = new PipelineClosed();
|
|
2093
|
-
controllers[index].abort(closed);
|
|
2094
|
-
throw closed;
|
|
2095
|
-
}
|
|
2096
|
-
throw error;
|
|
2097
|
-
}
|
|
2098
|
-
} };
|
|
2099
|
-
const resources = new BackgroundResources();
|
|
2100
|
-
resources.destination = backgroundDestination(pipeOutput ?? io.stdout);
|
|
2101
|
-
const executeStage = async () => {
|
|
3262
|
+
const incoming = pipes[index - 1];
|
|
3263
|
+
const outgoing = pipes[index];
|
|
3264
|
+
const childDepth = this.cancellationDepth + 1;
|
|
3265
|
+
const controls = [
|
|
3266
|
+
{ role: "pipeline-control", signal: controllers[index].signal },
|
|
3267
|
+
];
|
|
3268
|
+
const prepared = prepareChildCancellation(this.cancellation, undefined, this.cancellationAdmission(childDepth, controls.length), controls);
|
|
3269
|
+
const owner = new InvocationCancellationOwner(io[invocationScope], prepared, this.cancellationState);
|
|
3270
|
+
let boundary;
|
|
2102
3271
|
try {
|
|
2103
|
-
|
|
2104
|
-
try {
|
|
2105
|
-
const child = await cloneState(state, this.signal);
|
|
2106
|
-
child.isolated = true;
|
|
2107
|
-
const work = runtime.runCommandIsolated(command, child, {
|
|
2108
|
-
...isolateIO(io),
|
|
2109
|
-
[backgroundResources]: [...io[backgroundResources] ?? [], resources],
|
|
2110
|
-
[backgroundPrune]: descriptors => {
|
|
2111
|
-
if (!initialRedirection)
|
|
2112
|
-
return;
|
|
2113
|
-
initialRedirection = false;
|
|
2114
|
-
const destinations = new Set([...descriptors.values()].flatMap(descriptor => descriptor.output && !descriptor.closed ? [backgroundDestination(descriptor.output)] : []));
|
|
2115
|
-
for (const lease of inheritedLeases)
|
|
2116
|
-
if (!destinations.has(lease.resource.destination))
|
|
2117
|
-
lease.release();
|
|
2118
|
-
},
|
|
2119
|
-
stdin: input,
|
|
2120
|
-
...(incoming ? { stdinIsDefault: false } : {}),
|
|
2121
|
-
stdout: pipeOutput ? this.budget.sink(pipeOutput, signal) : signalSink(io.stdout, signal),
|
|
2122
|
-
stderr: signalSink(io.stderr, signal),
|
|
2123
|
-
}).finally(() => stateMonitor(child)?.closeValues());
|
|
2124
|
-
retain(work);
|
|
2125
|
-
exitCode = await interruptible(work, signal);
|
|
2126
|
-
if (resources.busy)
|
|
2127
|
-
await interruptible(resources.drain(), signal);
|
|
2128
|
-
}
|
|
2129
|
-
catch (error) {
|
|
2130
|
-
if (!(error instanceof PipelineClosed))
|
|
2131
|
-
throw error;
|
|
2132
|
-
exitCode = 141;
|
|
2133
|
-
}
|
|
2134
|
-
return { exitCode };
|
|
3272
|
+
boundary = owner.activate();
|
|
2135
3273
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
if (incoming) {
|
|
2140
|
-
const upstream = index - 1;
|
|
2141
|
-
const close = scheduleTurn(() => {
|
|
2142
|
-
closing.delete(close);
|
|
2143
|
-
if (written.has(upstream) && !completed.has(upstream))
|
|
2144
|
-
controllers[upstream].abort(new PipelineClosed());
|
|
2145
|
-
});
|
|
2146
|
-
closing.add(close);
|
|
2147
|
-
await incoming.abort();
|
|
2148
|
-
}
|
|
2149
|
-
await input.close().catch((error) => { if (!(error instanceof PipelineClosed))
|
|
2150
|
-
throw error; });
|
|
2151
|
-
if (outgoing)
|
|
2152
|
-
await outgoing.close().catch(() => undefined);
|
|
3274
|
+
catch (error) {
|
|
3275
|
+
await owner.abandon(Promise.resolve());
|
|
3276
|
+
throw error;
|
|
2153
3277
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
captured
|
|
3278
|
+
const signal = AbortSignal.any([boundary.deliverySignal, io[invocationScope].signal]);
|
|
3279
|
+
const frame = {};
|
|
3280
|
+
const runtime = new Runtime(this.fs, this.commands, this.middleware, this.budget, signal, this.fileWrites, this.outputFiles, boundary.deliverySignal, boundary, this.cancellationState, owner, childDepth, this.cancellationMaxDepth, frame, this.inputProfile);
|
|
3281
|
+
let captured;
|
|
3282
|
+
const preparationCleanup = [];
|
|
3283
|
+
let stageOwnsCleanup = false;
|
|
3284
|
+
try {
|
|
3285
|
+
const references = new PipeDescriptorFrame(io[invocationScope]);
|
|
3286
|
+
preparationCleanup.push(() => references.close());
|
|
3287
|
+
const descriptorFrame = new PreparedDescriptorFrame(references, this.budget);
|
|
3288
|
+
preparationCleanup.push(() => descriptorFrame.close());
|
|
3289
|
+
const reading = incoming?.endpoints?.read;
|
|
3290
|
+
const writing = outgoing?.endpoints?.write;
|
|
3291
|
+
const readReference = reading && references.open(reading, this.budget);
|
|
3292
|
+
const writeReference = writing && references.open(writing, this.budget);
|
|
3293
|
+
const input = incoming
|
|
3294
|
+
? new ShellInput(reading?.readable ?? incoming.readable, this.budget, signal, { provenance: "stream", poll: () => incoming.readiness() })
|
|
3295
|
+
: new ShellInput(io.stdin, this.budget, signal);
|
|
3296
|
+
preparationCleanup.push(() => input.close());
|
|
3297
|
+
const writable = writing?.writable ?? outgoing?.writable;
|
|
3298
|
+
const failOutput = writable?.[outputFailure]?.bind(writable);
|
|
3299
|
+
const pipeOutput = outgoing && { ...(failOutput ? { [outputFailure]: failOutput } : {}), ownedOutput: writable.ownedOutput, write: async (chunk) => {
|
|
3300
|
+
try {
|
|
3301
|
+
await writable.write(chunk);
|
|
3302
|
+
if (chunk.byteLength)
|
|
3303
|
+
written.add(index);
|
|
3304
|
+
}
|
|
3305
|
+
catch (error) {
|
|
3306
|
+
if (errorCode(error) === "EPIPE") {
|
|
3307
|
+
const closed = new PipelineClosed();
|
|
3308
|
+
controllers[index].abort(closed);
|
|
3309
|
+
throw closed;
|
|
3310
|
+
}
|
|
3311
|
+
throw error;
|
|
3312
|
+
}
|
|
3313
|
+
} };
|
|
3314
|
+
const executeStage = async () => {
|
|
3315
|
+
let preparedChild;
|
|
3316
|
+
let preparationFailed = false;
|
|
3317
|
+
let started = false;
|
|
3318
|
+
let checkpointFailure;
|
|
3319
|
+
let outcome;
|
|
3320
|
+
try {
|
|
3321
|
+
let exitCode;
|
|
3322
|
+
try {
|
|
3323
|
+
const child = await cloneState(state, this.signal);
|
|
3324
|
+
preparedChild = child;
|
|
3325
|
+
child.extensions = undefined;
|
|
3326
|
+
child.extensions = forkExtensions(state.extensions, "pipeline");
|
|
3327
|
+
child.isolated = true;
|
|
3328
|
+
const inherited = isolateIO(io, references);
|
|
3329
|
+
const childIO = {
|
|
3330
|
+
...inherited,
|
|
3331
|
+
stdin: input,
|
|
3332
|
+
...(incoming ? { asyncDefaultInput: undefined } : {}),
|
|
3333
|
+
...(incoming ? { stdinIsDefault: false } : {}),
|
|
3334
|
+
stdout: pipeOutput ? this.budget.sink(pipeOutput, signal) : signalSink(io.stdout, signal),
|
|
3335
|
+
stderr: signalSink(io.stderr, signal),
|
|
3336
|
+
terminal: { target: command, frame: descriptorFrame },
|
|
3337
|
+
};
|
|
3338
|
+
const descriptors = new Map(childIO.descriptors);
|
|
3339
|
+
descriptors.set(0, incoming ? { input, stdinIsDefault: false, ...(readReference ? { pipe: readReference } : {}) } : { ...descriptors.get(0), input });
|
|
3340
|
+
descriptors.set(1, outgoing ? { output: childIO.stdout, ...(writeReference ? { pipe: writeReference } : {}) } : { ...descriptors.get(1), output: childIO.stdout });
|
|
3341
|
+
descriptors.set(2, { ...descriptors.get(2), output: childIO.stderr });
|
|
3342
|
+
childIO.descriptors = descriptors;
|
|
3343
|
+
descriptorFrame.acquire(descriptors);
|
|
3344
|
+
admit();
|
|
3345
|
+
acceptPreparation?.();
|
|
3346
|
+
await retireBaseline;
|
|
3347
|
+
if (installation) {
|
|
3348
|
+
await installation;
|
|
3349
|
+
signal.throwIfAborted();
|
|
3350
|
+
}
|
|
3351
|
+
const execution = runtime.runCommandIsolated(command, child, childIO).then(status => runtime.finishShell(child, childIO, status));
|
|
3352
|
+
const work = (child.extensions?.checkpoints.length ? execution.catch(reason => {
|
|
3353
|
+
if (reason instanceof ExtensionCheckpointFailure)
|
|
3354
|
+
checkpointFailure = reason;
|
|
3355
|
+
throw reason;
|
|
3356
|
+
}) : execution).finally(async () => {
|
|
3357
|
+
try {
|
|
3358
|
+
await runtime.releaseExtensions(child);
|
|
3359
|
+
}
|
|
3360
|
+
catch (cleanup) {
|
|
3361
|
+
if (checkpointFailure)
|
|
3362
|
+
io[invocationScope].failures.push(cleanup);
|
|
3363
|
+
else
|
|
3364
|
+
throw cleanup;
|
|
3365
|
+
}
|
|
3366
|
+
finally {
|
|
3367
|
+
stateMonitor(child)?.closeValues();
|
|
3368
|
+
}
|
|
3369
|
+
});
|
|
3370
|
+
preparedChild = undefined;
|
|
3371
|
+
started = true;
|
|
3372
|
+
retain(work);
|
|
3373
|
+
exitCode = await interruptible(work, signal);
|
|
3374
|
+
}
|
|
3375
|
+
catch (error) {
|
|
3376
|
+
if (error instanceof ExtensionCheckpointFailure)
|
|
3377
|
+
checkpointFailure = error;
|
|
3378
|
+
if (!started) {
|
|
3379
|
+
preparationFailed = true;
|
|
3380
|
+
rejectPreparation?.(error);
|
|
3381
|
+
}
|
|
3382
|
+
if (!(error instanceof PipelineClosed))
|
|
3383
|
+
throw error;
|
|
3384
|
+
exitCode = 141;
|
|
3385
|
+
}
|
|
3386
|
+
outcome = { kind: "return", value: { exitCode } };
|
|
3387
|
+
}
|
|
3388
|
+
catch (reason) {
|
|
3389
|
+
outcome = { kind: "throw", reason };
|
|
3390
|
+
}
|
|
3391
|
+
finally {
|
|
3392
|
+
admit();
|
|
3393
|
+
const cleanupFailures = [];
|
|
3394
|
+
if (preparedChild) {
|
|
3395
|
+
const child = preparedChild;
|
|
3396
|
+
await io[invocationScope].cleanup(async () => {
|
|
3397
|
+
try {
|
|
3398
|
+
await runtime.releaseExtensions(child);
|
|
3399
|
+
}
|
|
3400
|
+
finally {
|
|
3401
|
+
stateMonitor(child)?.closeValues();
|
|
3402
|
+
}
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3405
|
+
completed.add(index);
|
|
3406
|
+
if (incoming && !reading) {
|
|
3407
|
+
const upstream = index - 1;
|
|
3408
|
+
const close = scheduleTurn(() => {
|
|
3409
|
+
closing.delete(close);
|
|
3410
|
+
if (written.has(upstream) && !completed.has(upstream))
|
|
3411
|
+
controllers[upstream].abort(new PipelineClosed());
|
|
3412
|
+
});
|
|
3413
|
+
closing.add(close);
|
|
3414
|
+
try {
|
|
3415
|
+
await incoming.abort();
|
|
3416
|
+
}
|
|
3417
|
+
catch (reason) {
|
|
3418
|
+
cleanupFailures.push(reason);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
try {
|
|
3422
|
+
await input.close().catch((error) => { if (!(error instanceof PipelineClosed))
|
|
3423
|
+
throw error; });
|
|
3424
|
+
}
|
|
3425
|
+
catch (reason) {
|
|
3426
|
+
cleanupFailures.push(reason);
|
|
3427
|
+
}
|
|
3428
|
+
try {
|
|
3429
|
+
await descriptorFrame.close();
|
|
3430
|
+
}
|
|
3431
|
+
catch (reason) {
|
|
3432
|
+
cleanupFailures.push(reason);
|
|
3433
|
+
}
|
|
3434
|
+
try {
|
|
3435
|
+
await references.close();
|
|
3436
|
+
}
|
|
3437
|
+
catch (reason) {
|
|
3438
|
+
cleanupFailures.push(reason);
|
|
3439
|
+
}
|
|
3440
|
+
if (outgoing && !writing)
|
|
3441
|
+
await outgoing.close().catch(() => undefined);
|
|
3442
|
+
if (checkpointFailure || installation && preparationFailed)
|
|
3443
|
+
io[invocationScope].failures.push(...cleanupFailures);
|
|
3444
|
+
else if (cleanupFailures.length) {
|
|
3445
|
+
io[invocationScope].failures.push(...cleanupFailures.slice(1));
|
|
3446
|
+
outcome = { kind: "throw", reason: cleanupFailures[0] };
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
if (outcome.kind === "throw")
|
|
3450
|
+
throw outcome.reason;
|
|
3451
|
+
return outcome.value;
|
|
3452
|
+
};
|
|
3453
|
+
stageOwnsCleanup = true;
|
|
3454
|
+
captured = { kind: "return", value: await executeStage() };
|
|
3455
|
+
}
|
|
3456
|
+
catch (reason) {
|
|
3457
|
+
rejectPreparation?.(reason);
|
|
3458
|
+
if (!stageOwnsCleanup)
|
|
3459
|
+
await Promise.all(preparationCleanup.map(cleanup => io[invocationScope].cleanup(cleanup)));
|
|
3460
|
+
captured = frame.report && Object.is(frame.report.origin.signal.reason, reason)
|
|
3461
|
+
? { kind: "throw", reason, report: frame.report }
|
|
3462
|
+
: { kind: "throw", reason };
|
|
3463
|
+
}
|
|
3464
|
+
const selection = await owner.finish(Promise.resolve(), captured);
|
|
3465
|
+
if (selection.outcome.kind === "throw")
|
|
3466
|
+
throw selection.outcome.reason;
|
|
3467
|
+
return selection.outcome.value.exitCode;
|
|
2158
3468
|
}
|
|
2159
3469
|
catch (reason) {
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
throw selection.outcome.reason;
|
|
2167
|
-
return selection.outcome.value.exitCode;
|
|
3470
|
+
rejectPreparation?.(reason);
|
|
3471
|
+
throw reason;
|
|
3472
|
+
}
|
|
3473
|
+
finally {
|
|
3474
|
+
admit();
|
|
3475
|
+
}
|
|
2168
3476
|
});
|
|
2169
3477
|
for (const task of tasks)
|
|
2170
3478
|
retain(task);
|
|
@@ -2172,9 +3480,6 @@ export class Runtime {
|
|
|
2172
3480
|
}
|
|
2173
3481
|
finally {
|
|
2174
3482
|
try {
|
|
2175
|
-
for (const leases of stageLeases)
|
|
2176
|
-
for (const lease of leases)
|
|
2177
|
-
lease.release();
|
|
2178
3483
|
for (const close of closing)
|
|
2179
3484
|
cancelTurn(close);
|
|
2180
3485
|
for (const [index, controller] of controllers.entries())
|
|
@@ -2195,25 +3500,31 @@ export class Runtime {
|
|
|
2195
3500
|
status = state.pipefail ? statuses.findLast((status) => status !== 0) ?? 0 : statuses.at(-1);
|
|
2196
3501
|
}
|
|
2197
3502
|
if (pipeline.commands.length > 1)
|
|
2198
|
-
this.errexit(status, state, io);
|
|
2199
|
-
return pipeline.negate ? Number(status === 0) : status;
|
|
3503
|
+
await this.errexit(status, state, io);
|
|
3504
|
+
return pipeline.negate && !terminal ? Number(status === 0) : status;
|
|
2200
3505
|
}
|
|
2201
3506
|
async runCommandIsolated(command, state, io, fileShortcut = false) {
|
|
2202
3507
|
try {
|
|
3508
|
+
await this.startExtensions(state, io);
|
|
2203
3509
|
return await this.command(command, state, io, fileShortcut);
|
|
2204
3510
|
}
|
|
2205
3511
|
catch (error) {
|
|
2206
3512
|
if (error instanceof NounsetDiagnosticFailure)
|
|
2207
3513
|
throw error;
|
|
2208
|
-
if (error instanceof Flow && (error.kind === "exit" || error.kind === "
|
|
3514
|
+
if (error instanceof Flow && (error.kind === "exit" || error.kind === "discard"))
|
|
3515
|
+
return this.finishShell(state, io, error.status);
|
|
3516
|
+
if (error instanceof Flow && error.kind === "return")
|
|
2209
3517
|
return error.status;
|
|
2210
3518
|
throw error;
|
|
2211
3519
|
}
|
|
2212
3520
|
}
|
|
2213
|
-
errexit(status, state, io) {
|
|
3521
|
+
async errexit(status, state, io) {
|
|
2214
3522
|
this.signal.throwIfAborted();
|
|
2215
|
-
if (status !== 0 &&
|
|
2216
|
-
|
|
3523
|
+
if (status !== 0 && !io.execution?.ignoreErrexit) {
|
|
3524
|
+
await this.extensionEvent("error", state, io, status);
|
|
3525
|
+
if (state.errexit)
|
|
3526
|
+
throw new Flow("exit", status);
|
|
3527
|
+
}
|
|
2217
3528
|
}
|
|
2218
3529
|
async publishStatus(state, statuses, io) {
|
|
2219
3530
|
this.signal.throwIfAborted();
|
|
@@ -2227,7 +3538,25 @@ export class Runtime {
|
|
|
2227
3538
|
}
|
|
2228
3539
|
async command(command, state, io, fileShortcut = false, publicationNegate = false) {
|
|
2229
3540
|
io[invocationScope].assertOpen();
|
|
3541
|
+
if (state.extensions && (command.kind === "simple" || command.kind === "arithmetic" || command.kind === "conditional")) {
|
|
3542
|
+
io = { ...io, diagnosticLine: io.diagnosticCommandLines?.get(command) ?? (command.line ?? 1) + (io.diagnosticOffset ?? 0) };
|
|
3543
|
+
const description = commandSpelling(command);
|
|
3544
|
+
if (!state.extensions.eventDepth)
|
|
3545
|
+
this.writeVariable(state, "BASH_COMMAND", description);
|
|
3546
|
+
if (await this.extensionEvent("command", state, io, state.status, description))
|
|
3547
|
+
return state.status;
|
|
3548
|
+
}
|
|
2230
3549
|
const publishes = command.kind === "simple" || command.kind === "subshell" || command.kind === "arithmetic" || command.kind === "conditional";
|
|
3550
|
+
const terminal = io.terminal?.target === command ? io.terminal : undefined;
|
|
3551
|
+
if (terminal)
|
|
3552
|
+
terminal.beforeExit = async (status, completionIO) => {
|
|
3553
|
+
if (publishes) {
|
|
3554
|
+
const reported = publicationNegate && (command.kind === "conditional" || command.kind === "arithmetic") ? Number(status === 0) : status;
|
|
3555
|
+
await this.publishStatus(state, [reported], completionIO);
|
|
3556
|
+
terminal.published = status;
|
|
3557
|
+
await this.errexit(status, state, completionIO);
|
|
3558
|
+
}
|
|
3559
|
+
};
|
|
2231
3560
|
let status;
|
|
2232
3561
|
try {
|
|
2233
3562
|
status = await io[invocationScope].run(() => this.executeCommand(command, state, io, fileShortcut));
|
|
@@ -2241,44 +3570,60 @@ export class Runtime {
|
|
|
2241
3570
|
}
|
|
2242
3571
|
throw error;
|
|
2243
3572
|
}
|
|
3573
|
+
if (terminal && state.extensions?.exitStatus !== undefined)
|
|
3574
|
+
state.extensions.exitStatus = status;
|
|
2244
3575
|
if (publishes) {
|
|
2245
3576
|
const reported = publicationNegate && (command.kind === "conditional" || command.kind === "arithmetic") ? Number(status === 0) : status;
|
|
2246
|
-
|
|
2247
|
-
|
|
3577
|
+
if (terminal?.published !== status)
|
|
3578
|
+
await this.publishStatus(state, [reported], io);
|
|
3579
|
+
if (!terminal?.completed)
|
|
3580
|
+
await this.errexit(status, state, io);
|
|
2248
3581
|
}
|
|
2249
3582
|
return status;
|
|
2250
3583
|
}
|
|
2251
3584
|
async executeCommand(command, state, originalIO, fileShortcut = false) {
|
|
2252
|
-
state = trackState(state, this.budget, originalIO[invocationScope]);
|
|
2253
|
-
originalIO = activeIO(originalIO);
|
|
2254
3585
|
originalIO.descriptors ??= new Map([
|
|
2255
3586
|
[0, { input: originalIO.stdin, ...(originalIO.stdinIsDefault === undefined ? {} : { stdinIsDefault: originalIO.stdinIsDefault }) }],
|
|
2256
3587
|
[1, { output: originalIO.stdout }], [2, { output: originalIO.stderr }],
|
|
2257
3588
|
]);
|
|
2258
|
-
const
|
|
3589
|
+
const terminal = originalIO.terminal?.target === command ? originalIO.terminal : undefined;
|
|
3590
|
+
originalIO = { ...originalIO, terminal: undefined };
|
|
3591
|
+
state = trackState(state, this.budget, originalIO[invocationScope]);
|
|
3592
|
+
if (originalIO.asyncDefaultInput) {
|
|
3593
|
+
if (!command.redirects.some(redirect => redirect.descriptor === 0)) {
|
|
3594
|
+
const descriptors = new Map(originalIO.descriptors);
|
|
3595
|
+
descriptors.set(0, { input: originalIO.asyncDefaultInput, stdinIsDefault: true });
|
|
3596
|
+
originalIO = { ...originalIO, descriptors, stdin: originalIO.asyncDefaultInput, stdinIsDefault: true, asyncDefaultInput: undefined };
|
|
3597
|
+
}
|
|
3598
|
+
else
|
|
3599
|
+
originalIO = { ...originalIO, asyncDefaultInput: undefined };
|
|
3600
|
+
}
|
|
3601
|
+
originalIO = activeIO(originalIO);
|
|
3602
|
+
const diagnosticLine = originalIO.diagnosticCommandLines?.get(command) ?? (command.line ?? 1) + (originalIO.diagnosticOffset ?? 0);
|
|
2259
3603
|
originalIO = { ...originalIO, diagnosticLine, substitutionDiagnosticLine: originalIO.substitutionDiagnosticLines?.get(command) ?? diagnosticLine };
|
|
3604
|
+
const references = new PipeDescriptorFrame(originalIO[invocationScope]);
|
|
2260
3605
|
if (command.kind === "subshell")
|
|
2261
|
-
originalIO = isolateIO(originalIO);
|
|
3606
|
+
originalIO = isolateIO(originalIO, references);
|
|
2262
3607
|
this.budget.tick();
|
|
2263
3608
|
if (this.budget.commands % 128 === 0)
|
|
2264
3609
|
await yieldTurn(this.signal);
|
|
2265
3610
|
this.signal.throwIfAborted();
|
|
2266
|
-
const resources = new BackgroundResources();
|
|
2267
|
-
originalIO = { ...originalIO, [backgroundResources]: [...originalIO[backgroundResources] ?? [], resources] };
|
|
2268
3611
|
const inputs = new Set();
|
|
2269
3612
|
const outputs = new Set();
|
|
3613
|
+
const outputFailures = [];
|
|
3614
|
+
let outputStatus;
|
|
2270
3615
|
const finishOutputs = async (status) => {
|
|
2271
3616
|
const pending = [...outputs];
|
|
2272
3617
|
outputs.clear();
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
const
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
3618
|
+
const settled = await Promise.allSettled(pending.map(close => close({ status })));
|
|
3619
|
+
for (const [index, result] of settled.entries()) {
|
|
3620
|
+
const descriptor = pending[index]?.descriptor;
|
|
3621
|
+
if (result.status === "rejected" && descriptor)
|
|
3622
|
+
outputFailures.push({ descriptor, reason: result.reason });
|
|
3623
|
+
}
|
|
3624
|
+
if (status !== 0)
|
|
3625
|
+
outputStatus = status;
|
|
3626
|
+
throwCleanupFailures(settled.filter(result => result.status === "rejected").map(result => result.reason));
|
|
2282
3627
|
};
|
|
2283
3628
|
const allocation = this.budget.values.scope();
|
|
2284
3629
|
originalIO = { ...originalIO, [valueScope]: allocation };
|
|
@@ -2289,21 +3634,37 @@ export class Runtime {
|
|
|
2289
3634
|
try {
|
|
2290
3635
|
const execute = async () => {
|
|
2291
3636
|
if (command.kind === "function") {
|
|
2292
|
-
if (state.profile === "sh" && specialBuiltinNames.has(command.name)) {
|
|
3637
|
+
if (state.profile === "sh" && (specialBuiltinNames.has(command.name) || state.extensions?.builtins.get(command.name)?.special)) {
|
|
2293
3638
|
await this.diagnostic(io, `\`${command.name}': is a special builtin`);
|
|
2294
3639
|
throw new Flow("exit", 2);
|
|
2295
3640
|
}
|
|
2296
|
-
|
|
3641
|
+
const body = { ...command.body, sourceName: io.scriptName ?? "shell" };
|
|
3642
|
+
const offset = io.diagnosticOffset ?? 0;
|
|
3643
|
+
const bodyLine = io.functionCommandLines?.get(command.body);
|
|
3644
|
+
if (bodyLine !== undefined)
|
|
3645
|
+
body.line = bodyLine - offset;
|
|
3646
|
+
functionDiagnostics.set(body, { offset, ...(bodyLine === undefined ? {} : { lines: io.functionCommandLines }) });
|
|
3647
|
+
state.functions.set(command.name, body);
|
|
2297
3648
|
return 0;
|
|
2298
3649
|
}
|
|
2299
|
-
if (command.kind === "simple")
|
|
2300
|
-
|
|
3650
|
+
if (command.kind === "simple") {
|
|
3651
|
+
const status = await this.simple(command, state, originalIO, inputs, outputs, () => {
|
|
2301
3652
|
snapshotScope = originalIO[invocationScope].child();
|
|
2302
3653
|
void snapshotScope.run(() => new Promise(resolve => { finishSnapshot = resolve; }));
|
|
2303
3654
|
return snapshotScope;
|
|
2304
|
-
}, fileShortcut);
|
|
3655
|
+
}, fileShortcut, terminal);
|
|
3656
|
+
if (originalIO.assignmentDiagnosticContext)
|
|
3657
|
+
originalIO.assignmentDiagnosticContext.name = undefined;
|
|
3658
|
+
return status;
|
|
3659
|
+
}
|
|
2305
3660
|
io = await this.redirect(command.redirects, state, io, inputs, outputs, command.kind === "subshell", command.kind !== "subshell");
|
|
3661
|
+
if (terminal) {
|
|
3662
|
+
terminal.io = io;
|
|
3663
|
+
await terminal.frame.reconcile(io.descriptors);
|
|
3664
|
+
}
|
|
2306
3665
|
if (command.kind === "conditional") {
|
|
3666
|
+
if (io.assignmentDiagnosticContext)
|
|
3667
|
+
io.assignmentDiagnosticContext.name = "[[";
|
|
2307
3668
|
try {
|
|
2308
3669
|
return await evaluateConditional(command.expression, {
|
|
2309
3670
|
fs: this.fs, cwd: state.cwd, signal: this.signal,
|
|
@@ -2311,7 +3672,7 @@ export class Runtime {
|
|
|
2311
3672
|
work: { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes"), allocation },
|
|
2312
3673
|
expand: async (word, pattern = false) => (await this.word(word, state, { ...io, nameExpansionContext: "conditional" }, false, pattern, false, pattern)).join(""),
|
|
2313
3674
|
regex: (subject, pattern) => this.ere(subject, pattern, state, { ...io, nameExpansionContext: "conditional" }),
|
|
2314
|
-
option: name => name === "braceexpand" ? state.braceexpand !== false : name === "errexit" ? !!state.errexit : name === "nounset" ? !!state.nounset : name === "pipefail" ? state.pipefail : false,
|
|
3675
|
+
option: name => name === "braceexpand" ? state.braceexpand !== false : name === "errexit" ? !!state.errexit : name === "nounset" ? !!state.nounset : name === "pipefail" ? state.pipefail : state.extensions?.options.get(name)?.enabled ?? false,
|
|
2315
3676
|
present: name => {
|
|
2316
3677
|
const match = /^([a-zA-Z_][a-zA-Z_0-9]*)(?:\[(0|[1-9][0-9]*|[@*])\])?$/u.exec(name);
|
|
2317
3678
|
if (!match)
|
|
@@ -2320,7 +3681,7 @@ export class Runtime {
|
|
|
2320
3681
|
const selector = match[2];
|
|
2321
3682
|
if (selector === "@" || selector === "*")
|
|
2322
3683
|
return binding ? binding.values.size > 0 : this.variable(state, match[1]) !== undefined;
|
|
2323
|
-
const index = selector === undefined ? 0 : numericIndex({ decimal: selector });
|
|
3684
|
+
const index = selector === undefined ? 0 : numericIndex({ decimal: selector }, 4294967295);
|
|
2324
3685
|
if (index === undefined)
|
|
2325
3686
|
throw new ConditionalUnsupported("[[ variable index: unsupported conditional profile");
|
|
2326
3687
|
return binding ? binding.get(index) !== undefined : index === 0 && this.variable(state, match[1]) !== undefined;
|
|
@@ -2366,6 +3727,8 @@ export class Runtime {
|
|
|
2366
3727
|
}
|
|
2367
3728
|
}
|
|
2368
3729
|
if (command.kind === "arithmetic") {
|
|
3730
|
+
if (io.assignmentDiagnosticContext)
|
|
3731
|
+
io.assignmentDiagnosticContext.name = "((";
|
|
2369
3732
|
try {
|
|
2370
3733
|
return Number(this.arithmeticValue(command.expression, state, io) === 0n);
|
|
2371
3734
|
}
|
|
@@ -2376,13 +3739,25 @@ export class Runtime {
|
|
|
2376
3739
|
}
|
|
2377
3740
|
if (command.kind === "subshell") {
|
|
2378
3741
|
const child = await cloneState(state, this.signal);
|
|
2379
|
-
child.
|
|
2380
|
-
|
|
3742
|
+
child.extensions = undefined;
|
|
3743
|
+
let started = false;
|
|
2381
3744
|
try {
|
|
3745
|
+
child.extensions = forkExtensions(state.extensions, "subshell");
|
|
3746
|
+
child.isolated = true;
|
|
3747
|
+
child.loopDepth = 0;
|
|
3748
|
+
if (state.extensions?.checkpoints.length)
|
|
3749
|
+
await this.extensionCheckpoint("child-job-install", state, io);
|
|
3750
|
+
started = true;
|
|
2382
3751
|
return await this.run(command.body, child, io);
|
|
2383
3752
|
}
|
|
2384
3753
|
finally {
|
|
2385
|
-
|
|
3754
|
+
try {
|
|
3755
|
+
if (!started && child.extensions?.cleanup.length)
|
|
3756
|
+
await io[invocationScope].cleanup(() => this.releaseExtensions(child));
|
|
3757
|
+
}
|
|
3758
|
+
finally {
|
|
3759
|
+
stateMonitor(child)?.closeValues();
|
|
3760
|
+
}
|
|
2386
3761
|
}
|
|
2387
3762
|
}
|
|
2388
3763
|
if (command.kind === "group")
|
|
@@ -2395,6 +3770,13 @@ export class Runtime {
|
|
|
2395
3770
|
return command.otherwise ? await this.script(command.otherwise, state, io) : 0;
|
|
2396
3771
|
}
|
|
2397
3772
|
if (command.kind === "case") {
|
|
3773
|
+
if (state.extensions) {
|
|
3774
|
+
const description = `case ${command.subject.spelling ?? command.subject.plain ?? ""} in `;
|
|
3775
|
+
if (!state.extensions.eventDepth)
|
|
3776
|
+
this.writeVariable(state, "BASH_COMMAND", description);
|
|
3777
|
+
if (await this.extensionEvent("command", state, io, state.status, description))
|
|
3778
|
+
return state.status;
|
|
3779
|
+
}
|
|
2398
3780
|
const subject = (await this.word(command.subject, state, io, false)).join("");
|
|
2399
3781
|
const work = { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes"), allocation };
|
|
2400
3782
|
let status = 0;
|
|
@@ -2430,7 +3812,16 @@ export class Runtime {
|
|
|
2430
3812
|
const values = command.words ? await this.valueWords(command.words, state, io) : this.positionalValues(state);
|
|
2431
3813
|
for (const value of values) {
|
|
2432
3814
|
this.budget.loop();
|
|
3815
|
+
if (io.assignmentDiagnosticContext)
|
|
3816
|
+
io.assignmentDiagnosticContext.name = undefined;
|
|
2433
3817
|
await this.assignVariable(state, command.name, value);
|
|
3818
|
+
if (state.extensions) {
|
|
3819
|
+
const description = `for ${command.name} in ${command.words?.map(word => word.spelling ?? word.plain ?? "").join(" ") ?? '"$@"'}`;
|
|
3820
|
+
if (!state.extensions.eventDepth)
|
|
3821
|
+
this.writeVariable(state, "BASH_COMMAND", description);
|
|
3822
|
+
if (await this.extensionEvent("command", state, io, state.status, description))
|
|
3823
|
+
continue;
|
|
3824
|
+
}
|
|
2434
3825
|
const result = await this.loopBody(command.body, state, io);
|
|
2435
3826
|
status = result.status;
|
|
2436
3827
|
if (result.stop)
|
|
@@ -2553,7 +3944,12 @@ export class Runtime {
|
|
|
2553
3944
|
}
|
|
2554
3945
|
return status;
|
|
2555
3946
|
};
|
|
2556
|
-
|
|
3947
|
+
let status = await execute();
|
|
3948
|
+
if (terminal) {
|
|
3949
|
+
await terminal.beforeExit?.(status, terminal.io ?? io);
|
|
3950
|
+
status = await this.finishShell(state, terminal.io ?? io, status);
|
|
3951
|
+
terminal.completed = true;
|
|
3952
|
+
}
|
|
2557
3953
|
await finishOutputs(status);
|
|
2558
3954
|
return status;
|
|
2559
3955
|
}
|
|
@@ -2565,20 +3961,37 @@ export class Runtime {
|
|
|
2565
3961
|
if (error instanceof NounsetDiagnosticFailure)
|
|
2566
3962
|
diagnosticFailure = error;
|
|
2567
3963
|
this.signal.throwIfAborted();
|
|
2568
|
-
if (error instanceof Flow)
|
|
2569
|
-
|
|
3964
|
+
if (error instanceof Flow) {
|
|
3965
|
+
if (terminal && error.kind === "exit" && !(error instanceof NounsetDiagnosticFailure)) {
|
|
3966
|
+
await this.finishShell(state, terminal.io ?? io, error.status);
|
|
3967
|
+
terminal.completed = true;
|
|
3968
|
+
}
|
|
3969
|
+
try {
|
|
3970
|
+
await finishOutputs(error.status);
|
|
3971
|
+
}
|
|
3972
|
+
catch (reason) {
|
|
3973
|
+
this.signal.throwIfAborted();
|
|
3974
|
+
if (!outputFailures.length)
|
|
3975
|
+
throw reason;
|
|
3976
|
+
await writeText(io.stderr, `${io.scriptName ?? "shell"}: line ${io.diagnosticLine ?? 1}: ${message(reason, this.budget.onInternalError)}\n`);
|
|
3977
|
+
for (const failure of outputFailures)
|
|
3978
|
+
failure.descriptor.acknowledgeCloseFailure(failure.reason);
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
2570
3981
|
if (error instanceof Flow || error instanceof ShellLimitError || error instanceof ShellSyntaxError)
|
|
2571
3982
|
throw error;
|
|
2572
3983
|
this.clearOutcomeReport();
|
|
2573
3984
|
if (error instanceof HereDocumentSyntaxError) {
|
|
2574
3985
|
await writeDiagnostic(io.stderr, error.diagnostic);
|
|
2575
3986
|
if (command.kind !== "simple" && command.kind !== "subshell" && command.kind !== "arithmetic" && command.kind !== "conditional")
|
|
2576
|
-
this.errexit(1, state, io);
|
|
3987
|
+
await this.errexit(1, state, io);
|
|
2577
3988
|
return 1;
|
|
2578
3989
|
}
|
|
2579
3990
|
if (errorCode(error) === "EPIPE") {
|
|
3991
|
+
for (const failure of outputFailures)
|
|
3992
|
+
failure.descriptor.acknowledgeCloseFailure(failure.reason);
|
|
2580
3993
|
if (command.kind !== "simple" && command.kind !== "subshell" && command.kind !== "arithmetic" && command.kind !== "conditional")
|
|
2581
|
-
this.errexit(141, state, io);
|
|
3994
|
+
await this.errexit(141, state, io);
|
|
2582
3995
|
return 141;
|
|
2583
3996
|
}
|
|
2584
3997
|
const publicMessage = message(error, this.budget.onInternalError);
|
|
@@ -2595,13 +4008,18 @@ export class Runtime {
|
|
|
2595
4008
|
diagnosticFailure = new NounsetDiagnosticFailure(reason);
|
|
2596
4009
|
throw diagnosticFailure;
|
|
2597
4010
|
}
|
|
2598
|
-
throw completedExit(error instanceof ParameterExpansionFailure && !state.isolated ? 127 : 1);
|
|
4011
|
+
throw completedExit(error instanceof ParameterExpansionFailure && !state.isolated ? 127 : 1, "exit", 1, undefined, true);
|
|
2599
4012
|
}
|
|
2600
4013
|
if (error instanceof ArrayFailure)
|
|
2601
4014
|
await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${diagnostic ?? publicMessage}\n`);
|
|
2602
4015
|
else {
|
|
2603
4016
|
try {
|
|
2604
|
-
|
|
4017
|
+
const origin = io.assignmentDiagnosticContext;
|
|
4018
|
+
const namedDeclaration = error instanceof DiscardCommandFailure && error.origin === "declaration" && origin?.name !== undefined;
|
|
4019
|
+
const detail = diagnostic ?? `${namedDeclaration ? `${origin.name}: ` : ""}${publicMessage}`;
|
|
4020
|
+
await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: line ${line}: ${detail}\n`);
|
|
4021
|
+
for (const failure of outputFailures)
|
|
4022
|
+
failure.descriptor.acknowledgeCloseFailure(failure.reason);
|
|
2605
4023
|
}
|
|
2606
4024
|
catch (failure) {
|
|
2607
4025
|
this.signal.throwIfAborted();
|
|
@@ -2612,35 +4030,26 @@ export class Runtime {
|
|
|
2612
4030
|
throw completedExit(error instanceof ParameterExpansionFailure && !state.isolated ? 127 : 1);
|
|
2613
4031
|
if (error instanceof FatalCommandFailure)
|
|
2614
4032
|
throw completedExit(error.status);
|
|
2615
|
-
|
|
4033
|
+
if (error instanceof DiscardCommandFailure)
|
|
4034
|
+
throw completedExit(error.status, "discard");
|
|
4035
|
+
const status = outputStatus ?? (error instanceof CommandFailure ? error.status : 1);
|
|
2616
4036
|
if (command.kind !== "simple" && command.kind !== "subshell" && command.kind !== "arithmetic" && command.kind !== "conditional")
|
|
2617
|
-
this.errexit(status, state, io);
|
|
4037
|
+
await this.errexit(status, state, io);
|
|
2618
4038
|
return status;
|
|
2619
4039
|
}
|
|
2620
4040
|
finally {
|
|
2621
4041
|
try {
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
const failures = results.filter(result => result.status === "rejected").map(result => result.reason);
|
|
2634
|
-
if (diagnosticFailure)
|
|
2635
|
-
io[invocationScope].failures.push(...failures);
|
|
2636
|
-
else
|
|
2637
|
-
throwCleanupFailures(failures);
|
|
2638
|
-
}).finally(() => allocation.close());
|
|
2639
|
-
};
|
|
2640
|
-
if (resources.busy)
|
|
2641
|
-
state.backgroundJobs.execution.track(close());
|
|
2642
|
-
else
|
|
2643
|
-
await close();
|
|
4042
|
+
await Promise.allSettled([
|
|
4043
|
+
references.close(),
|
|
4044
|
+
...[...outputs].map(async (close) => close({ reason: new FsError("ECANCELED", { syscall: "redirect" }) })),
|
|
4045
|
+
...[...inputs].map(async (input) => input.close()),
|
|
4046
|
+
]).then(results => {
|
|
4047
|
+
const failures = results.filter(result => result.status === "rejected").map(result => result.reason);
|
|
4048
|
+
if (diagnosticFailure)
|
|
4049
|
+
io[invocationScope].failures.push(...failures);
|
|
4050
|
+
else
|
|
4051
|
+
throwCleanupFailures(failures);
|
|
4052
|
+
}).finally(() => allocation.close());
|
|
2644
4053
|
}
|
|
2645
4054
|
finally {
|
|
2646
4055
|
finishSnapshot?.();
|
|
@@ -2649,55 +4058,68 @@ export class Runtime {
|
|
|
2649
4058
|
}
|
|
2650
4059
|
}
|
|
2651
4060
|
async loopBody(body, state, io) {
|
|
4061
|
+
let status = 0;
|
|
4062
|
+
let control;
|
|
2652
4063
|
try {
|
|
2653
|
-
|
|
4064
|
+
status = await this.script(body, state, io);
|
|
2654
4065
|
}
|
|
2655
4066
|
catch (error) {
|
|
2656
4067
|
if (!(error instanceof Flow) || (error.kind !== "break" && error.kind !== "continue"))
|
|
2657
4068
|
throw error;
|
|
2658
|
-
|
|
2659
|
-
throw error;
|
|
2660
|
-
return { status: 0, stop: error.kind === "break" };
|
|
4069
|
+
control = error;
|
|
2661
4070
|
}
|
|
4071
|
+
if (state.extensions?.checkpoints.length)
|
|
4072
|
+
await this.extensionCheckpoint("loop-body-complete", state, io);
|
|
4073
|
+
if (control && --control.levels > 0)
|
|
4074
|
+
throw control;
|
|
4075
|
+
return { status, stop: control?.kind === "break" };
|
|
2662
4076
|
}
|
|
2663
4077
|
async document(document, state, io, line = document.endLine) {
|
|
2664
4078
|
this.signal.throwIfAborted();
|
|
2665
4079
|
let value = "";
|
|
2666
4080
|
let fragments;
|
|
4081
|
+
const allocation = this.budget.values.scope();
|
|
2667
4082
|
let size = 0;
|
|
2668
4083
|
let words = 0;
|
|
2669
4084
|
const warnings = [];
|
|
2670
4085
|
try {
|
|
2671
|
-
for (const word of hereDocumentWords(document, line, byteLocale(state.variables), warnings, this.budget.parsing)) {
|
|
4086
|
+
for (const word of hereDocumentWords(document, line, byteLocale(state.variables), warnings, this.budget.parsing, state.extensions?.syntax)) {
|
|
2672
4087
|
this.signal.throwIfAborted();
|
|
2673
4088
|
for (const warning of warnings.splice(0))
|
|
2674
4089
|
await writeDiagnostic(io.stderr, `shell: warning: ${warning}\n`);
|
|
2675
4090
|
if (++words % 128 === 0)
|
|
2676
4091
|
await yieldTurn(this.signal);
|
|
2677
|
-
const part
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
4092
|
+
for (const part of await this.valueWord(word, state, { ...io, nameExpansionContext: "document" }, false, false, false, false, undefined, true, false)) {
|
|
4093
|
+
size += shellValueByteLength(part);
|
|
4094
|
+
if (size > this.budget.limits.maxExpansionBytes)
|
|
4095
|
+
this.budget.fail("maxExpansionBytes");
|
|
4096
|
+
if (!fragments && typeof part === "string")
|
|
4097
|
+
value += part;
|
|
4098
|
+
else {
|
|
4099
|
+
if (!fragments) {
|
|
4100
|
+
allocation.reserve(64 + value.length * 2, 2);
|
|
4101
|
+
fragments = [value];
|
|
4102
|
+
value = "";
|
|
4103
|
+
}
|
|
4104
|
+
allocation.reserve(32 + (typeof part === "string" ? part.length * 2 : 0), 1);
|
|
4105
|
+
fragments.push(part);
|
|
2688
4106
|
}
|
|
2689
|
-
io[valueScope]?.reserve(32 + (typeof part === "string" ? part.length * 2 : 0), 0);
|
|
2690
|
-
fragments.push(part);
|
|
2691
4107
|
}
|
|
2692
4108
|
}
|
|
4109
|
+
return fragments ? concatShellValues(fragments, io[valueScope] ?? allocation) : value;
|
|
2693
4110
|
}
|
|
2694
4111
|
finally {
|
|
2695
|
-
|
|
2696
|
-
|
|
4112
|
+
try {
|
|
4113
|
+
for (const warning of warnings.splice(0))
|
|
4114
|
+
await writeDiagnostic(io.stderr, `shell: warning: ${warning}\n`);
|
|
4115
|
+
}
|
|
4116
|
+
finally {
|
|
4117
|
+
allocation.close();
|
|
4118
|
+
}
|
|
2697
4119
|
}
|
|
2698
|
-
return fragments ? concatShellValues(fragments, io[valueScope]) : value;
|
|
2699
4120
|
}
|
|
2700
4121
|
async redirect(redirects, state, io, inputs, outputs, isolatedInlineInput = false, persistMoves = false, fileShortcut = false, line) {
|
|
4122
|
+
const resourceFs = scopeFileSystem(this.sourceFs, () => this.budget.fileSystemOperation(), this.commandSignal, () => this.budget.fileSystemCleanupOperation());
|
|
2701
4123
|
this.signal.throwIfAborted();
|
|
2702
4124
|
if (redirects.length > this.budget.limits.maxRedirects)
|
|
2703
4125
|
this.budget.fail("maxRedirects");
|
|
@@ -2715,6 +4137,17 @@ export class Runtime {
|
|
|
2715
4137
|
[2, errorDescriptor?.output === io.stderr ? errorDescriptor : { output: io.stderr }],
|
|
2716
4138
|
]);
|
|
2717
4139
|
const replaced = new Set();
|
|
4140
|
+
const references = new PipeDescriptorFrame(io[invocationScope]);
|
|
4141
|
+
inputs.add(references);
|
|
4142
|
+
const replaceDescriptor = async (number, next) => {
|
|
4143
|
+
const previous = descriptors.get(number)?.pipe;
|
|
4144
|
+
if (previous && references.references.has(previous))
|
|
4145
|
+
await previous.close();
|
|
4146
|
+
if (next)
|
|
4147
|
+
descriptors.set(number, next);
|
|
4148
|
+
else
|
|
4149
|
+
descriptors.delete(number);
|
|
4150
|
+
};
|
|
2718
4151
|
let errorTarget;
|
|
2719
4152
|
if (io.stdin === closedSource)
|
|
2720
4153
|
descriptors.delete(0);
|
|
@@ -2727,12 +4160,13 @@ export class Runtime {
|
|
|
2727
4160
|
const stdinIsDefault = descriptor?.input ? descriptor.stdinIsDefault : false;
|
|
2728
4161
|
return {
|
|
2729
4162
|
[invocationScope]: io[invocationScope],
|
|
2730
|
-
[backgroundResources]: io[backgroundResources],
|
|
2731
|
-
[backgroundPrune]: io[backgroundPrune],
|
|
2732
4163
|
...(io[valueScope] === undefined ? {} : { [valueScope]: io[valueScope] }),
|
|
2733
4164
|
...(io.execution === undefined ? {} : { execution: io.execution }),
|
|
2734
4165
|
...(io.diagnosticLine === undefined ? {} : { diagnosticLine: io.diagnosticLine }),
|
|
2735
4166
|
...(io.diagnosticOffset === undefined ? {} : { diagnosticOffset: io.diagnosticOffset }),
|
|
4167
|
+
...(io.assignmentDiagnosticContext === undefined ? {} : { assignmentDiagnosticContext: io.assignmentDiagnosticContext }),
|
|
4168
|
+
...(io.functionCommandLines === undefined ? {} : { functionCommandLines: io.functionCommandLines }),
|
|
4169
|
+
...(io.diagnosticCommandLines === undefined ? {} : { diagnosticCommandLines: io.diagnosticCommandLines }),
|
|
2736
4170
|
...(io.scriptName === undefined ? {} : { scriptName: io.scriptName }),
|
|
2737
4171
|
...(io.substitutionDiagnosticLine === undefined ? {} : { substitutionDiagnosticLine: io.substitutionDiagnosticLine }),
|
|
2738
4172
|
...(io.substitutionDiagnosticLines === undefined ? {} : { substitutionDiagnosticLines: io.substitutionDiagnosticLines }),
|
|
@@ -2768,9 +4202,17 @@ export class Runtime {
|
|
|
2768
4202
|
this.budget.fail("maxExpansionBytes");
|
|
2769
4203
|
value = concatShellValues([value, "\n"], io[valueScope]);
|
|
2770
4204
|
}
|
|
2771
|
-
const
|
|
2772
|
-
|
|
2773
|
-
|
|
4205
|
+
const prepared = prepareBytesInput(typeof value === "string" ? value : shellValueBytes(value, io[valueScope]), this.budget);
|
|
4206
|
+
const input = new ShellInput(prepared.source, this.budget, this.commandSignal, prepared.options);
|
|
4207
|
+
const lifetime = new DescriptorLifetime(async () => { try {
|
|
4208
|
+
await input.close();
|
|
4209
|
+
}
|
|
4210
|
+
finally {
|
|
4211
|
+
await prepared.close();
|
|
4212
|
+
} });
|
|
4213
|
+
inputs.add({ close: () => lifetime.release() });
|
|
4214
|
+
io[invocationScope].register(() => lifetime.release());
|
|
4215
|
+
await replaceDescriptor(redirect.descriptor, { input, stdinIsDefault: false, lifetime });
|
|
2774
4216
|
continue;
|
|
2775
4217
|
}
|
|
2776
4218
|
const targets = await this.word(redirect.target, state, currentIO());
|
|
@@ -2780,7 +4222,7 @@ export class Runtime {
|
|
|
2780
4222
|
errorTarget = target;
|
|
2781
4223
|
if (redirect.operator.endsWith("&")) {
|
|
2782
4224
|
if (target === "-")
|
|
2783
|
-
|
|
4225
|
+
await replaceDescriptor(redirect.descriptor);
|
|
2784
4226
|
else {
|
|
2785
4227
|
if (!/^\d+-?$/u.test(target))
|
|
2786
4228
|
throw new PublicDiagnostic(`${target}: Bad file descriptor`);
|
|
@@ -2791,9 +4233,12 @@ export class Runtime {
|
|
|
2791
4233
|
const descriptor = descriptors.get(sourceDescriptor);
|
|
2792
4234
|
if (!descriptor || descriptor.closed || (!move && (redirect.operator === "<&" ? !descriptor.input : !descriptor.output)))
|
|
2793
4235
|
throw new PublicDiagnostic(`${move ? sourceDescriptor : target}: Bad file descriptor`);
|
|
2794
|
-
|
|
4236
|
+
const pipe = descriptor.pipe && references.acquire(descriptor.pipe);
|
|
4237
|
+
await replaceDescriptor(redirect.descriptor, { ...descriptor, ...(pipe ? { pipe } : {}) });
|
|
2795
4238
|
if (move && sourceDescriptor !== redirect.descriptor) {
|
|
2796
4239
|
descriptors.delete(sourceDescriptor);
|
|
4240
|
+
if (descriptor.pipe && (references.references.has(descriptor.pipe) || persistMoves && !replaced.has(sourceDescriptor)))
|
|
4241
|
+
await descriptor.pipe.close();
|
|
2797
4242
|
if (persistMoves && !replaced.has(sourceDescriptor))
|
|
2798
4243
|
descriptor.closed = true;
|
|
2799
4244
|
}
|
|
@@ -2801,56 +4246,75 @@ export class Runtime {
|
|
|
2801
4246
|
}
|
|
2802
4247
|
else {
|
|
2803
4248
|
const path = pathOf(state, target);
|
|
2804
|
-
const options = { signal: this.
|
|
4249
|
+
const options = { signal: this.commandSignal };
|
|
2805
4250
|
if (redirect.operator === "<") {
|
|
2806
4251
|
await interruptible(this.fs.access(path, 4, options), this.signal);
|
|
2807
4252
|
const stat = await interruptible(this.fs.stat(path, options), this.signal);
|
|
2808
4253
|
if (stat.type === "directory" && !fileShortcut)
|
|
2809
4254
|
throw new PublicDiagnostic(`${target}: Is a directory`);
|
|
2810
|
-
const
|
|
2811
|
-
let
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
const
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
4255
|
+
const inputOwner = {};
|
|
4256
|
+
let closing;
|
|
4257
|
+
const cleanups = [];
|
|
4258
|
+
const close = () => closing ??= (async () => {
|
|
4259
|
+
const failures = [];
|
|
4260
|
+
try {
|
|
4261
|
+
await inputOwner.input?.close();
|
|
4262
|
+
}
|
|
4263
|
+
catch (error) {
|
|
4264
|
+
failures.push(error);
|
|
4265
|
+
}
|
|
4266
|
+
for (const cleanup of cleanups) {
|
|
4267
|
+
try {
|
|
4268
|
+
await cleanup();
|
|
4269
|
+
}
|
|
4270
|
+
catch (error) {
|
|
4271
|
+
if (!failures.some(failure => Object.is(failure, error)))
|
|
4272
|
+
failures.push(error);
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
throwCleanupFailures(failures.filter(error => !(error instanceof PipelineClosed
|
|
4276
|
+
&& this.signal.aborted && Object.is(error, this.signal.reason))));
|
|
4277
|
+
})();
|
|
4278
|
+
const lifetime = new DescriptorLifetime(close);
|
|
4279
|
+
inputs.add({ close: () => lifetime.release() });
|
|
4280
|
+
io[invocationScope].register(() => lifetime.release());
|
|
4281
|
+
const prepared = stat.type === "directory" ? prepareBytesInput("", this.budget)
|
|
4282
|
+
: await prepareFileInput({ fs: resourceFs, signal: this.commandSignal, cleanupFailurePrioritySignal: this.budget.signal, registerCleanup: close => {
|
|
4283
|
+
cleanups.push(close);
|
|
4284
|
+
} }, path, this.budget, this.inputProfile, stat);
|
|
4285
|
+
if (!cleanups.includes(prepared.close))
|
|
4286
|
+
cleanups.push(prepared.close);
|
|
4287
|
+
io[invocationScope].assertOpen();
|
|
4288
|
+
const input = new ShellInput(prepared.source, this.budget, this.commandSignal, prepared.options);
|
|
4289
|
+
inputOwner.input = input;
|
|
4290
|
+
await replaceDescriptor(redirect.descriptor, { input, stdinIsDefault: false, lifetime });
|
|
2826
4291
|
}
|
|
2827
4292
|
else {
|
|
2828
|
-
const outputSignal = this.commandSignal;
|
|
2829
|
-
const outputFs = scopeFileSystem(runtimeFileSystems.get(this.fs) ?? this.fs, () => this.budget.fileSystemOperation(), outputSignal, () => this.budget.fileSystemCleanupOperation());
|
|
2830
|
-
const options = { signal: outputSignal };
|
|
2831
4293
|
const append = redirect.operator === ">>";
|
|
2832
|
-
const capabilities = await
|
|
4294
|
+
const capabilities = await this.fs.capabilitiesFor?.(path, options) ?? this.fs.capabilities;
|
|
4295
|
+
const canonical = capabilities.open === true;
|
|
2833
4296
|
const random = capabilities.randomAccessWrite === true;
|
|
2834
4297
|
const key = resolvePath(state.cwd, path);
|
|
2835
4298
|
let file;
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
4299
|
+
if (!canonical)
|
|
4300
|
+
await this.fileOperation(key, async () => {
|
|
4301
|
+
file = this.outputFiles.get(key) ?? { data: undefined, references: 0 };
|
|
4302
|
+
if (!random && capabilities.independentWriteStreams !== true && file.references)
|
|
4303
|
+
throw new FsError("ENOTSUP", { path, message: "Conflicting sequential output descriptors" });
|
|
4304
|
+
file.references++;
|
|
4305
|
+
this.outputFiles.set(key, file);
|
|
4306
|
+
});
|
|
2843
4307
|
let closed = false;
|
|
2844
4308
|
let offset = 0;
|
|
2845
4309
|
const incremental = async () => {
|
|
2846
4310
|
await this.fileOperation(key, async () => {
|
|
2847
4311
|
if (append)
|
|
2848
|
-
await
|
|
4312
|
+
await this.fs.appendFile(path, new Uint8Array(), options);
|
|
2849
4313
|
else
|
|
2850
|
-
await
|
|
4314
|
+
await this.fs.writeFile(path, new Uint8Array(), { ...options, flag: "w" });
|
|
2851
4315
|
if (!append)
|
|
2852
4316
|
file.data = new Uint8Array();
|
|
2853
|
-
}
|
|
4317
|
+
});
|
|
2854
4318
|
return { write: (chunk) => {
|
|
2855
4319
|
const copy = new Uint8Array(chunk);
|
|
2856
4320
|
return this.fileOperation(key, async () => {
|
|
@@ -2860,18 +4324,18 @@ export class Runtime {
|
|
|
2860
4324
|
let atEOF = false;
|
|
2861
4325
|
if (!append && current && offset === current.length && capabilities.append === true && capabilities.stat !== false) {
|
|
2862
4326
|
try {
|
|
2863
|
-
atEOF = (await interruptible(
|
|
4327
|
+
atEOF = (await interruptible(this.fs.stat(path, options), this.signal)).size === offset;
|
|
2864
4328
|
}
|
|
2865
4329
|
catch {
|
|
2866
4330
|
// Metadata is optional for this optimization; writes need no read access.
|
|
2867
|
-
|
|
4331
|
+
this.signal.throwIfAborted();
|
|
2868
4332
|
}
|
|
2869
|
-
|
|
4333
|
+
this.signal.throwIfAborted();
|
|
2870
4334
|
}
|
|
2871
4335
|
if (append || atEOF) {
|
|
2872
4336
|
// Preparing a larger view only touches the unpublished tail of current.
|
|
2873
4337
|
const bytes = current ? appendOutputBytes(current, copy) : undefined;
|
|
2874
|
-
await
|
|
4338
|
+
await this.fs.appendFile(path, copy, options);
|
|
2875
4339
|
file.data = bytes;
|
|
2876
4340
|
}
|
|
2877
4341
|
else {
|
|
@@ -2879,12 +4343,12 @@ export class Runtime {
|
|
|
2879
4343
|
if (current)
|
|
2880
4344
|
bytes.set(current);
|
|
2881
4345
|
bytes.set(copy, offset);
|
|
2882
|
-
await
|
|
4346
|
+
await this.fs.writeFile(path, bytes, options);
|
|
2883
4347
|
file.data = bytes;
|
|
2884
4348
|
}
|
|
2885
4349
|
if (!append)
|
|
2886
4350
|
offset += copy.length;
|
|
2887
|
-
}
|
|
4351
|
+
});
|
|
2888
4352
|
} };
|
|
2889
4353
|
};
|
|
2890
4354
|
const resumePathCache = this.budget.pathLookup.suspend();
|
|
@@ -2893,14 +4357,27 @@ export class Runtime {
|
|
|
2893
4357
|
return;
|
|
2894
4358
|
closed = true;
|
|
2895
4359
|
resumePathCache();
|
|
4360
|
+
if (canonical)
|
|
4361
|
+
return;
|
|
2896
4362
|
if (--file.references === 0 && this.outputFiles.get(key) === file)
|
|
2897
4363
|
this.outputFiles.delete(key);
|
|
2898
4364
|
};
|
|
2899
4365
|
let target;
|
|
2900
4366
|
let outputScope;
|
|
4367
|
+
const retireOutputCleanups = [];
|
|
4368
|
+
let outputOwner = io[invocationScope];
|
|
4369
|
+
while (outputOwner.parent)
|
|
4370
|
+
outputOwner = outputOwner.parent;
|
|
2901
4371
|
try {
|
|
2902
|
-
outputScope = (
|
|
2903
|
-
|
|
4372
|
+
outputScope = new InvocationScope(this.commandSignal, io[invocationScope].failures);
|
|
4373
|
+
const context = { fs: resourceFs, signal: this.commandSignal, cleanupFailurePrioritySignal: this.budget.signal, registerCleanup: (cleanup) => {
|
|
4374
|
+
const retire = (canonical ? outputOwner : outputScope).register(cleanup);
|
|
4375
|
+
if (canonical)
|
|
4376
|
+
retireOutputCleanups.push(retire);
|
|
4377
|
+
} };
|
|
4378
|
+
if (canonical)
|
|
4379
|
+
bindFileOutputBudget(context, sink => this.budget.sink(sink, this.commandSignal), (chunk, write) => this.budget.writeCounted(chunk, write, this.commandSignal));
|
|
4380
|
+
target = await openFileOutput(context, path, canonical ? { flag: append ? "a" : "w", descriptor: true } : append ? "a" : "w", !canonical && random ? incremental : undefined);
|
|
2904
4381
|
}
|
|
2905
4382
|
catch (error) {
|
|
2906
4383
|
try {
|
|
@@ -2911,19 +4388,21 @@ export class Runtime {
|
|
|
2911
4388
|
}
|
|
2912
4389
|
throw error;
|
|
2913
4390
|
}
|
|
2914
|
-
|
|
4391
|
+
const finalize = async (completion) => {
|
|
2915
4392
|
try {
|
|
2916
|
-
if (
|
|
2917
|
-
await target.abort(
|
|
4393
|
+
if (this.commandSignal.aborted)
|
|
4394
|
+
await target.abort(this.commandSignal.reason);
|
|
2918
4395
|
else if ("reason" in completion)
|
|
2919
4396
|
await target.abort(completion.reason);
|
|
2920
4397
|
else {
|
|
2921
4398
|
try {
|
|
2922
4399
|
await target.finish();
|
|
4400
|
+
for (const retire of retireOutputCleanups)
|
|
4401
|
+
retire();
|
|
2923
4402
|
}
|
|
2924
4403
|
catch (error) {
|
|
2925
|
-
|
|
2926
|
-
if (completion.status === 0)
|
|
4404
|
+
this.signal.throwIfAborted();
|
|
4405
|
+
if (completion.status === 0 || target.descriptor && !target.signal.aborted)
|
|
2927
4406
|
throw error;
|
|
2928
4407
|
}
|
|
2929
4408
|
}
|
|
@@ -2936,13 +4415,20 @@ export class Runtime {
|
|
|
2936
4415
|
release();
|
|
2937
4416
|
}
|
|
2938
4417
|
}
|
|
2939
|
-
}
|
|
2940
|
-
|
|
4418
|
+
};
|
|
4419
|
+
let completion;
|
|
4420
|
+
const lifetime = new DescriptorLifetime(() => finalize(completion ?? { status: 0 }));
|
|
4421
|
+
const ownerFinalize = value => { completion ??= value; return lifetime.release(); };
|
|
4422
|
+
outputs.add(Object.assign(ownerFinalize, target.descriptor ? { descriptor: target.descriptor } : {}));
|
|
4423
|
+
const output = canonical ? target.sink : this.budget.sink(target.sink, this.commandSignal);
|
|
4424
|
+
if (canonical)
|
|
4425
|
+
budgetedSinks.set(output, { budget: this.budget, write: output.write });
|
|
2941
4426
|
budgetedSinks.get(output).file = Object.freeze({ path });
|
|
2942
|
-
|
|
4427
|
+
const binding = { output, lifetime, ...(target.descriptor ? { file: target.descriptor } : {}) };
|
|
4428
|
+
await replaceDescriptor(redirect.descriptor, binding);
|
|
2943
4429
|
if (redirect.operator === "&>") {
|
|
2944
4430
|
replaced.add(2);
|
|
2945
|
-
|
|
4431
|
+
await replaceDescriptor(2, { ...binding });
|
|
2946
4432
|
}
|
|
2947
4433
|
}
|
|
2948
4434
|
}
|
|
@@ -2952,15 +4438,14 @@ export class Runtime {
|
|
|
2952
4438
|
const diagnostic = errorTarget === undefined ? undefined : filesystemDiagnostic(error, errorTarget);
|
|
2953
4439
|
throw new ExecutionFailure(error, currentIO(), diagnostic);
|
|
2954
4440
|
}
|
|
2955
|
-
io[backgroundPrune]?.(descriptors);
|
|
2956
4441
|
return currentIO();
|
|
2957
4442
|
}
|
|
2958
|
-
async fileOperation(path, operation
|
|
4443
|
+
async fileOperation(path, operation) {
|
|
2959
4444
|
const previous = this.fileWrites.get(path) ?? Promise.resolve();
|
|
2960
|
-
const pending = previous.catch(() => undefined).then(() => { signal.throwIfAborted(); return operation(); });
|
|
4445
|
+
const pending = previous.catch(() => undefined).then(() => { this.signal.throwIfAborted(); return operation(); });
|
|
2961
4446
|
this.fileWrites.set(path, pending);
|
|
2962
4447
|
try {
|
|
2963
|
-
await interruptible(pending, signal);
|
|
4448
|
+
await interruptible(pending, this.signal);
|
|
2964
4449
|
}
|
|
2965
4450
|
finally {
|
|
2966
4451
|
if (this.fileWrites.get(path) === pending)
|
|
@@ -2976,7 +4461,7 @@ export class Runtime {
|
|
|
2976
4461
|
return undefined;
|
|
2977
4462
|
return { name: match[1], append: match[2] === "+", value: { offset: word.offset, parts: [{ ...first, value: first.value.slice(match[0].length) }, ...word.parts.slice(1)] } };
|
|
2978
4463
|
}
|
|
2979
|
-
async simple(command, state, originalIO, inputs, outputs, createSnapshotScope, fileShortcut = false) {
|
|
4464
|
+
async simple(command, state, originalIO, inputs, outputs, createSnapshotScope, fileShortcut = false, terminal) {
|
|
2980
4465
|
state.substitutionStatus = 0;
|
|
2981
4466
|
const assignments = [];
|
|
2982
4467
|
let wordIndex = 0;
|
|
@@ -2988,17 +4473,21 @@ export class Runtime {
|
|
|
2988
4473
|
}
|
|
2989
4474
|
const commandWords = command.words.slice(wordIndex);
|
|
2990
4475
|
let declarationIndex = 0;
|
|
2991
|
-
while (commandWords[declarationIndex]?.plain === "command") {
|
|
4476
|
+
while ((commandWords[declarationIndex]?.plain === "command" || commandWords[declarationIndex]?.plain === "builtin") && !state.extensions?.builtins.has(commandWords[declarationIndex].plain)) {
|
|
2992
4477
|
declarationIndex++;
|
|
2993
4478
|
if (commandWords[declarationIndex]?.plain === "--")
|
|
2994
4479
|
declarationIndex++;
|
|
2995
4480
|
}
|
|
2996
|
-
const
|
|
4481
|
+
const declarationName = commandWords[declarationIndex]?.plain ?? "";
|
|
4482
|
+
const declarationBuiltin = state.extensions?.builtins.get(declarationName);
|
|
4483
|
+
const declaration = declarationBuiltin ? declarationBuiltin.expansion === "declaration" : ["export", "local", "readonly"].includes(declarationName);
|
|
4484
|
+
const indexedDeclaration = declaration && state.extensions?.syntax.indexedDeclarations?.some(name => name === declarationName) === true;
|
|
4485
|
+
const wordValues = await this.valueWords(commandWords, state, originalIO, declaration, indexedDeclaration);
|
|
2997
4486
|
const words = wordValues.map(shellValueText);
|
|
2998
|
-
const special = state.profile === "sh" && specialBuiltinNames.has(words[0] ?? "");
|
|
4487
|
+
const special = state.profile === "sh" && (specialBuiltinNames.has(words[0] ?? "") || !!state.extensions?.builtins.get(words[0] ?? "")?.special);
|
|
2999
4488
|
const inlineInput = command.redirects.some((redirect) => redirect.document || redirect.operator === "<<<");
|
|
3000
4489
|
const functionCommand = words.length > 0 && state.functions.has(words[0]);
|
|
3001
|
-
const isolatedInlineInput = inlineInput && words.length > 0 && !shellBuiltinNames.has(words[0]) && !functionCommand;
|
|
4490
|
+
const isolatedInlineInput = inlineInput && words.length > 0 && !shellBuiltinNames.has(words[0]) && !state.extensions?.builtins.has(words[0]) && !functionCommand;
|
|
3002
4491
|
const snapshotScope = isolatedInlineInput ? createSnapshotScope() : undefined;
|
|
3003
4492
|
let io = snapshotScope ? { ...originalIO, [invocationScope]: snapshotScope } : originalIO;
|
|
3004
4493
|
const previous = new Map();
|
|
@@ -3015,7 +4504,7 @@ export class Runtime {
|
|
|
3015
4504
|
}
|
|
3016
4505
|
await this.arrayZero(state, assignment.name, async () => {
|
|
3017
4506
|
const fields = await this.valueWord(assignment.value, state, io, false);
|
|
3018
|
-
return
|
|
4507
|
+
return this.arrayJoin(requireArrays(state).owner, fields, "");
|
|
3019
4508
|
}, assignment.append);
|
|
3020
4509
|
continue;
|
|
3021
4510
|
}
|
|
@@ -3041,11 +4530,8 @@ export class Runtime {
|
|
|
3041
4530
|
};
|
|
3042
4531
|
let overlayOpen = false;
|
|
3043
4532
|
try {
|
|
3044
|
-
if (snapshotScope)
|
|
3045
|
-
const jobs = state.backgroundJobs;
|
|
4533
|
+
if (snapshotScope)
|
|
3046
4534
|
state = await cloneState(state, this.signal, snapshotScope);
|
|
3047
|
-
state.backgroundJobs = jobs;
|
|
3048
|
-
}
|
|
3049
4535
|
if (words.length) {
|
|
3050
4536
|
stateMonitor(state)?.openOverlay(previous);
|
|
3051
4537
|
overlayOpen = true;
|
|
@@ -3129,7 +4615,11 @@ export class Runtime {
|
|
|
3129
4615
|
}
|
|
3130
4616
|
}
|
|
3131
4617
|
else
|
|
3132
|
-
io = await this.redirect(command.redirects, state, io, inputs, outputs, isolatedInlineInput, !words.length || shellBuiltinNames.has(words[0]) || functionCommand, fileShortcut, command.line ?? 1);
|
|
4618
|
+
io = await this.redirect(command.redirects, state, io, inputs, outputs, isolatedInlineInput, !words.length || shellBuiltinNames.has(words[0]) || !!state.extensions?.builtins.has(words[0]) || functionCommand, fileShortcut, command.line ?? 1);
|
|
4619
|
+
if (terminal) {
|
|
4620
|
+
terminal.io = io;
|
|
4621
|
+
await terminal.frame.reconcile(io.descriptors);
|
|
4622
|
+
}
|
|
3133
4623
|
if (!inlineInput)
|
|
3134
4624
|
await assign();
|
|
3135
4625
|
if (fileShortcut) {
|
|
@@ -3139,11 +4629,14 @@ export class Runtime {
|
|
|
3139
4629
|
await pipeBytes(input, io.stdout, this.signal);
|
|
3140
4630
|
return 0;
|
|
3141
4631
|
}
|
|
3142
|
-
return words.length ? await this.dispatch(
|
|
4632
|
+
return words.length ? await this.dispatch(wordValues[0], words.slice(1), state, io, previous, false, wordValues.slice(1), previous) : state.substitutionStatus;
|
|
3143
4633
|
}
|
|
3144
4634
|
catch (error) {
|
|
3145
|
-
if (error instanceof Flow)
|
|
4635
|
+
if (error instanceof Flow) {
|
|
4636
|
+
if ((error.kind === "break" || error.kind === "continue" || error.kind === "return") && io.assignmentDiagnosticContext)
|
|
4637
|
+
io.assignmentDiagnosticContext.name = undefined;
|
|
3146
4638
|
throw error;
|
|
4639
|
+
}
|
|
3147
4640
|
this.signal.throwIfAborted();
|
|
3148
4641
|
const original = error instanceof ExecutionFailure ? error.original : error;
|
|
3149
4642
|
if (special && !(original instanceof ShellLimitError) && !(original instanceof ExpansionFailure) && !(original instanceof Flow) && !(original instanceof ShellSyntaxError)) {
|
|
@@ -3169,23 +4662,26 @@ export class Runtime {
|
|
|
3169
4662
|
saved.heldValue?.release();
|
|
3170
4663
|
}
|
|
3171
4664
|
}
|
|
3172
|
-
async dispatch(name, args, state, io, assignments, bypassFunctions = false, values = args) {
|
|
4665
|
+
async dispatch(name, args, state, io, assignments, bypassFunctions = false, values = args, temporaryEnvironment) {
|
|
3173
4666
|
const scope = io[invocationScope].child();
|
|
3174
4667
|
const runtime = new Runtime(this.fs, this.commands, this.middleware, this.budget, AbortSignal.any([this.signal, scope.signal]), this.fileWrites, this.outputFiles, this.commandSignal, this.cancellation, this.cancellationState, this.cancellationOwner, this.cancellationDepth, this.cancellationMaxDepth, this.outcomeFrame, this.inputProfile);
|
|
3175
4668
|
try {
|
|
3176
|
-
return await runtime.dispatchScoped(name, values, state, { ...io, [invocationScope]: scope }, assignments, bypassFunctions);
|
|
4669
|
+
return await runtime.dispatchScoped(name, values, state, { ...io, [invocationScope]: scope }, assignments, bypassFunctions, temporaryEnvironment);
|
|
3177
4670
|
}
|
|
3178
4671
|
finally {
|
|
3179
4672
|
await scope.close();
|
|
3180
4673
|
}
|
|
3181
4674
|
}
|
|
3182
|
-
async dispatchScoped(
|
|
4675
|
+
async dispatchScoped(nameValue, values, state, io, assignments, bypassFunctions, temporaryEnvironment) {
|
|
3183
4676
|
const { [invocationScope]: scope, ...publicIO } = io;
|
|
3184
4677
|
Reflect.deleteProperty(publicIO, valueScope);
|
|
3185
|
-
Reflect.deleteProperty(publicIO, backgroundResources);
|
|
3186
|
-
Reflect.deleteProperty(publicIO, backgroundPrune);
|
|
3187
4678
|
const allocation = this.budget.values.scope();
|
|
3188
4679
|
scope.register(() => allocation.close());
|
|
4680
|
+
if (typeof nameValue !== "string")
|
|
4681
|
+
allocation.hold(nameValue);
|
|
4682
|
+
const name = shellValueText(nameValue);
|
|
4683
|
+
let currentName = nameValue;
|
|
4684
|
+
const readName = () => shellValueText(currentName);
|
|
3189
4685
|
const argumentValues = this.admitArguments(values, allocation);
|
|
3190
4686
|
let builtinFailure;
|
|
3191
4687
|
const env = Object.create(null);
|
|
@@ -3217,8 +4713,13 @@ export class Runtime {
|
|
|
3217
4713
|
return invocation;
|
|
3218
4714
|
},
|
|
3219
4715
|
};
|
|
4716
|
+
if (typeof nameValue !== "string")
|
|
4717
|
+
Object.defineProperty(context, "command", {
|
|
4718
|
+
configurable: true, enumerable: true, get: readName,
|
|
4719
|
+
set(replacement) { currentName = replacement; },
|
|
4720
|
+
});
|
|
3220
4721
|
bindCommandIO(context);
|
|
3221
|
-
bindFileOutputBudget(context, sink => this.budget.sink(sink, this.signal));
|
|
4722
|
+
bindFileOutputBudget(context, sink => this.budget.sink(sink, this.signal), (chunk, write) => this.budget.writeCounted(chunk, write, this.signal));
|
|
3222
4723
|
if (argumentValues.values.every(value => typeof value === "string"))
|
|
3223
4724
|
Reflect.deleteProperty(context, "argumentValues");
|
|
3224
4725
|
const middleware = this.middleware.map((handler) => (context, next) => {
|
|
@@ -3233,10 +4734,10 @@ export class Runtime {
|
|
|
3233
4734
|
});
|
|
3234
4735
|
const execute = composeMiddleware(middleware, (forwarded) => scope.run(async () => {
|
|
3235
4736
|
scope.assertOpen();
|
|
4737
|
+
const commandName = Object.getOwnPropertyDescriptor(forwarded, "command")?.get === readName ? currentName : forwarded.command;
|
|
3236
4738
|
const forwardedValues = getCommandArguments(forwarded);
|
|
3237
4739
|
const admitted = forwardedValues === argumentValues ? argumentValues : this.admitArguments(forwardedValues.values, allocation);
|
|
3238
4740
|
const context = { ...forwarded, args: admitted.args, argumentValues: admitted, [invocationScope]: scope,
|
|
3239
|
-
[backgroundResources]: io[backgroundResources], [backgroundPrune]: io[backgroundPrune],
|
|
3240
4741
|
...(io[valueScope] === undefined ? {} : { [valueScope]: io[valueScope] }) };
|
|
3241
4742
|
const previous = new Map();
|
|
3242
4743
|
const cwd = state.cwd;
|
|
@@ -3376,15 +4877,71 @@ export class Runtime {
|
|
|
3376
4877
|
const callerLoopDepth = state.loopDepth;
|
|
3377
4878
|
if (mapfileCallbackStates.has(state))
|
|
3378
4879
|
state.loopDepth = 0;
|
|
4880
|
+
let restoredLocals = false;
|
|
4881
|
+
const restoreLocals = async () => {
|
|
4882
|
+
if (restoredLocals)
|
|
4883
|
+
return;
|
|
4884
|
+
restoredLocals = true;
|
|
4885
|
+
for (const [name, previous] of locals)
|
|
4886
|
+
await scope.cleanup(async () => {
|
|
4887
|
+
const typed = typedSavedVariables.has(previous);
|
|
4888
|
+
await restoreVariable(state, name, previous);
|
|
4889
|
+
if (!typed && !previous.readOnly)
|
|
4890
|
+
state.readonlyVariables?.delete(name);
|
|
4891
|
+
});
|
|
4892
|
+
};
|
|
4893
|
+
let primaryFailure = false;
|
|
4894
|
+
let outcome;
|
|
4895
|
+
let checkpointFailure;
|
|
3379
4896
|
try {
|
|
3380
|
-
|
|
4897
|
+
try {
|
|
4898
|
+
await this.extensionEvent("function-enter", state, io, state.status);
|
|
4899
|
+
if (await this.extensionEvent("command", state, { ...io, ...context }, state.status, context.command))
|
|
4900
|
+
outcome = { kind: "return", value: { exitCode: state.status } };
|
|
4901
|
+
else {
|
|
4902
|
+
const diagnostic = functionDiagnostics.get(body);
|
|
4903
|
+
const status = await this.command(body, state, {
|
|
4904
|
+
...io, ...context, scriptName: body.sourceName ?? io.scriptName ?? "shell",
|
|
4905
|
+
diagnosticOffset: diagnostic?.offset ?? 0,
|
|
4906
|
+
assignmentDiagnosticContext: { name: context.command },
|
|
4907
|
+
functionCommandLines: diagnostic?.lines, diagnosticCommandLines: diagnostic?.lines,
|
|
4908
|
+
});
|
|
4909
|
+
outcome = { kind: "return", value: { exitCode: await this.finishReturn("function-return", state, { ...io, ...context }, status) } };
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
catch (error) {
|
|
4913
|
+
if (error instanceof NounsetDiagnosticFailure)
|
|
4914
|
+
throw error;
|
|
4915
|
+
if (error instanceof Flow && error.kind === "return") {
|
|
4916
|
+
outcome = { kind: "return", value: { exitCode: await this.finishReturn("function-return", state, { ...io, ...context }, error.status, error.previousStatus) } };
|
|
4917
|
+
}
|
|
4918
|
+
else {
|
|
4919
|
+
if (error instanceof Flow && error.kind === "exit" && state.extensions) {
|
|
4920
|
+
if (error.previousStatus === undefined)
|
|
4921
|
+
await restoreLocals();
|
|
4922
|
+
throw new Flow("exit", await this.beginShellExit(state, { ...io, ...context }, error.status));
|
|
4923
|
+
}
|
|
4924
|
+
throw error;
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
3381
4927
|
}
|
|
3382
4928
|
catch (error) {
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
throw error;
|
|
4929
|
+
primaryFailure = !(error instanceof Flow) || error instanceof NounsetDiagnosticFailure;
|
|
4930
|
+
outcome = { kind: "throw", reason: error };
|
|
3386
4931
|
}
|
|
3387
4932
|
finally {
|
|
4933
|
+
await scope.cleanup(async () => {
|
|
4934
|
+
try {
|
|
4935
|
+
if (!this.signal.aborted)
|
|
4936
|
+
await this.extensionEvent("function-leave", state, io, state.status);
|
|
4937
|
+
}
|
|
4938
|
+
catch (error) {
|
|
4939
|
+
if (error instanceof ExtensionCheckpointFailure && !primaryFailure)
|
|
4940
|
+
checkpointFailure = error;
|
|
4941
|
+
else
|
|
4942
|
+
throw error;
|
|
4943
|
+
}
|
|
4944
|
+
});
|
|
3388
4945
|
const restoreControls = () => {
|
|
3389
4946
|
stateMonitor(state).positionals.restore(savedPositionals, () => { state.positional = positional; });
|
|
3390
4947
|
savedPositionals.close();
|
|
@@ -3399,14 +4956,8 @@ export class Runtime {
|
|
|
3399
4956
|
functionRestoration.apply(restoreControls, false);
|
|
3400
4957
|
else
|
|
3401
4958
|
restoreControls();
|
|
3402
|
-
});
|
|
3403
|
-
|
|
3404
|
-
await scope.cleanup(async () => {
|
|
3405
|
-
const typed = typedSavedVariables.has(previous);
|
|
3406
|
-
await restoreVariable(state, name, previous);
|
|
3407
|
-
if (!typed && !previous.readOnly)
|
|
3408
|
-
state.readonlyVariables?.delete(name);
|
|
3409
|
-
});
|
|
4959
|
+
});
|
|
4960
|
+
await restoreLocals();
|
|
3410
4961
|
if (locals.has("OPTIND"))
|
|
3411
4962
|
await scope.cleanup(() => {
|
|
3412
4963
|
const restoreGetopts = () => {
|
|
@@ -3421,13 +4972,27 @@ export class Runtime {
|
|
|
3421
4972
|
await scope.cleanup(() => getoptsRestoration?.close());
|
|
3422
4973
|
await scope.cleanup(() => functionRestoration?.close());
|
|
3423
4974
|
}
|
|
4975
|
+
if (checkpointFailure) {
|
|
4976
|
+
this.signal.throwIfAborted();
|
|
4977
|
+
throw checkpointFailure;
|
|
4978
|
+
}
|
|
4979
|
+
if (outcome.kind === "throw")
|
|
4980
|
+
throw outcome.reason;
|
|
4981
|
+
return outcome.value;
|
|
3424
4982
|
}
|
|
3425
4983
|
if (selected?.kind === "builtin") {
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
const special = state.profile === "sh" && !bypassFunctions && specialBuiltinNames.has(context.command);
|
|
4984
|
+
const extensionBuiltin = state.extensions?.builtins.get(context.command);
|
|
4985
|
+
const special = state.profile === "sh" && !bypassFunctions && (specialBuiltinNames.has(context.command) || !!extensionBuiltin?.special);
|
|
3429
4986
|
if (special)
|
|
3430
4987
|
assignments.clear();
|
|
4988
|
+
if (extensionBuiltin) {
|
|
4989
|
+
const status = validateExitCode(await extensionBuiltin.execute(this.extensionContext(state, { ...io, ...context }, context.command, context.args, context.argumentValues.values)));
|
|
4990
|
+
if (special && status !== 0)
|
|
4991
|
+
throw new Flow("exit", status);
|
|
4992
|
+
return { exitCode: status };
|
|
4993
|
+
}
|
|
4994
|
+
if (context.command === "command" || context.command === "builtin" || context.command === "type")
|
|
4995
|
+
return { exitCode: await this.discoveryBuiltin(context, state, io, assignments) };
|
|
3431
4996
|
if (context.command === "." || context.command === "source")
|
|
3432
4997
|
return { exitCode: await this.sourceBuiltin(context, state, { ...io, ...context }, special) };
|
|
3433
4998
|
if (context.command === "eval")
|
|
@@ -3452,7 +5017,12 @@ export class Runtime {
|
|
|
3452
5017
|
const target = await this.searchPath(context.command, state);
|
|
3453
5018
|
if (target !== undefined)
|
|
3454
5019
|
return { exitCode: await this.scriptFile(context, state, io, target, context.args, true) };
|
|
3455
|
-
|
|
5020
|
+
const localeValue = (key) => temporaryEnvironment?.has(key) && !previous.has(key)
|
|
5021
|
+
? temporaryEnvironment.get(key).value ?? "" : state.variables[key] ?? "";
|
|
5022
|
+
const displayed = diagnosticCommandName(commandName, byteLocale({
|
|
5023
|
+
LC_ALL: localeValue("LC_ALL"), LC_CTYPE: localeValue("LC_CTYPE"), LANG: localeValue("LANG"),
|
|
5024
|
+
}), allocation);
|
|
5025
|
+
await this.diagnostic({ ...io, ...context }, concatShellValues([displayed, ": command not found"], allocation));
|
|
3456
5026
|
return { exitCode: 127 };
|
|
3457
5027
|
}
|
|
3458
5028
|
this.budget.pathLookup.suspendUntilClosed(scope);
|
|
@@ -3508,13 +5078,13 @@ export class Runtime {
|
|
|
3508
5078
|
const matches = [];
|
|
3509
5079
|
if (!bypassFunctions && state.functions.has(name))
|
|
3510
5080
|
matches.push({ kind: "function", name });
|
|
3511
|
-
if (implementedBuiltins.has(name))
|
|
5081
|
+
if (implementedBuiltins.has(name) || state.extensions?.builtins.has(name))
|
|
3512
5082
|
matches.push({ kind: "builtin", name });
|
|
3513
5083
|
else if (this.commands.has(name))
|
|
3514
5084
|
matches.push({ kind: "command", name });
|
|
3515
5085
|
else if (name === "bash" || name === "sh")
|
|
3516
5086
|
matches.push({ kind: "interpreter", name });
|
|
3517
|
-
if (state.profile === "sh" && specialBuiltinNames.has(name))
|
|
5087
|
+
if (state.profile === "sh" && (specialBuiltinNames.has(name) || state.extensions?.builtins.get(name)?.special))
|
|
3518
5088
|
matches.sort((left, right) => Number(right.kind === "builtin") - Number(left.kind === "builtin"));
|
|
3519
5089
|
return matches;
|
|
3520
5090
|
}
|
|
@@ -3563,11 +5133,13 @@ export class Runtime {
|
|
|
3563
5133
|
}
|
|
3564
5134
|
}
|
|
3565
5135
|
if (!discover) {
|
|
5136
|
+
const targetIndex = context.args.length - args.length;
|
|
3566
5137
|
const target = args.shift();
|
|
3567
5138
|
if (target === undefined)
|
|
3568
5139
|
return 0;
|
|
3569
|
-
|
|
3570
|
-
|
|
5140
|
+
const targetValue = getCommandArguments(context).values[targetIndex];
|
|
5141
|
+
if (builtin && !shellBuiltinNames.has(target) && !state.extensions?.builtins.has(target)) {
|
|
5142
|
+
await this.diagnostic({ ...io, ...context }, concatShellValues(["builtin: ", targetValue, ": not a shell builtin"], io[valueScope]));
|
|
3571
5143
|
return 1;
|
|
3572
5144
|
}
|
|
3573
5145
|
this.budget.tick();
|
|
@@ -3582,7 +5154,7 @@ export class Runtime {
|
|
|
3582
5154
|
throw error;
|
|
3583
5155
|
}
|
|
3584
5156
|
try {
|
|
3585
|
-
return await this.dispatch(
|
|
5157
|
+
return await this.dispatch(targetValue, args, state, { ...io, ...context }, assignments, true, getCommandArguments(context).values.slice(context.args.length - args.length));
|
|
3586
5158
|
}
|
|
3587
5159
|
finally {
|
|
3588
5160
|
const restore = () => { state.depth--; };
|
|
@@ -3676,7 +5248,7 @@ export class Runtime {
|
|
|
3676
5248
|
throw denied;
|
|
3677
5249
|
return matches;
|
|
3678
5250
|
}
|
|
3679
|
-
processState(context, state, arg0, args
|
|
5251
|
+
processState(context, state, io, arg0, args) {
|
|
3680
5252
|
if (state.depth >= this.budget.limits.maxSubstitutionDepth)
|
|
3681
5253
|
this.budget.fail("maxSubstitutionDepth");
|
|
3682
5254
|
const variables = Object.assign(Object.create(null), context.env, { PWD: state.cwd });
|
|
@@ -3684,11 +5256,11 @@ export class Runtime {
|
|
|
3684
5256
|
variables.OPTIND = "1";
|
|
3685
5257
|
variables.OPTERR = "1";
|
|
3686
5258
|
const child = trackState({
|
|
5259
|
+
extensions: forkExtensions(state.extensions, "process"),
|
|
3687
5260
|
cwd: state.cwd, variables, exported, functions: new Map(), getopts: { cursor: createGetoptsState(), integer: true },
|
|
3688
5261
|
directoryStack: { entries: [], bytes: 0 },
|
|
3689
5262
|
dotglob: false,
|
|
3690
5263
|
globstar: false,
|
|
3691
|
-
backgroundJobs: new BackgroundJobs(state.backgroundJobs.execution),
|
|
3692
5264
|
positional: [], arg0: shellValueText(arg0), profile: context.command === "sh" ? "sh" : "bash", status: 0, substitutionStatus: 0, depth: state.depth + 1,
|
|
3693
5265
|
loopDepth: 0, functionDepth: 0, locals: [], pipefail: false, isolated: true,
|
|
3694
5266
|
errexit: false,
|
|
@@ -3730,28 +5302,37 @@ export class Runtime {
|
|
|
3730
5302
|
await writeDiagnostic(context.stderr, `${context.command}: -c: option requires an argument\n`);
|
|
3731
5303
|
return 2;
|
|
3732
5304
|
}
|
|
3733
|
-
const
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
const child = this.processState(context, state,
|
|
5305
|
+
const arg0 = commandString && args.length ? getCommandArguments(context).values[context.args.length - args.length] : context.command;
|
|
5306
|
+
if (commandString)
|
|
5307
|
+
args.shift();
|
|
5308
|
+
const child = this.processState(context, state, io, arg0, args);
|
|
3737
5309
|
child.errexit = errexit;
|
|
3738
5310
|
child.braceexpand = braceexpand;
|
|
3739
|
-
const
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
5311
|
+
const references = new PipeDescriptorFrame(io[invocationScope]);
|
|
5312
|
+
const childIO = isolateIO({ ...io, ...context, execution: { ignoreErrexit: false }, diagnosticLine: 1, diagnosticOffset: 0, assignmentDiagnosticContext: undefined, scriptName: shellValueText(arg0) }, references);
|
|
5313
|
+
try {
|
|
5314
|
+
if (source !== undefined) {
|
|
5315
|
+
this.budget.source(Buffer.byteLength(source));
|
|
5316
|
+
return await this.finishShell(child, childIO, await this.runCommandString(source, child, childIO));
|
|
5317
|
+
}
|
|
5318
|
+
const input = new ShellInput(context.stdin, this.budget, this.signal);
|
|
5319
|
+
const descriptors = new Map(childIO.descriptors);
|
|
5320
|
+
descriptors.set(0, { ...descriptors.get(0), input });
|
|
5321
|
+
const inputIO = { ...childIO, stdin: input, descriptors };
|
|
5322
|
+
return await this.finishShell(child, inputIO, await this.runStandardInput(input, child, inputIO));
|
|
5323
|
+
}
|
|
5324
|
+
finally {
|
|
5325
|
+
await references.close();
|
|
3743
5326
|
}
|
|
3744
|
-
const input = new ShellInput(context.stdin, this.budget, this.signal);
|
|
3745
|
-
return this.runStandardInput(input, child, { ...childIO, stdin: input });
|
|
3746
5327
|
}
|
|
3747
|
-
async syntaxFailure(error, source, io, commandString) {
|
|
5328
|
+
async syntaxFailure(error, source, io, commandString, includeContext = true) {
|
|
3748
5329
|
const offset = io.diagnosticOffset ?? 0;
|
|
3749
5330
|
const line = source.slice(0, error.offset).split("\n").length;
|
|
3750
5331
|
const prefix = `${io.scriptName ?? "shell"}:${commandString ? " -c:" : ""}`;
|
|
3751
5332
|
if (error.unclosedQuote)
|
|
3752
5333
|
await writeDiagnostic(io.stderr, `${prefix} line ${offset + error.unclosedQuote.line}: unexpected EOF while looking for matching \`${error.unclosedQuote.quote}'\n`);
|
|
3753
5334
|
else if (error.offset >= source.length && !/Unterminated|nesting|Unsupported/u.test(error.reason)) {
|
|
3754
|
-
const context = error.incompleteCommand ? ` from \`${error.incompleteCommand.name}' command on line ${offset + error.incompleteCommand.line}` : "";
|
|
5335
|
+
const context = includeContext && error.incompleteCommand ? ` from \`${error.incompleteCommand.name}' command on line ${offset + error.incompleteCommand.line}` : "";
|
|
3755
5336
|
await writeDiagnostic(io.stderr, `${prefix} line ${offset + source.split("\n").length + Number(!source.endsWith("\n"))}: syntax error: unexpected end of file${context}\n`);
|
|
3756
5337
|
}
|
|
3757
5338
|
else {
|
|
@@ -3767,7 +5348,7 @@ export class Runtime {
|
|
|
3767
5348
|
try {
|
|
3768
5349
|
do {
|
|
3769
5350
|
this.signal.throwIfAborted();
|
|
3770
|
-
const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing, lineIndex);
|
|
5351
|
+
const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing, lineIndex, undefined, false, state.extensions?.syntax);
|
|
3771
5352
|
for (const warning of unit.script.warnings ?? [])
|
|
3772
5353
|
await writeDiagnostic(io.stderr, `${io.scriptName}: warning: ${warning}\n`);
|
|
3773
5354
|
if (unit.script.lists.length) {
|
|
@@ -3814,7 +5395,7 @@ export class Runtime {
|
|
|
3814
5395
|
}
|
|
3815
5396
|
const unitIO = { ...io, diagnosticOffset: offset };
|
|
3816
5397
|
try {
|
|
3817
|
-
const unit = eof ? parseShellUnit(source, 0, byteLocale(state.variables), this.budget.parsing, lineIndex) : parseShellInputUnit(source, byteLocale(state.variables), this.budget.parsing, lineIndex);
|
|
5398
|
+
const unit = eof ? parseShellUnit(source, 0, byteLocale(state.variables), this.budget.parsing, lineIndex, undefined, false, state.extensions?.syntax) : parseShellInputUnit(source, byteLocale(state.variables), this.budget.parsing, lineIndex, state.extensions?.syntax);
|
|
3818
5399
|
if (unit) {
|
|
3819
5400
|
for (const warning of unit.script.warnings ?? [])
|
|
3820
5401
|
await writeDiagnostic(io.stderr, `${io.scriptName}: warning: ${warning}\n`);
|
|
@@ -3895,10 +5476,8 @@ export class Runtime {
|
|
|
3895
5476
|
};
|
|
3896
5477
|
Reflect.deleteProperty(context, invocationScope);
|
|
3897
5478
|
Reflect.deleteProperty(context, valueScope);
|
|
3898
|
-
Reflect.deleteProperty(context, backgroundResources);
|
|
3899
|
-
Reflect.deleteProperty(context, backgroundPrune);
|
|
3900
5479
|
bindCommandIO(context);
|
|
3901
|
-
bindFileOutputBudget(context, sink => this.budget.sink(sink, runtime.signal));
|
|
5480
|
+
bindFileOutputBudget(context, sink => this.budget.sink(sink, runtime.signal), (chunk, write) => this.budget.writeCounted(chunk, write, runtime.signal));
|
|
3902
5481
|
if (argumentValues.values.every(value => typeof value === "string"))
|
|
3903
5482
|
Reflect.deleteProperty(context, "argumentValues");
|
|
3904
5483
|
const child = await runtime.shebangState(context, state);
|
|
@@ -4046,6 +5625,8 @@ export class Runtime {
|
|
|
4046
5625
|
const path = pathOf(state, target);
|
|
4047
5626
|
let source;
|
|
4048
5627
|
let environmentInterpreter = null;
|
|
5628
|
+
let interpreterCommand;
|
|
5629
|
+
let sourceBytes;
|
|
4049
5630
|
let interpreterProfile;
|
|
4050
5631
|
try {
|
|
4051
5632
|
if (loadedSource?.path === path)
|
|
@@ -4067,18 +5648,33 @@ export class Runtime {
|
|
|
4067
5648
|
this.budget.fail("maxSourceBytes");
|
|
4068
5649
|
const bytes = await interruptible(this.fs.readFile(path, { ...options, maxBytes }), this.signal);
|
|
4069
5650
|
this.budget.source(bytes.byteLength);
|
|
4070
|
-
|
|
5651
|
+
sourceBytes = bytes;
|
|
5652
|
+
const newline = bytes.indexOf(10);
|
|
5653
|
+
source = direct && bytes[0] === 35 && bytes[1] === 33
|
|
5654
|
+
? this.sourceText(bytes.subarray(0, newline < 0 ? bytes.length : newline), target)
|
|
5655
|
+
: this.sourceText(bytes, target);
|
|
4071
5656
|
}
|
|
4072
5657
|
if (direct && source.startsWith("#!")) {
|
|
4073
5658
|
const interpreter = source.split("\n", 1)[0].slice(2).replace(/^[ \t]+|[ \t]+$/gu, "");
|
|
4074
5659
|
environmentInterpreter = /^\/usr\/bin\/env(?:[ \t]+([^\n]*))?$/u.exec(interpreter);
|
|
4075
5660
|
if (!environmentInterpreter) {
|
|
4076
5661
|
const shell = /^\/(?:usr\/)?bin\/(bash|sh)(?:[ \t]+([-+]e+))?$/u.exec(interpreter);
|
|
4077
|
-
if (!shell)
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
5662
|
+
if (!shell) {
|
|
5663
|
+
const split = Array.from(interpreter).findIndex(character => character === " " || character === "\t");
|
|
5664
|
+
const executable = split < 0 ? interpreter : interpreter.slice(0, split);
|
|
5665
|
+
const name = executable.slice(executable.lastIndexOf("/") + 1);
|
|
5666
|
+
const directory = executable.slice(0, executable.lastIndexOf("/"));
|
|
5667
|
+
if ((directory !== "/bin" && directory !== "/usr/bin") || name === "sh" || name === "bash" || !this.commands.has(name)) {
|
|
5668
|
+
throw new CommandFailure(`${target}: unsupported interpreter: ${interpreter}`, 126);
|
|
5669
|
+
}
|
|
5670
|
+
const argument = split < 0 ? undefined : interpreter.slice(split).trimStart();
|
|
5671
|
+
interpreterCommand = { name, ...(argument ? { argument } : {}) };
|
|
5672
|
+
}
|
|
5673
|
+
else {
|
|
5674
|
+
interpreterProfile = shell[1] === "sh" ? "sh" : "bash";
|
|
5675
|
+
if (shell[2])
|
|
5676
|
+
errexit = shell[2].startsWith("-");
|
|
5677
|
+
}
|
|
4082
5678
|
}
|
|
4083
5679
|
}
|
|
4084
5680
|
}
|
|
@@ -4090,15 +5686,25 @@ export class Runtime {
|
|
|
4090
5686
|
this.budget.fail("maxSourceBytes");
|
|
4091
5687
|
throw new CommandFailure(filesystemDiagnostic(error, target, this.budget.onInternalError) ?? `${target}: ${message(error, this.budget.onInternalError)}`, errorCode(error) === "ENOENT" ? 127 : 126);
|
|
4092
5688
|
}
|
|
5689
|
+
const decodeSource = () => sourceBytes ? this.sourceText(sourceBytes, target) : source;
|
|
5690
|
+
const scriptSource = { path, get source() { return decodeSource(); } };
|
|
4093
5691
|
if (direct && environmentInterpreter)
|
|
4094
|
-
return this.envShebang(context, state, io, environmentInterpreter[1], target, args,
|
|
5692
|
+
return this.envShebang(context, state, io, environmentInterpreter[1], target, args, scriptSource);
|
|
5693
|
+
if (direct && interpreterCommand) {
|
|
5694
|
+
const incoming = getCommandArguments({ args, ...(context.argumentValues ? { argumentValues: context.argumentValues } : {}) });
|
|
5695
|
+
const allocation = this.budget.values.scope();
|
|
5696
|
+
io[invocationScope].register(() => allocation.close());
|
|
5697
|
+
const argumentValues = this.admitArguments([...(interpreterCommand.argument ? [interpreterCommand.argument] : []), target, ...incoming.values], allocation);
|
|
5698
|
+
return (await this.shebangTarget(context, state, io, interpreterCommand.name, argumentValues.args, { argumentValues }, target, scriptSource)).exitCode;
|
|
5699
|
+
}
|
|
5700
|
+
source = scriptSource.source;
|
|
4095
5701
|
const units = [];
|
|
4096
5702
|
const lineIndex = new SourceLineIndex(source, this.budget.parsing);
|
|
4097
5703
|
try {
|
|
4098
5704
|
let position = 0;
|
|
4099
5705
|
do {
|
|
4100
5706
|
this.signal.throwIfAborted();
|
|
4101
|
-
const unit = parseShellUnit(source, position, byteLocale(context.env), this.budget.parsing, lineIndex);
|
|
5707
|
+
const unit = parseShellUnit(source, position, byteLocale(context.env), this.budget.parsing, lineIndex, undefined, false, state.extensions?.syntax);
|
|
4102
5708
|
units.push(unit.script);
|
|
4103
5709
|
position = unit.next;
|
|
4104
5710
|
} while (position < source.length);
|
|
@@ -4110,26 +5716,32 @@ export class Runtime {
|
|
|
4110
5716
|
await writeDiagnostic(context.stderr, `${target}: line ${line}: syntax error: ${error.reason}\n`);
|
|
4111
5717
|
return error.exitCode;
|
|
4112
5718
|
}
|
|
4113
|
-
const child = this.processState(context, state, target, args
|
|
5719
|
+
const child = this.processState(context, state, io, target, args);
|
|
4114
5720
|
child.errexit = errexit;
|
|
4115
5721
|
child.braceexpand = braceexpand;
|
|
4116
5722
|
if (direct)
|
|
4117
5723
|
child.profile = interpreterProfile ?? state.profile ?? "bash";
|
|
4118
|
-
const
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
5724
|
+
const references = new PipeDescriptorFrame(io[invocationScope]);
|
|
5725
|
+
const childIO = isolateIO({ ...io, ...context, execution: { ignoreErrexit: false }, diagnosticLine: 1, diagnosticOffset: 0, assignmentDiagnosticContext: undefined, scriptName: target }, references);
|
|
5726
|
+
try {
|
|
5727
|
+
let status = 0;
|
|
5728
|
+
for (const unit of units) {
|
|
5729
|
+
for (const warning of unit.warnings ?? [])
|
|
5730
|
+
await writeDiagnostic(context.stderr, `${target}: warning: ${warning}\n`);
|
|
5731
|
+
if (!unit.lists.length)
|
|
5732
|
+
continue;
|
|
5733
|
+
const result = await this.runUnit(unit, child, childIO);
|
|
5734
|
+
status = result.exitCode;
|
|
5735
|
+
if (result.terminated)
|
|
5736
|
+
break;
|
|
5737
|
+
}
|
|
5738
|
+
return await this.finishShell(child, childIO, status);
|
|
5739
|
+
}
|
|
5740
|
+
finally {
|
|
5741
|
+
await references.close();
|
|
4129
5742
|
}
|
|
4130
|
-
return status;
|
|
4131
5743
|
}
|
|
4132
|
-
async runCurrentText(source, state, io, fatalSyntax, syntaxName, sourceValues) {
|
|
5744
|
+
async runCurrentText(source, state, io, fatalSyntax, syntaxName, byteSource = false, includeSyntaxContext = true, sourceValues) {
|
|
4133
5745
|
const lineIndex = new SourceLineIndex(source, this.budget.parsing);
|
|
4134
5746
|
let position = 0;
|
|
4135
5747
|
let status = 0;
|
|
@@ -4137,11 +5749,11 @@ export class Runtime {
|
|
|
4137
5749
|
try {
|
|
4138
5750
|
do {
|
|
4139
5751
|
this.signal.throwIfAborted();
|
|
4140
|
-
const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing, lineIndex, sourceValues);
|
|
5752
|
+
const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing, lineIndex, sourceValues, byteSource, state.extensions?.syntax);
|
|
4141
5753
|
for (const warning of unit.script.warnings ?? [])
|
|
4142
5754
|
await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: warning: ${warning}\n`);
|
|
4143
5755
|
if (unit.script.lists.length) {
|
|
4144
|
-
status = await this.
|
|
5756
|
+
status = await this.inputUnit(unit.script, state, io);
|
|
4145
5757
|
executed = true;
|
|
4146
5758
|
}
|
|
4147
5759
|
position = unit.next;
|
|
@@ -4151,7 +5763,7 @@ export class Runtime {
|
|
|
4151
5763
|
catch (error) {
|
|
4152
5764
|
if (!(error instanceof ShellSyntaxError))
|
|
4153
5765
|
throw error;
|
|
4154
|
-
const status = await this.syntaxFailure(error, source, syntaxName === undefined ? io : { ...io, scriptName: syntaxName }, false);
|
|
5766
|
+
const status = await this.syntaxFailure(error, source, syntaxName === undefined ? io : { ...io, scriptName: syntaxName }, false, includeSyntaxContext);
|
|
4155
5767
|
if (fatalSyntax && !executed)
|
|
4156
5768
|
throw new Flow("exit", status);
|
|
4157
5769
|
return status;
|
|
@@ -4195,7 +5807,7 @@ export class Runtime {
|
|
|
4195
5807
|
throw error;
|
|
4196
5808
|
}
|
|
4197
5809
|
try {
|
|
4198
|
-
return await this.runCurrentText(source.text, state, { ...io, diagnosticOffset: (io.diagnosticLine ?? 1) - 1 }, special, `${io.scriptName ?? "shell"}: eval`, source.values);
|
|
5810
|
+
return await this.runCurrentText(source.text, state, { ...io, diagnosticOffset: (io.diagnosticLine ?? 1) - 1, assignmentDiagnosticContext: { name: context.command } }, special, `${io.scriptName ?? "shell"}: eval`, false, true, source.values);
|
|
4199
5811
|
}
|
|
4200
5812
|
finally {
|
|
4201
5813
|
const restore = () => { state.depth--; };
|
|
@@ -4314,15 +5926,38 @@ export class Runtime {
|
|
|
4314
5926
|
restoration?.close();
|
|
4315
5927
|
throw error;
|
|
4316
5928
|
}
|
|
5929
|
+
let primaryFailure = false;
|
|
5930
|
+
let outcome;
|
|
5931
|
+
let leaveFailure;
|
|
4317
5932
|
try {
|
|
4318
|
-
|
|
5933
|
+
try {
|
|
5934
|
+
await this.extensionEvent("source-enter", state, io, state.status);
|
|
5935
|
+
const status = await this.runCurrentText(source, state, { ...io, scriptName: target, diagnosticOffset: 0, diagnosticLine: 1, assignmentDiagnosticContext: undefined }, special);
|
|
5936
|
+
outcome = { kind: "return", value: await this.finishReturn("source-return", state, io, status) };
|
|
5937
|
+
}
|
|
5938
|
+
catch (error) {
|
|
5939
|
+
if (error instanceof Flow && error.kind === "return")
|
|
5940
|
+
outcome = { kind: "return", value: await this.finishReturn("source-return", state, io, error.status, error.previousStatus) };
|
|
5941
|
+
else
|
|
5942
|
+
throw error;
|
|
5943
|
+
}
|
|
4319
5944
|
}
|
|
4320
5945
|
catch (error) {
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
throw error;
|
|
5946
|
+
primaryFailure = !(error instanceof Flow) || error instanceof NounsetDiagnosticFailure;
|
|
5947
|
+
outcome = { kind: "throw", reason: error instanceof Flow ? error : new NounsetDiagnosticFailure(error) };
|
|
4324
5948
|
}
|
|
4325
5949
|
finally {
|
|
5950
|
+
const scope = io[invocationScope];
|
|
5951
|
+
try {
|
|
5952
|
+
if (!this.signal.aborted)
|
|
5953
|
+
await this.extensionEvent("source-leave", state, io, state.status);
|
|
5954
|
+
}
|
|
5955
|
+
catch (reason) {
|
|
5956
|
+
if (primaryFailure)
|
|
5957
|
+
scope.failures.push(reason);
|
|
5958
|
+
else
|
|
5959
|
+
leaveFailure = { reason };
|
|
5960
|
+
}
|
|
4326
5961
|
const restore = () => {
|
|
4327
5962
|
state.depth--;
|
|
4328
5963
|
state.sourceDepth = sourceDepth;
|
|
@@ -4339,10 +5974,29 @@ export class Runtime {
|
|
|
4339
5974
|
else
|
|
4340
5975
|
restore();
|
|
4341
5976
|
}
|
|
4342
|
-
|
|
5977
|
+
catch (error) {
|
|
5978
|
+
scope.failures.push(error);
|
|
5979
|
+
}
|
|
5980
|
+
try {
|
|
4343
5981
|
savedPositionals?.close();
|
|
4344
5982
|
}
|
|
5983
|
+
catch (error) {
|
|
5984
|
+
scope.failures.push(error);
|
|
5985
|
+
}
|
|
5986
|
+
try {
|
|
5987
|
+
restoration?.close();
|
|
5988
|
+
}
|
|
5989
|
+
catch (error) {
|
|
5990
|
+
scope.failures.push(error);
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
if (leaveFailure) {
|
|
5994
|
+
this.signal.throwIfAborted();
|
|
5995
|
+
throw leaveFailure.reason instanceof Flow ? leaveFailure.reason : new NounsetDiagnosticFailure(leaveFailure.reason);
|
|
4345
5996
|
}
|
|
5997
|
+
if (outcome.kind === "throw")
|
|
5998
|
+
throw outcome.reason;
|
|
5999
|
+
return outcome.value;
|
|
4346
6000
|
}
|
|
4347
6001
|
invoke(name, args, options = {}, context, state, parent) {
|
|
4348
6002
|
return this.invokeChild(options, state, parent, () => {
|
|
@@ -4360,6 +6014,7 @@ export class Runtime {
|
|
|
4360
6014
|
scope.register(() => allocation.close());
|
|
4361
6015
|
const carrier = this.admitArguments(getCommandArguments({ args, ...(options.argumentValues ? { argumentValues: options.argumentValues } : {}) }).values, allocation);
|
|
4362
6016
|
const child = await cloneState(state, this.signal, scope, false);
|
|
6017
|
+
child.extensions = forkExtensions(state.extensions, "invocation");
|
|
4363
6018
|
child.cwd = resolvePath(context.cwd, options.cwd ?? ".");
|
|
4364
6019
|
const env = options.replaceEnv ? { ...options.env } : { ...context.env, ...options.env, PWD: child.cwd };
|
|
4365
6020
|
if (guestArrays(child) || Object.keys(env).some(key => arrayStore(child)?.get(key)))
|
|
@@ -4382,14 +6037,15 @@ export class Runtime {
|
|
|
4382
6037
|
child.locals = [];
|
|
4383
6038
|
const input = options.stdin === undefined ? undefined : new ShellInput(options.stdin, this.budget, this.signal);
|
|
4384
6039
|
const stdinIsDefault = options.stdin === undefined ? context.stdinIsDefault : (options.stdinIsDefault ?? false);
|
|
4385
|
-
const
|
|
6040
|
+
const references = new PipeDescriptorFrame(scope);
|
|
6041
|
+
const io = isolateIO({
|
|
4386
6042
|
...context,
|
|
4387
6043
|
[invocationScope]: scope,
|
|
4388
6044
|
stdin: input ?? context.stdin,
|
|
4389
6045
|
...(stdinIsDefault === undefined ? {} : { stdinIsDefault }),
|
|
4390
6046
|
stdout: options.stdout ? this.budget.sink(options.stdout, this.signal) : context.stdout,
|
|
4391
6047
|
stderr: options.stderr ? this.budget.sink(options.stderr, this.signal) : context.stderr,
|
|
4392
|
-
};
|
|
6048
|
+
}, references);
|
|
4393
6049
|
const command = {
|
|
4394
6050
|
kind: "simple", redirects: [],
|
|
4395
6051
|
words: [name, ...carrier.values].map((value) => {
|
|
@@ -4403,8 +6059,18 @@ export class Runtime {
|
|
|
4403
6059
|
return { exitCode: await this.runCommandIsolated(command, child, io) };
|
|
4404
6060
|
}
|
|
4405
6061
|
finally {
|
|
4406
|
-
|
|
4407
|
-
|
|
6062
|
+
try {
|
|
6063
|
+
await this.releaseExtensions(child);
|
|
6064
|
+
}
|
|
6065
|
+
finally {
|
|
6066
|
+
try {
|
|
6067
|
+
stateMonitor(child)?.closeValues();
|
|
6068
|
+
await input?.close();
|
|
6069
|
+
}
|
|
6070
|
+
finally {
|
|
6071
|
+
await references.close();
|
|
6072
|
+
}
|
|
6073
|
+
}
|
|
4408
6074
|
}
|
|
4409
6075
|
}
|
|
4410
6076
|
async setOptions(context, state) {
|
|
@@ -4436,12 +6102,17 @@ export class Runtime {
|
|
|
4436
6102
|
state.errexit = enabled;
|
|
4437
6103
|
else if (flag === "u")
|
|
4438
6104
|
state.nounset = enabled;
|
|
6105
|
+
else if ([...state.extensions?.options.values() ?? []].some(option => option.flag === flag)) {
|
|
6106
|
+
for (const option of state.extensions.options.values())
|
|
6107
|
+
if (option.flag === flag)
|
|
6108
|
+
option.enabled = enabled;
|
|
6109
|
+
}
|
|
4439
6110
|
else if (flag === "B")
|
|
4440
6111
|
state.braceexpand = enabled;
|
|
4441
6112
|
else if (flag === "o" && position === option.length - 1) {
|
|
4442
6113
|
const name = args[index + 1];
|
|
4443
6114
|
if (name === undefined) {
|
|
4444
|
-
const options = [["braceexpand", state.braceexpand !== false], ["errexit", !!state.errexit], ["nounset", !!state.nounset], ["pipefail", state.pipefail]];
|
|
6115
|
+
const options = [["braceexpand", state.braceexpand !== false], ["errexit", !!state.errexit], ["nounset", !!state.nounset], ["pipefail", state.pipefail], ...[...state.extensions?.options.values() ?? []].map(option => [option.name, option.enabled])];
|
|
4445
6116
|
for (const [name, active] of options)
|
|
4446
6117
|
await writeText(stdout, enabled ? `${name}\t${active ? "on" : "off"}\n` : `set ${active ? "-" : "+"}o ${name}\n`);
|
|
4447
6118
|
}
|
|
@@ -4454,6 +6125,8 @@ export class Runtime {
|
|
|
4454
6125
|
state.pipefail = enabled;
|
|
4455
6126
|
else if (name === "braceexpand")
|
|
4456
6127
|
state.braceexpand = enabled;
|
|
6128
|
+
else if (state.extensions?.options.has(name))
|
|
6129
|
+
state.extensions.options.get(name).enabled = enabled;
|
|
4457
6130
|
else {
|
|
4458
6131
|
const unsupported = unsupportedSetOptionNames.has(name);
|
|
4459
6132
|
await this.diagnostic(context, `set: ${name}: ${unsupported ? "unsupported shell option" : "invalid option name"}`);
|
|
@@ -4886,13 +6559,27 @@ export class Runtime {
|
|
|
4886
6559
|
for (const name of ["dotglob", "globstar"])
|
|
4887
6560
|
if ((!set || state[name]) && (!unset || !state[name]))
|
|
4888
6561
|
await emit(name);
|
|
6562
|
+
for (const option of state.extensions?.shoptOptions.values() ?? [])
|
|
6563
|
+
if (!quiet && (!set || option.enabled) && (!unset || !option.enabled))
|
|
6564
|
+
await writeText(context.stdout, print ? `shopt -${option.enabled ? "s" : "u"} ${option.name}\n` : `${option.name.padEnd(19)}\t${option.enabled ? "on" : "off"}\n`);
|
|
4889
6565
|
return 0;
|
|
4890
6566
|
}
|
|
4891
6567
|
let status = 0;
|
|
4892
6568
|
for (; index < context.args.length; index++) {
|
|
4893
6569
|
this.signal.throwIfAborted();
|
|
4894
6570
|
const name = context.args[index];
|
|
4895
|
-
|
|
6571
|
+
const extension = state.extensions?.shoptOptions.get(name);
|
|
6572
|
+
if (extension) {
|
|
6573
|
+
if (set || unset)
|
|
6574
|
+
extension.enabled = set;
|
|
6575
|
+
else {
|
|
6576
|
+
if (!quiet)
|
|
6577
|
+
await writeText(context.stdout, print ? `shopt -${extension.enabled ? "s" : "u"} ${name}\n` : `${name.padEnd(19)}\t${extension.enabled ? "on" : "off"}\n`);
|
|
6578
|
+
if (!extension.enabled)
|
|
6579
|
+
status = 1;
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
else if (name !== "dotglob" && name !== "globstar") {
|
|
4896
6583
|
await this.diagnostic(context, `shopt: ${name}: unsupported shell option name (supported: dotglob, globstar)`);
|
|
4897
6584
|
status = 1;
|
|
4898
6585
|
}
|
|
@@ -4906,6 +6593,54 @@ export class Runtime {
|
|
|
4906
6593
|
}
|
|
4907
6594
|
return status;
|
|
4908
6595
|
}
|
|
6596
|
+
async printDeclarationValue(value, state, io) {
|
|
6597
|
+
const allocation = this.budget.values.scope();
|
|
6598
|
+
try {
|
|
6599
|
+
allocation.hold(value);
|
|
6600
|
+
const length = shellValueByteLength(value);
|
|
6601
|
+
allocation.reserve(length * 8 + 16, 1);
|
|
6602
|
+
const bytes = shellValueBytes(value, allocation);
|
|
6603
|
+
let text;
|
|
6604
|
+
if (!byteLocale(state.variables)) {
|
|
6605
|
+
try {
|
|
6606
|
+
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
6607
|
+
}
|
|
6608
|
+
catch {
|
|
6609
|
+
text = undefined;
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
const ansi = text === undefined ? bytes.some(byte => byte < 32 || byte >= 127) : /[\p{C}\p{Zl}\p{Zp}]/u.test(text);
|
|
6613
|
+
let quoted = ansi ? "$'" : '"';
|
|
6614
|
+
const controls = ["", "", "", "", "", "", "", "\\a", "\\b", "\\t", "\\n", "\\v", "\\f", "\\r"];
|
|
6615
|
+
let visited = 0;
|
|
6616
|
+
for (const item of text ?? bytes) {
|
|
6617
|
+
if (visited++ % 1024 === 0) {
|
|
6618
|
+
this.budget.cpuCheckpoint();
|
|
6619
|
+
await yieldTurn(this.signal);
|
|
6620
|
+
}
|
|
6621
|
+
const character = typeof item === "number" ? String.fromCharCode(item) : item;
|
|
6622
|
+
const code = character.codePointAt(0);
|
|
6623
|
+
if (ansi && controls[code])
|
|
6624
|
+
quoted += controls[code];
|
|
6625
|
+
else if (ansi && code === 27)
|
|
6626
|
+
quoted += "\\E";
|
|
6627
|
+
else if (ansi && (code < 32 || code === 127 || text === undefined && code >= 128 || /[\p{C}\p{Zl}\p{Zp}]/u.test(character))) {
|
|
6628
|
+
const encoded = text === undefined ? [code] : new TextEncoder().encode(character);
|
|
6629
|
+
for (const byte of encoded)
|
|
6630
|
+
quoted += `\\${byte.toString(8).padStart(3, "0")}`;
|
|
6631
|
+
}
|
|
6632
|
+
else if (character === "\\" || (ansi ? character === "'" : character === '"' || character === "$" || character === "`"))
|
|
6633
|
+
quoted += `\\${character}`;
|
|
6634
|
+
else
|
|
6635
|
+
quoted += character;
|
|
6636
|
+
}
|
|
6637
|
+
quoted += ansi ? "'" : '"';
|
|
6638
|
+
await writeText(io.stdout, quoted);
|
|
6639
|
+
}
|
|
6640
|
+
finally {
|
|
6641
|
+
allocation.close();
|
|
6642
|
+
}
|
|
6643
|
+
}
|
|
4909
6644
|
async printfVariable(context, state, assignments) {
|
|
4910
6645
|
const incoming = getCommandArguments(context);
|
|
4911
6646
|
let offset = 0;
|
|
@@ -5163,118 +6898,12 @@ export class Runtime {
|
|
|
5163
6898
|
}
|
|
5164
6899
|
}
|
|
5165
6900
|
}
|
|
5166
|
-
async waitBuiltin(context, state) {
|
|
5167
|
-
let next = false;
|
|
5168
|
-
let variable;
|
|
5169
|
-
let index = 0;
|
|
5170
|
-
for (; index < context.args.length; index++) {
|
|
5171
|
-
const option = context.args[index];
|
|
5172
|
-
if (option === "--") {
|
|
5173
|
-
index++;
|
|
5174
|
-
break;
|
|
5175
|
-
}
|
|
5176
|
-
if (!option.startsWith("-") || option === "-")
|
|
5177
|
-
break;
|
|
5178
|
-
for (let flag = 1; flag < option.length; flag++) {
|
|
5179
|
-
const letter = option[flag];
|
|
5180
|
-
if (letter === "n")
|
|
5181
|
-
next = true;
|
|
5182
|
-
else if (letter === "f") { /* Processes do not stop in the virtual shell. */ }
|
|
5183
|
-
else if (letter === "p") {
|
|
5184
|
-
variable = option.slice(flag + 1) || context.args[++index];
|
|
5185
|
-
if (variable === undefined) {
|
|
5186
|
-
await writeDiagnostic(context.stderr, "wait: -p: option requires an argument\n");
|
|
5187
|
-
return 2;
|
|
5188
|
-
}
|
|
5189
|
-
if (!/^[a-zA-Z_][a-zA-Z_0-9]*(?:\[.+\])?$/su.test(variable)) {
|
|
5190
|
-
await writeDiagnostic(context.stderr, `wait: ${variable}: not a valid identifier\n`);
|
|
5191
|
-
return 1;
|
|
5192
|
-
}
|
|
5193
|
-
break;
|
|
5194
|
-
}
|
|
5195
|
-
else {
|
|
5196
|
-
await writeDiagnostic(context.stderr, `wait: -${letter}: invalid option\n`);
|
|
5197
|
-
return 2;
|
|
5198
|
-
}
|
|
5199
|
-
}
|
|
5200
|
-
}
|
|
5201
|
-
const target = variable === undefined ? undefined : /^([a-zA-Z_][a-zA-Z_0-9]*)(?:\[(.+)\])?$/su.exec(variable);
|
|
5202
|
-
if (target) {
|
|
5203
|
-
const name = target[1];
|
|
5204
|
-
if (state.readonlyVariables?.has(name)) {
|
|
5205
|
-
await writeDiagnostic(context.stderr, `wait: ${name}: readonly variable\n`);
|
|
5206
|
-
return 1;
|
|
5207
|
-
}
|
|
5208
|
-
if (target[2] === undefined) {
|
|
5209
|
-
if (arrayStore(state)?.get(name))
|
|
5210
|
-
await this.unsetIndexed(state, name);
|
|
5211
|
-
else
|
|
5212
|
-
this.unsetVariable(state, name);
|
|
5213
|
-
}
|
|
5214
|
-
}
|
|
5215
|
-
const publish = async (id) => {
|
|
5216
|
-
if (!target)
|
|
5217
|
-
return;
|
|
5218
|
-
if (target[2] === undefined)
|
|
5219
|
-
await this.assignVariable(state, target[1], id);
|
|
5220
|
-
else
|
|
5221
|
-
await this.arrayAssignment({ kind: "element", name: target[1], index: { decimal: target[2], source: target[2] }, append: false, value: { offset: 0, parts: [{ kind: "text", value: id, quoted: true }] } }, state, context);
|
|
5222
|
-
};
|
|
5223
|
-
const table = state.backgroundJobs;
|
|
5224
|
-
const ids = context.args.slice(index);
|
|
5225
|
-
const selected = [];
|
|
5226
|
-
let invalid = false;
|
|
5227
|
-
for (const id of ids) {
|
|
5228
|
-
if (!id.startsWith("%") && !/^[0-9]+$/u.test(id)) {
|
|
5229
|
-
await writeDiagnostic(context.stderr, `wait: ${id}: not a pid or valid job spec\n`);
|
|
5230
|
-
if (next)
|
|
5231
|
-
continue;
|
|
5232
|
-
return 1;
|
|
5233
|
-
}
|
|
5234
|
-
const job = table.resolve(id);
|
|
5235
|
-
if (!job) {
|
|
5236
|
-
await writeDiagnostic(context.stderr, `wait: ${id}: not a child of this shell\n`);
|
|
5237
|
-
invalid = true;
|
|
5238
|
-
}
|
|
5239
|
-
else
|
|
5240
|
-
selected.push(job);
|
|
5241
|
-
}
|
|
5242
|
-
if (next) {
|
|
5243
|
-
const eligible = ids.length ? selected.filter(job => !job.waited) : [...table.jobs.values()].filter(job => !job.done && !job.consumed);
|
|
5244
|
-
if (!eligible.length)
|
|
5245
|
-
return 127;
|
|
5246
|
-
const result = await interruptible(Promise.race(eligible.map(async (job) => ({ job, status: await job.promise }))), this.signal);
|
|
5247
|
-
result.job.consumed = true;
|
|
5248
|
-
await publish(result.job.id);
|
|
5249
|
-
return result.status;
|
|
5250
|
-
}
|
|
5251
|
-
if (!ids.length) {
|
|
5252
|
-
await interruptible(Promise.all([...table.jobs.values()].map(job => job.promise)), this.signal);
|
|
5253
|
-
table.jobs.clear();
|
|
5254
|
-
return 0;
|
|
5255
|
-
}
|
|
5256
|
-
let status = invalid ? 127 : 0;
|
|
5257
|
-
for (const id of ids) {
|
|
5258
|
-
const job = table.resolve(id);
|
|
5259
|
-
if (!job) {
|
|
5260
|
-
status = 127;
|
|
5261
|
-
continue;
|
|
5262
|
-
}
|
|
5263
|
-
status = await interruptible(job.promise, this.signal);
|
|
5264
|
-
job.consumed = true;
|
|
5265
|
-
job.waited = true;
|
|
5266
|
-
await publish(job.id);
|
|
5267
|
-
}
|
|
5268
|
-
return status;
|
|
5269
|
-
}
|
|
5270
6901
|
async builtin(context, state, assignments, diagnose, suppressSpecial = false) {
|
|
5271
6902
|
const { command, args, stdout, stderr } = context;
|
|
5272
6903
|
if (command === ":" || command === "true")
|
|
5273
6904
|
return 0;
|
|
5274
6905
|
if (command === "false")
|
|
5275
6906
|
return 1;
|
|
5276
|
-
if (command === "wait")
|
|
5277
|
-
return this.waitBuiltin(context, state);
|
|
5278
6907
|
if (command === "shopt")
|
|
5279
6908
|
return this.shoptBuiltin(context, state);
|
|
5280
6909
|
if (command === "let")
|
|
@@ -5358,6 +6987,8 @@ export class Runtime {
|
|
|
5358
6987
|
const associativeDeclaration = command === "declare";
|
|
5359
6988
|
const declarationArgs = [...args];
|
|
5360
6989
|
let indexedLocal = associativeDeclaration;
|
|
6990
|
+
const readonlySyntax = state.extensions?.syntax.indexedDeclarations?.includes("readonly") === true;
|
|
6991
|
+
let indexedReadonly = false;
|
|
5361
6992
|
if (associativeDeclaration) {
|
|
5362
6993
|
if (declarationArgs.shift() !== "-A" || !declarationArgs.length) {
|
|
5363
6994
|
await this.diagnostic(context, "declare: only -A NAME is supported");
|
|
@@ -5382,9 +7013,26 @@ export class Runtime {
|
|
|
5382
7013
|
const option = declarationArgs.shift();
|
|
5383
7014
|
if (option === "--")
|
|
5384
7015
|
break;
|
|
5385
|
-
if (
|
|
5386
|
-
|
|
5387
|
-
|
|
7016
|
+
if (!readonlySyntax) {
|
|
7017
|
+
if (option !== "-p") {
|
|
7018
|
+
await writeDiagnostic(stderr, `readonly: ${option}: unsupported option\n`);
|
|
7019
|
+
return 2;
|
|
7020
|
+
}
|
|
7021
|
+
}
|
|
7022
|
+
else {
|
|
7023
|
+
if (option === "-") {
|
|
7024
|
+
declarationArgs.unshift(option);
|
|
7025
|
+
break;
|
|
7026
|
+
}
|
|
7027
|
+
for (const flag of option.slice(1)) {
|
|
7028
|
+
if (flag === "a")
|
|
7029
|
+
indexedReadonly = true;
|
|
7030
|
+
else if (flag !== "p") {
|
|
7031
|
+
await this.diagnostic(context, `readonly: -${flag}: invalid option`);
|
|
7032
|
+
await writeText(stderr, "readonly: usage: readonly [-aAf] [name[=value] ...] or readonly -p\n");
|
|
7033
|
+
return 2;
|
|
7034
|
+
}
|
|
7035
|
+
}
|
|
5388
7036
|
}
|
|
5389
7037
|
}
|
|
5390
7038
|
}
|
|
@@ -5396,6 +7044,48 @@ export class Runtime {
|
|
|
5396
7044
|
let status = 0;
|
|
5397
7045
|
if (!declarationArgs.length) {
|
|
5398
7046
|
const names = command === "readonly" ? state.readonlyVariables ?? [] : state.exported;
|
|
7047
|
+
if (command === "readonly" && readonlySyntax) {
|
|
7048
|
+
const allocation = this.budget.values.scope();
|
|
7049
|
+
try {
|
|
7050
|
+
const count = state.readonlyVariables?.size ?? 0;
|
|
7051
|
+
allocation.reserve(count * 32, count);
|
|
7052
|
+
for (const name of [...names].sort()) {
|
|
7053
|
+
this.signal.throwIfAborted();
|
|
7054
|
+
const binding = arrayStore(state)?.get(name);
|
|
7055
|
+
if (indexedReadonly && !binding)
|
|
7056
|
+
continue;
|
|
7057
|
+
if (binding) {
|
|
7058
|
+
binding.retain();
|
|
7059
|
+
try {
|
|
7060
|
+
allocation.reserve(binding.values.size * 16, binding.values.size);
|
|
7061
|
+
await writeText(stdout, `declare -ar ${name}=(`);
|
|
7062
|
+
let first = true;
|
|
7063
|
+
for (const index of [...binding.values.keys()].sort((left, right) => left - right)) {
|
|
7064
|
+
await writeText(stdout, `${first ? "" : " "}[${index}]=`);
|
|
7065
|
+
await this.printDeclarationValue(binding.getValue(index), state, context);
|
|
7066
|
+
first = false;
|
|
7067
|
+
}
|
|
7068
|
+
await writeText(stdout, ")\n");
|
|
7069
|
+
}
|
|
7070
|
+
finally {
|
|
7071
|
+
await binding.release();
|
|
7072
|
+
}
|
|
7073
|
+
}
|
|
7074
|
+
else {
|
|
7075
|
+
await writeText(stdout, `declare -r ${name}`);
|
|
7076
|
+
if (Object.hasOwn(state.variables, name)) {
|
|
7077
|
+
await writeText(stdout, "=");
|
|
7078
|
+
await this.printDeclarationValue(stateMonitor(state)?.values.get(name, state.variables[name]) ?? state.variables[name], state, context);
|
|
7079
|
+
}
|
|
7080
|
+
await writeText(stdout, "\n");
|
|
7081
|
+
}
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
finally {
|
|
7085
|
+
allocation.close();
|
|
7086
|
+
}
|
|
7087
|
+
return 0;
|
|
7088
|
+
}
|
|
5399
7089
|
for (const name of names)
|
|
5400
7090
|
if (arrayStore(state)?.get(name)) {
|
|
5401
7091
|
await this.diagnostic(context, "indexed array: listing indexed bindings is unsupported");
|
|
@@ -5409,15 +7099,21 @@ export class Runtime {
|
|
|
5409
7099
|
const declarationOffset = args.length - declarationArgs.length;
|
|
5410
7100
|
for (let declarationIndex = 0; declarationIndex < declarationArgs.length; declarationIndex++) {
|
|
5411
7101
|
const arg = declarationArgs[declarationIndex];
|
|
5412
|
-
const match = /^([a-zA-Z_][a-zA-Z_0-9]*)(?:=(.*))?$/su.exec(arg);
|
|
7102
|
+
const match = (command === "readonly" && readonlySyntax ? /^([a-zA-Z_][a-zA-Z_0-9]*)(?:\+?=(.*))?$/su : /^([a-zA-Z_][a-zA-Z_0-9]*)(?:=(.*))?$/su).exec(arg);
|
|
5413
7103
|
if (!match) {
|
|
5414
7104
|
await this.diagnostic(context, `${command}: \`${arg}': not a valid identifier`);
|
|
5415
7105
|
status = 1;
|
|
5416
7106
|
continue;
|
|
5417
7107
|
}
|
|
5418
7108
|
const name = match[1];
|
|
7109
|
+
const append = command === "readonly" && readonlySyntax && arg[name.length] === "+";
|
|
7110
|
+
const assignedValue = () => {
|
|
7111
|
+
const original = declarationValues[declarationOffset + declarationIndex];
|
|
7112
|
+
return typeof original === "string" ? match[2] : shellValueFromBytes(shellValueBytes(original, context[valueScope]).subarray(name.length + (append ? 2 : 1)), context[valueScope]);
|
|
7113
|
+
};
|
|
5419
7114
|
if (state.readonlyVariables?.has(name) && (match[2] !== undefined || command === "local")) {
|
|
5420
|
-
|
|
7115
|
+
const provenance = readonlySyntax && (command === "readonly" || command === "local" && indexedLocal) ? `${command}: ` : "";
|
|
7116
|
+
await this.diagnostic(context, `${provenance}${name}: readonly variable`);
|
|
5421
7117
|
status = 1;
|
|
5422
7118
|
continue;
|
|
5423
7119
|
}
|
|
@@ -5426,6 +7122,32 @@ export class Runtime {
|
|
|
5426
7122
|
status = 1;
|
|
5427
7123
|
continue;
|
|
5428
7124
|
}
|
|
7125
|
+
if (command === "readonly" && readonlySyntax && (indexedReadonly || match[2]?.startsWith("("))) {
|
|
7126
|
+
const assigned = match[2] !== undefined ? assignedValue() : undefined;
|
|
7127
|
+
if (match[2]?.startsWith("(")) {
|
|
7128
|
+
this.budget.source(shellValueByteLength(assigned));
|
|
7129
|
+
const source = typeof assigned === "string" ? assigned : Buffer.from(shellValueBytes(assigned, context[valueScope])).toString("latin1");
|
|
7130
|
+
const entries = parseCompoundArrayValue(source, byteLocale(state.variables), typeof assigned !== "string", state.extensions?.syntax, this.budget.parsing);
|
|
7131
|
+
await this.arrayAssignment({ kind: "compound", name, append, entries }, state, context, "readonly");
|
|
7132
|
+
}
|
|
7133
|
+
else if (assigned !== undefined) {
|
|
7134
|
+
await this.arrayAssignment({ kind: "element", name, append, index: { decimal: "0" }, value: {
|
|
7135
|
+
offset: 0, parts: [{ kind: "text", value: shellValueText(assigned), quoted: true, ...(typeof assigned === "string" ? {} : { byteValue: assigned }) }],
|
|
7136
|
+
} }, state, context, "readonly");
|
|
7137
|
+
}
|
|
7138
|
+
else if (!arrayStore(state)?.get(name) && Object.hasOwn(state.variables, name) && !state.readonlyVariables?.has(name)) {
|
|
7139
|
+
await this.arrayAssignment({ kind: "compound", name, append: true, entries: [] }, state, context, "readonly");
|
|
7140
|
+
}
|
|
7141
|
+
else {
|
|
7142
|
+
state.readonlyVariables ??= new Set();
|
|
7143
|
+
state.readonlyVariables.add(name);
|
|
7144
|
+
}
|
|
7145
|
+
const previous = assignments.get(name);
|
|
7146
|
+
if (previous && locals?.get(name) !== previous)
|
|
7147
|
+
previous.heldValue?.release();
|
|
7148
|
+
assignments.delete(name);
|
|
7149
|
+
continue;
|
|
7150
|
+
}
|
|
5429
7151
|
if ((command === "local" || associativeDeclaration) && indexedLocal) {
|
|
5430
7152
|
if (controlNames.has(name))
|
|
5431
7153
|
throw new ArrayFailure("control binding cannot be indexed");
|
|
@@ -5460,10 +7182,9 @@ export class Runtime {
|
|
|
5460
7182
|
if (!saved && current && current.associative !== associativeDeclaration)
|
|
5461
7183
|
throw new ArrayFailure("cannot convert array kind");
|
|
5462
7184
|
shadow = !saved && current ? await current.copy(this.signal) : IndexedBinding.create(store.owner, associativeDeclaration);
|
|
5463
|
-
const value = match[2]
|
|
7185
|
+
const value = match[2] !== undefined ? assignedValue() : !saved && !current && Object.hasOwn(state.variables, name) ? stateMonitor(state)?.values.get(name, state.variables[name]) ?? state.variables[name] : undefined;
|
|
5464
7186
|
if (value !== undefined) {
|
|
5465
|
-
const
|
|
5466
|
-
const token = await valueToken(shadow.owner, retained, this.signal);
|
|
7187
|
+
const token = await textToken(shadow.owner, value, this.signal);
|
|
5467
7188
|
try {
|
|
5468
7189
|
shadow.insert(associativeDeclaration ? (await shadow.keyIndex("0", operation, this.signal, true)) : 0, token);
|
|
5469
7190
|
}
|
|
@@ -5565,7 +7286,7 @@ export class Runtime {
|
|
|
5565
7286
|
const tickets = operation.reserve({ generation: true, version: true, epoch: true, work: 8 });
|
|
5566
7287
|
shadow = IndexedBinding.create(store.owner);
|
|
5567
7288
|
if (match[2] !== undefined) {
|
|
5568
|
-
const token = await textToken(shadow.owner,
|
|
7289
|
+
const token = await textToken(shadow.owner, assignedValue(), this.signal);
|
|
5569
7290
|
try {
|
|
5570
7291
|
shadow.insert(0, token);
|
|
5571
7292
|
}
|
|
@@ -5614,14 +7335,12 @@ export class Runtime {
|
|
|
5614
7335
|
}
|
|
5615
7336
|
}
|
|
5616
7337
|
if (match[2] !== undefined && arrayStore(state)?.get(name)) {
|
|
5617
|
-
await this.arrayZero(state, name, async () =>
|
|
7338
|
+
await this.arrayZero(state, name, async () => assignedValue(), append, command === "readonly");
|
|
5618
7339
|
assignments.delete(name);
|
|
5619
7340
|
continue;
|
|
5620
7341
|
}
|
|
5621
7342
|
if (match[2] !== undefined) {
|
|
5622
|
-
|
|
5623
|
-
const value = typeof original === "string" ? match[2] : shellValueFromBytes(shellValueBytes(original, context[valueScope]).subarray(name.length + 1), context[valueScope]);
|
|
5624
|
-
this.writeVariable(state, name, value);
|
|
7343
|
+
this.writeVariable(state, name, append ? concatShellValues([stateMonitor(state)?.values.get(name, state.variables[name] ?? "") ?? state.variables[name] ?? "", assignedValue()], context[valueScope]) : assignedValue());
|
|
5625
7344
|
}
|
|
5626
7345
|
else if (command === "local" && name === "OPTIND")
|
|
5627
7346
|
this.syncGetopts(state);
|
|
@@ -5647,7 +7366,7 @@ export class Runtime {
|
|
|
5647
7366
|
const base = selected[1];
|
|
5648
7367
|
const selector = selected[2];
|
|
5649
7368
|
if (state.readonlyVariables?.has(base)) {
|
|
5650
|
-
await this.diagnostic(context, "indexed array: readonly binding");
|
|
7369
|
+
await this.diagnostic(context, state.extensions?.syntax.indexedDeclarations?.includes("readonly") ? `unset: ${base}: cannot unset: readonly variable` : "indexed array: readonly binding");
|
|
5651
7370
|
status = 1;
|
|
5652
7371
|
continue;
|
|
5653
7372
|
}
|
|
@@ -5758,58 +7477,34 @@ export class Runtime {
|
|
|
5758
7477
|
return 2;
|
|
5759
7478
|
}
|
|
5760
7479
|
const input = context.stdin instanceof ShellInput ? context.stdin : new ShellInput(context.stdin, this.budget, this.signal);
|
|
5761
|
-
const line = count === 0 && context.stdin === closedSource ?
|
|
5762
|
-
: await input.line(raw,
|
|
7480
|
+
const line = count === 0 && context.stdin === closedSource ? undefined
|
|
7481
|
+
: await input.line(raw, {
|
|
5763
7482
|
...(count === undefined ? {} : { count }), ...(delimiter === undefined ? {} : { delimiter }), byteCount: byteLocale(state.variables), exact,
|
|
5764
7483
|
});
|
|
5765
|
-
|
|
5766
|
-
if (
|
|
5767
|
-
|
|
5768
|
-
|
|
7484
|
+
try {
|
|
7485
|
+
if (!names.length) {
|
|
7486
|
+
if (state.readonlyVariables?.has("REPLY")) {
|
|
7487
|
+
await this.diagnostic(context, "REPLY: readonly variable");
|
|
7488
|
+
return 1;
|
|
7489
|
+
}
|
|
7490
|
+
this.writeVariable(state, "REPLY", line?.shellValue ?? "");
|
|
7491
|
+
}
|
|
7492
|
+
else {
|
|
7493
|
+
const separators = exact ? "" : stateMonitor(state)?.values.get("IFS", state.variables.IFS ?? " \t\n") ?? state.variables.IFS ?? " \t\n";
|
|
7494
|
+
const fields = await line?.fields(separators, names.length) ?? [];
|
|
7495
|
+
for (let index = 0; index < names.length; index++) {
|
|
7496
|
+
if (state.readonlyVariables?.has(names[index])) {
|
|
7497
|
+
await this.diagnostic(context, `${names[index]}: readonly variable`);
|
|
7498
|
+
return index === names.length - 1 ? 1 : 2;
|
|
7499
|
+
}
|
|
7500
|
+
await this.assignVariable(state, names[index], fields[index]?.value ?? "");
|
|
7501
|
+
}
|
|
5769
7502
|
}
|
|
5770
|
-
|
|
7503
|
+
return line?.terminated ? 0 : 1;
|
|
7504
|
+
}
|
|
7505
|
+
finally {
|
|
7506
|
+
line?.release();
|
|
5771
7507
|
}
|
|
5772
|
-
else {
|
|
5773
|
-
const separators = exact ? "" : state.variables.IFS ?? " \t\n";
|
|
5774
|
-
let end = 0;
|
|
5775
|
-
let offset = 0;
|
|
5776
|
-
let point = 0;
|
|
5777
|
-
for (const character of line.value) {
|
|
5778
|
-
offset += character.length;
|
|
5779
|
-
if (line.escaped.has(point) || !separators.includes(character) || !" \t\n".includes(character))
|
|
5780
|
-
end = offset;
|
|
5781
|
-
point++;
|
|
5782
|
-
}
|
|
5783
|
-
let position = 0;
|
|
5784
|
-
point = 0;
|
|
5785
|
-
const separator = () => position < end && !line.escaped.has(point) && separators.includes(String.fromCodePoint(line.value.codePointAt(position)));
|
|
5786
|
-
const whitespace = () => separator() && " \t\n".includes(line.value[position]);
|
|
5787
|
-
const advance = () => { position += line.value.codePointAt(position) > 0xffff ? 2 : 1; point++; };
|
|
5788
|
-
while (position < end && whitespace())
|
|
5789
|
-
advance();
|
|
5790
|
-
const fields = [];
|
|
5791
|
-
while (position < end && fields.length < names.length) {
|
|
5792
|
-
const start = position;
|
|
5793
|
-
while (position < end && !separator())
|
|
5794
|
-
advance();
|
|
5795
|
-
fields.push({ start, end: position });
|
|
5796
|
-
while (position < end && whitespace())
|
|
5797
|
-
advance();
|
|
5798
|
-
if (position < end && separator())
|
|
5799
|
-
advance();
|
|
5800
|
-
while (position < end && whitespace())
|
|
5801
|
-
advance();
|
|
5802
|
-
}
|
|
5803
|
-
for (let index = 0; index < names.length; index++) {
|
|
5804
|
-
if (state.readonlyVariables?.has(names[index])) {
|
|
5805
|
-
await this.diagnostic(context, `${names[index]}: readonly variable`);
|
|
5806
|
-
return index === names.length - 1 ? 1 : 2;
|
|
5807
|
-
}
|
|
5808
|
-
const field = fields[index];
|
|
5809
|
-
await this.assignVariable(state, names[index], field ? line.value.slice(field.start, index === names.length - 1 && position < end ? end : field.end) : "");
|
|
5810
|
-
}
|
|
5811
|
-
}
|
|
5812
|
-
return line.terminated ? 0 : 1;
|
|
5813
7508
|
}
|
|
5814
7509
|
if (command === "exit" || command === "return") {
|
|
5815
7510
|
if (command === "return" && state.functionDepth === 0 && !state.sourceDepth) {
|
|
@@ -5825,7 +7520,7 @@ export class Runtime {
|
|
|
5825
7520
|
throw completedExit(2, command);
|
|
5826
7521
|
}
|
|
5827
7522
|
const status = args[0] === undefined ? state.status : Number((BigInt(args[0]) % 256n + 256n) % 256n);
|
|
5828
|
-
throw completedExit(status, command);
|
|
7523
|
+
throw completedExit(status, command, 1, state.status);
|
|
5829
7524
|
}
|
|
5830
7525
|
if (command === "break" || command === "continue") {
|
|
5831
7526
|
const levels = args[0] === undefined ? 1 : Number(args[0]);
|
|
@@ -5844,14 +7539,23 @@ export class Runtime {
|
|
|
5844
7539
|
async words(words, state, io, declaration = false) {
|
|
5845
7540
|
return (await this.valueWords(words, state, io, declaration)).map(shellValueText);
|
|
5846
7541
|
}
|
|
5847
|
-
async valueWords(words, state, io, declaration = false) {
|
|
7542
|
+
async valueWords(words, state, io, declaration = false, indexedDeclaration = false) {
|
|
5848
7543
|
const fields = [];
|
|
5849
7544
|
for (const word of words) {
|
|
5850
|
-
const
|
|
5851
|
-
if (
|
|
7545
|
+
const assignment = indexedDeclaration ? getArrayAssignment(word) : undefined;
|
|
7546
|
+
if (assignment) {
|
|
7547
|
+
await this.arrayAssignment(assignment, state, io, undefined, "declaration");
|
|
7548
|
+
fields.push(assignment.name);
|
|
7549
|
+
}
|
|
7550
|
+
else {
|
|
7551
|
+
const values = await this.valueWord(word, state, io, !(declaration && this.assignment(word)), false, false, false, undefined, false, true);
|
|
7552
|
+
if (values.length > this.budget.limits.maxExpansionFields - fields.length)
|
|
7553
|
+
this.budget.fail("maxExpansionFields");
|
|
7554
|
+
for (const value of values)
|
|
7555
|
+
fields.push(value);
|
|
7556
|
+
}
|
|
7557
|
+
if (fields.length > this.budget.limits.maxExpansionFields)
|
|
5852
7558
|
this.budget.fail("maxExpansionFields");
|
|
5853
|
-
for (const value of values)
|
|
5854
|
-
fields.push(value);
|
|
5855
7559
|
}
|
|
5856
7560
|
return fields;
|
|
5857
7561
|
}
|
|
@@ -5917,7 +7621,7 @@ export class Runtime {
|
|
|
5917
7621
|
holding?.release();
|
|
5918
7622
|
}
|
|
5919
7623
|
}
|
|
5920
|
-
async valuePart(part, state, io, hereString = false) {
|
|
7624
|
+
async valuePart(part, state, io, hereString = false, split = false, hereDocument = false) {
|
|
5921
7625
|
if (part.kind === "variable" && part.transform) {
|
|
5922
7626
|
const selector = getArraySelector(part);
|
|
5923
7627
|
if (selector?.kind === "members" || part.name === "@" || part.name === "*") {
|
|
@@ -5944,7 +7648,7 @@ export class Runtime {
|
|
|
5944
7648
|
const existing = selector?.kind === "element" ? arrayStore(state)?.get(part.name)?.get(numericIndex(selector.index) ?? -1) ?? (numericIndex(selector.index) === 0 ? this.variable(state, part.name) : undefined)
|
|
5945
7649
|
: /^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(part.name) ? this.variable(state, part.name)
|
|
5946
7650
|
: /^[1-9][0-9]*$/u.test(part.name) ? state.positional[Number(part.name) - 1] : "";
|
|
5947
|
-
const value = await this.valuePart(base, state, io, hereString);
|
|
7651
|
+
const value = await this.valuePart(base, state, io, hereString, split, hereDocument);
|
|
5948
7652
|
return existing === undefined ? "" : this.transformValue(value, part.transform, state, io);
|
|
5949
7653
|
}
|
|
5950
7654
|
if (part.kind === "variable" && part.prefixNames) {
|
|
@@ -5973,24 +7677,24 @@ export class Runtime {
|
|
|
5973
7677
|
const token = index === undefined ? undefined : binding.values.get(index)?.text;
|
|
5974
7678
|
this.requireParameter(token?.value, part.kind === "variable" ? part.name : "", state, io);
|
|
5975
7679
|
if (part.kind === "variable" && part.length)
|
|
5976
|
-
return this.
|
|
5977
|
-
return token?.
|
|
7680
|
+
return this.valueLength(token?.shellValue ?? "", state, io);
|
|
7681
|
+
return token?.shellValue ? shellValueFromBytes(shellValueBytes(token.shellValue, io[valueScope]), io[valueScope]) : token?.value ?? "";
|
|
5978
7682
|
}
|
|
5979
7683
|
finally {
|
|
5980
7684
|
holding?.release();
|
|
5981
7685
|
}
|
|
5982
7686
|
}
|
|
5983
|
-
const index = selector?.kind === "element" ? numericIndex(selector.index) : binding?.associative ? binding.keys.get("30")?.index : 0;
|
|
5984
|
-
const token = index === undefined || selector
|
|
7687
|
+
const index = selector?.kind === "element" ? numericIndex(selector.index, 4294967295) : binding?.associative ? binding.keys.get("30")?.index : 0;
|
|
7688
|
+
const token = index === undefined || selector && selector.kind !== "element" ? undefined : binding?.values.get(index)?.text;
|
|
5985
7689
|
token?.retain();
|
|
5986
7690
|
try {
|
|
5987
7691
|
if (token && part.kind === "variable" && part.operator && ["#", "##", "%", "%%"].includes(part.operator))
|
|
5988
|
-
return this.parameterPattern(part, token.
|
|
5989
|
-
if (token?.
|
|
5990
|
-
return shellValueFromBytes(shellValueBytes(token.
|
|
5991
|
-
const value = await this.partValue(part, state, io, hereString);
|
|
7692
|
+
return this.parameterPattern(part, token.shellValue, state, io, hereString);
|
|
7693
|
+
if (token?.shellValue && part.kind === "variable" && !part.length && !part.operator && !part.substring)
|
|
7694
|
+
return shellValueFromBytes(shellValueBytes(token.shellValue, io[valueScope]), io[valueScope]);
|
|
7695
|
+
const value = await this.partValue(part, state, io, hereString, split, hereDocument);
|
|
5992
7696
|
if (binding)
|
|
5993
|
-
await textToken(requireArrays(state).owner,
|
|
7697
|
+
await textToken(requireArrays(state).owner, value, this.signal);
|
|
5994
7698
|
return value;
|
|
5995
7699
|
}
|
|
5996
7700
|
finally {
|
|
@@ -6018,7 +7722,7 @@ export class Runtime {
|
|
|
6018
7722
|
allocation.close();
|
|
6019
7723
|
}
|
|
6020
7724
|
}
|
|
6021
|
-
async partValue(part, state, io, hereString) {
|
|
7725
|
+
async partValue(part, state, io, hereString, split, hereDocument) {
|
|
6022
7726
|
this.signal.throwIfAborted();
|
|
6023
7727
|
if (part.kind === "compound-substitution-eof") {
|
|
6024
7728
|
await writeDiagnostic(io.stderr, `${io.scriptName ?? "shell"}: command substitution: line ${io.diagnosticLine ?? part.line}: syntax error: unexpected end of file\n`);
|
|
@@ -6052,12 +7756,16 @@ export class Runtime {
|
|
|
6052
7756
|
if (part.kind === "substitution") {
|
|
6053
7757
|
if (state.depth >= this.budget.limits.maxSubstitutionDepth)
|
|
6054
7758
|
this.budget.fail("maxSubstitutionDepth");
|
|
7759
|
+
this.signal.throwIfAborted();
|
|
7760
|
+
if (part.form === "dollar-parenthesis" && state.extensions?.checkpoints.length)
|
|
7761
|
+
await this.extensionCheckpoint("source-input-read", state, io);
|
|
6055
7762
|
const parameterDepth = io.parameterDepth ?? 0;
|
|
6056
7763
|
if (parameterDepth > 0 && state.depth + parameterDepth + 1 > 64)
|
|
6057
7764
|
throw new ShellSyntaxError("Syntax nesting exceeds 64", 0);
|
|
6058
7765
|
const capture = new Capture();
|
|
6059
7766
|
const child = await cloneState(state, this.signal);
|
|
6060
7767
|
child.isolated = true;
|
|
7768
|
+
child.extensions = forkExtensions(state.extensions, "substitution");
|
|
6061
7769
|
if (state.profile !== "sh")
|
|
6062
7770
|
child.errexit = false;
|
|
6063
7771
|
for (const [name, value] of state.redirectAssignments ?? []) {
|
|
@@ -6074,16 +7782,31 @@ export class Runtime {
|
|
|
6074
7782
|
const substitutionDiagnosticLines = new Map();
|
|
6075
7783
|
for (const [command, line] of part.script.printedLines ?? [])
|
|
6076
7784
|
substitutionDiagnosticLines.set(command, part.sourceLine === undefined ? warningLine + (command.line ?? part.line) - part.line : warningLine + line - 1);
|
|
6077
|
-
const
|
|
6078
|
-
const
|
|
6079
|
-
|
|
7785
|
+
const reprintedLines = part.sourceLine === undefined ? undefined : functionReprintedLines(part.script);
|
|
7786
|
+
const functionCommandLines = reprintedLines && new Map([...reprintedLines].map(([command, line]) => [command, warningLine + line - 1]));
|
|
7787
|
+
const references = new PipeDescriptorFrame(io[invocationScope]);
|
|
7788
|
+
let captured;
|
|
7789
|
+
const capturedOutput = new Promise(resolve => { captured = resolve; });
|
|
7790
|
+
const lifetime = new DescriptorLifetime(captured);
|
|
7791
|
+
const captureIO = isolateIO({
|
|
7792
|
+
...io, substitutionDiagnosticLines,
|
|
7793
|
+
diagnosticOffset: (io.diagnosticLine ?? part.line) - (part.sourceLine ?? part.line),
|
|
7794
|
+
functionCommandLines, diagnosticCommandLines: undefined, stdout: this.budget.sink(capture, this.signal),
|
|
7795
|
+
}, references);
|
|
7796
|
+
captureIO.descriptors = new Map(captureIO.descriptors).set(1, { output: captureIO.stdout, lifetime });
|
|
6080
7797
|
try {
|
|
6081
7798
|
state.substitutionStatus = fileShortcut ? await this.runCommandIsolated(command, child, captureIO, true) : await this.run(part.script, child, captureIO);
|
|
6082
7799
|
}
|
|
6083
7800
|
finally {
|
|
6084
|
-
|
|
6085
|
-
|
|
7801
|
+
try {
|
|
7802
|
+
await references.close();
|
|
7803
|
+
await lifetime.release();
|
|
7804
|
+
}
|
|
7805
|
+
finally {
|
|
7806
|
+
stateMonitor(child)?.closeValues();
|
|
7807
|
+
}
|
|
6086
7808
|
}
|
|
7809
|
+
await capturedOutput;
|
|
6087
7810
|
state.status = state.substitutionStatus;
|
|
6088
7811
|
const bytes = capture.bytes();
|
|
6089
7812
|
if (bytes.includes(0))
|
|
@@ -6106,45 +7829,57 @@ export class Runtime {
|
|
|
6106
7829
|
return shellValueFromBytes(sanitized, io[valueScope]);
|
|
6107
7830
|
}
|
|
6108
7831
|
}
|
|
7832
|
+
let specialValue;
|
|
7833
|
+
if (part.specialParameter) {
|
|
7834
|
+
const hook = state.extensions?.specialParameters.get(part.specialParameter.name);
|
|
7835
|
+
if (!hook)
|
|
7836
|
+
throw new TypeError("Missing captured shell special parameter handler");
|
|
7837
|
+
specialValue = hook.lookup(this.extensionContext(state, io));
|
|
7838
|
+
}
|
|
6109
7839
|
const selector = getArraySelector(part);
|
|
6110
7840
|
if (selector) {
|
|
6111
7841
|
const store = requireArrays(state);
|
|
6112
7842
|
const binding = store.get(part.name);
|
|
6113
7843
|
if (selector.kind === "element") {
|
|
6114
|
-
const index = numericIndex(selector.index);
|
|
7844
|
+
const index = numericIndex(selector.index, 4294967295);
|
|
6115
7845
|
if (index === undefined)
|
|
6116
|
-
throw new ArrayFailure("index outside 0..
|
|
6117
|
-
const value = binding ? binding.
|
|
6118
|
-
|
|
6119
|
-
|
|
7846
|
+
throw new ArrayFailure("index outside 0..4294967295");
|
|
7847
|
+
const value = binding ? binding.getValue(index) : index === 0 && state.variables[part.name] !== undefined ? stateMonitor(state)?.values.get(part.name, state.variables[part.name]) ?? state.variables[part.name] : undefined;
|
|
7848
|
+
if (part.operator === "-" || part.operator === "+") {
|
|
7849
|
+
const alternate = part.operator === "+" ? value !== undefined : value === undefined;
|
|
7850
|
+
if (alternate)
|
|
7851
|
+
return concatShellValues(await this.valueWord(part.alternate, state, io, false, false, hereString, false, undefined, hereDocument), io[valueScope]);
|
|
7852
|
+
return part.operator === "+" ? "" : value ?? "";
|
|
7853
|
+
}
|
|
7854
|
+
this.requireParameter(value === undefined ? undefined : shellValueText(value), `${part.name}[${selector.index}]`, state, io, part.line);
|
|
7855
|
+
return part.length ? this.valueLength(value ?? "", state, io) : value ?? "";
|
|
6120
7856
|
}
|
|
6121
7857
|
if (part.length)
|
|
6122
7858
|
return String(binding?.values.size ?? (state.variables[part.name] === undefined ? 0 : 1));
|
|
6123
|
-
const values = await this.arrayMembers(part.name, state, io, part.keys);
|
|
6124
|
-
const
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
let value = part.name === "!" ? state.lastBackgroundJob
|
|
7859
|
+
const values = await this.arrayMembers(part.name, state, io, selector.kind === "keys" || part.keys === true);
|
|
7860
|
+
const space = selector.kind === "keys" && (hereDocument || (selector.separator === "@"
|
|
7861
|
+
? !part.quoted && !split || state.variables.IFS === ""
|
|
7862
|
+
: !part.quoted && split && state.variables.IFS === ""));
|
|
7863
|
+
return this.arrayJoin(store.owner, values, space ? " " : this.ifsSeparator(state, io));
|
|
7864
|
+
}
|
|
7865
|
+
let value = part.specialParameter ? specialValue === undefined ? undefined : shellValueText(specialValue)
|
|
6131
7866
|
: part.name === "?" ? String(state.status)
|
|
6132
7867
|
: part.name === "-" ? `${state.errexit ? "e" : ""}${state.nounset ? "u" : ""}${state.braceexpand !== false ? "B" : ""}`
|
|
6133
7868
|
: part.name === "#" ? String(state.positional.length)
|
|
6134
7869
|
: part.name === "@" || part.name === "*" ? state.positional.join(hereString && (part.name === "@" || !part.quoted) ? " " : Array.from(state.variables.IFS ?? " ")[0] ?? "")
|
|
6135
7870
|
: /^0+$/u.test(part.name) ? state.arg0 ?? "virtual-bash"
|
|
6136
7871
|
: /^\d+$/u.test(part.name) ? state.positional[Number(part.name) - 1]
|
|
6137
|
-
: this.variable(state, part.name);
|
|
6138
|
-
let retained = value;
|
|
6139
|
-
if (
|
|
6140
|
-
if (/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(part.name)
|
|
6141
|
-
retained = stateMonitor(state)?.values.get(part.name, value) ?? value;
|
|
7872
|
+
: part.name === "LINENO" && state.extensions ? String(io.diagnosticLine ?? part.line ?? 1) : this.variable(state, part.name);
|
|
7873
|
+
let retained = part.specialParameter ? specialValue : value;
|
|
7874
|
+
if (value !== undefined) {
|
|
7875
|
+
if (/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(part.name))
|
|
7876
|
+
retained = arrayStore(state)?.get(part.name)?.getValue(0) ?? stateMonitor(state)?.values.get(part.name, value) ?? value;
|
|
6142
7877
|
else if (/^0+$/u.test(part.name))
|
|
6143
7878
|
retained = stateMonitor(state)?.positionals.get(zeroPositionKey, value) ?? value;
|
|
6144
|
-
else if (/^[
|
|
7879
|
+
else if (/^[0-9]+$/u.test(part.name))
|
|
6145
7880
|
retained = stateMonitor(state)?.positionals.get(String(Number(part.name) - 1), value) ?? value;
|
|
6146
7881
|
else if (part.name === "@" || part.name === "*") {
|
|
6147
|
-
const separator = hereString && (part.name === "@" || !part.quoted) ? " " :
|
|
7882
|
+
const separator = hereString && (part.name === "@" || !part.quoted) ? " " : this.ifsSeparator(state, io);
|
|
6148
7883
|
const values = this.positionalValues(state);
|
|
6149
7884
|
retained = concatShellValues(values.flatMap((entry, index) => index ? [separator, entry] : [entry]), io[valueScope]);
|
|
6150
7885
|
}
|
|
@@ -6166,13 +7901,13 @@ export class Runtime {
|
|
|
6166
7901
|
if (operator === "=" && arrayStore(state)?.get(part.name)) {
|
|
6167
7902
|
alternate = "";
|
|
6168
7903
|
await this.arrayZero(state, part.name, async () => {
|
|
6169
|
-
|
|
6170
|
-
return
|
|
7904
|
+
retained = await this.arrayJoin(requireArrays(state).owner, await this.valueWord(part.alternate, state, operandIO, false, false, hereString, false, undefined, hereDocument), "");
|
|
7905
|
+
return retained;
|
|
6171
7906
|
});
|
|
6172
|
-
value =
|
|
6173
|
-
return part.length ? this.
|
|
7907
|
+
value = shellValueText(retained);
|
|
7908
|
+
return part.length ? this.valueLength(retained, state, io) : retained;
|
|
6174
7909
|
}
|
|
6175
|
-
retained = concatShellValues(await this.valueWord(part.alternate, state, operandIO, false, false, hereString), io[valueScope]);
|
|
7910
|
+
retained = concatShellValues(await this.valueWord(part.alternate, state, operandIO, false, false, hereString, false, undefined, hereDocument), io[valueScope]);
|
|
6176
7911
|
alternate = shellValueText(retained);
|
|
6177
7912
|
if (operator === "?")
|
|
6178
7913
|
throw new ParameterExpansionFailure(`${part.name}: ${alternate || (part.operator.startsWith(":") ? "parameter null or not set" : "parameter not set")}`, io.diagnosticLine ?? part.line);
|
|
@@ -6190,15 +7925,39 @@ export class Runtime {
|
|
|
6190
7925
|
}
|
|
6191
7926
|
else
|
|
6192
7927
|
this.requireParameter(value, part.name, state, io, part.line);
|
|
6193
|
-
return part.length ? this.
|
|
7928
|
+
return part.length ? this.valueLength(retained ?? "", state, io) : retained ?? "";
|
|
6194
7929
|
}
|
|
6195
|
-
async
|
|
7930
|
+
async valueLength(value, state, io) {
|
|
6196
7931
|
const limit = this.budget.limits.maxExpansionBytes;
|
|
6197
7932
|
const work = { remaining: Math.min(Number.MAX_SAFE_INTEGER, limit * 4 + 1024), signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
|
|
6198
|
-
|
|
6199
|
-
|
|
7933
|
+
if (typeof value === "string") {
|
|
7934
|
+
const scanned = await scanString(value, work);
|
|
7935
|
+
if (scanned.bytes > limit)
|
|
7936
|
+
this.budget.fail("maxExpansionBytes");
|
|
7937
|
+
return String(byteLocale(state.variables) ? scanned.bytes : scanned.count);
|
|
7938
|
+
}
|
|
7939
|
+
if (shellValueByteLength(value) > limit)
|
|
6200
7940
|
this.budget.fail("maxExpansionBytes");
|
|
6201
|
-
|
|
7941
|
+
const bytes = shellValueBytes(value, io[valueScope]);
|
|
7942
|
+
if (byteLocale(state.variables))
|
|
7943
|
+
return String(bytes.length);
|
|
7944
|
+
let length = 0;
|
|
7945
|
+
for (let offset = 0; offset < bytes.length; offset += shellCharacterWidth(bytes, offset, false)) {
|
|
7946
|
+
length++;
|
|
7947
|
+
const pending = stringCheckpoint(work);
|
|
7948
|
+
if (pending)
|
|
7949
|
+
await pending;
|
|
7950
|
+
}
|
|
7951
|
+
return String(length);
|
|
7952
|
+
}
|
|
7953
|
+
ifsSeparator(state, io) {
|
|
7954
|
+
const text = state.variables.IFS ?? " ";
|
|
7955
|
+
const value = stateMonitor(state)?.values.get("IFS", text) ?? text;
|
|
7956
|
+
const byteCount = byteLocale(state.variables);
|
|
7957
|
+
if (typeof value === "string" && (!byteCount || !value || value.charCodeAt(0) < 128))
|
|
7958
|
+
return value ? String.fromCodePoint(value.codePointAt(0)) : "";
|
|
7959
|
+
const bytes = shellValueBytes(value, io[valueScope]);
|
|
7960
|
+
return bytes.length ? shellValueFromBytes(bytes.subarray(0, shellCharacterWidth(bytes, 0, byteCount)), io[valueScope]) : "";
|
|
6202
7961
|
}
|
|
6203
7962
|
async substring(part, value, state, io) {
|
|
6204
7963
|
const owner = arrayStore(state)?.get(part.name) ? requireArrays(state).owner : undefined;
|
|
@@ -6422,12 +8181,12 @@ export class Runtime {
|
|
|
6422
8181
|
async word(word, state, io, split = true, pattern = false, hereString = false, conditionalPattern = false, regexAppend) {
|
|
6423
8182
|
return (await this.valueWord(word, state, io, split, pattern, hereString, conditionalPattern, regexAppend)).map(shellValueText);
|
|
6424
8183
|
}
|
|
6425
|
-
async valueWord(word, state, io, split = true, pattern = false, hereString = false, conditionalPattern = false, regexAppend, braces = split && !pattern && !hereString) {
|
|
8184
|
+
async valueWord(word, state, io, split = true, pattern = false, hereString = false, conditionalPattern = false, regexAppend, hereDocument = false, braces = split && !pattern && !hereString && !hereDocument) {
|
|
6426
8185
|
if (braces && state.braceexpand !== false && word.parts.some(part => part.kind === "text" && !part.quoted && part.value.includes("{"))) {
|
|
6427
8186
|
const fields = [];
|
|
6428
8187
|
let bytes = 0;
|
|
6429
8188
|
for await (const expanded of expandBraces(word, this.budget, this.signal)) {
|
|
6430
|
-
const values = await this.valueWord(expanded, state, io, split, pattern, hereString, conditionalPattern, regexAppend, false);
|
|
8189
|
+
const values = await this.valueWord(expanded, state, io, split, pattern, hereString, conditionalPattern, regexAppend, hereDocument, false);
|
|
6431
8190
|
if (values.length > this.budget.limits.maxExpansionFields - fields.length)
|
|
6432
8191
|
this.budget.fail("maxExpansionFields");
|
|
6433
8192
|
for (const value of values) {
|
|
@@ -6515,6 +8274,43 @@ export class Runtime {
|
|
|
6515
8274
|
if (shellValueByteLength(value) === 0)
|
|
6516
8275
|
return;
|
|
6517
8276
|
const separators = state.variables.IFS ?? " \t\n";
|
|
8277
|
+
const retainedSeparators = stateMonitor(state)?.values.get("IFS", separators) ?? separators;
|
|
8278
|
+
let byteSeparators = false;
|
|
8279
|
+
if (byteLocale(state.variables) && typeof retainedSeparators === "string") {
|
|
8280
|
+
const work = this.splitWork ??= { scanned: 0 };
|
|
8281
|
+
for (let index = 0; index < retainedSeparators.length; index++) {
|
|
8282
|
+
if (retainedSeparators.charCodeAt(index) > 127) {
|
|
8283
|
+
byteSeparators = true;
|
|
8284
|
+
break;
|
|
8285
|
+
}
|
|
8286
|
+
if (++work.scanned >= 4096) {
|
|
8287
|
+
work.scanned = 0;
|
|
8288
|
+
await yieldTurn(this.signal);
|
|
8289
|
+
}
|
|
8290
|
+
}
|
|
8291
|
+
}
|
|
8292
|
+
if (typeof value !== "string" || typeof retainedSeparators !== "string" || byteSeparators) {
|
|
8293
|
+
let boundary = false;
|
|
8294
|
+
for await (const piece of this.splitRawValue(value, retainedSeparators, io, byteLocale(state.variables))) {
|
|
8295
|
+
if (piece.separator) {
|
|
8296
|
+
if (!piece.whitespace) {
|
|
8297
|
+
fields.at(-1).present = true;
|
|
8298
|
+
addField();
|
|
8299
|
+
}
|
|
8300
|
+
else if (fields.at(-1).present)
|
|
8301
|
+
boundary = true;
|
|
8302
|
+
}
|
|
8303
|
+
else {
|
|
8304
|
+
if (boundary)
|
|
8305
|
+
addField();
|
|
8306
|
+
boundary = false;
|
|
8307
|
+
append(piece.value, true, true);
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
8310
|
+
if (boundary)
|
|
8311
|
+
addField();
|
|
8312
|
+
return;
|
|
8313
|
+
}
|
|
6518
8314
|
const separatorScope = this.budget.values.scope();
|
|
6519
8315
|
try {
|
|
6520
8316
|
separatorScope.reserve(64, 0);
|
|
@@ -6567,8 +8363,16 @@ export class Runtime {
|
|
|
6567
8363
|
const { part, splitText, io: partIO } = parts[index];
|
|
6568
8364
|
quoteGroup = prefixNameQuoteGroups.get(part);
|
|
6569
8365
|
const quotedPresence = part.quoted && !((arrayOwned || prefixOwned) && isQuoteMarker(part));
|
|
8366
|
+
const selector = getArraySelector(part);
|
|
6570
8367
|
if (part.kind === "variable" && ["-", "+", ":-", ":+"].includes(part.operator ?? "") && /^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(part.name)) {
|
|
6571
|
-
|
|
8368
|
+
let value = this.variable(state, part.name);
|
|
8369
|
+
if (selector?.kind === "element") {
|
|
8370
|
+
const index = numericIndex(selector.index, 4294967295);
|
|
8371
|
+
if (index === undefined)
|
|
8372
|
+
throw new ArrayFailure("index outside 0..4294967295");
|
|
8373
|
+
const binding = arrayStore(state)?.get(part.name);
|
|
8374
|
+
value = binding ? binding.getValue(index) : index === 0 ? value : undefined;
|
|
8375
|
+
}
|
|
6572
8376
|
const missing = value === undefined || (part.operator.startsWith(":") && value === "");
|
|
6573
8377
|
if (part.operator.endsWith("+") ? !missing : missing) {
|
|
6574
8378
|
const operandIO = this.parameterOperandIO(part.alternate, state, partIO);
|
|
@@ -6580,7 +8384,6 @@ export class Runtime {
|
|
|
6580
8384
|
continue;
|
|
6581
8385
|
}
|
|
6582
8386
|
}
|
|
6583
|
-
const selector = getArraySelector(part);
|
|
6584
8387
|
if (part.kind === "variable" && part.prefixNames === "@" && split && (part.quoted || state.variables.IFS === "")) {
|
|
6585
8388
|
let position = 0;
|
|
6586
8389
|
for await (const name of this.prefixNames(part.name, state)) {
|
|
@@ -6594,8 +8397,8 @@ export class Runtime {
|
|
|
6594
8397
|
emptyNameGroups.add(quoteGroup);
|
|
6595
8398
|
}
|
|
6596
8399
|
}
|
|
6597
|
-
else if (part.kind === "variable" && split && (selector
|
|
6598
|
-
const members = selector
|
|
8400
|
+
else if (part.kind === "variable" && split && (selector && selector.kind !== "element" && !part.length && (selector.kind === "members" ? !part.quoted || selector.separator === "@" : selector.separator === "@" && (part.quoted || state.variables.IFS === "")) || part.transform && part.name === "@")) {
|
|
8401
|
+
const members = selector && selector.kind !== "element" ? await this.arrayMembers(part.name, state, io, selector.kind === "keys" || part.keys === true) : this.positionalValues(state);
|
|
6599
8402
|
for (let position = 0; position < members.length; position++) {
|
|
6600
8403
|
if (position > 0)
|
|
6601
8404
|
addField();
|
|
@@ -6628,7 +8431,7 @@ export class Runtime {
|
|
|
6628
8431
|
fields[0].present = false;
|
|
6629
8432
|
}
|
|
6630
8433
|
else {
|
|
6631
|
-
const value = part.kind === "text" ? part.byteValue ?? part.value : await this.valuePart(part, state, partIO, hereString);
|
|
8434
|
+
const value = part.kind === "text" ? part.byteValue ?? part.value : await this.valuePart(part, state, partIO, hereString, split, hereDocument);
|
|
6632
8435
|
if (part.quoted || !split || state.variables.IFS === "")
|
|
6633
8436
|
append(value, !part.quoted, quotedPresence || !split || shellValueByteLength(value) > 0);
|
|
6634
8437
|
else
|
|
@@ -6749,6 +8552,62 @@ export class Runtime {
|
|
|
6749
8552
|
if (start < bytes.length)
|
|
6750
8553
|
yield start === 0 ? value : shellValueFromBytes(bytes.subarray(start), io[valueScope]);
|
|
6751
8554
|
}
|
|
8555
|
+
async *splitRawValue(value, separators, io, byteCount) {
|
|
8556
|
+
if (typeof value === "string" && typeof separators === "string" && !byteCount) {
|
|
8557
|
+
for (const character of value)
|
|
8558
|
+
yield { value: character, separator: separators.includes(character), whitespace: " \t\n".includes(character) };
|
|
8559
|
+
return;
|
|
8560
|
+
}
|
|
8561
|
+
const work = this.splitWork ??= { scanned: 0 };
|
|
8562
|
+
const bytes = shellValueBytes(value, io[valueScope]);
|
|
8563
|
+
const delimiters = shellValueBytes(separators, io[valueScope]);
|
|
8564
|
+
const key = (input, offset, length) => {
|
|
8565
|
+
let result = 1;
|
|
8566
|
+
for (let index = offset; index < offset + length; index++)
|
|
8567
|
+
result = result * 257 + input[index];
|
|
8568
|
+
return result;
|
|
8569
|
+
};
|
|
8570
|
+
const keys = new Set();
|
|
8571
|
+
for (let offset = 0; offset < delimiters.length;) {
|
|
8572
|
+
const length = shellCharacterWidth(delimiters, offset, byteCount);
|
|
8573
|
+
const token = key(delimiters, offset, length);
|
|
8574
|
+
if (!keys.has(token)) {
|
|
8575
|
+
io[valueScope]?.reserve(32, 1);
|
|
8576
|
+
keys.add(token);
|
|
8577
|
+
}
|
|
8578
|
+
for (let index = offset; index < offset + length; index++) {
|
|
8579
|
+
const byteKey = key(delimiters, index, 1);
|
|
8580
|
+
if (!keys.has(byteKey)) {
|
|
8581
|
+
io[valueScope]?.reserve(32, 1);
|
|
8582
|
+
keys.add(byteKey);
|
|
8583
|
+
}
|
|
8584
|
+
}
|
|
8585
|
+
offset += length;
|
|
8586
|
+
work.scanned += length;
|
|
8587
|
+
if (work.scanned >= 4096) {
|
|
8588
|
+
work.scanned = 0;
|
|
8589
|
+
await yieldTurn(this.signal);
|
|
8590
|
+
}
|
|
8591
|
+
}
|
|
8592
|
+
let start = 0;
|
|
8593
|
+
for (let offset = 0; offset < bytes.length;) {
|
|
8594
|
+
const length = shellCharacterWidth(bytes, offset, byteCount);
|
|
8595
|
+
if (keys.has(key(bytes, offset, length))) {
|
|
8596
|
+
if (start < offset)
|
|
8597
|
+
yield { value: shellValueFromBytes(bytes.subarray(start, offset), io[valueScope]), separator: false, whitespace: false };
|
|
8598
|
+
yield { value: "", separator: true, whitespace: length === 1 && (bytes[offset] === 32 || bytes[offset] === 9 || bytes[offset] === 10) };
|
|
8599
|
+
start = offset + length;
|
|
8600
|
+
}
|
|
8601
|
+
offset += length;
|
|
8602
|
+
work.scanned += length;
|
|
8603
|
+
if (work.scanned >= 4096) {
|
|
8604
|
+
work.scanned = 0;
|
|
8605
|
+
await yieldTurn(this.signal);
|
|
8606
|
+
}
|
|
8607
|
+
}
|
|
8608
|
+
if (start < bytes.length)
|
|
8609
|
+
yield { value: start === 0 ? value : shellValueFromBytes(bytes.subarray(start), io[valueScope]), separator: false, whitespace: false };
|
|
8610
|
+
}
|
|
6752
8611
|
positionalValues(state) {
|
|
6753
8612
|
return state.positional.map((text, index) => stateMonitor(state)?.positionals.get(String(index), text) ?? text);
|
|
6754
8613
|
}
|
|
@@ -6782,7 +8641,8 @@ export class Runtime {
|
|
|
6782
8641
|
const binding = store.get(name);
|
|
6783
8642
|
store.owner.reserve({ metadata: 64, work: 3 });
|
|
6784
8643
|
if (!binding) {
|
|
6785
|
-
const
|
|
8644
|
+
const text = state.variables[name];
|
|
8645
|
+
const value = text === undefined ? undefined : keys ? "0" : stateMonitor(state)?.values.get(name, text) ?? text;
|
|
6786
8646
|
if (value === undefined)
|
|
6787
8647
|
return [];
|
|
6788
8648
|
store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
@@ -6795,10 +8655,8 @@ export class Runtime {
|
|
|
6795
8655
|
const values = [];
|
|
6796
8656
|
for (const index of indices) {
|
|
6797
8657
|
store.owner.reserve({ metadata: 32, allocatedSlots: 1, work: 4 });
|
|
6798
|
-
const
|
|
6799
|
-
|
|
6800
|
-
if (typeof value === "string")
|
|
6801
|
-
await textToken(store.owner, value, this.signal);
|
|
8658
|
+
const value = keys ? binding.associative ? binding.keys.get(binding.keyByIndex.get(index)).text.shellValue : String(index) : binding.getValue(index);
|
|
8659
|
+
await textToken(store.owner, value, this.signal);
|
|
6802
8660
|
values.push(value);
|
|
6803
8661
|
await store.owner.ledger.checkpoint(this.signal);
|
|
6804
8662
|
}
|