/*++

Copyright (c) 1991-1993 Microsoft Corporation

Module Name:

    DosPrtW.c

Abstract:

    This module provides the UNICODE mapping layer from the old DosPrint APIs
    to the new all singing all dancing beautiful Print APIs.  (The ANSI
    mapping layer is in DosPrint.c)

Author:

    Dave Snipp (DaveSn) 26-Apr-1991

Revision History:

    09-Jul-1992 JohnRo
        Created this file (from DaveSn's DosPrint.c) for RAID 10324: net print
        vs. UNICODE.
    05-Oct-1992 JohnRo
        RAID 3556: DosPrintQGetInfo(from downlevel) level 3, rc=124.  (4&5 too.)
        RAID 3580: lmsvcs.exe: access violation from OS/2 DosPrintJobGetInfo.
        RAID 8333: view printer queues hangs DOS LM enh client.
        Make sure data type in job level 1 is null terminated.
        Fixed job submitted times.
        Fixed error code if GlobalAlloc fails.
        Fixed memory leak in DosPrintQGetInfoW.
        Fixed DosPrintQEnumW level 5 array bug.
        Fixed DosPrintJobEnumW levels 2 and 3.
    25-Nov-1992 JohnRo
        RAID 1661: downlevel to NT DosPrintDestEnum not supported.
        Added code to track down empty queue name.
        Quiet normal debug output.
        Avoid const vs. volatile compiler warnings.
        Avoid other new compiler warnings.
    08-Feb-1993 JohnRo
        RAID 10164: Data misalignment error during XsDosPrintQGetInfo().
    22-Mar-1993 JohnRo
        RAID 2974: NET PRINT says NT printer is held when it isn't.
    11-May-1993 JohnRo
        RAID 9942: fix queue name in job info level 3.
    14-May-1993 JohnRo
        RAID 9961: DosPrintDestEnum returns NO_ERROR to downlevel but
        pcReturned=0; should return NERR_DestNotFound.
        Fixed data type returned from PrjInfoFixedSizeW().
    18-May-1993 JohnRo
        DosPrintQGetInfoW underestimates number of bytes needed.
        Use NetpKdPrint() where possible.
        Made changes suggested by PC-LINT.
    04-Jun-1993 JohnRo
        RAID 10222: DosPrintQEnumW returns ERROR_INVALID_USER_BUFFER
        when queue is empty.
        Made changes suggested by PC-LINT 5.0
    08-Jul-1993 JohnRo
        RAID 15509: GetJob() API sometimes returned TRUE, even on error case.
        Also added some >64KB checks.
        Added some assert checks...
    13-Jul-1993 JohnRo
        Intermittent empty print queue (was buggy after some MyEnumJobs calls).

--*/


#ifndef UNICODE
#error "RxPrint APIs assume RxRemoteApi uses wide characters."
#endif

#define NOMINMAX
#define NOSERVICE       // Avoid <winsvc.h> vs. <lmsvc.h> conflicts.
#include <windows.h>

//#include <lm.h>
#include <netdebug.h>

#include <string.h>


#ifdef _WINSPOOL_
#error "Include of winspool.h moved, make sure it doesn't get UNICODE."
#endif

#undef UNICODE
#include <winspool.h>
#define UNICODE

#ifndef _WINSPOOL_
#error "Oops, winspool.h changed, make sure this code is still OK."
#endif


#include <dosprint.h>
#include <dosprtp.h>    // IF_DEBUG(), CommandALocalJob(), etc.
#include <lmapibuf.h>   // NetApiBufferFree(), etc.
#include <lmerr.h>      // NO_ERROR, NERR_, and ERROR_ equates.
#include <lmshare.h>    // LPSHARE_INFO_1, STYPE_ equates, etc.
#include <prefix.h>     // PREFIX_ equates.
#include <stddef.h>     // offsetof().
#include <timelib.h>    // NetpSystemTimeToGmtTime().
#include <tstring.h>    // WCSSIZE(), NetpNCopy{type}To{type}.
#include <wchar.h>      // wsclen(), wcscpy(), etc.
#include "myspool.h"


#define STR_CONV_SIZE(psz)      ( (strlen(psz)+1) * sizeof(WCHAR) )

// NULL_STR_CONV_SIZE: Compute size needed for converted string, which
// is possibly a null pointer but downlevel really wants ptr to null char.
#define NULL_STR_CONV_SIZE(psz) ( (psz) ? STR_CONV_SIZE(psz) : sizeof(WCHAR) )


#define ARRAY_END      ((DWORD) -1)

#define MAX_WORD        (  (WORD) (~0) )

// BUGBUG: Come up with a better error code for this?
#define MY_PROTOCOL_LIMIT_ERROR         ERROR_NOT_ENOUGH_MEMORY



DBGSTATIC LPWSTR
PackAnsiStringsToW(
   LPSTR *pSource,
   LPBYTE pDest,
   CONST DWORD *DestOffsets,
   LPWSTR pEnd
)
{
   while (*DestOffsets != ARRAY_END) {
      if (*pSource) {
         pEnd-=(strlen(*pSource) + 1);

         // Copy the string and convert chars while we're at it.
         NetpCopyStrToWStr(pEnd, *pSource);

         *(LPWSTR *)(pDest+*DestOffsets) = pEnd;
      } else {

         // BUGBUG?  I think this first version is wrong, as downlevel (e.g.
         // OS/2 print manager) doesn't handle null pointers well?
         // --JR, 19-Jun-1992
         //
         // *(LPWSTR *)(pDest+*DestOffsets)=0;

         --pEnd;             // need 1 char for this.
         *pEnd = L'\0';
         *(LPWSTR *)(pDest+*DestOffsets) = pEnd;
      }
      pSource++;
      DestOffsets++;
   }

   return pEnd;
}

DBGSTATIC DWORD
PrjInfoFixedSizeW(
    IN DWORD Level  // assumed valid
    )
{
    switch (Level) {

    case 0:
        return sizeof(WORD);    // job number.
    case 1:
        return (sizeof(PRJINFOW));
    case 2:
        return (sizeof(PRJINFO2W));
    case 3:
        return (sizeof(PRJINFO3W));
    default:
        NetpAssert( FALSE );
        return (0);
    }
    /*NOTREACHED*/
}

DBGSTATIC DWORD
GetPrjInfoSizeW(
    IN DWORD Level,
    IN LPJOB_INFO_2 pJob,
    IN LPCWSTR QueueNameW,
    IN LPPRINTER_INFO_2 pPrinter OPTIONAL
)
{
    NetpAssert( pJob != NULL );
    switch (Level) {

    case 0:

        return sizeof(WORD);    // job number.

    case 1:

        return sizeof(PRJINFOW) +
               NULL_STR_CONV_SIZE( (LPSTR) (pJob->pParameters) ) +
               NULL_STR_CONV_SIZE( (LPSTR) (pJob->pStatus) ) +
               NULL_STR_CONV_SIZE( (LPSTR) (pJob->pDocument) );  // fake pszComment

    case 2:

        return sizeof(PRJINFO2W) +
               NULL_STR_CONV_SIZE( (LPSTR) (pJob->pUserName) ) +
               NULL_STR_CONV_SIZE( (LPSTR) (pJob->pDocument) ) +  // fake pszComment
               NULL_STR_CONV_SIZE( (LPSTR) (pJob->pDocument) );

    case 3:

            NetpAssert( pPrinter != NULL );
            NetpAssert( QueueNameW != NULL );

            return sizeof(PRJINFO3W) +
                   NULL_STR_CONV_SIZE( (LPSTR) (pJob->pUserName) ) +
                   NULL_STR_CONV_SIZE( (LPSTR) (pJob->pDocument) ) + // fake pszComment
                   NULL_STR_CONV_SIZE( (LPSTR) (pJob->pDocument) ) +
                   NULL_STR_CONV_SIZE( (LPSTR) (pJob->pNotifyName) ) +
                   NULL_STR_CONV_SIZE( (LPSTR) (pJob->pDatatype) ) +
                   NULL_STR_CONV_SIZE( (LPSTR) (pJob->pParameters) ) +
                   NULL_STR_CONV_SIZE( (LPSTR) (pJob->pStatus) ) +
                   WCSSIZE( QueueNameW ) +  // pszQueue
                   NULL_STR_CONV_SIZE( (LPSTR) (pPrinter->pPrintProcessor) ) +
                   NULL_STR_CONV_SIZE( (LPSTR) (pPrinter->pParameters) ) +
                   NULL_STR_CONV_SIZE( (LPSTR) (pJob->pDriverName) ) +
                   // BUGBUG: pDriverData?
                   NULL_STR_CONV_SIZE( (LPSTR) (pJob->pPrinterName) );

    default:
        NetpKdPrint(( PREFIX_DOSPRINT
                "GetPrjInfoSizeW: invalid level!\n" ));
        return 0;

    }
    /*NOTREACHED*/
}

