File:  [WindowsNT SDKs] / ntddk / src / print / localmon / localmon.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Thu Aug 9 18:31:12 2018 UTC (7 years, 9 months ago) by root
Branches: msft, MAIN
CVS tags: ntddk-nov-1993, HEAD
Microsoft Windows NT Build 511 (DDK SDK) 11-01-1993

#include <windows.h>
#include <winspool.h>
#include <winsplp.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#include "spltypes.h"
#include "local.h"
#include "dialogs.h"


/* Definitions for MonitorThread:
 */
#define TRANSMISSION_DATA_SIZE  100

typedef struct _TRANSMISSION
{
    HANDLE       hPipe;
    WCHAR        Data[TRANSMISSION_DATA_SIZE];
    LPOVERLAPPED pOverlapped;
    INT          PipeInstance;
    HANDLE       hPrinter;
    DWORD        JobId;
    HANDLE       hFile;
    PINIPORT     pIniPort;
}
TRANSMISSION, *PTRANSMISSION;

extern WCHAR szWindows[];
extern WCHAR szINIKey_TransmissionRetryTimeout[];
WCHAR   *Network = L"Net:";

BOOL
CreateMonitorThread(
   PINIPORT pIniPort
);

VOID
CompleteRead(
    DWORD Error,
    DWORD ByteCount,
    LPOVERLAPPED pOverlapped
);

PSECURITY_DESCRIPTOR
CreateNamedPipeSecurityDescriptor(
    VOID
);

BOOL
BuildPrintObjectProtection(
    IN ULONG AceCount,
    IN PSID *AceSid,
    IN ACCESS_MASK *AceMask,
    IN BYTE *InheritFlags,
    IN PSID WorldSid,
    IN PGENERIC_MAPPING GenericMap,
    OUT PSECURITY_DESCRIPTOR *ppSecurityDescriptor
);

BOOL
PortExists(
    LPWSTR pName,
    LPWSTR pPortName,
    PDWORD pError
);

#define offsetof(type, identifier) (DWORD)(&(((type)0)->identifier))

#if DBG
DWORD GLOBAL_DEBUG_FLAGS = DBG_ERROR | DBG_WARNING;
#endif

HANDLE hInst;

DWORD PortInfo1Offsets[]={offsetof(LPPORT_INFO_1, pName),
                             (DWORD)-1};

WCHAR szPorts[]   = L"ports";
WCHAR szPortsEx[] = L"portsex"; /* Extra ports values */
WCHAR szFILE[]    = L"FILE:";
WCHAR szCOM[]     = L"COM";
WCHAR szLPT[]     = L"LPT";

/* These globals are needed so that AddPort can call
 * SPOOLSS!EnumPorts to see whether the port to be added
 * already exists.
 * They will be initialized the first time AddPort is called.
 */
HMODULE hSpoolssDll = NULL;
FARPROC pfnSpoolssEnumPorts = NULL;

#define IS_FILE_PORT(pName) \
    !wcsicmp( pName, szFILE )

#define IS_COM_PORT(pName) \
    !wcsnicmp( pName, szCOM, 3 )

#define IS_LPT_PORT(pName) \
    !wcsnicmp( pName, szLPT, 3 )

#define IS_NOT_LOCAL_PORT(pName, pLocalMonitorName) \
    wcsicmp( pName, pLocalMonitorName )


LPWSTR
GetPortName(
    HWND hWnd
);
VOID
ConfigCOMPort(
    HWND hWnd
);
BOOL
ConfigLPTPort(
    HWND hWnd
);


PINIPORT    pIniFirstPort;
CRITICAL_SECTION    SpoolerSection;

VOID
RemoveColon(
    LPWSTR  pName
)
{
    DWORD   Length;

    // Remove that damm trailing colon

    Length = wcslen(pName);

    if (pName[Length-1] == L':')
        pName[Length-1] = 0;

}

BOOL
LibMain(
    HANDLE hModule,
    DWORD dwReason,
    LPVOID lpRes
)
{
    if (dwReason != DLL_PROCESS_ATTACH)
        return TRUE;

    hInst = hModule;

    return TRUE;

    UNREFERENCED_PARAMETER( lpRes );
}


PINIPORT
CreatePortEntry(
    LPWSTR   pPortName
)
{
    DWORD       cb;
    PINIPORT    pIniPort, pPort;

    if (!lstrcmpi(pPortName, Network))
        return FALSE;

    cb = sizeof(INIPORT) + wcslen(pPortName)*sizeof(WCHAR) + sizeof(WCHAR);

    EnterSplSem( );

    pIniPort=AllocSplMem(cb);

    LeaveSplSem( );

    if( pIniPort )
    {
        pIniPort->pName = wcscpy((LPWSTR)(pIniPort+1), pPortName);
        pIniPort->cb = cb;
        pIniPort->pNext = 0;
        pIniPort->signature = IPO_SIGNATURE;

        if (pPort = pIniFirstPort) {

            while (pPort->pNext)
                pPort = pPort->pNext;

            pPort->pNext = pIniPort;

        } else

            pIniFirstPort = pIniPort;
    }

    return pIniPort;
}


BOOL
DeletePortEntry(
    LPWSTR   pPortName
)
{
    DWORD       cb;
    PINIPORT    pPort, pPrevPort;

    cb = sizeof(INIPORT) + wcslen(pPortName)*sizeof(WCHAR) + sizeof(WCHAR);

    pPort = pIniFirstPort;
    while (pPort && lstrcmpi(pPort->pName, pPortName)) {
        pPrevPort = pPort;
        pPort = pPort->pNext;
    }

    if (pPort) {
        if (pPort == pIniFirstPort) {
            pIniFirstPort = pPort->pNext;
        } else {
            pPrevPort->pNext = pPort->pNext;
        }
        FreeSplMem (pPort, cb);

        return TRUE;
    }
    else
        return FALSE;
}


BOOL
InitializeMonitor(
    LPWSTR  pRegistryRoot
)
{
    LPWSTR   pPorts, pFirstPort;
    DWORD   cbAlloc=4096;

    InitializeCriticalSection(&SpoolerSection);

   EnterSplSem();

    if (!(pPorts=AllocSplMem(cbAlloc))) {

        DBGMSG(DBG_ERROR, ("Failed to alloc %d bytes for ports.", cbAlloc));
        return FALSE;
    }

    GetProfileString(szPorts, NULL, L"", pPorts, cbAlloc);

    pFirstPort=pPorts;  // Remember the beginning so we can free it later

    // We now have all the ports

    while (*pPorts) {

        CreatePortEntry(pPorts);

        pPorts+=wcslen(pPorts)+1;
    }

    FreeSplMem(pFirstPort, cbAlloc);

   LeaveSplSem();

    return TRUE;
}

