/*++

Copyright (c) 1998 Microsoft Corporation

Module Name:

    progress.c

Abstract:

    This file implements routines that estimate the size of the progress
    bar.

Author:

    Jim Schmidt (jimschm) 02-Jul-1998

Revision History:

    jimschm     23-Sep-1998 MigrateShellFolders & split of usermig.c

--*/


/*++

Macro Expansion List Description:

   The macro expansion lists FIRST_SYSTEM_ROUTINES, USER_ROUTINES and
   LAST_SYSTEM_ROUTINES list all the functions called to perform the migration
   of user and system settings.  The functions are executed in the order they
   appear.  Each function is responsible for estimating a tick count and ticking
   the progress bar.

Line Syntax:

   SYSFUNCTION(Function, Flag) (for FIRST_SYSTEM_ROUTINES and LAST_SYSTEM_ROUTINES)

   or

   USERFUNCTION(Function, Flag) (for USER_ROUTINES)

Arguments:

   Function   - These functions must return DWORD and are called with a request as a parameter,
                request that can be either REQUEST_QUERYTICKS (the function should estimate the
                number of ticks it needs) or REQUEST_RUN (the function should do it's job).
                For user functions there are also three more parameters (UserName, UserAccount,
                and a handle to HKCU)

   Flag - Specifies NOFAIL if the function terminates migration when it fails, or
          CANFAIL if migration can proceed even if the function fails

Variables Generated From List:

   g_MigrationFnList

For accessing the arrays there are the following functions:

   PrepareMigrationProgressBar
   PerformMigration

--*/

#include "pch.h"
#include "migmainp.h"

#define NOFAIL      FALSE
#define CANFAIL     TRUE


#define FIRST_SYSTEM_ROUTINES \
        SYSFUNCTION(PrepareEnvironment, NOFAIL)             \
        SYSFUNCTION(ResolveDomains, NOFAIL)                 \
        SYSFUNCTION(DeleteSysTapiSettings, NOFAIL)          \
        SYSFUNCTION(ProcessLocalMachine_First, CANFAIL)     \
        SYSFUNCTION(UninstallStartMenuCleanupPreparation, CANFAIL)              \
        SYSFUNCTION(RemoveBootIniCancelOption, CANFAIL)     \
        SYSFUNCTION(MigrateShellFolders, CANFAIL)           \
        SYSFUNCTION(MigrateGhostSystemFiles, CANFAIL)       \


#define USER_ROUTINES \
        USERFUNCTION(RunPerUserUninstallUserProfileCleanupPreparation, CANFAIL) \
        USERFUNCTION(PrepareUserForMigration, NOFAIL)       \
        USERFUNCTION(DeleteUserTapiSettings, NOFAIL)        \
        USERFUNCTION(MigrateUserRegistry, CANFAIL)          \
        USERFUNCTION(MigrateLogonPromptSettings, CANFAIL)   \
        USERFUNCTION(MigrateUserSettings, CANFAIL)          \
        USERFUNCTION(RunPerUserExternalProcesses, CANFAIL)  \
        USERFUNCTION(SaveMigratedUserHive, CANFAIL)         \

#define LAST_SYSTEM_ROUTINES \
        SYSFUNCTION(DoCopyFile, CANFAIL)                    \
        SYSFUNCTION(ProcessLocalMachine_Last, CANFAIL)      \
        SYSFUNCTION(ConvertHiveFiles, CANFAIL)              \
        SYSFUNCTION(MigrateBriefcases, CANFAIL)             \
        SYSFUNCTION(MigrateAtmFonts, CANFAIL)               \
        SYSFUNCTION(AddOptionsDiskCleaner, CANFAIL)         \
        SYSFUNCTION(DoFileEdit, CANFAIL)                    \
        SYSFUNCTION(RunSystemExternalProcesses, CANFAIL)    \
        SYSFUNCTION(ProcessMigrationDLLs, CANFAIL)          \
        SYSFUNCTION(DisableFiles, CANFAIL)                  \
        SYSFUNCTION(RunSystemUninstallUserProfileCleanupPreparation, CANFAIL)   \
        SYSFUNCTION(WriteBackupInfo, CANFAIL)               \


//
// Declare tables of processing structures
//

// Create a combined list
#define MIGRATION_ROUTINES  FIRST_SYSTEM_ROUTINES USER_ROUTINES LAST_SYSTEM_ROUTINES

// Processing functions types
typedef DWORD (MIGMAIN_SYS_PROTOTYPE) (DWORD Request);
typedef MIGMAIN_SYS_PROTOTYPE * MIGMAIN_SYS_FN;

typedef DWORD (MIGMAIN_USER_PROTOTYPE) (DWORD Request, PMIGRATE_USER_ENUM EnumPtr);
typedef MIGMAIN_USER_PROTOTYPE * MIGMAIN_USER_FN;

