/*  demerror.c - Error handling routines of DEM
 *
 *  demSetHardErrorInfo
 *  demClientError
 *  demRetry
 *
 *  Modification History:
 *
 *  Sudeepb 27-Nov-1991 Created
 */

#include "dem.h"
#include "demmsg.h"

#include <softpc.h>

PVHE    pHardErrPacket;
PSYSDEV pDeviceChain;
SAVEDEMWORLD RetryInfo;

CHAR GetDriveLetterByHandle(HANDLE hFile);
VOID SubstituteDeviceName( PUNICODE_STRING InputDeviceName,
                           LPSTR OutputDriveLetter);

/* demSetHardErrorInfo - Store away harderr related address of DOSKRNL
 *
 * Entry
 *	Client (DS:DX) - VHE structure
 *
 * Exit
 *	None
 */

VOID demSetHardErrorInfo (VOID)
{
    pHardErrPacket = (PVHE) GetVDMAddr (getDS(),getDX());
    pDeviceChain = (PSYSDEV) GetVDMAddr(getDS(),getBX());
    return;
}

/* demRetry - Retry the operation which last resulted in hard error
 *
 * Entry
 *	None
 *
 * Exit
 *	None
 */

VOID demRetry (VOID)
{
ULONG iSvc;

    demRestoreHardErrInfo ();
    iSvc = CurrentISVC;

#if DBG
    if(iSvc < SVC_DEMLASTSVC && (fShowSVCMsg & DEMSVCTRACE) &&
	 apfnSVC[iSvc] != demNotYetImplemented){
	sprintf(demDebugBuffer,"demRetry:Retrying %s\n\tAX=%.4x BX=%.4x CX=%.4x DX=%.4x DI=%.4x SI=%.4x\n",
	       aSVCNames[iSvc],getAX(),getBX(),getCX(),getDX(),getDI(),getSI());
        OutputDebugStringOem(demDebugBuffer);
	sprintf(demDebugBuffer,"\tCS=%.4x IP=%.4x DS=%.4x ES=%.4x SS=%.4x SP=%.4x BP=%.4x\n",
                getCS(),getIP(), getDS(),getES(),getSS(),getSP(),getBP());
        OutputDebugStringOem(demDebugBuffer);
    }

    if (iSvc >= SVC_DEMLASTSVC || apfnSVC[iSvc] == demNotYetImplemented ){
        ASSERT(FALSE);
        setCF(1);
	setAX(0xff);
        return;
    }
#endif // DBG

    (apfnSVC [iSvc])();

#if DBG
    if((fShowSVCMsg & DEMSVCTRACE)){
	sprintf(demDebugBuffer,"demRetry:After %s\n\tAX=%.4x BX=%.4x CX=%.4x DX=%.4x DI=%.4x SI=%.4x\n",
               aSVCNames[iSvc],getAX(),getBX(),getCX(),getDX(),getDI(),getSI());
        OutputDebugStringOem(demDebugBuffer);
	sprintf(demDebugBuffer,"\tCS=%.4x IP=%.4x DS=%.4x ES=%.4x SS=%.4x SP=%.4x BP=%.4x CF=%x\n",
               getCS(),getIP(), getDS(),getES(),getSS(),getSP(),getBP(),getCF());
        OutputDebugStringOem(demDebugBuffer);
    }
#endif
    return;
}

/* demClientError - Update client registers to signal error
 *
 * Entry
 *       HANDLE hFile; file handle  , if none == -1
 *       char chDrive; drive letter , if none == -1
 *
 * Exit
 *	Client (CF) = 1
 *	Client (AX) = Error Code
 *
 * Notes
 *      the following errors cause hard errors
 *      errors above ERROR_GEN_FAILURE are mapped to general fail by the DOS
 *
 *
 *	ERROR_WRITE_PROTECT		 19L
 *	ERROR_BAD_UNIT			 20L
 *	ERROR_NOT_READY			 21L
 *	ERROR_BAD_COMMAND		 22L
 *	ERROR_CRC			 23L
 *	ERROR_BAD_LENGTH		 24L
 *	ERROR_SEEK			 25L
 *	ERROR_NOT_DOS_DISK		 26L
 *	ERROR_SECTOR_NOT_FOUND		 27L
 *	ERROR_OUT_OF_PAPER		 28L
 *	ERROR_WRITE_FAULT		 29L
 *	ERROR_READ_FAULT		 30L
 *      ERROR_GEN_FAILURE                31L
 *      ERROR_WRONG_DISK                 34l
 *      ERROR_NO_MEDIA_IN_DRIVE        1112l
 *
 */