// Print job info string table (for level 1).
DBGSTATIC CONST DWORD PrjInfo1StringsW[]={
                        offsetof(PRJINFOW, pszParms),
                        offsetof(PRJINFOW, pszStatus),
                        offsetof(PRJINFOW, pszComment),
                        ARRAY_END};

// Print job info string table (for level 2).
DBGSTATIC CONST DWORD PrjInfo2StringsW[]={
                        offsetof(PRJINFO2W, pszUserName),
                        offsetof(PRJINFO2W, pszComment),
                        offsetof(PRJINFO2W, pszDocument),
                        (DWORD) -1};

// Print job info string table (for items which level 3 has on top of level 2).
DBGSTATIC CONST DWORD PrjInfo3StringsW[]={
                        offsetof(PRJINFO3W, pszNotifyName),
                        offsetof(PRJINFO3W, pszDataType),
                        offsetof(PRJINFO3W, pszParms),
                        offsetof(PRJINFO3W, pszStatus),
                        offsetof(PRJINFO3W, pszQProcName),
                        offsetof(PRJINFO3W, pszQProcParms),
                        offsetof(PRJINFO3W, pszDriverName),
                        offsetof(PRJINFO3W, pszPrinterName),
                        (DWORD) -1};

DBGSTATIC LPWSTR
CopyJobToPrjInfoW(
    IN DWORD Level,
    IN LPJOB_INFO_2 pJob,
    IN LPCWSTR QueueNameW,
    IN LPPRINTER_INFO_2 pPrinter OPTIONAL,
    OUT PBYTE pBuffer,
    IN OUT LPWSTR pEnd
    )
{
    LPSTR *pSourceStrings;
    NET_API_STATUS rc;

    NetpAssert( pBuffer != NULL );
    NetpAssert( pEnd != NULL );
    NetpAssert( pJob != NULL );

    switch (Level) {

    case 0:

        {
            PWORD pJobIds = (PWORD) pBuffer;
            *pJobIds = (WORD)pJob->JobId;
        }
        break;

    case 1:
        {
            LPSTR SourceStrings[sizeof(PrjInfo1StringsW)/sizeof(DWORD)];
            PPRJINFOW pPrjInfo = (LPVOID) pBuffer;

            pSourceStrings=SourceStrings;
            *pSourceStrings++ = (LPSTR) (pJob->pParameters);
            *pSourceStrings++ = (LPSTR) (pJob->pStatus);
            *pSourceStrings++ = (LPSTR) (pJob->pDocument);  // fake pszComment

            pEnd = PackAnsiStringsToW(
                    SourceStrings,
                    (LPBYTE) (LPVOID) pPrjInfo,
                    PrjInfo1StringsW,
                    pEnd);

            pPrjInfo->uJobId = (WORD)pJob->JobId;

            if (pJob->pUserName)
                (VOID) NetpNCopyStrToWStr(
                        (LPWSTR) (pPrjInfo->szUserName),
                        (LPSTR) (pJob->pUserName),
                        LM20_UNLEN+1);
            else
                pPrjInfo->szUserName[0] = L'\0';

            if (pJob->pNotifyName)
                (VOID) NetpNCopyStrToWStr(
                        (LPWSTR) (pPrjInfo->szNotifyName),
                        (LPSTR) (pJob->pNotifyName),
                        LM20_CNLEN+1);
            else
                pPrjInfo->szNotifyName[0] = L'\0';

            if (pJob->pDatatype) {
                (VOID) NetpNCopyStrToWStr(
                        (LPWSTR) (pPrjInfo->szDataType),
                        (LPSTR) (pJob->pDatatype),
                        DTLEN+1);
                pPrjInfo->szDataType[DTLEN] = L'\0';
            } else {
                pPrjInfo->szDataType[0] = L'\0';
            }

            pPrjInfo->uPosition = (WORD)pJob->Position;

            pPrjInfo->fsStatus = PrjStatusFromJobStatus( pJob->Status );

            rc = NetpSystemTimeToGmtTime(
                    &pJob->Submitted,
                    &pPrjInfo->ulSubmitted );
            NetpAssert( rc == NO_ERROR );

            pPrjInfo->ulSize = pJob->Size;
        }
        break;

    case 2:  /*FALLTHROUGH*/
    case 3:
        {
            PPRJINFO2W pPrjInfo = (LPVOID) pBuffer;
            LPSTR SourceStrings[sizeof(PrjInfo2StringsW)/sizeof(DWORD)];

            pSourceStrings=SourceStrings;
            *pSourceStrings++ = (LPSTR) (pJob->pUserName);
            *pSourceStrings++ = (LPSTR) (pJob->pDocument);  // fake pszComment
            *pSourceStrings++ = (LPSTR) (pJob->pDocument);

            pEnd = PackAnsiStringsToW(
                    SourceStrings,
                    (LPBYTE) (LPVOID) pPrjInfo,
                    PrjInfo2StringsW,
                    pEnd);

            pPrjInfo->uJobId = (WORD)pJob->JobId;
            pPrjInfo->uPriority = (WORD)pJob->Priority;

            pPrjInfo->uPosition = (WORD)pJob->Position;

            pPrjInfo->fsStatus = PrjStatusFromJobStatus( pJob->Status );

            rc = NetpSystemTimeToGmtTime(
                    &pJob->Submitted,
                    &pPrjInfo->ulSubmitted );
            NetpAssert( rc == NO_ERROR );

            pPrjInfo->ulSize = pJob->Size;
        }

        if (Level == 3) {
            PPRJINFO3W pPrjInfo = (LPVOID) pBuffer;
            LPSTR SourceStrings[sizeof(PrjInfo3StringsW)/sizeof(DWORD)];

            NetpAssert( pPrinter != NULL );

            //
            // Copy queue name first, as it is already right char set.
            //
            NetpAssert( QueueNameW != NULL );
            pEnd-=(wcslen(QueueNameW) + 1);

            (VOID) wcscpy(pEnd, QueueNameW);

            pPrjInfo->pszQueue = pEnd;

            //
            // Copy and convert other strings.
            //
            pSourceStrings=SourceStrings;
            *pSourceStrings++ = (LPSTR) (pJob->pNotifyName);
            *pSourceStrings++ = (LPSTR) (pJob->pDatatype);
            *pSourceStrings++ = (LPSTR) (pJob->pParameters);
            *pSourceStrings++ = (LPSTR) (pJob->pStatus);
            *pSourceStrings++ = pPrinter->pPrintProcessor;
            *pSourceStrings++ = pPrinter->pParameters;
            *pSourceStrings++ = (LPSTR) (pJob->pDriverName);
            *pSourceStrings++ = (LPSTR) (pJob->pPrinterName);

            pEnd = PackAnsiStringsToW(
                    SourceStrings,
                    (LPBYTE) (LPVOID) pPrjInfo,
                    PrjInfo3StringsW,
                    pEnd);

            pPrjInfo->pDriverData = NULL;
        }

        break;

    default:
        NetpKdPrint(( PREFIX_DOSPRINT
                "CopyJobToPrjInfoW: invalid level!\n" ));

    }

    IF_DEBUG( DOSPRTW ) {
        if (NetpIsPrintJobLevelValid( Level, FALSE ) ) {
            NetpKdPrint(( PREFIX_DOSPRINT "CopyJobToPrjInfoW: "
                    " created print job:\n" ));
            NetpDbgDisplayPrintJob( Level, pBuffer, TRUE ); // Yes, has UNICODE.
        }
    }

    return pEnd;
}