// Structure holding state for processing functions
typedef struct {
    // One of the two will be NULL, the other will be a valid fn ptr:
    MIGMAIN_SYS_FN SysFnPtr;
    MIGMAIN_USER_FN UserFnPtr;

    BOOL CanFail;
    UINT Ticks;
    PCTSTR FnName;
    GROWBUFFER SliceIdArray;
} PROCESSING_ROUTINE, *PPROCESSING_ROUTINE;

#define PROCESSING_ROUTINE_TERMINATOR   {NULL, NULL, FALSE, 0, NULL, GROWBUF_INIT}


// Declaration of prototypes
#define SYSFUNCTION(fn,flag)     MIGMAIN_SYS_PROTOTYPE fn;
#define USERFUNCTION(fn,flag)    MIGMAIN_USER_PROTOTYPE fn;

MIGRATION_ROUTINES

#undef SYSFUNCTION
#undef USERFUNCTION


// Declaration of table
#define SYSFUNCTION(fn,flag) {fn, NULL, flag, 0, L###fn, GROWBUF_INIT},
#define USERFUNCTION(fn,flag) {NULL, fn, flag, 0, L###fn, GROWBUF_INIT},

static PROCESSING_ROUTINE g_FirstSystemRoutines[] = {
                              FIRST_SYSTEM_ROUTINES /* , */
                              PROCESSING_ROUTINE_TERMINATOR
                              };

static PROCESSING_ROUTINE g_UserRoutines [] = {
                              USER_ROUTINES /* , */
                              PROCESSING_ROUTINE_TERMINATOR
                              };

static PROCESSING_ROUTINE g_LastSystemRoutines[] = {
                              LAST_SYSTEM_ROUTINES /* , */
                              PROCESSING_ROUTINE_TERMINATOR
                              };

#undef SYSFUNCTION
#undef USERFUNCTION


//
// Prototypes
//

BOOL
pProcessTable (
    IN      DWORD Request,
    IN      PPROCESSING_ROUTINE Table
    );


//
// Implementation
//


VOID
pInitTable (
    PPROCESSING_ROUTINE p
    )
{
    while (p->SysFnPtr || p->UserFnPtr) {
        p->SliceIdArray.GrowSize = sizeof (DWORD) * 8;
        p++;
    }
}


VOID
InitProcessingTable (
    VOID
    )
{
    pInitTable (g_FirstSystemRoutines);
    pInitTable (g_UserRoutines);
    pInitTable (g_LastSystemRoutines);
}


VOID
pTerminateTable (
    PPROCESSING_ROUTINE p
    )
{
    while (p->SysFnPtr || p->UserFnPtr) {
        FreeGrowBuffer (&p->SliceIdArray);
        p++;
    }
}


VOID
TerminateProcessingTable (
    VOID
    )
{
    pTerminateTable (g_FirstSystemRoutines);
    pTerminateTable (g_UserRoutines);
    pTerminateTable (g_LastSystemRoutines);
}


BOOL
pCallAllRoutines (
    BOOL Run
    )
{
    BOOL b;
    DWORD Request;

    Request = Run ? REQUEST_RUN : REQUEST_QUERYTICKS;

    b = pProcessTable (Request, g_FirstSystemRoutines);

    if (b && Run) {
        b = pProcessTable (REQUEST_BEGINUSERPROCESSING, g_UserRoutines);
    }

    if (b) {
        b = pProcessTable (Request, g_UserRoutines);
    }

    if (b && Run) {
        b = pProcessTable (REQUEST_ENDUSERPROCESSING, g_UserRoutines);
    }

    if (b) {
        b = pProcessTable (Request, g_LastSystemRoutines);
    }

    return b;
}


VOID
PrepareMigrationProgressBar (
    VOID
    )
{
    InitProcessingTable();
    pCallAllRoutines (FALSE);
}


BOOL
CallAllMigrationFunctions (
    VOID
    )
{
    return pCallAllRoutines (TRUE);
}


