/*++

Copyright (c) Microsoft Corporation.  All rights reserved.

Module Name:

    diskspac.c

Abstract:

    APIs and supporting routines for disk space requirement
    calculation.

Author:

    Ted Miller (tedm) 26-Jul-1996

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop


//
// An HDSKSPC actually points to one of these.
//
typedef struct _DISK_SPACE_LIST {

    MYLOCK Lock;

    PVOID DrivesTable;

    UINT Flags;

} DISK_SPACE_LIST, *PDISK_SPACE_LIST;

#define LockIt(h)   BeginSynchronizedAccess(&h->Lock)
#define UnlockIt(h) EndSynchronizedAccess(&h->Lock)

__inline
LONGLONG
_AdjustSpace(
    IN LONGLONG sz,
    IN LONGLONG block
    )
{
    //
    // 4097,512 should return 4097+(512-1) = 4608
    // -4097,512 should return -(4097+(512-1)) = -4608
    //
    LONGLONG sign = (sz<0?-1:1);
    LONGLONG rem = (sz*sign)%block;

    return sz + (rem ? sign*(block-rem) : 0);
}

//
// These structures are stored as data associated with
// paths/filenames in the string table.
//

typedef struct _XFILE {
    //
    // -1 means it doesn't currently exist
    //
    LONGLONG CurrentSize;

    //
    // -1 means it will be deleted.
    //
    LONGLONG NewSize;

} XFILE, *PXFILE;


typedef struct _XDIRECTORY {
    //
    // Value indicating how many bytes will be required
    // to hold all the files in the FilesTable after they
    // are put on a file queue and then the queue is committed.
    //
    // This may be a negative number indicating that space will
    // actually be freed!
    //
    LONGLONG SpaceRequired;

    PVOID FilesTable;

} XDIRECTORY, *PXDIRECTORY;


typedef struct _XDRIVE {
    //
    // Value indicating how many bytes will be required
    // to hold all the files in the space list for this drive.
    //
    // This may be a negative number indicating that space will
    // actually be freed!
    //
    LONGLONG SpaceRequired;

    PVOID DirsTable;

    DWORD BytesPerCluster;

    //
    // This is the amount to skew SpaceRequired, based on
    // SetupAdjustDiskSpaceList(). We track this separately
    // for flexibility.
    //
    LONGLONG Slop;

} XDRIVE, *PXDRIVE;


typedef struct _RETURN_BUFFER_INFO {
    PVOID ReturnBuffer;
    DWORD ReturnBufferSize;
    DWORD RequiredSize;
#ifdef UNICODE
    BOOL IsUnicode;
#endif
} RETURN_BUFFER_INFO, *PRETURN_BUFFER_INFO;


BOOL
pSetupQueryDrivesInDiskSpaceList(
    IN  HDSKSPC DiskSpace,
    OUT PVOID   ReturnBuffer,       OPTIONAL
    IN  DWORD   ReturnBufferSize,
    OUT PDWORD  RequiredSize        OPTIONAL
#ifdef UNICODE
    IN ,BOOL    IsUnicode
#endif
    );

BOOL
pAddOrRemoveFileFromSectionToDiskSpaceList(
    IN OUT PDISK_SPACE_LIST        DiskSpaceList,
    IN     HINF                    LayoutInf,
    IN     PINFCONTEXT             LineInSection,   OPTIONAL
    IN     PCTSTR                  FileName,        OPTIONAL
    IN     PCTSTR                  TargetDirectory,
    IN     UINT                    Operation,
    IN     BOOL                    Add,
    IN     PSP_ALTPLATFORM_INFO_V2 AltPlatformInfo  OPTIONAL
    );

BOOL
_SetupAddSectionToDiskSpaceList(
    IN HDSKSPC                 DiskSpace,
    IN HINF                    InfHandle,
    IN HINF                    ListInfHandle,  OPTIONAL
    IN PCTSTR                  SectionName,
    IN UINT                    Operation,
    IN PVOID                   Reserved1,
    IN UINT                    Reserved2,
    IN PSP_ALTPLATFORM_INFO_V2 AltPlatformInfo OPTIONAL
    );

BOOL
_SetupRemoveSectionFromDiskSpaceList(
    IN HDSKSPC                 DiskSpace,
    IN HINF                    InfHandle,
    IN HINF                    ListInfHandle,  OPTIONAL
    IN PCTSTR                  SectionName,
    IN UINT                    Operation,
    IN PVOID                   Reserved1,
    IN UINT                    Reserved2,
    IN PSP_ALTPLATFORM_INFO_V2 AltPlatformInfo OPTIONAL
    );

BOOL
pAddOrRemoveInstallSection(
    IN HDSKSPC                 DiskSpace,
    IN HINF                    InfHandle,
    IN HINF                    LayoutInfHandle, OPTIONAL
    IN PCTSTR                  SectionName,
    IN BOOL                    Add,
    IN PSP_ALTPLATFORM_INFO_V2 AltPlatformInfo  OPTIONAL
    );

BOOL
pSetupAddToDiskSpaceList(
    IN  PDISK_SPACE_LIST DiskSpaceList,
    IN  PCTSTR           TargetFilespec,
    IN  LONGLONG         FileSize,
    IN  UINT             Operation
    );

BOOL
pSetupRemoveFromDiskSpaceList(
    IN  PDISK_SPACE_LIST DiskSpaceList,
    IN  PCTSTR           TargetFilespec,
    IN  UINT             Operation
    );

VOID
pRecalcSpace(
    IN OUT PDISK_SPACE_LIST DiskSpaceList,
    IN     LONG             DriveStringId
    );

BOOL
pStringTableCBEnumDrives(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    );

BOOL
pStringTableCBDelDrives(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    );

BOOL
pStringTableCBDelDirs(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    );

BOOL
pStringTableCBZeroDirsTableMember(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    );

BOOL
pStringTableCBDupMemberStringTable(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    );

DWORD
pParsePath(
    IN  PCTSTR    PathSpec,
    OUT PTSTR     Buffer,
    OUT PTSTR    *DirectoryPart,
    OUT PTSTR    *FilePart,
    OUT LONGLONG *FileSize,
    IN  UINT      Flags
    );




HDSKSPC
SetupCreateDiskSpaceList(
    IN PVOID Reserved1,
    IN DWORD Reserved2,
    IN UINT  Flags
    )

/*++

Routine Description:

    This routine creates a disk space list, which can be used to
    determine required disk space for a set of file operations
    that parallel those that an application will perform later,
    such as via the file queue APIs.

Arguments:

    Reserved1 - Unused, must be 0.

    Reserved2 - Unused, must be 0.

    Flags - Specifies flags that govern operation of the disk space list.

        SPDSL_IGNORE_DISK: If this flag is set, then delete operations
            will be ignored, and copy operations will behave as if
            the target files are not present on the disk, regardless of
            whether the files are actually present. This flag is useful
            to determine an approximate size that can be associated with
            a set of files.
        SPDSL_DISALLOW_NEGATIVE_ADJUST:

Return Value:

    Handle to disk space list to be used in subsequent operations,
    or NULL if the routine fails, in which case GetLastError()
    returns extended error info.

--*/

{
    PDISK_SPACE_LIST SpaceList;
    DWORD d;

    //
    // Validate args.
    //
    if(Reserved1 || Reserved2) {
        d = ERROR_INVALID_PARAMETER;
        goto c1;
    }
    //
    // validate what flags are allowed
    //
    if (Flags & ~(SPDSL_IGNORE_DISK|SPDSL_DISALLOW_NEGATIVE_ADJUST)) {
        d = ERROR_INVALID_PARAMETER;
        goto c1;
    }

    //
    // Allocate space for a structure.
    //
    SpaceList = MyMalloc(sizeof(DISK_SPACE_LIST));
    if(!SpaceList) {
        d = ERROR_NOT_ENOUGH_MEMORY;
        goto c1;
    }

    ZeroMemory(SpaceList,sizeof(DISK_SPACE_LIST));

    SpaceList->Flags = Flags;

    //
    // Create a string table for the drives.
    //
    SpaceList->DrivesTable = pStringTableInitialize(sizeof(XDRIVE));
    if(!SpaceList->DrivesTable) {
        d = ERROR_NOT_ENOUGH_MEMORY;
        goto c2;
    }

    //
    // Create a locking structure for this guy.
    //
    if(!InitializeSynchronizedAccess(&SpaceList->Lock)) {
        d = ERROR_NOT_ENOUGH_MEMORY;
        goto c3;
    }

    //
    // Success.
    //
    return(SpaceList);

c3:
    pStringTableDestroy(SpaceList->DrivesTable);
c2:
    if(SpaceList) {
        MyFree(SpaceList);
    }
c1:
    SetLastError(d);
    return(NULL);
}


#ifdef UNICODE
//
// Ansi version.
//
HDSKSPC
SetupCreateDiskSpaceListA(
    IN PVOID Reserved1,
    IN DWORD Reserved2,
    IN UINT  Flags
    )
{
    //
    // Nothing actually ansi/unicode specific now
    //
    return(SetupCreateDiskSpaceListW(Reserved1,Reserved2,Flags));
}
#else
//
// Unicode stub.
//
HDSKSPC
SetupCreateDiskSpaceListW(
    IN PVOID Reserved1,
    IN DWORD Reserved2,
    IN UINT  Flags
    )
{
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    UNREFERENCED_PARAMETER(Flags);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(NULL);
}
#endif


HDSKSPC
SetupDuplicateDiskSpaceList(
    IN HDSKSPC DiskSpace,
    IN PVOID   Reserved1,
    IN DWORD   Reserved2,
    IN UINT    Flags
    )

/*++

Routine Description:

    This routine duplicates a disk space, creating a new, fully independent
    disk space list.

Arguments:

    DiskSpace - supplies handle of disk space list to be duplicated.

    Reserved1 - reserved, must be 0.

    Reserved2 - reserved, must be 0.

    Flags - reserved, must be 0.

Return Value:

    If successful, returns a handle to a new disk space list.
    NULL if failure; GetLastError() returns extended error info.

--*/

