codexgpt 1.0.0
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/AGENTS.example.md +20 -0
- package/CHANGELOG.md +342 -0
- package/CHATGPT_PROMPT.md +12 -0
- package/CLOUDFLARED_VERIFIED_INSTALL.md +94 -0
- package/CODEX_PROMPT.md +12 -0
- package/CONTRIBUTING.md +62 -0
- package/DOMAIN_SETUP.md +275 -0
- package/FAQ.md +366 -0
- package/FAQ_ZH.md +329 -0
- package/LICENSE +21 -0
- package/PUBLIC_LAUNCH_CHECKLIST.md +111 -0
- package/README.md +490 -0
- package/README_ZH.md +747 -0
- package/SECURITY.md +295 -0
- package/config.example.env +103 -0
- package/dist/access/fullAccessLease.js +148 -0
- package/dist/access/fullAccessLease.js.map +1 -0
- package/dist/access/protectedRoots.js +122 -0
- package/dist/access/protectedRoots.js.map +1 -0
- package/dist/access/rootAdmission.js +347 -0
- package/dist/access/rootAdmission.js.map +1 -0
- package/dist/analysis/cache.js +27 -0
- package/dist/analysis/cache.js.map +1 -0
- package/dist/analysis/classify.js +102 -0
- package/dist/analysis/classify.js.map +1 -0
- package/dist/analysis/extract.js +139 -0
- package/dist/analysis/extract.js.map +1 -0
- package/dist/analysis/graph.js +22 -0
- package/dist/analysis/graph.js.map +1 -0
- package/dist/analysis/impact.js +167 -0
- package/dist/analysis/impact.js.map +1 -0
- package/dist/analysis/index.js +215 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/inventory.js +51 -0
- package/dist/analysis/inventory.js.map +1 -0
- package/dist/analysis/providers.js +24 -0
- package/dist/analysis/providers.js.map +1 -0
- package/dist/analysis/rank.js +27 -0
- package/dist/analysis/rank.js.map +1 -0
- package/dist/analysis/types.js +8 -0
- package/dist/analysis/types.js.map +1 -0
- package/dist/audit/canonicalJson.js +79 -0
- package/dist/audit/canonicalJson.js.map +1 -0
- package/dist/audit/diagnostics.js +128 -0
- package/dist/audit/diagnostics.js.map +1 -0
- package/dist/audit/index.js +12 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/lifecycleV3.js +53 -0
- package/dist/audit/lifecycleV3.js.map +1 -0
- package/dist/audit/lifecycleV4.js +70 -0
- package/dist/audit/lifecycleV4.js.map +1 -0
- package/dist/audit/lock.js +195 -0
- package/dist/audit/lock.js.map +1 -0
- package/dist/audit/queryTool.js +120 -0
- package/dist/audit/queryTool.js.map +1 -0
- package/dist/audit/runtime.js +48 -0
- package/dist/audit/runtime.js.map +1 -0
- package/dist/audit/schemas.js +640 -0
- package/dist/audit/schemas.js.map +1 -0
- package/dist/audit/store.js +1309 -0
- package/dist/audit/store.js.map +1 -0
- package/dist/audit/transactionParticipant.js +45 -0
- package/dist/audit/transactionParticipant.js.map +1 -0
- package/dist/audit/types.js +11 -0
- package/dist/audit/types.js.map +1 -0
- package/dist/auth/audit.js +44 -0
- package/dist/auth/audit.js.map +1 -0
- package/dist/auth/authorizationStore.js +333 -0
- package/dist/auth/authorizationStore.js.map +1 -0
- package/dist/auth/challenges.js +25 -0
- package/dist/auth/challenges.js.map +1 -0
- package/dist/auth/clientStore.js +469 -0
- package/dist/auth/clientStore.js.map +1 -0
- package/dist/auth/cloudflareConfig.js +112 -0
- package/dist/auth/cloudflareConfig.js.map +1 -0
- package/dist/auth/configuration.js +167 -0
- package/dist/auth/configuration.js.map +1 -0
- package/dist/auth/credentialStore.js +65 -0
- package/dist/auth/credentialStore.js.map +1 -0
- package/dist/auth/deploymentLock.js +220 -0
- package/dist/auth/deploymentLock.js.map +1 -0
- package/dist/auth/deploymentRegistry.js +149 -0
- package/dist/auth/deploymentRegistry.js.map +1 -0
- package/dist/auth/errors.js +26 -0
- package/dist/auth/errors.js.map +1 -0
- package/dist/auth/grantStore.js +307 -0
- package/dist/auth/grantStore.js.map +1 -0
- package/dist/auth/index.js +39 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/keyManager.js +148 -0
- package/dist/auth/keyManager.js.map +1 -0
- package/dist/auth/localAdminSession.js +173 -0
- package/dist/auth/localAdminSession.js.map +1 -0
- package/dist/auth/metadata.js +69 -0
- package/dist/auth/metadata.js.map +1 -0
- package/dist/auth/oauthProvider.js +589 -0
- package/dist/auth/oauthProvider.js.map +1 -0
- package/dist/auth/ownerApproval.js +51 -0
- package/dist/auth/ownerApproval.js.map +1 -0
- package/dist/auth/policyIdentity.js +189 -0
- package/dist/auth/policyIdentity.js.map +1 -0
- package/dist/auth/rateLimits.js +66 -0
- package/dist/auth/rateLimits.js.map +1 -0
- package/dist/auth/recovery.js +275 -0
- package/dist/auth/recovery.js.map +1 -0
- package/dist/auth/requestContext.js +34 -0
- package/dist/auth/requestContext.js.map +1 -0
- package/dist/auth/resourceMiddleware.js +147 -0
- package/dist/auth/resourceMiddleware.js.map +1 -0
- package/dist/auth/runtimeStatus.js +51 -0
- package/dist/auth/runtimeStatus.js.map +1 -0
- package/dist/auth/schemas.js +84 -0
- package/dist/auth/schemas.js.map +1 -0
- package/dist/auth/stateStore.js +468 -0
- package/dist/auth/stateStore.js.map +1 -0
- package/dist/auth/tokenService.js +631 -0
- package/dist/auth/tokenService.js.map +1 -0
- package/dist/auth/toolSecurity.js +108 -0
- package/dist/auth/toolSecurity.js.map +1 -0
- package/dist/auth/types.js +6 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/auth/windowsDpapi.js +193 -0
- package/dist/auth/windowsDpapi.js.map +1 -0
- package/dist/bashOps.js +292 -0
- package/dist/bashOps.js.map +1 -0
- package/dist/capabilitiesOps.js +412 -0
- package/dist/capabilitiesOps.js.map +1 -0
- package/dist/changesets/crypto.js +92 -0
- package/dist/changesets/crypto.js.map +1 -0
- package/dist/changesets/index.js +9 -0
- package/dist/changesets/index.js.map +1 -0
- package/dist/changesets/moveStore.js +237 -0
- package/dist/changesets/moveStore.js.map +1 -0
- package/dist/changesets/moveUndo.js +386 -0
- package/dist/changesets/moveUndo.js.map +1 -0
- package/dist/changesets/schemas.js +234 -0
- package/dist/changesets/schemas.js.map +1 -0
- package/dist/changesets/store.js +573 -0
- package/dist/changesets/store.js.map +1 -0
- package/dist/changesets/types.js +9 -0
- package/dist/changesets/types.js.map +1 -0
- package/dist/changesets/undo.js +525 -0
- package/dist/changesets/undo.js.map +1 -0
- package/dist/changesets/unifiedUndo.js +31 -0
- package/dist/changesets/unifiedUndo.js.map +1 -0
- package/dist/cliEnvironment.js +85 -0
- package/dist/cliEnvironment.js.map +1 -0
- package/dist/codexSessions.js +733 -0
- package/dist/codexSessions.js.map +1 -0
- package/dist/codexgptSupertool.js +216 -0
- package/dist/codexgptSupertool.js.map +1 -0
- package/dist/codexproSupertool.js +204 -0
- package/dist/codexproSupertool.js.map +1 -0
- package/dist/config.js +733 -0
- package/dist/config.js.map +1 -0
- package/dist/control/localApprovalClient.js +287 -0
- package/dist/control/localApprovalClient.js.map +1 -0
- package/dist/control/localApprovalServer.js +466 -0
- package/dist/control/localApprovalServer.js.map +1 -0
- package/dist/control/runtime.js +178 -0
- package/dist/control/runtime.js.map +1 -0
- package/dist/control/schemas.js +184 -0
- package/dist/control/schemas.js.map +1 -0
- package/dist/control/windowsLocalControl.js +348 -0
- package/dist/control/windowsLocalControl.js.map +1 -0
- package/dist/fsOps.js +541 -0
- package/dist/fsOps.js.map +1 -0
- package/dist/git/admission.js +26 -0
- package/dist/git/admission.js.map +1 -0
- package/dist/git/branchService.js +51 -0
- package/dist/git/branchService.js.map +1 -0
- package/dist/git/capabilities.js +143 -0
- package/dist/git/capabilities.js.map +1 -0
- package/dist/git/commitService.js +344 -0
- package/dist/git/commitService.js.map +1 -0
- package/dist/git/durableState.js +151 -0
- package/dist/git/durableState.js.map +1 -0
- package/dist/git/execution.js +451 -0
- package/dist/git/execution.js.map +1 -0
- package/dist/git/fileTransaction.js +50 -0
- package/dist/git/fileTransaction.js.map +1 -0
- package/dist/git/indexService.js +421 -0
- package/dist/git/indexService.js.map +1 -0
- package/dist/git/integrations.js +653 -0
- package/dist/git/integrations.js.map +1 -0
- package/dist/git/locks.js +287 -0
- package/dist/git/locks.js.map +1 -0
- package/dist/git/mutationContext.js +92 -0
- package/dist/git/mutationContext.js.map +1 -0
- package/dist/git/mutationJournal.js +91 -0
- package/dist/git/mutationJournal.js.map +1 -0
- package/dist/git/mutationService.js +352 -0
- package/dist/git/mutationService.js.map +1 -0
- package/dist/git/normalization.js +57 -0
- package/dist/git/normalization.js.map +1 -0
- package/dist/git/objectQuarantine.js +243 -0
- package/dist/git/objectQuarantine.js.map +1 -0
- package/dist/git/opaqueRecordStore.js +187 -0
- package/dist/git/opaqueRecordStore.js.map +1 -0
- package/dist/git/operationStore.js +287 -0
- package/dist/git/operationStore.js.map +1 -0
- package/dist/git/parsers.js +311 -0
- package/dist/git/parsers.js.map +1 -0
- package/dist/git/privateIndex.js +79 -0
- package/dist/git/privateIndex.js.map +1 -0
- package/dist/git/productionBootstrap.js +39 -0
- package/dist/git/productionBootstrap.js.map +1 -0
- package/dist/git/readService.js +846 -0
- package/dist/git/readService.js.map +1 -0
- package/dist/git/recovery.js +417 -0
- package/dist/git/recovery.js.map +1 -0
- package/dist/git/repositoryIdentity.js +611 -0
- package/dist/git/repositoryIdentity.js.map +1 -0
- package/dist/git/repositoryStore.js +237 -0
- package/dist/git/repositoryStore.js.map +1 -0
- package/dist/git/resources.js +113 -0
- package/dist/git/resources.js.map +1 -0
- package/dist/git/restoreService.js +178 -0
- package/dist/git/restoreService.js.map +1 -0
- package/dist/git/reviewToken.js +86 -0
- package/dist/git/reviewToken.js.map +1 -0
- package/dist/git/stashService.js +566 -0
- package/dist/git/stashService.js.map +1 -0
- package/dist/git/stateToken.js +139 -0
- package/dist/git/stateToken.js.map +1 -0
- package/dist/gitOps.js +147 -0
- package/dist/gitOps.js.map +1 -0
- package/dist/guard.js +608 -0
- package/dist/guard.js.map +1 -0
- package/dist/guidance/diagnostics.js +14 -0
- package/dist/guidance/diagnostics.js.map +1 -0
- package/dist/guidance/instructions.js +167 -0
- package/dist/guidance/instructions.js.map +1 -0
- package/dist/guidance/mode.js +18 -0
- package/dist/guidance/mode.js.map +1 -0
- package/dist/guidance/openaiSkillMetadata.js +29 -0
- package/dist/guidance/openaiSkillMetadata.js.map +1 -0
- package/dist/guidance/safeTextReader.js +171 -0
- package/dist/guidance/safeTextReader.js.map +1 -0
- package/dist/guidance/skillCatalog.js +70 -0
- package/dist/guidance/skillCatalog.js.map +1 -0
- package/dist/guidance/skillDiscovery.js +353 -0
- package/dist/guidance/skillDiscovery.js.map +1 -0
- package/dist/guidance/skillMetadata.js +115 -0
- package/dist/guidance/skillMetadata.js.map +1 -0
- package/dist/guidance/skillResources.js +146 -0
- package/dist/guidance/skillResources.js.map +1 -0
- package/dist/handoffOps.js +449 -0
- package/dist/handoffOps.js.map +1 -0
- package/dist/http/localAdminApp.js +203 -0
- package/dist/http/localAdminApp.js.map +1 -0
- package/dist/http/oauthMcpRuntime.js +294 -0
- package/dist/http/oauthMcpRuntime.js.map +1 -0
- package/dist/http/publicApp.js +350 -0
- package/dist/http/publicApp.js.map +1 -0
- package/dist/http/securityHeaders.js +11 -0
- package/dist/http/securityHeaders.js.map +1 -0
- package/dist/http.js +2076 -0
- package/dist/http.js.map +1 -0
- package/dist/moves/engine.js +1079 -0
- package/dist/moves/engine.js.map +1 -0
- package/dist/moves/index.js +6 -0
- package/dist/moves/index.js.map +1 -0
- package/dist/moves/planner.js +270 -0
- package/dist/moves/planner.js.map +1 -0
- package/dist/moves/recovery.js +557 -0
- package/dist/moves/recovery.js.map +1 -0
- package/dist/moves/service.js +300 -0
- package/dist/moves/service.js.map +1 -0
- package/dist/moves/types.js +11 -0
- package/dist/moves/types.js.map +1 -0
- package/dist/mutations/index.js +4 -0
- package/dist/mutations/index.js.map +1 -0
- package/dist/mutations/lifecycle.js +165 -0
- package/dist/mutations/lifecycle.js.map +1 -0
- package/dist/mutations/localService.js +163 -0
- package/dist/mutations/localService.js.map +1 -0
- package/dist/mutations/runtime.js +322 -0
- package/dist/mutations/runtime.js.map +1 -0
- package/dist/mutations/types.js +2 -0
- package/dist/mutations/types.js.map +1 -0
- package/dist/mutations/writers.js +360 -0
- package/dist/mutations/writers.js.map +1 -0
- package/dist/patchOps.js +420 -0
- package/dist/patchOps.js.map +1 -0
- package/dist/policy/approval.js +306 -0
- package/dist/policy/approval.js.map +1 -0
- package/dist/policy/audit.js +88 -0
- package/dist/policy/audit.js.map +1 -0
- package/dist/policy/authorizationFacts.js +187 -0
- package/dist/policy/authorizationFacts.js.map +1 -0
- package/dist/policy/compat.js +104 -0
- package/dist/policy/compat.js.map +1 -0
- package/dist/policy/context.js +31 -0
- package/dist/policy/context.js.map +1 -0
- package/dist/policy/enforcement.js +46 -0
- package/dist/policy/enforcement.js.map +1 -0
- package/dist/policy/evaluator.js +338 -0
- package/dist/policy/evaluator.js.map +1 -0
- package/dist/policy/executionCapabilities.js +80 -0
- package/dist/policy/executionCapabilities.js.map +1 -0
- package/dist/policy/executionResources.js +192 -0
- package/dist/policy/executionResources.js.map +1 -0
- package/dist/policy/fullAccessResources.js +23 -0
- package/dist/policy/fullAccessResources.js.map +1 -0
- package/dist/policy/hardPolicy.js +50 -0
- package/dist/policy/hardPolicy.js.map +1 -0
- package/dist/policy/identity.js +142 -0
- package/dist/policy/identity.js.map +1 -0
- package/dist/policy/integration.js +331 -0
- package/dist/policy/integration.js.map +1 -0
- package/dist/policy/pendingApprovals.js +301 -0
- package/dist/policy/pendingApprovals.js.map +1 -0
- package/dist/policy/profileStore.js +367 -0
- package/dist/policy/profileStore.js.map +1 -0
- package/dist/policy/resources.js +249 -0
- package/dist/policy/resources.js.map +1 -0
- package/dist/policy/runtime.js +713 -0
- package/dist/policy/runtime.js.map +1 -0
- package/dist/policy/schemas.js +625 -0
- package/dist/policy/schemas.js.map +1 -0
- package/dist/policy/toolPolicy.js +141 -0
- package/dist/policy/toolPolicy.js.map +1 -0
- package/dist/policy/transport.js +10 -0
- package/dist/policy/transport.js.map +1 -0
- package/dist/policy/types.js +31 -0
- package/dist/policy/types.js.map +1 -0
- package/dist/proContext.js +675 -0
- package/dist/proContext.js.map +1 -0
- package/dist/process/authority.js +18 -0
- package/dist/process/authority.js.map +1 -0
- package/dist/process/backendDiscovery.js +83 -0
- package/dist/process/backendDiscovery.js.map +1 -0
- package/dist/process/commandCompiler.js +71 -0
- package/dist/process/commandCompiler.js.map +1 -0
- package/dist/process/outputCursor.js +67 -0
- package/dist/process/outputCursor.js.map +1 -0
- package/dist/process/outputQuota.js +94 -0
- package/dist/process/outputQuota.js.map +1 -0
- package/dist/process/outputRing.js +138 -0
- package/dist/process/outputRing.js.map +1 -0
- package/dist/process/processAuditCoordinator.js +52 -0
- package/dist/process/processAuditCoordinator.js.map +1 -0
- package/dist/process/processManager.js +311 -0
- package/dist/process/processManager.js.map +1 -0
- package/dist/process/runCommand.js +332 -0
- package/dist/process/runCommand.js.map +1 -0
- package/dist/process/streamingRedactor.js +105 -0
- package/dist/process/streamingRedactor.js.map +1 -0
- package/dist/process/types.js +2 -0
- package/dist/process/types.js.map +1 -0
- package/dist/process/windowsHostClient.js +604 -0
- package/dist/process/windowsHostClient.js.map +1 -0
- package/dist/process/windowsHostProtocol.js +270 -0
- package/dist/process/windowsHostProtocol.js.map +1 -0
- package/dist/process/windowsPersistentBackend.js +263 -0
- package/dist/process/windowsPersistentBackend.js.map +1 -0
- package/dist/productionRuntime.js +907 -0
- package/dist/productionRuntime.js.map +1 -0
- package/dist/profileStore.js +231 -0
- package/dist/profileStore.js.map +1 -0
- package/dist/redact.js +124 -0
- package/dist/redact.js.map +1 -0
- package/dist/searchOps.js +186 -0
- package/dist/searchOps.js.map +1 -0
- package/dist/selfTestOps.js +786 -0
- package/dist/selfTestOps.js.map +1 -0
- package/dist/semantic/budgets.js +12 -0
- package/dist/semantic/budgets.js.map +1 -0
- package/dist/semantic/builtin/typescriptAssets.js +68 -0
- package/dist/semantic/builtin/typescriptAssets.js.map +1 -0
- package/dist/semantic/builtin/typescriptProvider.js +211 -0
- package/dist/semantic/builtin/typescriptProvider.js.map +1 -0
- package/dist/semantic/builtin/typescriptWorker.js +365 -0
- package/dist/semantic/builtin/typescriptWorker.js.map +1 -0
- package/dist/semantic/diffPreview.js +36 -0
- package/dist/semantic/diffPreview.js.map +1 -0
- package/dist/semantic/index.js +11 -0
- package/dist/semantic/index.js.map +1 -0
- package/dist/semantic/manager.js +522 -0
- package/dist/semantic/manager.js.map +1 -0
- package/dist/semantic/positions.js +86 -0
- package/dist/semantic/positions.js.map +1 -0
- package/dist/semantic/previewStore.js +276 -0
- package/dist/semantic/previewStore.js.map +1 -0
- package/dist/semantic/projectResolver.js +309 -0
- package/dist/semantic/projectResolver.js.map +1 -0
- package/dist/semantic/sourceSnapshot.js +54 -0
- package/dist/semantic/sourceSnapshot.js.map +1 -0
- package/dist/semantic/status.js +46 -0
- package/dist/semantic/status.js.map +1 -0
- package/dist/semantic/types.js +2 -0
- package/dist/semantic/types.js.map +1 -0
- package/dist/server.js +8676 -0
- package/dist/server.js.map +1 -0
- package/dist/stdio.js +64 -0
- package/dist/stdio.js.map +1 -0
- package/dist/toolCardWidget.js +2000 -0
- package/dist/toolCardWidget.js.map +1 -0
- package/dist/tools/contracts/catalog.js +111 -0
- package/dist/tools/contracts/catalog.js.map +1 -0
- package/dist/tools/contracts/index.js +8 -0
- package/dist/tools/contracts/index.js.map +1 -0
- package/dist/tools/contracts/registration.js +27 -0
- package/dist/tools/contracts/registration.js.map +1 -0
- package/dist/tools/contracts/schemas.js +11 -0
- package/dist/tools/contracts/schemas.js.map +1 -0
- package/dist/tools/contracts/types.js +2 -0
- package/dist/tools/contracts/types.js.map +1 -0
- package/dist/tools/contracts/v3.js +76 -0
- package/dist/tools/contracts/v3.js.map +1 -0
- package/dist/tools/contracts/v4.js +39 -0
- package/dist/tools/contracts/v4.js.map +1 -0
- package/dist/tools/contracts/v5.js +20 -0
- package/dist/tools/contracts/v5.js.map +1 -0
- package/dist/tools/phase3dServer.js +350 -0
- package/dist/tools/phase3dServer.js.map +1 -0
- package/dist/tools/schemas/applyPatch.js +388 -0
- package/dist/tools/schemas/applyPatch.js.map +1 -0
- package/dist/tools/schemas/bash.js +202 -0
- package/dist/tools/schemas/bash.js.map +1 -0
- package/dist/tools/schemas/closeWorkspace.js +105 -0
- package/dist/tools/schemas/closeWorkspace.js.map +1 -0
- package/dist/tools/schemas/codexContext.js +402 -0
- package/dist/tools/schemas/codexContext.js.map +1 -0
- package/dist/tools/schemas/codexSessions.js +328 -0
- package/dist/tools/schemas/codexSessions.js.map +1 -0
- package/dist/tools/schemas/codexgpt.js +1068 -0
- package/dist/tools/schemas/codexgpt.js.map +1 -0
- package/dist/tools/schemas/codexgptInventory.js +359 -0
- package/dist/tools/schemas/codexgptInventory.js.map +1 -0
- package/dist/tools/schemas/codexgptSelfTest.js +563 -0
- package/dist/tools/schemas/codexgptSelfTest.js.map +1 -0
- package/dist/tools/schemas/codexpro.js +917 -0
- package/dist/tools/schemas/codexpro.js.map +1 -0
- package/dist/tools/schemas/codexproInventory.js +352 -0
- package/dist/tools/schemas/codexproInventory.js.map +1 -0
- package/dist/tools/schemas/codexproSelfTest.js +546 -0
- package/dist/tools/schemas/codexproSelfTest.js.map +1 -0
- package/dist/tools/schemas/common.js +21 -0
- package/dist/tools/schemas/common.js.map +1 -0
- package/dist/tools/schemas/createTaskWorktree.js +51 -0
- package/dist/tools/schemas/createTaskWorktree.js.map +1 -0
- package/dist/tools/schemas/edit.js +321 -0
- package/dist/tools/schemas/edit.js.map +1 -0
- package/dist/tools/schemas/execution.js +330 -0
- package/dist/tools/schemas/execution.js.map +1 -0
- package/dist/tools/schemas/exportProContext.js +370 -0
- package/dist/tools/schemas/exportProContext.js.map +1 -0
- package/dist/tools/schemas/getTaskWorktree.js +19 -0
- package/dist/tools/schemas/getTaskWorktree.js.map +1 -0
- package/dist/tools/schemas/gitBranch.js +42 -0
- package/dist/tools/schemas/gitBranch.js.map +1 -0
- package/dist/tools/schemas/gitCommit.js +40 -0
- package/dist/tools/schemas/gitCommit.js.map +1 -0
- package/dist/tools/schemas/gitCreateBranch.js +22 -0
- package/dist/tools/schemas/gitCreateBranch.js.map +1 -0
- package/dist/tools/schemas/gitDiff.js +222 -0
- package/dist/tools/schemas/gitDiff.js.map +1 -0
- package/dist/tools/schemas/gitLog.js +41 -0
- package/dist/tools/schemas/gitLog.js.map +1 -0
- package/dist/tools/schemas/gitRestore.js +47 -0
- package/dist/tools/schemas/gitRestore.js.map +1 -0
- package/dist/tools/schemas/gitStage.js +33 -0
- package/dist/tools/schemas/gitStage.js.map +1 -0
- package/dist/tools/schemas/gitStash.js +102 -0
- package/dist/tools/schemas/gitStash.js.map +1 -0
- package/dist/tools/schemas/gitStatus.js +226 -0
- package/dist/tools/schemas/gitStatus.js.map +1 -0
- package/dist/tools/schemas/gitV4Common.js +205 -0
- package/dist/tools/schemas/gitV4Common.js.map +1 -0
- package/dist/tools/schemas/guidance.js +40 -0
- package/dist/tools/schemas/guidance.js.map +1 -0
- package/dist/tools/schemas/handoffToAgent.js +276 -0
- package/dist/tools/schemas/handoffToAgent.js.map +1 -0
- package/dist/tools/schemas/handoffToCodex.js +137 -0
- package/dist/tools/schemas/handoffToCodex.js.map +1 -0
- package/dist/tools/schemas/inspectWorkspace.js +379 -0
- package/dist/tools/schemas/inspectWorkspace.js.map +1 -0
- package/dist/tools/schemas/listTaskWorktrees.js +21 -0
- package/dist/tools/schemas/listTaskWorktrees.js.map +1 -0
- package/dist/tools/schemas/listWorkspaces.js +134 -0
- package/dist/tools/schemas/listWorkspaces.js.map +1 -0
- package/dist/tools/schemas/loadSkill.js +619 -0
- package/dist/tools/schemas/loadSkill.js.map +1 -0
- package/dist/tools/schemas/mergeTaskWorktree.js +105 -0
- package/dist/tools/schemas/mergeTaskWorktree.js.map +1 -0
- package/dist/tools/schemas/movePaths.js +175 -0
- package/dist/tools/schemas/movePaths.js.map +1 -0
- package/dist/tools/schemas/openCurrentWorkspace.js +174 -0
- package/dist/tools/schemas/openCurrentWorkspace.js.map +1 -0
- package/dist/tools/schemas/openFullAccessWorkspace.js +81 -0
- package/dist/tools/schemas/openFullAccessWorkspace.js.map +1 -0
- package/dist/tools/schemas/openWorkspace.js +193 -0
- package/dist/tools/schemas/openWorkspace.js.map +1 -0
- package/dist/tools/schemas/queryAuditEvents.js +193 -0
- package/dist/tools/schemas/queryAuditEvents.js.map +1 -0
- package/dist/tools/schemas/read.js +199 -0
- package/dist/tools/schemas/read.js.map +1 -0
- package/dist/tools/schemas/readCodexSession.js +353 -0
- package/dist/tools/schemas/readCodexSession.js.map +1 -0
- package/dist/tools/schemas/readHandoff.js +446 -0
- package/dist/tools/schemas/readHandoff.js.map +1 -0
- package/dist/tools/schemas/removeTaskWorktree.js +40 -0
- package/dist/tools/schemas/removeTaskWorktree.js.map +1 -0
- package/dist/tools/schemas/search.js +235 -0
- package/dist/tools/schemas/search.js.map +1 -0
- package/dist/tools/schemas/semantic.js +195 -0
- package/dist/tools/schemas/semantic.js.map +1 -0
- package/dist/tools/schemas/serverConfig.js +157 -0
- package/dist/tools/schemas/serverConfig.js.map +1 -0
- package/dist/tools/schemas/showChanges.js +283 -0
- package/dist/tools/schemas/showChanges.js.map +1 -0
- package/dist/tools/schemas/transactionResult.js +11 -0
- package/dist/tools/schemas/transactionResult.js.map +1 -0
- package/dist/tools/schemas/tree.js +136 -0
- package/dist/tools/schemas/tree.js.map +1 -0
- package/dist/tools/schemas/undoChangeSet.js +152 -0
- package/dist/tools/schemas/undoChangeSet.js.map +1 -0
- package/dist/tools/schemas/waitForHandoff.js +462 -0
- package/dist/tools/schemas/waitForHandoff.js.map +1 -0
- package/dist/tools/schemas/workspaceSnapshot.js +145 -0
- package/dist/tools/schemas/workspaceSnapshot.js.map +1 -0
- package/dist/tools/schemas/write.js +285 -0
- package/dist/tools/schemas/write.js.map +1 -0
- package/dist/transactions/atomicFs.js +438 -0
- package/dist/transactions/atomicFs.js.map +1 -0
- package/dist/transactions/atomicStateFile.js +190 -0
- package/dist/transactions/atomicStateFile.js.map +1 -0
- package/dist/transactions/engine.js +574 -0
- package/dist/transactions/engine.js.map +1 -0
- package/dist/transactions/index.js +12 -0
- package/dist/transactions/index.js.map +1 -0
- package/dist/transactions/installation.js +158 -0
- package/dist/transactions/installation.js.map +1 -0
- package/dist/transactions/manifestV2Store.js +96 -0
- package/dist/transactions/manifestV2Store.js.map +1 -0
- package/dist/transactions/participantRecovery.js +98 -0
- package/dist/transactions/participantRecovery.js.map +1 -0
- package/dist/transactions/recovery.js +513 -0
- package/dist/transactions/recovery.js.map +1 -0
- package/dist/transactions/schemas.js +365 -0
- package/dist/transactions/schemas.js.map +1 -0
- package/dist/transactions/stateRoot.js +63 -0
- package/dist/transactions/stateRoot.js.map +1 -0
- package/dist/transactions/types.js +11 -0
- package/dist/transactions/types.js.map +1 -0
- package/dist/transactions/workspaceLock.js +201 -0
- package/dist/transactions/workspaceLock.js.map +1 -0
- package/dist/workspaceOps.js +924 -0
- package/dist/workspaceOps.js.map +1 -0
- package/dist/worktrees/candidateWorkspace.js +566 -0
- package/dist/worktrees/candidateWorkspace.js.map +1 -0
- package/dist/worktrees/manager.js +258 -0
- package/dist/worktrees/manager.js.map +1 -0
- package/dist/worktrees/materializer.js +60 -0
- package/dist/worktrees/materializer.js.map +1 -0
- package/dist/worktrees/mergeExecute.js +498 -0
- package/dist/worktrees/mergeExecute.js.map +1 -0
- package/dist/worktrees/mergePlanStore.js +247 -0
- package/dist/worktrees/mergePlanStore.js.map +1 -0
- package/dist/worktrees/mergePrepare.js +709 -0
- package/dist/worktrees/mergePrepare.js.map +1 -0
- package/dist/worktrees/recovery.js +294 -0
- package/dist/worktrees/recovery.js.map +1 -0
- package/dist/worktrees/remove.js +296 -0
- package/dist/worktrees/remove.js.map +1 -0
- package/dist/worktrees/remover.js +57 -0
- package/dist/worktrees/remover.js.map +1 -0
- package/dist/worktrees/root.js +47 -0
- package/dist/worktrees/root.js.map +1 -0
- package/dist/worktrees/service.js +245 -0
- package/dist/worktrees/service.js.map +1 -0
- package/dist/worktrees/store.js +156 -0
- package/dist/worktrees/store.js.map +1 -0
- package/dist/worktrees/treeManifest.js +106 -0
- package/dist/worktrees/treeManifest.js.map +1 -0
- package/dist/worktrees/verificationReceipts.js +215 -0
- package/dist/worktrees/verificationReceipts.js.map +1 -0
- package/dist/worktrees/verificationTerminal.js +23 -0
- package/dist/worktrees/verificationTerminal.js.map +1 -0
- package/dist/worktrees/workspaceAuthority.js +64 -0
- package/dist/worktrees/workspaceAuthority.js.map +1 -0
- package/docs/favicon.svg +5 -0
- package/docs/index.html +624 -0
- package/docs/og.svg +52 -0
- package/docs/script.js +33 -0
- package/docs/star.svg +11 -0
- package/docs/styles.css +1571 -0
- package/docs/zh.html +428 -0
- package/package.json +121 -0
- package/scripts/analysis-cli-smoke.mjs +81 -0
- package/scripts/analysis-smoke.mjs +179 -0
- package/scripts/atomic-file.mjs +137 -0
- package/scripts/ci-change-classifier.mjs +59 -0
- package/scripts/ci-failure-summary.mjs +92 -0
- package/scripts/cloudflared-installer.mjs +215 -0
- package/scripts/cloudflared-release.mjs +63 -0
- package/scripts/codexgpt-entry.mjs +235 -0
- package/scripts/codexgpt.mjs +4987 -0
- package/scripts/connector-auth-output-shim.cjs +66 -0
- package/scripts/doctor-smoke.mjs +90 -0
- package/scripts/doctor.mjs +165 -0
- package/scripts/exact-head-ci.mjs +126 -0
- package/scripts/execute-handoff-smoke-platform-compat.mjs +38 -0
- package/scripts/execute-handoff-smoke.mjs +1117 -0
- package/scripts/g8-u-live-probe.mjs +208 -0
- package/scripts/http-smoke-compat.mjs +151 -0
- package/scripts/http-smoke.mjs +776 -0
- package/scripts/long-task-runner.mjs +1037 -0
- package/scripts/oauth-admin.mjs +1608 -0
- package/scripts/output-bounds.mjs +29 -0
- package/scripts/owned-temp-root.d.mts +50 -0
- package/scripts/owned-temp-root.mjs +567 -0
- package/scripts/phase-4a-smoke.mjs +36 -0
- package/scripts/policy-windows-spike.mjs +135 -0
- package/scripts/pro-apply.mjs +152 -0
- package/scripts/pro-bundle.mjs +121 -0
- package/scripts/pro-smoke.mjs +97 -0
- package/scripts/process-identity.mjs +67 -0
- package/scripts/repository-policy.mjs +166 -0
- package/scripts/run-and-summarize.mjs +134 -0
- package/scripts/run-smoke.mjs +52 -0
- package/scripts/run-with-cleanup.mjs +98 -0
- package/scripts/settings-smoke-platform-compat.mjs +85 -0
- package/scripts/settings-smoke.mjs +769 -0
- package/scripts/smoke-platform-compat.mjs +302 -0
- package/scripts/smoke.mjs +1180 -0
- package/scripts/stress-contract-compat.mjs +94 -0
- package/scripts/stress.mjs +882 -0
- package/scripts/test-domains.mjs +247 -0
- package/scripts/test-execution-profile-manifest.mjs +273 -0
- package/scripts/test-execution-profiles.mjs +147 -0
- package/scripts/test-performance-reporter.mjs +291 -0
- package/scripts/toolchain-manager.mjs +266 -0
- package/scripts/toolchains.json +19 -0
- package/scripts/windows-conpty-probe-child.mjs +82 -0
- package/scripts/windows-conpty-worker.ps1 +24 -0
- package/scripts/windows-credential-host-manifest.json +16 -0
- package/scripts/windows-credential-host-protocol-v1.json +16 -0
- package/scripts/windows-credential-host.cs +256 -0
- package/scripts/windows-credential-host.ps1 +26 -0
- package/scripts/windows-local-control-manifest.json +23 -0
- package/scripts/windows-local-control-spike.cs +738 -0
- package/scripts/windows-local-control-spike.mjs +415 -0
- package/scripts/windows-local-control-spike.ps1 +35 -0
- package/scripts/windows-local-control.cs +738 -0
- package/scripts/windows-local-control.ps1 +35 -0
- package/scripts/windows-native-api-inventory-v1.json +90 -0
- package/scripts/windows-process-host-manifest.json +20 -0
- package/scripts/windows-process-host-protocol-v1.json +59 -0
- package/scripts/windows-process-host.cs +2385 -0
- package/scripts/windows-process-host.ps1 +59 -0
- package/scripts/windows-realpath-shim.cjs +78 -0
|
@@ -0,0 +1,2385 @@
|
|
|
1
|
+
// Production Phase 4 native host; promoted unchanged from the Gate N proof.
|
|
2
|
+
using System;
|
|
3
|
+
using System.Collections;
|
|
4
|
+
using System.Collections.Concurrent;
|
|
5
|
+
using System.Collections.Generic;
|
|
6
|
+
using System.ComponentModel;
|
|
7
|
+
using System.Diagnostics;
|
|
8
|
+
using System.IO;
|
|
9
|
+
using System.IO.Pipes;
|
|
10
|
+
using System.Runtime.InteropServices;
|
|
11
|
+
using System.Security.Cryptography;
|
|
12
|
+
using System.Text;
|
|
13
|
+
using System.Threading;
|
|
14
|
+
using System.Threading.Tasks;
|
|
15
|
+
using System.Web.Script.Serialization;
|
|
16
|
+
using Microsoft.Win32.SafeHandles;
|
|
17
|
+
|
|
18
|
+
namespace CodexGPT.Phase4
|
|
19
|
+
{
|
|
20
|
+
public static class ProcessHost
|
|
21
|
+
{
|
|
22
|
+
private const ushort ProtocolVersion = 1;
|
|
23
|
+
private const ushort HeaderLength = 64;
|
|
24
|
+
private const int TagOffset = 48;
|
|
25
|
+
private const int TagLength = 16;
|
|
26
|
+
private const int MaxPayload = 65536;
|
|
27
|
+
private const int MaxHelloPayload = 4096;
|
|
28
|
+
private const int MaxFramedInputBytes = 131072;
|
|
29
|
+
private const int MaxFramedOutputBytes = 16777216;
|
|
30
|
+
private const int MaxArguments = 512;
|
|
31
|
+
private const int MaxArgumentTotalBytes = 65536;
|
|
32
|
+
private const int MaxOneShotTimeoutMs = 120000;
|
|
33
|
+
private const ushort Hello = 0x01;
|
|
34
|
+
private const ushort HelloAck = 0x02;
|
|
35
|
+
private const ushort RequestJson = 0x10;
|
|
36
|
+
private const ushort ResponseJson = 0x11;
|
|
37
|
+
private const ushort EventJson = 0x12;
|
|
38
|
+
private const ushort Output = 0x20;
|
|
39
|
+
private const ushort Input = 0x21;
|
|
40
|
+
private const ushort Credit = 0x22;
|
|
41
|
+
private const ushort Cancel = 0x23;
|
|
42
|
+
private const ushort Fatal = 0x7f;
|
|
43
|
+
private const ushort FlagStderr = 0x0001;
|
|
44
|
+
private const ushort FlagEof = 0x0002;
|
|
45
|
+
|
|
46
|
+
private const uint StartfUseStdHandles = 0x00000100;
|
|
47
|
+
private const uint ExtendedStartupInfoPresent = 0x00080000;
|
|
48
|
+
private const uint CreateSuspended = 0x00000004;
|
|
49
|
+
private const uint CreateUnicodeEnvironment = 0x00000400;
|
|
50
|
+
private const uint CreateNoWindow = 0x08000000;
|
|
51
|
+
private const uint HandleFlagInherit = 0x00000001;
|
|
52
|
+
private const uint JobObjectLimitKillOnJobClose = 0x00002000;
|
|
53
|
+
private const uint JobObjectLimitActiveProcess = 0x00000008;
|
|
54
|
+
private const int JobObjectExtendedLimitInformation = 9;
|
|
55
|
+
private static readonly IntPtr ProcThreadAttributeHandleList = new IntPtr(0x00020002);
|
|
56
|
+
private static readonly IntPtr ProcThreadAttributeJobList = new IntPtr(0x0002000D);
|
|
57
|
+
private static readonly IntPtr ProcThreadAttributePseudoConsole = new IntPtr(0x00020016);
|
|
58
|
+
private const uint GenericRead = 0x80000000;
|
|
59
|
+
private const uint FileShareRead = 0x00000001;
|
|
60
|
+
private const uint OpenExisting = 3;
|
|
61
|
+
private const uint FileAttributeNormal = 0x00000080;
|
|
62
|
+
private const uint WaitObject0 = 0;
|
|
63
|
+
private const uint WaitTimeout = 258;
|
|
64
|
+
private const uint Infinite = 0xffffffff;
|
|
65
|
+
private const int StdInputHandle = -10;
|
|
66
|
+
private const int StdOutputHandle = -11;
|
|
67
|
+
private const int StdErrorHandle = -12;
|
|
68
|
+
private const int ErrorBrokenPipe = 109;
|
|
69
|
+
private const int ErrorNoData = 232;
|
|
70
|
+
|
|
71
|
+
private static readonly JavaScriptSerializer Json = new JavaScriptSerializer
|
|
72
|
+
{
|
|
73
|
+
MaxJsonLength = MaxPayload,
|
|
74
|
+
RecursionLimit = 16
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
private static class HostLifetime
|
|
78
|
+
{
|
|
79
|
+
private static readonly ConcurrentDictionary<long, IntPtr> ActiveJobs = new ConcurrentDictionary<long, IntPtr>();
|
|
80
|
+
private static long nextJobId;
|
|
81
|
+
private static int watchdogStarted;
|
|
82
|
+
|
|
83
|
+
public static long RegisterJob(IntPtr job)
|
|
84
|
+
{
|
|
85
|
+
long id = Interlocked.Increment(ref nextJobId);
|
|
86
|
+
if (!ActiveJobs.TryAdd(id, job)) throw new InvalidOperationException("JOB_REGISTRATION_FAILED");
|
|
87
|
+
return id;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public static void ReleaseJob(long id, ref IntPtr job)
|
|
91
|
+
{
|
|
92
|
+
IntPtr registered;
|
|
93
|
+
ActiveJobs.TryRemove(id, out registered);
|
|
94
|
+
if (job != IntPtr.Zero && job != new IntPtr(-1)) CloseHandle(job);
|
|
95
|
+
job = IntPtr.Zero;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public static void StartInputWatchdog()
|
|
99
|
+
{
|
|
100
|
+
if (Interlocked.Exchange(ref watchdogStarted, 1) != 0) return;
|
|
101
|
+
IntPtr input = GetStdHandle(StdInputHandle);
|
|
102
|
+
Thread watchdog = new Thread(() =>
|
|
103
|
+
{
|
|
104
|
+
while (true)
|
|
105
|
+
{
|
|
106
|
+
uint available;
|
|
107
|
+
bool readable = PeekNamedPipe(input, IntPtr.Zero, 0, IntPtr.Zero, out available, IntPtr.Zero);
|
|
108
|
+
if (!readable)
|
|
109
|
+
{
|
|
110
|
+
int error = Marshal.GetLastWin32Error();
|
|
111
|
+
if (error == ErrorBrokenPipe || error == ErrorNoData)
|
|
112
|
+
{
|
|
113
|
+
foreach (KeyValuePair<long, IntPtr> entry in ActiveJobs)
|
|
114
|
+
{
|
|
115
|
+
IntPtr handle;
|
|
116
|
+
if (ActiveJobs.TryRemove(entry.Key, out handle) && handle != IntPtr.Zero && handle != new IntPtr(-1))
|
|
117
|
+
CloseHandle(handle);
|
|
118
|
+
}
|
|
119
|
+
Environment.Exit(0);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
Thread.Sleep(50);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
watchdog.IsBackground = true;
|
|
126
|
+
watchdog.Name = "CXP4 stdin lifetime watchdog";
|
|
127
|
+
watchdog.Start();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private sealed class Frame
|
|
132
|
+
{
|
|
133
|
+
public ushort Kind;
|
|
134
|
+
public ushort Flags;
|
|
135
|
+
public uint Sequence;
|
|
136
|
+
public byte[] RequestId;
|
|
137
|
+
public ulong ProcessGeneration;
|
|
138
|
+
public byte[] Payload;
|
|
139
|
+
public int FrameBytes;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private sealed class BoundedReadResult
|
|
143
|
+
{
|
|
144
|
+
public byte[] Bytes;
|
|
145
|
+
public long TotalBytes;
|
|
146
|
+
public long DroppedBytes;
|
|
147
|
+
public bool Truncated;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private sealed class ProcessRunResult
|
|
151
|
+
{
|
|
152
|
+
public bool Ok;
|
|
153
|
+
public string Code;
|
|
154
|
+
public uint ExitCode;
|
|
155
|
+
public bool TimedOut;
|
|
156
|
+
public int ProcessId;
|
|
157
|
+
public bool JobAssignedAtCreation;
|
|
158
|
+
public bool ExactHandleList;
|
|
159
|
+
public bool ImageIdentityVerified;
|
|
160
|
+
public uint VolumeSerial;
|
|
161
|
+
public ulong FileIndex;
|
|
162
|
+
public uint NumberOfLinks;
|
|
163
|
+
public byte[] Stdout;
|
|
164
|
+
public byte[] Stderr;
|
|
165
|
+
public long StdoutTotalBytes;
|
|
166
|
+
public long StderrTotalBytes;
|
|
167
|
+
public long StdoutDroppedBytes;
|
|
168
|
+
public long StderrDroppedBytes;
|
|
169
|
+
public bool StdoutTruncated;
|
|
170
|
+
public bool StderrTruncated;
|
|
171
|
+
public long ElapsedMilliseconds;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private sealed class FramedRunOutcome
|
|
175
|
+
{
|
|
176
|
+
public ProcessRunResult Run;
|
|
177
|
+
public string ErrorCode;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private sealed class PersistentProcessState
|
|
181
|
+
{
|
|
182
|
+
public readonly object Sync = new object();
|
|
183
|
+
public string Handle;
|
|
184
|
+
public IntPtr ProcessHandle;
|
|
185
|
+
public IntPtr JobHandle;
|
|
186
|
+
public long JobRegistration;
|
|
187
|
+
public FileStream Stdin;
|
|
188
|
+
public MemoryStream Stdout = new MemoryStream();
|
|
189
|
+
public MemoryStream Stderr = new MemoryStream();
|
|
190
|
+
public ManualResetEventSlim Changed = new ManualResetEventSlim(false);
|
|
191
|
+
public bool Running = true;
|
|
192
|
+
public uint ExitCode;
|
|
193
|
+
public string Reason = "running";
|
|
194
|
+
public bool StdinClosed;
|
|
195
|
+
public bool StdoutClosed;
|
|
196
|
+
public bool StderrClosed;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private static readonly ConcurrentDictionary<string, PersistentProcessState> PersistentProcesses = new ConcurrentDictionary<string, PersistentProcessState>();
|
|
200
|
+
|
|
201
|
+
public static void Run()
|
|
202
|
+
{
|
|
203
|
+
Stream input = Console.OpenStandardInput();
|
|
204
|
+
Stream output = Console.OpenStandardOutput();
|
|
205
|
+
byte[] nodeToHostKey = ReadExact(input, 32, false);
|
|
206
|
+
byte[] hostToNodeKey = ReadExact(input, 32, false);
|
|
207
|
+
if (nodeToHostKey == null || hostToNodeKey == null) return;
|
|
208
|
+
HostLifetime.StartInputWatchdog();
|
|
209
|
+
uint incomingSequence = 1;
|
|
210
|
+
uint outgoingSequence = 1;
|
|
211
|
+
try
|
|
212
|
+
{
|
|
213
|
+
while (true)
|
|
214
|
+
{
|
|
215
|
+
Frame frame = ReadFrame(input, nodeToHostKey, incomingSequence);
|
|
216
|
+
if (frame == null) return;
|
|
217
|
+
incomingSequence++;
|
|
218
|
+
if (incomingSequence == 0) throw new InvalidDataException("SEQUENCE_EXHAUSTED");
|
|
219
|
+
if (frame.Kind == Hello)
|
|
220
|
+
{
|
|
221
|
+
Dictionary<string, object> hello = ParseStrictObject(frame.Payload);
|
|
222
|
+
RequireExactKeys(hello, new string[] { "schemaVersion", "protocolVersion", "nonce" });
|
|
223
|
+
if (ToInt(hello["schemaVersion"], "schemaVersion") != 1 || ToInt(hello["protocolVersion"], "protocolVersion") != 1)
|
|
224
|
+
throw new InvalidDataException("HELLO_VERSION_MISMATCH");
|
|
225
|
+
string nonce = RequireSafeNonce(hello["nonce"]);
|
|
226
|
+
Dictionary<string, object> response = new Dictionary<string, object>();
|
|
227
|
+
response["schemaVersion"] = 1;
|
|
228
|
+
response["protocolVersion"] = 1;
|
|
229
|
+
response["nonce"] = nonce;
|
|
230
|
+
response["host"] = "windows-powershell-csharp-spike";
|
|
231
|
+
response["rawStandardStreams"] = true;
|
|
232
|
+
WriteJsonFrame(output, hostToNodeKey, ref outgoingSequence, HelloAck, frame.RequestId, 0, response);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (frame.Kind != RequestJson) throw new InvalidDataException("DIRECTION_INVALID_FRAME");
|
|
236
|
+
Dictionary<string, object> request = ParseStrictObject(frame.Payload);
|
|
237
|
+
bool framed = request.ContainsKey("stream");
|
|
238
|
+
RequireExactKeys(request, framed
|
|
239
|
+
? new string[] { "schemaVersion", "operation", "input", "stream" }
|
|
240
|
+
: new string[] { "schemaVersion", "operation", "input" });
|
|
241
|
+
if (ToInt(request["schemaVersion"], "schemaVersion") != 1) throw new InvalidDataException("REQUEST_VERSION_MISMATCH");
|
|
242
|
+
string operation = RequireBoundedString(request["operation"], "operation", 1, 80);
|
|
243
|
+
Dictionary<string, object> requestInput = AsObject(request["input"], "input");
|
|
244
|
+
if (framed)
|
|
245
|
+
{
|
|
246
|
+
HandleFramedRequest(
|
|
247
|
+
input,
|
|
248
|
+
output,
|
|
249
|
+
nodeToHostKey,
|
|
250
|
+
hostToNodeKey,
|
|
251
|
+
ref incomingSequence,
|
|
252
|
+
ref outgoingSequence,
|
|
253
|
+
frame,
|
|
254
|
+
operation,
|
|
255
|
+
requestInput,
|
|
256
|
+
AsObject(request["stream"], "stream"));
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
Dictionary<string, object> result;
|
|
260
|
+
try
|
|
261
|
+
{
|
|
262
|
+
result = Dispatch(operation, requestInput);
|
|
263
|
+
}
|
|
264
|
+
catch (Exception error)
|
|
265
|
+
{
|
|
266
|
+
result = ErrorResult(SafeErrorCode(error));
|
|
267
|
+
}
|
|
268
|
+
WriteJsonFrame(output, hostToNodeKey, ref outgoingSequence, ResponseJson, frame.RequestId, 0, result);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (Exception error)
|
|
272
|
+
{
|
|
273
|
+
try
|
|
274
|
+
{
|
|
275
|
+
Dictionary<string, object> fatal = new Dictionary<string, object>();
|
|
276
|
+
fatal["schemaVersion"] = 1;
|
|
277
|
+
fatal["code"] = SafeErrorCode(error);
|
|
278
|
+
WriteJsonFrame(output, hostToNodeKey, ref outgoingSequence, Fatal, new byte[16], 0, fatal);
|
|
279
|
+
}
|
|
280
|
+
catch { }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private static void HandleFramedRequest(
|
|
285
|
+
Stream input,
|
|
286
|
+
Stream output,
|
|
287
|
+
byte[] nodeToHostKey,
|
|
288
|
+
byte[] hostToNodeKey,
|
|
289
|
+
ref uint incomingSequence,
|
|
290
|
+
ref uint outgoingSequence,
|
|
291
|
+
Frame requestFrame,
|
|
292
|
+
string operation,
|
|
293
|
+
Dictionary<string, object> requestInput,
|
|
294
|
+
Dictionary<string, object> stream)
|
|
295
|
+
{
|
|
296
|
+
RequireExactKeys(stream, new string[] { "version", "inputBytes", "output" });
|
|
297
|
+
if (ToInt(stream["version"], "version") != 1) throw new InvalidDataException("STREAM_VERSION_MISMATCH");
|
|
298
|
+
int inputLength = ToBoundedInt(stream["inputBytes"], "inputBytes", 0, MaxFramedInputBytes);
|
|
299
|
+
if (RequireBoundedString(stream["output"], "output", 6, 6) != "frames") throw new InvalidDataException("STREAM_OUTPUT_INVALID");
|
|
300
|
+
WriteCredit(output, hostToNodeKey, ref outgoingSequence, requestFrame.RequestId, requestFrame.FrameBytes);
|
|
301
|
+
byte[] streamedInput = ReadFramedInput(
|
|
302
|
+
input,
|
|
303
|
+
output,
|
|
304
|
+
nodeToHostKey,
|
|
305
|
+
hostToNodeKey,
|
|
306
|
+
ref incomingSequence,
|
|
307
|
+
ref outgoingSequence,
|
|
308
|
+
requestFrame.RequestId,
|
|
309
|
+
inputLength);
|
|
310
|
+
using (ManualResetEvent cancel = new ManualResetEvent(false))
|
|
311
|
+
{
|
|
312
|
+
Task<FramedRunOutcome> work = Task.Run(() =>
|
|
313
|
+
{
|
|
314
|
+
try
|
|
315
|
+
{
|
|
316
|
+
return new FramedRunOutcome { Run = RunFramedOperation(operation, requestInput, streamedInput, cancel) };
|
|
317
|
+
}
|
|
318
|
+
catch (Exception error)
|
|
319
|
+
{
|
|
320
|
+
return new FramedRunOutcome { ErrorCode = SafeErrorCode(error) };
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
Task<Frame> controlRead = StartFrameRead(input, nodeToHostKey, incomingSequence);
|
|
324
|
+
bool canceled = false;
|
|
325
|
+
while (!work.Wait(10))
|
|
326
|
+
{
|
|
327
|
+
if (!controlRead.IsCompleted) continue;
|
|
328
|
+
Frame control = CompleteControlRead(controlRead, ref incomingSequence);
|
|
329
|
+
if (HandleInterleavedRequest(control, output, hostToNodeKey, ref outgoingSequence, requestFrame.RequestId))
|
|
330
|
+
{
|
|
331
|
+
controlRead = StartFrameRead(input, nodeToHostKey, incomingSequence);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
ValidateCorrelatedControl(control, requestFrame.RequestId);
|
|
335
|
+
if (control.Kind != Cancel) throw new InvalidDataException("DIRECTION_INVALID_FRAME");
|
|
336
|
+
cancel.Set();
|
|
337
|
+
canceled = true;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
if (!canceled && controlRead.IsCompleted)
|
|
341
|
+
{
|
|
342
|
+
Frame control = CompleteControlRead(controlRead, ref incomingSequence);
|
|
343
|
+
while (HandleInterleavedRequest(control, output, hostToNodeKey, ref outgoingSequence, requestFrame.RequestId))
|
|
344
|
+
{
|
|
345
|
+
controlRead = StartFrameRead(input, nodeToHostKey, incomingSequence);
|
|
346
|
+
if (!controlRead.IsCompleted)
|
|
347
|
+
{
|
|
348
|
+
control = null;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
control = CompleteControlRead(controlRead, ref incomingSequence);
|
|
352
|
+
}
|
|
353
|
+
if (control != null)
|
|
354
|
+
{
|
|
355
|
+
ValidateCorrelatedControl(control, requestFrame.RequestId);
|
|
356
|
+
if (control.Kind == Cancel)
|
|
357
|
+
{
|
|
358
|
+
cancel.Set();
|
|
359
|
+
canceled = true;
|
|
360
|
+
}
|
|
361
|
+
else throw new InvalidDataException("DIRECTION_INVALID_FRAME");
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (canceled)
|
|
365
|
+
{
|
|
366
|
+
if (!work.Wait(10000)) throw new InvalidDataException("REQUEST_CANCEL_FAILED");
|
|
367
|
+
Dictionary<string, object> canceledResult = ErrorResult("REQUEST_CANCELLED");
|
|
368
|
+
canceledResult["streamTransport"] = "framed_v1";
|
|
369
|
+
WriteJsonFrame(output, hostToNodeKey, ref outgoingSequence, ResponseJson, requestFrame.RequestId, 0, canceledResult);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
FramedRunOutcome outcome = work.Result;
|
|
373
|
+
byte[] stdout = outcome.Run == null ? new byte[0] : outcome.Run.Stdout;
|
|
374
|
+
byte[] stderr = outcome.Run == null ? new byte[0] : outcome.Run.Stderr;
|
|
375
|
+
if (!WriteFramedOutput(
|
|
376
|
+
input,
|
|
377
|
+
output,
|
|
378
|
+
nodeToHostKey,
|
|
379
|
+
hostToNodeKey,
|
|
380
|
+
ref incomingSequence,
|
|
381
|
+
ref outgoingSequence,
|
|
382
|
+
requestFrame.RequestId,
|
|
383
|
+
stdout,
|
|
384
|
+
stderr,
|
|
385
|
+
cancel,
|
|
386
|
+
ref controlRead))
|
|
387
|
+
{
|
|
388
|
+
Dictionary<string, object> canceledResult = ErrorResult("REQUEST_CANCELLED");
|
|
389
|
+
canceledResult["streamTransport"] = "framed_v1";
|
|
390
|
+
WriteJsonFrame(output, hostToNodeKey, ref outgoingSequence, ResponseJson, requestFrame.RequestId, 0, canceledResult);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
Dictionary<string, object> result = outcome.Run == null
|
|
394
|
+
? ErrorResult(outcome.ErrorCode ?? "HOST_REQUEST_FAILED")
|
|
395
|
+
: RunResult(outcome.Run, false);
|
|
396
|
+
result["streamTransport"] = "framed_v1";
|
|
397
|
+
if (outcome.Run == null)
|
|
398
|
+
{
|
|
399
|
+
result["stdoutTotalBytes"] = 0L;
|
|
400
|
+
result["stderrTotalBytes"] = 0L;
|
|
401
|
+
result["stdoutDroppedBytes"] = 0L;
|
|
402
|
+
result["stderrDroppedBytes"] = 0L;
|
|
403
|
+
result["stdoutTruncated"] = false;
|
|
404
|
+
result["stderrTruncated"] = false;
|
|
405
|
+
}
|
|
406
|
+
WriteJsonFrame(output, hostToNodeKey, ref outgoingSequence, ResponseJson, requestFrame.RequestId, 0, result);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private static byte[] ReadFramedInput(
|
|
411
|
+
Stream input,
|
|
412
|
+
Stream output,
|
|
413
|
+
byte[] nodeToHostKey,
|
|
414
|
+
byte[] hostToNodeKey,
|
|
415
|
+
ref uint incomingSequence,
|
|
416
|
+
ref uint outgoingSequence,
|
|
417
|
+
byte[] requestId,
|
|
418
|
+
int expectedLength)
|
|
419
|
+
{
|
|
420
|
+
using (MemoryStream collected = new MemoryStream(expectedLength))
|
|
421
|
+
{
|
|
422
|
+
bool eof = false;
|
|
423
|
+
while (!eof)
|
|
424
|
+
{
|
|
425
|
+
Frame frame = ReadFrame(input, nodeToHostKey, incomingSequence);
|
|
426
|
+
if (frame == null) throw new EndOfStreamException("TRUNCATED_INPUT");
|
|
427
|
+
IncrementSequence(ref incomingSequence);
|
|
428
|
+
if (frame.Kind != Input || frame.ProcessGeneration != 0 || !RequestIdsEqual(frame.RequestId, requestId)) {
|
|
429
|
+
throw new InvalidDataException("DIRECTION_INVALID_FRAME");
|
|
430
|
+
}
|
|
431
|
+
if (collected.Length + frame.Payload.Length > expectedLength) throw new InvalidDataException("INPUT_LENGTH_MISMATCH");
|
|
432
|
+
if (frame.Payload.Length > 0) collected.Write(frame.Payload, 0, frame.Payload.Length);
|
|
433
|
+
eof = (frame.Flags & FlagEof) != 0;
|
|
434
|
+
if (eof && collected.Length != expectedLength) throw new InvalidDataException("INPUT_LENGTH_MISMATCH");
|
|
435
|
+
WriteCredit(output, hostToNodeKey, ref outgoingSequence, requestId, frame.FrameBytes);
|
|
436
|
+
}
|
|
437
|
+
return collected.ToArray();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private static ProcessRunResult RunFramedOperation(
|
|
442
|
+
string operation,
|
|
443
|
+
Dictionary<string, object> input,
|
|
444
|
+
byte[] streamedInput,
|
|
445
|
+
EventWaitHandle cancel)
|
|
446
|
+
{
|
|
447
|
+
if (operation == "run")
|
|
448
|
+
{
|
|
449
|
+
RequireExactKeys(input, new string[] { "executable", "arguments", "cwd", "environment", "timeoutMs", "stdoutLimitBytes", "stderrLimitBytes" });
|
|
450
|
+
return RunOwnedProcess(
|
|
451
|
+
RequireAbsoluteFile(input["executable"]),
|
|
452
|
+
AsStringArray(input["arguments"], "arguments", MaxArguments, 8192),
|
|
453
|
+
RequireAbsoluteDirectory(input["cwd"]),
|
|
454
|
+
AsEnvironment(input["environment"]),
|
|
455
|
+
streamedInput,
|
|
456
|
+
ToBoundedInt(input["timeoutMs"], "timeoutMs", 1, MaxOneShotTimeoutMs),
|
|
457
|
+
ToBoundedInt(input["stdoutLimitBytes"], "stdoutLimitBytes", 1, MaxFramedOutputBytes),
|
|
458
|
+
ToBoundedInt(input["stderrLimitBytes"], "stderrLimitBytes", 1, MaxFramedOutputBytes),
|
|
459
|
+
cancel);
|
|
460
|
+
}
|
|
461
|
+
if (operation == "run_powershell")
|
|
462
|
+
{
|
|
463
|
+
RequireExactKeys(input, new string[] { "executable", "cwd", "environment", "timeoutMs", "stdoutLimitBytes", "stderrLimitBytes" });
|
|
464
|
+
string script;
|
|
465
|
+
try { script = new UTF8Encoding(false, true).GetString(streamedInput); }
|
|
466
|
+
catch { throw new InvalidDataException("INVALID_SCRIPT_UTF8"); }
|
|
467
|
+
string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(PowerShellBootstrap()));
|
|
468
|
+
return RunOwnedProcess(
|
|
469
|
+
RequireAbsoluteFile(input["executable"]),
|
|
470
|
+
new string[] { "-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded },
|
|
471
|
+
RequireAbsoluteDirectory(input["cwd"]),
|
|
472
|
+
AsEnvironment(input["environment"]),
|
|
473
|
+
Encoding.UTF8.GetBytes(script),
|
|
474
|
+
ToBoundedInt(input["timeoutMs"], "timeoutMs", 1, MaxOneShotTimeoutMs),
|
|
475
|
+
ToBoundedInt(input["stdoutLimitBytes"], "stdoutLimitBytes", 1, MaxFramedOutputBytes),
|
|
476
|
+
ToBoundedInt(input["stderrLimitBytes"], "stderrLimitBytes", 1, MaxFramedOutputBytes),
|
|
477
|
+
cancel);
|
|
478
|
+
}
|
|
479
|
+
throw new InvalidDataException("STREAM_OPERATION_UNSUPPORTED");
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private static bool WriteFramedOutput(
|
|
483
|
+
Stream input,
|
|
484
|
+
Stream output,
|
|
485
|
+
byte[] nodeToHostKey,
|
|
486
|
+
byte[] hostToNodeKey,
|
|
487
|
+
ref uint incomingSequence,
|
|
488
|
+
ref uint outgoingSequence,
|
|
489
|
+
byte[] requestId,
|
|
490
|
+
byte[] stdout,
|
|
491
|
+
byte[] stderr,
|
|
492
|
+
EventWaitHandle cancel,
|
|
493
|
+
ref Task<Frame> controlRead)
|
|
494
|
+
{
|
|
495
|
+
int remainingFrames = OutputFrameCount(stdout) + OutputFrameCount(stderr);
|
|
496
|
+
byte[][] streams = new byte[][] { stdout, stderr };
|
|
497
|
+
for (int streamIndex = 0; streamIndex < streams.Length; streamIndex++)
|
|
498
|
+
{
|
|
499
|
+
byte[] bytes = streams[streamIndex] ?? new byte[0];
|
|
500
|
+
int offset = 0;
|
|
501
|
+
bool sent = false;
|
|
502
|
+
while (!sent || offset < bytes.Length)
|
|
503
|
+
{
|
|
504
|
+
int count = Math.Min(MaxPayload, bytes.Length - offset);
|
|
505
|
+
byte[] payload = new byte[count];
|
|
506
|
+
if (count > 0) Buffer.BlockCopy(bytes, offset, payload, 0, count);
|
|
507
|
+
offset += count;
|
|
508
|
+
sent = true;
|
|
509
|
+
bool eof = offset == bytes.Length;
|
|
510
|
+
ushort flags = (ushort)(streamIndex == 1 ? FlagStderr : 0);
|
|
511
|
+
if (eof) flags = (ushort)(flags | FlagEof);
|
|
512
|
+
WriteFrame(output, hostToNodeKey, outgoingSequence, Output, flags, requestId, 0, payload);
|
|
513
|
+
IncrementSequence(ref outgoingSequence);
|
|
514
|
+
remainingFrames--;
|
|
515
|
+
Frame control = CompleteControlRead(controlRead, ref incomingSequence);
|
|
516
|
+
while (HandleInterleavedRequest(control, output, hostToNodeKey, ref outgoingSequence, requestId))
|
|
517
|
+
{
|
|
518
|
+
controlRead = StartFrameRead(input, nodeToHostKey, incomingSequence);
|
|
519
|
+
control = CompleteControlRead(controlRead, ref incomingSequence);
|
|
520
|
+
}
|
|
521
|
+
ValidateCorrelatedControl(control, requestId);
|
|
522
|
+
if (control.Kind == Cancel)
|
|
523
|
+
{
|
|
524
|
+
cancel.Set();
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
if (control.Kind != Credit || ReadCredit(control.Payload) != HeaderLength + payload.Length) {
|
|
528
|
+
throw new InvalidDataException("INVALID_CREDIT");
|
|
529
|
+
}
|
|
530
|
+
if (remainingFrames > 0) controlRead = StartFrameRead(input, nodeToHostKey, incomingSequence);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private static int OutputFrameCount(byte[] bytes)
|
|
537
|
+
{
|
|
538
|
+
return bytes == null || bytes.Length == 0 ? 1 : (bytes.Length + MaxPayload - 1) / MaxPayload;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private static Task<Frame> StartFrameRead(Stream input, byte[] key, uint expectedSequence)
|
|
542
|
+
{
|
|
543
|
+
return Task.Run(() => ReadFrame(input, key, expectedSequence));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private static Frame CompleteControlRead(Task<Frame> read, ref uint incomingSequence)
|
|
547
|
+
{
|
|
548
|
+
Frame frame = read.Result;
|
|
549
|
+
if (frame == null) throw new EndOfStreamException("TRUNCATED_CONTROL");
|
|
550
|
+
IncrementSequence(ref incomingSequence);
|
|
551
|
+
return frame;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private static void ValidateCorrelatedControl(Frame frame, byte[] requestId)
|
|
555
|
+
{
|
|
556
|
+
if (frame.ProcessGeneration != 0 || !RequestIdsEqual(frame.RequestId, requestId)) throw new InvalidDataException("CONTROL_CORRELATION_INVALID");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private static bool HandleInterleavedRequest(
|
|
560
|
+
Frame frame,
|
|
561
|
+
Stream output,
|
|
562
|
+
byte[] hostToNodeKey,
|
|
563
|
+
ref uint outgoingSequence,
|
|
564
|
+
byte[] activeRequestId)
|
|
565
|
+
{
|
|
566
|
+
if (frame.Kind != RequestJson || RequestIdsEqual(frame.RequestId, activeRequestId)) return false;
|
|
567
|
+
Dictionary<string, object> request = ParseStrictObject(frame.Payload);
|
|
568
|
+
RequireExactKeys(request, new string[] { "schemaVersion", "operation", "input" });
|
|
569
|
+
if (ToInt(request["schemaVersion"], "schemaVersion") != 1) throw new InvalidDataException("REQUEST_VERSION_MISMATCH");
|
|
570
|
+
string operation = RequireBoundedString(request["operation"], "operation", 1, 80);
|
|
571
|
+
Dictionary<string, object> requestInput = AsObject(request["input"], "input");
|
|
572
|
+
Dictionary<string, object> result;
|
|
573
|
+
try { result = Dispatch(operation, requestInput); }
|
|
574
|
+
catch (Exception error) { result = ErrorResult(SafeErrorCode(error)); }
|
|
575
|
+
WriteJsonFrame(output, hostToNodeKey, ref outgoingSequence, ResponseJson, frame.RequestId, 0, result);
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private static bool RequestIdsEqual(byte[] left, byte[] right)
|
|
580
|
+
{
|
|
581
|
+
return left != null && right != null && left.Length == 16 && right.Length == 16 && FixedEquals(left, 0, right, 0, 16);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private static void WriteCredit(Stream output, byte[] key, ref uint sequence, byte[] requestId, int bytes)
|
|
585
|
+
{
|
|
586
|
+
if (bytes < 1) throw new InvalidDataException("INVALID_CREDIT");
|
|
587
|
+
byte[] payload = BitConverter.GetBytes((ulong)bytes);
|
|
588
|
+
WriteFrame(output, key, sequence, Credit, 0, requestId, 0, payload);
|
|
589
|
+
IncrementSequence(ref sequence);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
private static long ReadCredit(byte[] payload)
|
|
593
|
+
{
|
|
594
|
+
if (payload == null || payload.Length != 8) throw new InvalidDataException("INVALID_CREDIT_LENGTH");
|
|
595
|
+
ulong value = BitConverter.ToUInt64(payload, 0);
|
|
596
|
+
if (value == 0 || value > Int64.MaxValue) throw new InvalidDataException("INVALID_CREDIT");
|
|
597
|
+
return (long)value;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private static void IncrementSequence(ref uint sequence)
|
|
601
|
+
{
|
|
602
|
+
sequence++;
|
|
603
|
+
if (sequence == 0) throw new InvalidDataException("SEQUENCE_EXHAUSTED");
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
private static Dictionary<string, object> Dispatch(string operation, Dictionary<string, object> input)
|
|
607
|
+
{
|
|
608
|
+
if (operation == "capabilities")
|
|
609
|
+
{
|
|
610
|
+
RequireExactKeys(input, new string[0]);
|
|
611
|
+
bool parentInJob;
|
|
612
|
+
if (!IsProcessInJob(GetCurrentProcess(), IntPtr.Zero, out parentInJob)) ThrowLastWin32("IS_PROCESS_IN_JOB_FAILED");
|
|
613
|
+
Dictionary<string, object> response = SuccessResult();
|
|
614
|
+
response["platform"] = "win32";
|
|
615
|
+
response["architecture"] = Environment.Is64BitProcess ? "x64" : "x86";
|
|
616
|
+
response["jobListAttribute"] = true;
|
|
617
|
+
response["handleListAttribute"] = true;
|
|
618
|
+
response["killOnJobClose"] = true;
|
|
619
|
+
response["nativeMonotonicDeadline"] = true;
|
|
620
|
+
response["conPtyApiPresent"] = HasProc("kernel32.dll", "CreatePseudoConsole") && HasProc("kernel32.dll", "ResizePseudoConsole") && HasProc("kernel32.dll", "ClosePseudoConsole");
|
|
621
|
+
response["parentInJob"] = parentInJob;
|
|
622
|
+
response["processTreeControl"] = "job_object_members_only";
|
|
623
|
+
response["brokerEscapeResistance"] = "none";
|
|
624
|
+
response["persistentHostChanges"] = false;
|
|
625
|
+
return response;
|
|
626
|
+
}
|
|
627
|
+
if (operation == "run") return RunOperation(input, false);
|
|
628
|
+
if (operation == "run_powershell") return RunOperation(input, true);
|
|
629
|
+
if (operation == "start_persistent") return StartPersistent(input);
|
|
630
|
+
if (operation == "start_conpty_worker") return StartConPtyWorker(input);
|
|
631
|
+
if (operation == "poll_persistent") return PollPersistent(input);
|
|
632
|
+
if (operation == "write_persistent") return WritePersistent(input);
|
|
633
|
+
if (operation == "interrupt_persistent") return InterruptPersistent(input);
|
|
634
|
+
if (operation == "terminate_persistent") return TerminatePersistent(input);
|
|
635
|
+
if (operation == "conpty_probe") return RunConPtyProbe(input, false);
|
|
636
|
+
if (operation == "conpty_close_hang_probe") return RunConPtyProbe(input, true);
|
|
637
|
+
throw new InvalidDataException("UNKNOWN_OPERATION");
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private static Dictionary<string, object> RunOperation(Dictionary<string, object> input, bool powerShell)
|
|
641
|
+
{
|
|
642
|
+
ProcessRunResult run = powerShell ? RunPowerShell(input) : RunExecutable(input);
|
|
643
|
+
return RunResult(run, true);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private static Dictionary<string, object> RunResult(ProcessRunResult run, bool includeBase64)
|
|
647
|
+
{
|
|
648
|
+
Dictionary<string, object> result = new Dictionary<string, object>();
|
|
649
|
+
result["schemaVersion"] = 1;
|
|
650
|
+
result["ok"] = run.Ok;
|
|
651
|
+
result["code"] = run.Code;
|
|
652
|
+
result["exitCode"] = (long)run.ExitCode;
|
|
653
|
+
result["timedOut"] = run.TimedOut;
|
|
654
|
+
result["processId"] = run.ProcessId;
|
|
655
|
+
result["jobAssignedAtCreation"] = run.JobAssignedAtCreation;
|
|
656
|
+
result["exactHandleList"] = run.ExactHandleList;
|
|
657
|
+
result["imageIdentityVerified"] = run.ImageIdentityVerified;
|
|
658
|
+
result["volumeSerial"] = (long)run.VolumeSerial;
|
|
659
|
+
result["fileIndex"] = run.FileIndex.ToString();
|
|
660
|
+
result["numberOfLinks"] = (long)run.NumberOfLinks;
|
|
661
|
+
if (includeBase64)
|
|
662
|
+
{
|
|
663
|
+
result["stdoutBase64"] = Convert.ToBase64String(run.Stdout ?? new byte[0]);
|
|
664
|
+
result["stderrBase64"] = Convert.ToBase64String(run.Stderr ?? new byte[0]);
|
|
665
|
+
}
|
|
666
|
+
result["stdoutTotalBytes"] = run.StdoutTotalBytes;
|
|
667
|
+
result["stderrTotalBytes"] = run.StderrTotalBytes;
|
|
668
|
+
result["stdoutDroppedBytes"] = run.StdoutDroppedBytes;
|
|
669
|
+
result["stderrDroppedBytes"] = run.StderrDroppedBytes;
|
|
670
|
+
result["stdoutTruncated"] = run.StdoutTruncated;
|
|
671
|
+
result["stderrTruncated"] = run.StderrTruncated;
|
|
672
|
+
result["elapsedMilliseconds"] = run.ElapsedMilliseconds;
|
|
673
|
+
result["processTreeControl"] = "job_object_members_only";
|
|
674
|
+
result["brokerEscapeResistance"] = "none";
|
|
675
|
+
return result;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private static ProcessRunResult RunPowerShell(Dictionary<string, object> input)
|
|
679
|
+
{
|
|
680
|
+
RequireExactKeys(input, new string[] { "executable", "script", "cwd", "environment", "timeoutMs", "stdoutLimitBytes", "stderrLimitBytes" });
|
|
681
|
+
string script = RequireBoundedString(input["script"], "script", 0, 65536);
|
|
682
|
+
string executable = RequireAbsoluteFile(input["executable"]);
|
|
683
|
+
string bootstrap = "$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue';[Console]::InputEncoding=New-Object Text.UTF8Encoding($false);[Console]::OutputEncoding=New-Object Text.UTF8Encoding($false);$t=[Console]::In.ReadToEnd();$global:LASTEXITCODE=$null;$f=';$__cxp4_ok=$?;$__cxp4_native=$LASTEXITCODE;if($null -ne $__cxp4_native){exit $__cxp4_native};if($__cxp4_ok){exit 0}else{exit 1}';try{$s=[ScriptBlock]::Create($t+[Environment]::NewLine+$f);. $s;exit 0}catch{exit 1}";
|
|
684
|
+
string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(bootstrap));
|
|
685
|
+
string[] arguments = new string[] { "-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded };
|
|
686
|
+
return RunOwnedProcess(
|
|
687
|
+
executable,
|
|
688
|
+
arguments,
|
|
689
|
+
RequireAbsoluteDirectory(input["cwd"]),
|
|
690
|
+
AsEnvironment(input["environment"]),
|
|
691
|
+
Encoding.UTF8.GetBytes(script),
|
|
692
|
+
ToBoundedInt(input["timeoutMs"], "timeoutMs", 1, MaxOneShotTimeoutMs),
|
|
693
|
+
ToBoundedInt(input["stdoutLimitBytes"], "stdoutLimitBytes", 1, 1048576),
|
|
694
|
+
ToBoundedInt(input["stderrLimitBytes"], "stderrLimitBytes", 1, 1048576));
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
private static string PowerShellBootstrap()
|
|
698
|
+
{
|
|
699
|
+
return "$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue';[Console]::InputEncoding=New-Object Text.UTF8Encoding($false);[Console]::OutputEncoding=New-Object Text.UTF8Encoding($false);$t=[Console]::In.ReadToEnd();$global:LASTEXITCODE=$null;$f=';$__cxp4_ok=$?;$__cxp4_native=$LASTEXITCODE;if($null -ne $__cxp4_native){exit $__cxp4_native};if($__cxp4_ok){exit 0}else{exit 1}';try{$s=[ScriptBlock]::Create($t+[Environment]::NewLine+$f);. $s;exit 0}catch{exit 1}";
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private static string NewPersistentHandle()
|
|
703
|
+
{
|
|
704
|
+
byte[] bytes = new byte[16];
|
|
705
|
+
using (RandomNumberGenerator random = RandomNumberGenerator.Create()) random.GetBytes(bytes);
|
|
706
|
+
StringBuilder value = new StringBuilder("native_");
|
|
707
|
+
for (int index = 0; index < bytes.Length; index++) value.Append(bytes[index].ToString("x2"));
|
|
708
|
+
return value.ToString();
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private static Dictionary<string, object> StartPersistent(Dictionary<string, object> input)
|
|
712
|
+
{
|
|
713
|
+
string operation = RequireBoundedString(input["commandOperation"], "commandOperation", 1, 32);
|
|
714
|
+
string executable;
|
|
715
|
+
string[] arguments;
|
|
716
|
+
byte[] initialInput;
|
|
717
|
+
if (operation == "run_powershell")
|
|
718
|
+
{
|
|
719
|
+
RequireExactKeys(input, new string[] { "commandOperation", "executable", "script", "cwd", "environment", "timeoutMs", "stdoutLimitBytes", "stderrLimitBytes", "lifetimeMs" });
|
|
720
|
+
string script = RequireBoundedString(input["script"], "script", 0, 65536);
|
|
721
|
+
executable = RequireAbsoluteFile(input["executable"]);
|
|
722
|
+
string encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(PowerShellBootstrap()));
|
|
723
|
+
arguments = new string[] { "-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded };
|
|
724
|
+
initialInput = Encoding.UTF8.GetBytes(script);
|
|
725
|
+
}
|
|
726
|
+
else if (operation == "run")
|
|
727
|
+
{
|
|
728
|
+
RequireExactKeys(input, new string[] { "commandOperation", "executable", "arguments", "cwd", "environment", "stdinBase64", "timeoutMs", "stdoutLimitBytes", "stderrLimitBytes", "lifetimeMs" });
|
|
729
|
+
executable = RequireAbsoluteFile(input["executable"]);
|
|
730
|
+
arguments = AsStringArray(input["arguments"], "arguments", 512, 8192);
|
|
731
|
+
try { initialInput = Convert.FromBase64String(RequireBoundedString(input["stdinBase64"], "stdinBase64", 0, 131072)); }
|
|
732
|
+
catch { throw new InvalidDataException("INVALID_STDIN_BASE64"); }
|
|
733
|
+
}
|
|
734
|
+
else throw new InvalidDataException("PERSISTENT_COMMAND_INVALID");
|
|
735
|
+
PersistentProcessState state = StartPersistentOwnedProcess(executable, arguments, RequireAbsoluteDirectory(input["cwd"]), AsEnvironment(input["environment"]), initialInput, operation == "run_powershell", ToBoundedInt(input["lifetimeMs"], "lifetimeMs", 1, 7200000));
|
|
736
|
+
if (!PersistentProcesses.TryAdd(state.Handle, state)) { ClosePersistentState(state, "host_collision"); throw new InvalidDataException("PERSISTENT_HANDLE_COLLISION"); }
|
|
737
|
+
Dictionary<string, object> result = SuccessResult();
|
|
738
|
+
result["processHandle"] = state.Handle;
|
|
739
|
+
result["jobAssignedAtCreation"] = true;
|
|
740
|
+
result["exactHandleList"] = true;
|
|
741
|
+
result["processTreeControl"] = "job_object_members_only";
|
|
742
|
+
result["brokerEscapeResistance"] = "none";
|
|
743
|
+
return result;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private static Dictionary<string, object> StartConPtyWorker(Dictionary<string, object> input)
|
|
747
|
+
{
|
|
748
|
+
RequireExactKeys(input, new string[] { "workerInputBase64", "lifetimeMs" });
|
|
749
|
+
if (!HasProc("kernel32.dll", "CreatePseudoConsole") || !HasProc("kernel32.dll", "ResizePseudoConsole") || !HasProc("kernel32.dll", "ClosePseudoConsole"))
|
|
750
|
+
return ErrorResult("CONPTY_UNAVAILABLE");
|
|
751
|
+
byte[] workerInput;
|
|
752
|
+
try { workerInput = Convert.FromBase64String(RequireBoundedString(input["workerInputBase64"], "workerInputBase64", 1, 131072)); }
|
|
753
|
+
catch { throw new InvalidDataException("INVALID_INPUT_BASE64"); }
|
|
754
|
+
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? "C:\\Windows";
|
|
755
|
+
string executable = Path.Combine(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
756
|
+
string workerScript = Path.Combine(Directory.GetCurrentDirectory(), "scripts", "windows-conpty-worker.ps1");
|
|
757
|
+
if (!File.Exists(workerScript)) return ErrorResult("CONPTY_WORKER_MISSING");
|
|
758
|
+
string[] arguments = new string[] { "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", workerScript, "-Persistent" };
|
|
759
|
+
PersistentProcessState state = StartPersistentOwnedProcess(
|
|
760
|
+
executable,
|
|
761
|
+
arguments,
|
|
762
|
+
Directory.GetCurrentDirectory(),
|
|
763
|
+
AsEnvironment(new Dictionary<string, object>()),
|
|
764
|
+
workerInput,
|
|
765
|
+
false,
|
|
766
|
+
ToBoundedInt(input["lifetimeMs"], "lifetimeMs", 1, 7200000));
|
|
767
|
+
if (!PersistentProcesses.TryAdd(state.Handle, state)) { ClosePersistentState(state, "host_collision"); throw new InvalidDataException("PERSISTENT_HANDLE_COLLISION"); }
|
|
768
|
+
Dictionary<string, object> result = SuccessResult();
|
|
769
|
+
result["processHandle"] = state.Handle;
|
|
770
|
+
result["jobAssignedAtCreation"] = true;
|
|
771
|
+
result["exactHandleList"] = true;
|
|
772
|
+
result["processTreeControl"] = "job_object_members_only";
|
|
773
|
+
result["brokerEscapeResistance"] = "none";
|
|
774
|
+
result["isolatedConPtyWorker"] = true;
|
|
775
|
+
return result;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
private static PersistentProcessState RequirePersistent(Dictionary<string, object> input, string[] keys)
|
|
779
|
+
{
|
|
780
|
+
RequireExactKeys(input, keys);
|
|
781
|
+
string handle = RequireBoundedString(input["processHandle"], "processHandle", 39, 39);
|
|
782
|
+
PersistentProcessState state;
|
|
783
|
+
if (!PersistentProcesses.TryGetValue(handle, out state)) throw new InvalidDataException("PROCESS_NOT_FOUND");
|
|
784
|
+
return state;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private static Dictionary<string, object> PollPersistent(Dictionary<string, object> input)
|
|
788
|
+
{
|
|
789
|
+
PersistentProcessState state = RequirePersistent(input, new string[] { "processHandle", "waitMs" });
|
|
790
|
+
int wait = ToBoundedInt(input["waitMs"], "waitMs", 0, 1000);
|
|
791
|
+
if (wait > 0) state.Changed.Wait(wait);
|
|
792
|
+
byte[] stdout;
|
|
793
|
+
byte[] stderr;
|
|
794
|
+
bool running;
|
|
795
|
+
uint exitCode;
|
|
796
|
+
string reason;
|
|
797
|
+
lock (state.Sync)
|
|
798
|
+
{
|
|
799
|
+
stdout = state.Stdout.ToArray(); state.Stdout.SetLength(0);
|
|
800
|
+
stderr = state.Stderr.ToArray(); state.Stderr.SetLength(0);
|
|
801
|
+
running = state.Running || !state.StdoutClosed || !state.StderrClosed; exitCode = state.ExitCode; reason = state.Reason; state.Changed.Reset();
|
|
802
|
+
}
|
|
803
|
+
Dictionary<string, object> result = SuccessResult();
|
|
804
|
+
result["running"] = running;
|
|
805
|
+
result["exitCode"] = running ? null : (object)(long)exitCode;
|
|
806
|
+
result["reason"] = reason;
|
|
807
|
+
result["stdoutBase64"] = Convert.ToBase64String(stdout);
|
|
808
|
+
result["stderrBase64"] = Convert.ToBase64String(stderr);
|
|
809
|
+
if (!running)
|
|
810
|
+
{
|
|
811
|
+
PersistentProcessState removed;
|
|
812
|
+
if (PersistentProcesses.TryRemove(state.Handle, out removed)) DisposePersistentState(removed);
|
|
813
|
+
}
|
|
814
|
+
return result;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
private static Dictionary<string, object> WritePersistent(Dictionary<string, object> input)
|
|
818
|
+
{
|
|
819
|
+
PersistentProcessState state = RequirePersistent(input, new string[] { "processHandle", "dataBase64", "close" });
|
|
820
|
+
byte[] data;
|
|
821
|
+
try { data = Convert.FromBase64String(RequireBoundedString(input["dataBase64"], "dataBase64", 0, 131072)); }
|
|
822
|
+
catch { throw new InvalidDataException("INVALID_INPUT_BASE64"); }
|
|
823
|
+
if (!(input["close"] is bool)) throw new InvalidDataException("INVALID_close");
|
|
824
|
+
bool close = (bool)input["close"];
|
|
825
|
+
lock (state.Sync)
|
|
826
|
+
{
|
|
827
|
+
if (!state.Running || state.StdinClosed) throw new InvalidDataException("PROCESS_NOT_FOUND");
|
|
828
|
+
if (data.Length > 0) { state.Stdin.Write(data, 0, data.Length); state.Stdin.Flush(); }
|
|
829
|
+
if (close) { state.Stdin.Dispose(); state.StdinClosed = true; }
|
|
830
|
+
}
|
|
831
|
+
return SuccessResult();
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
private static Dictionary<string, object> InterruptPersistent(Dictionary<string, object> input)
|
|
835
|
+
{
|
|
836
|
+
PersistentProcessState state = RequirePersistent(input, new string[] { "processHandle" });
|
|
837
|
+
Dictionary<string, object> result = SuccessResult();
|
|
838
|
+
result["delivered"] = false;
|
|
839
|
+
result["code"] = "INTERRUPT_UNSUPPORTED";
|
|
840
|
+
return result;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
private static Dictionary<string, object> TerminatePersistent(Dictionary<string, object> input)
|
|
844
|
+
{
|
|
845
|
+
PersistentProcessState state = RequirePersistent(input, new string[] { "processHandle" });
|
|
846
|
+
ClosePersistentState(state, "user_terminated");
|
|
847
|
+
PersistentProcessState removed;
|
|
848
|
+
if (PersistentProcesses.TryRemove(state.Handle, out removed)) DisposePersistentState(removed);
|
|
849
|
+
Dictionary<string, object> result = SuccessResult(); result["changed"] = true; return result;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
private static void AppendPersistent(PersistentProcessState state, bool stderr, byte[] buffer, int count)
|
|
853
|
+
{
|
|
854
|
+
lock (state.Sync)
|
|
855
|
+
{
|
|
856
|
+
MemoryStream target = stderr ? state.Stderr : state.Stdout;
|
|
857
|
+
if (target.Length + count > 1048576) { ClosePersistentState(state, "output_limit_exceeded"); return; }
|
|
858
|
+
target.Write(buffer, 0, count); state.Changed.Set();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private static void ReadPersistent(PersistentProcessState state, Stream stream, bool stderr)
|
|
863
|
+
{
|
|
864
|
+
using (stream)
|
|
865
|
+
{
|
|
866
|
+
byte[] buffer = new byte[8192];
|
|
867
|
+
while (true) { int count; try { count = stream.Read(buffer, 0, buffer.Length); } catch { break; } if (count <= 0) break; AppendPersistent(state, stderr, buffer, count); }
|
|
868
|
+
}
|
|
869
|
+
lock (state.Sync)
|
|
870
|
+
{
|
|
871
|
+
if (stderr) state.StderrClosed = true; else state.StdoutClosed = true;
|
|
872
|
+
state.Changed.Set();
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
private static void ClosePersistentState(PersistentProcessState state, string reason)
|
|
877
|
+
{
|
|
878
|
+
lock (state.Sync)
|
|
879
|
+
{
|
|
880
|
+
if (!state.Running) return;
|
|
881
|
+
state.Reason = reason;
|
|
882
|
+
HostLifetime.ReleaseJob(state.JobRegistration, ref state.JobHandle);
|
|
883
|
+
WaitForSingleObject(state.ProcessHandle, 10000);
|
|
884
|
+
uint exitCode; if (GetExitCodeProcess(state.ProcessHandle, out exitCode)) state.ExitCode = exitCode;
|
|
885
|
+
state.Running = false;
|
|
886
|
+
try { if (!state.StdinClosed) state.Stdin.Dispose(); } catch { }
|
|
887
|
+
state.StdinClosed = true; state.Changed.Set();
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
private static void DisposePersistentState(PersistentProcessState state)
|
|
892
|
+
{
|
|
893
|
+
ClosePersistentState(state, state.Reason == "running" ? "cleanup" : state.Reason);
|
|
894
|
+
if (state.ProcessHandle != IntPtr.Zero) { CloseHandle(state.ProcessHandle); state.ProcessHandle = IntPtr.Zero; }
|
|
895
|
+
try { state.Stdin.Dispose(); } catch { }
|
|
896
|
+
state.Changed.Dispose(); state.Stdout.Dispose(); state.Stderr.Dispose();
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
private static ProcessRunResult RunExecutable(Dictionary<string, object> input)
|
|
900
|
+
{
|
|
901
|
+
RequireExactKeys(input, new string[] { "executable", "arguments", "cwd", "environment", "stdinBase64", "timeoutMs", "stdoutLimitBytes", "stderrLimitBytes" });
|
|
902
|
+
string executable = RequireAbsoluteFile(input["executable"]);
|
|
903
|
+
string[] arguments = AsStringArray(input["arguments"], "arguments", MaxArguments, 8192);
|
|
904
|
+
byte[] stdin;
|
|
905
|
+
try { stdin = Convert.FromBase64String(RequireBoundedString(input["stdinBase64"], "stdinBase64", 0, 131072)); }
|
|
906
|
+
catch { throw new InvalidDataException("INVALID_STDIN_BASE64"); }
|
|
907
|
+
return RunOwnedProcess(
|
|
908
|
+
executable,
|
|
909
|
+
arguments,
|
|
910
|
+
RequireAbsoluteDirectory(input["cwd"]),
|
|
911
|
+
AsEnvironment(input["environment"]),
|
|
912
|
+
stdin,
|
|
913
|
+
ToBoundedInt(input["timeoutMs"], "timeoutMs", 1, MaxOneShotTimeoutMs),
|
|
914
|
+
ToBoundedInt(input["stdoutLimitBytes"], "stdoutLimitBytes", 1, 1048576),
|
|
915
|
+
ToBoundedInt(input["stderrLimitBytes"], "stderrLimitBytes", 1, 1048576));
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
private static Dictionary<string, object> RunConPtyProbe(Dictionary<string, object> input, bool simulateCloseHang)
|
|
919
|
+
{
|
|
920
|
+
RequireExactKeys(input, new string[] { "nodeExecutable", "probeScript" });
|
|
921
|
+
string nodeExecutable = RequireAbsoluteFile(input["nodeExecutable"]);
|
|
922
|
+
string probeScript = RequireAbsoluteFile(input["probeScript"]);
|
|
923
|
+
string expectedProbe = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "scripts", "windows-conpty-probe-child.mjs"));
|
|
924
|
+
if (!String.Equals(probeScript, expectedProbe, StringComparison.OrdinalIgnoreCase)) throw new InvalidDataException("CONPTY_PROBE_IDENTITY_MISMATCH");
|
|
925
|
+
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? "C:\\Windows";
|
|
926
|
+
string executable = Path.Combine(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
927
|
+
string workerScript = Path.Combine(Directory.GetCurrentDirectory(), "scripts", "windows-conpty-worker.ps1");
|
|
928
|
+
if (!File.Exists(workerScript)) return ErrorResult("CONPTY_WORKER_MISSING");
|
|
929
|
+
string[] workerArguments = simulateCloseHang
|
|
930
|
+
? new string[] { "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", workerScript, "-SimulateCloseHang", "-NodeExecutable", nodeExecutable, "-ProbeScript", probeScript }
|
|
931
|
+
: new string[] { "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", workerScript, "-NodeExecutable", nodeExecutable, "-ProbeScript", probeScript };
|
|
932
|
+
ProcessRunResult worker = RunOwnedProcess(
|
|
933
|
+
executable,
|
|
934
|
+
workerArguments,
|
|
935
|
+
Directory.GetCurrentDirectory(),
|
|
936
|
+
AsEnvironment(new Dictionary<string, object>()),
|
|
937
|
+
new byte[0],
|
|
938
|
+
60000,
|
|
939
|
+
32768,
|
|
940
|
+
4096);
|
|
941
|
+
if (simulateCloseHang && !worker.Ok && worker.StdoutTotalBytes == 0)
|
|
942
|
+
{
|
|
943
|
+
Dictionary<string, object> isolatedFailure = ErrorResult(worker.TimedOut ? "CONPTY_WORKER_TIMED_OUT" : "HOST_FATAL_CONPTY_CLOSE");
|
|
944
|
+
isolatedFailure["workerExitCode"] = (long)worker.ExitCode;
|
|
945
|
+
isolatedFailure["workerTimedOut"] = worker.TimedOut;
|
|
946
|
+
isolatedFailure["workerJobAssignedAtCreation"] = worker.JobAssignedAtCreation;
|
|
947
|
+
isolatedFailure["workerExactHandleList"] = worker.ExactHandleList;
|
|
948
|
+
isolatedFailure["workerImageIdentityVerified"] = worker.ImageIdentityVerified;
|
|
949
|
+
isolatedFailure["workerElapsedMilliseconds"] = worker.ElapsedMilliseconds;
|
|
950
|
+
return isolatedFailure;
|
|
951
|
+
}
|
|
952
|
+
Dictionary<string, object> result;
|
|
953
|
+
try { result = ParseStrictObject(worker.Stdout ?? new byte[0]); }
|
|
954
|
+
catch
|
|
955
|
+
{
|
|
956
|
+
Dictionary<string, object> invalid = ErrorResult("CONPTY_WORKER_INVALID_RESPONSE");
|
|
957
|
+
invalid["workerStdoutBytes"] = worker.StdoutTotalBytes;
|
|
958
|
+
invalid["workerStderrBytes"] = worker.StderrTotalBytes;
|
|
959
|
+
return invalid;
|
|
960
|
+
}
|
|
961
|
+
if (!worker.Ok)
|
|
962
|
+
{
|
|
963
|
+
Dictionary<string, object> failed = ErrorResult("CONPTY_WORKER_FAILED");
|
|
964
|
+
failed["workerCode"] = result.ContainsKey("code") ? Convert.ToString(result["code"]) : "CONPTY_WORKER_ERROR";
|
|
965
|
+
failed["workerExitCode"] = (long)worker.ExitCode;
|
|
966
|
+
failed["workerTimedOut"] = worker.TimedOut;
|
|
967
|
+
failed["workerJobAssignedAtCreation"] = worker.JobAssignedAtCreation;
|
|
968
|
+
failed["workerExactHandleList"] = worker.ExactHandleList;
|
|
969
|
+
failed["workerImageIdentityVerified"] = worker.ImageIdentityVerified;
|
|
970
|
+
if (result.ContainsKey("conPtyCreated")) failed["conPtyCreated"] = result["conPtyCreated"];
|
|
971
|
+
if (result.ContainsKey("resized")) failed["resized"] = result["resized"];
|
|
972
|
+
if (result.ContainsKey("etxDelivered")) failed["etxDelivered"] = result["etxDelivered"];
|
|
973
|
+
if (result.ContainsKey("outputContainsReady")) failed["outputContainsReady"] = result["outputContainsReady"];
|
|
974
|
+
if (result.ContainsKey("outputContainsInputAck")) failed["outputContainsInputAck"] = result["outputContainsInputAck"];
|
|
975
|
+
if (result.ContainsKey("outputContainsEtxAck")) failed["outputContainsEtxAck"] = result["outputContainsEtxAck"];
|
|
976
|
+
if (result.ContainsKey("exitCode")) failed["exitCode"] = result["exitCode"];
|
|
977
|
+
if (result.ContainsKey("timedOut")) failed["timedOut"] = result["timedOut"];
|
|
978
|
+
if (result.ContainsKey("workerInOwnedJob")) failed["workerInOwnedJob"] = result["workerInOwnedJob"];
|
|
979
|
+
if (result.ContainsKey("targetInInheritedJobAtCreation")) failed["targetInInheritedJobAtCreation"] = result["targetInInheritedJobAtCreation"];
|
|
980
|
+
if (result.ContainsKey("closeDurationMs")) failed["closeDurationMs"] = result["closeDurationMs"];
|
|
981
|
+
if (result.ContainsKey("closeDeadlineMs")) failed["closeDeadlineMs"] = result["closeDeadlineMs"];
|
|
982
|
+
return failed;
|
|
983
|
+
}
|
|
984
|
+
result["workerJobAssignedAtCreation"] = worker.JobAssignedAtCreation;
|
|
985
|
+
result["workerExactHandleList"] = worker.ExactHandleList;
|
|
986
|
+
result["workerImageIdentityVerified"] = worker.ImageIdentityVerified;
|
|
987
|
+
result["workerVolumeSerial"] = (long)worker.VolumeSerial;
|
|
988
|
+
result["workerFileIndex"] = worker.FileIndex.ToString();
|
|
989
|
+
result["workerElapsedMilliseconds"] = worker.ElapsedMilliseconds;
|
|
990
|
+
result["jobOwnershipMode"] = "job_list_worker_inheritance_before_resume";
|
|
991
|
+
return result;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
public static void RunConPtyWorker(bool simulateCloseHang, string nodeExecutable, string probeScript)
|
|
995
|
+
{
|
|
996
|
+
Dictionary<string, object> result;
|
|
997
|
+
try { result = RunConPtyWorkerCore(simulateCloseHang, nodeExecutable, probeScript); }
|
|
998
|
+
catch (Exception error) { result = ErrorResult(SafeErrorCode(error)); }
|
|
999
|
+
Console.Out.WriteLine(Json.Serialize(result));
|
|
1000
|
+
Console.Out.Flush();
|
|
1001
|
+
Environment.ExitCode = result.ContainsKey("ok") && Convert.ToBoolean(result["ok"]) ? 0 : 1;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
private static readonly object ConPtyWorkerOutputSync = new object();
|
|
1005
|
+
|
|
1006
|
+
private static void EmitConPtyWorkerEvent(Dictionary<string, object> value)
|
|
1007
|
+
{
|
|
1008
|
+
lock (ConPtyWorkerOutputSync)
|
|
1009
|
+
{
|
|
1010
|
+
Console.Out.WriteLine(Json.Serialize(value));
|
|
1011
|
+
Console.Out.Flush();
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
private static void EmitConPtyWorkerResponse(string requestId, bool ok, string code)
|
|
1016
|
+
{
|
|
1017
|
+
Dictionary<string, object> response = new Dictionary<string, object>();
|
|
1018
|
+
response["schemaVersion"] = 1;
|
|
1019
|
+
response["type"] = "response";
|
|
1020
|
+
response["requestId"] = requestId;
|
|
1021
|
+
response["ok"] = ok;
|
|
1022
|
+
response["code"] = code;
|
|
1023
|
+
EmitConPtyWorkerEvent(response);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
public static void RunConPtyPersistentWorker()
|
|
1027
|
+
{
|
|
1028
|
+
try
|
|
1029
|
+
{
|
|
1030
|
+
RunConPtyPersistentWorkerCore();
|
|
1031
|
+
Environment.ExitCode = 0;
|
|
1032
|
+
}
|
|
1033
|
+
catch (Exception error)
|
|
1034
|
+
{
|
|
1035
|
+
Dictionary<string, object> failed = new Dictionary<string, object>();
|
|
1036
|
+
failed["schemaVersion"] = 1;
|
|
1037
|
+
failed["type"] = "exit";
|
|
1038
|
+
failed["exitCode"] = null;
|
|
1039
|
+
failed["reason"] = "worker_failed";
|
|
1040
|
+
failed["code"] = SafeErrorCode(error);
|
|
1041
|
+
EmitConPtyWorkerEvent(failed);
|
|
1042
|
+
Environment.ExitCode = 1;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
private static void RunConPtyPersistentWorkerCore()
|
|
1047
|
+
{
|
|
1048
|
+
if (!HasProc("kernel32.dll", "CreatePseudoConsole") || !HasProc("kernel32.dll", "ResizePseudoConsole") || !HasProc("kernel32.dll", "ClosePseudoConsole"))
|
|
1049
|
+
throw new InvalidDataException("CONPTY_UNAVAILABLE");
|
|
1050
|
+
StreamReader protocolInput = new StreamReader(Console.OpenStandardInput(), new UTF8Encoding(false), false, 4096, true);
|
|
1051
|
+
string firstLine = protocolInput.ReadLine();
|
|
1052
|
+
if (String.IsNullOrEmpty(firstLine) || Encoding.UTF8.GetByteCount(firstLine) > 131072) throw new InvalidDataException("CONPTY_WORKER_START_INVALID");
|
|
1053
|
+
Dictionary<string, object> start = ParseStrictObject(Encoding.UTF8.GetBytes(firstLine));
|
|
1054
|
+
RequireExactKeys(start, new string[] { "schemaVersion", "operation", "executable", "arguments", "cwd", "environment", "initialInputBase64", "columns", "rows", "controlPipe", "controlKey" });
|
|
1055
|
+
if (ToInt(start["schemaVersion"], "schemaVersion") != 1 || RequireBoundedString(start["operation"], "operation", 5, 5) != "start")
|
|
1056
|
+
throw new InvalidDataException("CONPTY_WORKER_START_INVALID");
|
|
1057
|
+
string executable = RequireAbsoluteFile(start["executable"]);
|
|
1058
|
+
string[] arguments = AsStringArray(start["arguments"], "arguments", 512, 8192);
|
|
1059
|
+
string cwd = RequireAbsoluteDirectory(start["cwd"]);
|
|
1060
|
+
SortedDictionary<string, string> environment = AsEnvironment(start["environment"]);
|
|
1061
|
+
byte[] initialInput;
|
|
1062
|
+
try { initialInput = Convert.FromBase64String(RequireBoundedString(start["initialInputBase64"], "initialInputBase64", 0, 131072)); }
|
|
1063
|
+
catch { throw new InvalidDataException("INVALID_INPUT_BASE64"); }
|
|
1064
|
+
short columns = checked((short)ToBoundedInt(start["columns"], "columns", 1, 500));
|
|
1065
|
+
short rows = checked((short)ToBoundedInt(start["rows"], "rows", 1, 500));
|
|
1066
|
+
string controlPipeName = RequireSafeNonce(start["controlPipe"]);
|
|
1067
|
+
string controlKey = RequireSafeNonce(start["controlKey"]);
|
|
1068
|
+
|
|
1069
|
+
IntPtr pseudoConsole = IntPtr.Zero;
|
|
1070
|
+
SafeFileHandle inputRead = null;
|
|
1071
|
+
SafeFileHandle inputWrite = null;
|
|
1072
|
+
SafeFileHandle outputRead = null;
|
|
1073
|
+
SafeFileHandle outputWrite = null;
|
|
1074
|
+
IntPtr attributeList = IntPtr.Zero;
|
|
1075
|
+
IntPtr environmentPointer = IntPtr.Zero;
|
|
1076
|
+
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
|
|
1077
|
+
FileStream terminalInput = null;
|
|
1078
|
+
Task outputTask = null;
|
|
1079
|
+
BlockingCollection<string> commands = new BlockingCollection<string>(64);
|
|
1080
|
+
NamedPipeServerStream controlPipe = null;
|
|
1081
|
+
try
|
|
1082
|
+
{
|
|
1083
|
+
bool workerInJob;
|
|
1084
|
+
if (!IsProcessInJob(GetCurrentProcess(), IntPtr.Zero, out workerInJob)) ThrowLastWin32("CONPTY_WORKER_JOB_QUERY_FAILED");
|
|
1085
|
+
if (!workerInJob) throw new InvalidDataException("CONPTY_WORKER_NOT_JOB_OWNED");
|
|
1086
|
+
controlPipe = new NamedPipeServerStream(controlPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 4096, 4096);
|
|
1087
|
+
Dictionary<string, object> controlReady = new Dictionary<string, object>();
|
|
1088
|
+
controlReady["schemaVersion"] = 1; controlReady["type"] = "control_ready"; controlReady["controlPipe"] = controlPipeName;
|
|
1089
|
+
EmitConPtyWorkerEvent(controlReady);
|
|
1090
|
+
controlPipe.WaitForConnection();
|
|
1091
|
+
if (!CreateConPtyPipe(out inputRead, out inputWrite, IntPtr.Zero, 0)) ThrowLastWin32("CONPTY_INPUT_PIPE_FAILED");
|
|
1092
|
+
if (!CreateConPtyPipe(out outputRead, out outputWrite, IntPtr.Zero, 0)) ThrowLastWin32("CONPTY_OUTPUT_PIPE_FAILED");
|
|
1093
|
+
COORD initialSize = new COORD { X = columns, Y = rows };
|
|
1094
|
+
int createResult = CreatePseudoConsoleSafe(initialSize, inputRead, outputWrite, 0, out pseudoConsole);
|
|
1095
|
+
if (createResult < 0 || pseudoConsole == IntPtr.Zero) throw new InvalidDataException("CONPTY_CREATE_FAILED");
|
|
1096
|
+
|
|
1097
|
+
IntPtr attributeBytes = IntPtr.Zero;
|
|
1098
|
+
InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref attributeBytes);
|
|
1099
|
+
if (attributeBytes == IntPtr.Zero) ThrowLastWin32("CONPTY_ATTRIBUTE_SIZE_FAILED");
|
|
1100
|
+
attributeList = Marshal.AllocHGlobal(attributeBytes);
|
|
1101
|
+
if (!InitializeProcThreadAttributeList(attributeList, 1, 0, ref attributeBytes)) ThrowLastWin32("CONPTY_ATTRIBUTE_INIT_FAILED");
|
|
1102
|
+
if (!UpdateProcThreadAttribute(attributeList, 0, ProcThreadAttributePseudoConsole, pseudoConsole, new IntPtr(IntPtr.Size), IntPtr.Zero, IntPtr.Zero))
|
|
1103
|
+
ThrowLastWin32("CONPTY_ATTRIBUTE_FAILED");
|
|
1104
|
+
|
|
1105
|
+
STARTUPINFOEX startup = new STARTUPINFOEX();
|
|
1106
|
+
startup.StartupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFOEX));
|
|
1107
|
+
startup.lpAttributeList = attributeList;
|
|
1108
|
+
SECURITY_ATTRIBUTES processAttributes = new SECURITY_ATTRIBUTES(); processAttributes.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
|
|
1109
|
+
SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES(); threadAttributes.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
|
|
1110
|
+
byte[] environmentBytes = Encoding.Unicode.GetBytes(BuildEnvironmentBlock(environment));
|
|
1111
|
+
environmentPointer = Marshal.AllocHGlobal(environmentBytes.Length);
|
|
1112
|
+
Marshal.Copy(environmentBytes, 0, environmentPointer, environmentBytes.Length);
|
|
1113
|
+
string commandLine = BuildCommandLine(executable, arguments);
|
|
1114
|
+
IntPtr savedStdInput = GetStdHandle(StdInputHandle);
|
|
1115
|
+
IntPtr savedStdOutput = GetStdHandle(StdOutputHandle);
|
|
1116
|
+
IntPtr savedStdError = GetStdHandle(StdErrorHandle);
|
|
1117
|
+
bool created;
|
|
1118
|
+
if (!SetStdHandle(StdInputHandle, IntPtr.Zero) ||
|
|
1119
|
+
!SetStdHandle(StdOutputHandle, IntPtr.Zero) ||
|
|
1120
|
+
!SetStdHandle(StdErrorHandle, IntPtr.Zero))
|
|
1121
|
+
ThrowLastWin32("CONPTY_STANDARD_HANDLE_DETACH_FAILED");
|
|
1122
|
+
try
|
|
1123
|
+
{
|
|
1124
|
+
created = CreateConPtyProcessW(executable, commandLine, ref processAttributes, ref threadAttributes, false, ExtendedStartupInfoPresent | CreateUnicodeEnvironment, environmentPointer, cwd, ref startup, out processInfo);
|
|
1125
|
+
}
|
|
1126
|
+
finally
|
|
1127
|
+
{
|
|
1128
|
+
SetStdHandle(StdInputHandle, savedStdInput);
|
|
1129
|
+
SetStdHandle(StdOutputHandle, savedStdOutput);
|
|
1130
|
+
SetStdHandle(StdErrorHandle, savedStdError);
|
|
1131
|
+
}
|
|
1132
|
+
if (!created) ThrowLastWin32("CONPTY_CREATE_PROCESS_FAILED");
|
|
1133
|
+
CloseIfValid(ref processInfo.hThread);
|
|
1134
|
+
inputRead.Dispose(); inputRead = null;
|
|
1135
|
+
outputWrite.Dispose(); outputWrite = null;
|
|
1136
|
+
|
|
1137
|
+
bool targetInJob;
|
|
1138
|
+
if (!IsProcessInJob(processInfo.hProcess, IntPtr.Zero, out targetInJob)) ThrowLastWin32("CONPTY_JOB_MEMBERSHIP_QUERY_FAILED");
|
|
1139
|
+
if (!targetInJob) throw new InvalidDataException("CONPTY_TARGET_NOT_JOB_OWNED_BEFORE_RESUME");
|
|
1140
|
+
StringBuilder imagePath = new StringBuilder(32768); int imagePathLength = imagePath.Capacity;
|
|
1141
|
+
if (!QueryFullProcessImageNameW(processInfo.hProcess, 0, imagePath, ref imagePathLength)) ThrowLastWin32("IMAGE_QUERY_FAILED");
|
|
1142
|
+
if (!String.Equals(Path.GetFullPath(imagePath.ToString()), Path.GetFullPath(executable), StringComparison.OrdinalIgnoreCase))
|
|
1143
|
+
throw new InvalidDataException("IMAGE_IDENTITY_MISMATCH");
|
|
1144
|
+
|
|
1145
|
+
terminalInput = new FileStream(inputWrite, FileAccess.Write, 4096, false); inputWrite = null;
|
|
1146
|
+
FileStream terminalOutput = new FileStream(outputRead, FileAccess.Read, 4096, false); outputRead = null;
|
|
1147
|
+
outputTask = Task.Run(() =>
|
|
1148
|
+
{
|
|
1149
|
+
using (terminalOutput)
|
|
1150
|
+
{
|
|
1151
|
+
byte[] buffer = new byte[8192];
|
|
1152
|
+
while (true)
|
|
1153
|
+
{
|
|
1154
|
+
int count;
|
|
1155
|
+
try { count = terminalOutput.Read(buffer, 0, buffer.Length); } catch { break; }
|
|
1156
|
+
if (count <= 0) break;
|
|
1157
|
+
byte[] chunk = new byte[count]; Buffer.BlockCopy(buffer, 0, chunk, 0, count);
|
|
1158
|
+
Dictionary<string, object> output = new Dictionary<string, object>();
|
|
1159
|
+
output["schemaVersion"] = 1; output["type"] = "output"; output["dataBase64"] = Convert.ToBase64String(chunk);
|
|
1160
|
+
EmitConPtyWorkerEvent(output);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
Task.Run(() =>
|
|
1165
|
+
{
|
|
1166
|
+
StreamReader controlReader = new StreamReader(controlPipe, new UTF8Encoding(false), false, 4096, true);
|
|
1167
|
+
try
|
|
1168
|
+
{
|
|
1169
|
+
string line;
|
|
1170
|
+
while ((line = controlReader.ReadLine()) != null) commands.Add(line);
|
|
1171
|
+
}
|
|
1172
|
+
finally { commands.CompleteAdding(); }
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
Dictionary<string, object> ready = new Dictionary<string, object>();
|
|
1176
|
+
ready["schemaVersion"] = 1; ready["type"] = "ready"; ready["targetInInheritedJobAtCreation"] = true; ready["imageIdentityVerified"] = true;
|
|
1177
|
+
EmitConPtyWorkerEvent(ready);
|
|
1178
|
+
if (initialInput.Length > 0) { terminalInput.Write(initialInput, 0, initialInput.Length); terminalInput.Flush(); }
|
|
1179
|
+
|
|
1180
|
+
string reason = "natural_exit";
|
|
1181
|
+
while (WaitForSingleObject(processInfo.hProcess, 0) == WaitTimeout)
|
|
1182
|
+
{
|
|
1183
|
+
string line;
|
|
1184
|
+
if (!commands.TryTake(out line, 100))
|
|
1185
|
+
{
|
|
1186
|
+
if (commands.IsCompleted) { reason = "transport_closed"; TerminateProcess(processInfo.hProcess, 0xC000013A); break; }
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
Dictionary<string, object> command = ParseStrictObject(Encoding.UTF8.GetBytes(line));
|
|
1190
|
+
string operation = RequireBoundedString(command["operation"], "operation", 1, 32);
|
|
1191
|
+
string requestId = RequireSafeNonce(command["requestId"]);
|
|
1192
|
+
if (RequireSafeNonce(command["controlKey"]) != controlKey) throw new InvalidDataException("CONPTY_CONTROL_AUTH_FAILED");
|
|
1193
|
+
if (operation == "input")
|
|
1194
|
+
{
|
|
1195
|
+
RequireExactKeys(command, new string[] { "schemaVersion", "operation", "requestId", "controlKey", "dataBase64", "close" });
|
|
1196
|
+
byte[] data;
|
|
1197
|
+
try { data = Convert.FromBase64String(RequireBoundedString(command["dataBase64"], "dataBase64", 0, 131072)); }
|
|
1198
|
+
catch { throw new InvalidDataException("INVALID_INPUT_BASE64"); }
|
|
1199
|
+
if (!(command["close"] is bool)) throw new InvalidDataException("INVALID_close");
|
|
1200
|
+
if (terminalInput == null) { EmitConPtyWorkerResponse(requestId, false, "PROCESS_NOT_FOUND"); continue; }
|
|
1201
|
+
if (data.Length > 0) { terminalInput.Write(data, 0, data.Length); terminalInput.Flush(); }
|
|
1202
|
+
if ((bool)command["close"]) { terminalInput.Dispose(); terminalInput = null; }
|
|
1203
|
+
EmitConPtyWorkerResponse(requestId, true, "INPUT_DELIVERED");
|
|
1204
|
+
}
|
|
1205
|
+
else if (operation == "resize")
|
|
1206
|
+
{
|
|
1207
|
+
RequireExactKeys(command, new string[] { "schemaVersion", "operation", "requestId", "controlKey", "columns", "rows" });
|
|
1208
|
+
COORD size = new COORD {
|
|
1209
|
+
X = checked((short)ToBoundedInt(command["columns"], "columns", 1, 500)),
|
|
1210
|
+
Y = checked((short)ToBoundedInt(command["rows"], "rows", 1, 500))
|
|
1211
|
+
};
|
|
1212
|
+
if (ResizePseudoConsole(pseudoConsole, size) < 0) throw new InvalidDataException("CONPTY_RESIZE_FAILED");
|
|
1213
|
+
EmitConPtyWorkerResponse(requestId, true, "RESIZED");
|
|
1214
|
+
}
|
|
1215
|
+
else if (operation == "interrupt")
|
|
1216
|
+
{
|
|
1217
|
+
RequireExactKeys(command, new string[] { "schemaVersion", "operation", "requestId", "controlKey" });
|
|
1218
|
+
if (terminalInput == null) { EmitConPtyWorkerResponse(requestId, false, "PROCESS_NOT_FOUND"); continue; }
|
|
1219
|
+
terminalInput.WriteByte(0x03); terminalInput.Flush();
|
|
1220
|
+
EmitConPtyWorkerResponse(requestId, true, "INTERRUPT_DELIVERED");
|
|
1221
|
+
}
|
|
1222
|
+
else
|
|
1223
|
+
{
|
|
1224
|
+
RequireExactKeys(command, new string[] { "schemaVersion", "operation", "requestId", "controlKey" });
|
|
1225
|
+
EmitConPtyWorkerResponse(requestId, false, "UNKNOWN_OPERATION");
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
if (WaitForSingleObject(processInfo.hProcess, 10000) != WaitObject0) throw new InvalidDataException("CONPTY_PROCESS_TERMINATE_TIMEOUT");
|
|
1229
|
+
uint exitCode;
|
|
1230
|
+
if (!GetExitCodeProcess(processInfo.hProcess, out exitCode)) ThrowLastWin32("CONPTY_EXIT_CODE_FAILED");
|
|
1231
|
+
if (terminalInput != null) { terminalInput.Dispose(); terminalInput = null; }
|
|
1232
|
+
ClosePseudoConsoleWithDeadline(ref pseudoConsole, false);
|
|
1233
|
+
if (outputTask != null && !outputTask.Wait(10000)) throw new InvalidDataException("CONPTY_OUTPUT_DRAIN_TIMEOUT");
|
|
1234
|
+
Dictionary<string, object> exited = new Dictionary<string, object>();
|
|
1235
|
+
exited["schemaVersion"] = 1; exited["type"] = "exit"; exited["exitCode"] = (long)exitCode; exited["reason"] = reason;
|
|
1236
|
+
EmitConPtyWorkerEvent(exited);
|
|
1237
|
+
}
|
|
1238
|
+
finally
|
|
1239
|
+
{
|
|
1240
|
+
if (terminalInput != null) terminalInput.Dispose();
|
|
1241
|
+
if (processInfo.hProcess != IntPtr.Zero && WaitForSingleObject(processInfo.hProcess, 0) == WaitTimeout) TerminateProcess(processInfo.hProcess, 0xC000013A);
|
|
1242
|
+
CloseIfValid(ref processInfo.hThread); CloseIfValid(ref processInfo.hProcess);
|
|
1243
|
+
if (inputRead != null) inputRead.Dispose(); if (inputWrite != null) inputWrite.Dispose();
|
|
1244
|
+
if (outputRead != null) outputRead.Dispose(); if (outputWrite != null) outputWrite.Dispose();
|
|
1245
|
+
if (pseudoConsole != IntPtr.Zero) ClosePseudoConsoleWithDeadline(ref pseudoConsole, false);
|
|
1246
|
+
if (attributeList != IntPtr.Zero) { DeleteProcThreadAttributeList(attributeList); Marshal.FreeHGlobal(attributeList); }
|
|
1247
|
+
if (environmentPointer != IntPtr.Zero) Marshal.FreeHGlobal(environmentPointer);
|
|
1248
|
+
if (controlPipe != null) controlPipe.Dispose();
|
|
1249
|
+
commands.Dispose();
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
private static Dictionary<string, object> RunConPtyWorkerCore(bool simulateCloseHang, string requestedNodeExecutable, string requestedProbeScript)
|
|
1254
|
+
{
|
|
1255
|
+
if (!HasProc("kernel32.dll", "CreatePseudoConsole") || !HasProc("kernel32.dll", "ResizePseudoConsole") || !HasProc("kernel32.dll", "ClosePseudoConsole"))
|
|
1256
|
+
return ErrorResult("CONPTY_UNAVAILABLE");
|
|
1257
|
+
string executable = Path.GetFullPath(requestedNodeExecutable ?? String.Empty);
|
|
1258
|
+
string probeScript = Path.GetFullPath(requestedProbeScript ?? String.Empty);
|
|
1259
|
+
string expectedProbe = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "scripts", "windows-conpty-probe-child.mjs"));
|
|
1260
|
+
if (!File.Exists(executable) || !File.Exists(probeScript) || !String.Equals(probeScript, expectedProbe, StringComparison.OrdinalIgnoreCase))
|
|
1261
|
+
return ErrorResult("CONPTY_PROBE_IDENTITY_MISMATCH");
|
|
1262
|
+
|
|
1263
|
+
IntPtr pseudoConsole = IntPtr.Zero;
|
|
1264
|
+
SafeFileHandle inputRead = null;
|
|
1265
|
+
SafeFileHandle inputWrite = null;
|
|
1266
|
+
SafeFileHandle outputRead = null;
|
|
1267
|
+
SafeFileHandle outputWrite = null;
|
|
1268
|
+
IntPtr attributeList = IntPtr.Zero;
|
|
1269
|
+
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
|
|
1270
|
+
Task<BoundedReadResult> outputTask = null;
|
|
1271
|
+
Stopwatch totalTimer = Stopwatch.StartNew();
|
|
1272
|
+
long closeDuration = 0;
|
|
1273
|
+
bool resized = false;
|
|
1274
|
+
bool etxDelivered = false;
|
|
1275
|
+
try
|
|
1276
|
+
{
|
|
1277
|
+
bool workerInJob;
|
|
1278
|
+
if (!IsProcessInJob(GetCurrentProcess(), IntPtr.Zero, out workerInJob)) ThrowLastWin32("CONPTY_WORKER_JOB_QUERY_FAILED");
|
|
1279
|
+
if (!workerInJob) throw new InvalidDataException("CONPTY_WORKER_NOT_JOB_OWNED");
|
|
1280
|
+
|
|
1281
|
+
if (!CreateConPtyPipe(out inputRead, out inputWrite, IntPtr.Zero, 0)) ThrowLastWin32("CONPTY_INPUT_PIPE_FAILED");
|
|
1282
|
+
if (!CreateConPtyPipe(out outputRead, out outputWrite, IntPtr.Zero, 0)) ThrowLastWin32("CONPTY_OUTPUT_PIPE_FAILED");
|
|
1283
|
+
|
|
1284
|
+
COORD initialSize = new COORD { X = 80, Y = 25 };
|
|
1285
|
+
int createResult = CreatePseudoConsoleSafe(initialSize, inputRead, outputWrite, 0, out pseudoConsole);
|
|
1286
|
+
if (createResult < 0) throw new InvalidDataException("CONPTY_CREATE_FAILED");
|
|
1287
|
+
if (pseudoConsole == IntPtr.Zero) throw new InvalidDataException("CONPTY_ZERO_HANDLE");
|
|
1288
|
+
|
|
1289
|
+
IntPtr attributeBytes = IntPtr.Zero;
|
|
1290
|
+
InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref attributeBytes);
|
|
1291
|
+
if (attributeBytes == IntPtr.Zero) ThrowLastWin32("CONPTY_ATTRIBUTE_SIZE_FAILED");
|
|
1292
|
+
attributeList = Marshal.AllocHGlobal(attributeBytes);
|
|
1293
|
+
if (!InitializeProcThreadAttributeList(attributeList, 1, 0, ref attributeBytes)) ThrowLastWin32("CONPTY_ATTRIBUTE_INIT_FAILED");
|
|
1294
|
+
if (!UpdateProcThreadAttribute(attributeList, 0, ProcThreadAttributePseudoConsole, pseudoConsole, new IntPtr(IntPtr.Size), IntPtr.Zero, IntPtr.Zero))
|
|
1295
|
+
ThrowLastWin32("CONPTY_ATTRIBUTE_FAILED");
|
|
1296
|
+
|
|
1297
|
+
STARTUPINFOEX startup = new STARTUPINFOEX();
|
|
1298
|
+
startup.StartupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFOEX));
|
|
1299
|
+
startup.lpAttributeList = attributeList;
|
|
1300
|
+
string commandLine = BuildCommandLine(executable, new string[] { probeScript });
|
|
1301
|
+
SECURITY_ATTRIBUTES processAttributes = new SECURITY_ATTRIBUTES();
|
|
1302
|
+
processAttributes.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
|
|
1303
|
+
SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
|
|
1304
|
+
threadAttributes.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
|
|
1305
|
+
IntPtr savedStdInput = GetStdHandle(StdInputHandle);
|
|
1306
|
+
IntPtr savedStdOutput = GetStdHandle(StdOutputHandle);
|
|
1307
|
+
IntPtr savedStdError = GetStdHandle(StdErrorHandle);
|
|
1308
|
+
bool created;
|
|
1309
|
+
if (!SetStdHandle(StdInputHandle, IntPtr.Zero) ||
|
|
1310
|
+
!SetStdHandle(StdOutputHandle, IntPtr.Zero) ||
|
|
1311
|
+
!SetStdHandle(StdErrorHandle, IntPtr.Zero))
|
|
1312
|
+
ThrowLastWin32("CONPTY_STANDARD_HANDLE_DETACH_FAILED");
|
|
1313
|
+
try
|
|
1314
|
+
{
|
|
1315
|
+
created = CreateConPtyProcessW(
|
|
1316
|
+
null,
|
|
1317
|
+
commandLine,
|
|
1318
|
+
ref processAttributes,
|
|
1319
|
+
ref threadAttributes,
|
|
1320
|
+
false,
|
|
1321
|
+
ExtendedStartupInfoPresent,
|
|
1322
|
+
IntPtr.Zero,
|
|
1323
|
+
null,
|
|
1324
|
+
ref startup,
|
|
1325
|
+
out processInfo);
|
|
1326
|
+
}
|
|
1327
|
+
finally
|
|
1328
|
+
{
|
|
1329
|
+
SetStdHandle(StdInputHandle, savedStdInput);
|
|
1330
|
+
SetStdHandle(StdOutputHandle, savedStdOutput);
|
|
1331
|
+
SetStdHandle(StdErrorHandle, savedStdError);
|
|
1332
|
+
}
|
|
1333
|
+
if (!created) ThrowLastWin32("CONPTY_CREATE_PROCESS_FAILED");
|
|
1334
|
+
inputRead.Dispose();
|
|
1335
|
+
inputRead = null;
|
|
1336
|
+
outputWrite.Dispose();
|
|
1337
|
+
outputWrite = null;
|
|
1338
|
+
|
|
1339
|
+
bool targetInInheritedJob;
|
|
1340
|
+
if (!IsProcessInJob(processInfo.hProcess, IntPtr.Zero, out targetInInheritedJob)) ThrowLastWin32("CONPTY_JOB_MEMBERSHIP_QUERY_FAILED");
|
|
1341
|
+
if (!targetInInheritedJob) throw new InvalidDataException("CONPTY_TARGET_NOT_JOB_OWNED_BEFORE_RESUME");
|
|
1342
|
+
|
|
1343
|
+
FileStream outputStream = new FileStream(outputRead, FileAccess.Read, 4096, false);
|
|
1344
|
+
outputRead = null;
|
|
1345
|
+
outputTask = Task.Run(() => ReadBounded(outputStream, 16384));
|
|
1346
|
+
CloseIfValid(ref processInfo.hThread);
|
|
1347
|
+
using (FileStream inputStream = new FileStream(inputWrite, FileAccess.Write, 4096, false))
|
|
1348
|
+
{
|
|
1349
|
+
inputWrite = null;
|
|
1350
|
+
byte[] interactiveCommands = Encoding.UTF8.GetBytes("CXP4_INPUT\r\n");
|
|
1351
|
+
inputStream.Write(interactiveCommands, 0, interactiveCommands.Length);
|
|
1352
|
+
inputStream.Flush();
|
|
1353
|
+
Thread.Sleep(500);
|
|
1354
|
+
COORD resizedSize = new COORD { X = 100, Y = 30 };
|
|
1355
|
+
int resizeResult = ResizePseudoConsole(pseudoConsole, resizedSize);
|
|
1356
|
+
if (resizeResult < 0) throw new InvalidDataException("CONPTY_RESIZE_FAILED");
|
|
1357
|
+
resized = true;
|
|
1358
|
+
inputStream.WriteByte(0x03);
|
|
1359
|
+
inputStream.Flush();
|
|
1360
|
+
etxDelivered = true;
|
|
1361
|
+
Thread.Sleep(1000);
|
|
1362
|
+
byte[] exitCommand = Encoding.UTF8.GetBytes("CXP4_EXIT\r\n");
|
|
1363
|
+
inputStream.Write(exitCommand, 0, exitCommand.Length);
|
|
1364
|
+
inputStream.Flush();
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
uint wait = WaitForSingleObject(processInfo.hProcess, 10000);
|
|
1368
|
+
bool timedOut = wait == WaitTimeout;
|
|
1369
|
+
if (timedOut)
|
|
1370
|
+
{
|
|
1371
|
+
if (!TerminateProcess(processInfo.hProcess, 0xC000013A)) ThrowLastWin32("CONPTY_PROCESS_TERMINATE_FAILED");
|
|
1372
|
+
if (WaitForSingleObject(processInfo.hProcess, 10000) != WaitObject0) throw new InvalidDataException("CONPTY_PROCESS_TERMINATE_TIMEOUT");
|
|
1373
|
+
}
|
|
1374
|
+
else if (wait != WaitObject0) ThrowLastWin32("CONPTY_PROCESS_WAIT_FAILED");
|
|
1375
|
+
uint exitCode;
|
|
1376
|
+
if (!GetExitCodeProcess(processInfo.hProcess, out exitCode)) ThrowLastWin32("CONPTY_EXIT_CODE_FAILED");
|
|
1377
|
+
|
|
1378
|
+
closeDuration = ClosePseudoConsoleWithDeadline(ref pseudoConsole, simulateCloseHang);
|
|
1379
|
+
if (!outputTask.Wait(10000)) throw new InvalidDataException("CONPTY_OUTPUT_DRAIN_TIMEOUT");
|
|
1380
|
+
BoundedReadResult output = outputTask.Result;
|
|
1381
|
+
string outputText = Encoding.UTF8.GetString(output.Bytes);
|
|
1382
|
+
bool outputContainsReady = outputText.IndexOf("CXP4_CONPTY_READY", StringComparison.Ordinal) >= 0;
|
|
1383
|
+
bool outputContainsInputAck = outputText.IndexOf("CXP4_INPUT_ACK", StringComparison.Ordinal) >= 0;
|
|
1384
|
+
bool outputContainsEtxAck = outputText.IndexOf("CXP4_ETX_ACK", StringComparison.Ordinal) >= 0;
|
|
1385
|
+
const uint StatusControlCExit = 0xC000013A;
|
|
1386
|
+
bool expectedExit = exitCode == 0 || exitCode == StatusControlCExit;
|
|
1387
|
+
bool evidenceOk = !timedOut && expectedExit && outputContainsReady && outputContainsInputAck && outputContainsEtxAck;
|
|
1388
|
+
totalTimer.Stop();
|
|
1389
|
+
Dictionary<string, object> result = evidenceOk ? SuccessResult() : ErrorResult(timedOut ? "CONPTY_PROCESS_TIMED_OUT" : "CONPTY_EVIDENCE_MISMATCH");
|
|
1390
|
+
if (evidenceOk) result["code"] = "CONPTY_PROBE_OK";
|
|
1391
|
+
result["conPtyCreated"] = true;
|
|
1392
|
+
result["resized"] = resized;
|
|
1393
|
+
result["etxDelivered"] = etxDelivered;
|
|
1394
|
+
result["outputContainsReady"] = outputContainsReady;
|
|
1395
|
+
result["outputContainsInputAck"] = outputContainsInputAck;
|
|
1396
|
+
result["outputContainsEtxAck"] = outputContainsEtxAck;
|
|
1397
|
+
result["outputBase64"] = Convert.ToBase64String(output.Bytes);
|
|
1398
|
+
result["outputTotalBytes"] = output.TotalBytes;
|
|
1399
|
+
result["outputDroppedBytes"] = output.DroppedBytes;
|
|
1400
|
+
result["outputTruncated"] = output.Truncated;
|
|
1401
|
+
result["exitCode"] = (long)exitCode;
|
|
1402
|
+
result["timedOut"] = timedOut;
|
|
1403
|
+
result["workerInOwnedJob"] = workerInJob;
|
|
1404
|
+
result["targetInInheritedJobAtCreation"] = targetInInheritedJob;
|
|
1405
|
+
result["targetCreatedSuspended"] = false;
|
|
1406
|
+
result["jobAssignedAtCreation"] = targetInInheritedJob;
|
|
1407
|
+
result["closeDurationMs"] = closeDuration;
|
|
1408
|
+
result["closeDeadlineMs"] = 5000;
|
|
1409
|
+
result["elapsedMilliseconds"] = totalTimer.ElapsedMilliseconds;
|
|
1410
|
+
return result;
|
|
1411
|
+
}
|
|
1412
|
+
finally
|
|
1413
|
+
{
|
|
1414
|
+
CloseIfValid(ref processInfo.hThread);
|
|
1415
|
+
CloseIfValid(ref processInfo.hProcess);
|
|
1416
|
+
if (inputRead != null) inputRead.Dispose();
|
|
1417
|
+
if (inputWrite != null) inputWrite.Dispose();
|
|
1418
|
+
if (outputRead != null) outputRead.Dispose();
|
|
1419
|
+
if (outputWrite != null) outputWrite.Dispose();
|
|
1420
|
+
if (pseudoConsole != IntPtr.Zero) ClosePseudoConsoleWithDeadline(ref pseudoConsole, false);
|
|
1421
|
+
if (attributeList != IntPtr.Zero)
|
|
1422
|
+
{
|
|
1423
|
+
DeleteProcThreadAttributeList(attributeList);
|
|
1424
|
+
Marshal.FreeHGlobal(attributeList);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
private static long ClosePseudoConsoleWithDeadline(ref IntPtr pseudoConsole, bool simulateCloseHang)
|
|
1430
|
+
{
|
|
1431
|
+
IntPtr handle = pseudoConsole;
|
|
1432
|
+
pseudoConsole = IntPtr.Zero;
|
|
1433
|
+
if (handle == IntPtr.Zero) return 0;
|
|
1434
|
+
Stopwatch timer = Stopwatch.StartNew();
|
|
1435
|
+
Task closeTask = Task.Run(() =>
|
|
1436
|
+
{
|
|
1437
|
+
if (simulateCloseHang) Thread.Sleep(Timeout.Infinite);
|
|
1438
|
+
else ClosePseudoConsole(handle);
|
|
1439
|
+
});
|
|
1440
|
+
if (!closeTask.Wait(5000)) Environment.FailFast("HOST_FATAL_CONPTY_CLOSE");
|
|
1441
|
+
timer.Stop();
|
|
1442
|
+
return timer.ElapsedMilliseconds;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
private static PersistentProcessState StartPersistentOwnedProcess(string executable, string[] arguments, string cwd, SortedDictionary<string, string> environment, byte[] initialInput, bool closeInitialInput, int lifetimeMs)
|
|
1446
|
+
{
|
|
1447
|
+
IntPtr executableHandle = IntPtr.Zero; IntPtr job = IntPtr.Zero; long jobRegistration = 0; IntPtr attributeList = IntPtr.Zero; IntPtr jobValue = IntPtr.Zero; IntPtr handleValues = IntPtr.Zero; IntPtr environmentPointer = IntPtr.Zero;
|
|
1448
|
+
IntPtr stdinRead = IntPtr.Zero; IntPtr stdinWrite = IntPtr.Zero; IntPtr stdoutRead = IntPtr.Zero; IntPtr stdoutWrite = IntPtr.Zero; IntPtr stderrRead = IntPtr.Zero; IntPtr stderrWrite = IntPtr.Zero; PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
|
|
1449
|
+
bool transferred = false;
|
|
1450
|
+
try
|
|
1451
|
+
{
|
|
1452
|
+
executableHandle = CreateFileW(executable, GenericRead, FileShareRead, IntPtr.Zero, OpenExisting, FileAttributeNormal, IntPtr.Zero); if (executableHandle == new IntPtr(-1)) ThrowLastWin32("EXECUTABLE_OPEN_FAILED");
|
|
1453
|
+
SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES(); attributes.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)); attributes.bInheritHandle = 1;
|
|
1454
|
+
if (!CreatePipe(out stdinRead, out stdinWrite, ref attributes, 0)) ThrowLastWin32("STDIN_PIPE_FAILED");
|
|
1455
|
+
if (!CreatePipe(out stdoutRead, out stdoutWrite, ref attributes, 0)) ThrowLastWin32("STDOUT_PIPE_FAILED");
|
|
1456
|
+
if (!CreatePipe(out stderrRead, out stderrWrite, ref attributes, 0)) ThrowLastWin32("STDERR_PIPE_FAILED");
|
|
1457
|
+
if (!SetHandleInformation(stdinWrite, HandleFlagInherit, 0) || !SetHandleInformation(stdoutRead, HandleFlagInherit, 0) || !SetHandleInformation(stderrRead, HandleFlagInherit, 0)) ThrowLastWin32("PIPE_INHERITANCE_FAILED");
|
|
1458
|
+
job = CreateJobObjectW(IntPtr.Zero, null); if (job == IntPtr.Zero) ThrowLastWin32("JOB_CREATE_FAILED");
|
|
1459
|
+
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); limits.BasicLimitInformation.LimitFlags = JobObjectLimitKillOnJobClose | JobObjectLimitActiveProcess; limits.BasicLimitInformation.ActiveProcessLimit = 64;
|
|
1460
|
+
int limitsSize = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); IntPtr limitsPointer = Marshal.AllocHGlobal(limitsSize);
|
|
1461
|
+
try { Marshal.StructureToPtr(limits, limitsPointer, false); if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, limitsPointer, (uint)limitsSize)) ThrowLastWin32("JOB_LIMIT_FAILED"); } finally { Marshal.FreeHGlobal(limitsPointer); }
|
|
1462
|
+
jobRegistration = HostLifetime.RegisterJob(job);
|
|
1463
|
+
IntPtr attributeBytes = IntPtr.Zero; InitializeProcThreadAttributeList(IntPtr.Zero, 2, 0, ref attributeBytes); if (attributeBytes == IntPtr.Zero) ThrowLastWin32("ATTRIBUTE_SIZE_FAILED");
|
|
1464
|
+
attributeList = Marshal.AllocHGlobal(attributeBytes); if (!InitializeProcThreadAttributeList(attributeList, 2, 0, ref attributeBytes)) ThrowLastWin32("ATTRIBUTE_INIT_FAILED");
|
|
1465
|
+
jobValue = Marshal.AllocHGlobal(IntPtr.Size); Marshal.WriteIntPtr(jobValue, job); if (!UpdateProcThreadAttribute(attributeList, 0, ProcThreadAttributeJobList, jobValue, new IntPtr(IntPtr.Size), IntPtr.Zero, IntPtr.Zero)) ThrowLastWin32("JOB_LIST_ATTRIBUTE_FAILED");
|
|
1466
|
+
handleValues = Marshal.AllocHGlobal(IntPtr.Size * 3); Marshal.WriteIntPtr(handleValues, 0, stdinRead); Marshal.WriteIntPtr(handleValues, IntPtr.Size, stdoutWrite); Marshal.WriteIntPtr(handleValues, IntPtr.Size * 2, stderrWrite);
|
|
1467
|
+
if (!UpdateProcThreadAttribute(attributeList, 0, ProcThreadAttributeHandleList, handleValues, new IntPtr(IntPtr.Size * 3), IntPtr.Zero, IntPtr.Zero)) ThrowLastWin32("HANDLE_LIST_ATTRIBUTE_FAILED");
|
|
1468
|
+
STARTUPINFOEX startup = new STARTUPINFOEX(); startup.StartupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFOEX)); startup.StartupInfo.dwFlags = unchecked((int)StartfUseStdHandles); startup.StartupInfo.hStdInput = stdinRead; startup.StartupInfo.hStdOutput = stdoutWrite; startup.StartupInfo.hStdError = stderrWrite; startup.lpAttributeList = attributeList;
|
|
1469
|
+
StringBuilder mutableCommandLine = new StringBuilder(BuildCommandLine(executable, arguments)); byte[] environmentBytes = Encoding.Unicode.GetBytes(BuildEnvironmentBlock(environment)); environmentPointer = Marshal.AllocHGlobal(environmentBytes.Length); Marshal.Copy(environmentBytes, 0, environmentPointer, environmentBytes.Length);
|
|
1470
|
+
if (!CreateProcessW(executable, mutableCommandLine, IntPtr.Zero, IntPtr.Zero, true, ExtendedStartupInfoPresent | CreateUnicodeEnvironment | CreateNoWindow, environmentPointer, cwd, ref startup, out processInfo)) ThrowLastWin32("CREATE_PROCESS_FAILED");
|
|
1471
|
+
CloseIfValid(ref stdinRead); CloseIfValid(ref stdoutWrite); CloseIfValid(ref stderrWrite); CloseIfValid(ref processInfo.hThread);
|
|
1472
|
+
bool inExactJob; if (!IsProcessInJob(processInfo.hProcess, job, out inExactJob)) ThrowLastWin32("JOB_MEMBERSHIP_QUERY_FAILED"); if (!inExactJob) throw new InvalidDataException("JOB_NOT_ASSIGNED_AT_CREATION");
|
|
1473
|
+
StringBuilder imagePath = new StringBuilder(32768); int imagePathLength = imagePath.Capacity; if (!QueryFullProcessImageNameW(processInfo.hProcess, 0, imagePath, ref imagePathLength)) ThrowLastWin32("IMAGE_QUERY_FAILED"); if (!String.Equals(Path.GetFullPath(imagePath.ToString()), Path.GetFullPath(executable), StringComparison.OrdinalIgnoreCase)) throw new InvalidDataException("IMAGE_IDENTITY_MISMATCH");
|
|
1474
|
+
PersistentProcessState state = new PersistentProcessState(); state.Handle = NewPersistentHandle(); state.ProcessHandle = processInfo.hProcess; processInfo.hProcess = IntPtr.Zero; state.JobHandle = job; job = IntPtr.Zero; state.JobRegistration = jobRegistration; jobRegistration = 0;
|
|
1475
|
+
state.Stdin = new FileStream(new SafeFileHandle(stdinWrite, true), FileAccess.Write, 4096, false); stdinWrite = IntPtr.Zero;
|
|
1476
|
+
FileStream stdoutStream = new FileStream(new SafeFileHandle(stdoutRead, true), FileAccess.Read, 4096, false); stdoutRead = IntPtr.Zero; FileStream stderrStream = new FileStream(new SafeFileHandle(stderrRead, true), FileAccess.Read, 4096, false); stderrRead = IntPtr.Zero;
|
|
1477
|
+
if (initialInput.Length > 0) { state.Stdin.Write(initialInput, 0, initialInput.Length); state.Stdin.Flush(); }
|
|
1478
|
+
if (closeInitialInput) { state.Stdin.Dispose(); state.StdinClosed = true; }
|
|
1479
|
+
Task.Run(() => ReadPersistent(state, stdoutStream, false)); Task.Run(() => ReadPersistent(state, stderrStream, true));
|
|
1480
|
+
Task.Run(() =>
|
|
1481
|
+
{
|
|
1482
|
+
uint wait = WaitForSingleObject(state.ProcessHandle, (uint)lifetimeMs);
|
|
1483
|
+
if (wait == WaitTimeout) ClosePersistentState(state, "expired");
|
|
1484
|
+
else if (wait == WaitObject0)
|
|
1485
|
+
{
|
|
1486
|
+
lock (state.Sync)
|
|
1487
|
+
{
|
|
1488
|
+
if (state.Running) { uint code; if (GetExitCodeProcess(state.ProcessHandle, out code)) state.ExitCode = code; state.Reason = "natural_exit"; state.Running = false; HostLifetime.ReleaseJob(state.JobRegistration, ref state.JobHandle); state.Changed.Set(); }
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
else ClosePersistentState(state, "host_wait_failed");
|
|
1492
|
+
});
|
|
1493
|
+
transferred = true; return state;
|
|
1494
|
+
}
|
|
1495
|
+
finally
|
|
1496
|
+
{
|
|
1497
|
+
CloseIfValid(ref processInfo.hThread); CloseIfValid(ref processInfo.hProcess); CloseIfValid(ref stdinRead); CloseIfValid(ref stdinWrite); CloseIfValid(ref stdoutRead); CloseIfValid(ref stdoutWrite); CloseIfValid(ref stderrRead); CloseIfValid(ref stderrWrite);
|
|
1498
|
+
if (!transferred) HostLifetime.ReleaseJob(jobRegistration, ref job);
|
|
1499
|
+
if (attributeList != IntPtr.Zero) { DeleteProcThreadAttributeList(attributeList); Marshal.FreeHGlobal(attributeList); }
|
|
1500
|
+
if (jobValue != IntPtr.Zero) Marshal.FreeHGlobal(jobValue); if (handleValues != IntPtr.Zero) Marshal.FreeHGlobal(handleValues); if (environmentPointer != IntPtr.Zero) Marshal.FreeHGlobal(environmentPointer); if (executableHandle != IntPtr.Zero && executableHandle != new IntPtr(-1)) CloseHandle(executableHandle);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
private static ProcessRunResult RunOwnedProcess(string executable, string[] arguments, string cwd, SortedDictionary<string, string> environment, byte[] stdinBytes, int timeoutMs, int stdoutLimit, int stderrLimit)
|
|
1505
|
+
{
|
|
1506
|
+
return RunOwnedProcess(executable, arguments, cwd, environment, stdinBytes, timeoutMs, stdoutLimit, stderrLimit, null);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
private static ProcessRunResult RunOwnedProcess(string executable, string[] arguments, string cwd, SortedDictionary<string, string> environment, byte[] stdinBytes, int timeoutMs, int stdoutLimit, int stderrLimit, EventWaitHandle cancel)
|
|
1510
|
+
{
|
|
1511
|
+
Stopwatch timer = Stopwatch.StartNew();
|
|
1512
|
+
IntPtr executableHandle = IntPtr.Zero;
|
|
1513
|
+
IntPtr job = IntPtr.Zero;
|
|
1514
|
+
long jobRegistration = 0;
|
|
1515
|
+
IntPtr attributeList = IntPtr.Zero;
|
|
1516
|
+
IntPtr jobValue = IntPtr.Zero;
|
|
1517
|
+
IntPtr handleValues = IntPtr.Zero;
|
|
1518
|
+
IntPtr environmentPointer = IntPtr.Zero;
|
|
1519
|
+
IntPtr stdinRead = IntPtr.Zero;
|
|
1520
|
+
IntPtr stdinWrite = IntPtr.Zero;
|
|
1521
|
+
IntPtr stdoutRead = IntPtr.Zero;
|
|
1522
|
+
IntPtr stdoutWrite = IntPtr.Zero;
|
|
1523
|
+
IntPtr stderrRead = IntPtr.Zero;
|
|
1524
|
+
IntPtr stderrWrite = IntPtr.Zero;
|
|
1525
|
+
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
|
|
1526
|
+
Task<BoundedReadResult> stdoutTask = null;
|
|
1527
|
+
Task<BoundedReadResult> stderrTask = null;
|
|
1528
|
+
try
|
|
1529
|
+
{
|
|
1530
|
+
executableHandle = CreateFileW(executable, GenericRead, FileShareRead, IntPtr.Zero, OpenExisting, FileAttributeNormal, IntPtr.Zero);
|
|
1531
|
+
if (executableHandle == new IntPtr(-1)) ThrowLastWin32("EXECUTABLE_OPEN_FAILED");
|
|
1532
|
+
BY_HANDLE_FILE_INFORMATION fileInfo;
|
|
1533
|
+
if (!GetFileInformationByHandle(executableHandle, out fileInfo)) ThrowLastWin32("EXECUTABLE_IDENTITY_FAILED");
|
|
1534
|
+
|
|
1535
|
+
SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES();
|
|
1536
|
+
attributes.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
|
|
1537
|
+
attributes.bInheritHandle = 1;
|
|
1538
|
+
if (!CreatePipe(out stdinRead, out stdinWrite, ref attributes, 0)) ThrowLastWin32("STDIN_PIPE_FAILED");
|
|
1539
|
+
if (!CreatePipe(out stdoutRead, out stdoutWrite, ref attributes, 0)) ThrowLastWin32("STDOUT_PIPE_FAILED");
|
|
1540
|
+
if (!CreatePipe(out stderrRead, out stderrWrite, ref attributes, 0)) ThrowLastWin32("STDERR_PIPE_FAILED");
|
|
1541
|
+
if (!SetHandleInformation(stdinWrite, HandleFlagInherit, 0) || !SetHandleInformation(stdoutRead, HandleFlagInherit, 0) || !SetHandleInformation(stderrRead, HandleFlagInherit, 0))
|
|
1542
|
+
ThrowLastWin32("PIPE_INHERITANCE_FAILED");
|
|
1543
|
+
|
|
1544
|
+
job = CreateJobObjectW(IntPtr.Zero, null);
|
|
1545
|
+
if (job == IntPtr.Zero) ThrowLastWin32("JOB_CREATE_FAILED");
|
|
1546
|
+
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
|
|
1547
|
+
limits.BasicLimitInformation.LimitFlags = JobObjectLimitKillOnJobClose | JobObjectLimitActiveProcess;
|
|
1548
|
+
limits.BasicLimitInformation.ActiveProcessLimit = 64;
|
|
1549
|
+
int limitsSize = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
|
1550
|
+
IntPtr limitsPointer = Marshal.AllocHGlobal(limitsSize);
|
|
1551
|
+
try
|
|
1552
|
+
{
|
|
1553
|
+
Marshal.StructureToPtr(limits, limitsPointer, false);
|
|
1554
|
+
if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, limitsPointer, (uint)limitsSize)) ThrowLastWin32("JOB_LIMIT_FAILED");
|
|
1555
|
+
}
|
|
1556
|
+
finally { Marshal.FreeHGlobal(limitsPointer); }
|
|
1557
|
+
jobRegistration = HostLifetime.RegisterJob(job);
|
|
1558
|
+
|
|
1559
|
+
IntPtr attributeBytes = IntPtr.Zero;
|
|
1560
|
+
InitializeProcThreadAttributeList(IntPtr.Zero, 2, 0, ref attributeBytes);
|
|
1561
|
+
if (attributeBytes == IntPtr.Zero) ThrowLastWin32("ATTRIBUTE_SIZE_FAILED");
|
|
1562
|
+
attributeList = Marshal.AllocHGlobal(attributeBytes);
|
|
1563
|
+
if (!InitializeProcThreadAttributeList(attributeList, 2, 0, ref attributeBytes)) ThrowLastWin32("ATTRIBUTE_INIT_FAILED");
|
|
1564
|
+
jobValue = Marshal.AllocHGlobal(IntPtr.Size);
|
|
1565
|
+
Marshal.WriteIntPtr(jobValue, job);
|
|
1566
|
+
if (!UpdateProcThreadAttribute(attributeList, 0, ProcThreadAttributeJobList, jobValue, new IntPtr(IntPtr.Size), IntPtr.Zero, IntPtr.Zero))
|
|
1567
|
+
ThrowLastWin32("JOB_LIST_ATTRIBUTE_FAILED");
|
|
1568
|
+
handleValues = Marshal.AllocHGlobal(IntPtr.Size * 3);
|
|
1569
|
+
Marshal.WriteIntPtr(handleValues, 0, stdinRead);
|
|
1570
|
+
Marshal.WriteIntPtr(handleValues, IntPtr.Size, stdoutWrite);
|
|
1571
|
+
Marshal.WriteIntPtr(handleValues, IntPtr.Size * 2, stderrWrite);
|
|
1572
|
+
if (!UpdateProcThreadAttribute(attributeList, 0, ProcThreadAttributeHandleList, handleValues, new IntPtr(IntPtr.Size * 3), IntPtr.Zero, IntPtr.Zero))
|
|
1573
|
+
ThrowLastWin32("HANDLE_LIST_ATTRIBUTE_FAILED");
|
|
1574
|
+
|
|
1575
|
+
STARTUPINFOEX startup = new STARTUPINFOEX();
|
|
1576
|
+
startup.StartupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFOEX));
|
|
1577
|
+
startup.StartupInfo.dwFlags = unchecked((int)StartfUseStdHandles);
|
|
1578
|
+
startup.StartupInfo.hStdInput = stdinRead;
|
|
1579
|
+
startup.StartupInfo.hStdOutput = stdoutWrite;
|
|
1580
|
+
startup.StartupInfo.hStdError = stderrWrite;
|
|
1581
|
+
startup.lpAttributeList = attributeList;
|
|
1582
|
+
string commandLine = BuildCommandLine(executable, arguments);
|
|
1583
|
+
StringBuilder mutableCommandLine = new StringBuilder(commandLine);
|
|
1584
|
+
string environmentBlock = BuildEnvironmentBlock(environment);
|
|
1585
|
+
byte[] environmentBytes = Encoding.Unicode.GetBytes(environmentBlock);
|
|
1586
|
+
environmentPointer = Marshal.AllocHGlobal(environmentBytes.Length);
|
|
1587
|
+
Marshal.Copy(environmentBytes, 0, environmentPointer, environmentBytes.Length);
|
|
1588
|
+
bool created = CreateProcessW(
|
|
1589
|
+
executable,
|
|
1590
|
+
mutableCommandLine,
|
|
1591
|
+
IntPtr.Zero,
|
|
1592
|
+
IntPtr.Zero,
|
|
1593
|
+
true,
|
|
1594
|
+
ExtendedStartupInfoPresent | CreateUnicodeEnvironment | CreateNoWindow,
|
|
1595
|
+
environmentPointer,
|
|
1596
|
+
cwd,
|
|
1597
|
+
ref startup,
|
|
1598
|
+
out processInfo);
|
|
1599
|
+
if (!created) ThrowLastWin32("CREATE_PROCESS_FAILED");
|
|
1600
|
+
|
|
1601
|
+
CloseIfValid(ref stdinRead);
|
|
1602
|
+
CloseIfValid(ref stdoutWrite);
|
|
1603
|
+
CloseIfValid(ref stderrWrite);
|
|
1604
|
+
|
|
1605
|
+
bool inExactJob;
|
|
1606
|
+
if (!IsProcessInJob(processInfo.hProcess, job, out inExactJob)) ThrowLastWin32("JOB_MEMBERSHIP_QUERY_FAILED");
|
|
1607
|
+
if (!inExactJob) throw new InvalidDataException("JOB_NOT_ASSIGNED_AT_CREATION");
|
|
1608
|
+
StringBuilder imagePath = new StringBuilder(32768);
|
|
1609
|
+
int imagePathLength = imagePath.Capacity;
|
|
1610
|
+
if (!QueryFullProcessImageNameW(processInfo.hProcess, 0, imagePath, ref imagePathLength)) ThrowLastWin32("IMAGE_QUERY_FAILED");
|
|
1611
|
+
string actualImage = Path.GetFullPath(imagePath.ToString());
|
|
1612
|
+
string expectedImage = Path.GetFullPath(executable);
|
|
1613
|
+
if (!String.Equals(actualImage, expectedImage, StringComparison.OrdinalIgnoreCase)) throw new InvalidDataException("IMAGE_IDENTITY_MISMATCH");
|
|
1614
|
+
|
|
1615
|
+
FileStream stdoutStream = new FileStream(new SafeFileHandle(stdoutRead, true), FileAccess.Read, 4096, false);
|
|
1616
|
+
stdoutRead = IntPtr.Zero;
|
|
1617
|
+
FileStream stderrStream = new FileStream(new SafeFileHandle(stderrRead, true), FileAccess.Read, 4096, false);
|
|
1618
|
+
stderrRead = IntPtr.Zero;
|
|
1619
|
+
stdoutTask = Task.Run(() => ReadBounded(stdoutStream, stdoutLimit));
|
|
1620
|
+
stderrTask = Task.Run(() => ReadBounded(stderrStream, stderrLimit));
|
|
1621
|
+
|
|
1622
|
+
using (FileStream stdinStream = new FileStream(new SafeFileHandle(stdinWrite, true), FileAccess.Write, 4096, false))
|
|
1623
|
+
{
|
|
1624
|
+
stdinWrite = IntPtr.Zero;
|
|
1625
|
+
if (stdinBytes.Length > 0) stdinStream.Write(stdinBytes, 0, stdinBytes.Length);
|
|
1626
|
+
stdinStream.Flush();
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
bool timedOut = false;
|
|
1630
|
+
bool canceled = false;
|
|
1631
|
+
while (true)
|
|
1632
|
+
{
|
|
1633
|
+
if (cancel != null && cancel.WaitOne(0))
|
|
1634
|
+
{
|
|
1635
|
+
canceled = true;
|
|
1636
|
+
break;
|
|
1637
|
+
}
|
|
1638
|
+
long remaining = timeoutMs - timer.ElapsedMilliseconds;
|
|
1639
|
+
if (remaining <= 0)
|
|
1640
|
+
{
|
|
1641
|
+
timedOut = true;
|
|
1642
|
+
break;
|
|
1643
|
+
}
|
|
1644
|
+
uint wait = WaitForSingleObject(processInfo.hProcess, (uint)Math.Min(remaining, 25));
|
|
1645
|
+
if (wait == WaitObject0) break;
|
|
1646
|
+
if (wait != WaitTimeout) ThrowLastWin32("PROCESS_WAIT_FAILED");
|
|
1647
|
+
}
|
|
1648
|
+
if (timedOut || canceled)
|
|
1649
|
+
{
|
|
1650
|
+
HostLifetime.ReleaseJob(jobRegistration, ref job);
|
|
1651
|
+
if (WaitForSingleObject(processInfo.hProcess, 10000) != WaitObject0) throw new InvalidDataException("PROCESS_TERMINATION_TIMEOUT");
|
|
1652
|
+
}
|
|
1653
|
+
uint exitCode;
|
|
1654
|
+
if (!GetExitCodeProcess(processInfo.hProcess, out exitCode)) ThrowLastWin32("EXIT_CODE_FAILED");
|
|
1655
|
+
if (!timedOut && !canceled) HostLifetime.ReleaseJob(jobRegistration, ref job);
|
|
1656
|
+
if (!Task.WaitAll(new Task[] { stdoutTask, stderrTask }, 10000)) throw new InvalidDataException("PROCESS_OUTPUT_DRAIN_TIMEOUT");
|
|
1657
|
+
BoundedReadResult stdout = stdoutTask.Result;
|
|
1658
|
+
BoundedReadResult stderr = stderrTask.Result;
|
|
1659
|
+
timer.Stop();
|
|
1660
|
+
return new ProcessRunResult
|
|
1661
|
+
{
|
|
1662
|
+
Ok = !timedOut && !canceled && exitCode == 0,
|
|
1663
|
+
Code = canceled ? "REQUEST_CANCELLED" : timedOut ? "PROCESS_TIMED_OUT" : "PROCESS_EXITED",
|
|
1664
|
+
ExitCode = exitCode,
|
|
1665
|
+
TimedOut = timedOut,
|
|
1666
|
+
ProcessId = processInfo.dwProcessId,
|
|
1667
|
+
JobAssignedAtCreation = true,
|
|
1668
|
+
ExactHandleList = true,
|
|
1669
|
+
ImageIdentityVerified = true,
|
|
1670
|
+
VolumeSerial = fileInfo.dwVolumeSerialNumber,
|
|
1671
|
+
FileIndex = ((ulong)fileInfo.nFileIndexHigh << 32) | fileInfo.nFileIndexLow,
|
|
1672
|
+
NumberOfLinks = fileInfo.nNumberOfLinks,
|
|
1673
|
+
Stdout = stdout.Bytes,
|
|
1674
|
+
Stderr = stderr.Bytes,
|
|
1675
|
+
StdoutTotalBytes = stdout.TotalBytes,
|
|
1676
|
+
StderrTotalBytes = stderr.TotalBytes,
|
|
1677
|
+
StdoutDroppedBytes = stdout.DroppedBytes,
|
|
1678
|
+
StderrDroppedBytes = stderr.DroppedBytes,
|
|
1679
|
+
StdoutTruncated = stdout.Truncated,
|
|
1680
|
+
StderrTruncated = stderr.Truncated,
|
|
1681
|
+
ElapsedMilliseconds = timer.ElapsedMilliseconds
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
finally
|
|
1685
|
+
{
|
|
1686
|
+
CloseIfValid(ref processInfo.hThread);
|
|
1687
|
+
CloseIfValid(ref processInfo.hProcess);
|
|
1688
|
+
CloseIfValid(ref stdinRead);
|
|
1689
|
+
CloseIfValid(ref stdinWrite);
|
|
1690
|
+
CloseIfValid(ref stdoutRead);
|
|
1691
|
+
CloseIfValid(ref stdoutWrite);
|
|
1692
|
+
CloseIfValid(ref stderrRead);
|
|
1693
|
+
CloseIfValid(ref stderrWrite);
|
|
1694
|
+
HostLifetime.ReleaseJob(jobRegistration, ref job);
|
|
1695
|
+
if (attributeList != IntPtr.Zero)
|
|
1696
|
+
{
|
|
1697
|
+
DeleteProcThreadAttributeList(attributeList);
|
|
1698
|
+
Marshal.FreeHGlobal(attributeList);
|
|
1699
|
+
}
|
|
1700
|
+
if (jobValue != IntPtr.Zero) Marshal.FreeHGlobal(jobValue);
|
|
1701
|
+
if (handleValues != IntPtr.Zero) Marshal.FreeHGlobal(handleValues);
|
|
1702
|
+
if (environmentPointer != IntPtr.Zero) Marshal.FreeHGlobal(environmentPointer);
|
|
1703
|
+
if (executableHandle != IntPtr.Zero && executableHandle != new IntPtr(-1)) CloseHandle(executableHandle);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
private static BoundedReadResult ReadBounded(Stream stream, int limit)
|
|
1708
|
+
{
|
|
1709
|
+
return ReadBounded(stream, limit, null, null);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
private static BoundedReadResult ReadBounded(Stream stream, int limit, byte[] marker, EventWaitHandle markerSeen)
|
|
1713
|
+
{
|
|
1714
|
+
using (stream)
|
|
1715
|
+
{
|
|
1716
|
+
byte[] buffer = new byte[8192];
|
|
1717
|
+
Queue<byte[]> chunks = new Queue<byte[]>();
|
|
1718
|
+
int retained = 0;
|
|
1719
|
+
long total = 0;
|
|
1720
|
+
int markerIndex = 0;
|
|
1721
|
+
while (true)
|
|
1722
|
+
{
|
|
1723
|
+
int read = stream.Read(buffer, 0, buffer.Length);
|
|
1724
|
+
if (read == 0) break;
|
|
1725
|
+
total += read;
|
|
1726
|
+
if (marker != null && markerSeen != null && !markerSeen.WaitOne(0))
|
|
1727
|
+
{
|
|
1728
|
+
for (int index = 0; index < read; index++)
|
|
1729
|
+
{
|
|
1730
|
+
if (buffer[index] == marker[markerIndex])
|
|
1731
|
+
{
|
|
1732
|
+
markerIndex++;
|
|
1733
|
+
if (markerIndex == marker.Length)
|
|
1734
|
+
{
|
|
1735
|
+
markerSeen.Set();
|
|
1736
|
+
break;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
else
|
|
1740
|
+
{
|
|
1741
|
+
markerIndex = buffer[index] == marker[0] ? 1 : 0;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
byte[] copy = new byte[read];
|
|
1746
|
+
Buffer.BlockCopy(buffer, 0, copy, 0, read);
|
|
1747
|
+
chunks.Enqueue(copy);
|
|
1748
|
+
retained += read;
|
|
1749
|
+
while (retained > limit && chunks.Count > 0)
|
|
1750
|
+
{
|
|
1751
|
+
byte[] first = chunks.Dequeue();
|
|
1752
|
+
int excess = retained - limit;
|
|
1753
|
+
if (first.Length > excess)
|
|
1754
|
+
{
|
|
1755
|
+
byte[] remainder = new byte[first.Length - excess];
|
|
1756
|
+
Buffer.BlockCopy(first, excess, remainder, 0, remainder.Length);
|
|
1757
|
+
chunks.Enqueue(remainder);
|
|
1758
|
+
Queue<byte[]> reordered = new Queue<byte[]>();
|
|
1759
|
+
reordered.Enqueue(remainder);
|
|
1760
|
+
while (chunks.Count > 0)
|
|
1761
|
+
{
|
|
1762
|
+
byte[] item = chunks.Dequeue();
|
|
1763
|
+
if (!Object.ReferenceEquals(item, remainder)) reordered.Enqueue(item);
|
|
1764
|
+
}
|
|
1765
|
+
chunks = reordered;
|
|
1766
|
+
retained -= excess;
|
|
1767
|
+
break;
|
|
1768
|
+
}
|
|
1769
|
+
retained -= first.Length;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
byte[] result = new byte[retained];
|
|
1773
|
+
int offset = 0;
|
|
1774
|
+
foreach (byte[] chunk in chunks)
|
|
1775
|
+
{
|
|
1776
|
+
Buffer.BlockCopy(chunk, 0, result, offset, chunk.Length);
|
|
1777
|
+
offset += chunk.Length;
|
|
1778
|
+
}
|
|
1779
|
+
return new BoundedReadResult
|
|
1780
|
+
{
|
|
1781
|
+
Bytes = result,
|
|
1782
|
+
TotalBytes = total,
|
|
1783
|
+
DroppedBytes = total - retained,
|
|
1784
|
+
Truncated = total > retained
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
private static Dictionary<string, object> SuccessResult()
|
|
1790
|
+
{
|
|
1791
|
+
Dictionary<string, object> value = new Dictionary<string, object>();
|
|
1792
|
+
value["schemaVersion"] = 1;
|
|
1793
|
+
value["ok"] = true;
|
|
1794
|
+
value["code"] = "OK";
|
|
1795
|
+
return value;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
private static Dictionary<string, object> ErrorResult(string code)
|
|
1799
|
+
{
|
|
1800
|
+
Dictionary<string, object> value = new Dictionary<string, object>();
|
|
1801
|
+
value["schemaVersion"] = 1;
|
|
1802
|
+
value["ok"] = false;
|
|
1803
|
+
value["code"] = code;
|
|
1804
|
+
return value;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
private static void WriteJsonFrame(Stream output, byte[] key, ref uint sequence, ushort kind, byte[] requestId, ulong generation, Dictionary<string, object> value)
|
|
1808
|
+
{
|
|
1809
|
+
byte[] payload = new UTF8Encoding(false, true).GetBytes(Json.Serialize(value));
|
|
1810
|
+
WriteFrame(output, key, sequence, kind, 0, requestId, generation, payload);
|
|
1811
|
+
sequence++;
|
|
1812
|
+
if (sequence == 0) throw new InvalidDataException("SEQUENCE_EXHAUSTED");
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
private static Frame ReadFrame(Stream input, byte[] key, uint expectedSequence)
|
|
1816
|
+
{
|
|
1817
|
+
byte[] header = ReadExact(input, HeaderLength, true);
|
|
1818
|
+
if (header == null) return null;
|
|
1819
|
+
if (header[0] != 0x43 || header[1] != 0x58 || header[2] != 0x50 || header[3] != 0x34) throw new InvalidDataException("BAD_MAGIC");
|
|
1820
|
+
if (BitConverter.ToUInt16(header, 4) != ProtocolVersion) throw new InvalidDataException("BAD_VERSION");
|
|
1821
|
+
if (BitConverter.ToUInt16(header, 6) != HeaderLength) throw new InvalidDataException("BAD_HEADER_LENGTH");
|
|
1822
|
+
ushort kind = BitConverter.ToUInt16(header, 8);
|
|
1823
|
+
ushort flags = BitConverter.ToUInt16(header, 10);
|
|
1824
|
+
uint sequence = BitConverter.ToUInt32(header, 12);
|
|
1825
|
+
if (sequence != expectedSequence) throw new InvalidDataException(sequence < expectedSequence ? "DUPLICATE_SEQUENCE" : "OUT_OF_ORDER_SEQUENCE");
|
|
1826
|
+
if (!IsKnownKind(kind)) throw new InvalidDataException("UNKNOWN_FRAME_KIND");
|
|
1827
|
+
if ((flags & ~AllowedFlags(kind)) != 0) throw new InvalidDataException("INVALID_FLAGS");
|
|
1828
|
+
uint payloadLength = BitConverter.ToUInt32(header, 40);
|
|
1829
|
+
if (payloadLength > MaxPayload) throw new InvalidDataException("FRAME_TOO_LARGE");
|
|
1830
|
+
if ((kind == Hello || kind == HelloAck) && payloadLength > MaxHelloPayload) throw new InvalidDataException("HELLO_TOO_LARGE");
|
|
1831
|
+
if (BitConverter.ToUInt32(header, 44) != 0) throw new InvalidDataException("NONZERO_RESERVED");
|
|
1832
|
+
if (kind == Credit && payloadLength != 8) throw new InvalidDataException("INVALID_CREDIT_LENGTH");
|
|
1833
|
+
if (kind == Cancel && payloadLength != 0) throw new InvalidDataException("INVALID_CANCEL_LENGTH");
|
|
1834
|
+
byte[] payload = ReadExact(input, checked((int)payloadLength), false) ?? new byte[0];
|
|
1835
|
+
byte[] expectedTag = ComputeTag(key, header, payload);
|
|
1836
|
+
if (!FixedEquals(expectedTag, 0, header, TagOffset, TagLength)) throw new InvalidDataException("BAD_AUTH_TAG");
|
|
1837
|
+
byte[] requestId = new byte[16];
|
|
1838
|
+
Buffer.BlockCopy(header, 16, requestId, 0, 16);
|
|
1839
|
+
return new Frame
|
|
1840
|
+
{
|
|
1841
|
+
Kind = kind,
|
|
1842
|
+
Flags = flags,
|
|
1843
|
+
Sequence = sequence,
|
|
1844
|
+
RequestId = requestId,
|
|
1845
|
+
ProcessGeneration = BitConverter.ToUInt64(header, 32),
|
|
1846
|
+
Payload = payload,
|
|
1847
|
+
FrameBytes = HeaderLength + payload.Length
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
private static void WriteFrame(Stream output, byte[] key, uint sequence, ushort kind, ushort flags, byte[] requestId, ulong generation, byte[] payload)
|
|
1852
|
+
{
|
|
1853
|
+
if (payload.Length > MaxPayload) throw new InvalidDataException("FRAME_TOO_LARGE");
|
|
1854
|
+
byte[] header = new byte[HeaderLength];
|
|
1855
|
+
header[0] = 0x43; header[1] = 0x58; header[2] = 0x50; header[3] = 0x34;
|
|
1856
|
+
WriteUInt16(header, 4, ProtocolVersion);
|
|
1857
|
+
WriteUInt16(header, 6, HeaderLength);
|
|
1858
|
+
WriteUInt16(header, 8, kind);
|
|
1859
|
+
WriteUInt16(header, 10, flags);
|
|
1860
|
+
WriteUInt32(header, 12, sequence);
|
|
1861
|
+
Buffer.BlockCopy(requestId, 0, header, 16, 16);
|
|
1862
|
+
WriteUInt64(header, 32, generation);
|
|
1863
|
+
WriteUInt32(header, 40, (uint)payload.Length);
|
|
1864
|
+
byte[] tag = ComputeTag(key, header, payload);
|
|
1865
|
+
Buffer.BlockCopy(tag, 0, header, TagOffset, TagLength);
|
|
1866
|
+
output.Write(header, 0, header.Length);
|
|
1867
|
+
if (payload.Length > 0) output.Write(payload, 0, payload.Length);
|
|
1868
|
+
output.Flush();
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
private static byte[] ComputeTag(byte[] key, byte[] header, byte[] payload)
|
|
1872
|
+
{
|
|
1873
|
+
using (HMACSHA256 hmac = new HMACSHA256(key))
|
|
1874
|
+
{
|
|
1875
|
+
byte[] covered = new byte[TagOffset + payload.Length];
|
|
1876
|
+
Buffer.BlockCopy(header, 0, covered, 0, TagOffset);
|
|
1877
|
+
if (payload.Length > 0) Buffer.BlockCopy(payload, 0, covered, TagOffset, payload.Length);
|
|
1878
|
+
byte[] full = hmac.ComputeHash(covered);
|
|
1879
|
+
byte[] tag = new byte[TagLength];
|
|
1880
|
+
Buffer.BlockCopy(full, 0, tag, 0, TagLength);
|
|
1881
|
+
return tag;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
private static bool FixedEquals(byte[] left, int leftOffset, byte[] right, int rightOffset, int length)
|
|
1886
|
+
{
|
|
1887
|
+
int difference = 0;
|
|
1888
|
+
for (int index = 0; index < length; index++) difference |= left[leftOffset + index] ^ right[rightOffset + index];
|
|
1889
|
+
return difference == 0;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
private static Dictionary<string, object> ParseStrictObject(byte[] payload)
|
|
1893
|
+
{
|
|
1894
|
+
string text;
|
|
1895
|
+
try { text = new UTF8Encoding(false, true).GetString(payload); }
|
|
1896
|
+
catch { throw new InvalidDataException("INVALID_UTF8"); }
|
|
1897
|
+
JsonDuplicateKeyDetector.Validate(text, 16, 256, 16384);
|
|
1898
|
+
object parsed;
|
|
1899
|
+
try { parsed = Json.DeserializeObject(text); }
|
|
1900
|
+
catch { throw new InvalidDataException("INVALID_JSON"); }
|
|
1901
|
+
Dictionary<string, object> value = parsed as Dictionary<string, object>;
|
|
1902
|
+
if (value == null) throw new InvalidDataException("JSON_OBJECT_REQUIRED");
|
|
1903
|
+
return value;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
private static class JsonDuplicateKeyDetector
|
|
1907
|
+
{
|
|
1908
|
+
private sealed class Context
|
|
1909
|
+
{
|
|
1910
|
+
public bool Object;
|
|
1911
|
+
public HashSet<string> Keys;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
public static void Validate(string text, int maxDepth, int maxKeys, int maxStringLength)
|
|
1915
|
+
{
|
|
1916
|
+
Stack<Context> stack = new Stack<Context>();
|
|
1917
|
+
int totalKeys = 0;
|
|
1918
|
+
for (int index = 0; index < text.Length; index++)
|
|
1919
|
+
{
|
|
1920
|
+
char current = text[index];
|
|
1921
|
+
if (current == '"')
|
|
1922
|
+
{
|
|
1923
|
+
int start = index;
|
|
1924
|
+
bool escaped = false;
|
|
1925
|
+
index++;
|
|
1926
|
+
while (index < text.Length)
|
|
1927
|
+
{
|
|
1928
|
+
char character = text[index];
|
|
1929
|
+
if (escaped) escaped = false;
|
|
1930
|
+
else if (character == '\\') escaped = true;
|
|
1931
|
+
else if (character == '"') break;
|
|
1932
|
+
index++;
|
|
1933
|
+
}
|
|
1934
|
+
if (index >= text.Length) throw new InvalidDataException("INVALID_JSON");
|
|
1935
|
+
string literal = text.Substring(start, index - start + 1);
|
|
1936
|
+
string value;
|
|
1937
|
+
try { value = Json.Deserialize<string>(literal); }
|
|
1938
|
+
catch { throw new InvalidDataException("INVALID_JSON"); }
|
|
1939
|
+
if (value.Length > maxStringLength) throw new InvalidDataException("JSON_STRING_TOO_LONG");
|
|
1940
|
+
int cursor = index + 1;
|
|
1941
|
+
while (cursor < text.Length && Char.IsWhiteSpace(text[cursor])) cursor++;
|
|
1942
|
+
if (cursor < text.Length && text[cursor] == ':')
|
|
1943
|
+
{
|
|
1944
|
+
if (stack.Count == 0 || !stack.Peek().Object) throw new InvalidDataException("INVALID_JSON");
|
|
1945
|
+
if (!stack.Peek().Keys.Add(value)) throw new InvalidDataException("DUPLICATE_JSON_KEY");
|
|
1946
|
+
totalKeys++;
|
|
1947
|
+
if (totalKeys > maxKeys) throw new InvalidDataException("JSON_TOO_MANY_KEYS");
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
else if (current == '{')
|
|
1951
|
+
{
|
|
1952
|
+
stack.Push(new Context { Object = true, Keys = new HashSet<string>(StringComparer.Ordinal) });
|
|
1953
|
+
if (stack.Count > maxDepth) throw new InvalidDataException("JSON_TOO_DEEP");
|
|
1954
|
+
}
|
|
1955
|
+
else if (current == '[')
|
|
1956
|
+
{
|
|
1957
|
+
stack.Push(new Context { Object = false, Keys = null });
|
|
1958
|
+
if (stack.Count > maxDepth) throw new InvalidDataException("JSON_TOO_DEEP");
|
|
1959
|
+
}
|
|
1960
|
+
else if (current == '}' || current == ']')
|
|
1961
|
+
{
|
|
1962
|
+
if (stack.Count == 0) throw new InvalidDataException("INVALID_JSON");
|
|
1963
|
+
Context context = stack.Pop();
|
|
1964
|
+
if ((current == '}') != context.Object) throw new InvalidDataException("INVALID_JSON");
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
if (stack.Count != 0) throw new InvalidDataException("INVALID_JSON");
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
private static Dictionary<string, object> AsObject(object value, string name)
|
|
1972
|
+
{
|
|
1973
|
+
Dictionary<string, object> result = value as Dictionary<string, object>;
|
|
1974
|
+
if (result == null) throw new InvalidDataException("INVALID_" + name.ToUpperInvariant());
|
|
1975
|
+
return result;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
private static SortedDictionary<string, string> AsEnvironment(object value)
|
|
1979
|
+
{
|
|
1980
|
+
Dictionary<string, object> source = AsObject(value, "environment");
|
|
1981
|
+
if (source.Count > 64) throw new InvalidDataException("ENVIRONMENT_TOO_LARGE");
|
|
1982
|
+
SortedDictionary<string, string> result = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
1983
|
+
foreach (KeyValuePair<string, object> entry in source)
|
|
1984
|
+
{
|
|
1985
|
+
if (String.IsNullOrEmpty(entry.Key) || entry.Key.Length > 128 || entry.Key.IndexOf('=') >= 0 || entry.Key.IndexOf('\0') >= 0)
|
|
1986
|
+
throw new InvalidDataException("INVALID_ENVIRONMENT_KEY");
|
|
1987
|
+
if (result.ContainsKey(entry.Key)) throw new InvalidDataException("DUPLICATE_ENVIRONMENT_KEY");
|
|
1988
|
+
result.Add(entry.Key, RequireBoundedString(entry.Value, "environmentValue", 0, 32768));
|
|
1989
|
+
}
|
|
1990
|
+
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? "C:\\Windows";
|
|
1991
|
+
string systemDriveRoot = Path.GetPathRoot(systemRoot);
|
|
1992
|
+
string systemDrive = systemDriveRoot.TrimEnd('\\');
|
|
1993
|
+
result["SystemDrive"] = systemDrive;
|
|
1994
|
+
result["SystemRoot"] = systemRoot;
|
|
1995
|
+
result["WINDIR"] = systemRoot;
|
|
1996
|
+
result["ProgramData"] = Path.Combine(systemDriveRoot, "ProgramData");
|
|
1997
|
+
result["ComSpec"] = Path.Combine(systemRoot, "System32", "cmd.exe");
|
|
1998
|
+
result["PATH"] = Path.Combine(systemRoot, "System32") + ";" + systemRoot;
|
|
1999
|
+
result["PATHEXT"] = ".COM;.EXE;.BAT;.CMD";
|
|
2000
|
+
return result;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
private static string BuildEnvironmentBlock(SortedDictionary<string, string> environment)
|
|
2004
|
+
{
|
|
2005
|
+
StringBuilder block = new StringBuilder();
|
|
2006
|
+
foreach (KeyValuePair<string, string> entry in environment)
|
|
2007
|
+
{
|
|
2008
|
+
block.Append(entry.Key).Append('=').Append(entry.Value).Append('\0');
|
|
2009
|
+
}
|
|
2010
|
+
block.Append('\0');
|
|
2011
|
+
return block.ToString();
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
private static string[] AsStringArray(object value, string name, int maxItems, int maxStringLength)
|
|
2015
|
+
{
|
|
2016
|
+
IList array = value as IList;
|
|
2017
|
+
if (array == null || array.Count > maxItems) throw new InvalidDataException("INVALID_" + name.ToUpperInvariant());
|
|
2018
|
+
string[] result = new string[array.Count];
|
|
2019
|
+
int totalBytes = 0;
|
|
2020
|
+
for (int index = 0; index < array.Count; index++)
|
|
2021
|
+
{
|
|
2022
|
+
result[index] = RequireBoundedString(array[index], name, 0, maxStringLength);
|
|
2023
|
+
totalBytes = checked(totalBytes + Encoding.UTF8.GetByteCount(result[index]));
|
|
2024
|
+
if (totalBytes > MaxArgumentTotalBytes) throw new InvalidDataException("INVALID_" + name.ToUpperInvariant());
|
|
2025
|
+
}
|
|
2026
|
+
return result;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
private static string RequireAbsoluteFile(object value)
|
|
2030
|
+
{
|
|
2031
|
+
string candidate = RequireBoundedString(value, "executable", 1, 32768);
|
|
2032
|
+
if (!Path.IsPathRooted(candidate) || !File.Exists(candidate)) throw new InvalidDataException("INVALID_EXECUTABLE_PATH");
|
|
2033
|
+
return Path.GetFullPath(candidate);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
private static string RequireAbsoluteDirectory(object value)
|
|
2037
|
+
{
|
|
2038
|
+
string candidate = RequireBoundedString(value, "cwd", 1, 32768);
|
|
2039
|
+
if (!Path.IsPathRooted(candidate) || !Directory.Exists(candidate)) throw new InvalidDataException("INVALID_CWD");
|
|
2040
|
+
return Path.GetFullPath(candidate);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
private static string RequireSafeNonce(object value)
|
|
2044
|
+
{
|
|
2045
|
+
string nonce = RequireBoundedString(value, "nonce", 64, 64);
|
|
2046
|
+
for (int index = 0; index < nonce.Length; index++)
|
|
2047
|
+
{
|
|
2048
|
+
char character = nonce[index];
|
|
2049
|
+
bool hex = (character >= '0' && character <= '9') || (character >= 'a' && character <= 'f');
|
|
2050
|
+
if (!hex) throw new InvalidDataException("INVALID_NONCE");
|
|
2051
|
+
}
|
|
2052
|
+
return nonce;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
private static string RequireBoundedString(object value, string name, int minimum, int maximum)
|
|
2056
|
+
{
|
|
2057
|
+
string result = value as string;
|
|
2058
|
+
if (result == null || result.Length < minimum || result.Length > maximum || result.IndexOf('\0') >= 0)
|
|
2059
|
+
throw new InvalidDataException("INVALID_" + name.ToUpperInvariant());
|
|
2060
|
+
return result;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
private static int ToInt(object value, string name)
|
|
2064
|
+
{
|
|
2065
|
+
try { return Convert.ToInt32(value); }
|
|
2066
|
+
catch { throw new InvalidDataException("INVALID_" + name.ToUpperInvariant()); }
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
private static int ToBoundedInt(object value, string name, int minimum, int maximum)
|
|
2070
|
+
{
|
|
2071
|
+
int result = ToInt(value, name);
|
|
2072
|
+
if (result < minimum || result > maximum) throw new InvalidDataException("INVALID_" + name.ToUpperInvariant());
|
|
2073
|
+
return result;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
private static void RequireExactKeys(Dictionary<string, object> value, string[] keys)
|
|
2077
|
+
{
|
|
2078
|
+
HashSet<string> expected = new HashSet<string>(keys, StringComparer.Ordinal);
|
|
2079
|
+
if (value.Count != expected.Count) throw new InvalidDataException("UNKNOWN_JSON_FIELD");
|
|
2080
|
+
foreach (string key in value.Keys) if (!expected.Contains(key)) throw new InvalidDataException("UNKNOWN_JSON_FIELD");
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
private static string BuildCommandLine(string executable, string[] arguments)
|
|
2084
|
+
{
|
|
2085
|
+
StringBuilder command = new StringBuilder();
|
|
2086
|
+
command.Append(QuoteArgument(executable));
|
|
2087
|
+
foreach (string argument in arguments) command.Append(' ').Append(QuoteArgument(argument));
|
|
2088
|
+
return command.ToString();
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
private static string QuoteArgument(string value)
|
|
2092
|
+
{
|
|
2093
|
+
if (value.Length > 0 && value.IndexOfAny(new char[] { ' ', '\t', '\n', '\v', '"' }) < 0) return value;
|
|
2094
|
+
StringBuilder result = new StringBuilder();
|
|
2095
|
+
result.Append('"');
|
|
2096
|
+
int backslashes = 0;
|
|
2097
|
+
foreach (char character in value)
|
|
2098
|
+
{
|
|
2099
|
+
if (character == '\\') { backslashes++; continue; }
|
|
2100
|
+
if (character == '"')
|
|
2101
|
+
{
|
|
2102
|
+
result.Append('\\', backslashes * 2 + 1).Append('"');
|
|
2103
|
+
backslashes = 0;
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
result.Append('\\', backslashes).Append(character);
|
|
2107
|
+
backslashes = 0;
|
|
2108
|
+
}
|
|
2109
|
+
result.Append('\\', backslashes * 2).Append('"');
|
|
2110
|
+
return result.ToString();
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
private static string SafeErrorCode(Exception error)
|
|
2114
|
+
{
|
|
2115
|
+
string message = error.Message ?? "HOST_ERROR";
|
|
2116
|
+
int separator = message.IndexOf(':');
|
|
2117
|
+
string candidate = separator >= 0 ? message.Substring(0, separator) : message;
|
|
2118
|
+
StringBuilder safe = new StringBuilder();
|
|
2119
|
+
foreach (char character in candidate.ToUpperInvariant())
|
|
2120
|
+
{
|
|
2121
|
+
if ((character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || character == '_') safe.Append(character);
|
|
2122
|
+
}
|
|
2123
|
+
return safe.Length == 0 || safe.Length > 80 ? "HOST_ERROR" : safe.ToString();
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
private static bool IsKnownKind(ushort kind)
|
|
2127
|
+
{
|
|
2128
|
+
return kind == Hello || kind == HelloAck || kind == RequestJson || kind == ResponseJson || kind == EventJson || kind == Output || kind == Input || kind == Credit || kind == Cancel || kind == Fatal;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
private static ushort AllowedFlags(ushort kind)
|
|
2132
|
+
{
|
|
2133
|
+
if (kind == Output) return FlagStderr | FlagEof;
|
|
2134
|
+
if (kind == Input) return FlagEof;
|
|
2135
|
+
return 0;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
private static bool HasProc(string moduleName, string procName)
|
|
2139
|
+
{
|
|
2140
|
+
IntPtr module = GetModuleHandleW(moduleName);
|
|
2141
|
+
return module != IntPtr.Zero && GetProcAddress(module, procName) != IntPtr.Zero;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
private static byte[] ReadExact(Stream stream, int length, bool allowCleanEof)
|
|
2145
|
+
{
|
|
2146
|
+
byte[] result = new byte[length];
|
|
2147
|
+
int offset = 0;
|
|
2148
|
+
while (offset < length)
|
|
2149
|
+
{
|
|
2150
|
+
int read = stream.Read(result, offset, length - offset);
|
|
2151
|
+
if (read == 0)
|
|
2152
|
+
{
|
|
2153
|
+
if (offset == 0 && allowCleanEof) return null;
|
|
2154
|
+
throw new EndOfStreamException("TRUNCATED_FRAME");
|
|
2155
|
+
}
|
|
2156
|
+
offset += read;
|
|
2157
|
+
}
|
|
2158
|
+
return result;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
private static void WriteUInt16(byte[] buffer, int offset, ushort value) { Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buffer, offset, 2); }
|
|
2162
|
+
private static void WriteUInt32(byte[] buffer, int offset, uint value) { Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buffer, offset, 4); }
|
|
2163
|
+
private static void WriteUInt64(byte[] buffer, int offset, ulong value) { Buffer.BlockCopy(BitConverter.GetBytes(value), 0, buffer, offset, 8); }
|
|
2164
|
+
|
|
2165
|
+
private static void ThrowLastWin32(string code) { throw new Win32Exception(Marshal.GetLastWin32Error(), code); }
|
|
2166
|
+
|
|
2167
|
+
private static void CloseIfValid(ref IntPtr handle)
|
|
2168
|
+
{
|
|
2169
|
+
if (handle != IntPtr.Zero && handle != new IntPtr(-1)) CloseHandle(handle);
|
|
2170
|
+
handle = IntPtr.Zero;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
2174
|
+
private struct COORD
|
|
2175
|
+
{
|
|
2176
|
+
public short X;
|
|
2177
|
+
public short Y;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
2181
|
+
private struct SECURITY_ATTRIBUTES
|
|
2182
|
+
{
|
|
2183
|
+
public int nLength;
|
|
2184
|
+
public IntPtr lpSecurityDescriptor;
|
|
2185
|
+
public int bInheritHandle;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
2189
|
+
private struct STARTUPINFO
|
|
2190
|
+
{
|
|
2191
|
+
public int cb;
|
|
2192
|
+
public string lpReserved;
|
|
2193
|
+
public string lpDesktop;
|
|
2194
|
+
public string lpTitle;
|
|
2195
|
+
public int dwX;
|
|
2196
|
+
public int dwY;
|
|
2197
|
+
public int dwXSize;
|
|
2198
|
+
public int dwYSize;
|
|
2199
|
+
public int dwXCountChars;
|
|
2200
|
+
public int dwYCountChars;
|
|
2201
|
+
public int dwFillAttribute;
|
|
2202
|
+
public int dwFlags;
|
|
2203
|
+
public short wShowWindow;
|
|
2204
|
+
public short cbReserved2;
|
|
2205
|
+
public IntPtr lpReserved2;
|
|
2206
|
+
public IntPtr hStdInput;
|
|
2207
|
+
public IntPtr hStdOutput;
|
|
2208
|
+
public IntPtr hStdError;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
2212
|
+
private struct STARTUPINFOEX
|
|
2213
|
+
{
|
|
2214
|
+
public STARTUPINFO StartupInfo;
|
|
2215
|
+
public IntPtr lpAttributeList;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
2219
|
+
private struct PROCESS_INFORMATION
|
|
2220
|
+
{
|
|
2221
|
+
public IntPtr hProcess;
|
|
2222
|
+
public IntPtr hThread;
|
|
2223
|
+
public int dwProcessId;
|
|
2224
|
+
public int dwThreadId;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
2228
|
+
private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
|
2229
|
+
{
|
|
2230
|
+
public long PerProcessUserTimeLimit;
|
|
2231
|
+
public long PerJobUserTimeLimit;
|
|
2232
|
+
public uint LimitFlags;
|
|
2233
|
+
public UIntPtr MinimumWorkingSetSize;
|
|
2234
|
+
public UIntPtr MaximumWorkingSetSize;
|
|
2235
|
+
public uint ActiveProcessLimit;
|
|
2236
|
+
public long Affinity;
|
|
2237
|
+
public uint PriorityClass;
|
|
2238
|
+
public uint SchedulingClass;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
2242
|
+
private struct IO_COUNTERS
|
|
2243
|
+
{
|
|
2244
|
+
public ulong ReadOperationCount;
|
|
2245
|
+
public ulong WriteOperationCount;
|
|
2246
|
+
public ulong OtherOperationCount;
|
|
2247
|
+
public ulong ReadTransferCount;
|
|
2248
|
+
public ulong WriteTransferCount;
|
|
2249
|
+
public ulong OtherTransferCount;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
2253
|
+
private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
2254
|
+
{
|
|
2255
|
+
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
|
2256
|
+
public IO_COUNTERS IoInfo;
|
|
2257
|
+
public UIntPtr ProcessMemoryLimit;
|
|
2258
|
+
public UIntPtr JobMemoryLimit;
|
|
2259
|
+
public UIntPtr PeakProcessMemoryUsed;
|
|
2260
|
+
public UIntPtr PeakJobMemoryUsed;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
2264
|
+
private struct FILETIME
|
|
2265
|
+
{
|
|
2266
|
+
public uint dwLowDateTime;
|
|
2267
|
+
public uint dwHighDateTime;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
2271
|
+
private struct BY_HANDLE_FILE_INFORMATION
|
|
2272
|
+
{
|
|
2273
|
+
public uint dwFileAttributes;
|
|
2274
|
+
public FILETIME ftCreationTime;
|
|
2275
|
+
public FILETIME ftLastAccessTime;
|
|
2276
|
+
public FILETIME ftLastWriteTime;
|
|
2277
|
+
public uint dwVolumeSerialNumber;
|
|
2278
|
+
public uint nFileSizeHigh;
|
|
2279
|
+
public uint nFileSizeLow;
|
|
2280
|
+
public uint nNumberOfLinks;
|
|
2281
|
+
public uint nFileIndexHigh;
|
|
2282
|
+
public uint nFileIndexLow;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
2286
|
+
private static extern IntPtr CreateJobObjectW(IntPtr jobAttributes, string name);
|
|
2287
|
+
|
|
2288
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2289
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2290
|
+
private static extern bool SetInformationJobObject(IntPtr job, int informationClass, IntPtr information, uint informationLength);
|
|
2291
|
+
|
|
2292
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2293
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2294
|
+
private static extern bool InitializeProcThreadAttributeList(IntPtr attributeList, int attributeCount, uint flags, ref IntPtr size);
|
|
2295
|
+
|
|
2296
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2297
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2298
|
+
private static extern bool UpdateProcThreadAttribute(IntPtr attributeList, uint flags, IntPtr attribute, IntPtr value, IntPtr size, IntPtr previousValue, IntPtr returnSize);
|
|
2299
|
+
|
|
2300
|
+
[DllImport("kernel32.dll")]
|
|
2301
|
+
private static extern void DeleteProcThreadAttributeList(IntPtr attributeList);
|
|
2302
|
+
|
|
2303
|
+
[DllImport("kernel32.dll", EntryPoint = "CreatePseudoConsole", SetLastError = true)]
|
|
2304
|
+
private static extern int CreatePseudoConsoleSafe(COORD size, SafeFileHandle input, SafeFileHandle output, uint flags, out IntPtr pseudoConsole);
|
|
2305
|
+
|
|
2306
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2307
|
+
private static extern int ResizePseudoConsole(IntPtr pseudoConsole, COORD size);
|
|
2308
|
+
|
|
2309
|
+
[DllImport("kernel32.dll")]
|
|
2310
|
+
private static extern void ClosePseudoConsole(IntPtr pseudoConsole);
|
|
2311
|
+
|
|
2312
|
+
[DllImport("kernel32.dll", EntryPoint = "CreateProcessW", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
2313
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2314
|
+
private static extern bool CreateConPtyProcessW(string applicationName, string commandLine, ref SECURITY_ATTRIBUTES processAttributes, ref SECURITY_ATTRIBUTES threadAttributes, [MarshalAs(UnmanagedType.Bool)] bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, [In] ref STARTUPINFOEX startupInfo, out PROCESS_INFORMATION processInformation);
|
|
2315
|
+
|
|
2316
|
+
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
2317
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2318
|
+
private static extern bool CreateProcessW(string applicationName, StringBuilder commandLine, IntPtr processAttributes, IntPtr threadAttributes, [MarshalAs(UnmanagedType.Bool)] bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref STARTUPINFOEX startupInfo, out PROCESS_INFORMATION processInformation);
|
|
2319
|
+
|
|
2320
|
+
[DllImport("kernel32.dll", EntryPoint = "CreatePipe", SetLastError = true)]
|
|
2321
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2322
|
+
private static extern bool CreateConPtyPipe(out SafeFileHandle readPipe, out SafeFileHandle writePipe, IntPtr pipeAttributes, uint size);
|
|
2323
|
+
|
|
2324
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2325
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2326
|
+
private static extern bool CreatePipe(out IntPtr readPipe, out IntPtr writePipe, ref SECURITY_ATTRIBUTES pipeAttributes, uint size);
|
|
2327
|
+
|
|
2328
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2329
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2330
|
+
private static extern bool SetHandleInformation(IntPtr handle, uint mask, uint flags);
|
|
2331
|
+
|
|
2332
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2333
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2334
|
+
private static extern bool IsProcessInJob(IntPtr process, IntPtr job, [MarshalAs(UnmanagedType.Bool)] out bool result);
|
|
2335
|
+
|
|
2336
|
+
[DllImport("kernel32.dll")]
|
|
2337
|
+
private static extern IntPtr GetCurrentProcess();
|
|
2338
|
+
|
|
2339
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2340
|
+
private static extern uint ResumeThread(IntPtr thread);
|
|
2341
|
+
|
|
2342
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2343
|
+
private static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
|
|
2344
|
+
|
|
2345
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2346
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2347
|
+
private static extern bool GetExitCodeProcess(IntPtr process, out uint exitCode);
|
|
2348
|
+
|
|
2349
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2350
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2351
|
+
private static extern bool TerminateProcess(IntPtr process, uint exitCode);
|
|
2352
|
+
|
|
2353
|
+
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
2354
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2355
|
+
private static extern bool QueryFullProcessImageNameW(IntPtr process, uint flags, StringBuilder executableName, ref int size);
|
|
2356
|
+
|
|
2357
|
+
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
2358
|
+
private static extern IntPtr CreateFileW(string filename, uint desiredAccess, uint shareMode, IntPtr securityAttributes, uint creationDisposition, uint flagsAndAttributes, IntPtr templateFile);
|
|
2359
|
+
|
|
2360
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2361
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2362
|
+
private static extern bool GetFileInformationByHandle(IntPtr file, out BY_HANDLE_FILE_INFORMATION information);
|
|
2363
|
+
|
|
2364
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2365
|
+
private static extern IntPtr GetStdHandle(int standardHandle);
|
|
2366
|
+
|
|
2367
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2368
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2369
|
+
private static extern bool SetStdHandle(int standardHandle, IntPtr handle);
|
|
2370
|
+
|
|
2371
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2372
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2373
|
+
private static extern bool PeekNamedPipe(IntPtr pipe, IntPtr buffer, uint bufferSize, IntPtr bytesRead, out uint totalBytesAvailable, IntPtr bytesLeftThisMessage);
|
|
2374
|
+
|
|
2375
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
2376
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
2377
|
+
private static extern bool CloseHandle(IntPtr handle);
|
|
2378
|
+
|
|
2379
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
|
2380
|
+
private static extern IntPtr GetModuleHandleW(string moduleName);
|
|
2381
|
+
|
|
2382
|
+
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
|
|
2383
|
+
private static extern IntPtr GetProcAddress(IntPtr module, string procName);
|
|
2384
|
+
}
|
|
2385
|
+
}
|