717 lines
22 KiB
C
717 lines
22 KiB
C
/*************************************************************************
|
||
* Microsoft Windows NT *
|
||
* *
|
||
* Copyright(c) Microsoft Corp., 1994 *
|
||
* *
|
||
* Revision History: *
|
||
* *
|
||
* Jan. 24,94 Koti Created *
|
||
* Jan. 29,96 JBallard Modified *
|
||
* *
|
||
* Description: *
|
||
* *
|
||
* This file contains functions for carrying out LPD printing *
|
||
* *
|
||
*************************************************************************/
|
||
|
||
|
||
|
||
#include "lpd.h"
|
||
|
||
|
||
extern FILE * pErrFile; // Debug output log file.
|
||
|
||
BOOL GetSpoolFileName(
|
||
HANDLE hPrinter,
|
||
PSOCKCONN pscConn,
|
||
PCHAR *ppwchSpoolPath
|
||
);
|
||
|
||
VOID CleanupConn( PSOCKCONN pscConn);
|
||
|
||
/*****************************************************************************
|
||
* *
|
||
* ProcessJob(): *
|
||
* This function receives the subcommand from the client to expect the *
|
||
* control file, then accepts the control file, then the subcommand to *
|
||
* expect the data file, then accepts the data and then hands it over to *
|
||
* the spooler to print. *
|
||
* If the very first subcommand was to abort the job, we just return. *
|
||
* *
|
||
* Returns: *
|
||
* Nothing *
|
||
* *
|
||
* Parameters: *
|
||
* pscConn (IN-OUT): pointer to SOCKCONN structure for this connection *
|
||
* *
|
||
* History: *
|
||
* Jan.24 94 Koti Created *
|
||
* *
|
||
*****************************************************************************/
|
||
|
||
VOID ProcessJob( PSOCKCONN pscConn )
|
||
{
|
||
|
||
// the main functionality of LPD implemented in this function!
|
||
|
||
CHAR chSubCmdCode;
|
||
DWORD cbTotalDataLen;
|
||
DWORD cbBytesSpooled;
|
||
DWORD cbBytesToRead;
|
||
DWORD cbDataBufLen;
|
||
DWORD cbBytesRemaining;
|
||
DWORD dwErrcode;
|
||
CHAR chAck;
|
||
HANDLE hDFile;
|
||
PDFILE_ENTRY pDFile;
|
||
PCHAR lpFileName;
|
||
PCHAR pchDataBuf;
|
||
|
||
DWORD ClientCmd;
|
||
// initialize the printer that the client wants to use
|
||
|
||
#ifdef DBG
|
||
if( !pscConn || !pscConn->pchPrinterName
|
||
|| strstr( pscConn->pchPrinterName, "debug" )
|
||
){
|
||
print__sockconn( "ProcessJob: entered", pscConn );
|
||
}
|
||
#endif
|
||
|
||
|
||
if ( InitializePrinter( pscConn ) != NO_ERROR )
|
||
{
|
||
PCHAR aszStrings[2];
|
||
|
||
aszStrings[0] = pscConn->pchPrinterName;
|
||
aszStrings[1] = pscConn->szIPAddr;
|
||
|
||
LpdReportEvent( LPDLOG_NONEXISTENT_PRINTER, 2, aszStrings, 0 );
|
||
|
||
pscConn->fLogGenericEvent = FALSE;
|
||
|
||
return; // fatal error: exit
|
||
}
|
||
|
||
// thank the client for the command. If we couldn't send, quit
|
||
|
||
if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR )
|
||
{
|
||
LPD_DEBUG( "ProcessJob(): couldn't ACK to \"receive job\"\n" );
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
// 2 subcommands expected: "receive control file" and "receive data file"
|
||
// They can come in any order. (One of the two subcommands can also be
|
||
// "abort this job" in which case we abort the job and return).
|
||
|
||
for ( ; ; )
|
||
{
|
||
|
||
// don't need the previous one (in fact, pchCommand field is reused)
|
||
|
||
if ( pscConn->pchCommand != NULL )
|
||
{
|
||
LocalFree( pscConn->pchCommand );
|
||
|
||
pscConn->pchCommand = NULL;
|
||
}
|
||
|
||
// get the first subcommand from the client
|
||
// ------------------------------ N = 02, 03, or 01
|
||
// | N | Count | SP | Name | LF | Count => control file length
|
||
// ------------------------------ Name => controlfile name
|
||
|
||
ClientCmd = GetCmdFromClient( pscConn );
|
||
switch ( ClientCmd )
|
||
{
|
||
case CONNECTION_CLOSED:
|
||
|
||
// performance enhancement: close the socket here and then start
|
||
// printing: printing could take several seconds,
|
||
// so don't tie up client
|
||
|
||
|
||
if ( pscConn->sSock != INVALID_SOCKET )
|
||
{
|
||
SureCloseSocket( pscConn->sSock );
|
||
pscConn->sSock = INVALID_SOCKET;
|
||
}
|
||
|
||
// if we came this far, everything went as planned.
|
||
// tell spooler that we are done spooling: go ahead and print!
|
||
|
||
PrintData( pscConn );
|
||
pscConn->wState = LPDS_ALL_WENT_WELL;
|
||
return;
|
||
|
||
case NO_ERROR:
|
||
|
||
// Not yet done, back to outer loop.
|
||
|
||
break;
|
||
|
||
case SOCKET_ERROR:
|
||
|
||
default:
|
||
|
||
// If we didn't get a subcommand from client, it's bad news!
|
||
// client died or something catastophic like that!
|
||
|
||
LOGIT(("ProcessJob:GetCmdFromClient %d failed %d.\n",
|
||
ClientCmd, GetLastError() ));
|
||
|
||
return; // the thread exits without doing anything
|
||
}
|
||
|
||
chSubCmdCode = pscConn->pchCommand[0];
|
||
|
||
switch (chSubCmdCode) {
|
||
|
||
case LPDCS_RECV_CFILE: // N = 02 ("receive control file")
|
||
|
||
// client is going to give us a control file: prepare for it
|
||
|
||
pscConn->wState = LPDSS_RECVD_CFILENAME;
|
||
|
||
|
||
// get the controlfile name, file size out of the command
|
||
|
||
if ( ParseSubCommand( pscConn, &cbTotalDataLen, &lpFileName ) != NO_ERROR )
|
||
{
|
||
PCHAR aszStrings[2]={ pscConn->szIPAddr, NULL };
|
||
|
||
LpdReportEvent( LPDLOG_BAD_FORMAT, 1, aszStrings, 0 );
|
||
|
||
pscConn->fLogGenericEvent = FALSE;
|
||
|
||
return; // fatal error: exit
|
||
}
|
||
|
||
// tell client we got the name of the controlfile ok
|
||
|
||
if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR )
|
||
{
|
||
return; // fatal error: exit
|
||
}
|
||
|
||
|
||
// Get the control file (we already know how big it is)
|
||
|
||
if ( GetControlFileFromClient( pscConn, cbTotalDataLen, lpFileName ) != NO_ERROR )
|
||
{
|
||
LPD_DEBUG( "GetControlFileFromClient() failed in ProcessJob()\n" );
|
||
|
||
return;
|
||
}
|
||
|
||
pscConn->wState = LPDSS_RECVD_CFILE;
|
||
|
||
// tell client we got the controlfile and things look good so far!
|
||
|
||
if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR )
|
||
{
|
||
LOGIT(("ProcessJob:%d: ReplyToClient failed %d\n",
|
||
__LINE__, GetLastError() ));
|
||
|
||
return; // fatal error: exit
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case LPDCS_RECV_DFILE: // N = 03 ("receive data file")
|
||
|
||
pscConn->wState = LPDSS_RECVD_DFILENAME;
|
||
|
||
// tell client we got the name of the datafile ok
|
||
|
||
if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR )
|
||
{
|
||
LOGIT(("ProcessJob:%d: ReplyToClient failed %d\n",
|
||
__LINE__, GetLastError() ));
|
||
|
||
return; // fatal error: exit
|
||
}
|
||
|
||
|
||
// get the datafile name, data size out of the command
|
||
|
||
if ( ParseSubCommand( pscConn, &cbTotalDataLen, &lpFileName ) != NO_ERROR )
|
||
{
|
||
|
||
PCHAR aszStrings[2]={ pscConn->szIPAddr, NULL };
|
||
|
||
LpdReportEvent( LPDLOG_BAD_FORMAT, 1, aszStrings, 0 );
|
||
|
||
pscConn->fLogGenericEvent = FALSE;
|
||
|
||
LOGIT(("ProcessJob:%d: ParseSubCommand failed %d\n",
|
||
__LINE__, GetLastError() ));
|
||
|
||
return; // fatal error: exit
|
||
}
|
||
|
||
|
||
// at this point, we know exactly how much data is coming.
|
||
// Allocate buffer to hold the data. If data is more than
|
||
// LPD_BIGBUFSIZE, keep reading and spooling several times
|
||
// over until data is done
|
||
|
||
pscConn->wState = LPDSS_SPOOLING;
|
||
|
||
pDFile = LocalAlloc( LMEM_FIXED, sizeof(DFILE_ENTRY) );
|
||
|
||
if (pDFile == NULL) {
|
||
LocalFree( lpFileName );
|
||
|
||
LOGIT(("ProcessJob:%d: LocalAlloc failed %d\n",
|
||
__LINE__, GetLastError() ));
|
||
|
||
return; // Fatal Error
|
||
}
|
||
|
||
pDFile->cbDFileLen = cbTotalDataLen;
|
||
pDFile->pchDFileName = lpFileName;
|
||
|
||
if ( !GetSpoolFileName( pscConn->hPrinter, pscConn, &lpFileName ) )
|
||
{
|
||
LPD_DEBUG( "ERROR: GetSpoolFileName() failed in ProcessJob\n" );
|
||
LocalFree( pDFile->pchDFileName );
|
||
LocalFree( pDFile );
|
||
return;
|
||
}
|
||
|
||
//
|
||
// GetTempFileName has already created this file, so use OPEN_ALWAYS.
|
||
// Also, use FILE_ATTRIBUTE_TEMPORARY so that it will be faster
|
||
// FILE_FLAG_SEQUENTIAL_SCAN, ntbug 79854, MohsinA, 03-Jun-97.
|
||
//
|
||
|
||
pDFile->hDataFile = CreateFile( lpFileName,
|
||
GENERIC_READ|GENERIC_WRITE,
|
||
FILE_SHARE_READ,
|
||
NULL,
|
||
OPEN_ALWAYS,
|
||
FILE_ATTRIBUTE_NORMAL
|
||
|FILE_ATTRIBUTE_TEMPORARY
|
||
|FILE_FLAG_DELETE_ON_CLOSE
|
||
|FILE_FLAG_SEQUENTIAL_SCAN
|
||
,
|
||
NULL );
|
||
|
||
|
||
LocalFree( lpFileName );
|
||
|
||
if ( pDFile->hDataFile == INVALID_HANDLE_VALUE )
|
||
{
|
||
LPD_DEBUG( "ERROR: CreatFile() failed in ProcessJob \n" );
|
||
LocalFree( pDFile->pchDFileName );
|
||
LocalFree( pDFile );
|
||
return;
|
||
}
|
||
|
||
cbBytesToRead = (cbTotalDataLen > LPD_BIGBUFSIZE ) ?
|
||
LPD_BIGBUFSIZE : cbTotalDataLen;
|
||
|
||
pchDataBuf = LocalAlloc( LMEM_FIXED, cbBytesToRead );
|
||
|
||
if ( pchDataBuf == NULL )
|
||
{
|
||
LOGIT(("ProcessJob:%d: LocalAlloc failed %d\n",
|
||
__LINE__, GetLastError() ));
|
||
|
||
CloseHandle(pDFile->hDataFile);
|
||
pDFile->hDataFile = INVALID_HANDLE_VALUE;
|
||
LocalFree( pDFile->pchDFileName );
|
||
LocalFree( pDFile );
|
||
return; // fatal error: exit
|
||
}
|
||
|
||
cbBytesSpooled = 0;
|
||
|
||
cbBytesRemaining = cbTotalDataLen;
|
||
|
||
// keep receiving until we have all the data client said it
|
||
// would send
|
||
|
||
while( cbBytesSpooled < cbTotalDataLen )
|
||
{
|
||
if ( ReadData( pscConn->sSock, pchDataBuf,
|
||
cbBytesToRead ) != NO_ERROR )
|
||
{
|
||
LPD_DEBUG( "ProcessJob:ReadData failed, job aborted)\n" );
|
||
|
||
LocalFree( pchDataBuf );
|
||
CloseHandle(pDFile->hDataFile);
|
||
pDFile->hDataFile = INVALID_HANDLE_VALUE;
|
||
LocalFree( pDFile->pchDFileName );
|
||
LocalFree( pDFile );
|
||
return; // fatal error: exit
|
||
}
|
||
|
||
cbDataBufLen = cbBytesToRead;
|
||
|
||
if ( SpoolData( pDFile->hDataFile, pchDataBuf, cbDataBufLen ) != NO_ERROR )
|
||
{
|
||
LPD_DEBUG( "SpoolData() failed in ProcessJob(): job aborted)\n" );
|
||
|
||
LocalFree( pchDataBuf );
|
||
CloseHandle(pDFile->hDataFile);
|
||
pDFile->hDataFile = INVALID_HANDLE_VALUE;
|
||
LocalFree( pDFile->pchDFileName );
|
||
LocalFree( pDFile );
|
||
return; // fatal error: exit
|
||
}
|
||
|
||
cbBytesSpooled += cbBytesToRead;
|
||
|
||
cbBytesRemaining -= cbBytesToRead;
|
||
|
||
cbBytesToRead = (cbBytesRemaining > LPD_BIGBUFSIZE ) ?
|
||
LPD_BIGBUFSIZE : cbBytesRemaining;
|
||
|
||
}
|
||
|
||
LocalFree( pchDataBuf );
|
||
|
||
InsertTailList( &pscConn->DFile_List, &pDFile->Link );
|
||
|
||
// LPR client sends one byte (of 0 bits) after sending data
|
||
|
||
dwErrcode = ReadData( pscConn->sSock, &chAck, 1 );
|
||
|
||
if ( ( dwErrcode != NO_ERROR ) || (chAck != LPD_ACK ) )
|
||
{
|
||
return;
|
||
}
|
||
|
||
// tell client we got the data and things look good so far!
|
||
|
||
if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR )
|
||
{
|
||
|
||
LOGIT(("ProcessJob:%d: ReplyToClient failed %d\n",
|
||
__LINE__, GetLastError() ));
|
||
|
||
return; // fatal error: exit
|
||
}
|
||
break;
|
||
|
||
|
||
case LPDCS_ABORT_JOB: // N = 01 ("abort this job")
|
||
|
||
// client asked us to abort the job: tell him "ok" and quit!
|
||
|
||
ReplyToClient( pscConn, LPD_ACK );
|
||
|
||
pscConn->wState = LPDS_ALL_WENT_WELL; // we did what client wanted
|
||
|
||
return;
|
||
|
||
|
||
// unknown subcommand: log the event and quit
|
||
|
||
default:
|
||
{
|
||
PCHAR aszStrings[2]={ pscConn->szIPAddr, NULL };
|
||
|
||
LpdReportEvent( LPDLOG_MISBEHAVED_CLIENT, 1, aszStrings, 0 );
|
||
|
||
pscConn->fLogGenericEvent = FALSE;
|
||
|
||
LPD_DEBUG( "ProcessJob(): invalid subcommand, request rejected\n" );
|
||
|
||
return;
|
||
}
|
||
|
||
}
|
||
|
||
} // done processing both subcommands
|
||
|
||
} // end ProcessJob()
|
||
|
||
/*****************************************************************************
|
||
* *
|
||
* GetControlFileFromClient(): *
|
||
* This function receives the control file from the client. In the *
|
||
* previsous subcommand, the client told us how many bytes there are in *
|
||
* the control file. *
|
||
* Also,after reading all the bytes, we read the 1 byte "ack" from client *
|
||
* *
|
||
* Returns: *
|
||
* NO_ERROR if everything went well *
|
||
* ErrorCode if something went wrong somewhere *
|
||
* *
|
||
* Parameters: *
|
||
* pscConn (IN-OUT): pointer to SOCKCONN structure for this connection *
|
||
* *
|
||
* History: *
|
||
* Jan.24, 94 Koti Created *
|
||
* *
|
||
*****************************************************************************/
|
||
|
||
DWORD
|
||
GetControlFileFromClient( PSOCKCONN pscConn, DWORD FileSize, PCHAR FileName )
|
||
{
|
||
|
||
PCHAR pchAllocBuf;
|
||
DWORD cbBytesToRead;
|
||
PCFILE_ENTRY pCFile;
|
||
|
||
|
||
if (FileSize > LPD_MAX_CONTROL_FILE_LEN)
|
||
{
|
||
return( (DWORD)LPDERR_NOBUFS );
|
||
}
|
||
|
||
pCFile = LocalAlloc( LMEM_FIXED, sizeof(CFILE_ENTRY) );
|
||
if (pCFile == NULL) {
|
||
return( (DWORD)LPDERR_NOBUFS );
|
||
}
|
||
|
||
pCFile->cbCFileLen = FileSize;
|
||
pCFile->pchCFileName = FileName;
|
||
|
||
// we know how big the control file is going to be: alloc space for it
|
||
// Client sends one byte after sending the control file: read it along
|
||
// with the rest of the data
|
||
|
||
cbBytesToRead = FileSize + 1;
|
||
|
||
pchAllocBuf = LocalAlloc( LMEM_FIXED, cbBytesToRead );
|
||
|
||
if (pchAllocBuf == NULL)
|
||
{
|
||
LocalFree( pCFile );
|
||
|
||
return( (DWORD)LPDERR_NOBUFS );
|
||
}
|
||
|
||
// now read the data (and the trailing byte) into this allocated buffer
|
||
|
||
if ( ReadData( pscConn->sSock, pchAllocBuf, cbBytesToRead ) != NO_ERROR )
|
||
{
|
||
LocalFree( pCFile );
|
||
|
||
LocalFree( pchAllocBuf );
|
||
|
||
return( LPDERR_NORESPONSE );
|
||
}
|
||
|
||
// if the trailing byte is not zero, treat it as job aborted (though
|
||
// we don't expect this to happen really)
|
||
|
||
if ( pchAllocBuf[cbBytesToRead-1] != 0 )
|
||
{
|
||
LocalFree( pchAllocBuf );
|
||
|
||
LocalFree( pCFile );
|
||
|
||
LPD_DEBUG( "GetControlFileFromClient: got data followed by a NAK!\n");
|
||
|
||
return( LPDERR_JOBABORTED );
|
||
}
|
||
|
||
pCFile->pchCFile = pchAllocBuf;
|
||
InsertTailList( &pscConn->CFile_List, &pCFile->Link );
|
||
|
||
return( NO_ERROR );
|
||
|
||
|
||
} // end GetControlFileFromClient()
|
||
|
||
|
||
/*****************************************************************************
|
||
* *
|
||
* GetSpoolFileName(): *
|
||
* This function figures out where to put the spool file. *
|
||
* *
|
||
* Returns: *
|
||
* TRUE if spool location found. *
|
||
* FALSE if no spool location available. *
|
||
* *
|
||
* Parameters: *
|
||
* hPrinter (IN): Handle to printer for which we are spooling *
|
||
* pscConn (IN-OUT): pointer to SOCKCONN structure for this connection *
|
||
* ppchSpoolPath (IN-OUT): Address of pointer which will receive the *
|
||
* spool path. *
|
||
* *
|
||
* History: *
|
||
* Nov.21, 94 JBallard *
|
||
* *
|
||
*****************************************************************************/
|
||
BOOL
|
||
GetSpoolFileName
|
||
(
|
||
HANDLE hPrinter,
|
||
PSOCKCONN pscConn,
|
||
PCHAR *ppchSpoolPath
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function comes up with a name for a spool file that we should be
|
||
able to write to.
|
||
|
||
Note: The file name returned has already been created.
|
||
|
||
Arguments:
|
||
|
||
hPrinter - handle to the printer that we want a spool file for.
|
||
|
||
ppchSpoolFileName: pointer that will receive an allocated buffer
|
||
containing the file name to spool to. CALLER
|
||
MUST FREE. Use LocalFree().
|
||
|
||
|
||
Return Value:
|
||
|
||
TRUE if everything goes as expected.
|
||
FALSE if anything goes wrong.
|
||
|
||
--*/
|
||
{
|
||
PBYTE pBuffer = NULL;
|
||
DWORD dwAllocSize;
|
||
DWORD dwNeeded;
|
||
PCHAR pchSpoolPath = NULL;
|
||
DWORD dwRetval;
|
||
|
||
pchSpoolPath = LocalAlloc( LMEM_FIXED, 2 * MAX_PATH + 1 );
|
||
|
||
if ( pchSpoolPath == NULL )
|
||
{
|
||
goto Failure;
|
||
}
|
||
|
||
//
|
||
// In order to find out where the spooler's directory is, we add
|
||
// call GetPrinterData with DefaultSpoolDirectory.
|
||
//
|
||
|
||
dwAllocSize = WCS_LEN( MAX_PATH + 1 );
|
||
|
||
for (;;)
|
||
{
|
||
pBuffer = LocalAlloc( LMEM_FIXED, dwAllocSize );
|
||
|
||
if ( pBuffer == NULL )
|
||
{
|
||
goto Failure;
|
||
}
|
||
|
||
if ( GetPrinterData( hPrinter,
|
||
SPLREG_DEFAULT_SPOOL_DIRECTORY,
|
||
NULL,
|
||
pBuffer,
|
||
dwAllocSize,
|
||
&dwNeeded ) == ERROR_SUCCESS )
|
||
{
|
||
break;
|
||
}
|
||
|
||
if ( ( dwNeeded < dwAllocSize ) ||( GetLastError() != ERROR_MORE_DATA ))
|
||
{
|
||
goto Failure;
|
||
}
|
||
|
||
//
|
||
// Free the current buffer and increase the size that we try to allocate
|
||
// next time around.
|
||
//
|
||
|
||
LocalFree( pBuffer );
|
||
|
||
dwAllocSize = dwNeeded;
|
||
}
|
||
|
||
if( !GetTempFileName( (LPSTR)pBuffer, "LprSpl", 0, pchSpoolPath ))
|
||
{
|
||
goto Failure;
|
||
}
|
||
|
||
//
|
||
// At this point, the spool file name should be done. Free the structure
|
||
// we used to get the spooler temp dir and return.
|
||
//
|
||
|
||
LocalFree( pBuffer );
|
||
|
||
*ppchSpoolPath = pchSpoolPath;
|
||
|
||
return( TRUE );
|
||
|
||
Failure:
|
||
|
||
//
|
||
// Clean up and fail.
|
||
//
|
||
if ( pBuffer != NULL )
|
||
{
|
||
LocalFree( pBuffer );
|
||
}
|
||
|
||
if ( pchSpoolPath != NULL )
|
||
{
|
||
LocalFree( pchSpoolPath );
|
||
}
|
||
|
||
return( FALSE );
|
||
}
|
||
|
||
VOID CleanupCFile( PCFILE_ENTRY pCFile )
|
||
{
|
||
if (pCFile->pchCFileName != NULL) {
|
||
LocalFree( pCFile->pchCFileName );
|
||
pCFile->pchCFileName = NULL;
|
||
}
|
||
if (pCFile->pchCFile != NULL) {
|
||
LocalFree( pCFile->pchCFile );
|
||
pCFile->pchCFile = NULL;
|
||
}
|
||
LocalFree( pCFile );
|
||
}
|
||
|
||
VOID CleanupDFile( PDFILE_ENTRY pDFile )
|
||
{
|
||
if (pDFile->pchDFileName != NULL) {
|
||
LocalFree( pDFile->pchDFileName );
|
||
pDFile->pchDFileName = NULL;
|
||
}
|
||
if (pDFile->hDataFile != INVALID_HANDLE_VALUE) {
|
||
CloseHandle(pDFile->hDataFile);
|
||
}
|
||
LocalFree( pDFile );
|
||
}
|
||
|
||
VOID CleanupConn( PSOCKCONN pscConn)
|
||
{
|
||
LIST_ENTRY *pTmpList;
|
||
CFILE_ENTRY *pCFile;
|
||
DFILE_ENTRY *pDFile;
|
||
|
||
while ( !IsListEmpty( &pscConn->CFile_List ) ) {
|
||
pTmpList = RemoveHeadList( &pscConn->CFile_List );
|
||
pCFile = CONTAINING_RECORD( pTmpList,
|
||
CFILE_ENTRY,
|
||
Link );
|
||
CleanupCFile( pCFile );
|
||
}
|
||
|
||
while ( !IsListEmpty( &pscConn->DFile_List ) ) {
|
||
pTmpList = RemoveHeadList( &pscConn->DFile_List );
|
||
pDFile = CONTAINING_RECORD( pTmpList,
|
||
DFILE_ENTRY,
|
||
Link );
|
||
CleanupDFile( pDFile );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
|