mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
717 lines
22 KiB
717 lines
22 KiB
/*************************************************************************
|
|
* 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;
|
|
}
|
|
|
|
|
|
|