/*++

Copyright (c) 1992-1993  Microsoft Corporation

Module Name:

    ConvPrt.c

Abstract:

    This module contains:

        NetpConvertPrintDestArrayCharSet
        NetpConvertPrintDestCharSet
        NetpConvertPrintJobArrayCharSet
        NetpConvertPrintJobCharSet
        NetpConvertPrintQArrayCharSet
        NetpConvertPrintQCharSet

    This routines may be used for UNICODE-to-ANSI conversion, or
    ANSI-to-UNICODE conversion.  The routines assume the structures are
    in native format for both input and output.

Author:

    John Rogers (JohnRo) 20-Jul-1992

Environment:

    Portable to any flat, 32-bit environment.  (Uses Win32 typedefs.)
    Requires ANSI C extensions: slash-slash comments, long external names.

Notes:

    Beware that many of the parameters to the functions in this file
    are implicitly used by the various COPY_ and CONVERT_ macros:

        IN LPVOID FromInfo
        OUT LPVOID ToInfo
        IN BOOL ToUnicode
        IN OUT LPBYTE * ToStringAreaPtr

Revision History:

    20-Jul-1992 JohnRo
        Created for RAID 10324: net print vs. UNICODE.
    16-Dec-1992 JohnRo
        DosPrint API cleanup.
        Allow use of these routines for setinfo APIs.
        Added NetpConvertPrintQArrayCharSet.
    07-Apr-1993 JohnRo
        RAID 5670: "NET PRINT \\server\share" gives err 124 (bad level) on NT.
    14-Apr-1993 JohnRo
        RAID 6167: avoid _access violation or assert with WFW print server.

--*/

// These must be included first:

#include <windef.h>     // IN, DWORD, etc.
#include <lmcons.h>     // NET_API_STATUS.

// These may be included in any order:

#include <align.h>      // POINTER_IS_ALIGNED(), ALIGN_ equates.
#include <debuglib.h>   // IF_DEBUG().
#include <netdebug.h>   // NetpAssert(), NetpKdPrint(()), etc.
#include <prefix.h>     // PREFIX_ equates.
#include <rxprint.h>    // My prototypes, NetpJobCountForQueue().
#include <string.h>     // memcpy().
#include <strucinf.h>   // My prototypes.
#include <tstring.h>    // NetpCopy{type}To{type}().


#define COPY_DWORD( typedefRoot, fieldName ) \
    { \
        if (ToUnicode) { \
            P ## typedefRoot ## A srcStruct = FromInfo; \
            P ## typedefRoot ## W destStruct = ToInfo; \
            NetpAssert( sizeof( destStruct->fieldName ) == sizeof( DWORD ) ); \
            NetpAssert( sizeof(  srcStruct->fieldName ) == sizeof( DWORD ) ); \
            destStruct->fieldName = srcStruct->fieldName; \
        } else { \
            P ## typedefRoot ## W srcStruct = FromInfo; \
            P ## typedefRoot ## A destStruct = ToInfo; \
            NetpAssert( sizeof( destStruct->fieldName ) == sizeof( DWORD ) ); \
            NetpAssert( sizeof(  srcStruct->fieldName ) == sizeof( DWORD ) ); \
            destStruct->fieldName = srcStruct->fieldName; \
        } \
    }

#define COPY_WORD( typedefRoot, fieldName ) \
    { \
        if (ToUnicode) { \
            P ## typedefRoot ## A srcStruct = FromInfo; \
            P ## typedefRoot ## W destStruct = ToInfo; \
            NetpAssert( sizeof( destStruct->fieldName ) == sizeof( WORD ) ); \
            NetpAssert( sizeof(  srcStruct->fieldName ) == sizeof( WORD ) ); \
            destStruct->fieldName = srcStruct->fieldName; \
        } else { \
            P ## typedefRoot ## W srcStruct = FromInfo; \
            P ## typedefRoot ## A destStruct = ToInfo; \
            NetpAssert( sizeof( destStruct->fieldName ) == sizeof( WORD ) ); \
            NetpAssert( sizeof(  srcStruct->fieldName ) == sizeof( WORD ) ); \
            destStruct->fieldName = srcStruct->fieldName; \
        } \
    }