DWORD
GetPortSize(
    PINIPORT pIniPort,
    DWORD   Level
)
{
    DWORD   cb;

    switch (Level) {

    case 1:

        cb=sizeof(PORT_INFO_1) +
           wcslen(pIniPort->pName)*sizeof(WCHAR) + sizeof(WCHAR);
        break;

    default:
        cb = 0;
        break;
    }

    return cb;
}

// We are being a bit naughty here as we are not sure exactly how much
// memory to allocate for the source strings. We will just assume that
// PORT_INFO_2 is the biggest structure around for the moment.

LPBYTE
CopyIniPortToPort(
    PINIPORT pIniPort,
    DWORD   Level,
    LPBYTE  pPortInfo,
    LPBYTE   pEnd
)
{
    LPWSTR   SourceStrings[sizeof(PORT_INFO_1)/sizeof(LPWSTR)];
    LPWSTR   *pSourceStrings=SourceStrings;
    PPORT_INFO_1 pPort1 = (PPORT_INFO_1)pPortInfo;
    DWORD   *pOffsets;

    switch (Level) {

    case 1:
        pOffsets = PortInfo1Offsets;
        break;

    default:
        return pEnd;
    }

    switch (Level) {

    case 1:
        *pSourceStrings++=pIniPort->pName;

        pEnd = PackStrings(SourceStrings, pPortInfo, pOffsets, pEnd);

        break;

    default:
        return pEnd;
    }

    return pEnd;
}

BOOL
EnumPorts(
    LPWSTR   pName,
    DWORD   Level,
    LPBYTE  pPorts,
    DWORD   cbBuf,
    LPDWORD pcbNeeded,
    LPDWORD pcReturned
)
{
    PINIPORT pIniPort;
    DWORD   cb;
    LPBYTE  pEnd;
    DWORD   LastError=0;

   EnterSplSem();

    cb=0;

    pIniPort=pIniFirstPort;

    while (pIniPort) {
        cb+=GetPortSize(pIniPort, Level);
        pIniPort=pIniPort->pNext;
    }

    *pcbNeeded=cb;

    if (cb <= cbBuf) {

        pEnd=pPorts+cbBuf;
        *pcReturned=0;

        pIniPort=pIniFirstPort;
        while (pIniPort) {
            pEnd = CopyIniPortToPort(pIniPort, Level, pPorts, pEnd);
            switch (Level) {
            case 1:
                pPorts+=sizeof(PORT_INFO_1);
                break;
            }
            pIniPort=pIniPort->pNext;
            (*pcReturned)++;
        }

    } else

        LastError = ERROR_INSUFFICIENT_BUFFER;

   LeaveSplSem();

    if (LastError) {

        SetLastError(LastError);
        return FALSE;

    } else

        return TRUE;
}

#define NEXTVAL(pch)                    \
    while( *pch && ( *pch != L',' ) )    \
        pch++;                          \
    if( *pch )                          \
        pch++


BOOL
GetIniCommValues(
    LPWSTR          pName,
    LPDCB          pdcb,
    LPCOMMTIMEOUTS pcto
)
{
    WCHAR IniEntry[20];

    *IniEntry = L'\0';

    GetProfileString( szPorts, pName, L"", IniEntry, sizeof IniEntry );

    BuildCommDCB(IniEntry, pdcb);

    pcto->WriteTotalTimeoutConstant = GetProfileInt(szWindows,
                                            szINIKey_TransmissionRetryTimeout,
                                            45 );
    pcto->WriteTotalTimeoutConstant*=1000;
    return TRUE;
}


BOOL
OpenPort(
    LPWSTR   pName,
    PHANDLE pHandle
)
{
    PINIPORT     pIniPort;

   EnterSplSem();
    pIniPort = FindPort(pName);

    if (pIniPort) {

        *pHandle = pIniPort;

        CreateMonitorThread(pIniPort);
       LeaveSplSem();

        return TRUE;

    } else {

       DBGMSG(DBG_ERROR, ("localmon!OpenPort %s : Failed\n", pName));

       LeaveSplSem();
        return FALSE;
    }
}