DBGSTATIC DWORD
GetPrqInfoSizeW(
    IN DWORD Level,
    IN LPCWSTR QueueNameW,
    IN LPPRINTER_INFO_2 pPrinter
    )
{
    NetpAssert( QueueNameW != NULL );
    NetpAssert( (*QueueNameW) != L'\0' );

    switch (Level) {

    case 0:

        return ( (LM20_QNLEN+1) * sizeof(WCHAR) );

    case 1: /*FALLTHROUGH*/
    case 2:

        return sizeof(PRQINFOW) +
                NULL_STR_CONV_SIZE( pPrinter->pSepFile ) +
                NULL_STR_CONV_SIZE( pPrinter->pPrintProcessor ) +
                NULL_STR_CONV_SIZE( pPrinter->pPortName ) +
                NULL_STR_CONV_SIZE( pPrinter->pParameters ) +
                NULL_STR_CONV_SIZE( pPrinter->pComment );

    case 3: /*FALLTHROUGH*/
    case 4:

        NetpAssert( QueueNameW != NULL );

        return sizeof(PRQINFO3W) +
                WCSSIZE( QueueNameW ) +   // pszName
                NULL_STR_CONV_SIZE( pPrinter->pSepFile ) +
                NULL_STR_CONV_SIZE( pPrinter->pPrintProcessor ) +
                NULL_STR_CONV_SIZE( pPrinter->pParameters ) +
                NULL_STR_CONV_SIZE( pPrinter->pComment ) +
                NULL_STR_CONV_SIZE( pPrinter->pPortName ) +
                NULL_STR_CONV_SIZE( pPrinter->pDriverName );
                // BUGBUG: no support for pszDriverData?

    case 5:

        NetpAssert( QueueNameW != NULL );

        return sizeof(LPWSTR) +
                WCSSIZE( QueueNameW );    // pszName

    default:
        NetpKdPrint(( PREFIX_DOSPRINT
                "GetPrqInfoSizeW: invalid level!\n" ));

    }

    return 0;
}

DBGSTATIC DWORD
PrqInfoFixedSizeW(
    IN DWORD Level  // assumed valid
    )
{
    switch (Level) {
    case 0:
        return ( (LM20_QNLEN+1) * sizeof(WCHAR) );
    case 1: /*FALLTHROUGH*/
    case 2:
        return (sizeof(PRQINFOW));
    case 3: /*FALLTHROUGH*/
    case 4:
        return (sizeof(PRQINFO3W));
    case 5:
        return (sizeof(LPWSTR));
    default:
        NetpAssert( FALSE );   // Level should be valid!
        return (0);
    }
    /*NOTREACHED*/
}

// String table for Q levels 1,2
DBGSTATIC CONST DWORD PrqInfo1StringsW[]={
                        offsetof(PRQINFOW, pszSepFile),
                        offsetof(PRQINFOW, pszPrProc),
                        offsetof(PRQINFOW, pszDestinations),
                        offsetof(PRQINFOW, pszParms),
                        offsetof(PRQINFOW, pszComment),
                        ARRAY_END};

// String table for Q levels 3,4.
DBGSTATIC CONST DWORD PrqInfo3StringsW[]={
                        offsetof(PRQINFO3W, pszSepFile),
                        offsetof(PRQINFO3W, pszPrProc),
                        offsetof(PRQINFO3W, pszParms),
                        offsetof(PRQINFO3W, pszComment),
                        offsetof(PRQINFO3W, pszPrinters),
                        offsetof(PRQINFO3W, pszDriverName),
                        (DWORD) -1};

DBGSTATIC LPWSTR
CopyPrinterToPrqInfoW(
    IN LPPRINTER_INFO_2 pPrinter,
    IN DWORD Level,
    OUT LPBYTE pBuffer,
    IN LPCWSTR QueueNameW,
    OUT LPWSTR pEnd
    )
{
    LPSTR *pSourceStrings;

    NetpAssert( pEnd != NULL );
    NetpAssert( QueueNameW != NULL );
    NetpAssert( (*QueueNameW) != L'\0' );

    switch (Level) {

    case 0:
        (VOID) wcsncpy(
                (LPWSTR) (LPVOID) pBuffer,
                QueueNameW,
                LM20_QNLEN);
        break;

    case 1: /*FALLTHROUGH*/
    case 2:

        {
            LPSTR SourceStrings[sizeof(PrqInfo1StringsW)/sizeof(DWORD)];
            PPRQINFOW pPrqInfo = (LPVOID) pBuffer;

            pSourceStrings=SourceStrings;
            *pSourceStrings++ = pPrinter->pSepFile;
            *pSourceStrings++ = pPrinter->pPrintProcessor;
            *pSourceStrings++ = pPrinter->pPortName;
            *pSourceStrings++ = pPrinter->pParameters;
            *pSourceStrings++ = pPrinter->pComment;

            pEnd = PackAnsiStringsToW(
                    SourceStrings,
                    (LPBYTE) (LPVOID) pPrqInfo,
                    PrqInfo1StringsW,
                    pEnd);

            NetpAssert( QueueNameW != NULL );

            (VOID) wcsncpy(
                    pPrqInfo->szName,  // dest
                    QueueNameW,        // src
                    LM20_QNLEN+1);     // char count

            pPrqInfo->uPriority = (WORD)pPrinter->Priority;
            pPrqInfo->uStartTime = (WORD)pPrinter->StartTime;
            pPrqInfo->uUntilTime = (WORD)pPrinter->UntilTime;

            pPrqInfo->fsStatus = PrqStatusFromPrinterStatus( pPrinter->Status );

            pPrqInfo->cJobs = (WORD)pPrinter->cJobs;
        }

        break;

    case 3: /*FALLTHROUGH*/
    case 4:
        {
            LPSTR SourceStrings[sizeof(PrqInfo3StringsW)/sizeof(DWORD)];
            PPRQINFO3W pPrqInfo = (LPVOID) pBuffer;

            //
            // Copy queue name first, as it is already right char set.
            //
            NetpAssert( QueueNameW != NULL );
            pEnd-=(wcslen(QueueNameW) + 1);

            (VOID) wcscpy(pEnd, QueueNameW);

            pPrqInfo->pszName = pEnd;

            //
            // Copy and convert other strings.
            //
            pSourceStrings=SourceStrings;
            *pSourceStrings++ = pPrinter->pSepFile;
            *pSourceStrings++ = pPrinter->pPrintProcessor;
            *pSourceStrings++ = pPrinter->pParameters;
            *pSourceStrings++ = pPrinter->pComment;
            *pSourceStrings++ = pPrinter->pPortName;  // pszPrinters
            *pSourceStrings++ = pPrinter->pDriverName;

            pEnd = PackAnsiStringsToW(
                    SourceStrings,
                    (LPBYTE) (LPVOID) pPrqInfo,
                    PrqInfo3StringsW,
                    pEnd);

            pPrqInfo->uPriority = (WORD)pPrinter->Priority;
            pPrqInfo->uStartTime = (WORD)pPrinter->StartTime;
            pPrqInfo->uUntilTime = (WORD)pPrinter->UntilTime;

            pPrqInfo->fsStatus = PrqStatusFromPrinterStatus( pPrinter->Status );

            pPrqInfo->cJobs = (WORD)pPrinter->cJobs;
            pPrqInfo->pDriverData = NULL;  // BUGBUG better support for this?

            // Note: if level is 4, caller will add array of jobs after this.

            break;
        }

    case 5:
        NetpAssert( QueueNameW != NULL );

        pEnd -= (wcslen( QueueNameW ) + 1);
        * (LPWSTR *) pBuffer = pEnd;

        (VOID) wcscpy(
                pEnd,           // dest
                QueueNameW );   // src


        break;

    default:
        NetpKdPrint(( PREFIX_DOSPRINT
                "CopyPrinterToPrqInfoW: invalid level!\n" ));

    }

#if DBG
    IF_DEBUG( DOSPRTW ) {
        if (NetpIsPrintQLevelValid( Level, FALSE) ) {
            DWORD PretendLevel = Level;  // we can use this level unless...
            NetpKdPrint(( PREFIX_DOSPRINT "CopyPrinterToPrqInfoW: "
                    " created print queue:\n" ));
            if ( (Level == 2) || (Level == 4) ) {
                if (Level == 2) {
                    PretendLevel = 1;
                } else {
                    PretendLevel = 3;
                }
                NetpKdPrint((
                        "  (no jobs copied yet, so pretending info level is "
                        FORMAT_DWORD ", real info level is "
                        FORMAT_DWORD ").\n",
                        PretendLevel, Level ));
            }
            NetpDbgDisplayPrintQ(
                    PretendLevel,
                    pBuffer,
                    TRUE );  // Yes, has UNICODE.
        }
    }
#endif

    return pEnd;
}