#define COPY_FIXED_PART_WITHOUT_STRUCT( dataType ) \
    { \
        (VOID) memcpy( \
                ToInfo,   /* dest */ \
                FromInfo, /* src */ \
                sizeof( dataType ) );  /* size */ \
    }

#define CONVERT_CHAR_ARRAY( typedefRoot, fieldName ) \
    { \
        if (ToUnicode) { \
            P ## typedefRoot ## A structA = FromInfo; \
            P ## typedefRoot ## W structW = ToInfo; \
            NetpCopyStrToWStr( \
                    structW->fieldName,  /* dest */ \
                    structA->fieldName); /* src */ \
        } else { \
            P ## typedefRoot ## A structA = ToInfo; \
            P ## typedefRoot ## W structW = FromInfo; \
            NetpCopyWStrToStrDBCSN( \
                    structA->fieldName,         /* dest */ \
                    structW->fieldName,         /* src */ \
                    sizeof(structA->fieldName));/*max bytes to copy*/ \
        } \
    }

#define CONVERT_OPTIONAL_STRING( typedefRoot, fieldName ) \
    { \
        NetpAssert( ToStringAreaPtr != NULL ); \
        NetpAssert( (*ToStringAreaPtr) != NULL ); \
        if (ToUnicode) { \
            P ## typedefRoot ## A structA = FromInfo; \
            P ## typedefRoot ## W structW = ToInfo; \
            LPSTR Src = structA->fieldName; \
            NetpAssert( POINTER_IS_ALIGNED(*ToStringAreaPtr, ALIGN_WCHAR) ); \
            if (Src == NULL) { \
                structW->fieldName = NULL; \
            } else { \
                LPWSTR Dest; \
                DWORD DestSize; \
                DestSize = (strlen(Src)+1) * sizeof(WCHAR); \
                Dest = (LPVOID) ( (*ToStringAreaPtr) - DestSize ); \
                *ToStringAreaPtr = (LPVOID) Dest; \
                structW->fieldName = Dest; \
                NetpCopyStrToWStr( Dest, Src ); \
            } \
        } else { \
            P ## typedefRoot ## W structW = FromInfo; \
            P ## typedefRoot ## A structA = ToInfo; \
            LPWSTR Src = structW->fieldName; \
            if (Src == NULL) { \
                structA->fieldName = NULL; \
            } else { \
                LPSTR Dest; \
                DWORD DestSize; \
                DestSize = (NetpUnicodeToDBCSLen(Src)+1); \
                Dest = (LPVOID) ( (*ToStringAreaPtr) - DestSize ); \
                *ToStringAreaPtr = (LPVOID) Dest; \
                structA->fieldName = Dest; \
                NetpCopyWStrToStrDBCS( Dest, Src ); \
            } \
        } \
    }

#define CONVERT_OPTIONAL_STRING_TO_REQ( typedefRoot, fieldName ) \
    { \
        NetpAssert( ToStringAreaPtr != NULL ); \
        NetpAssert( (*ToStringAreaPtr) != NULL ); \
        if (ToUnicode) { \
            P ## typedefRoot ## A structA = FromInfo; \
            P ## typedefRoot ## W structW = ToInfo; \
            LPWSTR Dest; \
            DWORD DestSize; \
            LPSTR Src = structA->fieldName; \
            NetpAssert( POINTER_IS_ALIGNED(*ToStringAreaPtr, ALIGN_WCHAR) ); \
            if (Src == NULL) { \
                Src = ""; \
            } \
            DestSize = (strlen(Src)+1) * sizeof(WCHAR); \
            Dest = (LPVOID) ( (*ToStringAreaPtr) - DestSize ); \
            *ToStringAreaPtr = (LPVOID) Dest; \
            structW->fieldName = Dest; \
            NetpCopyStrToWStr( Dest, Src ); \
        } else { \
            P ## typedefRoot ## A structA = ToInfo; \
            P ## typedefRoot ## W structW = FromInfo; \
            LPSTR Dest; \
            DWORD DestSize; \
            LPWSTR Src = structW->fieldName; \
            if (Src == NULL) { \
                Src = L""; \
            } \
            DestSize = (NetpUnicodeToDBCSLen(Src)+1); \
            Dest = (LPVOID) ( (*ToStringAreaPtr) - DestSize ); \
            *ToStringAreaPtr = (LPVOID) Dest; \
            structA->fieldName = Dest; \
            NetpCopyWStrToStrDBCS( Dest, Src ); \
        } \
    }