BOOL
StartDocPort(
    HANDLE  hPort,
    LPWSTR  pPrinterName,
    DWORD   JobId,
    DWORD   Level,
    LPBYTE  pDocInfo
)
{
    PINIPORT    pIniPort = (PINIPORT)hPort;
    LPWSTR       pPortName;
    DCB          dcb;
    COMMTIMEOUTS cto;
    WCHAR       TempDosDeviceName[MAX_PATH];
    HANDLE      hToken;

    UNREFERENCED_PARAMETER( Level );
    UNREFERENCED_PARAMETER( pDocInfo );

    DBGMSG(DBG_TRACE, ("StartDocPort(%08x, %ws, %d, %d, %08x)\n",
                       hPort, pPrinterName, JobId, Level, pDocInfo));

   EnterSplSem();
    pIniPort->pPrinterName = AllocSplStr(pPrinterName);
   LeaveSplSem();

    if (pIniPort->pPrinterName) {

        if (OpenPrinter(pPrinterName, &pIniPort->hPrinter, NULL)) {

            pIniPort->JobId = JobId;

            pPortName = pIniPort->pName;

            if (!IS_FILE_PORT (pPortName)) {

                LPWSTR  pName;

                if (pIniPort->Status & PP_MONITORRUNNING) {

                    WCHAR   DeviceNames[MAX_PATH];
                    WCHAR   DosDeviceName[MAX_PATH];
                    WCHAR  *pDeviceNames=DeviceNames;

                    wcscpy(DosDeviceName, pIniPort->pName);
                    RemoveColon(DosDeviceName);

                    hToken = RevertToPrinterSelf();

                    QueryDosDevice(DosDeviceName,
                                   DeviceNames,
                                   sizeof(DeviceNames));

                    if (!lstrcmpi(pDeviceNames, pIniPort->pNewDeviceName)) {

                        pDeviceNames+=wcslen(pDeviceNames)+1;
                    }

                    wcscpy(TempDosDeviceName, L"NONSPOOLED_");
                    wcscat(TempDosDeviceName, pIniPort->pName);
                    RemoveColon(TempDosDeviceName);

                    DefineDosDevice(DDD_RAW_TARGET_PATH, TempDosDeviceName, pDeviceNames);

                    ImpersonatePrinterClient(hToken);

                    wcscpy(TempDosDeviceName, L"\\\\.\\NONSPOOLED_");
                    wcscat(TempDosDeviceName, pIniPort->pName);
                    RemoveColon(TempDosDeviceName);

                    pName = TempDosDeviceName;

                } else
                    pName = pIniPort->pName;

                pIniPort->hFile = CreateFile(pName, GENERIC_WRITE,
                                            FILE_SHARE_READ, NULL, OPEN_ALWAYS,
                                            FILE_ATTRIBUTE_NORMAL |
                                            FILE_FLAG_SEQUENTIAL_SCAN, NULL);

                if (pIniPort->hFile == INVALID_HANDLE_VALUE) {

                    if (pIniPort->Status & PP_MONITORRUNNING) {

                        wcscpy(TempDosDeviceName, L"NONSPOOLED_");
                        wcscat(TempDosDeviceName, pIniPort->pName);
                        RemoveColon(TempDosDeviceName);

                        DefineDosDevice(DDD_REMOVE_DEFINITION, TempDosDeviceName, NULL);
                    }
                   EnterSplSem();
                    FreeSplStr(pIniPort->pPrinterName);
                   LeaveSplSem();
                    return FALSE;
                }

                SetEndOfFile(pIniPort->hFile);

                if (IS_COM_PORT (pPortName)) {

                    if (GetCommState (pIniPort->hFile, &dcb)) {

                        GetCommTimeouts(pIniPort->hFile, &cto);
                        GetIniCommValues (pPortName, &dcb, &cto);
                        SetCommState (pIniPort->hFile, &dcb);
                        SetCommTimeouts(pIniPort->hFile, &cto);

                    } else {

                        DBGMSG( DBG_ERROR, ("ERROR: Failed to set Comm State") );
                    }
                }

                else if (IS_LPT_PORT (pPortName)) {

                    if (GetCommTimeouts(pIniPort->hFile, &cto)) {
                        cto.WriteTotalTimeoutConstant = GetProfileInt(szWindows,
                                                szINIKey_TransmissionRetryTimeout,
                                                45 );
                        cto.WriteTotalTimeoutConstant*=1000;
                        SetCommTimeouts(pIniPort->hFile, &cto);

                    } else {

                        DBGMSG( DBG_ERROR, ("ERROR: Failed to set Comm State") );
                    }
                }

            } else {

                HANDLE  hToken;
                int    rc;
                HANDLE hFile;

                hToken = RevertToPrinterSelf();


                rc = DialogBoxParam( hInst,
                                    MAKEINTRESOURCE( DLG_PRINTTOFILE ),
                                    NULL, (DLGPROC)PrintToFileDlg,
                                    (LPARAM)&hFile );

                ImpersonatePrinterClient(hToken);

                if( rc == -1 ) {

                   EnterSplSem();
                    FreeSplStr(pIniPort->pPrinterName);
                   LeaveSplSem();
                    return FALSE;

                } else if( rc == 0 ) {

                   EnterSplSem();
                    FreeSplStr(pIniPort->pPrinterName);
                   LeaveSplSem();
                    SetLastError(ERROR_PRINT_CANCELLED);
                    return FALSE;

                } else {

                    pIniPort->hFile = hFile;
                }

                SetEndOfFile(pIniPort->hFile);
            }

        } else {
              EnterSplSem( );
              FreeSplStr(pIniPort->pPrinterName);
              LeaveSplSem( );
        }
    }

    if (pIniPort->hFile == INVALID_HANDLE_VALUE) {
       DBGMSG(DBG_ERROR, ("StartDocPort FAILED %x\n", GetLastError()));
        if (pIniPort->pPrinterName) {
           EnterSplSem();
            FreeSplStr(pIniPort->pPrinterName);
           LeaveSplSem();
        }
        return FALSE;
    } else {
        return TRUE;
    }
}

BOOL
ReadPort(
    HANDLE hPort,
    LPBYTE pBuffer,
    DWORD  cbBuf,
    LPDWORD pcbRead
)
{
    PINIPORT    pIniPort = (PINIPORT)hPort;
    BOOL    rc;

    DBGMSG(DBG_TRACE, ("ReadPort(%08x, %08x, %d)\n", hPort, pBuffer, cbBuf));

    rc = ReadFile(pIniPort->hFile, pBuffer, cbBuf, pcbRead, NULL);

    DBGMSG(DBG_TRACE, ("ReadPort returns %d; %d bytes read\n", rc, *pcbRead));

    return rc;
}

BOOL
WritePort(
    HANDLE  hPort,
    LPBYTE  pBuffer,
    DWORD   cbBuf,
    LPDWORD pcbWritten
)
{
    PINIPORT    pIniPort = (PINIPORT)hPort;
    BOOL    rc;

    DBGMSG(DBG_TRACE, ("WritePort(%08x, %08x, %d)\n", hPort, pBuffer, cbBuf));

    rc = WriteFile(pIniPort->hFile, pBuffer, cbBuf, pcbWritten, NULL);

    DBGMSG(DBG_TRACE, ("WritePort returns %d; %d bytes written\n", rc, *pcbWritten));

    return rc;
}

BOOL
EndDocPort(
   HANDLE   hPort
)
{
    PINIPORT    pIniPort = (PINIPORT)hPort;
    WCHAR       TempDosDeviceName[MAX_PATH];

    DBGMSG(DBG_TRACE, ("EndDocPort(%08x)\n", hPort));

    CloseHandle(pIniPort->hFile);

    SetJob(pIniPort->hPrinter, pIniPort->JobId, 0, NULL, JOB_CONTROL_CANCEL);

    ClosePrinter(pIniPort->hPrinter);

   EnterSplSem();

    if (pIniPort->Status & PP_MONITORRUNNING) {

        wcscpy(TempDosDeviceName, L"NONSPOOLED_");
        wcscat(TempDosDeviceName, pIniPort->pName);
        RemoveColon(TempDosDeviceName);

        DefineDosDevice(DDD_REMOVE_DEFINITION, TempDosDeviceName, NULL);
    }

    FreeSplStr(pIniPort->pPrinterName);

   LeaveSplSem();

    return TRUE;
}