{
    PDISK_SPACE_LIST OldSpaceList;
    PDISK_SPACE_LIST NewSpaceList = NULL; // shut up preFast
    DWORD d;
    BOOL b;
    XDRIVE xDrive;

    //
    // Validate args.
    //
    if(Reserved1 || Reserved2 || Flags) {
        d = ERROR_INVALID_PARAMETER;
        goto c0;
    }

    //
    // Allocate space for a new structure and create a locking structure.
    //
    NewSpaceList = MyMalloc(sizeof(DISK_SPACE_LIST));
    if(!NewSpaceList) {
        d = ERROR_NOT_ENOUGH_MEMORY;
        goto c0;
    }
    ZeroMemory(NewSpaceList,sizeof(DISK_SPACE_LIST));

    if(!InitializeSynchronizedAccess(&NewSpaceList->Lock)) {
        d = ERROR_NOT_ENOUGH_MEMORY;
        goto c1;
    }

    //
    // Lock down the existing space list.
    //
    OldSpaceList = DiskSpace;
    d = NO_ERROR;
    try {
        if(!LockIt(OldSpaceList)) {
            d = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        d = ERROR_INVALID_HANDLE;
    }

    if(d != NO_ERROR) {
        goto c2;
    }

    //
    // Duplicate the top-level string table. After we do this, we'll have
    // a string table whose items' extra data is XDRIVE structures,
    // which will each contain a string table handle for a string table for
    // directories. But we don't want to share that string table between
    // the old and new disk space tables. So start by zeroing the DirsTable
    // members of all the XDRIVE structures. This will let us clean up
    // more easily later in the error path.
    //
    MYASSERT(OldSpaceList->DrivesTable);
    NewSpaceList->DrivesTable = pStringTableDuplicate(OldSpaceList->DrivesTable);
    if(!NewSpaceList->DrivesTable) {
        d = ERROR_NOT_ENOUGH_MEMORY;
        goto c3;
    }

    pStringTableEnum(
        NewSpaceList->DrivesTable,
        &xDrive,
        sizeof(XDRIVE),
        pStringTableCBZeroDirsTableMember,
        0
        );

    //
    // Now we enumerate the old drives table and duplicate each directory
    // string table into the new drives table. We take heavy advantage
    // of the fact that the ids are the same between the old and new tables.
    //
    b = pStringTableEnum(
            OldSpaceList->DrivesTable,
            &xDrive,
            sizeof(XDRIVE),
            pStringTableCBDupMemberStringTable,
            (LPARAM)NewSpaceList->DrivesTable
            );

    if(!b) {
        d = ERROR_NOT_ENOUGH_MEMORY;
    }

    if(d != NO_ERROR) {
        pStringTableEnum(
            NewSpaceList->DrivesTable,
            &xDrive,
            sizeof(XDRIVE),
            pStringTableCBDelDrives,
            0
            );
        pStringTableDestroy(NewSpaceList->DrivesTable);
    }
c3:
    //
    // Unlock the existing space list.
    //
    try {
        UnlockIt(OldSpaceList);
    } except(EXCEPTION_EXECUTE_HANDLER) {
        //
        // Don't worry if the pointer went bad, we're already done
        // with the important work.
        //
        ;
    }
c2:
    if(d != NO_ERROR) {
        DestroySynchronizedAccess(&NewSpaceList->Lock);
    }
c1:
    if(d != NO_ERROR) {
        MyFree(NewSpaceList);
    }
c0:
    SetLastError(d);
    return((d == NO_ERROR) ? NewSpaceList : NULL);
}

#ifdef UNICODE
//
// Ansi version.
//
HDSKSPC
SetupDuplicateDiskSpaceListA(
    IN HDSKSPC DiskSpace,
    IN PVOID   Reserved1,
    IN DWORD   Reserved2,
    IN UINT    Flags
    )
{
    //
    // Nothing actually ansi/unicode specific now
    //
    return(SetupDuplicateDiskSpaceListW(DiskSpace,Reserved1,Reserved2,Flags));
}
#else
//
// Unicode stub.
//
HDSKSPC
SetupDuplicateDiskSpaceListW(
    IN HDSKSPC DiskSpace,
    IN PVOID   Reserved1,
    IN DWORD   Reserved2,
    IN UINT    Flags
    )
{
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    UNREFERENCED_PARAMETER(Flags);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(NULL);
}
#endif


BOOL
SetupDestroyDiskSpaceList(
    IN OUT HDSKSPC DiskSpace
    )

/*++

Routine Description:

    This routine destryos a disk space list which was created
    with SetupCreateDiskSpaceList() and releases all resources
    used thereby.

Arguments:

    DiskSpace - supplies handle to space list to be deconstructed.

Return Value:

    Boolean value indicating outcome. If FALSE, extended error info
    is available from GetLastError().

--*/

{
    PDISK_SPACE_LIST DiskSpaceList;
    DWORD rc;
    XDRIVE xDrive;

    DiskSpaceList = DiskSpace;
    rc = NO_ERROR;

    try {
        if(!LockIt(DiskSpaceList)) {
            rc = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    if(rc != NO_ERROR) {
        SetLastError(rc);
        return(FALSE);
    }

    try {
        DestroySynchronizedAccess(&DiskSpaceList->Lock);
    } except(EXCEPTION_EXECUTE_HANDLER) {
        //
        // Just swallow this.
        //
        ;
    }

    try {

        MYASSERT(DiskSpaceList->DrivesTable);
        //
        // Enumerate the drives string table. This in turn causes
        // all directory and file string tables to get destroyed.
        //
        pStringTableEnum(
            DiskSpaceList->DrivesTable,
            &xDrive,
            sizeof(XDRIVE),
            pStringTableCBDelDrives,
            0
            );

        pStringTableDestroy(DiskSpaceList->DrivesTable);

        //
        // Free the disk space list guy.
        //
        MyFree(DiskSpaceList);

    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    SetLastError(rc);
    return(rc == NO_ERROR);
}


BOOL
SetupAdjustDiskSpaceList(
    IN HDSKSPC  DiskSpace,
    IN LPCTSTR  DriveRoot,
    IN LONGLONG Amount,
    IN PVOID    Reserved1,
    IN UINT     Reserved2
    )

/*++

Routine Description:

    This routine is used to add an absolute amount of required disk space
    for a drive.

Arguments:

    DiskSpace - supplies a handle to a disk space list.

    DriveRoot - specifies a valid Win32 drive root. If this drive is not
        currently represented in the disk space list then an entry for it
        is added.

    Amount - supplies the amount of disk space by which to adjust space
        required on the drive. Use a negative number to remove space.

    Reserved1 - must be 0.

    Reserved2 - must be 0.

Return Value:

--*/

{
    DWORD rc;
    BOOL b;

    if(Reserved1 || Reserved2) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    rc = NO_ERROR;

    try {
        if(!LockIt(((PDISK_SPACE_LIST)DiskSpace))) {
            rc = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    if(rc != NO_ERROR) {
        SetLastError(rc);
        return(FALSE);
    }

    //
    // pSetupAddToDiskSpaceList does all the work. That routine
    // uses SEH so no need for try/excepts here.
    //
    b = pSetupAddToDiskSpaceList(DiskSpace,DriveRoot,Amount,(UINT)(-1));
    rc = GetLastError();

    //
    // The try/except around the unlock simply prevents us from faulting
    // but we don't return error if the pointer goes bad.
    //
    try {
        UnlockIt(((PDISK_SPACE_LIST)DiskSpace));
    } except(EXCEPTION_EXECUTE_HANDLER) {
        ;
    }

    SetLastError(rc);
    return(b);
}


#ifdef UNICODE
//
// ANSI version
//
BOOL
SetupAdjustDiskSpaceListA(
    IN HDSKSPC  DiskSpace,
    IN LPCSTR   DriveRoot,
    IN LONGLONG Amount,
    IN PVOID    Reserved1,
    IN UINT     Reserved2
    )
{
    LPCWSTR p;
    BOOL b;
    DWORD rc;

    rc = pSetupCaptureAndConvertAnsiArg(DriveRoot,&p);
    if(rc != NO_ERROR) {
        SetLastError(rc);
        return(FALSE);
    }

    b = SetupAdjustDiskSpaceListW(DiskSpace,p,Amount,Reserved1,Reserved2);
    rc = GetLastError();

    MyFree(p);

    SetLastError(rc);
    return(b);
}
#else
//
// Unicode stub
//
BOOL
SetupAdjustDiskSpaceListW(
    IN HDSKSPC  DiskSpace,
    IN LPCWSTR  DriveRoot,
    IN LONGLONG Amount,
    IN PVOID    Reserved1,
    IN UINT     Reserved2
    )
{
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(DriveRoot);
    UNREFERENCED_PARAMETER(Amount);
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(FALSE);
}
#endif


BOOL
SetupAddToDiskSpaceList(
    IN HDSKSPC  DiskSpace,
    IN PCTSTR   TargetFilespec,
    IN LONGLONG FileSize,
    IN UINT     Operation,
    IN PVOID    Reserved1,
    IN UINT     Reserved2
    )

/*++

Routine Description:

    This routine adds a single delete or copy operation to a
    disk space list.

    Note that disk compression is completely ignored by this routine.
    Files are assumed to occupy their full size on the disk.

Arguments:

    DiskSpace - specifies handle to disk space list created by
        SetupCreateDiskSpaceList().

    TargetFilespec - specifies filename of the file to add
        to the disk space list. This will generally be a full win32
        path, though this is not a requirement. If it is not then
        standard win32 path semantics apply.

    FileSize - supplies the (uncompressed) size of the file as it will
        exist on the target when copied. Ignored for FILEOP_DELETE.

    Operation - one of FILEOP_DELETE or FILEOP_COPY.

    Reserved1 - must be 0.

    Reserved2 - must be 0.

Return Value:

    Boolean value indicating outcome. If FALSE, GetLastError() returns
    extended error information.

--*/

{
    DWORD rc;
    BOOL b;

    if(Reserved1 || Reserved2) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    rc = NO_ERROR;

    try {
        if(!LockIt(((PDISK_SPACE_LIST)DiskSpace))) {
            rc = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    if(rc != NO_ERROR) {
        SetLastError(rc);
        return(FALSE);
    }

    b = pSetupAddToDiskSpaceList(DiskSpace,TargetFilespec,FileSize,Operation);
    rc = GetLastError();

    //
    // The try/except around the unlock simply prevents us from faulting
    // but we don't return error if the pointer goes bad.
    //
    try {
        UnlockIt(((PDISK_SPACE_LIST)DiskSpace));
    } except(EXCEPTION_EXECUTE_HANDLER) {
        ;
    }

    SetLastError(rc);
    return(b);
}


#ifdef UNICODE
//
// ANSI version
//
BOOL
SetupAddToDiskSpaceListA(
    IN HDSKSPC  DiskSpace,
    IN PCSTR    TargetFilespec,
    IN LONGLONG FileSize,
    IN UINT     Operation,
    IN PVOID    Reserved1,
    IN UINT     Reserved2
    )
{
    PWSTR targetFilespec;
    DWORD rc;
    BOOL b;

    rc = pSetupCaptureAndConvertAnsiArg(TargetFilespec,&targetFilespec);
    if(rc != NO_ERROR) {
        return(rc);
    }

    b = SetupAddToDiskSpaceListW(DiskSpace,targetFilespec,FileSize,Operation,Reserved1,Reserved2);
    rc = GetLastError();

    MyFree(targetFilespec);

    SetLastError(rc);
    return(b);
}
#else
//
// Unicode stub
//
BOOL
SetupAddToDiskSpaceListW(
    IN HDSKSPC  DiskSpace,
    IN PCWSTR   TargetFilespec,
    IN LONGLONG FileSize,
    IN UINT     Operation,
    IN PVOID    Reserved1,
    IN UINT     Reserved2
    )
{
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(TargetFilespec);
    UNREFERENCED_PARAMETER(FileSize);
    UNREFERENCED_PARAMETER(Operation);
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(FALSE);
}
#endif


BOOL
_SetupAddSectionToDiskSpaceList(
    IN HDSKSPC                 DiskSpace,
    IN HINF                    InfHandle,
    IN HINF                    ListInfHandle,  OPTIONAL
    IN PCTSTR                  SectionName,
    IN UINT                    Operation,
    IN PVOID                   Reserved1,
    IN UINT                    Reserved2,
    IN PSP_ALTPLATFORM_INFO_V2 AltPlatformInfo OPTIONAL
    )

/*++

Routine Description:

    This routine adds a delete or copy section to a disk space list.

    Note that disk compression is completely ignored by this routine.
    Files are assumed to occupy their full size on the disk.

Arguments:

    DiskSpace - specifies handle to disk space list created by
        SetupCreateDiskSpaceList().

    InfHandle - supplies a handle to an open inf file, that contains the
        [SourceDisksFiles] section, and, if ListInfHandle is not specified,
        contains the section named by SectionName. This handle must be for
        a win95-style inf.

    ListInfHandle - if specified, supplies a handle to an open inf file
        containing the section to be added to the disk space list.
        Otherwise InfHandle is assumed to contain the section.

    SectionName - supplies the name of the section to be added to
        the disk space list.

    Operation - one of FILEOP_DELETE or FILEOP_COPY.

    Reserved1 - must be 0.

    Reserved2 - must be 0.

    AltPlatformInfo - optionally, supplies alternate platform info to be used
        in determining the appropriately-decorated [SourceDisksFiles] section
        containing file size information.

Return Value:

    Boolean value indicating outcome. If FALSE, GetLastError() returns
    extended error information.

--*/

{
    PDISK_SPACE_LIST DiskSpaceList;
    LONG LineCount;
    PCTSTR TargetFilename;
    BOOL b;
    INFCONTEXT LineContext;
    TCHAR FullTargetPath[MAX_PATH];
    DWORD FileSize;
    DWORD rc;

    //
    // Note throughout this routine that very little structured exception handling
    // is needed, since most of the work is performed by subroutines that are
    // properly guarded.
    //

    if(Reserved1 || Reserved2) {
        rc = ERROR_INVALID_PARAMETER;
        b = FALSE;
        goto c0;
    }

    //
    // Lock down the DiskSpace handle/structure.
    //
    DiskSpaceList = DiskSpace;
    rc = NO_ERROR;

    try {
        if(!LockIt(DiskSpaceList)) {
            rc = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    if(rc != NO_ERROR) {
        b = FALSE;
        goto c0;
    }

    if(!ListInfHandle) {
        ListInfHandle = InfHandle;
    }

    //
    // The section must at least exist; an empty section is
    // a trivial success case.
    //
    LineCount = SetupGetLineCount(ListInfHandle,SectionName);
    if(LineCount == -1) {
        rc = ERROR_SECTION_NOT_FOUND;
        b = FALSE;
        goto c1;
    }
    if(!LineCount) {
        b = TRUE;
        goto c1;
    }

    //
    // Find the first line. We know there is at least one since the line count
    // was checked above. Sanity check it anyway.
    //
    b = SetupFindFirstLine(ListInfHandle,SectionName,NULL,&LineContext);
    MYASSERT(b);
    if(!b) {
        rc = ERROR_SECTION_NOT_FOUND;
        goto c1;
    }

    //
    // Find the target path for this section.
    //
    if(!SetupGetTargetPath(NULL,&LineContext,NULL,FullTargetPath,MAX_PATH,NULL)) {
        rc = GetLastError();
        goto c1;
    }

    //
    // Process each line in the section.
    //
    do {

        b = pAddOrRemoveFileFromSectionToDiskSpaceList(
                DiskSpaceList,
                InfHandle,
                &LineContext,
                NULL,
                FullTargetPath,
                Operation,
                TRUE,
                AltPlatformInfo
                );

        if(!b) {
            rc = GetLastError();
        }

    } while(b && SetupFindNextLine(&LineContext,&LineContext));

c1:
    try {
        UnlockIt(DiskSpaceList);
    } except(EXCEPTION_EXECUTE_HANDLER) {
        ;
    }
c0:
    SetLastError(rc);
    return(b);
}

#ifdef UNICODE
//
// ANSI version
//
BOOL
SetupAddSectionToDiskSpaceListA(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    ListInfHandle,  OPTIONAL
    IN PCSTR   SectionName,
    IN UINT    Operation,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    PWSTR sectionName;
    BOOL b;
    DWORD rc;

    rc = pSetupCaptureAndConvertAnsiArg(SectionName,&sectionName);
    if(rc == NO_ERROR) {

        b = _SetupAddSectionToDiskSpaceList(
                DiskSpace,
                InfHandle,
                ListInfHandle,
                sectionName,
                Operation,
                Reserved1,
                Reserved2,
                NULL
               );

        rc = GetLastError();

        MyFree(sectionName);
    } else {
        b = FALSE;
    }

    SetLastError(rc);
    return(b);
}
#else
//
// Unicode stub
//
BOOL
SetupAddSectionToDiskSpaceListW(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    ListInfHandle,  OPTIONAL
    IN PCWSTR  SectionName,
    IN UINT    Operation,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(InfHandle);
    UNREFERENCED_PARAMETER(ListInfHandle);
    UNREFERENCED_PARAMETER(SectionName);
    UNREFERENCED_PARAMETER(Operation);
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(FALSE);
}
#endif

BOOL
SetupAddSectionToDiskSpaceList(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    ListInfHandle,  OPTIONAL
    IN PCTSTR  SectionName,
    IN UINT    Operation,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    return _SetupAddSectionToDiskSpaceList(DiskSpace,
                                           InfHandle,
                                           ListInfHandle,
                                           SectionName,
                                           Operation,
                                           Reserved1,
                                           Reserved2,
                                           NULL
                                          );
}


BOOL
SetupAddInstallSectionToDiskSpaceList(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    LayoutInfHandle,     OPTIONAL
    IN PCTSTR  SectionName,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )

/*++

Routine Description:

    Processes an install section, looking for CopyFiles and DelFiles
    lines, and adds those sections to a disk space list.

Arguments:

Return Value:

    Win32 error code indicating outcome.

--*/

{
    if(Reserved1 || Reserved2) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    return(pAddOrRemoveInstallSection(DiskSpace,
                                      InfHandle,
                                      LayoutInfHandle,
                                      SectionName,
                                      TRUE,
                                      NULL
                                     ));
}

#ifdef UNICODE
//
// ANSI version
//
BOOL
SetupAddInstallSectionToDiskSpaceListA(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    LayoutInfHandle,     OPTIONAL
    IN PCSTR   SectionName,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    PWSTR sectionName;
    DWORD rc;
    BOOL b;

    rc = pSetupCaptureAndConvertAnsiArg(SectionName,&sectionName);
    if(rc == NO_ERROR) {

        b = SetupAddInstallSectionToDiskSpaceListW(
                DiskSpace,
                InfHandle,
                LayoutInfHandle,
                sectionName,
                Reserved1,
                Reserved2
                );

        rc = GetLastError();

        MyFree(sectionName);
    } else {
        b = FALSE;
    }

    SetLastError(rc);
    return(b);
}
#else
//
// Unicode stub
//
BOOL
SetupAddInstallSectionToDiskSpaceListW(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    LayoutInfHandle,     OPTIONAL
    IN PCWSTR  SectionName,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(InfHandle);
    UNREFERENCED_PARAMETER(LayoutInfHandle);
    UNREFERENCED_PARAMETER(SectionName);
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(FALSE);
}
#endif


BOOL
SetupRemoveFromDiskSpaceList(
    IN HDSKSPC DiskSpace,
    IN PCTSTR  TargetFilespec,
    IN UINT    Operation,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )

/*++

Routine Description:

    This routine removes a single delete or copy operation from a
    disk space list.

Arguments:

    DiskSpace - specifies handle to disk space list created by
        SetupCreateDiskSpaceList().

    TargetFilespec - specifies filename of the file to remove from
        the disk space list. This will generally be a full win32
        path, though this is not a requirement. If it is not then
        standard win32 path semantics apply.

    Operation - one of FILEOP_DELETE or FILEOP_COPY.

    Reserved1 - must be 0.

    Reserved2 - must be 0.

Return Value:

    If the file was not in the list, the routine returns TRUE and
    GetLastError() returns ERROR_INVALID_DRIVE or ERROR_INVALID_NAME.
    If the file was in the list then upon success the routine returns
    TRUE and GetLastError() returns NO_ERROR.

    If the routine fails for some other reason it returns FALSE and GetLastError()
    can be used to fetch extended error info.

--*/

{
    DWORD rc;
    BOOL b;

    if(Reserved1 || Reserved2) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    rc = NO_ERROR;

    try {
        if(!LockIt(((PDISK_SPACE_LIST)DiskSpace))) {
            rc = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    if(rc != NO_ERROR) {
        SetLastError(rc);
        return(FALSE);
    }

    b = pSetupRemoveFromDiskSpaceList(DiskSpace,TargetFilespec,Operation);
    rc = GetLastError();

    //
    // The try/except around the unlock simply prevents us from faulting
    // but we don't return error if the pointer goes bad.
    //
    try {
        UnlockIt(((PDISK_SPACE_LIST)DiskSpace));
    } except(EXCEPTION_EXECUTE_HANDLER) {
        ;
    }

    SetLastError(rc);
    return(b);
}


#ifdef UNICODE
//
// ANSI version
//
BOOL
SetupRemoveFromDiskSpaceListA(
    IN HDSKSPC DiskSpace,
    IN PCSTR   TargetFilespec,
    IN UINT    Operation,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    PWSTR targetFilespec;
    DWORD rc;
    BOOL b;

    rc = pSetupCaptureAndConvertAnsiArg(TargetFilespec,&targetFilespec);
    if(rc != NO_ERROR) {
        return(rc);
    }

    b = SetupRemoveFromDiskSpaceListW(DiskSpace,targetFilespec,Operation,Reserved1,Reserved2);
    rc = GetLastError();

    MyFree(targetFilespec);

    SetLastError(rc);
    return(b);
}
#else
//
// Unicode stub
//
BOOL
SetupRemoveFromDiskSpaceListW(
    IN HDSKSPC DiskSpace,
    IN PCWSTR  TargetFilespec,
    IN UINT    Operation,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(TargetFilespec);
    UNREFERENCED_PARAMETER(Operation);
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(FALSE);
}
#endif


BOOL
_SetupRemoveSectionFromDiskSpaceList(
    IN HDSKSPC                 DiskSpace,
    IN HINF                    InfHandle,
    IN HINF                    ListInfHandle,  OPTIONAL
    IN PCTSTR                  SectionName,
    IN UINT                    Operation,
    IN PVOID                   Reserved1,
    IN UINT                    Reserved2,
    IN PSP_ALTPLATFORM_INFO_V2 AltPlatformInfo OPTIONAL
    )

/*++

Routine Description:

    This routine removes a delete or copy section from a disk space list.
    The section is presumed to have been added via SetupAddSectionToDiskSpaceList,
    though this is not a requirement. Files that have not actually been added
    will not be removed.

    Note that disk compression is completely ignored by this routine.
    Files are assumed to occupy their full size on the disk.

Arguments:

    DiskSpace - specifies handle to disk space list created by
        SetupCreateDiskSpaceList().

    InfHandle - supplies a handle to an open inf file, that contains the
        [SourceDisksFiles] section, and, if ListInfHandle is not specified,
        contains the section named by SectionName. This handle must be for
        a win95-style inf.

    ListInfHandle - if specified, supplies a handle to an open inf file
        containing the section to be removed from the disk space list.
        Otherwise InfHandle is assumed to contain the section.

    SectionName - supplies the name of the section to be added to
        the disk space list.

    Operation - one of FILEOP_DELETE or FILEOP_COPY.

    Reserved1 - must be 0.

    Reserved2 - must be 0.

    AltPlatformInfo - optionally, supplies alternate platform info to be used
        in determining the appropriately-decorated [SourceDisksFiles] section
        containing file size information.


Return Value:

    Boolean value indicating outcome. If FALSE, GetLastError() returns
    extended error information.

--*/

{
    PDISK_SPACE_LIST DiskSpaceList;
    LONG LineCount;
    PCTSTR TargetFilename;
    BOOL b;
    INFCONTEXT LineContext;
    TCHAR FullTargetPath[MAX_PATH];
    DWORD rc;

    //
    // Note throughout this routine that very little structured exception handling
    // is needed, since most of the work is performed by subroutines that are
    // properly guarded.
    //

    if(Reserved1 || Reserved2) {
        rc = ERROR_INVALID_PARAMETER;
        b = FALSE;
        goto c0;
    }

    //
    // Lock down the DiskSpace handle/structure.
    //
    DiskSpaceList = DiskSpace;
    rc = NO_ERROR;

    try {
        if(!LockIt(DiskSpaceList)) {
            rc = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    if(rc != NO_ERROR) {
        b = FALSE;
        goto c0;
    }

    if(!ListInfHandle) {
        ListInfHandle = InfHandle;
    }

    //
    // The section must at least exist; an empty section is
    // a trivial success case.
    //
    LineCount = SetupGetLineCount(ListInfHandle,SectionName);
    if(LineCount == -1) {
        rc = ERROR_SECTION_NOT_FOUND;
        b = FALSE;
        goto c1;
    }
    if(!LineCount) {
        b = TRUE;
        goto c1;
    }

    //
    // Find the first line. We know there is at least one since the line count
    // was checked above. Sanity check it anyway.
    //
    b = SetupFindFirstLine(ListInfHandle,SectionName,NULL,&LineContext);
    MYASSERT(b);
    if(!b) {
        rc = ERROR_SECTION_NOT_FOUND;
        b = FALSE;
        goto c1;
    }

    //
    // Find the target path for this section.
    //
    if(!SetupGetTargetPath(NULL,&LineContext,NULL,FullTargetPath,MAX_PATH,NULL)) {
        rc = GetLastError();
        b = FALSE;
        goto c1;
    }

    //
    // Process each line in the section.
    //
    do {

        b = pAddOrRemoveFileFromSectionToDiskSpaceList(
                DiskSpaceList,
                InfHandle,
                &LineContext,
                NULL,
                FullTargetPath,
                Operation,
                FALSE,
                AltPlatformInfo
               );

        if(!b) {
            rc = GetLastError();
        }
    } while(b && SetupFindNextLine(&LineContext,&LineContext));

c1:
    try {
        UnlockIt(DiskSpaceList);
    } except(EXCEPTION_EXECUTE_HANDLER) {
        ;
    }

c0:
    SetLastError(rc);
    return(b);
}

#ifdef UNICODE
//
// ANSI version
//
BOOL
SetupRemoveSectionFromDiskSpaceListA(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    ListInfHandle,  OPTIONAL
    IN PCSTR   SectionName,
    IN UINT    Operation,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    PWSTR sectionName;
    BOOL b;
    DWORD rc;

    rc = pSetupCaptureAndConvertAnsiArg(SectionName,&sectionName);
    if(rc == NO_ERROR) {

        b = _SetupRemoveSectionFromDiskSpaceList(
                DiskSpace,
                InfHandle,
                ListInfHandle,
                sectionName,
                Operation,
                Reserved1,
                Reserved2,
                NULL
               );

        rc = GetLastError();

        MyFree(sectionName);
    } else {
        b = FALSE;
    }

    SetLastError(rc);
    return(b);
}
#else
//
// Unicode stub
//
BOOL
SetupRemoveSectionFromDiskSpaceListW(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    ListInfHandle,  OPTIONAL
    IN PCWSTR  SectionName,
    IN UINT    Operation,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(InfHandle);
    UNREFERENCED_PARAMETER(ListInfHandle);
    UNREFERENCED_PARAMETER(SectionName);
    UNREFERENCED_PARAMETER(Operation);
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(FALSE);
}
#endif

BOOL
SetupRemoveSectionFromDiskSpaceList(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    ListInfHandle,  OPTIONAL
    IN PCTSTR  SectionName,
    IN UINT    Operation,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    return _SetupRemoveSectionFromDiskSpaceList(DiskSpace,
                                                InfHandle,
                                                ListInfHandle,
                                                SectionName,
                                                Operation,
                                                Reserved1,
                                                Reserved2,
                                                NULL
                                               );
}


BOOL
SetupRemoveInstallSectionFromDiskSpaceList(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    LayoutInfHandle,     OPTIONAL
    IN PCTSTR  SectionName,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )

/*++

Routine Description:

    Processes an install section, looking for CopyFiles and DelFiles
    lines, and removes those sections from a disk space list.

Arguments:

    DiskSpace - supplies a handle to a disk space list.

    InfHandle - supplies a handle to an open inf file, that contains the
        [SourceDisksFiles] section, and, if ListInfHandle is not specified,
        contains the section named by SectionName. This handle must be for
        a win95-style inf.

    ListInfHandle - if specified, supplies a handle to an open inf file
        containing the section to be removed from the disk space list.
        Otherwise InfHandle is assumed to contain the section.

    SectionName - supplies the name of the section to be added to
        the disk space list.

    Reserved1 - must be 0.

    Reserved2 - must be 0.

Return Value:

    Boolean value indicating outcome. If FALSE, extended error info
    is available via GetLastError().


--*/

{
    if(Reserved1 || Reserved2) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    return(pAddOrRemoveInstallSection(DiskSpace,
                                      InfHandle,
                                      LayoutInfHandle,
                                      SectionName,
                                      FALSE,
                                      NULL
                                     ));
}

#ifdef UNICODE
//
// ANSI version
//
BOOL
SetupRemoveInstallSectionFromDiskSpaceListA(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    LayoutInfHandle,     OPTIONAL
    IN PCSTR   SectionName,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    PWSTR sectionName;
    DWORD rc;
    BOOL b;

    rc = pSetupCaptureAndConvertAnsiArg(SectionName,&sectionName);
    if(rc == NO_ERROR) {

        b = SetupRemoveInstallSectionFromDiskSpaceListW(
                DiskSpace,
                InfHandle,
                LayoutInfHandle,
                sectionName,
                Reserved1,
                Reserved2
                );

        rc = GetLastError();

        MyFree(sectionName);
    } else {
        b = FALSE;
    }

    SetLastError(rc);
    return(b);
}
#else
//
// Unicode stub
//
BOOL
SetupRemoveInstallSectionFromDiskSpaceListW(
    IN HDSKSPC DiskSpace,
    IN HINF    InfHandle,
    IN HINF    LayoutInfHandle,     OPTIONAL
    IN PCWSTR  SectionName,
    IN PVOID   Reserved1,
    IN UINT    Reserved2
    )
{
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(InfHandle);
    UNREFERENCED_PARAMETER(LayoutInfHandle);
    UNREFERENCED_PARAMETER(SectionName);
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(FALSE);
}
#endif


BOOL
SetupQuerySpaceRequiredOnDrive(
    IN  HDSKSPC   DiskSpace,
    IN  PCTSTR    DriveSpec,
    OUT LONGLONG *SpaceRequired,
    IN  PVOID     Reserved1,
    IN  UINT      Reserved2
    )

/*++

Routine Description:

    Examine a disk space list to determine the space required on a
    particular drive.

Arguments:

    DiskSpace - supplies a handle to a disk space list.

    DriveSpec - specifies the drive for which space info is desired.
        This should be in the form x: or \\server\share.

    SpaceRequired - if the function succeeds, receives the amount
        of space required. This may be 0 or a negative number!

    Reserved1 - reserved, must be 0.

    Reserved2 - reserved, must be 0.

Return Value:

    Boolean value indicating outcome. If TRUE, SpaceRequired is filled in.

    If FALSE, extended error info is available via GetLastError():

    ERROR_INVALID_HANDLE - the specified DiskSpace handle is invalid.
    ERROR_INVALID_DRIVE - the given drive is not in the disk space list.

--*/

{
    PDISK_SPACE_LIST DiskSpaceList;
    DWORD rc;
    BOOL b;
    LONG l;
    DWORD Hash;
    DWORD StringLength;
    XDRIVE xDrive;
    TCHAR drive[MAX_PATH];

    if(Reserved1 || Reserved2) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    //
    // Lock down the DiskSpace handle/structure.
    //
    DiskSpaceList = DiskSpace;
    rc = NO_ERROR;

    try {
        if(!LockIt(DiskSpaceList)) {
            rc = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    if(rc != NO_ERROR) {
        SetLastError(rc);
        return(FALSE);
    }

    try {
        lstrcpyn(drive,DriveSpec,MAX_PATH);

        MYASSERT(DiskSpaceList->DrivesTable);

        l = pStringTableLookUpString(
                DiskSpaceList->DrivesTable,
                drive,
                &StringLength,
                &Hash,
                NULL,
                STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                NULL,
                0
                );

        if(l != -1) {
            //
            // Found the drive. Recalc space and return it.
            //
            pRecalcSpace(DiskSpaceList,l);
            pStringTableGetExtraData(DiskSpaceList->DrivesTable,l,&xDrive,sizeof(XDRIVE));
            *SpaceRequired = xDrive.SpaceRequired + xDrive.Slop;
            b = TRUE;
        } else {
            rc = ERROR_INVALID_DRIVE;
            b = FALSE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_PARAMETER;
        b = FALSE;
    }

    try {
        UnlockIt(DiskSpaceList);
    } except(EXCEPTION_EXECUTE_HANDLER) {
        ;
    }

    SetLastError(rc);
    return(b);
}


#ifdef UNICODE
//
// ANSI version
//
BOOL
SetupQuerySpaceRequiredOnDriveA(
    IN  HDSKSPC   DiskSpace,
    IN  PCSTR     DriveSpec,
    OUT LONGLONG *SpaceRequired,
    IN  PVOID     Reserved1,
    IN  UINT      Reserved2
    )
{
    PCWSTR drivespec;
    DWORD rc;
    BOOL b;

    rc = pSetupCaptureAndConvertAnsiArg(DriveSpec,&drivespec);
    if(rc == NO_ERROR) {

        b = SetupQuerySpaceRequiredOnDrive(DiskSpace,drivespec,SpaceRequired,Reserved1,Reserved2);
        rc = GetLastError();

        MyFree(drivespec);
    } else {
        b = FALSE;
    }

    SetLastError(rc);
    return(b);
}
#else
//
// Unicode stub
//
BOOL
SetupQuerySpaceRequiredOnDriveW(
    IN  HDSKSPC   DiskSpace,
    IN  PCWSTR    DriveSpec,
    OUT LONGLONG *SpaceRequired,
    IN  PVOID     Reserved1,
    IN  UINT      Reserved2
    )
{
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(DriveSpec);
    UNREFERENCED_PARAMETER(SpaceRequired);
    UNREFERENCED_PARAMETER(Reserved1);
    UNREFERENCED_PARAMETER(Reserved2);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(FALSE);
}
#endif


BOOL
SetupQueryDrivesInDiskSpaceListA(
    IN  HDSKSPC DiskSpace,
    OUT PSTR    ReturnBuffer,       OPTIONAL
    IN  DWORD   ReturnBufferSize,
    OUT PDWORD  RequiredSize        OPTIONAL
    )

/*++

Routine Description:

    This routine fills a caller-supplied buffer with drive specs for each
    drive currently represented in the given disk space list.

Arguments:

    DiskSpace - supplies a disk space list handle.

    ReturnBuffer - if supplied, points to a buffer that gets packed with
        the drive specs, followed by a final terminating nul. If not specified
        and not other error occurs, the function succeeds and fills in
        RequiredSize.

    ReturnBufferSize - supplies the size (chars for Unicode, bytes for ANSI)
        of the buffer pointed by ReturnBuffer. Ingored if ReturnBuffer
        is not specified.

    RequiredSize - if specified, receives the size of the buffer required
        to hold the list of drives and terminating nul.

Return Value:

    Boolean value indicating outcome. If the function returns FALSE,
    extended error info is available via GetLastError(). If GetLastError()
    returns ERROR_INSUFFICIENT_BUFFER then ReturnBuffer was specified but
    ReturnBufferSize indicated that the supplied buffer was too small.

--*/

{
    BOOL b;

    b = pSetupQueryDrivesInDiskSpaceList(
            DiskSpace,
            ReturnBuffer,
            ReturnBufferSize,
            RequiredSize
#ifdef UNICODE
           ,FALSE
#endif
            );

    return(b);
}


BOOL
SetupQueryDrivesInDiskSpaceListW(
    IN  HDSKSPC DiskSpace,
    OUT PWSTR   ReturnBuffer,       OPTIONAL
    IN  DWORD   ReturnBufferSize,
    OUT PDWORD  RequiredSize        OPTIONAL
    )

/*++

Routine Description:

    See SetupQueryDrivesInDiskSpaceListA.

Arguments:

    See SetupQueryDrivesInDiskSpaceListA.

Return Value:

    See SetupQueryDrivesInDiskSpaceListA.

--*/

{
    BOOL b;

#ifdef UNICODE
    b = pSetupQueryDrivesInDiskSpaceList(
            DiskSpace,
            ReturnBuffer,
            ReturnBufferSize,
            RequiredSize,
            TRUE
            );
#else
    UNREFERENCED_PARAMETER(DiskSpace);
    UNREFERENCED_PARAMETER(ReturnBuffer);
    UNREFERENCED_PARAMETER(ReturnBufferSize);
    UNREFERENCED_PARAMETER(RequiredSize);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    b = FALSE;
#endif

    return(b);
}


BOOL
pSetupQueryDrivesInDiskSpaceList(
    IN  HDSKSPC DiskSpace,
    OUT PVOID   ReturnBuffer,       OPTIONAL
    IN  DWORD   ReturnBufferSize,
    OUT PDWORD  RequiredSize        OPTIONAL
#ifdef UNICODE
    IN ,BOOL    IsUnicode
#endif
    )

/*++

Routine Description:

    Worker routine for SetupQueryDrivesInDiskSpaceList.

Arguments:

    Same as SetupQueryDrivesInDiskSpaceListA/W.

    IsUnicode - for Unicode DLL, specifies whether buffer args
        are ansi or unicode.

Return Value:

    Same as SetupQueryDrivesInDiskSpaceListA/W.

--*/

{
    PDISK_SPACE_LIST DiskSpaceList;
    DWORD rc;
    BOOL b;
    XDRIVE xDrive;
    RETURN_BUFFER_INFO ReturnBufferInfo;

    //
    // Lock down the DiskSpace handle/structure.
    //
    DiskSpaceList = DiskSpace;
    rc = NO_ERROR;

    try {
        if(!LockIt(DiskSpaceList)) {
            rc = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    if(rc != NO_ERROR) {
        SetLastError(rc);
        return(FALSE);
    }

    try {
        ReturnBufferInfo.ReturnBuffer = ReturnBuffer;
        ReturnBufferInfo.ReturnBufferSize = ReturnBufferSize;
        ReturnBufferInfo.RequiredSize = 0;
        #ifdef UNICODE
        ReturnBufferInfo.IsUnicode = IsUnicode;
        #endif

        MYASSERT(DiskSpaceList->DrivesTable);

        b = pStringTableEnum(
                DiskSpaceList->DrivesTable,
                &xDrive,
                sizeof(XDRIVE),
                pStringTableCBEnumDrives,
                (LPARAM)&ReturnBufferInfo
                );

        if(b) {
            //
            // Need one more char slot for the extra terminating nul.
            //
            ReturnBufferInfo.RequiredSize++;
            if(RequiredSize) {
                *RequiredSize = ReturnBufferInfo.RequiredSize;
            }

            if(ReturnBuffer) {

                if(ReturnBufferInfo.RequiredSize <= ReturnBufferSize) {

                    #ifdef UNICODE
                    if(!IsUnicode) {
                        ((PSTR)ReturnBuffer)[ReturnBufferInfo.RequiredSize-1] = 0;
                    } else
                    #endif
                    ((PTSTR)ReturnBuffer)[ReturnBufferInfo.RequiredSize-1] = 0;

                } else {
                    rc = ERROR_INSUFFICIENT_BUFFER;
                    b = FALSE;
                }
            }
        } else {
            rc = ERROR_INSUFFICIENT_BUFFER;
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_PARAMETER;
        b = FALSE;
    }

    try {
        UnlockIt(DiskSpaceList);
    } except(EXCEPTION_EXECUTE_HANDLER) {
        ;
    }

    SetLastError(rc);
    return(b);
}


BOOL
pAddOrRemoveInstallSection(
    IN HDSKSPC                 DiskSpace,
    IN HINF                    InfHandle,
    IN HINF                    LayoutInfHandle, OPTIONAL
    IN PCTSTR                  SectionName,
    IN BOOL                    Add,
    IN PSP_ALTPLATFORM_INFO_V2 AltPlatformInfo  OPTIONAL
    )

/*++

Routine Description:

Arguments:

Return Value:

--*/

{
    DWORD rc;
    BOOL b;
    unsigned i;
    unsigned numops;
    UINT operation;
    PDISK_SPACE_LIST DiskSpaceList;
    INFCONTEXT LineContext;
    DWORD FieldCount;
    DWORD Field;
    PCTSTR SectionSpec;
    PCTSTR Operations[1] = { TEXT("Copyfiles") };
    INFCONTEXT SectionLineContext;
    TCHAR DefaultTarget[MAX_PATH];

    //
    // Delfiles causes too many issues
    // removed to give a good "worst case" scenario
    // however we intend to add it back along with
    // RenFiles When this issue is revisited, change numops
    // The Operations array and the switch to convert i to operation
    //
    // PCTSTR Operations[2] = { TEXT("Delfiles"),TEXT("Copyfiles") };
    //

    //
    // Lock down the DiskSpace handle/structure.
    //
    DiskSpaceList = DiskSpace;
    rc = NO_ERROR;
    b = TRUE;
    DefaultTarget[0] = 0;

    //
    // only handle Copyfiles at the moment
    //
    numops = 1;

    try {
        if(!LockIt(DiskSpaceList)) {
            rc = ERROR_INVALID_HANDLE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_HANDLE;
    }

    if(rc != NO_ERROR) {
        b = FALSE;
        goto c0;
    }
    if(!LayoutInfHandle) {
        LayoutInfHandle = InfHandle;
    }

    //
    // see if install section exists for diagnostics (this will also check InfHandle)
    // however proceed so that we don't break existing broken code :-(
    //
    if (!SetupFindFirstLine(InfHandle,SectionName,NULL,&LineContext)) {
        DWORD x;
        x = GetLastError();
        pSetupLogSectionError(InfHandle,NULL,NULL,NULL,SectionName,MSG_LOG_NOSECTION_SPACE,x,NULL);
    }

    b = TRUE;
    for(i=0; b && (i < numops); i++) {

        //
        // Find the relevent line in the given install section.
        // If not present then we're done with this operation.
        //
        if(!SetupFindFirstLine(InfHandle,SectionName,Operations[i],&LineContext)) {
            continue;
        }

        switch(i) {
        case 0:
            operation = FILEOP_COPY;
            break;
        default:
            //
            // if we get here, someone changed numops
            // without changing this switch
            //
            MYASSERT(FALSE);
            break;
        }


        do {
            //
            // Each value on the line in the given install section
            // is the name of another section.
            //
            FieldCount = SetupGetFieldCount(&LineContext);
            for(Field=1; b && (Field<=FieldCount); Field++) {

                if(SectionSpec = pSetupGetField(&LineContext,Field)) {

                    //
                    // Handle single-file copy specially.
                    //
                    if((operation == FILEOP_COPY) && (*SectionSpec == TEXT('@'))) {

                        if(!DefaultTarget[0]) {
                            //
                            // Fetch the default target path for this inf, for use with
                            // single-file copy specs.
                            //
                            b = SetupGetTargetPath(
                                    InfHandle,
                                    NULL,
                                    NULL,
                                    DefaultTarget,
                                    MAX_PATH,
                                    NULL
                                    );
                        }

                        if(b) {
                            b = pAddOrRemoveFileFromSectionToDiskSpaceList(
                                    DiskSpace,
                                    LayoutInfHandle,
                                    NULL,
                                    SectionSpec+1,
                                    DefaultTarget,
                                    operation,
                                    Add,
                                    AltPlatformInfo
                                    );
                        }

                        if(!b) {
                            rc = GetLastError();
                        }
                    } else if(SetupGetLineCount(InfHandle,SectionSpec) > 0) {
                        //
                        // The section exists and is not empty.
                        // Add/remove it to the space list.
                        //
                        if(Add) {
                            b = _SetupAddSectionToDiskSpaceList(
                                    DiskSpace,
                                    LayoutInfHandle,
                                    InfHandle,
                                    SectionSpec,
                                    operation,
                                    0,0,
                                    AltPlatformInfo
                                    );
                        } else {
                            b = _SetupRemoveSectionFromDiskSpaceList(
                                    DiskSpace,
                                    LayoutInfHandle,
                                    InfHandle,
                                    SectionSpec,
                                    operation,
                                    0,0,
                                    AltPlatformInfo
                                    );
                        }

                        if(!b) {
                            rc = GetLastError();
                        }
                    }
                }
            }
        } while(b && SetupFindNextMatchLine(&LineContext,Operations[i],&LineContext));
    }

    try {
        UnlockIt(DiskSpaceList);
    } except(EXCEPTION_EXECUTE_HANDLER) {
        ;
    }
c0:
    SetLastError(rc);
    return(b);
}


BOOL
pAddOrRemoveFileFromSectionToDiskSpaceList(
    IN OUT PDISK_SPACE_LIST        DiskSpaceList,
    IN     HINF                    LayoutInf,
    IN     PINFCONTEXT             LineInSection,   OPTIONAL
    IN     PCTSTR                  FileName,        OPTIONAL
    IN     PCTSTR                  TargetDirectory,
    IN     UINT                    Operation,
    IN     BOOL                    Add,
    IN     PSP_ALTPLATFORM_INFO_V2 AltPlatformInfo  OPTIONAL
    )
{
    PCTSTR TargetFilename;
    TCHAR FullTargetPath[MAX_PATH];
    DWORD FileSize;
    BOOL b;
    DWORD rc;

    //
    // Get the target filename out of the line.
    // Field 1 is the target so there must be one for the line to be valid.
    //
    if(TargetFilename = LineInSection ? pSetupFilenameFromLine(LineInSection,FALSE) : FileName) {

        //
        // Form the full target path by concatenating the target dir
        // for this section and the target filename.
        //
        lstrcpyn(FullTargetPath,TargetDirectory,MAX_PATH);
        pSetupConcatenatePaths(FullTargetPath,TargetFilename,MAX_PATH,NULL);

        if(Add) {
            //
            // Fetch the size of the target file and add the operation
            // to the disk space list.
            //
            if(_SetupGetSourceFileSize(LayoutInf,
                                       LineInSection,
                                       FileName,
                                       NULL,
                                       AltPlatformInfo,
                                       &FileSize,
                                       0)) {

                b = pSetupAddToDiskSpaceList(
                        DiskSpaceList,
                        FullTargetPath,
                        (LONGLONG)(LONG)FileSize,
                        Operation
                        );

                if(!b) {
                    rc = GetLastError();
                }
            } else {
                b = FALSE;
                rc = GetLastError();
            }
        } else {
            //
            // Remove the operation from the disk space list.
            //
            b = pSetupRemoveFromDiskSpaceList(
                    DiskSpaceList,
                    FullTargetPath,
                    Operation
                    );

            if (!b) {
                rc = GetLastError();
            }
        }
    } else {
        b = FALSE;
        rc = ERROR_INVALID_DATA;
    }

    SetLastError(rc);
    return(b);
}


BOOL
pSetupAddToDiskSpaceList(
    IN  PDISK_SPACE_LIST DiskSpaceList,
    IN  PCTSTR           TargetFilespec,
    IN  LONGLONG         FileSize,
    IN  UINT             Operation
    )

/*++

Routine Description:

    Worker routine to add an item to a disk space list.
    Assumes locking is done by the caller.

Arguments:

    DiskSpaceList - specifies pointer to disk space list structure
        created by SetupCreateDiskSpaceList().

    TargetFilespec - specifies filename of the file to add
        to the disk space list. This will generally be a full win32
        path, though this is not a requirement. If it is not then
        standard win32 path semantics apply.

    FileSize - supplies the (uncompressed) size of the file as it will
        exist on the target when copied. Ignored for FILEOP_DELETE.

    Operation - one of FILEOP_DELETE or FILEOP_COPY.

Return Value:

    Boolean value indicating outcome. If FALSE, GetLastError() returns
    extended error information.

--*/

{
    TCHAR Buffer[MAX_PATH];
    DWORD rc;
    BOOL b;
    PTSTR DirPart;
    PTSTR FilePart;
    PTSTR drivespec;
    TCHAR drivelet[4];
    LONGLONG ExistingFileSize;
    XDRIVE xDrive;
    XDIRECTORY xDir;
    XFILE xFile;
    DWORD StringLength;
    DWORD Hash;
    LONG l;
    DWORD SectorsPerCluster;
    DWORD BytesPerSector;
    DWORD TotalClusters;
    DWORD FreeClusters;

    if((Operation != FILEOP_DELETE) && (Operation != FILEOP_COPY) && (Operation != (UINT)(-1))) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    rc = NO_ERROR;

    try {
        rc = pParsePath(
                TargetFilespec,
                Buffer,
                &DirPart,
                &FilePart,
                &ExistingFileSize,
                DiskSpaceList->Flags
                );

        if(rc != NO_ERROR) {
            goto c0;
        }

        //
        // If we're not just doing the adjust case, drivespecs are not
        // acceptable.
        //
        if((Operation != (UINT)(-1)) && (*FilePart == 0)) {
            rc = ERROR_INVALID_PARAMETER;
            goto c0;
        }

        //
        // See whether the drive is already present in the drive list.
        //

        MYASSERT(DiskSpaceList->DrivesTable);

        l = pStringTableLookUpString(
                DiskSpaceList->DrivesTable,
                Buffer,
                &StringLength,
                &Hash,
                NULL,
                STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                &xDrive,
                sizeof(XDRIVE)
                );

        if(l == -1) {
            //
            // Determine cluster size for the drive and then add the drive
            // to the drive list and create a string table for the
            // directory list for this drive.
            //
            if(xDrive.DirsTable = pStringTableInitialize(sizeof(XDIRECTORY))) {
                //
                // The API is a little picky about what it is passed.
                // For the local drive case we have to use x:\ but pParsePath
                // sets things up so it's x:.
                //
                if(Buffer[1] == TEXT(':')) {
                    drivelet[0] = Buffer[0];
                    drivelet[1] = Buffer[1];
                    drivelet[2] = TEXT('\\');
                    drivelet[3] = 0;
                    drivespec = drivelet;
                } else {
                    drivespec = Buffer;
                }

                b = GetDiskFreeSpace(
                        drivespec,
                        &SectorsPerCluster,
                        &BytesPerSector,
                        &FreeClusters,
                        &TotalClusters
                        );

                if(!b) {
                    //
                    // This should probably be an error but there could be
                    // cases where people want to queue files say to a UNC path
                    // that isn't accessible now or something. Use reasonable defaults.
                    //
                    SectorsPerCluster = 1;
                    BytesPerSector = 512;
                    FreeClusters = 0;
                    TotalClusters = 0;
                }

                xDrive.SpaceRequired = 0;
                xDrive.Slop = 0;
                xDrive.BytesPerCluster = SectorsPerCluster * BytesPerSector;

                l = pStringTableAddString(
                        DiskSpaceList->DrivesTable,
                        Buffer,
                        STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                        &xDrive,
                        sizeof(XDRIVE)
                        );

                if(l == -1) {
                    pStringTableDestroy(xDrive.DirsTable);
                }
            }
        }

        if(l == -1) {
            //
            // Assume OOM.
            //
            rc = ERROR_NOT_ENOUGH_MEMORY;
            goto c0;
        }

        if(Operation == (UINT)(-1)) {
            //
            // Only want to add the drive. Adjust the slop for the drive.
            // rc is already set to NO_ERROR.
            //
            xDrive.Slop += FileSize;
            if((DiskSpaceList->Flags & SPDSL_DISALLOW_NEGATIVE_ADJUST) && (xDrive.Slop < 0)) {
                xDrive.Slop = 0;
            }

            pStringTableSetExtraData(
                DiskSpaceList->DrivesTable,
                l,
                &xDrive,
                sizeof(XDRIVE)
                );

            goto c0;
        }

        //
        // Adjust sizes to account for cluster size.
        //
        FileSize = _AdjustSpace(FileSize,xDrive.BytesPerCluster);
        if(ExistingFileSize != -1) {
            ExistingFileSize = _AdjustSpace(ExistingFileSize,xDrive.BytesPerCluster);
        }

        //
        // OK, xDrive has the drive info relevent for this file.
        // Now handle the directory part. First see whether the directory
        // is already present in the drive list.
        //
        l = pStringTableLookUpString(
                xDrive.DirsTable,
                DirPart,
                &StringLength,
                &Hash,
                NULL,
                STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                &xDir,
                sizeof(XDIRECTORY)
                );

        if(l == -1) {
            //
            // Add the directory to the directory string table.
            //
            if(xDir.FilesTable = pStringTableInitialize(sizeof(XFILE))) {

                xDir.SpaceRequired = 0;

                l = pStringTableAddString(
                        xDrive.DirsTable,
                        DirPart,
                        STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                        &xDir,
                        sizeof(XDIRECTORY)
                        );

                if(l == -1) {
                    pStringTableDestroy(xDir.FilesTable);
                }
            }
        }

        if(l == -1) {
            //
            // Assume OOM.
            //
            rc = ERROR_NOT_ENOUGH_MEMORY;
            goto c0;
        }

        //
        // Finally, deal with the file itself.
        // First see if it's in the list already.
        //
        l = pStringTableLookUpString(
                xDir.FilesTable,
                FilePart,
                &StringLength,
                &Hash,
                NULL,
                STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                &xFile,
                sizeof(XFILE)
                );

        if(l == -1) {
            //
            // The file is not already in there so put it in.
            //
            xFile.CurrentSize = ExistingFileSize;
            xFile.NewSize = (Operation == FILEOP_DELETE) ? -1 : FileSize;

            l = pStringTableAddString(
                    xDir.FilesTable,
                    FilePart,
                    STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                    &xFile,
                    sizeof(XFILE)
                    );

            if(l == -1) {
                rc = ERROR_NOT_ENOUGH_MEMORY;
                goto c0;
            }

        } else {

            if((xFile.CurrentSize == -1) && (xFile.NewSize == -1)) {
                //
                // This is a special "no-op" coding.
                //
                // The file is in there, but either the file was previously added
                // for a delete op but it didn't exist on the disk, or it was removed
                // via SetupRemoveFromDiskSpaceList().
                //
                xFile.CurrentSize = ExistingFileSize;
                xFile.NewSize = (Operation == FILEOP_DELETE) ? -1 : FileSize;

            } else {

                //
                // File is already in there. Remembering that deletes are done
                // before copies when a file queue is committed and assuming
                // that operations are put on the disk list in the same order they
                // will eventually be done on the file queue, there are 4 cases:
                //
                // 1) On list as delete, caller wants to delete. Just refresh
                //    the existing file size in case it changed.
                //
                // 2) On list as delete, caller wants to copy. We treat this case
                //    as a copy and override the existing info on the disk space list.
                //
                // 3) On list as copy, caller wants to delete. At commit time the file
                //    will be deleted but then later copied; just refresh the existing
                //    file size, in case it changed.
                //
                // 4) On list as copy, caller wants to copy. Override existing
                //    info in this case.
                //
                // This actually boils down to the following: Always refresh the
                // existing file size, and if the caller wants a copy, then
                // remember the new size.
                //
                xFile.CurrentSize = ExistingFileSize;
                if(Operation == FILEOP_COPY) {

                    xFile.NewSize = FileSize;
                }
            }

            pStringTableSetExtraData(xDir.FilesTable,l,&xFile,sizeof(XFILE));
        }

        c0:

        ;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_PARAMETER;
    }

    SetLastError(rc);
    return(rc == NO_ERROR);
}


BOOL
pSetupRemoveFromDiskSpaceList(
    IN  PDISK_SPACE_LIST DiskSpace,
    IN  PCTSTR           TargetFilespec,
    IN  UINT             Operation
    )

/*++

Routine Description:

    Worker routine to remove a single delete or copy operation from a
    disk space list.

    Assumes locking is handled by the caller.

Arguments:

    DiskSpaceList - specifies pointer to disk space list structure created by
        SetupCreateDiskSpaceList().

    TargetFilespec - specifies filename of the file to remove from
        the disk space list. This will generally be a full win32
        path, though this is not a requirement. If it is not then
        standard win32 path semantics apply.

    Operation - one of FILEOP_DELETE or FILEOP_COPY.

Return Value:

    If the file was not in the list, the routine returns TRUE and
    GetLastError() returns ERROR_INVALID_DRIVE or ERROR_INVALID_NAME.
    If the file was in the list then upon success the routine returns
    TRUE and GetLastError() returns NO_ERROR.

    If the routine fails for some other reason it returns FALSE and GetLastError()
    can be used to fetch extended error info.

--*/

{
    DWORD rc;
    BOOL b;
    TCHAR Buffer[MAX_PATH];
    PTSTR DirPart;
    PTSTR FilePart;
    LONGLONG ExistingFileSize;
    LONG l;
    DWORD StringLength;
    DWORD Hash;
    XDRIVE xDrive;
    XDIRECTORY xDir;
    XFILE xFile;

    if((Operation != FILEOP_DELETE) && (Operation != FILEOP_COPY)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    rc = NO_ERROR;
    b = TRUE;

    try {
        //
        // Split up the path into its constituent components.
        //
        rc = pParsePath(
                TargetFilespec,
                Buffer,
                &DirPart,
                &FilePart,
                &ExistingFileSize,
                DiskSpace->Flags
                );

        if(rc != NO_ERROR) {
            goto c0;
        }

        //
        // Drivespecs alone are not acceptable.
        //
        if(*FilePart == 0) {
            rc = ERROR_INVALID_PARAMETER;
            goto c0;
        }

        //
        // Follow the trail down to the file string table.
        //

        MYASSERT(DiskSpace->DrivesTable);

        l = pStringTableLookUpString(
                DiskSpace->DrivesTable,
                Buffer,
                &StringLength,
                &Hash,
                NULL,
                STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                &xDrive,
                sizeof(XDRIVE)
                );

        if(l == -1) {
            //
            // Return success but set last error to indicate condition.
            //
            rc = ERROR_INVALID_DRIVE;
            goto c0;
        }

        MYASSERT(xDrive.DirsTable);

        l = pStringTableLookUpString(
                xDrive.DirsTable,
                DirPart,
                &StringLength,
                &Hash,
                NULL,
                STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                &xDir,
                sizeof(XDIRECTORY)
                );

        if(l == -1) {
            //
            // Return success but set last error to indicate condition.
            //
            rc = ERROR_INVALID_NAME;
            goto c0;
        }

        MYASSERT(xDir.FilesTable);

        l = pStringTableLookUpString(
                xDir.FilesTable,
                FilePart,
                &StringLength,
                &Hash,
                NULL,
                STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE,
                &xFile,
                sizeof(XFILE)
                );

        if(l == -1) {
            //
            // Return success but set last error to indicate condition.
            //
            rc = ERROR_INVALID_NAME;
            goto c0;
        }

        //
        // Set special 'no-op' code for this file if the operations match.
        //
        if(Operation == FILEOP_DELETE) {
            if(xFile.NewSize == -1) {
                xFile.CurrentSize = -1;
            }
        } else {
            if(xFile.NewSize != -1) {
                xFile.NewSize = -1;
                xFile.CurrentSize = -1;
            }
        }

        pStringTableSetExtraData(xDir.FilesTable,l,&xFile,sizeof(XFILE));

        c0:

        ;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        rc = ERROR_INVALID_PARAMETER;
        b = FALSE;
    }

    SetLastError(rc);
    return(b);
}


BOOL
pStringTableCBEnumDrives(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    )

/*++

Routine Description:

    Internal routine used as the callback when enumerating drives
    in the disk space list. Writes the drivespec into a buffer
    supplies to the enumeration routine.

Arguments:

Return Value:

--*/

{
    PRETURN_BUFFER_INFO p;
    UINT Length;
    BOOL b;
    PCVOID string;

    UNREFERENCED_PARAMETER(StringTable);
    UNREFERENCED_PARAMETER(StringId);
    UNREFERENCED_PARAMETER(ExtraData);
    UNREFERENCED_PARAMETER(ExtraDataSize);

    p = (PRETURN_BUFFER_INFO)lParam;

#ifdef UNICODE
    if(!p->IsUnicode) {
        if(string = pSetupUnicodeToAnsi(String)) {
            Length = lstrlenA(string) + 1;
        } else {
            return(FALSE);
        }
    } else
#endif
    {
        string = String;
        Length = lstrlen(string) + 1;
    }

    p->RequiredSize += Length;

    if(p->ReturnBuffer) {

        if(p->RequiredSize <= p->ReturnBufferSize) {

            //
            // There's still room in the caller's buffer for this drive spec.
            //
#ifdef UNICODE
            if(!p->IsUnicode) {
                lstrcpyA((PSTR)p->ReturnBuffer+p->RequiredSize-Length,string);
            } else
#endif
            lstrcpy((PTSTR)p->ReturnBuffer+p->RequiredSize-Length,string);

            b = TRUE;

        } else {
            //
            // Buffer is too small. Abort the enumeration.
            //
            b = FALSE;
        }
    } else {
        //
        // No buffer: just update the length required.
        //
        b = TRUE;
    }

#ifdef UNICODE
    if(string != String) {
        MyFree(string);
    }
#endif
    return(b);
}


BOOL
pStringTableCBDelDrives(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    )

/*++

Routine Description:

    Internal routine used as the callback when calling pStringTableEnum
    to determine which drives are part of a disk space list.
    Enumerates directories on the drive, and then deletes the drives
    string table.

Arguments:

Return Value:

--*/

{
    PXDRIVE xDrive;
    XDIRECTORY xDir;
    BOOL b;

    UNREFERENCED_PARAMETER(StringTable);
    UNREFERENCED_PARAMETER(StringId);
    UNREFERENCED_PARAMETER(String);
    UNREFERENCED_PARAMETER(ExtraDataSize);
    UNREFERENCED_PARAMETER(lParam);

    //
    // The extra data for the drives table is an XDRIVE structure.
    //
    xDrive = ExtraData;

    //
    // Enumerate the directory table for this drive. This destroys
    // all of *those* string tables.
    //
    if(xDrive->DirsTable) {
        b = pStringTableEnum(
                xDrive->DirsTable,
                &xDir,
                sizeof(XDIRECTORY),
                pStringTableCBDelDirs,
                0
                );

        pStringTableDestroy(xDrive->DirsTable);
    } else {
        b = FALSE;
    }

    return(b);
}


BOOL
pStringTableCBDelDirs(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    )

/*++

Routine Description:

    Internal routine used as the callback when calling pStringTableEnum
    to determine which directories on a given drive are part of a
    disk space list. Basically we just destroy the directory's file string table.

Arguments:

Return Value:

--*/

{
    PXDIRECTORY xDir;

    UNREFERENCED_PARAMETER(StringTable);
    UNREFERENCED_PARAMETER(StringId);
    UNREFERENCED_PARAMETER(String);
    UNREFERENCED_PARAMETER(ExtraDataSize);
    UNREFERENCED_PARAMETER(lParam);

    //
    // The extra data for the dirs table is an XDIRECTORY structure.
    //
    xDir = ExtraData;

    if(xDir->FilesTable) {
        pStringTableDestroy(xDir->FilesTable);
    }

    return(TRUE);
}


DWORD
pParsePath(
    IN  PCTSTR    PathSpec,
    OUT PTSTR     Buffer,
    OUT PTSTR    *DirectoryPart,
    OUT PTSTR    *FilePart,
    OUT LONGLONG *FileSize,
    IN  UINT      Flags
    )

/*++

Routine Description:

    Given a (possibly relative or incomplete) pathspec, determine
    the drive part, the directory part, and the filename parts and
    return pointers thereto.

Arguments:

    PathSpec - supplies the (possible relative) filename.

    Buffer - must be MAX_PATH TCHAR elements. Receives the full win32
        path, which is then carved up into drive, dir, and file parts.
        When the function returns, the first part of Buffer is the
        0-terminated drive spec, not including a terminating \ char.

    DirectoryPart - receives a pointer within Buffer to the first char
        in the full path (which will not be \). The string starting
        with that char will be nul-terminated.

    FilePart - receives a pointer within Buffer to the nul-terminated
        filename part (ie, the final component) of the win32 path
        (no path sep chars are involved in that part of the path).

    FileSize - receives the size of the file if it exists or -1 if not.

    Flags - specifies flags.
        SPDSL_IGNORE_DISK: this forces the routine to behave as if the file
            does not exist on-disk.

Return Value:

    Win32 error code indicating outcome.

--*/

{
    DWORD rc;
    WIN32_FIND_DATA FindData;
    LPTSTR p;

    rc = GetFullPathName(PathSpec,
                         MAX_PATH,
                         Buffer,
                         FilePart
                        );

    if(!rc) {
        return(GetLastError());
    } else if(rc >= MAX_PATH) {
        MYASSERT(0);
        return(ERROR_BUFFER_OVERFLOW);
    }

    //
    // Get the file size, if the file exists.
    //
    if(Flags & SPDSL_IGNORE_DISK) {
        *FileSize = -1;
    } else {
        *FileSize = FileExists(Buffer,&FindData)
                  ? ((LONGLONG)FindData.nFileSizeHigh << 32) | FindData.nFileSizeLow
                  : -1;
    }

    //
    // Figure the drive part. We have no choice but to assume that
    // full paths are either x:\... or \\server\share\... because
    // there isn't any solid way to ask win32 itself what the drive
    // part of the path is.
    //
    // Stick a nul-terminator into the buffer to set off the drive part
    // once we've found it. Note that drive roots are acceptable in
    // the following forms:
    //
    //      x:
    //      x:\
    //      \\server\share
    //      \\server\share\
    //
    if(Buffer[0] && (Buffer[1] == TEXT(':'))) {
        if(Buffer[2] == 0) {
            p = &Buffer[2];
        } else {
            if(Buffer[2] == TEXT('\\')) {
                Buffer[2] = 0;
                p = &Buffer[3];
            } else {
                return(ERROR_INVALID_DRIVE);
            }
        }
    } else {
        if((Buffer[0] == TEXT('\\')) && (Buffer[1] == TEXT('\\')) && Buffer[2]
        && (p = _tcschr(&Buffer[3],TEXT('\\'))) && *(p+1) && (*(p+1) != TEXT('\\'))) {
            //
            // Dir part starts at next \, or it could be a drive root.
            //
            if(p = _tcschr(p+2,TEXT('\\'))) {
                *p++ = 0;
            } else {
                p = _tcschr(p+2,0);
            }
        } else {
            return(ERROR_INVALID_DRIVE);
        }
    }

    //
    // If we have a drive root, we're done. Set the dir and file parts
    // to point at an empty string and return.
    //
    if(*p == 0) {
        *DirectoryPart = p;
        *FilePart = p;
        return(NO_ERROR);
    }

    if(_tcschr(p,TEXT('\\'))) {
        //
        // There are at least 2 path components, so we have
        // a directory and filename. We need to nul-terminate
        // the directory part.
        //
        *DirectoryPart = p;
        *(*FilePart - 1) = 0;
    } else {
        //
        // There's only the one path component, so we have a file
        // at the root of the drive. FilePart is already set from
        // the call to GetFullPathName above. Set DirectoryPart
        // to a nul-terminator to make it an empty string.
        //
        *DirectoryPart = Buffer+lstrlen(Buffer);
    }

    return(NO_ERROR);
}


BOOL
pStringTableCBRecalcFiles(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    )

/*++

Routine Description:

Arguments:

Return Value:

--*/

{
    PXFILE xFile;
    LONGLONG Delta;

    UNREFERENCED_PARAMETER(StringTable);
    UNREFERENCED_PARAMETER(StringId);
    UNREFERENCED_PARAMETER(String);
    UNREFERENCED_PARAMETER(ExtraDataSize);
    UNREFERENCED_PARAMETER(lParam);

    //
    // Extra data points to an XFILE.
    //
    xFile = ExtraData;

    //
    // Calculate the additional space the new file will require
    // or the space that will be freed after the file is copied/deleted.
    //
    if(xFile->NewSize == -1) {
        //
        // File is being deleted. Account for the special 'no-op' coding.
        //
        Delta = (xFile->CurrentSize == -1) ? 0 : (0 - xFile->CurrentSize);

    } else {
        //
        // File is being copied. Account for the fact that the file might not
        // already exist on the disk.
        //
        Delta = (xFile->CurrentSize == -1) ? xFile->NewSize : (xFile->NewSize - xFile->CurrentSize);
    }

    //
    // Update running accumulated total.
    //
    *(LONGLONG *)lParam += Delta;

    return(TRUE);
}


BOOL
pStringTableCBRecalcDirs(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    )

/*++

Routine Description:

Arguments:

Return Value:

--*/

{
    PXDIRECTORY xDir;
    XFILE xFile;

    UNREFERENCED_PARAMETER(StringTable);
    UNREFERENCED_PARAMETER(StringId);
    UNREFERENCED_PARAMETER(String);
    UNREFERENCED_PARAMETER(ExtraDataSize);
    UNREFERENCED_PARAMETER(lParam);

    //
    // Extra data points to an XDIRECTORY.
    //
    xDir = ExtraData;

    xDir->SpaceRequired = 0;

    pStringTableEnum(
        xDir->FilesTable,
        &xFile,
        sizeof(XFILE),
        pStringTableCBRecalcFiles,
        (LPARAM)&xDir->SpaceRequired
        );

    //
    // Update running accumulated total.
    //
    *(LONGLONG *)lParam += xDir->SpaceRequired;

    return(TRUE);
}


BOOL
pStringTableCBZeroDirsTableMember(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    )

/*++

Routine Description:

Arguments:

    Standard string table callback arguments.

Return Value:

--*/

{
    UNREFERENCED_PARAMETER(String);
    UNREFERENCED_PARAMETER(lParam);

    if(lParam) {
        ((PXDIRECTORY)ExtraData)->FilesTable = NULL;
    } else {
        ((PXDRIVE)ExtraData)->DirsTable = NULL;
    }

    MYASSERT(StringTable);

    pStringTableSetExtraData(StringTable,StringId,ExtraData,ExtraDataSize);
    return(TRUE);
}


BOOL
pStringTableCBDupMemberStringTable2(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    )

/*++

Routine Description:

Arguments:

    Standard string table callback arguments.

Return Value:

--*/

{
    PXDIRECTORY xDir;
    BOOL b;

    UNREFERENCED_PARAMETER(StringTable);
    UNREFERENCED_PARAMETER(String);

    //
    // Extra data is the XDIRECTORY structure in the old string table.
    //
    xDir = ExtraData;

    //
    // Duplicate the old FilesTable string table into the new table.
    // We can reuse the xDir buffer.
    //
    xDir->FilesTable = pStringTableDuplicate(xDir->FilesTable);
    if(!xDir->FilesTable) {
        return(FALSE);
    }

    pStringTableSetExtraData((PVOID)lParam,StringId,ExtraData,ExtraDataSize);
    return(TRUE);
}


BOOL
pStringTableCBDupMemberStringTable(
    IN PVOID  StringTable,
    IN LONG   StringId,
    IN PCTSTR String,
    IN PVOID  ExtraData,
    IN UINT   ExtraDataSize,
    IN LPARAM lParam
    )

/*++

Routine Description:

Arguments:

    Standard string table callback arguments.

Return Value:

--*/

{
    PXDRIVE xDrive;
    XDIRECTORY xDir;
    BOOL b;
    PVOID OldTable;

    UNREFERENCED_PARAMETER(StringTable);
    UNREFERENCED_PARAMETER(String);

    //
    // Extra data is the XDRIVE structure in the old string table.
    //
    xDrive = ExtraData;

    //
    // Duplicate the old DirsTable string table into the new table.
    // We can reuse the xDrive buffer.
    //
    OldTable = xDrive->DirsTable;
    xDrive->DirsTable = pStringTableDuplicate(xDrive->DirsTable);
    if(!xDrive->DirsTable) {
        return(FALSE);
    }

    pStringTableSetExtraData((PVOID)lParam,StringId,ExtraData,ExtraDataSize);

    //
    // Now zero out the FilesTable members of the XDIRECTORY extra data
    // items in DirsTable string table.
    //
    pStringTableEnum(
        xDrive->DirsTable,
        &xDir,
        sizeof(XDIRECTORY),
        pStringTableCBZeroDirsTableMember,
        1
        );

    //
    // Finally, take advantage of the fact that the ids in the table we just
    // duplicated are the same in the old and new tables, to iterate the
    // old table to duplicate its FilesTable string tables into the new
    // string table. Clean up if failure.
    //
    b = pStringTableEnum(
            OldTable,
            &xDir,
            sizeof(XDIRECTORY),
            pStringTableCBDupMemberStringTable2,
            (LPARAM)xDrive->DirsTable
            );

    if(!b) {
        //
        // Clean up.
        //
        pStringTableEnum(
            xDrive->DirsTable,
            &xDir,
            sizeof(XDIRECTORY),
            pStringTableCBDelDirs,
            0
            );
    }

    return(b);
}


VOID
pRecalcSpace(
    IN OUT PDISK_SPACE_LIST DiskSpaceList,
    IN     LONG             DriveStringId
    )

/*++

Routine Description:

    Recalcuates the disk space required for a given drive by
    traversing all the dirs and files that are on the space list
    for the drive and performing additions/subtractions as necessary.

    Assumes locking is handled by the caller and does not guard args.

Arguments:

    DiskSpaceList - supplies the disk space list structure created
        by SetupCreateDiskSpaceList().

    DriveStringId - supplies the string id for the drive
        (in DiskSpaceList->DrivesTable) for the drive to be updated.

Return Value:

    None.

--*/

{
    XDRIVE xDrive;
    XDIRECTORY xDir;

    if(DriveStringId == -1) {
        return;
    }

    MYASSERT(DiskSpaceList->DrivesTable);

    pStringTableGetExtraData(DiskSpaceList->DrivesTable,DriveStringId,&xDrive,sizeof(XDRIVE));

    xDrive.SpaceRequired = 0;

    pStringTableEnum(
        xDrive.DirsTable,
        &xDir,
        sizeof(XDIRECTORY),
        pStringTableCBRecalcDirs,
        (LPARAM)&xDrive.SpaceRequired
        );

    pStringTableSetExtraData(DiskSpaceList->DrivesTable,DriveStringId,&xDrive,sizeof(XDRIVE));
}