#define CONVERT_CHAR_ARRAY_WITHOUT_STRUCT( ) \
    { \
        if (ToUnicode) { \
            NetpCopyStrToWStr( ToInfo, FromInfo ); \
        } else { \
            NetpCopyWStrToStrDBCS( ToInfo, FromInfo ); \
        } \
    }

#define CONVERT_CHAR_PTR_WITHOUT_STRUCT( ) \
    { \
        if (ToUnicode) { \
            NetpCopyStrToWStr( ToInfo, FromInfo ); \
        } else { \
            NetpCopyWStrToStrDBCS( ToInfo, FromInfo ); \
        } \
    }

NET_API_STATUS
NetpConvertPrintDestCharSet(
    IN     DWORD    Level,
    IN     BOOL     AddOrSetInfoApi,
    IN     LPVOID   FromInfo,
    OUT    LPVOID   ToInfo,
    IN     BOOL     ToUnicode,
    IN OUT LPBYTE * ToStringAreaPtr OPTIONAL
    )
{
    IF_DEBUG( CONVPRT ) {
        NetpKdPrint(( PREFIX_NETLIB "NetpConvertPrintDestCharSet: "
                "level " FORMAT_DWORD ":\n", Level ));
    }

    if ( (FromInfo == NULL) || (ToInfo == NULL) ) {
        return (ERROR_INVALID_PARAMETER);
    }

    switch (Level) {

    case 0 :
        //
        // No structure for this level.
        // Only field is name, which is in the fixed part itself.
        //
        CONVERT_CHAR_ARRAY_WITHOUT_STRUCT( );

        break;

    case 1 :
        CONVERT_CHAR_ARRAY(      PRDINFO, szName );
        CONVERT_CHAR_ARRAY(      PRDINFO, szUserName );
        COPY_WORD(               PRDINFO, uJobId );
        COPY_WORD(               PRDINFO, fsStatus );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRDINFO, pszStatus );
        COPY_WORD(               PRDINFO, time );

        break;

    case 2 :
        //
        // No structure for this level.
        // Only field is pointer to name.
        //
        CONVERT_CHAR_PTR_WITHOUT_STRUCT( );

        break;

    case 3 :
        CONVERT_OPTIONAL_STRING_TO_REQ( PRDINFO3, pszPrinterName );
        CONVERT_OPTIONAL_STRING( PRDINFO3, pszUserName );
        CONVERT_OPTIONAL_STRING( PRDINFO3, pszLogAddr );
        COPY_WORD(               PRDINFO3, uJobId );
        COPY_WORD(               PRDINFO3, fsStatus );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRDINFO3, pszStatus );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRDINFO3, pszComment );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRDINFO3, pszDrivers );
        COPY_WORD(               PRDINFO3, time );
        // No need to copy pad1.

        break;

    default :
        return (ERROR_INVALID_LEVEL);
    }

    IF_DEBUG( CONVPRT ) {
        NetpKdPrint(( PREFIX_NETLIB
                "NetpConvertPrintDest: converted charset:\n" ));
        NetpDbgDisplayPrintDest( Level, ToInfo, ToUnicode );
    }

    return (NO_ERROR);

} // NetpConvertPrintDestCharSet