DBGSTATIC NET_API_STATUS
ComputeSpaceNeededForJobs(
    IN  LPCWSTR          QueueNameW,
    IN  DWORD            QLevel,
    IN  HANDLE           PrinterHandle,
    IN  LPPRINTER_INFO_2 pPrinter,
    OUT LPDWORD          pcbNeeded
    )
{
    NET_API_STATUS ApiStatus;
    DWORD          cJobs;
    DWORD          cbJobs;
    DWORD          cbNeeded = 0;
    DWORD          JobLevel;
    LPJOB_INFO_2   pJob = NULL;
    LPJOB_INFO_2   pJobs = NULL;

    NetpAssert( (QLevel==2) || (QLevel==4) );
    NetpAssert( QueueNameW != NULL );
    NetpAssert( pPrinter != NULL );

    if (QLevel==2) {
        JobLevel = 1;
    } else {
        JobLevel = 2;
    }


    if (!MyEnumJobs(PrinterHandle, 0, (DWORD) -1, 2, NULL, 0, &cbJobs, &cJobs)) {

        ApiStatus = (NET_API_STATUS) GetLastError();
        if (ApiStatus == ERROR_INSUFFICIENT_BUFFER) {

            pJobs = (LPVOID) GlobalAlloc(GMEM_FIXED, cbJobs);
            if (pJobs == NULL) {

                ApiStatus = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            }

        } else {
            NetpKdPrint(( PREFIX_DOSPRINT
                    "ComputeSpaceNeededForJobs: got error " FORMAT_API_STATUS
                    " from MyEnumJobs(first).\n", ApiStatus ));
            goto Cleanup;
        }
    }

    if (!MyEnumJobs(PrinterHandle, 0, (DWORD) -1, 2, (LPBYTE)pJobs, cbJobs,
                              &cbJobs, &cJobs)) {

        ApiStatus = (NET_API_STATUS) GetLastError();
        NetpAssert( ApiStatus != ERROR_INSUFFICIENT_BUFFER );
        NetpKdPrint(( PREFIX_DOSPRINT
                "ComputeSpaceNeededForJobs: got error " FORMAT_API_STATUS
                " from MyEnumJobs(second)\n", ApiStatus ));
        goto Cleanup;
    }

    if (cJobs == 0) {
        cbNeeded = 0;
        ApiStatus = NO_ERROR;
        goto Cleanup;
    }
    if (pJobs == NULL) {
        NetpKdPrint(( PREFIX_DOSPRINT
                "ComputeSpaceNeededForJobs: never allocated array!\n" ));
        ApiStatus = NERR_InternalError;
        goto Cleanup;
    }

    pJob=pJobs;

    while (cJobs--) {
        cbNeeded+=GetPrjInfoSizeW(JobLevel, pJob++, QueueNameW, pPrinter);
    }

    *pcbNeeded=(WORD)cbNeeded;  // final byte count for this queue's jobs.

    ApiStatus = NO_ERROR;

Cleanup:
    if (pJobs != NULL) {
        (VOID) GlobalFree(pJobs);
    }

    *pcbNeeded = cbNeeded;  // final byte count for this queue's jobs.

    return (ApiStatus);

} // ComputeSpaceNeededForJobs


DBGSTATIC NET_API_STATUS
AppendJobsToPrqW(
    IN LPCWSTR QueueNameW,
    IN DWORD QLevel,
    IN HANDLE PrinterHandle,
    IN LPPRINTER_INFO_2 pPrinter,
    OUT LPBYTE pbBuf,
    IN DWORD cbBuf,
    IN LPVOID pEnd,
    OUT LPVOID * pNewEnd,
    OUT LPDWORD pcbNeeded,
    OUT LPDWORD pcReturned
    )
{
    DWORD cJobs;
    DWORD cbJobs;
    DWORD cbNeeded = 0;
    DWORD cbPrj;
    DWORD JobLevel;
    DWORD rc;
    LPJOB_INFO_2 pJob = NULL;
    LPJOB_INFO_2 pJobs = NULL;

    NetpAssert( (QLevel==2) || (QLevel==4) );
    NetpAssert( QueueNameW != NULL );
    NetpAssert( pPrinter != NULL );

    if (QLevel==2) {
        cbPrj = sizeof(PRJINFOW);
        JobLevel = 1;
    } else {
        cbPrj = sizeof(PRJINFO2W);
        JobLevel = 2;
    }


    if (!MyEnumJobs(PrinterHandle, 0, (DWORD) -1, 2, NULL, 0, &cbJobs, pcReturned)) {

        rc = GetLastError();
        if (rc == ERROR_INSUFFICIENT_BUFFER) {

            pJobs = (LPVOID) GlobalAlloc(GMEM_FIXED, cbJobs);
            if (pJobs == NULL) {

                rc = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            }

        } else {
            NetpKdPrint(( PREFIX_DOSPRINT
                    "AppendJobsToPrqW: got error " FORMAT_API_STATUS
                    " from MyEnumJobs(first)\n", rc ));
            goto Cleanup;
        }
    }

    if (!MyEnumJobs(PrinterHandle, 0, (DWORD) -1, 2, (LPBYTE)pJobs, cbJobs,
                              &cbJobs, pcReturned)) {

        rc = GetLastError();
        NetpAssert( rc != ERROR_INSUFFICIENT_BUFFER );
        NetpKdPrint(( PREFIX_DOSPRINT
                "AppendJobsToPrqW: got error " FORMAT_API_STATUS
                " from MyEnumJobs(second)\n", rc ));
        goto Cleanup;
    }

    if (*pcReturned == 0) {
        cbNeeded = 0;
        rc = NO_ERROR;
        goto Cleanup;
    }
    if (pJobs == NULL) {
        NetpKdPrint(( PREFIX_DOSPRINT
                "AppendJobsToPrqW: never allocated array!\n" ));
        rc = NERR_InternalError;
        goto Cleanup;
    }

    cJobs = *pcReturned;
    pJob=pJobs;

    while (cJobs--)
        cbNeeded+=GetPrjInfoSizeW(JobLevel, pJob++, QueueNameW, pPrinter);

    *pcbNeeded=(WORD)cbNeeded;  // final byte count for this queue's jobs.

    if (cbNeeded <= cbBuf) {

        cJobs = *pcReturned;
        pJob=pJobs;
        while (cJobs--) {
            pEnd = CopyJobToPrjInfoW(JobLevel, pJob++, QueueNameW,
                    pPrinter,
                    pbBuf,
                    pEnd);
            pbBuf += cbPrj;  // Note: Wasn't DWORD aligned
        }
        rc = NO_ERROR;

    } else {

        rc = NERR_BufTooSmall;
    }

Cleanup:
    if (pJobs != NULL) {
        (VOID) GlobalFree(pJobs);
    }

    *pcbNeeded=(WORD)cbNeeded;  // final byte count for this queue's jobs.

    if (pNewEnd != NULL) {
        *pNewEnd = pEnd;
    }

    return (rc);

}
SPLERR SPLENTRY DosPrintQGetInfoW(
    LPWSTR  pszServer,
    LPWSTR  pszQueueName,
    WORD    uLevel,
    PBYTE   pbBuf,
    WORD    cbBuf,
    PUSHORT pcbNeeded
   )
{
    DWORD               cbPrinter;
    DWORD               cJobsReturned;
    LPWSTR              pEnd;
    DWORD               rc;
    HANDLE              hPrinter = INVALID_HANDLE_VALUE;
    LPPRINTER_INFO_2    pPrinter = NULL;
    LPWSTR              PrinterNameW = NULL;
    DWORD               cbNeeded;
    DWORD               cbNeededForJobs;

    if (pszServer && *pszServer) {
        rc = RxPrintQGetInfo(pszServer, pszQueueName, uLevel, pbBuf,
                               cbBuf, &cbNeeded);
        if (cbNeeded > MAX_WORD) {
            rc = MY_PROTOCOL_LIMIT_ERROR;
            goto Cleanup;
        }
        *pcbNeeded = (USHORT)cbNeeded;
        goto Cleanup;
    }

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintQGetInfoW(NULL, "
                FORMAT_LPWSTR ", " FORMAT_DWORD ", " FORMAT_LPVOID ", "
                FORMAT_DWORD ", " FORMAT_LPVOID ")\n",
                (pszQueueName!=NULL) ? pszQueueName : (LPWSTR) L"(missing)",
                uLevel,
                pbBuf,
                cbBuf,
                pcbNeeded));
    }

    *pcbNeeded = 0;  // in case an error occurs.
    if ( !NetpIsPrintQLevelValid( uLevel, FALSE ) ) {
        rc = ERROR_INVALID_LEVEL;
        goto Cleanup;
    }
    if ( (pszQueueName==NULL) || ((*pszQueueName)==L'\0') ) {
        rc = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    rc = QueueNameToPrinterNameW( pszQueueName, & PrinterNameW );
    if (rc != NO_ERROR) {
        goto Cleanup;
    }
    NetpAssert( PrinterNameW != NULL );

    if ( !MyOpenPrinterW( PrinterNameW, &hPrinter, NULL) ) {

        rc = GetLastError();
        if ( rc == ERROR_INVALID_PRINTER_NAME )
            rc = NERR_QNotFound;
        goto Cleanup;

    }

    if (!MyGetPrinter(hPrinter, 2, NULL, 0, &cbPrinter)) {

        rc = GetLastError();
        if (rc == ERROR_INSUFFICIENT_BUFFER) {

            pPrinter = (LPVOID) GlobalAlloc(GMEM_FIXED, cbPrinter);
            if (pPrinter == NULL) {

                rc = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            }
        } else {
            goto Cleanup;
        }

    }

    if (!MyGetPrinter(hPrinter, 2, (LPBYTE)pPrinter, cbPrinter, &cbPrinter)) {

        rc = GetLastError();
        goto Cleanup;
    }

    // How much for just the queue structure and its strings?
    cbNeeded=GetPrqInfoSizeW(uLevel, pszQueueName, pPrinter);
    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint(( PREFIX_DOSPRINT
                "DosPrintQGetInfoW: got " FORMAT_DWORD " for Q size.\n",
                (DWORD) cbNeeded ));
    }
    if (cbNeeded > MAX_WORD) {
        rc = MY_PROTOCOL_LIMIT_ERROR;
        goto Cleanup;
    }
    *pcbNeeded = (WORD)cbNeeded;  // Tell caller the size (so far).

    //
    // Build the queue structure itself.
    //
    if (cbNeeded <= (DWORD) cbBuf) {

        pEnd = CopyPrinterToPrqInfoW(pPrinter, uLevel, pbBuf,
                pszQueueName, (LPWSTR) (pbBuf+cbBuf) );

    } else {

        //
        // Too small.  Well, need to find total size before we can tell caller.
        //
        if ( (uLevel==2) || (uLevel==4) ) {
            rc = ComputeSpaceNeededForJobs(
                    pszQueueName,
                    uLevel,             // Q info level
                    hPrinter,
                    pPrinter,
                    & cbNeededForJobs );
            if (rc != NO_ERROR) {
                goto Cleanup;
            }
            cbNeeded += cbNeededForJobs;
        }
        if (cbNeeded > MAX_WORD) {
            rc = MY_PROTOCOL_LIMIT_ERROR;
            goto Cleanup;
        }
        rc = NERR_BufTooSmall;
        goto Cleanup;
    }

    //
    // Append jobs if necessary.
    //

    if ( (uLevel==2) || (uLevel==4) ) {
        DWORD cbPrq = PrqInfoFixedSizeW( uLevel );

        rc = AppendJobsToPrqW(
                pszQueueName,
                uLevel,             // Q info level
                hPrinter,
                pPrinter,
                pbBuf + cbPrq,      // put first job here
                cbBuf - cbNeeded,   // bytes avail for jobs
                pEnd,               // str area
                NULL,               // don't need new pEnd
                & cbNeededForJobs,
                & cJobsReturned );

        *pcbNeeded = (USHORT) (cbNeeded + cbNeededForJobs);

        //
        // Update job count in queue structure, as it may be out of date.
        //

        NetpSetJobCountForQueue(
                uLevel,                 // queue info level
                pbBuf,                  // queue structure to update
                TRUE,                   // yes, UNICODE strings
                cJobsReturned );        // actual job count

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

    }

    rc = NO_ERROR;

