void ADUC_Workflow_HandlePropertyUpdate()

in src/adu_workflow/src/agent_workflow.c [426:612]


void ADUC_Workflow_HandlePropertyUpdate(
    ADUC_WorkflowData* currentWorkflowData, const unsigned char* propertyUpdateValue, bool forceUpdate)
{
    ADUC_WorkflowHandle nextWorkflow;

    ADUC_Result result = workflow_init((const char*)propertyUpdateValue, true /* shouldValidate */, &nextWorkflow);

    workflow_set_force_update(nextWorkflow, forceUpdate);

    ADUC_Result_t rootkeyErc = RootKeyUtility_GetReportingErc();
    if (rootkeyErc != 0)
    {
        // Save this rootkeyErc for later informational purposes, since getting this far
        // means processing of rootkey package actually succeeded.
        workflow_add_erc(nextWorkflow, rootkeyErc);
    }

    // N.B. Do NOT do IsAducResultCodeFailure() check on rootkeyErc.
    if (IsAducResultCodeFailure(result.ResultCode))
    {
        Log_Error("Invalid desired update action data. Update data: (%s)", propertyUpdateValue);

        ADUC_Workflow_SetUpdateStateWithResult(currentWorkflowData, ADUCITF_State_Failed, result);
        return;
    }

    ADUCITF_UpdateAction nextUpdateAction = workflow_get_action(nextWorkflow);

    //
    // Take lock until goto done.
    //
    // N.B.
    // Lock must *NOT* be taken in HandleStartupWorkflowData and HandleUpdateAction or any functions they call
    //
    s_workflow_lock();

    if (currentWorkflowData->WorkflowHandle != NULL)
    {
        if (nextUpdateAction == ADUCITF_UpdateAction_Cancel)
        {
            ADUC_WorkflowCancellationType currentCancellationType =
                workflow_get_cancellation_type(currentWorkflowData->WorkflowHandle);
            if (currentCancellationType == ADUC_WorkflowCancellationType_None)
            {
                workflow_set_cancellation_type(
                    currentWorkflowData->WorkflowHandle, ADUC_WorkflowCancellationType_Normal);

                // call into handle update action for cancellation logic to invoke ADUC_Workflow_MethodCall_Cancel
                ADUC_Workflow_HandleUpdateAction(currentWorkflowData);

                goto done;
            }
            else
            {
                Log_Info(
                    "Ignoring duplicate '%s' action. Current cancellation type is already '%s'.",
                    ADUCITF_UpdateActionToString(nextUpdateAction),
                    ADUC_Workflow_CancellationTypeToString(currentCancellationType));
                goto done;
            }
        }
        else if (nextUpdateAction == ADUCITF_UpdateAction_ProcessDeployment)
        {
            if (!forceUpdate && workflow_id_compare(currentWorkflowData->WorkflowHandle, nextWorkflow) == 0)
            {
                // Possible retry of the current workflow.
                const char* currentRetryToken = workflow_peek_retryTimestamp(currentWorkflowData->WorkflowHandle);
                const char* newRetryToken = workflow_peek_retryTimestamp(nextWorkflow);

                if (!AgentOrchestration_IsRetryApplicable(currentRetryToken, newRetryToken))
                {
                    Log_Warn(
                        "Ignoring Retry. currentRetryToken '%s', nextRetryToken '%s'.",
                        newRetryToken ? newRetryToken : "(NULL)",
                        currentRetryToken ? currentRetryToken : "(NULL)");
                    goto done;
                }

                Log_Debug("Retry %s is applicable", newRetryToken);

                // Sets both cancellation type to Retry and updates the current retry token
                workflow_update_retry_deployment(currentWorkflowData->WorkflowHandle, newRetryToken);

                // call into handle update action for cancellation logic to invoke ADUC_Workflow_MethodCall_Cancel
                ADUC_Workflow_HandleUpdateAction(currentWorkflowData);
                goto done;
            }
            else
            {
                // Possible replacement with a new workflow.
                ADUCITF_State currentState = ADUC_WorkflowData_GetLastReportedState(currentWorkflowData);
                ADUCITF_WorkflowStep currentWorkflowStep =
                    workflow_get_current_workflowstep(currentWorkflowData->WorkflowHandle);

                if (currentState != ADUCITF_State_Idle && currentState != ADUCITF_State_Failed
                    && currentWorkflowStep != ADUCITF_WorkflowStep_Undefined)
                {
                    Log_Info(
                        "Replacement. workflow '%s' is being replaced with workflow '%s'.",
                        workflow_peek_id(currentWorkflowData->WorkflowHandle),
                        workflow_peek_id(nextWorkflow));

                    // If operation is in progress, then in the same critical section we set cancellation type to replacement
                    // and set the pending workflow on the handle for use by WorkCompletionCallback to continue on with the
                    // replacement deployment instead of going to idle and reporting the results as a cancel failure.
                    // Otherwise, if the operation is not in progress, in the same critical section it transfers the
                    // workflow handle of the new deployment into the current workflow data, so that we can handle the update action.
                    bool deferredReplacement =
                        workflow_update_replacement_deployment(currentWorkflowData->WorkflowHandle, nextWorkflow);

                    if (deferredReplacement)
                    {
                        Log_Info(
                            "Deferred Replacement workflow id [%s] since current workflow id [%s] was still in progress.",
                            workflow_peek_id(nextWorkflow),
                            workflow_peek_id(currentWorkflowData->WorkflowHandle));

                        // Ownership was transferred to current workflow so ensure it doesn't get freed.
                        nextWorkflow = NULL;

                        // call into handle update action for cancellation logic to invoke ADUC_Workflow_MethodCall_Cancel
                        ADUC_Workflow_HandleUpdateAction(currentWorkflowData);
                        goto done;
                    }

                    Log_Debug("deferral not needed. Processing '%s' now", workflow_peek_id(nextWorkflow));

                    workflow_transfer_data(
                        currentWorkflowData->WorkflowHandle /* wfTarget */, nextWorkflow /* wfSource */);

                    ADUC_Workflow_HandleUpdateAction(currentWorkflowData);
                    goto done;
                }

                // Fall through to handle new workflow
            }
        }
    }
    else
    {
        // This is a top level workflow, make sure that we set the working folder correctly.
        const ADUC_ConfigInfo* config = ADUC_ConfigInfo_GetInstance();
        if (config != NULL)
        {
            workflow_set_workfolder(nextWorkflow, "%s/%s", config->downloadsFolder, workflow_peek_id(nextWorkflow));
            ADUC_ConfigInfo_ReleaseInstance(config);
        }
        else
        {
            Log_Error("Cannot set workfolder. Config is NULL.");
            goto done;
        }
    }

    // Continue with the new workflow.
    workflow_free(currentWorkflowData->WorkflowHandle);
    currentWorkflowData->WorkflowHandle = nextWorkflow;

    nextWorkflow = NULL;

    workflow_set_cancellation_type(
        currentWorkflowData->WorkflowHandle,
        nextUpdateAction == ADUCITF_UpdateAction_Cancel ? ADUC_WorkflowCancellationType_Normal
                                                        : ADUC_WorkflowCancellationType_None);

    // If the agent has just started up but we have yet to report the installedUpdateId along with a state of 'Idle'
    // we want to ignore any further action received so we don't confuse the workflow which would interpret a state of 'Idle'
    // not accompanied with an installedUpdateId as a failed end state in some cases.
    // In this case we will go through our startup logic which would report the installedUpdateId with a state of 'Idle',
    // if we can determine that the update has been installed successfully (by calling IsInstalled()).
    // Otherwise we will honor and process the action requested.
    if (!currentWorkflowData->StartupIdleCallSent)
    {
        ADUC_Workflow_HandleStartupWorkflowData(currentWorkflowData);
    }
    else
    {
        ADUC_Workflow_HandleUpdateAction(currentWorkflowData);
    }

done:

    s_workflow_unlock();

    workflow_free(nextWorkflow);
    Log_Debug("PropertyUpdated event handler completed.");
}