NET_API_STATUS
NetpConvertPrintDestArrayCharSet(
    IN     DWORD    Level,
    IN     BOOL     AddOrSetInfoApi,
    IN     LPVOID   FromInfo,
    OUT    LPVOID   ToInfo,
    IN     BOOL     ToUnicode,
    IN OUT LPBYTE * ToStringAreaPtr OPTIONAL,
    IN     DWORD    DestCount
    )
{
    NET_API_STATUS ApiStatus;
    DWORD DestsLeft;
    DWORD FromEntrySize, ToEntrySize;
    LPVOID FromDest = FromInfo;
    LPVOID ToDest   = ToInfo;

    if ( (FromInfo == NULL) || (ToInfo == NULL) ) {
        return (ERROR_INVALID_PARAMETER);
    }

    ApiStatus = NetpPrintDestStructureInfo (
            Level,
            PARMNUM_ALL,
            TRUE,              // yes, we want native sizes.
            AddOrSetInfoApi,
            (ToUnicode ? sizeof(CHAR) : sizeof(WCHAR) ),  // FROM char size
            NULL,              // don't need data desc 16
            NULL,              // don't need data desc 32
            NULL,              // don't need data desc SMB
            NULL,              // don't need max total size
            & FromEntrySize,   // yes, we want fixed entry size
            NULL );            // don't need string size
    if (ApiStatus != NO_ERROR) {
        return (ApiStatus);
    }
    NetpAssert( FromEntrySize > 0 );

    ApiStatus = NetpPrintDestStructureInfo (
            Level,
            PARMNUM_ALL,
            TRUE,              // yes, we want native sizes.
            AddOrSetInfoApi,
            (ToUnicode ? sizeof(WCHAR) : sizeof(CHAR) ),  // TO char size
            NULL,              // don't need data desc 16
            NULL,              // don't need data desc 32
            NULL,              // don't need data desc SMB
            NULL,              // don't need max total size
            & ToEntrySize,     // yes, we want fixed entry size
            NULL );            // don't need string size
    NetpAssert( ApiStatus == NO_ERROR );
    NetpAssert( ToEntrySize > 0 );

    for (DestsLeft = DestCount; DestsLeft>0; --DestsLeft) {

        ApiStatus = NetpConvertPrintDestCharSet(
                Level,   // info level (for print Dest APIs)
                AddOrSetInfoApi,
                FromDest,
                ToDest,
                ToUnicode,
                ToStringAreaPtr ); // update and move string area
        NetpAssert( ApiStatus == NO_ERROR );  // BUGBUG

        FromDest = (((LPBYTE) FromDest) + FromEntrySize);
        ToDest   = (((LPBYTE) ToDest)   + ToEntrySize  );
    }

    return (NO_ERROR);

} // NetpConvertPrintDestArrayCharSet


NET_API_STATUS
NetpConvertPrintJobCharSet(
    IN     DWORD    Level,
    IN     BOOL     AddOrSetInfoApi,
    IN     LPVOID   FromInfo,
    OUT    LPVOID   ToInfo,
    IN     BOOL     ToUnicode,
    IN OUT LPBYTE * ToStringAreaPtr OPTIONAL
    )
{
    IF_DEBUG( CONVPRT ) {
        NetpKdPrint(( PREFIX_NETLIB "NetpConvertPrintJobCharSet: "
                "level " FORMAT_DWORD ":\n", Level ));
    }

    if ( (FromInfo == NULL) || (ToInfo == NULL) ) {
        return (ERROR_INVALID_PARAMETER);
    }

    switch (Level) {
    case 0 :
        COPY_FIXED_PART_WITHOUT_STRUCT( WORD );

        break;

    case 1 :
        COPY_WORD(               PRJINFO, uJobId );
        CONVERT_CHAR_ARRAY(      PRJINFO, szUserName );
        CONVERT_CHAR_ARRAY(      PRJINFO, szNotifyName );
        CONVERT_CHAR_ARRAY(      PRJINFO, szDataType );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO, pszParms );
        COPY_WORD(               PRJINFO, uPosition );
        COPY_WORD(               PRJINFO, fsStatus );
        CONVERT_OPTIONAL_STRING( PRJINFO, pszStatus );
        COPY_DWORD(              PRJINFO, ulSubmitted );
        COPY_DWORD(              PRJINFO, ulSize );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO, pszComment );

        break;

    case 2 :

        COPY_WORD(               PRJINFO2, uJobId );
        COPY_WORD(               PRJINFO2, uPriority );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO2, pszUserName );
        COPY_WORD(               PRJINFO2, uPosition );
        COPY_WORD(               PRJINFO2, fsStatus );
        COPY_DWORD(              PRJINFO2, ulSubmitted );
        COPY_DWORD(              PRJINFO2, ulSize );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO2, pszComment );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO2, pszDocument );

        break;

    case 3 :
        COPY_WORD(               PRJINFO3, uJobId );
        COPY_WORD(               PRJINFO3, uPriority );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO3, pszUserName );
        COPY_WORD(               PRJINFO3, uPosition );
        COPY_WORD(               PRJINFO3, fsStatus );
        COPY_DWORD(              PRJINFO3, ulSubmitted );
        COPY_DWORD(              PRJINFO3, ulSize );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO3, pszComment );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO3, pszDocument );
        CONVERT_OPTIONAL_STRING( PRJINFO3, pszNotifyName );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO3, pszDataType );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO3, pszParms );
        CONVERT_OPTIONAL_STRING( PRJINFO3, pszStatus );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO3, pszQueue );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRJINFO3, pszQProcName );
        CONVERT_OPTIONAL_STRING( PRJINFO3, pszDriverName );