BOOL
ClosePort(
    HANDLE  hPort
)
{
    PINIPORT pIniPort = (PINIPORT)hPort;

    // Now destroy the monitor

    if (pIniPort->Status & PP_MONITORRUNNING) {
        pIniPort->Status &= ~PP_MONITORRUNNING;
        pIniPort->Status &= ~PP_RUNMONITOR;
        SetEvent(pIniPort->Semaphore);
    }

    return TRUE;
}

BOOL
DeletePort(
    LPWSTR   pName,
    HWND    hWnd,
    LPWSTR   pPortName
)
{
    BOOL rc;

    if( !hWnd )
        hWnd == GetDesktopWindow( );

    EnterSplSem();

    if (rc = DeletePortEntry( pPortName ))
        WriteProfileString(szPorts, pPortName, NULL);

    LeaveSplSem();

    return rc;

    UNREFERENCED_PARAMETER( pName );
}

BOOL
AddPort(
    LPWSTR   pName,
    HWND    hWnd,
    LPWSTR   pMonitorName
)
{
    LPWSTR pPortName;
    DWORD  ThreadId;
    DWORD  WindowThreadId;
    BOOL   rc = TRUE;
    DWORD  Error;
    BOOL   DoAddPort = TRUE;
    WCHAR  szLocalMonitor[MAX_PATH+1];


    LoadString(hInst, IDS_LOCALMONITOR, szLocalMonitor, MAX_PATH);
    if  (IS_NOT_LOCAL_PORT( pMonitorName, szLocalMonitor)) {

        // If pMonitorName != "Local Port", we have an
        // invalid Monitor name
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    ThreadId = GetCurrentThreadId( );
    WindowThreadId = GetWindowThreadProcessId(hWnd, NULL);

    if (!AttachThreadInput(ThreadId, WindowThreadId, TRUE))
        DBGMSG(DBG_WARNING, ("AttachThreadInput failed: Error %d\n", GetLastError()));

    /* Get the user to enter a port name:
     */
    pPortName = GetPortName( hWnd );

    if( pPortName )
    {
        if( PortExists( pName, pPortName, &Error ) )
        {
            Message( hWnd, MSG_ERROR, IDS_LOCALMONITOR,
                     IDS_PORTALREADYEXISTS_S, pPortName );

            DoAddPort = FALSE;

            /* In this case, the error was handled, and the user was
             * notified with a message box, so we return true to ensure
             * that the caller doesn't have to do so too.
             */
            rc = TRUE;
        }

        else if( Error != NO_ERROR )
        {
            DBGMSG(DBG_ERROR, ("Error %d occurred checking whether port exists\n",
                               Error));
            DoAddPort = FALSE;
            rc = FALSE;
        }

        if( DoAddPort )
        {
            /* Now, let's see if it's a COM port:
             */
            if( IS_COM_PORT( pPortName ) )

                ConfigCOMPort( hWnd );

            else
            /* No, well maybe it's an LPT?
             */
            if( IS_LPT_PORT( pPortName ) )

                ConfigLPTPort( hWnd );


            if( CreatePortEntry( pPortName ) ) {
                if (!WriteProfileString(szPorts, pPortName, L"")) {
                    DeletePortEntry(pPortName);
                    rc = FALSE;
                }
            }
            else
                rc = FALSE;
        }

    }

    AttachThreadInput(WindowThreadId, ThreadId, FALSE);

    return rc;
}


/* PortExists
 *
 * Calls EnumPorts to check whether the port name already exists.
 * This asks every monitor, rather than just this one.
 * The function will return TRUE if the specified port is in the list.
 * If an error occurs, the return is FALSE and the variable pointed
 * to by pError contains the return from GetLastError().
 * The caller must therefore always check that *pError == NO_ERROR.
 */
BOOL
PortExists(
    LPWSTR pName,
    LPWSTR pPortName,
    PDWORD pError
)
{
    DWORD cbNeeded;
    DWORD cReturned;
    DWORD cbPorts;
    LPPORT_INFO_1 pPorts;
    DWORD i;
    BOOL  Found = TRUE;

    *pError = NO_ERROR;

    if (!hSpoolssDll) {

        hSpoolssDll = LoadLibrary(L"SPOOLSS.DLL");

        if (hSpoolssDll) {
            pfnSpoolssEnumPorts = GetProcAddress(hSpoolssDll,
                                                 "EnumPortsW");
            if (!pfnSpoolssEnumPorts) {

                *pError = GetLastError();
                FreeLibrary(hSpoolssDll);
                hSpoolssDll = NULL;
            }

        } else {

            *pError = GetLastError();
        }
    }

    if (!pfnSpoolssEnumPorts)
        return FALSE;


    if (!(*pfnSpoolssEnumPorts)(pName, 1, NULL, 0, &cbNeeded, &cReturned))
    {
        if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
        {
            cbPorts = cbNeeded;

            EnterSplSem();
            pPorts = AllocSplMem(cbPorts);
            LeaveSplSem();

            if (pPorts)
            {
                if ((*pfnSpoolssEnumPorts)(pName, 1, (LPBYTE)pPorts, cbPorts,
                                           &cbNeeded, &cReturned))
                {
                    Found = FALSE;

                    for (i = 0; i < cReturned; i++)
                    {
                        if (!lstrcmpi(pPorts[i].pName, pPortName))
                            Found = TRUE;
                    }
                }
            }

            EnterSplSem();
            FreeSplMem(pPorts, cbPorts);
            LeaveSplSem();
        }
    }

    else
        Found = FALSE;


    return Found;
}



/* ConfigurePort
 *
 */
BOOL
ConfigurePort(
    LPWSTR   pName,
    HWND  hWnd,
    LPWSTR pPortName
)
{
    DWORD  ThreadId;
    DWORD  WindowThreadId;

    ThreadId = GetCurrentThreadId( );
    WindowThreadId = GetWindowThreadProcessId(hWnd, NULL);

    if (!AttachThreadInput(ThreadId, WindowThreadId, TRUE))
        DBGMSG(DBG_WARNING, ("AttachThreadInput failed: Error %d\n", GetLastError()));

    if( IS_COM_PORT( pPortName ) )

        ConfigCOMPort( hWnd );

    else
    /* No, well maybe it's an LPT?
     */
    if( IS_LPT_PORT( pPortName ) )

        ConfigLPTPort( hWnd );

    else
        Message( hWnd, MSG_INFORMATION, IDS_LOCALMONITOR,
                 IDS_NOTHING_TO_CONFIGURE );

    AttachThreadInput(WindowThreadId, ThreadId, FALSE);

    return TRUE;
}


/* GetPortName
 *
 * Puts up a dialog containing a free entry field.
 * The dialog allocates a string for the name, if a selection is made.
 */
LPWSTR
GetPortName(
    HWND hWnd
)
{
    LPWSTR pPortName;

    if( DialogBoxParam( hInst, MAKEINTRESOURCE(DLG_PORTNAME), hWnd,
                        (DLGPROC)PortNameDlg, (LPARAM)&pPortName ) != IDOK )
        pPortName = NULL;

    return pPortName;
}

/* From Control Panel's control.h:
 */
#define CHILD_PORTS 4

/* From cpl.h:
 */
#define CPL_INIT        1
#define CPL_DBLCLK      5
#define CPL_EXIT        7

/* Hack:
 */
#define CHILD_PORTS_HELPID  2

/* ConfigCOMPort
 *
 * Calls the Control Panel Ports applet
 * to permit user to set Baud rate etc.
 */
typedef void (WINAPI *CFGPROC)(HWND, ULONG, ULONG, ULONG);


VOID
ConfigCOMPort(
    HWND hWnd
)
{
    HANDLE  hLibrary;
    CFGPROC pfnCplApplet;

    if( hLibrary = LoadLibrary( L"MAIN.CPL" ) ) {

        if( pfnCplApplet = (CFGPROC)GetProcAddress( hLibrary, "CPlApplet" ) ) {
            (*pfnCplApplet)( hWnd, CPL_INIT, 0, 0 );
            (*pfnCplApplet)( hWnd, CPL_DBLCLK, CHILD_PORTS_HELPID, CHILD_PORTS );
            (*pfnCplApplet)( hWnd, CPL_EXIT, 0, 0 );
        }

        FreeLibrary( hLibrary );
    }
}


/* ConfigLPTPort
 *
 * Calls a dialog box which prompts the user to enter timeout and retry
 * values for the port concerned.
 * The dialog writes the information to the registry (win.ini for now).
 */
BOOL
ConfigLPTPort(
    HWND hWnd
)
{
    DialogBox( hInst, MAKEINTRESOURCE( DLG_CONFIGURE_LPT ),
               hWnd, (DLGPROC)ConfigureLPTPortDlg );

    return TRUE;
}

VOID
MonitorThread(
    PINIPORT  pIniPort
);

BOOL
CreateMonitorThread(
   PINIPORT pIniPort
)
{
    if (!(pIniPort->Status & PP_THREADRUNNING)) {

        pIniPort->Status |= PP_RUNTHREAD;

        pIniPort->Semaphore=CreateEvent(NULL, FALSE, FALSE, NULL);

        pIniPort->AccessSem=CreateSemaphore(NULL, 1, 256,NULL);

        pIniPort->ThreadHandle = CreateThread(NULL, 16*1024,
                                 (LPTHREAD_START_ROUTINE)MonitorThread,
                                 pIniPort,
                                 0, &pIniPort->ThreadId);

        pIniPort->Status |= PP_THREADRUNNING;

    } else {

        DBGMSG(DBG_ERROR, ("localmon!MonitorThread already running %x\n",
                           pIniPort->Status));
    }

    return TRUE;
}

#define NUMBER_OF_PIPE_INSTANCES 10

VOID
MonitorThread(
    PINIPORT  pIniPort
)
{
    WCHAR   NewNtDeviceName[MAX_PATH];
    WCHAR   OldNtDeviceName[MAX_PATH];
    WCHAR   DosDeviceName[MAX_PATH];
    WCHAR   szPipeName[MAX_PATH];
    HANDLE  hPipe[NUMBER_OF_PIPE_INSTANCES];
    HANDLE  hEvent[NUMBER_OF_PIPE_INSTANCES+1];  // One extra event for semaphore
    DWORD   WaitResult;
    DWORD   i;
    DWORD   Error;
    OVERLAPPED  OverLapped;
    OVERLAPPED  Overlapped[NUMBER_OF_PIPE_INSTANCES];
    LPOVERLAPPED    pOverlapped;
    PTRANSMISSION   pTransmission;
    SECURITY_ATTRIBUTES SecurityAttributes;

   EnterSplSem();

    wcscpy(DosDeviceName, pIniPort->pName);
    RemoveColon(DosDeviceName);

    if (!QueryDosDevice(DosDeviceName, OldNtDeviceName,
                       sizeof(OldNtDeviceName)/sizeof(OldNtDeviceName[0]))) {
        LeaveSplSem();
        return;
    }

    SecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);

    SecurityAttributes.lpSecurityDescriptor = CreateNamedPipeSecurityDescriptor();

    SecurityAttributes.bInheritHandle = FALSE;

    wcscpy(NewNtDeviceName, L"\\Device\\NamedPipe\\Spooler\\");
    wcscat(NewNtDeviceName, pIniPort->pName);
    RemoveColon(NewNtDeviceName);

    DefineDosDevice(DDD_RAW_TARGET_PATH, DosDeviceName, NewNtDeviceName);

    pIniPort->pNewDeviceName = AllocSplStr(NewNtDeviceName);

   LeaveSplSem();

    wsprintf(szPipeName, L"\\\\.\\Pipe\\Spooler\\%ws", pIniPort->pName);
    RemoveColon(szPipeName);

    pIniPort->Status |= PP_MONITORRUNNING;

    memset(&OverLapped, 0, sizeof(OVERLAPPED));

    /* Put the semaphore event in the extra member of the event array:
     */
    hEvent[NUMBER_OF_PIPE_INSTANCES] = pIniPort->Semaphore;

    /* Create several instances of a named pipe, create an event for each,
     * and connect to wait for a client:
     */
    for( i = 0; i < NUMBER_OF_PIPE_INSTANCES; i++ )
    {
        hPipe[i] = CreateNamedPipe(szPipeName, PIPE_ACCESS_DUPLEX |
                                                           FILE_FLAG_OVERLAPPED,
                                               PIPE_WAIT | PIPE_READMODE_BYTE |
                                                           PIPE_TYPE_BYTE,
                                               PIPE_UNLIMITED_INSTANCES,
                                               4096,
                                               64*1024,   // 64k
                                               0,
                                               &SecurityAttributes);
        if( hPipe[i] == (HANDLE)-1 )
        {
            DBGMSG( DBG_ERROR, ( "CreateNamedPipe of %s failed. Error %d\n",
                                 szPipeName, GetLastError( ) ) );
            pIniPort->Status &= ~PP_THREADRUNNING;
            return;
        }

        hEvent[i] = Overlapped[i].hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

        if( !hEvent[i] )
        {
            DBGMSG( DBG_ERROR, ( "CreateEvent failed. Error %d\n", GetLastError( ) ) );
            pIniPort->Status &= ~PP_THREADRUNNING;
            return;
        }

        if( !ConnectNamedPipe( hPipe[i], &Overlapped[i] ) )
        {
            Error = GetLastError( );

            if( Error == ERROR_IO_PENDING )
            {
                DBGMSG( DBG_INFO, ( "ConnectNamedPipe %d, IO pending\n", i ) );
            }
            else
            {
                DBGMSG( DBG_ERROR, ( "ConnectNamedPipe failed. Error %d\n", GetLastError( ) ) );
                pIniPort->Status &= ~PP_THREADRUNNING;
                return;
            }
        }
    }


    while( TRUE )
    {
        DBGMSG( DBG_INFO, ( "Waiting to connect...\n" ) );

        /* WaitForMultipleObjectsEx should return the index of the pipe instance
         * that is in signalled state.  Any other value except WAIT_IO_COMPLETION
         * is an error:
         */
        while ((WaitResult = WaitForMultipleObjectsEx( NUMBER_OF_PIPE_INSTANCES + 1,
                                               hEvent,
                                               FALSE, // wait for any
                                               INFINITE,
                                               TRUE )) == WAIT_IO_COMPLETION)
            /* do nothing */ ;

        DBGMSG( DBG_INFO, ( "\nWaitForMultipleObjectsEx returned %d\n", WaitResult ) );

        if( ( WaitResult >= NUMBER_OF_PIPE_INSTANCES )
          &&( WaitResult != WAIT_IO_COMPLETION ) )
        {
            DBGMSG( DBG_INFO, ( "WaitForMultipleObjects returned %d; Last error = %d\n",
                    WaitResult, GetLastError( ) ) );
            EnterSplSem();

//           if (pIniPort->pPreviousNtDeviceName) {

                 wcscpy(DosDeviceName, pIniPort->pName);
                 RemoveColon(DosDeviceName);

                 wcscpy(NewNtDeviceName, L"\\Device\\NamedPipe\\Spooler\\");
                 wcscat(NewNtDeviceName, pIniPort->pName);
                 RemoveColon(NewNtDeviceName);

                 DefineDosDevice(DDD_REMOVE_DEFINITION |
                                         DDD_EXACT_MATCH_ON_REMOVE |
                                         DDD_RAW_TARGET_PATH,
                                 DosDeviceName,
                                 NewNtDeviceName);
//           }

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

                if( !CloseHandle( hPipe[i] ) ) {
                    DBGMSG( DBG_ERROR, ( "localspl: CloseHandle failed %d %d\n", hPipe[i], GetLastError( ) ) );
                }

                if( !CloseHandle( hEvent[i] ) ) {
                    DBGMSG( DBG_ERROR, ( "localspl: CloseHandle failed %d %d\n", hPipe[i], GetLastError( ) ) );
                }
             }

             pIniPort->Status &= ~PP_THREADRUNNING;

            LeaveSplSem();
            SplOutSem();
             return;
        }

        i = WaitResult;


        /* Set up the transmission structure with the handles etc. needed by
         * the completion callback routine:
         */
        pOverlapped = (LPOVERLAPPED)LocalAlloc( LMEM_ZEROINIT, sizeof( OVERLAPPED ) );
        pTransmission = (PTRANSMISSION)LocalAlloc( LMEM_ZEROINIT, sizeof( TRANSMISSION ) );

        if( pOverlapped && pTransmission )
        {
            /* hEvent isn't used in Win32.
             * We can use it for our own data:
             */
            pTransmission->hPipe = hPipe[i];
            pOverlapped->hEvent = (HANDLE)pTransmission;
            pTransmission->pOverlapped = &Overlapped[i];
            pTransmission->PipeInstance = i;
            pTransmission->hPrinter = NULL;
            pTransmission->hFile = NULL;    // CompleteRead will initialise this
            pTransmission->pIniPort = pIniPort;

            if( !ReadFileEx( hPipe[i], pTransmission->Data, TRANSMISSION_DATA_SIZE,
                             pOverlapped, CompleteRead ) )
            {
                DBGMSG( DBG_ERROR, ( "ReadFileEx[%d] Failed with %d\n", i, GetLastError( ) ) );

                if (GetLastError() == ERROR_BROKEN_PIPE)
                {
                    /* If the pipe has broken, we must disconnect
                     * then reconnect for further transmissions
                     * to succeed:
                     */
                    DisconnectNamedPipe( hPipe[i] );

                    if( !ConnectNamedPipe( hPipe[i], &Overlapped[i] ) )
                    {
                        Error = GetLastError( );

                        if( Error == ERROR_IO_PENDING )
                        {
                            DBGMSG( DBG_INFO, ( "re-ConnectNamedPipe %d, IO pending\n",
                                    pTransmission->PipeInstance ) );
                        }
                        else
                        {
                            DBGMSG( DBG_ERROR, ( "re-ConnectNamedPipe %d failed. Error %d\n",
                                    pTransmission->PipeInstance, GetLastError( ) ) );
                        }
                    }
                }

                LocalFree( pTransmission );
                LocalFree( pOverlapped );

            }
            else
            {
                DBGMSG( DBG_INFO, ( "ReadFileEx succeeded" ) );

            }
        }
    }
}