VOID demClientError (HANDLE hFile, CHAR chDrive)
{
    demClientErrorEx (hFile, chDrive, TRUE);
}

ULONG demClientErrorEx (HANDLE hFile, CHAR chDrive, BOOL bSetRegs)
{
ULONG ulErrCode;

    if(!(ulErrCode = GetLastError()))
	ulErrCode = ERROR_ACCESS_DENIED;

    if ((ulErrCode < ERROR_WRITE_PROTECT || ulErrCode > ERROR_GEN_FAILURE)
        && ulErrCode != ERROR_WRONG_DISK )
       {
#if DBG
       if (fShowSVCMsg & DEMERROR) {
           sprintf(demDebugBuffer,"demClientErr: ErrCode=%ld\n", ulErrCode);
           OutputDebugStringOem(demDebugBuffer);
           }
#endif
        if (bSetRegs) {
            setAX((USHORT)ulErrCode);
            }
        }
    else {   // handle hard error case
        if (ulErrCode > ERROR_GEN_FAILURE)
            ulErrCode = ERROR_GEN_FAILURE;

        // Set the hard error flag
        pHardErrPacket->vhe_fbInt24 = 1;

        // Get the drive letter
        if (hFile != INVALID_HANDLE_VALUE)
            chDrive = GetDriveLetterByHandle(hFile);

        pHardErrPacket->vhe_bDriveNum = chDrive == -1
                                        ? -1 : toupper(chDrive) - 'A';

        // convert error code to i24 based error.
        ulErrCode -= ERROR_WRITE_PROTECT;
        pHardErrPacket->vhe_HrdErrCode =  (UCHAR)ulErrCode;

#if DBG
        if (fShowSVCMsg & DEMERROR) {
            sprintf(demDebugBuffer,
                    "demClientErr HRDERR: DriveNum=%ld ErrCode=%ld\n",
                    (DWORD)pHardErrPacket->vhe_bDriveNum,
                    (DWORD)pHardErrPacket->vhe_HrdErrCode);
            OutputDebugStringOem(demDebugBuffer);
            }
#endif
        // Save Away Information for possible retry operation
        demSaveHardErrInfo ();


        }

    if (bSetRegs)
        setCF(1);
    return (ulErrCode);
}



/*
 *  GetDriveLetterByHandle
 *
 *  retrieves the drive letter for the file handle
 *  if its a remote drive or fails returns -1
 */
CHAR GetDriveLetterByHandle(HANDLE hFile)
{
     NTSTATUS Status;
     ULONG    ul;
     ANSI_STRING  AnsiString;
     FILE_FS_DEVICE_INFORMATION DeviceInfo;
     IO_STATUS_BLOCK IoStatusBlock;
     POBJECT_NAME_INFORMATION pObNameInfo;
     CHAR    Buffer[MAX_PATH+sizeof(OBJECT_NAME_INFORMATION)];
     CHAR    ch;

       // if a remote drive return -1 for drive letter
     Status = NtQueryVolumeInformationFile(
                hFile,
                &IoStatusBlock,
                &DeviceInfo,
                sizeof(DeviceInfo),
                FileFsDeviceInformation );

     if (NT_SUCCESS(Status) &&
         DeviceInfo.Characteristics & FILE_REMOTE_DEVICE )
         return (CHAR) -1;

       // get the name
     pObNameInfo = (POBJECT_NAME_INFORMATION)Buffer;
     Status = NtQueryObject(              // get len of name
                hFile,
                ObjectNameInformation,
                pObNameInfo,
                sizeof(Buffer),
                &ul);

     if (!NT_SUCCESS(Status))
          return -1;

     RtlUnicodeStringToAnsiString(&AnsiString, &(pObNameInfo->Name), TRUE);
     if (strstr(AnsiString.Buffer,"\\Device") == AnsiString.Buffer)
         SubstituteDeviceName(&(pObNameInfo->Name), AnsiString.Buffer);

     ch = AnsiString.Buffer[0];
     RtlFreeAnsiString(&AnsiString);
     return ch;
}


/*
 *  SubstituteDeviceName
 *
 *  lifted this code from the user\harderror hard error thread
 */