#if DBG
        {
            if (ToUnicode) {
                PPRJINFO3A p3 = FromInfo;
                NetpAssert( p3->pDriverData == NULL );
            } else {
                PPRJINFO3W p3 = FromInfo;
                NetpAssert( p3->pDriverData == NULL );
            }
        }
#endif

        CONVERT_OPTIONAL_STRING( PRJINFO3, pszPrinterName );

        break;

    default :
        return (ERROR_INVALID_LEVEL);
    }

    IF_DEBUG( CONVPRT ) {
        NetpKdPrint(( PREFIX_NETLIB
                "NetpConvertPrintJob: converted charset:\n" ));
        NetpDbgDisplayPrintJob( Level, ToInfo, ToUnicode );
    }

    return (NO_ERROR);

} // NetpConvertPrintJobCharSet


NET_API_STATUS
NetpConvertPrintJobArrayCharSet(
    IN     DWORD    Level,
    IN     BOOL     AddOrSetInfoApi,
    IN     LPVOID   FromInfo,
    OUT    LPVOID   ToInfo,
    IN     BOOL     ToUnicode,
    IN OUT LPBYTE * ToStringAreaPtr OPTIONAL,
    IN     DWORD    JobCount
    )
{
    NET_API_STATUS ApiStatus;
    DWORD FromEntrySize, ToEntrySize;
    LPVOID FromJob = FromInfo;   // job structure
    DWORD JobsLeft;
    LPVOID ToJob = ToInfo;   // job structure

    if ( (FromInfo == NULL) || (ToInfo == NULL) ) {
        return (ERROR_INVALID_PARAMETER);
    }

    ApiStatus = NetpPrintJobStructureInfo (
            Level,
            PARMNUM_ALL,
            TRUE,              // yes, we want native sizes.
            AddOrSetInfoApi,
            (ToUnicode ? sizeof(CHAR) : sizeof(WCHAR) ),  // FROM char size
            NULL,              // don't need data desc 16
            NULL,              // don't need data desc 32
            NULL,              // don't need data desc SMB
            NULL,              // don't need max total size
            & FromEntrySize,   // yes, we want fixed entry size
            NULL );            // don't need string size
    if (ApiStatus != NO_ERROR) {
        return (ApiStatus);
    }
    NetpAssert( FromEntrySize > 0 );

    ApiStatus = NetpPrintJobStructureInfo (
            Level,
            PARMNUM_ALL,
            TRUE,              // yes, we want native sizes.
            AddOrSetInfoApi,
            (ToUnicode ? sizeof(WCHAR) : sizeof(CHAR) ),  // TO char size
            NULL,              // don't need data desc 16
            NULL,              // don't need data desc 32
            NULL,              // don't need data desc SMB
            NULL,              // don't need max total size
            & ToEntrySize,     // yes, we want fixed entry size
            NULL );            // don't need string size
    NetpAssert( ApiStatus == NO_ERROR );
    NetpAssert( ToEntrySize > 0 );

    for (JobsLeft = JobCount; JobsLeft>0; --JobsLeft) {

        ApiStatus = NetpConvertPrintJobCharSet(
                Level,   // info level (for print job APIs)
                AddOrSetInfoApi,
                FromJob,
                ToJob,
                ToUnicode,
                ToStringAreaPtr ); // update and move string area
        NetpAssert( ApiStatus == NO_ERROR );  // BUGBUG


        FromJob = (((LPBYTE) FromJob) + FromEntrySize);
        ToJob   = (((LPBYTE) ToJob  ) + ToEntrySize  );

        if ((LPBYTE)*ToStringAreaPtr < (LPBYTE)ToJob)
            return (ERROR_MORE_DATA) ;
    }

    return (NO_ERROR);

} // NetpConvertPrintJobArrayCharSet