Cleanup:

    if (hPrinter != INVALID_HANDLE_VALUE) {
        (VOID) MyClosePrinter( hPrinter );
    }
    if (pPrinter) {
        (VOID) GlobalFree( pPrinter );
    }
    if (PrinterNameW != NULL) {
        (VOID) NetApiBufferFree( PrinterNameW );
    }

    return rc;
}


SPLERR SPLENTRY DosPrintJobGetInfoW(
    LPWSTR  pszServer,
    WORD    uJobId,
    WORD    uLevel,
    PBYTE   pbBuf,
    WORD    cbBuf,
    PUSHORT pcbNeeded
)
{
    DWORD               cb;
    DWORD               cbPrinter;
    HANDLE              hPrinter = INVALID_HANDLE_VALUE;
    LPSTR               QueueNameA = NULL;
    LPWSTR              QueueNameW = NULL;
    LPJOB_INFO_2        pJob = NULL;
    LPWSTR              pEnd;
    LPPRINTER_INFO_2    pPrinter = NULL;
    DWORD               rc;
    DWORD               cbNeeded;

    if (pszServer && *pszServer) {
        rc = RxPrintJobGetInfo(pszServer, uJobId, uLevel, pbBuf,
                                 cbBuf, &cbNeeded);
        *pcbNeeded = (USHORT)cbNeeded;
        return rc;
    }

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintJobGetInfoW(NULL, " FORMAT_DWORD
                ", " FORMAT_DWORD ", " FORMAT_LPVOID ", " FORMAT_DWORD
                ", " FORMAT_LPVOID ")\n",
                uJobId, uLevel, pbBuf, cbBuf, pcbNeeded));
    }

    *pcbNeeded = 0;  // in case an error occurs.

    if ( !NetpIsPrintJobLevelValid( uLevel, FALSE ) ) {
        rc = ERROR_INVALID_LEVEL;
        goto Cleanup;
    }

    hPrinter = FindLocalJob(uJobId);
    if (hPrinter == INVALID_HANDLE_VALUE) {
        rc = GetLastError();
        goto Cleanup;
    }

    if (!MyGetPrinter(hPrinter, 2, NULL, 0, &cbPrinter)) {

        rc = GetLastError();
        if (rc == ERROR_INSUFFICIENT_BUFFER) {

            pPrinter = (LPVOID) GlobalAlloc(GMEM_FIXED, cbPrinter);
            if (pPrinter == NULL) {

                rc = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            }
        } else {
            goto Cleanup;
        }

    }

    if (!MyGetPrinter(hPrinter, 2, (LPBYTE)pPrinter, cbPrinter, &cbPrinter)) {

        rc = GetLastError();
        goto Cleanup;
    }

    if (!MyGetJob(hPrinter, uJobId, 2, NULL, 0, &cb)) {

        rc=GetLastError();

        NetpAssert( rc != NO_ERROR );
        if (rc == ERROR_INSUFFICIENT_BUFFER) {

            pJob = (LPVOID) GlobalAlloc(GMEM_FIXED, cb);
            if (pJob == NULL) {
                rc = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            }

            if ( !MyGetJob(hPrinter, uJobId, 2, (LPBYTE)pJob, cb, &cb) ) {
                rc=GetLastError();
                NetpAssert( rc != NO_ERROR );
                goto Cleanup;
            }

        } else {
            if (rc == ERROR_INVALID_PARAMETER) {
                rc = NERR_JobNotFound;
            }
            goto Cleanup;  // Job deleted?  Not enough mem?
        }

    }
    if (pJob == NULL) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintJobGetInfoW: "
                "*** STILL INVALID RESULT FROM MyGetJob, pJob IS NULL!\n" ));
        rc = NERR_InternalError;
        goto Cleanup;
    }

    NetpAssert( pJob != NULL );
    NetpAssert( pPrinter != NULL );
    NetpAssert( pPrinter->pPrinterName != NULL );
    QueueNameA = FindQueueNameInPrinterNameA(
            (pPrinter->pPrinterName) );
    NetpAssert( QueueNameA != NULL );
    QueueNameW = NetpAllocWStrFromStr( QueueNameA );
    if (QueueNameW == NULL) {
        rc = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }
    NetpAssert( QueueNameW != NULL );

    cb=GetPrjInfoSizeW(uLevel, pJob, QueueNameW, pPrinter);

    *pcbNeeded=(WORD)cb;

    if (cb > (DWORD) cbBuf) {
        rc = NERR_BufTooSmall;
        goto Cleanup;
    }

    pEnd = (LPVOID) (pbBuf+cbBuf);

    (VOID) CopyJobToPrjInfoW(uLevel, pJob, QueueNameW, pPrinter, pbBuf, pEnd);

    rc = NO_ERROR;

