ExecutionContext.cpp (9216B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "AppInstallerRuntime.h" 5 #include "Argument.h" 6 #include "COMContext.h" 7 #include "Command.h" 8 #include "ExecutionContext.h" 9 #include "Public/ShutdownMonitoring.h" 10 #include <winget/Checkpoint.h> 11 #include <winget/Reboot.h> 12 #include <winget/UserSettings.h> 13 #include <winget/NetworkSettings.h> 14 15 using namespace AppInstaller::Checkpoints; 16 17 namespace AppInstaller::CLI::Execution 18 { 19 using namespace Settings; 20 21 namespace 22 { 23 bool ShouldRemoveCheckpointDatabase(HRESULT hr) 24 { 25 switch (hr) 26 { 27 case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL: 28 case APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED: 29 case APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH: 30 return false; 31 default: 32 return true; 33 } 34 } 35 } 36 37 Context::~Context() 38 { 39 if (Settings::ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Resume)) 40 { 41 if (m_checkpointManager && (!IsTerminated() || ShouldRemoveCheckpointDatabase(GetTerminationHR()))) 42 { 43 m_checkpointManager->CleanUpDatabase(); 44 AppInstaller::Reboot::UnregisterRestartForWER(); 45 } 46 } 47 48 if (m_disableSignalTerminationHandlerOnExit) 49 { 50 EnableSignalTerminationHandler(false); 51 } 52 } 53 54 Context Context::CreateEmptyContext() 55 { 56 AppInstaller::ThreadLocalStorage::WingetThreadGlobals threadGlobals; 57 return Context(Reporter, threadGlobals); 58 } 59 60 std::unique_ptr<Context> Context::CreateSubContext() 61 { 62 auto clone = std::make_unique<Context>(Reporter, m_threadGlobals); 63 clone->m_flags = m_flags; 64 clone->m_executingCommand = m_executingCommand; 65 // If the parent is hooked up to the CTRL signal, have the clone be as well 66 if (m_disableSignalTerminationHandlerOnExit) 67 { 68 clone->EnableSignalTerminationHandler(); 69 } 70 CopyArgsToSubContext(clone.get()); 71 CopyDataToSubContext(clone.get()); 72 return clone; 73 } 74 75 void Context::CopyArgsToSubContext(Context* subContext) 76 { 77 auto argProperties = ArgumentCommon::GetFromExecArgs(Args); 78 for (const auto& arg : argProperties) 79 { 80 if (WI_IsFlagSet(arg.TypeCategory, ArgTypeCategory::CopyFlagToSubContext)) 81 { 82 subContext->Args.AddArg(arg.Type); 83 } 84 else if (WI_IsFlagSet(arg.TypeCategory, ArgTypeCategory::CopyValueToSubContext)) 85 { 86 subContext->Args.AddArg(arg.Type, Args.GetArg(arg.Type)); 87 } 88 } 89 } 90 91 void Context::CopyDataToSubContext(Context* subContext) 92 { 93 #define COPY_DATA_IF_EXISTS(dataType) \ 94 if (this->Contains(dataType)) \ 95 { \ 96 subContext->Add<dataType>(this->Get<dataType>()); \ 97 } 98 99 COPY_DATA_IF_EXISTS(Data::InstallerDownloadAuthenticators); 100 } 101 102 void Context::EnableSignalTerminationHandler(bool enabled) 103 { 104 ShutdownMonitoring::TerminationSignalHandler::EnableListener(enabled, this); 105 m_disableSignalTerminationHandlerOnExit = enabled; 106 } 107 108 void Context::UpdateForArgs() 109 { 110 // Change logging level to Info if Verbose not requested 111 if (Args.Contains(Args::Type::VerboseLogs)) 112 { 113 Logging::Log().SetLevel(Logging::Level::Verbose); 114 } 115 116 // Disable warnings if requested 117 if (Args.Contains(Args::Type::IgnoreWarnings)) 118 { 119 Reporter.SetLevelMask(Reporter::Level::Warning, false); 120 } 121 122 // Set proxy 123 if (Args.Contains(Args::Type::Proxy)) 124 { 125 Network().SetProxyUri(std::string{ Args.GetArg(Args::Type::Proxy) }); 126 } 127 else if (Args.Contains(Args::Type::NoProxy)) 128 { 129 Network().SetProxyUri(std::nullopt); 130 } 131 132 // Set visual style 133 if (Args.Contains(Args::Type::NoVT)) 134 { 135 Reporter.SetStyle(VisualStyle::NoVT); 136 } 137 else if (Args.Contains(Args::Type::RetroStyle)) 138 { 139 Reporter.SetStyle(VisualStyle::Retro); 140 } 141 else if (Args.Contains(Args::Type::RainbowStyle)) 142 { 143 Reporter.SetStyle(VisualStyle::Rainbow); 144 } 145 else 146 { 147 Reporter.SetStyle(User().Get<Setting::ProgressBarVisualStyle>()); 148 } 149 } 150 151 void Context::Terminate(HRESULT hr, std::string_view file, size_t line) 152 { 153 if (hr == APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED) 154 { 155 ++m_CtrlSignalCount; 156 // Use a more recognizable error 157 hr = E_ABORT; 158 159 // If things aren't terminating fast enough for the user, they will probably press CTRL+C again. 160 // In that case, we should forcibly terminate. 161 // Unless we want to spin a separate thread for all work, we have to just exit here. 162 if (m_CtrlSignalCount >= 2) 163 { 164 Reporter.CloseOutputStream(true); 165 Logging::Telemetry().LogCommandTermination(hr, file, line); 166 std::exit(hr); 167 } 168 } 169 else if (hr == APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED) 170 { 171 AICLI_LOG(CLI, Info, << "Got app termination signal"); 172 hr = E_ABORT; 173 } 174 175 Logging::Telemetry().LogCommandTermination(hr, file, line); 176 177 if (!m_isTerminated) 178 { 179 SetTerminationHR(hr); 180 } 181 } 182 183 void Context::SetTerminationHR(HRESULT hr) 184 { 185 m_terminationHR = hr; 186 m_isTerminated = true; 187 } 188 189 void Context::Cancel(CancelReason reason, bool bypassUser) 190 { 191 HRESULT hr = ToHRESULT(reason); 192 193 Terminate(hr); 194 Reporter.CancelInProgressTask(bypassUser, reason); 195 } 196 197 void Context::SetExecutionStage(Workflow::ExecutionStage stage) 198 { 199 if (m_executionStage == stage) 200 { 201 return; 202 } 203 else if (m_executionStage > stage) 204 { 205 THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), "Reporting ExecutionStage to an earlier Stage: current[%d], new[%d]", ToIntegral(m_executionStage), ToIntegral(stage)); 206 } 207 208 m_executionStage = stage; 209 GetThreadGlobals().GetTelemetryLogger().SetExecutionStage(static_cast<uint32_t>(m_executionStage)); 210 } 211 212 AppInstaller::ThreadLocalStorage::WingetThreadGlobals& Context::GetThreadGlobals() 213 { 214 return m_threadGlobals; 215 } 216 217 std::unique_ptr<AppInstaller::ThreadLocalStorage::PreviousThreadGlobals> Context::SetForCurrentThread() 218 { 219 return m_threadGlobals.SetForCurrentThread(); 220 } 221 222 #ifndef AICLI_DISABLE_TEST_HOOKS 223 bool Context::ShouldExecuteWorkflowTask(const Workflow::WorkflowTask& task) 224 { 225 return (m_shouldExecuteWorkflowTask ? m_shouldExecuteWorkflowTask(task) : true); 226 } 227 #endif 228 229 void ContextEnumBasedVariantMapActionCallback(const void* map, Data data, EnumBasedVariantMapAction action) 230 { 231 switch (action) 232 { 233 case EnumBasedVariantMapAction::Add: 234 AICLI_LOG(Workflow, Verbose, << "Setting data item: " << data); 235 break; 236 case EnumBasedVariantMapAction::Contains: 237 AICLI_LOG(Workflow, Verbose, << "Checking data item: " << data); 238 break; 239 case EnumBasedVariantMapAction::Get: 240 AICLI_LOG(Workflow, Verbose, << "Getting data item: " << data); 241 break; 242 } 243 244 UNREFERENCED_PARAMETER(map); 245 } 246 247 std::string Context::GetResumeId() 248 { 249 return m_checkpointManager->GetResumeId(); 250 } 251 252 std::optional<Checkpoint<AutomaticCheckpointData>> Context::LoadCheckpoint(const std::string& resumeId) 253 { 254 m_checkpointManager = std::make_unique<AppInstaller::Checkpoints::CheckpointManager>(resumeId); 255 return m_checkpointManager->GetAutomaticCheckpoint(); 256 } 257 258 std::vector<AppInstaller::Checkpoints::Checkpoint<Execution::Data>> Context::GetCheckpoints() 259 { 260 return m_checkpointManager->GetCheckpoints(); 261 } 262 263 void Context::Checkpoint(std::string_view checkpointName, std::vector<Execution::Data> contextData) 264 { 265 UNREFERENCED_PARAMETER(checkpointName); 266 UNREFERENCED_PARAMETER(contextData); 267 268 if (!m_checkpointManager) 269 { 270 m_checkpointManager = std::make_unique<AppInstaller::Checkpoints::CheckpointManager>(); 271 m_checkpointManager->CreateAutomaticCheckpoint(*this); 272 273 // Register for restart only when we first call checkpoint to support restarting from an unexpected shutdown. 274 AppInstaller::Reboot::RegisterRestartForWER("resume -g " + GetResumeId()); 275 } 276 277 // TODO: Capture context data for checkpoint. 278 } 279 }