NET_API_STATUS
NetpConvertPrintQCharSet(
    IN     DWORD    Level,
    IN     BOOL     AddOrSetInfoApi,
    IN     LPVOID   FromInfo,
    OUT    LPVOID   ToInfo,
    IN     BOOL     ToUnicode,
    IN OUT LPBYTE * ToStringAreaPtr OPTIONAL
    )
{
    NET_API_STATUS ApiStatus;
    DWORD FromEntrySize, ToEntrySize;

    IF_DEBUG( CONVPRT ) {
        NetpKdPrint(( PREFIX_NETLIB "NetpConvertPrintQCharSet: "
                "level " FORMAT_DWORD ":\n", Level ));
    }

    if ( (FromInfo == NULL) || (ToInfo == NULL) ) {
        return (ERROR_INVALID_PARAMETER);
    }

    ApiStatus = NetpPrintQStructureInfo (
            Level,
            PARMNUM_ALL,
            TRUE,              // yes, we want native sizes.
            AddOrSetInfoApi,
            (ToUnicode ? sizeof(WCHAR) : sizeof(CHAR) ),  // TO char size
            NULL,              // don't need data desc 16
            NULL,              // don't need data desc 32
            NULL,              // don't need data desc SMB
            NULL,              // don't need aux desc 16
            NULL,              // don't need aux desc 32
            NULL,              // don't need aux desc SMB
            NULL,              // don't need max total size
            & ToEntrySize,     // yes, we want fixed entry size
            NULL );            // don't need string size
    if (ApiStatus != NO_ERROR) {
        return (ApiStatus);
    }
    NetpAssert( ToEntrySize > 0 );

    ApiStatus = NetpPrintQStructureInfo (
            Level,
            PARMNUM_ALL,
            TRUE,              // yes, we want native sizes.
            AddOrSetInfoApi,
            (ToUnicode ? sizeof(CHAR) : sizeof(WCHAR) ),  // FROM char size
            NULL,              // don't need data desc 16
            NULL,              // don't need data desc 32
            NULL,              // don't need data desc SMB
            NULL,              // don't need aux desc 16
            NULL,              // don't need aux desc 32
            NULL,              // don't need aux desc SMB
            NULL,              // don't need max total size
            & FromEntrySize,     // yes, we want fixed entry size
            NULL );            // don't need string size
    NetpAssert( ApiStatus == NO_ERROR );
    NetpAssert( FromEntrySize > 0 );

    switch (Level) {

    case 0 :
        //
        // No structure for this level.
        // Only field is queue name, which is in the fixed part itself.
        //
        CONVERT_CHAR_ARRAY_WITHOUT_STRUCT( );

        break;

    case 1 :  /*FALLTHROUGH*/
    case 2 :

        CONVERT_CHAR_ARRAY(      PRQINFO, szName );
        // No need to copy pad1.
        COPY_WORD(               PRQINFO, uPriority );
        COPY_WORD(               PRQINFO, uStartTime );
        COPY_WORD(               PRQINFO, uUntilTime );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRQINFO, pszSepFile );
        CONVERT_OPTIONAL_STRING( PRQINFO, pszPrProc );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRQINFO, pszDestinations );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRQINFO, pszParms );
        CONVERT_OPTIONAL_STRING_TO_REQ( PRQINFO, pszComment );
        COPY_WORD(               PRQINFO, fsStatus );
        COPY_WORD(               PRQINFO, cJobs );

        if (Level == 2) {
            NET_API_STATUS ApiStatus;
            LPVOID FromArray, ToArray;  // job structures
            DWORD JobCount;

            if (ToUnicode) {
                PPRQINFOA pq = FromInfo;
                JobCount = pq->cJobs;
            } else {
                PPRQINFOW pq = FromInfo;
                JobCount = pq->cJobs;
            }

            FromArray = ( ((LPBYTE) FromInfo) + FromEntrySize );
            ToArray   = ( ((LPBYTE) ToInfo  ) + ToEntrySize   );

            ApiStatus = NetpConvertPrintJobArrayCharSet(
                    1,   // job info level
                    AddOrSetInfoApi,
                    FromArray,
                    ToArray,
                    ToUnicode,
                    ToStringAreaPtr,   // update and move string area
                    JobCount );
            if ( ApiStatus != NO_ERROR )
                return (ApiStatus) ;
        }

        break;

    case 3 :  /*FALLTHROUGH*/
    case 4 :

        {

            CONVERT_OPTIONAL_STRING_TO_REQ( PRQINFO3, pszName );
            COPY_WORD(               PRQINFO3, uPriority );
            COPY_WORD(               PRQINFO3, uStartTime );
            COPY_WORD(               PRQINFO3, uUntilTime );
            // No need to copy pad3.
            CONVERT_OPTIONAL_STRING_TO_REQ( PRQINFO3, pszSepFile );
            CONVERT_OPTIONAL_STRING( PRQINFO3, pszPrProc );
            CONVERT_OPTIONAL_STRING_TO_REQ( PRQINFO3, pszParms );
            CONVERT_OPTIONAL_STRING_TO_REQ( PRQINFO3, pszComment );
            COPY_WORD(               PRQINFO3, fsStatus );
            COPY_WORD(               PRQINFO3, cJobs );
            CONVERT_OPTIONAL_STRING( PRQINFO3, pszPrinters );
            CONVERT_OPTIONAL_STRING( PRQINFO3, pszDriverName );

#if DBG
            if (ToUnicode) {
                PPRQINFO3A pq = FromInfo;
                NetpAssert( pq->pDriverData == NULL );  // BUGBUG?
            } else {
                PPRQINFO3W pq = FromInfo;
                NetpAssert( pq->pDriverData == NULL );  // BUGBUG?
            }
#endif

            if (Level == 4) {

                NET_API_STATUS ApiStatus;
                LPVOID FromFirstJob,ToFirstJob;   // job structures
                DWORD JobCount;

                FromFirstJob = ( ((LPBYTE) FromInfo) + FromEntrySize );
                ToFirstJob   = ( ((LPBYTE) ToInfo  ) + ToEntrySize   );

                if (ToUnicode) {
                    PPRQINFO3A pq = FromInfo;
                    JobCount = pq->cJobs;
                } else {
                    PPRQINFO3W pq = FromInfo;
                    JobCount = pq->cJobs;
                }

                ApiStatus = NetpConvertPrintJobArrayCharSet(
                        2,  // job info level
                        AddOrSetInfoApi,
                        FromFirstJob,
                        ToFirstJob,
                        ToUnicode,
                        ToStringAreaPtr,
                        JobCount );
                NetpAssert( ApiStatus == NO_ERROR );  // BUGBUG
            }
        }

        break;


    case 5 :
        //
        // No structure for this level.
        // Only field is queue name, which is just a pointer in the fixed part.
        //
        CONVERT_CHAR_PTR_WITHOUT_STRUCT( );

        break;

    default :
        return (ERROR_INVALID_LEVEL);
    }

    IF_DEBUG( CONVPRT ) {
        NetpKdPrint(( PREFIX_NETLIB
                "NetpConvertPrintQ: converted charset:\n" ));
        NetpDbgDisplayPrintQ( Level, ToInfo, ToUnicode );
    }

    return (NO_ERROR);

} // NetpConvertPrintQCharSet