Cleanup:

    if (hPrinter != INVALID_HANDLE_VALUE) {
        (VOID) MyClosePrinter( hPrinter );
    }
    if (pJob != NULL) {
        (VOID) GlobalFree( pJob );
    }
    if (pPrinter != NULL) {
        (VOID) GlobalFree( pPrinter );
    }
    if (QueueNameW != NULL) {
        (VOID) NetApiBufferFree( QueueNameW );
    }

    return (rc);

}

SPLERR SPLENTRY DosPrintJobDelW(
    LPWSTR  pszServer,
    WORD    uJobId
)
{

    if (pszServer && *pszServer)
        return RxPrintJobDel(pszServer, uJobId);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint(( PREFIX_DOSPRINT
                "DosPrintJobDelW(NULL, " FORMAT_DWORD ")\n", uJobId ));
    }

    return (CommandALocalJob( uJobId, JOB_CONTROL_CANCEL ) );
}

SPLERR SPLENTRY DosPrintJobContinueW(
    LPWSTR  pszServer,
    WORD    uJobId
)
{

    if (pszServer && *pszServer)
        return RxPrintJobContinue(pszServer, uJobId);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintJobContinueW(NULL, "
                FORMAT_DWORD ")\n", uJobId));
    }

    return (CommandALocalJob( uJobId, JOB_CONTROL_RESUME ) );
}

SPLERR SPLENTRY DosPrintJobPauseW(
   LPWSTR pszServer,
   WORD  uJobId
)
{

    if (pszServer && *pszServer)
        return RxPrintJobPause(pszServer, uJobId);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT
                "DosPrintJobPauseW(NULL, " FORMAT_DWORD ")\n",
                uJobId));
    }

    return (CommandALocalJob( uJobId, JOB_CONTROL_PAUSE ) );
}

SPLERR SPLENTRY DosPrintJobEnumW(
    LPWSTR  pszServer,
    LPWSTR  pszQueueName,
    WORD    uLevel,
    PBYTE   pbBuf,
    WORD    cbBuf,
    PWORD   pcReturned,
    PWORD   pcTotal
)
{
    DWORD               cbPrinter;
    LPJOB_INFO_2        pJob = NULL;
    LPJOB_INFO_2        pJobs;
    DWORD               cb, cbJobs, cReturned, cJobs;
    HANDLE              hPrinter = INVALID_HANDLE_VALUE;
    LPWSTR              pEnd;
    LPPRINTER_INFO_2    pPrinter = NULL;
    LPWSTR              PrinterNameW = NULL;
    DWORD               rc;
    DWORD               cTotal;

    if (pszServer && *pszServer) {
        rc = RxPrintJobEnum(pszServer, pszQueueName, uLevel, pbBuf,
                              cbBuf, &cReturned, &cTotal);
        *pcReturned = (WORD)cReturned;
        *pcTotal = (WORD)cTotal;
        goto Cleanup;
    }

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintJobEnumW(NULL, " FORMAT_LPWSTR
                ", " FORMAT_DWORD ", " FORMAT_LPVOID ", " FORMAT_DWORD
                ", " FORMAT_LPVOID ", " FORMAT_LPVOID ")\n",
                pszQueueName,
                uLevel,
                pbBuf,
                cbBuf,
                pcReturned,
                pcTotal));
    }

    *pcReturned=0;
    *pcTotal = 0;

    if ( !NetpIsPrintJobLevelValid( uLevel, FALSE ) ) {
        rc = ERROR_INVALID_LEVEL;
        goto Cleanup;
    }

    rc = QueueNameToPrinterNameW( pszQueueName, & PrinterNameW );
    if (rc != NO_ERROR) {
        goto Cleanup;
    }
    NetpAssert( PrinterNameW != NULL );

    if (!MyOpenPrinterW( PrinterNameW, &hPrinter, NULL)) {
        rc = GetLastError();
        goto Cleanup;
    }
    NetpAssert( hPrinter != INVALID_HANDLE_VALUE );

    if (!MyGetPrinter(hPrinter, 2, NULL, 0, &cbPrinter)) {

        rc = GetLastError();
        if (rc == ERROR_INSUFFICIENT_BUFFER) {

            pPrinter = (LPVOID) GlobalAlloc(GMEM_FIXED, cbPrinter);
            if (pPrinter == NULL) {

                rc = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            }
        } else {
            goto Cleanup;
        }

    }

    if (!MyGetPrinter(hPrinter, 2, (LPBYTE)pPrinter, cbPrinter,
                            &cbPrinter)) {

        rc = GetLastError();
        NetpAssert( rc != ERROR_INSUFFICIENT_BUFFER );
        goto Cleanup;
    }

    if (!MyEnumJobs(hPrinter, 0, (DWORD) -1, 2, NULL, 0, &cbJobs, &cReturned)) {

        rc = GetLastError();
        NetpAssert( rc != NO_ERROR );
        if (rc == ERROR_INSUFFICIENT_BUFFER) {

            if (pJob = (LPVOID) GlobalAlloc(GMEM_FIXED, cbJobs)) {

                if (!MyEnumJobs(hPrinter, 0, (DWORD) -1, 2, (LPBYTE)pJob, cbJobs,
                              &cbJobs, &cReturned)) {

                    rc = GetLastError();
                    NetpAssert( rc != NO_ERROR );
                    NetpAssert( rc != ERROR_INSUFFICIENT_BUFFER );  // BUGBUG
                    NetpKdPrint(( PREFIX_DOSPRINT
                            "DosPrintJobEnumW: got error " FORMAT_API_STATUS
                            " from MyEnumJobs(first)\n", rc ));
                    goto Cleanup;
                }
            } else {

                rc = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            }
        } else {
            NetpKdPrint(( PREFIX_DOSPRINT
                    "DosPrintJobEnumW: got error " FORMAT_API_STATUS
                    " from MyEnumJobs(first)\n", rc ));
            goto Cleanup;
        }
    }

    if (cReturned == 0) {
        *pcReturned = 0;
        *pcTotal = 0;
        rc = NO_ERROR;
        goto Cleanup;
    }
    if (pJob == NULL) {
        NetpKdPrint(( PREFIX_DOSPRINT
                "DosPrintJobEnumW: never allocated array!\n" ));
        rc = NERR_InternalError;
        goto Cleanup;
    }


    *pcTotal = (WORD)cReturned;

    cb=0;
    cJobs=cReturned;
    pJobs=pJob;
    while (cJobs--)
        cb+=GetPrjInfoSizeW(uLevel, pJobs++, pszQueueName, pPrinter);

    if (cb <= (DWORD) cbBuf) {

        DWORD cbFixedPortion = PrjInfoFixedSizeW( uLevel );
        NetpAssert( cbFixedPortion != 0 );  // level already checked!

        pEnd = (LPWSTR)(pbBuf+cbBuf);

        cJobs=cReturned;
        pJobs=pJob;

        while (cJobs--) {

            pEnd = CopyJobToPrjInfoW(uLevel, pJobs++,
                    pszQueueName,
                    pPrinter,
                    pbBuf, pEnd);
            pbBuf += cbFixedPortion;
        }

        *pcReturned = (WORD)cReturned;
        rc = NO_ERROR;

    } else {

        rc = NERR_BufTooSmall;
        goto Cleanup;
    }

Cleanup:

    if (hPrinter != INVALID_HANDLE_VALUE) {
        (VOID) MyClosePrinter( hPrinter );
    }
    if (pJob != NULL) {
        (VOID) GlobalFree( pJob );
    }
    if (pPrinter) {
        (VOID) GlobalFree( pPrinter );
    }
    if (PrinterNameW != NULL) {
        (VOID) NetApiBufferFree( PrinterNameW );
    }

    return rc;
}