VOID SubstituteDeviceName( PUNICODE_STRING InputDeviceName,
                           LPSTR OutputDriveLetter )
{
    UNICODE_STRING LinkName;
    UNICODE_STRING DeviceName;
    OBJECT_ATTRIBUTES Obja;
    HANDLE LinkHandle;
    NTSTATUS Status;
    ULONG i;
    PWCHAR p;
    PWCHAR pSlash = L"\\";
    WCHAR DeviceNameBuffer[MAXIMUM_FILENAME_LENGTH];

       /*
        *  Ensure have trailing backslash
        */

    if (InputDeviceName->Buffer[(InputDeviceName->Length >>1) - 1] != *pSlash)
        RtlAppendUnicodeToString(InputDeviceName, pSlash);

    RtlInitUnicodeString(&LinkName,L"\\DosDevices\\?:");
    p = (PWCHAR)LinkName.Buffer;
    p = p+12;
    for(i=0;i<26;i++){
        *p = (WCHAR)'A' + (WCHAR)i;

        InitializeObjectAttributes(
            &Obja,
            &LinkName,
            OBJ_CASE_INSENSITIVE,
            NULL,
            NULL
            );
        Status = NtOpenSymbolicLinkObject(
                    &LinkHandle,
                    SYMBOLIC_LINK_QUERY,
                    &Obja
                    );
        if (NT_SUCCESS( Status )) {

            //
            // Open succeeded, Now get the link value
            //
            DeviceName.Length = 0;
            DeviceName.MaximumLength = sizeof(DeviceNameBuffer);
            DeviceName.Buffer = DeviceNameBuffer;

            Status = NtQuerySymbolicLinkObject(
                        LinkHandle,
                        &DeviceName,
                        NULL
                        );
            NtClose(LinkHandle);
            if ( NT_SUCCESS(Status) ) {

                if (DeviceName.Buffer[(DeviceName.Length >>1) - 1] != *pSlash)
                    RtlAppendUnicodeToString(&DeviceName, pSlash);

                if ( RtlEqualUnicodeString(InputDeviceName,&DeviceName,TRUE) )
                   {
                    OutputDriveLetter[0]='A'+(WCHAR)i;
                    OutputDriveLetter[1]='\0';
                    return;
                    }
                }
            }
        }

     // just in case we don't find it
    OutputDriveLetter[0]=(char)-1;
    OutputDriveLetter[1]='\0';
    return;


}





/* demSaveHardErrInfo
 * demRestoreHardErrInfo
 *
 * These two routines are used to preserve all the DOSKRNL registers
 * which will be needed to retry an SVC handler, in case user opts for
 * retry in harderr popup. This is a preferred way to handle retry
 * as it gives the DOSKRNL code the freedom to trash any register
 * even though it might have to retry the operation. It saves lots
 * of code bytes in heavily used DOS macro "HrdSVC".
 *
 * Entry
 *	None
 *
 * Exit
 *	None
 *
 * Notes
 *
 *  1. Doing things this way means, DOSKRNL cannot change the
 *     registers for retry. Under any circumstances, i can't think
 *     why it would need to do that anyway.
 *
 *  2. This mechanism also assumes that DOSKRNL never uses CS,IP,SS,SP
 *     for passing SVC parameters.
 *
 *  3. DOS does'nt allow int24 hookers to make any call which comes
 *     to DEM, so using CurrentISVC is safe.
 *
 *  4. If an SVC handler can pssibly return a hard error it should never
 *     modify the client registers.
 */


VOID demSaveHardErrInfo (VOID)
{
    RetryInfo.ax    =	getAX();
    RetryInfo.bx    =	getBX();
    RetryInfo.cx    =	getCX();
    RetryInfo.dx    =	getDX();
    RetryInfo.ds    =	getDS();
    RetryInfo.es    =	getES();
    RetryInfo.si    =	getSI();
    RetryInfo.di    =	getDI();
    RetryInfo.bp    =	getBP();
    RetryInfo.iSVC  =	CurrentISVC;
    return;
}


VOID demRestoreHardErrInfo (VOID)
{
    setAX(RetryInfo.ax);
    setBX(RetryInfo.bx);
    setCX(RetryInfo.cx);
    setDX(RetryInfo.dx);
    setDS(RetryInfo.ds);
    setES(RetryInfo.es);
    setSI(RetryInfo.si);
    setDI(RetryInfo.di);
    setBP(RetryInfo.bp);
    CurrentISVC =   RetryInfo.iSVC;
    return;
}