#define ADDJOB_INFO_SIZE         ( sizeof( ADDJOB_INFO_1 ) + MAX_PATH )

VOID CompleteRead( DWORD Error, DWORD ByteCount, LPOVERLAPPED pOverlapped )
{
    PTRANSMISSION pTransmission;
    DWORD         BytesWritten;
    BOOL          ReadResult;
    DWORD         cbNeeded;
    HANDLE        hFile;
    WCHAR         szDefaultPrinter[MAX_PATH];

    pTransmission = (PTRANSMISSION)pOverlapped->hEvent;

    DBGMSG( DBG_INFO, ( "CompleteRead: Pipe Instance %d; Error = %d; ByteCount = %d\n",
             pTransmission->PipeInstance, Error, ByteCount ) );

    if (!ImpersonateNamedPipeClient(pTransmission->hPipe))
        DBGMSG( DBG_ERROR, ("ImpersonateNamedPipeClient failed %d\n",
                GetLastError()));

    if( pTransmission->hFile == NULL )
    {
        PADDJOB_INFO_1 pAddJobInfo;
        BYTE           AddJobInfo[ADDJOB_INFO_SIZE];

        pAddJobInfo = (PADDJOB_INFO_1)AddJobInfo;

        OpenProfileUserMapping();

        GetProfileString(L"windows", L"device", L"", szDefaultPrinter,
                         sizeof(szDefaultPrinter)/sizeof(szDefaultPrinter[0]));

        CloseProfileUserMapping();

        wcstok(szDefaultPrinter, L",");

        /* Open the port (???) !!!
         */
        if( !OpenPrinter( szDefaultPrinter, &pTransmission->hPrinter, NULL ) )
        {
            DBGMSG( DBG_ERROR, ( "OpenPrinter failed: Error %d", GetLastError( ) ) );
            return;
        }

        memset( &AddJobInfo, '\0', ADDJOB_INFO_SIZE );

        if( AddJob( pTransmission->hPrinter, 1, AddJobInfo, ADDJOB_INFO_SIZE, &cbNeeded ) )
        {
            DBGMSG( DBG_INFO, ( "AddJob: JobId = %d\n", pAddJobInfo->JobId ) );

            hFile = CreateFile( pAddJobInfo->Path,
                                GENERIC_WRITE,
                                FILE_SHARE_READ,
                                NULL,             // PSECURITY_DESCRIPTOR
                                CREATE_ALWAYS,
                                FILE_FLAG_SEQUENTIAL_SCAN,
                                NULL );

            if( hFile != INVALID_HANDLE_VALUE )
            {
                DBGMSG( DBG_INFO, ( "File created\n" ) );

                pTransmission->JobId    = pAddJobInfo->JobId;
                pTransmission->hFile    = hFile;

            }
            else
            {
                DBGMSG( DBG_ERROR, ( "CreateFile %s failed: Error %d\n",
                        pAddJobInfo->Path, GetLastError( ) ) );
            }
        }
        else
        {
            DBGMSG( DBG_ERROR, ( "AddJob failed: Error %d\n", GetLastError( ) ) );
        }
    }

    if( Error == NO_ERROR )
    {
        if( !WriteFile( pTransmission->hFile,
                        pTransmission->Data,
                        ByteCount,
                        &BytesWritten,
                        NULL ) )
        {
            DBGMSG( DBG_ERROR, ( "WriteFile failed: Error %d\n", GetLastError( ) ) );
        }

        memset( pTransmission->Data, '\0', TRANSMISSION_DATA_SIZE );

        ReadResult = ReadFileEx( pTransmission->hPipe, pTransmission->Data,
                                 TRANSMISSION_DATA_SIZE, pOverlapped, CompleteRead );

        if( ReadResult == FALSE )
        {
            Error = GetLastError( );
            DBGMSG( DBG_ERROR, ( "ReadFileEx failed: Error %d\n", Error ) );
        }
    }

    if( Error != NO_ERROR )
    {
        DisconnectNamedPipe( pTransmission->hPipe );

        if( !ConnectNamedPipe( pTransmission->hPipe, pTransmission->pOverlapped ) )
        {
            Error = GetLastError( );

            if( Error == ERROR_IO_PENDING )
            {
                DBGMSG( DBG_INFO, ( "re-ConnectNamedPipe %d, IO pending\n",
                        pTransmission->PipeInstance ) );
            }
            else
            {
                DBGMSG( DBG_ERROR, ("re-ConnectNamedPipe %d failed. Error %d\n",
                        pTransmission->PipeInstance, GetLastError( ) ) );
            }
        }

        if( !CloseHandle( pTransmission->hFile ) )
        {
            DBGMSG( DBG_ERROR, ( "CloseHandle failed: Error %d\n", GetLastError( ) ) );
        }

        if( !ScheduleJob( pTransmission->hPrinter, pTransmission->JobId ) )
        {
            DBGMSG( DBG_ERROR, ( "ScheduleJob failed: Error %d\n", GetLastError( ) ) );
        }

        ClosePrinter(pTransmission->hPrinter);

        LocalFree( pTransmission );
        LocalFree( pOverlapped );

        DBGMSG( DBG_INFO, ( "\nEnd of transmission\n" ) );
    }

/*  !!! HACK HACK HACK swapping in and out of impersonation doesn't work
    if (!RevertToSelf())
        DBGMSG( DBG_ERROR, ( "RevertToSelf failed: Error %d\n", GetLastError( ) ) );
*/
}