SPLERR SPLENTRY
DosPrintDestEnumW(
    IN LPWSTR pszServer OPTIONAL,
    IN WORD uLevel,
    OUT PBYTE pbBuf,
    IN WORD cbBuf,
    OUT PUSHORT pcReturned,
    OUT PUSHORT pcTotal
    )
{
    DWORD   cReturned, cTotal, rc;

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint(( PREFIX_DOSPRINT "DosPrintDestEnumW(" FORMAT_LPWSTR ", "
                FORMAT_DWORD
                ", " FORMAT_LPVOID ", " FORMAT_DWORD ", " FORMAT_LPVOID
                ", " FORMAT_LPVOID ")\n",
                (pszServer!=NULL) ? pszServer : (LPWSTR) L"(local)",
                uLevel, pbBuf, cbBuf, pcReturned, pcTotal));
    }

    if (pszServer && *pszServer) {
        rc = RxPrintDestEnum(pszServer, uLevel, pbBuf, cbBuf,
                               &cReturned, &cTotal);
        *pcReturned = (USHORT)cReturned;
        *pcTotal = (USHORT)cTotal;
        return rc;
    }

    // Stub for local dest enum - no entries, dest not found.
    *pcReturned = 0;
    *pcTotal = 0;
    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintDestControlW(
            LPWSTR  pszServer,
            LPWSTR  pszDevName,
            WORD    uControl
)
{
    if (pszServer && *pszServer)
        return RxPrintDestControl(pszServer, pszDevName, uControl);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintDestEnumW(NULL, "
                FORMAT_LPWSTR ", " FORMAT_DWORD ")\n", pszDevName, uControl));
    }

    return ERROR_NOT_SUPPORTED;
}


SPLERR SPLENTRY DosPrintDestGetInfoW(
            LPWSTR  pszServer,
            LPWSTR  pszName,
            WORD    uLevel,
            PBYTE   pbBuf,
            WORD    cbBuf,
            PUSHORT pcbNeeded
)
{
    DWORD   cbNeeded, rc;

    if (pszServer && *pszServer) {
        rc = RxPrintDestGetInfo(pszServer, pszName, uLevel, pbBuf,
                                  cbBuf, &cbNeeded);
        *pcbNeeded = (USHORT)cbNeeded;
        return rc;
    }

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintDestGetInfoW(NULL, "
                FORMAT_LPWSTR ", " FORMAT_DWORD ", " FORMAT_LPVOID
                ", " FORMAT_DWORD ", " FORMAT_LPVOID ")\n",
                pszName, uLevel, pbBuf, cbBuf, pcbNeeded));
    }

    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintDestAddW(
            LPWSTR  pszServer,
            WORD    uLevel,
            PBYTE   pbBuf,
            WORD    cbBuf
)
{
    if (pszServer && *pszServer)
        return RxPrintDestAdd(pszServer, uLevel, pbBuf, cbBuf);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintDestAddW(NULL, "
                FORMAT_DWORD ", " FORMAT_LPVOID ", " FORMAT_DWORD ")\n",
                uLevel, pbBuf, cbBuf));
    }

    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintDestSetInfoW(
            LPWSTR  pszServer,
            LPWSTR  pszName,
            WORD    uLevel,
            PBYTE   pbBuf,
            WORD    cbBuf,
            WORD    uParmNum
)
{
    if (pszServer && *pszServer)
        return RxPrintDestSetInfo(pszServer, pszName, uLevel, pbBuf,
                                  cbBuf, uParmNum);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintDestSetInfoW(NULL, " FORMAT_LPWSTR
                ", " FORMAT_DWORD ", " FORMAT_LPVOID ", " FORMAT_DWORD
                ", " FORMAT_DWORD ")\n",
                pszName, uLevel, pbBuf, cbBuf, uParmNum));
    }

    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintDestDelW(
            LPWSTR  pszServer,
            LPWSTR  pszPrinterName
)
{
    if (pszServer && *pszServer)
        return RxPrintDestDel(pszServer, pszPrinterName);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintDestDelW(NULL, "
                FORMAT_LPWSTR ")\n",
                pszPrinterName));
    }

    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintQEnumW(
            LPWSTR  pszServer,
            WORD    uLevel,
            PBYTE   pbBuf,
            WORD    cbBuf,
            PUSHORT pcReturned,
            PUSHORT pcTotal
)
{
    DWORD               cJobsReturned;
    DWORD               Total, cbNeeded, rc;
    HANDLE              hPrinter = INVALID_HANDLE_VALUE;
    DWORD               i;
    DWORD               JobFixedEntrySize = 0;
    DWORD               JobLevel;
    LPSHARE_INFO_1      pShareInfo = NULL;
    DWORD               cbPrinter;
    LPPRINTER_INFO_2    pPrinter = NULL;
    BOOL                BufferTooSmall=FALSE;
    DWORD               cReturned, cTotal;
#if DBG
    LPVOID              OutputBufferStart = pbBuf;
#endif
    LPVOID              pEnd;
    LPWSTR              PrinterNameW = NULL;
    DWORD               SharesRead;

    if ( !NetpIsPrintQLevelValid( uLevel, FALSE ) ) {
        rc = ERROR_INVALID_LEVEL;
        goto Cleanup;
    }

    if (pszServer && *pszServer) {
        rc = RxPrintQEnum(pszServer, uLevel, pbBuf, cbBuf, &cReturned, &cTotal);
        *pcReturned = (USHORT)cReturned;
        *pcTotal = (USHORT)cTotal;
        goto Cleanup;
    }

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintQEnumW(NULL, "
                FORMAT_DWORD ", " FORMAT_LPVOID ", " FORMAT_DWORD ", "
                FORMAT_LPVOID ", " FORMAT_LPVOID ")\n",
                uLevel, pbBuf, cbBuf, pcReturned, pcTotal));
    }

    *pcReturned = 0;
    *pcTotal = 0;

    rc=NetShareEnum(
            NULL,
            1,
            (LPBYTE *)(LPVOID)&pShareInfo,
            MAX_PREFERRED_LENGTH,
            &SharesRead,
            &Total,
            NULL);
    if (rc != NO_ERROR) {

        NetpKdPrint((PREFIX_DOSPRINT "DosPrintQEnumW: NetShareEnum returned "
                FORMAT_API_STATUS "\n", rc));
        goto Cleanup;
    }

    pEnd = (pbBuf + cbBuf);

    if (uLevel==2) {
        JobLevel = 1;
        JobFixedEntrySize = PrjInfoFixedSizeW( JobLevel );
    } else if (uLevel == 4) {
        JobLevel = 2;
        JobFixedEntrySize = PrjInfoFixedSizeW( JobLevel );
    }

    for (i=0; i<SharesRead; i++) {

        if (pShareInfo[i].shi1_type != STYPE_PRINTQ) {
            continue;
        }

        NetpAssert( pShareInfo[i].shi1_netname != NULL );
        NetpAssert( (*pShareInfo[i].shi1_netname) != L'\0' );
        if (STRLEN( pShareInfo[i].shi1_netname ) > LM20_QNLEN) {
            IF_DEBUG( DOSPRTW ) {
                NetpKdPrint(( PREFIX_DOSPRINT
                        "DosPrintQEnumW: ignoring long share name of '"
                        FORMAT_LPTSTR "'.\n",
                        pShareInfo[i].shi1_netname ));
            }
            continue;
        }

        rc = QueueNameToPrinterNameW(
                pShareInfo[i].shi1_netname,
                & PrinterNameW );
        if (rc != NO_ERROR) {
            goto Cleanup;
        }
        NetpAssert( PrinterNameW != NULL );

        if ( !MyOpenPrinterW(PrinterNameW, &hPrinter, NULL)) {
            rc = (NET_API_STATUS) GetLastError();
            NetpKdPrint(( PREFIX_DOSPRINT
                    "DosPrintQEnumW: MyOpenPrinter failed, status "
                    FORMAT_API_STATUS ".\n", rc ));
            NetpAssert( rc != NO_ERROR );
            goto Cleanup;
        }
        NetpAssert( hPrinter != INVALID_HANDLE_VALUE );

        if (!MyGetPrinter(hPrinter, 2, NULL, 0, &cbPrinter)) {

            if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
                rc = (NET_API_STATUS) GetLastError();
                NetpKdPrint(( PREFIX_DOSPRINT
                        "DosPrintQEnumW: MyGetPrinter(first) failed, status "
                        FORMAT_API_STATUS ".\n", rc ));
                NetpAssert( rc != NO_ERROR );
                goto Cleanup;
            }
        }
        NetpAssert( cbPrinter != 0 );

        pPrinter = (LPVOID) GlobalAlloc(GMEM_FIXED, cbPrinter);
        if (pPrinter == NULL) {
            rc = ERROR_NOT_ENOUGH_MEMORY;
            goto Cleanup;
        }

        if ( !MyGetPrinter(hPrinter, 2, (LPBYTE)pPrinter,
                cbPrinter, &cbPrinter)) {
            rc = (NET_API_STATUS) GetLastError();
            NetpKdPrint(( PREFIX_DOSPRINT
                    "DosPrintQEnumW: MyGetPrinter(second) failed, status "
                    FORMAT_API_STATUS ".\n", rc ));
            NetpAssert( rc != NO_ERROR );
            goto Cleanup;
        }

        cbNeeded=GetPrqInfoSizeW(uLevel,
                pShareInfo[i].shi1_netname, // Q nam
                pPrinter);
        NetpAssert( cbNeeded > 0 );
        NetpAssert( cbNeeded <= (DWORD) MAX_WORD );

        if ( (!BufferTooSmall) && ((DWORD)cbBuf >= cbNeeded) ) {

            LPVOID pbQueue = pbBuf;
            //
            // Handle queue structure itself.
            //
            pEnd = CopyPrinterToPrqInfoW(pPrinter,
                      uLevel,
                      pbBuf,
                      pShareInfo[i].shi1_netname,
                      pEnd);

            pbBuf += PrqInfoFixedSizeW( uLevel );
            cbBuf -= (WORD) cbNeeded;

            //
            // Append job structures if needed.
            //
            if ( (uLevel==2) || (uLevel==4) ) {    // info level includes jobs

                NetpAssert( pbBuf < (LPBYTE) pEnd );
                rc = AppendJobsToPrqW(
                        pShareInfo[i].shi1_netname,
                        uLevel,    // Q info level
                        hPrinter,
                        pPrinter,
                        pbBuf,  // first job here
                        cbBuf,  // bytes avail
                        pEnd,   // str area
                        & pEnd, // set new end ptr
                        & cbNeeded,
                        & cJobsReturned );
                if (rc == NERR_BufTooSmall) {
                    BufferTooSmall = TRUE;  // continue, as we need pcTotal...
                } else if (rc != NO_ERROR) {
                    goto Cleanup;
                } else {  // Must be NO_ERROR.
                    NetpAssert( cbNeeded <= (DWORD) MAX_WORD );
                    NetpAssert( pbBuf < (LPBYTE) pEnd );
                    NetpAssert( JobFixedEntrySize !=0 );

                    pbBuf += (JobFixedEntrySize * cJobsReturned);
                    cbBuf -= (WORD) cbNeeded;
                    (*pcReturned)++;

                    // Correct possible out of date
                    // job count in queue structure.
                    NetpSetJobCountForQueue(
                            uLevel,
                            pbQueue,
                            TRUE, // yes, UNICODE strs
                            cJobsReturned );
                }

            } else {  // info level does not include jobs

                (*pcReturned)++;
            }

        } else {  // not enough mem for Q struct

            BufferTooSmall = TRUE;
            // Continue, as we want to compute pcTotal for subsequent queues.
        }

        (*pcTotal)++;

        NetpAssert( pPrinter != NULL );
        (VOID) GlobalFree(pPrinter);
        pPrinter = NULL;

        NetpAssert( hPrinter != INVALID_HANDLE_VALUE );
        (VOID) MyClosePrinter(hPrinter);
        hPrinter = INVALID_HANDLE_VALUE;

        NetpAssert( PrinterNameW != NULL );
        (VOID) NetApiBufferFree( PrinterNameW );
        PrinterNameW = NULL;

    } // for each share

