/*++ Copyright (C) Microsoft Corporation, 1990 - 1998 All rights reserved Module Name: thread.cxx Abstract: Contains the worker thread for the Connect To browsing dialog. This is a secondary thread spun off to call the spooler enumerate-printer APIs. EnumPrinters frequently takes a long time to return, especially across the network, so calling it on a separate thread enables the window to be painted without delays. There is a common data structure between the main thread and this worker thread, pBrowseDlgData, defined in browse.h. Author: Created by AndrewBe on 1 Dec 1992 Steve Kiraly (SteveKi) 1 May 1998 Lazar Ivanov (LazarI) Jun-2000 (Win64 fixes) Environment: User Mode -Win32 Revision History: 1 May 1998 moved from winspool.drv to printui.dll --*/ #include "precomp.hxx" #pragma hdrstop #include "browse.hxx" #include "thread.hxx" #if DBG //#define DBG_THREADINFO DBG_INFO #define DBG_THREADINFO DBG_NONE #endif PSAVED_BUFFER_SIZE pFirstSavedBufferSize = NULL; VOID BrowseThread( PBROWSE_DLG_DATA pBrowseDlgData ) { DWORD RequestId; LPTSTR pEnumerateName; /* These may get overwritten before we return */ PVOID pEnumerateObject; /* in the case of BROWSE_THREAD_GET_PRINTER, */ /* so make sure we take a copy. */ for( ; ; ) { RECEIVE_BROWSE_THREAD_REQUEST( pBrowseDlgData, RequestId, pEnumerateName, pEnumerateObject ); DBGMSG( DBG_THREADINFO, ( "BrowseThread: Request ID = %d\n", RequestId ) ); switch( pBrowseDlgData->RequestId ) { case BROWSE_THREAD_ENUM_OBJECTS: BrowseThreadEnumerate( pBrowseDlgData, (PCONNECTTO_OBJECT )pEnumerateObject, pEnumerateName ); break; case BROWSE_THREAD_GET_PRINTER: BrowseThreadGetPrinter( pBrowseDlgData, pEnumerateName, (PPRINTER_INFO_2)pEnumerateObject ); break; case BROWSE_THREAD_TERMINATE: BrowseThreadDelete( pBrowseDlgData ); BrowseThreadTerminate( pBrowseDlgData ); ExitThread( 0 ); } } } /* BrowseThreadEnumerate * * Called to enumerate objects on a given node. * */ VOID BrowseThreadEnumerate( PBROWSE_DLG_DATA pBrowseDlgData, PCONNECTTO_OBJECT pConnectToParent, LPTSTR pParentName ) { DWORD cEnum; DWORD Result; Result = EnumConnectToObjects( pBrowseDlgData, pConnectToParent, pParentName ); DBGMSG( DBG_TRACE, ( "Sending WM_ENUM_OBJECTS_COMPLETE; cEnum == %d\n", pConnectToParent->cSubObjects ) ); ENTER_CRITICAL( pBrowseDlgData ); cEnum = pConnectToParent->cSubObjects; SEND_BROWSE_THREAD_REQUEST_COMPLETE( pBrowseDlgData, WM_ENUM_OBJECTS_COMPLETE, pConnectToParent, cEnum ); LEAVE_CRITICAL( pBrowseDlgData ); DBGMSG( DBG_TRACE, ( "Sent WM_ENUM_OBJECTS_COMPLETE; cEnum == %d\n", pConnectToParent->cSubObjects ) ); } /* * */ VOID BrowseThreadGetPrinter( PBROWSE_DLG_DATA pBrowseDlgData, LPTSTR pPrinterName, LPPRINTER_INFO_2 pPrinterInfo ) { HANDLE hPrinter = NULL; LPPRINTER_INFO_2 pPrinter = NULL; DWORD cbPrinter = 0; DWORD cbNeeded = 0; BOOL OK = FALSE; DBGMSG( DBG_TRACE, ( "BrowseThreadGetPrinter %s\n", pPrinterName ) ); if( OpenPrinter( pPrinterName, &hPrinter, NULL ) ) { /* We don't free up this memory until the thread terminates. * Just leave it so we can use it the next time we're called, * increasing the size when necessary: */ if( cbPrinter = pBrowseDlgData->cbPrinterInfo ) pPrinter = (PPRINTER_INFO_2)AllocSplMem( cbPrinter ); if( pPrinter || !cbPrinter ) { DBGMSG( DBG_TRACE, ( "GetPrinter( %x, %d, %08x, %x )\n", hPrinter, 2, pPrinter, cbPrinter ) ); OK = GetPrinter( hPrinter, 2, (LPBYTE)pPrinter, cbPrinter, &cbNeeded ); DBGMSG( DBG_TRACE, ( "GetPrinter( %x, %d, %08x, %x ) returned %d; cbNeeded = %x\n", hPrinter, 2, pPrinter, cbPrinter, OK, cbNeeded ) ); if( !OK ) { if( GetLastError( ) == ERROR_INSUFFICIENT_BUFFER ) { DBGMSG( DBG_TRACE, ( "ReallocSplMem( %08x, %x, %x )\n", pPrinter, cbPrinter, cbNeeded ) ); if( pPrinter ) pPrinter = (PPRINTER_INFO_2)ReallocSplMem( pPrinter, cbPrinter, cbNeeded ); else pPrinter = (PPRINTER_INFO_2)AllocSplMem( cbNeeded ); if( pPrinter ) { cbPrinter = cbNeeded; DBGMSG( DBG_TRACE, ( "GetPrinter( %x, %d, %08x, %x )\n", hPrinter, 2, pPrinter, cbPrinter ) ); OK = GetPrinter( hPrinter, 2, (LPBYTE)pPrinter, cbPrinter, &cbNeeded ); DBGMSG( DBG_TRACE, ( "GetPrinter( %x, %d, %08x, %x ) returned %d; cbNeeded = %x\n", hPrinter, 2, pPrinter, cbPrinter, OK, cbNeeded ) ); } } } } ClosePrinter(hPrinter); } else { DBGMSG( DBG_WARNING, ( "Couldn't open "TSTR"\n", pPrinterName ) ); } ENTER_CRITICAL( pBrowseDlgData ); if( pBrowseDlgData->pPrinterInfo ) FreeSplMem( pBrowseDlgData->pPrinterInfo ); pBrowseDlgData->pPrinterInfo = pPrinter; pBrowseDlgData->cbPrinterInfo = cbPrinter; LEAVE_CRITICAL( pBrowseDlgData ); SEND_BROWSE_THREAD_REQUEST_COMPLETE( pBrowseDlgData, ( OK ? WM_GET_PRINTER_COMPLETE : WM_GET_PRINTER_ERROR ), pPrinterName, ( OK ? (ULONG_PTR)pPrinter : GetLastError( ) ) ); } /* BrowseThreadDelete * * Called to delete objects on a given node. * */ VOID BrowseThreadDelete( PBROWSE_DLG_DATA pBrowseDlgData ) { PCONNECTTO_OBJECT pConnectToParent; DWORD ObjectsDeleted; DBGMSG( DBG_TRACE, ( "BrowseThreadDelete\n" ) ); pConnectToParent = pBrowseDlgData->pConnectToData; if( pConnectToParent ) { ENTER_CRITICAL( pBrowseDlgData ); ObjectsDeleted = FreeConnectToObjects( &pConnectToParent->pSubObject[0], pConnectToParent->cSubObjects, pConnectToParent->cbPrinterInfo ); pConnectToParent->pSubObject = NULL; pConnectToParent->cSubObjects = 0; pConnectToParent->cbPrinterInfo = 0; LEAVE_CRITICAL( pBrowseDlgData ); } } /* BrowseThreadTerminate * * Frees up the top-level connect-to object, deletes the critical section, * and closes the semaphore, then frees the dialog data. * Note, if there are remaining enumerated objects below the top-level * object, they should have been freed by BrowseThreadDelete. */ VOID BrowseThreadTerminate( PBROWSE_DLG_DATA pBrowseDlgData ) { DBGMSG( DBG_TRACE, ( "BrowseThreadTerminate\n" ) ); pBrowseDlgData->cDecRef(); } /* EnumConnectToObjects * * Calls GetPrinterInfo (which in turn calls EnumPrinters) on the requested * parent node. It allocates an initial buffer and sets up the subobject * fields in the supplied CONNECTTO_OBJECT structure. * * Arguments: * * pParentName - The object on which the enumeration is to be performed. * This may be a domain or server name, depending upon the proprietory * network implementation. If this value is NULL, this indicates * that the top-level objects should be enumerated. * * pConnectToParent - A pointer to a CONNECTTO_OBJECT for the parent node. * The subobject and cbPrinterInfo fields will be filled in * by this function. * * Return: * * FALSE if an error occurred, otherwise TRUE. * * Author: * * andrewbe, July 1992 * */ DWORD EnumConnectToObjects( PBROWSE_DLG_DATA pBrowseDlgData, PCONNECTTO_OBJECT pConnectToParent, LPTSTR pParentName ) { DWORD i, j, cReturned; DWORD cbNeeded; DWORD cbPrinter; LPPRINTER_INFO_1 pPrinter; LPPRINTER_INFO_1 pOrderPrinter; PCONNECTTO_OBJECT pConnectToChildren; BOOL Success = FALSE; DWORD Error = 0; cbPrinter = GetSavedBufferSize( pParentName, NULL ); /* Allocate a buffer that will probably be big enough to hold * all the information we'll need. * This is so that GetPrinterInfo doesn't have to call EnumPrinters twice. */ pPrinter = (PPRINTER_INFO_1)AllocSplMem( cbPrinter ); if( pPrinter ) { pPrinter = (LPPRINTER_INFO_1)GetPrinterInfo( PRINTER_ENUM_NAME | PRINTER_ENUM_REMOTE, pParentName, 1, (LPBYTE)pPrinter, &cbPrinter, &cReturned, &cbNeeded, &Error ); if( pPrinter ) { /* Allocate an array of CONNECTTO_OBJECTs, one for each object returned: */ if( cReturned > 0 ) { pConnectToChildren = (PCONNECTTO_OBJECT)AllocSplMem( cReturned * sizeof( CONNECTTO_OBJECT ) ); SaveBufferSize( pParentName, cbPrinter ); } else { FreeSplMem( pPrinter ); cbPrinter = 0; pConnectToChildren = EMPTY_CONTAINER; } if( pConnectToChildren && ( pConnectToChildren != EMPTY_CONTAINER ) ) { /* * Allocate a buffer to sort the printer info entries. Once ordered * copy the entries back to original area and free the buffer. * * */ pOrderPrinter = (PPRINTER_INFO_1)AllocSplMem( cReturned * sizeof( PRINTER_INFO_1 ) ); if( pOrderPrinter ) { pOrderPrinter[0] = pPrinter[0]; /* Copy 1st printer info */ for( i = 1; i < cReturned; i++ ) { for ( j = 0; j< i; j++ ) { if ( _wcsicmp(pPrinter[i].pDescription, pOrderPrinter[j].pDescription) < 0 ) { memmove(&pOrderPrinter[j+1], &pOrderPrinter[j], sizeof( PRINTER_INFO_1 ) * (i-j)); break; } } pOrderPrinter[j] = pPrinter[i]; } memmove(pPrinter, pOrderPrinter, cReturned * sizeof( PRINTER_INFO_1 )); FreeSplMem( pOrderPrinter ); } /* * Build ConnectTo array */ for( i = 0; i < cReturned; i++ ) { pConnectToChildren[i].pPrinterInfo = &pPrinter[i]; pConnectToChildren[i].pSubObject = NULL; pConnectToChildren[i].cSubObjects = 0; pConnectToChildren[i].cbPrinterInfo = 0; } ENTER_CRITICAL( pBrowseDlgData ); pConnectToParent->pSubObject = pConnectToChildren; pConnectToParent->cSubObjects = cReturned; pConnectToParent->cbPrinterInfo = cbPrinter; LEAVE_CRITICAL( pBrowseDlgData ); Success = TRUE; } } } SetCursor( pBrowseDlgData->hcursorWait ); return Error; } /* GetPrinterInfo * * Calls EnumPrinters using the supplied parameters. * If the buffer is not big enough, it is reallocated, * and a second attempt is made. * * Returns a pointer to the buffer of printer info. * * pPrinters may be NULL, in which case *pcbPrinters must equal 0. * * andrewbe, April 1992 */ #define MAX_RETRIES 5 /* How many times we retry if we get ERROR_INSUFFICIENT_BUFFER */ LPBYTE GetPrinterInfo( IN DWORD Flags, IN LPTSTR Name, IN DWORD Level, IN LPBYTE pPrinters, OUT LPDWORD pcbPrinters, OUT LPDWORD pcReturned, OUT LPDWORD pcbNeeded OPTIONAL, OUT LPDWORD pError OPTIONAL ) { DWORD cbCurrent; BOOL rc; DWORD cbNeeded; DWORD Error = 0; DWORD Retry; /* cbCurrent holds our current buffer size. * This will change if we have to realloc: */ cbCurrent = *pcbPrinters; DBGMSG( DBG_TRACE, ( "Calling EnumPrinters( %08x, "TSTR", %d, %08x, 0x%x )\n", Flags, ( Name ? Name : TEXT("NULL") ), Level, pPrinters, cbCurrent ) ); rc = EnumPrinters( Flags, Name, Level, pPrinters, cbCurrent, &cbNeeded, pcReturned ); DBGMSG( DBG_TRACE, ( "EnumPrinters( %08x, "TSTR", %d, %08x, 0x%x ) returned %d; cbNeeded 0x%x; cReturned 0x%x\n", Flags, ( Name ? Name : TEXT("NULL") ), Level, pPrinters, cbCurrent, rc, cbNeeded, *pcReturned ) ); Retry = 1; while (!rc) { Error = GetLastError( ); if ( Error != ERROR_INSUFFICIENT_BUFFER || Retry > MAX_RETRIES ) { break; } /* Hopefully the error will be buffer not big enough. * If not, we'll have to bomb out: */ /* The problem here is that, the second time we call EnumPrinters, * the size of buffer we need may have increased because new devices * have come on line, or the server that provided the list of objects * has updated itself. In this case, we should try again, * but don't keep trying indefinitely, because something might be amiss. */ DBGMSG( DBG_THREADINFO, ( "EnumPrinters failed with ERROR_INSUFFICIENT_BUFFER; buffer size = %d\nRetry #%d with buffer size = %d\n", cbCurrent, Retry, cbNeeded ) ); if( cbCurrent != 0 ) FreeSplMem(pPrinters); pPrinters = (PBYTE)AllocSplMem( cbNeeded ); if( pPrinters ) { cbCurrent = cbNeeded; DBGMSG( DBG_THREADINFO, ( "Calling EnumPrinters( %08x, "TSTR", %d, %08x, 0x%x )\n", Flags, ( Name ? Name : TEXT("NULL") ), Level, pPrinters, cbCurrent ) ); rc = EnumPrinters( Flags, Name, Level, pPrinters, cbCurrent, &cbNeeded, pcReturned ); DBGMSG( DBG_THREADINFO, ( "EnumPrinters( %08x, "TSTR", %d, %08x, 0x%x )\nreturned %d; cbNeeded 0x%x; cReturned 0x%x\n", Flags, ( Name ? Name : TEXT("NULL") ), Level, pPrinters, cbCurrent, rc, cbNeeded, *pcReturned ) ); } Retry++; } if( !rc ) { DBGMSG( DBG_WARNING, ( "EnumPrinters failed: Error %d\n", Error ) ); if( cbCurrent != 0 ) FreeSplMem(pPrinters); pPrinters = NULL; cbCurrent = 0; *pcReturned = 0; Error = GetLastError( ); } *pcbPrinters = cbCurrent; if( pError ) *pError = Error; return pPrinters; } DWORD GetSavedBufferSize( LPTSTR pName, PSAVED_BUFFER_SIZE *ppSavedBufferSize OPTIONAL ) { PSAVED_BUFFER_SIZE pSavedBufferSize; if( !pName ) pName = TEXT(""); pSavedBufferSize = pFirstSavedBufferSize; while( pSavedBufferSize ) { if( !_tcscmp( pSavedBufferSize->pName, pName ) ) { if( ppSavedBufferSize ) *ppSavedBufferSize = pSavedBufferSize; return pSavedBufferSize->Size; } pSavedBufferSize = pSavedBufferSize->pNext; } if( ppSavedBufferSize ) *ppSavedBufferSize = NULL; return 0; } VOID SaveBufferSize( LPTSTR pName, DWORD Size ) { PSAVED_BUFFER_SIZE pSavedBufferSize; if( !pName ) pName = TEXT(""); if( GetSavedBufferSize( pName, &pSavedBufferSize ) ) { if( pSavedBufferSize->Size < Size ) { DBGMSG( DBG_TRACE, ( "Updating buffer size for "TSTR" from %d (0x%x) to %d (0x%x)\n", ( pName ? pName : TEXT("NULL") ), pSavedBufferSize->Size, pSavedBufferSize->Size, Size, Size ) ); pSavedBufferSize->Size = Size; } } else { DBGMSG( DBG_TRACE, ( "Saving buffer size %d (0x%x) for "TSTR"\n", Size, Size, ( pName ? pName : TEXT("NULL") ) ) ); if( pSavedBufferSize = (PSAVED_BUFFER_SIZE)AllocSplMem( sizeof( SAVED_BUFFER_SIZE ) ) ) { pSavedBufferSize->pName = AllocSplStr( pName ); pSavedBufferSize->Size = Size; pSavedBufferSize->pNext = pFirstSavedBufferSize; pFirstSavedBufferSize = pSavedBufferSize; } } }