/* CreateNamedPipeSecurityDescriptor
 *
 * Creates a security descriptor giving everyone access
 *
 * Arguments: None
 *
 * Return: The security descriptor returned by BuildPrintObjectProtection.
 *
 */
#define MAX_ACE 2
#define DBGCHK( Condition, ErrorInfo ) \
    if( Condition ) DBGMSG( DBG_ERROR, ErrorInfo )

PSECURITY_DESCRIPTOR
CreateNamedPipeSecurityDescriptor(
    VOID
)
{
    PSID AceSid[MAX_ACE];          // Don't expect more than MAX_ACE ACEs in any of these.
    ACCESS_MASK AceMask[MAX_ACE];  // Access masks corresponding to Sids
    BYTE InheritFlags[MAX_ACE];  //
    ULONG AceCount;
    SID_IDENTIFIER_AUTHORITY WorldSidAuthority = SECURITY_WORLD_SID_AUTHORITY;
    PSID WorldSid;
    PSECURITY_DESCRIPTOR ServerSD;
    BOOL OK;

    //
    // Printer SD
    //

    AceCount = 0;

    /* World SID */

    OK = AllocateAndInitializeSid( &WorldSidAuthority, 1,
                                   SECURITY_WORLD_RID,
                                   0, 0, 0, 0, 0, 0, 0,
                                   &WorldSid );

    DBGCHK( !OK, ( "Couldn't Allocate and initialize SID" ) );

    AceSid[AceCount]           = WorldSid;
    AceMask[AceCount]          = GENERIC_ALL;
    InheritFlags[AceCount]     = 0;
    AceCount++;

    OK = BuildPrintObjectProtection( AceCount,
                                     AceSid,
                                     AceMask,
                                     InheritFlags,
                                     WorldSid,
                                     NULL,
                                     &ServerSD );

    FreeSid( WorldSid );

    return ServerSD;
}


