/*
 *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
 *
 *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#elif defined(_MSC_VER)
#include <config-msvc.h>
#endif

#include "msica_op.h"
#include "../tapctl/error.h"
#include "../tapctl/tap.h"

#include <windows.h>
#include <malloc.h>
#include <msiquery.h>
#include <objbase.h>

#ifdef _MSC_VER
#pragma comment(lib, "msi.lib")
#pragma comment(lib, "ole32.lib")
#endif


/**
 * Operation data persist header
 */
struct msica_op_hdr
{
    enum msica_op_type type;  /** Action type */
    int ticks;                /** Number of ticks on the progress indicator this operation represents */
    DWORD size_data;          /** Size of the operation data (DWORD to better align with Win32 API) */
};


void
msica_op_seq_init(_Inout_ struct msica_op_seq *seq)
{
    seq->head = NULL;
    seq->tail = NULL;
}


void
msica_op_seq_free(_Inout_ struct msica_op_seq *seq)
{
    while (seq->head)
    {
        struct msica_op *op = seq->head;
        seq->head = seq->head->next;
        free(op);
    }
    seq->tail = NULL;
}


struct msica_op*
msica_op_create_bool(
    _In_     enum msica_op_type  type,
    _In_     int                 ticks,
    _In_opt_ struct msica_op    *next,
    _In_     bool                value)
{
    if (MSICA_OP_TYPE_DATA(type) != 0x1)
    {
        msg(M_NONFATAL, "%s: Operation data type not bool (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
        return NULL;
    }

    /* Create and fill operation struct. */
    struct msica_op_bool *op = (struct msica_op_bool*)malloc(sizeof(struct msica_op_bool));
    op->base.type  = type;
    op->base.ticks = ticks;
    op->base.next  = next;
    op->value      = value;

    return &op->base;
}


struct msica_op*
msica_op_create_string(
    _In_     enum msica_op_type  type,
    _In_     int                 ticks,
    _In_opt_ struct msica_op    *next,
    _In_z_   LPCTSTR             value)
{
    if (MSICA_OP_TYPE_DATA(type) != 0x2)
    {
        msg(M_NONFATAL, "%s: Operation data type not string (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
        return NULL;
    }

    /* Create and fill operation struct. */
    size_t value_size = (_tcslen(value) + 1) * sizeof(TCHAR);
    struct msica_op_string *op = (struct msica_op_string*)malloc(sizeof(struct msica_op_string) + value_size);
    op->base.type  = type;
    op->base.ticks = ticks;
    op->base.next  = next;
    memcpy(op->value, value, value_size);

    return &op->base;
}


struct msica_op*
msica_op_create_multistring_va(
    _In_     enum msica_op_type  type,
    _In_     int                 ticks,
    _In_opt_ struct msica_op    *next,
    _In_     va_list             arglist)
{
    if (MSICA_OP_TYPE_DATA(type) != 0x3)
    {
        msg(M_NONFATAL, "%s: Operation data type not multi-string (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
        return NULL;
    }

    /* Calculate required space first. */
    LPCTSTR str;
    size_t value_size = 1;
    for (va_list a = arglist; (str = va_arg(a, LPCTSTR)) != NULL; value_size += _tcslen(str) + 1);
    value_size *= sizeof(TCHAR);

    /* Create and fill operation struct. */
    struct msica_op_multistring *op = (struct msica_op_multistring*)malloc(sizeof(struct msica_op_multistring) + value_size);
    op->base.type  = type;
    op->base.ticks = ticks;
    op->base.next  = next;
    LPTSTR value = op->value;
    for (va_list a = arglist; (str = va_arg(a, LPCTSTR)) != NULL;)
    {
        size_t size = _tcslen(str) + 1;
        memcpy(value, str, size*sizeof(TCHAR));
        value += size;
    }
    value[0] = 0;

    return &op->base;
}


struct msica_op*
msica_op_create_guid(
    _In_     enum msica_op_type  type,
    _In_     int                 ticks,
    _In_opt_ struct msica_op    *next,
    _In_     const GUID         *value)
{
    if (MSICA_OP_TYPE_DATA(type) != 0x4)
    {
        msg(M_NONFATAL, "%s: Operation data type not GUID (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
        return NULL;
    }

    /* Create and fill operation struct. */
    struct msica_op_guid *op = (struct msica_op_guid*)malloc(sizeof(struct msica_op_guid));
    op->base.type  = type;
    op->base.ticks = ticks;
    op->base.next  = next;
    memcpy(&op->value, value, sizeof(GUID));

    return &op->base;
}


struct msica_op*
msica_op_create_guid_string(
    _In_     enum msica_op_type  type,
    _In_     int                 ticks,
    _In_opt_ struct msica_op    *next,
    _In_     const GUID         *value_guid,
    _In_z_   LPCTSTR             value_str)
{
    if (MSICA_OP_TYPE_DATA(type) != 0x5)
    {
        msg(M_NONFATAL, "%s: Operation data type not GUID-string (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type));
        return NULL;
    }

    /* Create and fill operation struct. */
    size_t value_str_size = (_tcslen(value_str) + 1) * sizeof(TCHAR);
    struct msica_op_guid_string *op = (struct msica_op_guid_string*)malloc(sizeof(struct msica_op_guid_string) + value_str_size);
    op->base.type  = type;
    op->base.ticks = ticks;
    op->base.next  = next;
    memcpy(&op->value_guid, value_guid, sizeof(GUID)  );
    memcpy( op->value_str , value_str , value_str_size);

    return &op->base;
}


void
msica_op_seq_add_head(
    _Inout_ struct msica_op_seq *seq,
    _Inout_ struct msica_op     *operation)
{
    /* Insert list in the head. */
    struct msica_op *op;
    for (op = operation; op->next; op = op->next);
    op->next = seq->head;

    /* Update head (and tail). */
    seq->head = operation;
    if (seq->tail == NULL)
        seq->tail = op;
}


void
msica_op_seq_add_tail(
    _Inout_ struct msica_op_seq *seq,
    _Inout_ struct msica_op     *operation)
{
    /* Append list to the tail. */
    struct msica_op *op;
    for (op = operation; op->next; op = op->next);
    if (seq->tail)
        seq->tail->next = operation;
    else
        seq->head = operation;
    seq->tail = op;
}


DWORD
msica_op_seq_save(
    _In_ const struct msica_op_seq *seq,
    _In_ HANDLE                     hFile)
{
    DWORD dwWritten;
    for (const struct msica_op *op = seq->head; op; op = op->next)
    {
        struct msica_op_hdr hdr;
        hdr.type  = op->type;
        hdr.ticks = op->ticks;

        /* Calculate size of data. */
        switch (MSICA_OP_TYPE_DATA(op->type))
        {
        case 0x1: /* msica_op_bool */
            hdr.size_data = sizeof(struct msica_op_bool) - sizeof(struct msica_op);
            break;

        case 0x2: /* msica_op_string */
            hdr.size_data =
                sizeof(struct msica_op_string) - sizeof(struct msica_op) +
                (DWORD)(_tcslen(((struct msica_op_string*)op)->value) + 1) * sizeof(TCHAR);
            break;

        case 0x3: /* msica_op_multistring */
        {
            LPCTSTR str;
            for (str = ((struct msica_op_multistring*)op)->value; str[0]; str += _tcslen(str) + 1);
            hdr.size_data =
                sizeof(struct msica_op_multistring) - sizeof(struct msica_op) +
                (DWORD)(str + 1 - ((struct msica_op_multistring*)op)->value) * sizeof(TCHAR);
            break;
        }

        case 0x4: /* msica_op_guid */
            hdr.size_data = sizeof(struct msica_op_guid) - sizeof(struct msica_op);
            break;

        case 0x5: /* msica_op_guid_string */
            hdr.size_data =
                sizeof(struct msica_op_guid_string) - sizeof(struct msica_op) +
                (DWORD)(_tcslen(((struct msica_op_guid_string*)op)->value_str) + 1) * sizeof(TCHAR);
            break;

        default:
            msg(M_NONFATAL, "%s: Unknown operation data type (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(op->type));
            return ERROR_BAD_ARGUMENTS;
        }

        if (!WriteFile(hFile, &hdr, sizeof(struct msica_op_hdr), &dwWritten, NULL) ||
            !WriteFile(hFile, op + 1, hdr.size_data, &dwWritten, NULL))
        {
            DWORD dwResult = GetLastError();
            msg(M_NONFATAL | M_ERRNO, "%s: WriteFile failed", __FUNCTION__);
            return dwResult;
        }
    }

    return ERROR_SUCCESS;
}


DWORD
msica_op_seq_load(
    _Inout_ struct msica_op_seq *seq,
    _In_    HANDLE               hFile)
{
    DWORD dwRead;

    seq->head = seq->tail = NULL;

    for (;;)
    {
        struct msica_op_hdr hdr;
        if (!ReadFile(hFile, &hdr, sizeof(struct msica_op_hdr), &dwRead, NULL))
        {
            DWORD dwResult = GetLastError();
            msg(M_NONFATAL | M_ERRNO, "%s: ReadFile failed", __FUNCTION__);
            return dwResult;
        }
        else if (dwRead == 0)
        {
            /* EOF */
            return ERROR_SUCCESS;
        }
        else if (dwRead < sizeof(struct msica_op_hdr))
        {
            msg(M_NONFATAL, "%s: Incomplete ReadFile", __FUNCTION__);
            return ERROR_INVALID_DATA;
        }
        struct msica_op *op = (struct msica_op*)malloc(sizeof(struct msica_op) + hdr.size_data);
        op->type  = hdr.type;
        op->ticks = hdr.ticks;
        op->next  = NULL;
        if (!ReadFile(hFile, op + 1, hdr.size_data, &dwRead, NULL))
        {
            DWORD dwResult = GetLastError();
            msg(M_NONFATAL | M_ERRNO, "%s: ReadFile failed", __FUNCTION__);
            free(op);
            return dwResult;
        }
        else if (dwRead < hdr.size_data)
        {
            msg(M_NONFATAL, "%s: Incomplete ReadFile", __FUNCTION__);
            return ERROR_INVALID_DATA;
        }
        msica_op_seq_add_tail(seq, op);
    }
}


static DWORD
msica_op_tap_interface_create_exec(
    _Inout_ const struct msica_op_string *op,
    _Inout_ struct msica_session         *session)
{
    if (op == NULL || session == NULL)
        return ERROR_BAD_ARGUMENTS;

    {
        /* Report the name of the interface to installer. */
        MSIHANDLE hRecord = MsiCreateRecord(3);
        MsiRecordSetString(hRecord, 1, TEXT("Creating TAP interface"));
        MsiRecordSetString(hRecord, 2, op->value);
        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
        MsiCloseHandle(hRecord);
        if (iResult == IDCANCEL)
            return ERROR_INSTALL_USEREXIT;
    }

    /* Get available network interfaces. */
    struct tap_interface_node *pInterfaceList = NULL;
    DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
    if (dwResult == ERROR_SUCCESS)
    {
        /* Does interface exist? */
        for (struct tap_interface_node *pInterfaceOther = pInterfaceList; ; pInterfaceOther = pInterfaceOther->pNext)
        {
            if (pInterfaceOther == NULL)
            {
                /* No interface with a same name found. Create one. */
                BOOL bRebootRequired = FALSE;
                GUID guidInterface;
                dwResult = tap_create_interface(NULL, NULL, &bRebootRequired, &guidInterface);
                if (dwResult == ERROR_SUCCESS)
                {
                    /* Set interface name. */
                    dwResult = tap_set_interface_name(&guidInterface, op->value);
                    if (dwResult == ERROR_SUCCESS)
                    {
                        if (session->rollback_enabled)
                        {
                            /* Order rollback action to delete it. */
                            msica_op_seq_add_head(
                                &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
                                msica_op_create_guid(
                                    msica_op_tap_interface_delete_by_guid,
                                    0,
                                    NULL,
                                    &guidInterface));
                        }
                    }
                    else
                        tap_delete_interface(NULL, &guidInterface, &bRebootRequired);

                    if (bRebootRequired)
                        MsiSetMode(session->hInstall, MSIRUNMODE_REBOOTATEND, TRUE);
                }
                break;
            }
            else if (_tcsicmp(op->value, pInterfaceOther->szName) == 0)
            {
                /* Interface with a same name found. */
                for (LPCTSTR hwid = pInterfaceOther->szzHardwareIDs; ; hwid += _tcslen(hwid) + 1)
                {
                    if (hwid[0] == 0)
                    {
                        /* This is not a TAP interface. */
                        msg(M_NONFATAL, "%s: Interface with name \"%"PRIsLPTSTR"\" already exists", __FUNCTION__, pInterfaceOther->szName);
                        dwResult = ERROR_ALREADY_EXISTS;
                        break;
                    }
                    else if (
                        _tcsicmp(hwid, TEXT(TAP_WIN_COMPONENT_ID)) == 0 ||
                        _tcsicmp(hwid, TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID)) == 0)
                    {
                        /* This is a TAP interface. We already got what we wanted! */
                        dwResult = ERROR_SUCCESS;
                        break;
                    }
                }
                break;
            }
        }

        tap_free_interface_list(pInterfaceList);
    }

    return dwResult;
}


static DWORD
msica_op_tap_interface_delete(
    _In_    struct tap_interface_node *pInterfaceList,
    _In_    struct tap_interface_node *pInterface,
    _Inout_ struct msica_session      *session)
{
    if (pInterfaceList == NULL || pInterface == NULL || session == NULL)
        return ERROR_BAD_ARGUMENTS;

    DWORD dwResult;

    if (session->rollback_enabled)
    {
        int count = 0;

        do {
            /* Rename the interface to keep it as a backup. */
            TCHAR szNameBackup[10/*"Interface "*/ + 10/*maximum int*/ + 1/*terminator*/];
            _stprintf_s(
                szNameBackup, _countof(szNameBackup),
                TEXT("Interface %i"),
                ++count);
            for (struct tap_interface_node *pInterfaceOther = pInterfaceList; ; pInterfaceOther = pInterfaceOther->pNext)
            {
                if (pInterfaceOther == NULL)
                {
                    /* No interface with a same name found. All clear to rename the interface. */
                    dwResult = tap_set_interface_name(&pInterface->guid, szNameBackup);
                    break;
                }
                else if (_tcsicmp(szNameBackup, pInterfaceOther->szName) == 0)
                {
                    /* Interface with a same name found. Duplicate interface names are not allowed. */
                    dwResult = ERROR_ALREADY_EXISTS;
                    break;
                }
            }
        } while (dwResult == ERROR_ALREADY_EXISTS);

        if (dwResult == ERROR_SUCCESS) {
            /* Schedule rollback action to rename the interface back. */
            msica_op_seq_add_head(
                &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
                msica_op_create_guid_string(
                    msica_op_tap_interface_set_name,
                    0,
                    NULL,
                    &pInterface->guid,
                    pInterface->szName));

            /* Schedule commit action to delete the interface. */
            msica_op_seq_add_tail(
                &session->seq_cleanup[MSICA_CLEANUP_ACTION_COMMIT],
                msica_op_create_guid(
                    msica_op_tap_interface_delete_by_guid,
                    0,
                    NULL,
                    &pInterface->guid));
        }
    }
    else
    {
        /* Delete the interface. */
        BOOL bRebootRequired = FALSE;
        dwResult = tap_delete_interface(NULL, &pInterface->guid, &bRebootRequired);
        if (bRebootRequired)
            MsiSetMode(session->hInstall, MSIRUNMODE_REBOOTATEND, TRUE);
    }

    return dwResult;
}


static DWORD
msica_op_tap_interface_delete_by_name_exec(
    _Inout_ const struct msica_op_string *op,
    _Inout_ struct msica_session         *session)
{
    if (op == NULL || session == NULL)
        return ERROR_BAD_ARGUMENTS;

    {
        /* Report the name of the interface to installer. */
        MSIHANDLE hRecord = MsiCreateRecord(3);
        MsiRecordSetString(hRecord, 1, TEXT("Deleting interface"));
        MsiRecordSetString(hRecord, 2, op->value);
        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
        MsiCloseHandle(hRecord);
        if (iResult == IDCANCEL)
            return ERROR_INSTALL_USEREXIT;
    }

    /* Get available network interfaces. */
    struct tap_interface_node *pInterfaceList = NULL;
    DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
    if (dwResult == ERROR_SUCCESS)
    {
        /* Does interface exist? */
        for (struct tap_interface_node *pInterface = pInterfaceList; ; pInterface = pInterface->pNext)
        {
            if (pInterface == NULL)
            {
                /* Interface not found. We already got what we wanted! */
                dwResult = ERROR_SUCCESS;
                break;
            }
            else if (_tcsicmp(op->value, pInterface->szName) == 0)
            {
                /* Interface found. */
                dwResult = msica_op_tap_interface_delete(
                    pInterfaceList,
                    pInterface,
                    session);
                break;
            }
        }

        tap_free_interface_list(pInterfaceList);
    }

    return dwResult;
}


static DWORD
msica_op_tap_interface_delete_by_guid_exec(
    _Inout_ const struct msica_op_guid *op,
    _Inout_ struct msica_session       *session)
{
    if (op == NULL || session == NULL)
        return ERROR_BAD_ARGUMENTS;

    {
        /* Report the GUID of the interface to installer. */
        MSIHANDLE hRecord = MsiCreateRecord(3);
        LPOLESTR szInterfaceId = NULL;
        StringFromIID((REFIID)&op->value, &szInterfaceId);
        MsiRecordSetString(hRecord, 1, TEXT("Deleting interface"));
        MsiRecordSetString(hRecord, 2, szInterfaceId);
        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
        CoTaskMemFree(szInterfaceId);
        MsiCloseHandle(hRecord);
        if (iResult == IDCANCEL)
            return ERROR_INSTALL_USEREXIT;
    }

    /* Get available network interfaces. */
    struct tap_interface_node *pInterfaceList = NULL;
    DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
    if (dwResult == ERROR_SUCCESS)
    {
        /* Does interface exist? */
        for (struct tap_interface_node *pInterface = pInterfaceList; ; pInterface = pInterface->pNext)
        {
            if (pInterface == NULL)
            {
                /* Interface not found. We already got what we wanted! */
                dwResult = ERROR_SUCCESS;
                break;
            }
            else if (memcmp(&op->value, &pInterface->guid, sizeof(GUID)) == 0)
            {
                /* Interface found. */
                dwResult = msica_op_tap_interface_delete(
                    pInterfaceList,
                    pInterface,
                    session);
                break;
            }
        }

        tap_free_interface_list(pInterfaceList);
    }

    return dwResult;
}


static DWORD
msica_op_tap_interface_set_name_exec(
    _Inout_ const struct msica_op_guid_string *op,
    _Inout_ struct msica_session              *session)
{
    if (op == NULL || session == NULL)
        return ERROR_BAD_ARGUMENTS;

    {
        /* Report the GUID of the interface to installer. */
        MSIHANDLE hRecord = MsiCreateRecord(3);
        LPOLESTR szInterfaceId = NULL;
        StringFromIID((REFIID)&op->value_guid, &szInterfaceId);
        MsiRecordSetString(hRecord, 1, TEXT("Setting interface name"));
        MsiRecordSetString(hRecord, 2, szInterfaceId);
        MsiRecordSetString(hRecord, 3, op->value_str);
        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
        CoTaskMemFree(szInterfaceId);
        MsiCloseHandle(hRecord);
        if (iResult == IDCANCEL)
            return ERROR_INSTALL_USEREXIT;
    }

    /* Get available network interfaces. */
    struct tap_interface_node *pInterfaceList = NULL;
    DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList);
    if (dwResult == ERROR_SUCCESS)
    {
        /* Does interface exist? */
        for (struct tap_interface_node *pInterface = pInterfaceList; ; pInterface = pInterface->pNext)
        {
            if (pInterface == NULL)
            {
                /* Interface not found. */
                LPOLESTR szInterfaceId = NULL;
                StringFromIID((REFIID)&op->value_guid, &szInterfaceId);
                msg(M_NONFATAL, "%s: %"PRIsLPOLESTR" interface not found", __FUNCTION__, szInterfaceId);
                CoTaskMemFree(szInterfaceId);
                dwResult = ERROR_FILE_NOT_FOUND;
                break;
            }
            else if (memcmp(&op->value_guid, &pInterface->guid, sizeof(GUID)) == 0)
            {
                /* Interface found. */
                for (struct tap_interface_node *pInterfaceOther = pInterfaceList; ; pInterfaceOther = pInterfaceOther->pNext)
                {
                    if (pInterfaceOther == NULL)
                    {
                        /* No other interface with a same name found. All clear to rename the interface. */
                        dwResult = tap_set_interface_name(&pInterface->guid, op->value_str);
                        if (dwResult == ERROR_SUCCESS)
                        {
                            if (session->rollback_enabled)
                            {
                                /* Order rollback action to rename it back. */
                                msica_op_seq_add_head(
                                    &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
                                    msica_op_create_guid_string(
                                        msica_op_tap_interface_set_name,
                                        0,
                                        NULL,
                                        &pInterface->guid,
                                        pInterface->szName));
                            }
                        }
                        break;
                    }
                    else if (_tcsicmp(op->value_str, pInterfaceOther->szName) == 0)
                    {
                        /* Interface with a same name found. Duplicate interface names are not allowed. */
                        msg(M_NONFATAL, "%s: Interface with name \"%"PRIsLPTSTR"\" already exists", __FUNCTION__, pInterfaceOther->szName);
                        dwResult = ERROR_ALREADY_EXISTS;
                        break;
                    }
                }
                break;
            }
        }

        tap_free_interface_list(pInterfaceList);
    }

    return dwResult;
}


static DWORD
msica_op_file_delete_exec(
    _Inout_ const struct msica_op_string *op,
    _Inout_ struct msica_session         *session)
{
    if (op == NULL || session == NULL)
        return ERROR_BAD_ARGUMENTS;

    {
        /* Report the name of the file to installer. */
        MSIHANDLE hRecord = MsiCreateRecord(3);
        MsiRecordSetString(hRecord, 1, TEXT("Deleting file"));
        MsiRecordSetString(hRecord, 2, op->value);
        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
        MsiCloseHandle(hRecord);
        if (iResult == IDCANCEL)
            return ERROR_INSTALL_USEREXIT;
    }

    DWORD dwResult;

    if (session->rollback_enabled)
    {
        size_t sizeNameBackupLenZ = _tcslen(op->value) + 7/*" (orig "*/ + 10/*maximum int*/ + 1/*")"*/ + 1/*terminator*/;
        LPTSTR szNameBackup = (LPTSTR)malloc(sizeNameBackupLenZ * sizeof(TCHAR));
        int count = 0;

        do {
            /* Rename the file to make a backup. */
            _stprintf_s(
                szNameBackup, sizeNameBackupLenZ,
                TEXT("%s (orig %i)"),
                op->value,
                ++count);
            dwResult = MoveFile(op->value, szNameBackup) ? ERROR_SUCCESS : GetLastError();
        } while (dwResult == ERROR_ALREADY_EXISTS);

        if (dwResult == ERROR_SUCCESS)
        {
            /* Schedule rollback action to restore from backup. */
            msica_op_seq_add_head(
                &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
                msica_op_create_multistring(
                    msica_op_file_move,
                    0,
                    NULL,
                    szNameBackup,
                    op->value,
                    NULL));

            /* Schedule commit action to delete the backup. */
            msica_op_seq_add_tail(
                &session->seq_cleanup[MSICA_CLEANUP_ACTION_COMMIT],
                msica_op_create_string(
                    msica_op_file_delete,
                    0,
                    NULL,
                    szNameBackup));
        }
        else if (dwResult == ERROR_FILE_NOT_FOUND) /* File does not exist: We already got what we wanted! */
            dwResult = ERROR_SUCCESS;
        else
            msg(M_NONFATAL | M_ERRNO, "%s: MoveFile(\"%"PRIsLPTSTR"\", \"%"PRIsLPTSTR"\") failed", __FUNCTION__, op->value, szNameBackup);

        free(szNameBackup);
    }
    else
    {
        /* Delete the file. */
        dwResult = DeleteFile(op->value) ? ERROR_SUCCESS : GetLastError();
        if (dwResult == ERROR_FILE_NOT_FOUND) /* File does not exist: We already got what we wanted! */
            dwResult = ERROR_SUCCESS;
        else if (dwResult != ERROR_SUCCESS)
            msg(M_NONFATAL | M_ERRNO, "%s: DeleteFile(\"%"PRIsLPTSTR"\") failed", __FUNCTION__, op->value);
    }

    return dwResult;
}


static DWORD
msica_op_file_move_exec(
    _Inout_ const struct msica_op_multistring *op,
    _Inout_ struct msica_session              *session)
{
    if (op == NULL || session == NULL)
        return ERROR_BAD_ARGUMENTS;

    /* Get source filename. */
    LPCTSTR szNameSrc = op->value;
    if (szNameSrc[0] == 0)
        return ERROR_BAD_ARGUMENTS;

    /* Get destination filename. */
    LPCTSTR szNameDst = szNameSrc + _tcslen(szNameSrc) + 1;
    if (szNameDst[0] == 0)
        return ERROR_BAD_ARGUMENTS;

    {
        /* Report the name of the files to installer. */
        MSIHANDLE hRecord = MsiCreateRecord(3);
        MsiRecordSetString(hRecord, 1, TEXT("Moving file"));
        MsiRecordSetString(hRecord, 2, szNameSrc);
        MsiRecordSetString(hRecord, 3, szNameDst);
        int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
        MsiCloseHandle(hRecord);
        if (iResult == IDCANCEL)
            return ERROR_INSTALL_USEREXIT;
    }

    DWORD dwResult = MoveFile(szNameSrc, szNameDst) ? ERROR_SUCCESS : GetLastError();
    if (dwResult == ERROR_SUCCESS) {
        if (session->rollback_enabled) {
            /* Order rollback action to move it back. */
            msica_op_seq_add_head(
                &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK],
                msica_op_create_multistring(
                    msica_op_file_move,
                    0,
                    NULL,
                    szNameDst,
                    szNameSrc,
                    NULL));
        }
    }
    else
        msg(M_NONFATAL | M_ERRNO, "%s: MoveFile(\"%"PRIsLPTSTR"\", \"%"PRIsLPTSTR"\") failed", __FUNCTION__, szNameSrc, szNameDst);

    return dwResult;
}


void
openvpnmsica_session_init(
    _Inout_ struct msica_session *session,
    _In_    MSIHANDLE             hInstall,
    _In_    bool                  continue_on_error,
    _In_    bool                  rollback_enabled)
{
    session->hInstall          = hInstall;
    session->continue_on_error = continue_on_error;
    session->rollback_enabled  = rollback_enabled;
    for (size_t i = 0; i < MSICA_CLEANUP_ACTION_COUNT; i++)
        msica_op_seq_init(&session->seq_cleanup[i]);
}


DWORD
msica_op_seq_process(
    _Inout_ const struct msica_op_seq *seq,
    _Inout_ struct msica_session      *session)
{
    DWORD dwResult;

    if (seq == NULL || session == NULL)
        return ERROR_BAD_ARGUMENTS;

    /* Tell the installer to use explicit progress messages. */
    MSIHANDLE hRecordProg = MsiCreateRecord(3);
    MsiRecordSetInteger(hRecordProg, 1, 1);
    MsiRecordSetInteger(hRecordProg, 2, 1);
    MsiRecordSetInteger(hRecordProg, 3, 0);
    MsiProcessMessage(session->hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg);

    /* Prepare hRecordProg for progress messages. */
    MsiRecordSetInteger(hRecordProg, 1, 2);
    MsiRecordSetInteger(hRecordProg, 3, 0);

    for (const struct msica_op *op = seq->head; op; op = op->next)
    {
        switch (op->type)
        {
        case msica_op_rollback_enable:
            session->rollback_enabled = ((const struct msica_op_bool*)op)->value;
            dwResult = ERROR_SUCCESS;
            break;

        case msica_op_tap_interface_create:
            dwResult = msica_op_tap_interface_create_exec((const struct msica_op_string*)op, session);
            break;

        case msica_op_tap_interface_delete_by_name:
            dwResult = msica_op_tap_interface_delete_by_name_exec((const struct msica_op_string*)op, session);
            break;

        case msica_op_tap_interface_delete_by_guid:
            dwResult = msica_op_tap_interface_delete_by_guid_exec((const struct msica_op_guid*)op, session);
            break;

        case msica_op_tap_interface_set_name:
            dwResult = msica_op_tap_interface_set_name_exec((const struct msica_op_guid_string*)op, session);
            break;

        case msica_op_file_delete:
            dwResult = msica_op_file_delete_exec((const struct msica_op_string*)op, session);
            break;

        case msica_op_file_move:
            dwResult = msica_op_file_move_exec((const struct msica_op_multistring*)op, session);
            break;

        default:
            msg(M_NONFATAL, "%s: Unknown operation type (%x)", __FUNCTION__, op->type);
            dwResult = ERROR_FILE_NOT_FOUND;
        }

        if (!session->continue_on_error && dwResult != ERROR_SUCCESS) {
            /* Operation failed. It should have sent error message to Installer. Therefore, just quit here. */
            goto cleanup_hRecordProg;
        }

        /* Report progress and check for user cancellation. */
        MsiRecordSetInteger(hRecordProg, 2, op->ticks);
        if (MsiProcessMessage(session->hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL)
        {
            dwResult = ERROR_INSTALL_USEREXIT;
            goto cleanup_hRecordProg;
        }
    }

    dwResult = ERROR_SUCCESS;

cleanup_hRecordProg:
    MsiCloseHandle(hRecordProg);
    return dwResult;
}