Cleanup:

#if DBG
    IF_DEBUG( DOSPRTW ) {
        if (rc == NO_ERROR) {
            NetpKdPrint((PREFIX_DOSPRINT
                    "DosPrintQEnumW: returning array:\n" ));
            NetpDbgDisplayPrintQArray( uLevel,
                    OutputBufferStart,
                    *pcReturned, TRUE );
        }
    }
#endif

    if (hPrinter != INVALID_HANDLE_VALUE) {
        (VOID) MyClosePrinter( hPrinter );
    }
    if (pPrinter != NULL) {
        (VOID) GlobalFree( pPrinter );
    }
    if (pShareInfo != NULL) {
        (VOID) NetApiBufferFree(pShareInfo);
    }

    if (PrinterNameW != NULL) {
        (VOID) NetApiBufferFree( PrinterNameW );
    }

    if (BufferTooSmall) {
        rc = NERR_BufTooSmall;
    }

    return (rc);
}

SPLERR SPLENTRY DosPrintQSetInfoW(
            LPWSTR  pszServer,
            LPWSTR  pszQueueName,
            WORD    uLevel,
            PBYTE   pbBuf,
            WORD    cbBuf,
            WORD    uParmNum
)
{
    if (pszServer && *pszServer)
        return RxPrintQSetInfo(pszServer, pszQueueName, uLevel, pbBuf,
                               cbBuf, uParmNum);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintQSetInfoW(NULL, " FORMAT_LPWSTR
                ", " FORMAT_DWORD ", " FORMAT_LPVOID ", " FORMAT_DWORD ", "
                FORMAT_DWORD ")\n",
                pszQueueName, uLevel, pbBuf, cbBuf, uParmNum));
    }

    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintQPauseW(
            LPWSTR  pszServer,
            LPWSTR  pszQueueName
)
{
    if (pszServer && *pszServer)
        return RxPrintQPause(pszServer, pszQueueName);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintQPauseW(NULL, "
                FORMAT_LPWSTR ")\n",
                pszQueueName));
    }
    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintQContinueW(
            LPWSTR  pszServer,
            LPWSTR  pszQueueName
)
{
    if (pszServer && *pszServer)
        return RxPrintQContinue(pszServer, pszQueueName);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintQContinueW(NULL, "
                FORMAT_LPWSTR ")\n", pszQueueName));
    }
    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintQPurgeW(
            LPWSTR  pszServer,
            LPWSTR  pszQueueName
)
{
    if (pszServer && *pszServer)
        return RxPrintQPurge(pszServer, pszQueueName);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintQPurgeW(NULL, "
                FORMAT_LPWSTR ")\n",
                pszQueueName));
    }
    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintQAddW(
            LPWSTR  pszServer,
            WORD    uLevel,
            PBYTE   pbBuf,
            WORD    cbBuf
)
{
    if (pszServer && *pszServer)
        return RxPrintQAdd(pszServer, uLevel, pbBuf, cbBuf);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintQAddW(NULL, " FORMAT_DWORD ", "
                FORMAT_LPVOID ", " FORMAT_DWORD ")\n",
                uLevel, pbBuf, cbBuf));
    }

    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintQDelW(
            LPWSTR  pszServer,
            LPWSTR  pszQueueName
)
{
    if (pszServer && *pszServer)
        return RxPrintQDel(pszServer, pszQueueName);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintQDelW(NULL, " FORMAT_LPWSTR ")\n",
                pszQueueName));
    }

    return ERROR_NOT_SUPPORTED;
}

SPLERR SPLENTRY DosPrintJobSetInfoW(
            LPWSTR  pszServer,
            WORD    uJobId,
            WORD    uLevel,
            PBYTE   pbBuf,
            WORD    cbBuf,
            WORD    uParmNum
)
{
    if (pszServer && *pszServer)
        return RxPrintJobSetInfo(pszServer, uJobId, uLevel, pbBuf,
                                 cbBuf, uParmNum);

    IF_DEBUG( DOSPRTW ) {
        NetpKdPrint((PREFIX_DOSPRINT "DosPrintJobSetInfoW(NULL, " FORMAT_DWORD
                ", " FORMAT_DWORD ", " FORMAT_LPVOID ", " FORMAT_DWORD
                ", " FORMAT_DWORD ")\n",
                uJobId, uLevel, pbBuf, cbBuf, uParmNum));
    }

    return ERROR_NOT_SUPPORTED;
}