BOOL
BuildPrintObjectProtection(
    IN ULONG AceCount,
    IN PSID *AceSid,
    IN ACCESS_MASK *AceMask,
    IN BYTE *InheritFlags,
    IN PSID WorldSid,
    IN PGENERIC_MAPPING GenericMap,
    OUT PSECURITY_DESCRIPTOR *ppSecurityDescriptor
    )

/*++


Routine Description:

    This routine builds a self-relative security descriptor ready
    to be applied to one of the print manager objects.

    If so indicated, a pointer to the last RID of the SID in the last
    ACE of the DACL is returned and a flag set indicating that the RID
    must be replaced before the security descriptor is applied to an object.
    This is to support USER object protection, which must grant some
    access to the user represented by the object.

    The owner and group of each security descriptor will be set
    to:

                    Owner:  Administrators Alias
                    Group:  Administrators Alias


    The SACL of each of these objects will be set to:


                    Audit
                    Success | Fail
                    WORLD
                    (Write | Delete | WriteDacl | AccessSystemSecurity)



Arguments:

    AceCount - The number of ACEs to be included in the DACL.

    AceSid - Points to an array of SIDs to be granted access by the DACL.
        If the target SAM object is a User object, then the last entry
        in this array is expected to be the SID of an account within the
        domain with the last RID not yet set.  The RID will be set during
        actual account creation.

    AceMask - Points to an array of accesses to be granted by the DACL.
        The n'th entry of this array corresponds to the n'th entry of
        the AceSid array.  These masks should not include any generic
        access types.

    InheritOnly - Inidicates whether each ace is inherit only or not.

    GenericMap - Points to a generic mapping for the target object type.


    UserObject - Indicates whether the target SAM object is a User object
        or not.  If TRUE (it is a User object), then the resultant
        protection will be set up indicating Rid replacement is necessary.

    Result - Receives a pointer to the resultant protection information.
        All access masks in ACLs in the result are mapped to standard and
        specific accesses.


Return Value:

    TBS.

--*/
{



    SECURITY_DESCRIPTOR     Absolute;
    PSECURITY_DESCRIPTOR    Relative;
    PACL                    TmpAcl;
    PACCESS_ALLOWED_ACE     TmpAce;
    ULONG                   SDLength;
    ULONG                   DaclLength;
    ULONG                   i;
    BOOL                    OK;

    //
    // The approach is to set up an absolute security descriptor that
    // looks like what we want and then copy it to make a self-relative
    // security descriptor.
    //

    OK = InitializeSecurityDescriptor( &Absolute,
                                       SECURITY_DESCRIPTOR_REVISION1 );

    DBGCHK( !OK, ( "Failed to initialize security descriptor.  Error %d", GetLastError() ) );

    //
    // Owner
    //
                                                // No owner -- OK??

    OK = SetSecurityDescriptorOwner( &Absolute, NULL, FALSE );

    DBGCHK( !OK, ( "Failed to set security descriptor owner.  Error %d", GetLastError() ) );


    //
    // Group
    //

    OK = SetSecurityDescriptorGroup( &Absolute, WorldSid, FALSE );

    DBGCHK( !OK, ( "Failed to set security descriptor group.  Error %d", GetLastError() ) );




    //
    // Discretionary ACL
    //
    //      Calculate its length,
    //      Allocate it,
    //      Initialize it,
    //      Add each ACE
    //      Set ACE as InheritOnly if necessary
    //      Add it to the security descriptor
    //

    DaclLength = (ULONG)sizeof(ACL);
    for (i=0; i<AceCount; i++) {

        DaclLength += GetLengthSid( AceSid[i] ) +
                      (ULONG)sizeof(ACCESS_ALLOWED_ACE) -
                      (ULONG)sizeof(ULONG);  //Subtract out SidStart field length
    }

    EnterSplSem( );
    TmpAcl = AllocSplMem( DaclLength );
    LeaveSplSem( );

    DBGCHK( !TmpAcl, ( "Out of heap space: Can't allocate ACL." ) );

    OK = InitializeAcl( TmpAcl, DaclLength, ACL_REVISION2 );

    DBGCHK( !OK, ( "Failed to set initialize ACL.  Error %d", GetLastError() ) );

    for (i=0; i<AceCount; i++)
    {
        OK = AddAccessAllowedAce ( TmpAcl, ACL_REVISION2, AceMask[i], AceSid[i] );

        DBGCHK( !OK, ( "Failed to add access-allowed ACE.  Error %d", GetLastError() ) );

        if (InheritFlags[i] != 0)
        {
            OK = GetAce( TmpAcl, i, (LPVOID *)&TmpAce );
            DBGCHK( !OK, ( "Failed to get ACE.  Error %d", GetLastError() ) );

            TmpAce->Header.AceFlags = InheritFlags[i];
        }
    }

    OK = SetSecurityDescriptorDacl (&Absolute, TRUE, TmpAcl, FALSE );
    DBGCHK( !OK, ( "Failed to set security descriptor DACL.  Error %d", GetLastError() ) );



    //
    // Convert the Security Descriptor to Self-Relative
    //
    //      Get the length needed
    //      Allocate that much memory
    //      Copy it
    //      Free the generated absolute ACLs
    //

    SDLength = GetSecurityDescriptorLength( &Absolute );

    EnterSplSem( );
    Relative = AllocSplMem( SDLength );
    LeaveSplSem( );

    DBGCHK( !Relative, ( "Out of heap space: Can't allocate security descriptor" ) );

    OK = MakeSelfRelativeSD(&Absolute, Relative, &SDLength );

    DBGCHK( !OK, ( "Failed to create self-relative security descriptor DACL.  Error %d", GetLastError() ) );

    EnterSplSem( );
    FreeSplMem( Absolute.Dacl, DaclLength );
    LeaveSplSem( );

    *ppSecurityDescriptor = Relative;

    return( OK );
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.