NET_API_STATUS
NetpConvertPrintQArrayCharSet(
    IN     DWORD    QLevel,
    IN     BOOL     AddOrSetInfoApi,
    IN     LPVOID   FromInfo,
    OUT    LPVOID   ToInfo,
    IN     BOOL     ToUnicode,
    IN OUT LPBYTE * ToStringAreaPtr OPTIONAL,
    IN     DWORD    QCount
    )
{
    NET_API_STATUS ApiStatus;
    DWORD FromQEntrySize, ToQEntrySize;
    DWORD FromJobEntrySize, ToJobEntrySize;
    LPVOID FromQ = FromInfo;   // Q structure
    DWORD JobLevel;
    DWORD QsLeft;
    LPVOID ToQ = ToInfo;   // Q structure

    if ( (FromInfo == NULL) || (ToInfo == NULL) ) {
        return (ERROR_INVALID_PARAMETER);
    }

    ApiStatus = NetpPrintQStructureInfo (
            QLevel,
            PARMNUM_ALL,
            TRUE,              // yes, we want native sizes.
            AddOrSetInfoApi,
            (ToUnicode ? sizeof(CHAR) : sizeof(WCHAR) ),  // FROM char size
            NULL,              // don't need data desc 16
            NULL,              // don't need data desc 32
            NULL,              // don't need data desc SMB
            NULL,              // don't need aux desc 16
            NULL,              // don't need aux desc 32
            NULL,              // don't need aux desc SMB
            NULL,              // don't need max total size
            & FromQEntrySize,  // yes, we want fixed entry size
            NULL );            // don't need string size
    if (ApiStatus != NO_ERROR) {
        return (ApiStatus);
    }
    NetpAssert( FromQEntrySize > 0 );

    ApiStatus = NetpPrintQStructureInfo (
            QLevel,
            PARMNUM_ALL,
            TRUE,              // yes, we want native sizes.
            AddOrSetInfoApi,
            (ToUnicode ? sizeof(WCHAR) : sizeof(CHAR) ),  // TO char size
            NULL,              // don't need data desc 16
            NULL,              // don't need data desc 32
            NULL,              // don't need data desc SMB
            NULL,              // don't need aux desc 16
            NULL,              // don't need aux desc 32
            NULL,              // don't need aux desc SMB
            NULL,              // don't need max total size
            & ToQEntrySize,    // yes, we want fixed entry size
            NULL );            // don't need string size
    NetpAssert( ApiStatus == NO_ERROR );
    NetpAssert( ToQEntrySize > 0 );

    // Figure-out job-level associated with this queue info level.
    switch (QLevel) {
    case 2:
        JobLevel = 1;
        break;
    case 4:
        JobLevel = 2;
        break;
    default:
       // No jobs for this Q info level.
       JobLevel = (DWORD)-1;
    }

    if (JobLevel != (DWORD)-1) {
        ApiStatus = NetpPrintJobStructureInfo (
                JobLevel,
                PARMNUM_ALL,
                TRUE,              // yes, we want native sizes.
                AddOrSetInfoApi,
                (ToUnicode ? sizeof(CHAR) : sizeof(WCHAR) ),  // FROM char size
                NULL,              // don't need data desc 16
                NULL,              // don't need data desc 32
                NULL,              // don't need data desc SMB
                NULL,              // don't need max total size
                & FromJobEntrySize,    // yes, we want fixed entry size
                NULL );            // don't need string size
        NetpAssert( ApiStatus == NO_ERROR );
        NetpAssert( FromJobEntrySize > 0 );

        ApiStatus = NetpPrintJobStructureInfo (
                JobLevel,
                PARMNUM_ALL,
                TRUE,              // yes, we want native sizes.
                AddOrSetInfoApi,
                (ToUnicode ? sizeof(WCHAR) : sizeof(CHAR) ),  // TO char size
                NULL,              // don't need data desc 16
                NULL,              // don't need data desc 32
                NULL,              // don't need data desc SMB
                NULL,              // don't need max total size
                & ToJobEntrySize,    // yes, we want fixed entry size
                NULL );            // don't need string size
        NetpAssert( ApiStatus == NO_ERROR );
        NetpAssert( ToJobEntrySize > 0 );
    }

    for (QsLeft = QCount; QsLeft>0; --QsLeft) {

        DWORD JobCount;

        // Convert 1 queue structure and 0 or more job structures.
        ApiStatus = NetpConvertPrintQCharSet(
                QLevel,   // info level (for print Q APIs)
                AddOrSetInfoApi,
                FromQ,
                ToQ,
                ToUnicode,
                ToStringAreaPtr ); // update and move string area
        NetpAssert( ApiStatus == NO_ERROR );  // BUGBUG

        // Bump pointers to start of next fixed queue structure.
        // To do this, we need to find out how many jobs there are.
        JobCount = NetpJobCountForQueue(
                QLevel,         // Q info level
                FromQ,          // Q fixed structure
                !ToUnicode );   // does input have UNICODE strings?

        // Bump past this queue structure.
        FromQ = (((LPBYTE) FromQ) + FromQEntrySize);
        ToQ   = (((LPBYTE) ToQ  ) + ToQEntrySize  );

        // Bump past jobs (if any).
        if (JobCount > 0) {
            NetpAssert( JobLevel != (DWORD)-1 );
            FromQ = ( ((LPBYTE) FromQ) + (FromJobEntrySize * JobCount) );
            ToQ   = ( ((LPBYTE) ToQ  ) + (ToJobEntrySize   * JobCount) );
        }

    }

    return (NO_ERROR);

} // NetpConvertPrintQArrayCharSet