BOOL
pProcessWorker (
    IN      DWORD Request,
    IN      PPROCESSING_ROUTINE fn,
    IN      PMIGRATE_USER_ENUM EnumPtr      OPTIONAL
    )
{
    DWORD rc;
    PDWORD SliceId;
    DWORD Size;
    BOOL Result = TRUE;

    //
    // If running the function, start the progress bar slice
    //

    if (Request == REQUEST_RUN) {
        if (fn->Ticks == 0) {
            return TRUE;
        }

        Size = fn->SliceIdArray.End / sizeof (DWORD);
        if (fn->SliceIdArray.UserIndex >= Size) {
            DEBUGMSG ((DBG_WHOOPS, "pProcessWorker: QUERYTICKS vs. RUN mismatch"));
            return fn->CanFail;
        }

        SliceId = (PDWORD) fn->SliceIdArray.Buf + fn->SliceIdArray.UserIndex;
        fn->SliceIdArray.UserIndex += 1;

        BeginSliceProcessing (*SliceId);

        DEBUGLOGTIME (("Starting function: %ls", fn->FnName));
    }

    //
    // Now call the function
    //

    if (fn->SysFnPtr) {

        //
        // System processing
        //

        MYASSERT (!EnumPtr);
        rc = fn->SysFnPtr (Request);

        if (Request != REQUEST_QUERYTICKS && rc != ERROR_SUCCESS) {
            DEBUGMSG ((DBG_ERROR, "%s failed with rc=%u", fn->FnName, rc));
            Result = fn->CanFail;
        }

   } else {

        //
        // User processing
        //
        MYASSERT (fn->UserFnPtr);
        rc = fn->UserFnPtr (Request, EnumPtr);

        if (Request != REQUEST_QUERYTICKS && rc != ERROR_SUCCESS) {
            DEBUGMSG ((DBG_ERROR, "%s failed with rc=%u", fn->FnName, rc));
            Result = fn->CanFail;
        }

    }

    //
    // If running the function, end the progress bar slice
    //

    if (Request == REQUEST_RUN) {
        if (rc != ERROR_SUCCESS) {
            LOG ((LOG_ERROR, "Failure in %s, rc=%u", fn->FnName, rc));
        }

        EndSliceProcessing();

        DEBUGLOGTIME (("Function complete: %ls", fn->FnName));
    }


    if (Request != REQUEST_QUERYTICKS) {
        SetLastError (rc);
    }

    //
    // If querying the ticks, register them and add slice ID to grow buffer
    //

    else {
        fn->Ticks += rc;

        SliceId = (PDWORD) GrowBuffer (&fn->SliceIdArray, sizeof (DWORD));
        *SliceId = RegisterProgressBarSlice (rc);
    }

    return Result;
}


BOOL
pProcessTable (
    IN      DWORD Request,
    IN      PPROCESSING_ROUTINE Table
    )

/*++

Routine Description:

  pProcessTable calls all routines in the specified table to perform
  the specified request.

Arguments:

  Request - Specifies REQUEST_QUERYTICKS when a tick estimate is needed,
            or REQUEST_RUN when the function needs to perform its
            processing. For User routines, there are the additional two
            requests REQUEST_BEGINUSERPROCESSING and REQUEST_ENDUSERPROCESSING
            Functions can use these requests to init/free needed resources
            for user processing.

Return Value:

  none

--*/

{
    MIGRATE_USER_ENUM e;
    PPROCESSING_ROUTINE OrgStart;
    DWORD Flags;

    g_DomainUserName = NULL;
    g_Win9xUserName  = NULL;
    g_FixedUserName  = NULL;

    MYASSERT (Table->SysFnPtr || Table->UserFnPtr);

    while (Table->SysFnPtr || Table->UserFnPtr) {

        if (Table->SysFnPtr ||
            Request == REQUEST_BEGINUSERPROCESSING  ||
            Request == REQUEST_ENDUSERPROCESSING
            ) {

            //
            // Call system routine, or call per-user routine with begin or
            // end request
            //

            __try {
                if (!pProcessWorker (Request, Table, NULL)) {
                    return FALSE;
                }
            } __except (1) {

                LOG ((LOG_WARNING, "Unhandled exception occurred during processing of function %s.", Table->FnName));
                SafeModeExceptionOccured ();
                if (!Table->CanFail) {
                    return FALSE;
                }
            }

            //
            // Loop inc
            //

            Table++;

        } else {

            //
            // Enumerate each user, and run through all the per-user
            // routines in the group.
            //

            OrgStart = Table;

            if (Request == REQUEST_QUERYTICKS) {
                Flags = ENUM_NO_FLAGS;
            } else {
                Flags = ENUM_SET_WIN9X_HKR;
            }

            if (EnumFirstUserToMigrate (&e, Flags)) {

                do {
                    if (!e.CreateOnly) {

                        for (Table = OrgStart ; Table->UserFnPtr ; Table++) {

                            __try {
                                if (!pProcessWorker (Request, Table, &e)) {
                                    return FALSE;
                                }
                            } __except (1) {
                                LOG ((LOG_WARNING, "Unhandled exception occurred during processing of function %s.", Table->FnName));
                                SafeModeExceptionOccured ();
                                if (!Table->CanFail) {
                                    return FALSE;
                                }
                            }
                        }

                    }

                } while (EnumNextUserToMigrate (&e));
            }
            ELSE_DEBUGMSG ((DBG_WARNING, "No active users to process!"));

            //
            // Loop inc
            //

            while (Table->UserFnPtr) {
                Table++;
            }
        }

        TickProgressBar ();
    }

    return TRUE;
}