static ADUC_Result StepsHandler_Install()

in src/extensions/update_manifest_handlers/steps_handler/src/steps_handler.cpp [748:1132]


static ADUC_Result StepsHandler_Install(const tagADUC_WorkflowData* workflowData)
{
    ADUC_Result result;
    result.ResultCode = ADUC_Result_Failure;
    result.ExtendedResultCode = 0;
    ADUC_WorkflowHandle handle = workflowData->WorkflowHandle;
    ADUC_WorkflowHandle stepHandle = nullptr;

    const char* workflowId = workflow_peek_id(handle);
    char* workFolder = workflow_get_workfolder(handle);
    JSON_Array* selectedComponentsArray = nullptr;
    int workflowLevel = workflow_get_level(handle);
    int workflowStep = workflow_get_step_index(handle);
    int selectedComponentsCount = 0;
    char* serializedComponentString = nullptr;
    bool isComponentsEnumeratorRegistered = ExtensionManager::IsComponentsEnumeratorRegistered();
    int createResult = 0;

    if (workflow_is_cancel_requested(handle))
    {
        Log_Info(
            "Install task cancelled (level: %d, step:%d, wfid:%s, h_addr:0x%x).",
            workflowLevel,
            workflowStep,
            workflowId,
            handle);
        result.ResultCode = ADUC_Result_Failure_Cancelled;
        result.ExtendedResultCode = 0;
        goto done;
    }

    Log_Debug(
        "\n#\n#Install task begin (level: %d, step:%d, wfid:%s, h_addr:0x%x).",
        workflowLevel,
        workflowStep,
        workflowId,
        handle);

    createResult = ADUC_SystemUtils_MkSandboxDirRecursive(workFolder);
    if (createResult != 0)
    {
        Log_Error("Unable to create folder %s, error %d", workFolder, createResult);
        result = { ADUC_Result_Failure, ADUC_ERC_STEPS_HANDLER_CREATE_SANDBOX_FAILURE };
        goto done;
    }

    result = PrepareStepsWorkflowDataObject(handle);
    if (IsAducResultCodeFailure(result.ResultCode))
    {
        workflow_set_result_details(handle, "Invalid steps workflow collection");
        goto done;
    }

    if (workflowLevel == 0 || !isComponentsEnumeratorRegistered)
    {
        // If this is a top-level step or component enumerator is not registered, we will assume that this step is
        // intended for the host device and will set selectedComponentCount to 1 to iterate through
        // every step once  without setting any component data on the workflow.
        selectedComponentsCount = 1;
    }
    else
    {
        // This is a reference step (workflowLevel == 1), this intended for one or more components.
        result = GetSelectedComponentsArray(handle, &selectedComponentsArray);
        if (IsAducResultCodeFailure(result.ResultCode))
        {
            const char* fmt = "Missing selected components. workflow level %d, step %d";
            Log_Error(fmt, workflowLevel, workflowStep);
            workflow_set_result_details(handle, fmt, workflowLevel, workflowStep);
            goto done;
        }

        selectedComponentsCount = static_cast<int>(json_array_get_count(selectedComponentsArray));

        if (selectedComponentsCount == 0)
        {
            // If there's no matching component, we will consider this step 'optional' and this
            // step is no-op.
            const char* msg = "Optional step (no matching components)";
            Log_Debug(msg);
            result = { ADUC_Result_Install_Skipped_NoMatchingComponents };

            // This is a good opportunity to set the workflow state to indicates that
            // the current component is 'optional'.
            // We do this by setting workflow result code to ADUC_Result_Install_Skipped_NoMatchingComponents
            ADUC_Result currentResult = workflow_get_result(handle);
            if (IsAducResultCodeFailure(currentResult.ResultCode))
            {
                ADUC_Result newResult;
                newResult.ResultCode = ADUC_Result_Install_Skipped_NoMatchingComponents;
                newResult.ExtendedResultCode = 0;
                workflow_set_result(handle, newResult);
                workflow_set_result_details(handle, msg);
            }
        }
    }

    // For each selected component, perform step's backup, install & apply phase, restore phase if needed, in order.
    for (size_t iCom = 0, stepsCount = workflow_get_children_count(handle); iCom < selectedComponentsCount; iCom++)
    {
        serializedComponentString = CreateComponentSerializedString(selectedComponentsArray, iCom);

        //
        // For each step (child workflow), invoke backup, install and apply actions.
        // if install or apply fails, invoke restore action.
        //
        for (size_t i = 0; i < stepsCount; i++)
        {
            if (IsStepsHandlerExtraDebugLogsEnabled())
            {
                Log_Debug(
                    "Perform install action of child step #%d on component #%d.\n#### Component ####\n%s\n###################\n",
                    i,
                    iCom,
                    serializedComponentString);
            }

            // Use a wrapper workflow to hold a stepHandle.
            ADUC_WorkflowData stepWorkflow = {};

            stepHandle = workflow_get_child(handle, i);
            if (stepHandle == nullptr)
            {
                const char* errorFmt = "Cannot process step #%lu due to missing (child) workflow data.";
                Log_Error(errorFmt, i);
                result.ExtendedResultCode = ADUC_ERC_STEPS_HANDLER_INSTALL_FAILURE_MISSING_CHILD_WORKFLOW;
                workflow_set_result_details(handle, errorFmt, i);
                goto done;
            }
            stepWorkflow.WorkflowHandle = stepHandle;

            // For inline step - set current component info on the workflow.
            if (serializedComponentString != nullptr && workflow_is_inline_step(handle, i))
            {
                if (!workflow_set_selected_components(stepHandle, serializedComponentString))
                {
                    result.ResultCode = ADUC_Result_Failure;
                    result.ExtendedResultCode = ADUC_ERC_STEPS_HANDLER_SET_SELECTED_COMPONENTS_FAILURE;
                    workflow_set_result_details(handle, "Cannot set target component(s) for step #%d", i);
                    goto done;
                }
            }

            ContentHandler* contentHandler = nullptr;
            const char* stepUpdateType = workflow_is_inline_step(handle, i)
                ? workflow_peek_update_manifest_step_handler(handle, i)
                : DEFAULT_REF_STEP_HANDLER;

            Log_Info("Loading handler for child step #%lu (handler: '%s')", i, stepUpdateType);

            result = ExtensionManager::LoadUpdateContentHandlerExtension(stepUpdateType, &contentHandler);

            if (IsAducResultCodeFailure(result.ResultCode))
            {
                const char* errorFmt = "Cannot load a handler for step #%lu (handler :%s)";
                Log_Error(errorFmt, i, stepUpdateType);
                workflow_set_result(stepHandle, result);
                workflow_set_result_details(handle, errorFmt, i, stepUpdateType == nullptr ? "NULL" : stepUpdateType);
                goto done;
            }

            // If this item is already installed, skip to the next one.
            try
            {
                result = contentHandler->IsInstalled(&stepWorkflow);
            }
            catch (...)
            {
                // Cannot determine whether the step has been applied, so, we'll try to process the step.
                result.ResultCode = ADUC_Result_IsInstalled_NotInstalled;
                result.ExtendedResultCode = 0;
            }

            if (IsAducResultCodeSuccess(result.ResultCode) && result.ResultCode == ADUC_Result_IsInstalled_Installed)
            {
                result.ResultCode = ADUC_Result_Install_Skipped_UpdateAlreadyInstalled;
                result.ExtendedResultCode = 0;
                workflow_set_result(stepHandle, result);
                workflow_set_result_details(handle, workflow_peek_result_details(stepHandle));
                // Skipping 'backup', 'install' and 'apply'.
                goto instanceDone;
            }

            //
            // Perform 'backup' action before install.
            //
            try
            {
                result = contentHandler->Backup(&stepWorkflow);
            }
            catch (...)
            {
                result.ResultCode = ADUC_Result_Failure;
                result.ExtendedResultCode = ADUC_ERC_STEPS_HANDLER_INSTALL_UNKNOWN_EXCEPTION_BACKUP_CHILD_STEP;
                goto done;
            }
            if (IsAducResultCodeFailure(result.ResultCode))
            {
                // Propagate item's resultDetails to parent.
                workflow_set_result_details(handle, workflow_peek_result_details(stepHandle));
                goto done;
            }

            //
            // Perform 'install' action.
            //
            try
            {
                result = contentHandler->Install(&stepWorkflow);
            }
            catch (...)
            {
                Log_Error("The handler throws an exception inside Install().");
                result.ResultCode = ADUC_Result_Failure;
                result.ExtendedResultCode = ADUC_ERC_STEPS_HANDLER_INSTALL_UNKNOWN_EXCEPTION_INSTALL_CHILD_STEP;
                goto done;
            }

            // If the workflow interruption is required as part of the Install action,
            // we must propagate that request to the wrapping workflow.

            if (workflow_is_immediate_reboot_requested(stepHandle)
                || workflow_is_immediate_agent_restart_requested(stepHandle))
            {
                // Skip remaining tasks for this instance.
                // And then skip remaining instance(s) if requested.
                goto instanceDone;
            }

            // If any step reported that the update is already installed on the
            // selected component, we will skip the 'apply' phase, and skip all
            // remaining step(s).
            switch (result.ResultCode)
            {
            case ADUC_Result_Install_Skipped_UpdateAlreadyInstalled:
            case ADUC_Result_Install_Skipped_NoMatchingComponents:
                goto instanceDone;
            }

            // If Install task failed, try to restore (best effort).
            // The restore result is discarded, since install result is more important to customer.
            if (IsAducResultCodeFailure(result.ResultCode))
            {
                // Propagate item's resultDetails to parent.
                workflow_set_result_details(handle, workflow_peek_result_details(stepHandle));

                // When install fails, invoke Restore action
                try
                {
                    // Try to restore from the install failure, but it shouldn't impact the result code.
                    // To know the restore result on each step, the corresponding Update Handler will need to
                    // implement proper logging and send it up through Diagnostics service.
                    contentHandler->Restore(&stepWorkflow);
                }
                catch (...)
                {
                    Log_Warn("Unexpected error happened during restore action.");
                }
                goto done;
            }

            //
            // Perform 'apply' action.
            //
            try
            {
                result = contentHandler->Apply(&stepWorkflow);
                Log_Debug("Step's apply() return r:0x%x rc:0x%x", result.ResultCode, result.ExtendedResultCode);
            }
            catch (...)
            {
                Log_Error("The handler throws an exception inside Apply().");
                result.ResultCode = ADUC_Result_Failure;
                result.ExtendedResultCode = ADUC_ERC_STEPS_HANDLER_INSTALL_UNKNOWN_EXCEPTION_APPLY_CHILD_STEP;
                goto done;
            }

            if (IsAducResultCodeFailure(result.ResultCode))
            {
                // Propagate item's resultDetails to parent.
                workflow_set_result_details(handle, workflow_peek_result_details(stepHandle));

                // when apply fails, invoke restore action
                try
                {
                    Log_Info("Failed to install or apply. Try to restore now...");
                    // Try to restore from the apply failure, but it shouldn't impact the result code.
                    // To know the restore result on each step, the corresponding Update Handler will need to
                    // implement proper logging and send it up through Diagnostics service.
                    contentHandler->Restore(&stepWorkflow);
                }
                catch (...)
                {
                    Log_Warn("Unexpected error happened during restore action.");
                    goto done;
                }
            }

        instanceDone:
            // If the workflow interruption is required as part of the Install action,
            // we must propagate that request to the wrapping workflow.

            if (workflow_is_immediate_reboot_requested(stepHandle))
            {
                workflow_request_immediate_reboot(handle);
                // We must skip the remaining instance(s).
                goto done;
            }

            if (workflow_is_immediate_agent_restart_requested(stepHandle))
            {
                // We must skip the remaining instance(s).
                workflow_request_immediate_agent_restart(handle);
                goto done;
            }

            if (workflow_is_reboot_requested(stepHandle))
            {
                // Continue with the remaining instance(s).
                workflow_request_reboot(handle);
                break;
            }

            if (workflow_is_agent_restart_requested(stepHandle))
            {
                // Continue with the remaining instance(s).
                workflow_request_agent_restart(handle);
                break;
            }

            workflow_set_result(stepHandle, result);
            stepHandle = nullptr;

            if (IsAducResultCodeFailure(result.ResultCode))
            {
                goto componentDone;
            }
        } // steps

    componentDone:
        json_free_serialized_string(serializedComponentString);
        serializedComponentString = nullptr;

        if (IsAducResultCodeFailure(result.ResultCode))
        {
            goto done;
        }
    }

    if (workflow_is_cancel_requested(workflowData->WorkflowHandle))
    {
        result.ResultCode = ADUC_Result_Failure_Cancelled;
        result.ExtendedResultCode = 0;
    }
    else
    {
        result.ResultCode = ADUC_Result_Install_Success;
        result.ExtendedResultCode = 0;
    }

done:

    // NOTE: Do not free child workflow here, so that it can be reused in the next phase.
    // Only free child handle when the workflow is done.
    //
    // Alternatively, we can persist child workflow state, to free up some memory, and
    // load the state when needed in the next phase

    workflow_set_result(handle, result);

    if (IsAducResultCodeSuccess(result.ResultCode))
    {
        workflow_set_state(handle, ADUCITF_State_InstallSucceeded);
    }
    else
    {
        workflow_set_state(handle, ADUCITF_State_Failed);
    }

    json_free_serialized_string(serializedComponentString);
    workflow_free_string(workFolder);

    Log_Debug("Steps_Handler Install end (level %d).", workflowLevel);
    return result;
}