Source code of Windows XP (NT5)
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.

1044 lines
25 KiB

  1. ////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // MacPrint - Windows NT Print Server for Macintosh Clients
  4. // Copyright (c) Microsoft Corp., 1991, 1992, 1993
  5. //
  6. // macpsq.c - Macintosh Print Service queue service routines
  7. //
  8. // Author: Frank D. Byrum
  9. // adapted from MacPrint from LAN Manager Services for Macintosh
  10. //
  11. // DESCRIPTION:
  12. // This module provides the routines to manage an NT Printer Object
  13. // on an AppleTalk network. A QueueServiceThread is started for
  14. // each NT Printer Object that is to be shared on the AppleTalk
  15. // network. This thread publishes an NBP name for the printer,
  16. // listens for connection requests from Macintosh clients, and
  17. // handles the communication between the Macintosh and the NT
  18. // Print Spooler.
  19. //
  20. ////////////////////////////////////////////////////////////////////////////////
  21. #include <stdio.h>
  22. #include <stdlib.h>
  23. #include <windows.h>
  24. #include <winsvc.h>
  25. #include <macps.h>
  26. #include <macpsmsg.h>
  27. #include <debug.h>
  28. extern HANDLE DbgSpoolFile;
  29. extern PQR pqrHead;
  30. ////////////////////////////////////////////////////////////////////////////////
  31. //
  32. // QueueServiceThread() - Thread routine to service an NT Printer Object
  33. //
  34. // DESCRIPTION:
  35. // This routine fields all AppleTalk PAP requests and service all
  36. // events associtated with each job.
  37. //
  38. // pqr ===> points to the Print Queue record for the printer to
  39. // be serviced.
  40. //
  41. // On exit from this routine, the queue is shut down and all resources
  42. // associated with the queue are freed.
  43. //
  44. ////////////////////////////////////////////////////////////////////////////////
  45. void
  46. QueueServiceThread(
  47. PQR pqr
  48. )
  49. {
  50. PQR * ppQr;
  51. PJR pjr;
  52. DBGPRINT(("Enter QueueServiceThread for %ws\n", pqr->pPrinterName));
  53. if (CreateListenerSocket(pqr) != NO_ERROR)
  54. {
  55. DBGPRINT(("ERROR: failed to create session listener.\n"));
  56. pqr->ExitThread = TRUE;
  57. }
  58. else
  59. {
  60. ReportEvent(hEventLog,
  61. EVENTLOG_INFORMATION_TYPE,
  62. EVENT_CATEGORY_ADMIN,
  63. EVENT_PRINTER_REGISTERED,
  64. NULL,
  65. 1,
  66. 0,
  67. &(pqr->pPrinterName),
  68. NULL);
  69. }
  70. // service jobs until told to exit
  71. while (!pqr->ExitThread)
  72. {
  73. //
  74. // service PAP events. HandleNextPAPEvent will wait for up to 2
  75. // seconds for a read or open to occur on this queue. If one
  76. // happens, pjr is the job record the event happened on. If
  77. // pjr is NULL, then no event was found.
  78. //
  79. HandleNextPAPEvent(pqr);
  80. //
  81. // check for service stop
  82. //
  83. if (WaitForSingleObject(hevStopRequested, 0) == WAIT_OBJECT_0)
  84. {
  85. DBGPRINT(("%ws thread gets service stop request\n", pqr->pPrinterName));
  86. pqr->ExitThread = TRUE;
  87. break;
  88. }
  89. } // end while !ExitThread
  90. DBGPRINT(("%ws received signal to die\n", pqr->pPrinterName));
  91. // Remove all outstanding pending jobs
  92. DBGPRINT(("%ws removing pending jobs\n", pqr->pPrinterName));
  93. while ((pjr = pqr->PendingJobs) != NULL)
  94. {
  95. RemoveJob(pjr);
  96. }
  97. // close the listener
  98. DBGPRINT(("%ws closing listener socket\n", pqr->pPrinterName));
  99. if (pqr->sListener != INVALID_SOCKET)
  100. {
  101. closesocket(pqr->sListener);
  102. // report printer removed
  103. DBGPRINT(("%ws reporting printer removed\n", pqr->pPrinterName));
  104. ReportEvent(hEventLog,
  105. EVENTLOG_INFORMATION_TYPE,
  106. EVENT_CATEGORY_ADMIN,
  107. EVENT_PRINTER_DEREGISTERED,
  108. NULL,
  109. 1,
  110. 0,
  111. &(pqr->pPrinterName),
  112. NULL);
  113. }
  114. // remove ourselves from the queue list
  115. DBGPRINT(("queue thread waiting for the queue list mutex\n"));
  116. WaitForSingleObject(mutexQueueList, INFINITE);
  117. DBGPRINT(("queue thread removing self from queue\n"));
  118. for (ppQr = &pqrHead; ; ppQr = &(*ppQr)->pNext)
  119. {
  120. if (*ppQr == pqr)
  121. {
  122. *ppQr = pqr->pNext;
  123. break;
  124. }
  125. }
  126. DBGPRINT(("queue thread releasing list mutex\n"));
  127. ReleaseMutex(mutexQueueList);
  128. // close the handle to the thread that was opened on create
  129. CloseHandle(pqr->hThread);
  130. DBGPRINT(("closed thread for %ws\n", pqr->pPrinterName));
  131. // all of this memory allocated in PScriptQInit()
  132. DBGPRINT(("%ws freeing memory\n", pqr->pPrinterName));
  133. if (pqr->pPrinterName != NULL)
  134. {
  135. LocalFree(pqr->pPrinterName);
  136. }
  137. if (pqr->pMacPrinterName != NULL)
  138. {
  139. LocalFree(pqr->pMacPrinterName);
  140. }
  141. if (pqr->pDriverName != NULL)
  142. {
  143. LocalFree(pqr->pDriverName);
  144. }
  145. if (pqr->IdleStatus != NULL)
  146. {
  147. LocalFree(pqr->IdleStatus);
  148. }
  149. if (pqr->SpoolingStatus != NULL)
  150. {
  151. LocalFree(pqr->SpoolingStatus);
  152. }
  153. if (pqr->pPortName != NULL)
  154. {
  155. LocalFree(pqr->pPortName);
  156. }
  157. if (pqr->pDataType != NULL)
  158. {
  159. LocalFree(pqr->pDataType);
  160. }
  161. if (pqr->fonts != NULL)
  162. {
  163. LocalFree(pqr->fonts);
  164. }
  165. LocalFree(pqr);
  166. DBGPRINT(("leaving QueueServiceThread\n"));
  167. }
  168. ////////////////////////////////////////////////////////////////////////////////
  169. //
  170. // HandleNewJob() - Handle the open of a print job from a Macintosh
  171. //
  172. // DESCRIPTION:
  173. // This routine does the necessary processing to handle the open
  174. // of a PAP connection from a Macintosh.
  175. //
  176. // If this routine is unable to complete the processesing necessary
  177. // to open a job, the job is cancelled, the job data structures are
  178. // cleaned up.
  179. //
  180. ////////////////////////////////////////////////////////////////////////////////
  181. DWORD
  182. HandleNewJob(
  183. PQR pqr
  184. )
  185. {
  186. PJR pjr = NULL;
  187. DOC_INFO_1 diJobInfo;
  188. PRINTER_DEFAULTS pdDefaults;
  189. DWORD dwError = NO_ERROR;
  190. BOOL boolOK = TRUE;
  191. DWORD rc = NO_ERROR;
  192. PJOB_INFO_2 pji2GetJob=NULL;
  193. DWORD dwNeeded;
  194. int fNonBlocking;
  195. DBGPRINT(("enter HandleNewJob()\n"));
  196. do
  197. {
  198. // allocate a job structure
  199. if ((rc = CreateNewJob(pqr)) != NO_ERROR)
  200. {
  201. DBGPRINT(("FAIL - cannot create a new job structure\n"));
  202. break;
  203. }
  204. pjr = pqr->PendingJobs;
  205. // accept the connection
  206. if ((pjr->sJob = accept(pqr->sListener, NULL, NULL)) == INVALID_SOCKET)
  207. {
  208. rc = GetLastError();
  209. DBGPRINT(("accept() fails with %d\n", rc));
  210. break;
  211. }
  212. // make the socket non-blocking
  213. fNonBlocking = 1;
  214. if (ioctlsocket(pjr->sJob, FIONBIO, &fNonBlocking) == SOCKET_ERROR)
  215. {
  216. rc = GetLastError();
  217. DBGPRINT(("ioctlsocket(FIONBIO) fails with %d\n", rc));
  218. break;
  219. }
  220. // initialize an NT print job
  221. pdDefaults.pDatatype = pqr->pDataType;
  222. pdDefaults.pDevMode = NULL;
  223. pdDefaults.DesiredAccess = PRINTER_ACCESS_USE;
  224. if (!OpenPrinter(pqr->pPrinterName, &pjr->hPrinter, &pdDefaults))
  225. {
  226. rc = GetLastError();
  227. DBGPRINT(("OpenPrinter() fails with %d\n"));
  228. pjr->hPrinter = INVALID_HANDLE_VALUE;
  229. break;
  230. }
  231. diJobInfo.pDocName = NULL;
  232. diJobInfo.pOutputFile = NULL;
  233. diJobInfo.pDatatype = pqr->pDataType;
  234. pjr->dwJobId = StartDocPrinter(pjr->hPrinter, 1, (LPBYTE) &diJobInfo);
  235. if (pjr->dwJobId == 0)
  236. {
  237. rc = GetLastError();
  238. DBGPRINT(("StartDocPrinter() fails with %d\n", rc));
  239. break;
  240. }
  241. #if DBG_SPOOL_LOCALLY
  242. if (DbgSpoolFile == INVALID_HANDLE_VALUE)
  243. {
  244. DbgSpoolFile = CreateFile( L"e:\\tmp\\injob.ps",
  245. GENERIC_READ|GENERIC_WRITE,
  246. FILE_SHARE_READ,
  247. NULL,
  248. OPEN_ALWAYS,
  249. FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_TEMPORARY,
  250. NULL );
  251. }
  252. #endif
  253. //
  254. // set pParameters field of the jobinfo to a unique string that our
  255. // monitor can identify, so that it can know if the job came from a Mac.
  256. //
  257. dwNeeded = 1024;
  258. while (1)
  259. {
  260. pji2GetJob = LocalAlloc( LMEM_FIXED, dwNeeded );
  261. if (pji2GetJob == NULL)
  262. {
  263. DBGPRINT(("HandleNewJob: alloc for %d bytes failed\n", dwNeeded));
  264. rc = ERROR_INSUFFICIENT_BUFFER;
  265. break;
  266. }
  267. rc = 0;
  268. if (!GetJob( pjr->hPrinter, pjr->dwJobId, 2,
  269. (LPBYTE)pji2GetJob, dwNeeded, &dwNeeded ))
  270. {
  271. rc = GetLastError();
  272. }
  273. if ( rc == ERROR_INSUFFICIENT_BUFFER )
  274. {
  275. LocalFree(pji2GetJob);
  276. }
  277. else
  278. {
  279. break;
  280. }
  281. }
  282. if (rc != 0)
  283. {
  284. DBGPRINT(("HandleNewJob: GetJob failed, rc=%d\n", rc));
  285. break;
  286. }
  287. pji2GetJob->pParameters = LFILTERCONTROL;
  288. pji2GetJob->Position = JOB_POSITION_UNSPECIFIED;
  289. SetJob( pjr->hPrinter,pjr->dwJobId, 2, (LPBYTE)pji2GetJob, 0 );
  290. LocalFree(pji2GetJob);
  291. pjr->FirstWrite = TRUE;
  292. // prime for a read
  293. if (setsockopt(pjr->sJob,
  294. SOL_APPLETALK,
  295. SO_PAP_PRIME_READ,
  296. pjr->bufPool[pjr->bufIndx].Buffer,
  297. PAP_DEFAULT_BUFFER) == SOCKET_ERROR)
  298. {
  299. DBGPRINT(("setsockopt(SO_PAP_PRIME_READ) fails with %d\n", GetLastError()));
  300. rc = GetLastError();
  301. break;
  302. }
  303. } while (FALSE);
  304. if ((rc != NO_ERROR) && (NULL != pjr))
  305. {
  306. RemoveJob(pjr);
  307. }
  308. return rc;
  309. }
  310. ////////////////////////////////////////////////////////////////////////////////
  311. //
  312. // HandleRead() - Handle a read event from a Macintosh print job
  313. //
  314. // DESCRIPTION:
  315. // This routine does the necessary processing to handle a read
  316. // on a PAP connection from a Macintosh.
  317. //
  318. ////////////////////////////////////////////////////////////////////////////////
  319. DWORD
  320. HandleRead(
  321. PJR pjr
  322. )
  323. {
  324. DWORD rc = NO_ERROR;
  325. DWORD dwParseError = NO_ERROR;
  326. PQR pqr = pjr->job_pQr;
  327. WSABUF wsaBuf;
  328. int iRecvFlags = 0;
  329. DWORD dwBytesRead;
  330. BOOL fRemoveJob = FALSE;
  331. #if DBG
  332. int CheckPoint = 0;
  333. #endif
  334. DBGPRINT(("enter HandleRead()\n"));
  335. do
  336. {
  337. // get the data. recv() will return the negative count of
  338. // bytes read if EOM is not set. SOCKET_ERROR is -1.
  339. wsaBuf.len = pjr->dwFlowQuantum * PAP_QUANTUM_SIZE;
  340. wsaBuf.buf = pjr->bufPool[pjr->bufIndx].Buffer;
  341. if (WSARecv(pjr->sJob,
  342. &wsaBuf,
  343. 1,
  344. &pjr->cbRead,
  345. &iRecvFlags,
  346. NULL,
  347. NULL) == SOCKET_ERROR)
  348. {
  349. DBGPRINT(("CheckPoint = %d\n", CheckPoint = 1));
  350. rc = GetLastError();
  351. DBGPRINT(("recv() fails with %d, removing job\n", rc));
  352. if (rc == WSAEDISCON)
  353. rc = NO_ERROR;
  354. RemoveJob(pjr);
  355. break;
  356. }
  357. // if this is flagged EOM, echo the EOM and ignore any error
  358. // (disconnect will show when we try to prime for a read)
  359. pjr->EOFRecvd = FALSE;
  360. if (iRecvFlags != MSG_PARTIAL)
  361. {
  362. rc = TellClient(pjr, TRUE, NULL, 0);
  363. pjr->EOFRecvd = TRUE;
  364. pjr->EOFRecvdAt = GetTickCount();
  365. }
  366. DBGPRINT(("%ws: Read (%d%s)\n", pqr->pPrinterName,
  367. pjr->cbRead, pjr->EOFRecvd ? ", EOF" : ""));
  368. // deal with the pending buffer if there is one
  369. pjr->DataBuffer = pjr->bufPool[pjr->bufIndx].Buffer;
  370. pjr->XferLen = pjr->cbRead;
  371. if (pjr->PendingLen)
  372. {
  373. DBGPRINT(("USING PENDING BUFFER\n"));
  374. pjr->DataBuffer -= pjr->PendingLen;
  375. pjr->XferLen += pjr->PendingLen;
  376. pjr->PendingLen = 0;
  377. }
  378. // setup buffers for next read
  379. pjr->bufIndx ^= 1;
  380. // prime for the next read if we haven't disconnected
  381. if (rc == NO_ERROR)
  382. {
  383. DBGPRINT(("priming for another read\n"));
  384. if (setsockopt(pjr->sJob,
  385. SOL_APPLETALK,
  386. SO_PAP_PRIME_READ,
  387. pjr->bufPool[pjr->bufIndx].Buffer,
  388. PAP_DEFAULT_BUFFER) == SOCKET_ERROR)
  389. {
  390. rc = GetLastError();
  391. DBGPRINT(("setsockopt() fails with %d\n", rc));
  392. //
  393. // this call could fail if the client has disconnected. Therefore,
  394. // we parse the data we have received first, then return this
  395. // error code.
  396. //
  397. }
  398. }
  399. // parse this data.
  400. switch (dwParseError = PSParse(pjr, pjr->DataBuffer, pjr->XferLen))
  401. {
  402. case NO_ERROR:
  403. break;
  404. case ERROR_NOT_SUPPORTED:
  405. //
  406. // job from a downlevel client
  407. //
  408. DBGPRINT(("aborting a downlevel driver job\n"));
  409. ReportEvent(hEventLog,
  410. EVENTLOG_WARNING_TYPE,
  411. EVENT_CATEGORY_ADMIN,
  412. EVENT_DOWNLEVEL_DRIVER,
  413. NULL,
  414. 0,
  415. 0,
  416. NULL,
  417. NULL);
  418. DBGPRINT(("CheckPoint = %d\n", CheckPoint = 2));
  419. fRemoveJob = TRUE;
  420. break;
  421. case ERROR_INVALID_PARAMETER:
  422. //
  423. // PostScript DSC error.
  424. //
  425. DBGPRINT(("ERROR on PSParse(). Aborting job\n"));
  426. ReportEvent(hEventLog,
  427. EVENTLOG_WARNING_TYPE,
  428. EVENT_CATEGORY_USAGE,
  429. EVENT_DSC_SYNTAX_ERROR,
  430. NULL,
  431. 1,
  432. 0,
  433. (LPCWSTR *)(&pjr->pszUser),
  434. NULL);
  435. DBGPRINT(("CheckPoint = %d\n", CheckPoint = 3));
  436. fRemoveJob = TRUE;
  437. break;
  438. case WSAEINVAL:
  439. //
  440. // TellClient got a disconnect
  441. //
  442. DBGPRINT(("CheckPoint = %d\n", CheckPoint = 4));
  443. DBGPRINT(("PSParse returns WSAEINVAL, RemoveJob for disconnect\n"));
  444. fRemoveJob = TRUE;
  445. break;
  446. default:
  447. //
  448. // some other error - report unknown error
  449. // and remove job
  450. //
  451. DBGPRINT(("CheckPoint = %d\n", CheckPoint = 5));
  452. DBGPRINT(("PSParse returns error %d\n", dwParseError));
  453. ReportWin32Error(dwParseError);
  454. fRemoveJob = TRUE;
  455. }
  456. // rc is the return code for TellClient. If it is an error, we
  457. // have a disconnect and need to return it. If it's not, psparse
  458. // could have gotten a disconnect and we need to return that
  459. if (rc != NO_ERROR || (fRemoveJob == TRUE))
  460. {
  461. DBGPRINT(("HandleRead: rc = %d, fRemoveJob = %d, so removejob\n",rc,fRemoveJob));
  462. RemoveJob(pjr);
  463. rc = NO_ERROR;
  464. }
  465. } while (FALSE);
  466. return rc;
  467. }
  468. ////////////////////////////////////////////////////////////////////////////////
  469. //
  470. // CreateNewJob() - Initialize a job data structure
  471. //
  472. // DESCRIPTION:
  473. // This routine allocates, initializes and links a job data structure to the
  474. // job chain for a queue.
  475. //
  476. // if this fails (due to lack of memory), the returned value is NULL.
  477. // Otherwise, it is a pointer to a job structure.
  478. //
  479. ////////////////////////////////////////////////////////////////////////////////
  480. DWORD CreateNewJob(PQR pqr)
  481. {
  482. PJR pjr = NULL;
  483. DWORD rc = NO_ERROR;
  484. DBGPRINT(("enter CreateNewJob(%ws)\n", pqr->pPrinterName));
  485. do
  486. {
  487. // allocate a job structure
  488. if ((pjr = (PJR)LocalAlloc(LPTR, sizeof(JOB_RECORD))) == NULL)
  489. {
  490. //
  491. // log an error and return
  492. //
  493. rc = GetLastError();
  494. DBGPRINT(("LocalAlloc(pjr) fails with %d\n", rc));
  495. break;
  496. }
  497. // initialize job structure
  498. pjr->job_pQr = pqr;
  499. pjr->NextJob = NULL;
  500. pjr->dwFlags = JOB_FLAG_NULL;
  501. pjr->hPrinter = INVALID_HANDLE_VALUE;
  502. pjr->dwJobId = 0;
  503. pjr->sJob = INVALID_SOCKET;
  504. pjr->hicFontFamily = INVALID_HANDLE_VALUE;
  505. pjr->hicFontFace = INVALID_HANDLE_VALUE;
  506. pjr->dwFlowQuantum = 8;
  507. pjr->XferLen = 0;
  508. pjr->DataBuffer = NULL;
  509. pjr->bufPool = (PBR)(pjr->buffer);
  510. pjr->bufIndx = 0;
  511. pjr->cbRead = 0;
  512. pjr->PendingLen = 0;
  513. pjr->psJobState = psStandardJob;
  514. pjr->JSState = JSWrite;
  515. pjr->SavedJSState = JSWrite;
  516. pjr->InProgress = NOTHING;
  517. pjr->InBinaryOp = 0;
  518. #if DBG
  519. pjr->PapEventCount = 1;
  520. #endif
  521. pjr->JSKeyWord[0] = 0;
  522. // get an information context for font family query
  523. if ((pjr->hicFontFamily = CreateIC(pqr->pDriverName,
  524. pqr->pPrinterName,
  525. pqr->pPortName,
  526. NULL)) == NULL)
  527. {
  528. rc = GetLastError();
  529. DBGPRINT(("CreateIC(hicFontFamily) fails with %d\n", rc));
  530. break;
  531. }
  532. // get an information context for font face query
  533. if ((pjr->hicFontFace = CreateIC(pqr->pDriverName,
  534. pqr->pPrinterName,
  535. pqr->pPortName,
  536. NULL)) == NULL)
  537. {
  538. rc = GetLastError();
  539. DBGPRINT(("CreateIC(hicFontFace) fails with %d\n", rc));
  540. break;
  541. }
  542. // if this is first job, bump thread priority and change our status
  543. if (pqr->PendingJobs == NULL)
  544. {
  545. DBGPRINT(("first job on queue, bumping thread priority\n"));
  546. SetThreadPriority(pqr->hThread, THREAD_PRIORITY_ABOVE_NORMAL);
  547. // Change our status from idle to spooling
  548. DBGPRINT(("setting status to %s\n", pqr->IdleStatus));
  549. if ((setsockopt(pqr->sListener,
  550. SOL_APPLETALK,
  551. SO_PAP_SET_SERVER_STATUS,
  552. pqr->SpoolingStatus,
  553. strlen(pqr->SpoolingStatus))) == SOCKET_ERROR)
  554. {
  555. rc = GetLastError();
  556. DBGPRINT(("setsockopt(status) fails with %d\n", rc));
  557. break;
  558. }
  559. }
  560. // Add the new job to the list of pending jobs for this print queue.
  561. pjr->NextJob = pqr->PendingJobs;
  562. pqr->PendingJobs = pjr;
  563. } while (FALSE);
  564. if (rc != NO_ERROR)
  565. {
  566. if (pjr != NULL)
  567. {
  568. if ((pjr->hicFontFamily != NULL) && (pjr->hicFontFamily != INVALID_HANDLE_VALUE))
  569. {
  570. DeleteDC(pjr->hicFontFamily);
  571. }
  572. if ((pjr->hicFontFace != NULL) && (pjr->hicFontFace != INVALID_HANDLE_VALUE))
  573. {
  574. DeleteDC(pjr->hicFontFace);
  575. }
  576. LocalFree(pjr);
  577. }
  578. }
  579. return rc;
  580. }
  581. ////////////////////////////////////////////////////////////////////////////////
  582. //
  583. // RemoveJob() - Close a job and clean up the job list
  584. //
  585. // DESCRIPTION:
  586. // This routine examines the state of a job and cleans up appropriately.
  587. // It then unlinks the job structure from the job list and frees it.
  588. //
  589. ////////////////////////////////////////////////////////////////////////////////
  590. void
  591. RemoveJob(
  592. PJR pjr
  593. )
  594. {
  595. PJR * ppjob;
  596. char psEOF = '\04';
  597. DWORD cbWritten;
  598. PQR pqr = pjr->job_pQr;
  599. DBGPRINT(("enter RemoveJob(%ws)\n", pqr->pPrinterName));
  600. // find the job in the pending list
  601. ppjob = &pqr->PendingJobs;
  602. while (*ppjob != NULL && *ppjob != pjr)
  603. ppjob = &(*ppjob)->NextJob;
  604. // remove it from the list
  605. *ppjob = pjr->NextJob;
  606. // clean up the socket
  607. if (pjr->sJob != INVALID_SOCKET)
  608. {
  609. DBGPRINT(("closing socket\n"));
  610. closesocket(pjr->sJob);
  611. }
  612. // clean up information contexts
  613. if (pjr->hicFontFamily != NULL)
  614. {
  615. DeleteDC(pjr->hicFontFamily);
  616. }
  617. if (pjr->hicFontFace != NULL)
  618. {
  619. DeleteDC(pjr->hicFontFace);
  620. }
  621. // end the NT print job and close the printer
  622. if (pjr->hPrinter != INVALID_HANDLE_VALUE)
  623. {
  624. if (pqr->ExitThread)
  625. {
  626. // we are aborting, so delete the job
  627. if (!SetJob(pjr->hPrinter, pjr->dwJobId, 0, NULL, JOB_CONTROL_CANCEL))
  628. {
  629. DBGPRINT(("ERROR: unable to cancel print job on service stop, rc=%d\n", GetLastError()));
  630. }
  631. }
  632. // Do not write anything if we have not written anything yet !!!
  633. if (!pjr->FirstWrite && !wcscmp(pqr->pDataType, MACPS_DATATYPE_RAW))
  634. {
  635. WritePrinter(pjr->hPrinter,
  636. &psEOF,
  637. 1,
  638. &cbWritten);
  639. }
  640. EndDocPrinter(pjr->hPrinter);
  641. #if DBG_SPOOL_LOCALLY
  642. CloseHandle(DbgSpoolFile);
  643. DbgSpoolFile = INVALID_HANDLE_VALUE;
  644. #endif
  645. ClosePrinter(pjr->hPrinter);
  646. }
  647. // if all the jobs in this queue handled, drop back to normal priority
  648. if (pqr->PendingJobs == NULL)
  649. {
  650. DBGPRINT(("last job removed, dropping thread priority\n"));
  651. SetThreadPriority(pqr->hThread, THREAD_PRIORITY_NORMAL);
  652. // change the status from spooling to idle
  653. DBGPRINT(("setting status to %s\n", pqr->IdleStatus));
  654. setsockopt(pqr->sListener,
  655. SOL_APPLETALK,
  656. SO_PAP_SET_SERVER_STATUS,
  657. pqr->IdleStatus,
  658. strlen(pqr->IdleStatus));
  659. }
  660. // free the job structure
  661. LocalFree(pjr);
  662. }
  663. ////////////////////////////////////////////////////////////////////////////////
  664. //
  665. // HandleNextPAPEvent() - Wait for a PAP event
  666. //
  667. // DESCRIPTION:
  668. // This routine waits for a service stop request or an Open or Read to
  669. // complete on an outstanding job. In the event of an Open or Read
  670. // event, the routine finds the job that the event completed for and
  671. // returns a pointer to that job.
  672. //
  673. // In the case of a service stop event, the return value is NULL
  674. //
  675. // NOTES:
  676. //
  677. // Finding the job that corresponds to the event is tricky. In the
  678. // case of the open event it is simple as only one job ever has an
  679. // open pending. However, for reads, most jobs will have reads
  680. // pending simultaneously.
  681. //
  682. // To find a job with a completed read, we depend on three things.
  683. // First, all reads are done so that they will trigger a single
  684. // NT Event. When this event is signalled, we start looking for
  685. // completed reads. Second, when a read completes it changes a
  686. // status code that is stored on a per job basis, so it's possible
  687. // to walk a list to find reads that have completed. Third, we
  688. // need to be careful about when we reset the event. The race
  689. // condition to avoid is between walking the list and reseting
  690. // the event. If there are reads outstanding, a read at the beginning
  691. // of the list could complete before we finish walking the list.
  692. // To avoid this, we only reset the event when no reads are outstanding
  693. //
  694. ////////////////////////////////////////////////////////////////////////////////
  695. void
  696. HandleNextPAPEvent(
  697. PQR pqr
  698. )
  699. {
  700. DWORD rc = NO_ERROR;
  701. DWORD dwIndex;
  702. PJR pjr, pjrNext, pjrOrgFirst;
  703. fd_set readfds;
  704. fd_set exceptfds;
  705. struct timeval timeout;
  706. int cEvents;
  707. do
  708. {
  709. //
  710. // check to see if any OTI-jobs need to be timed out
  711. // this is a hack to work-around the Apple's OTI bug where the Mac client fails to
  712. // send the ConnectionClose to us after it has sent EOF (because it crashes!). To
  713. // avoid the job staying in our spooler forever, we force the connection closed if
  714. // we haven't heard from the mac for 60 seconds after it sends an EOF
  715. //
  716. pjr = pqr->PendingJobs;
  717. while(pjr != NULL)
  718. {
  719. pjrNext = pjr->NextJob;
  720. if (pjr->EOFRecvd && EXECUTE_OTI_HACK(pjr->EOFRecvdAt))
  721. {
  722. DBGPRINT(("%ws must be OTI user ! closing the connection on behalf of client!\n",pjr->pszUser));
  723. RemoveJob(pjr);
  724. }
  725. pjr = pjrNext;
  726. }
  727. // setup socket list with all pending jobs and listener socket
  728. FD_ZERO(&readfds);
  729. FD_ZERO(&exceptfds);
  730. FD_SET(pqr->sListener, &readfds);
  731. for (dwIndex = 1, pjr = pqr->PendingJobs;
  732. (dwIndex < FD_SETSIZE) && (pjr != NULL);
  733. dwIndex++, pjr = pjr->NextJob)
  734. {
  735. FD_SET(pjr->sJob, &readfds);
  736. FD_SET(pjr->sJob, &exceptfds);
  737. }
  738. // wait for up to 2 seconds for a set of sockets to be ready
  739. timeout.tv_sec = 2;
  740. timeout.tv_usec = 0;
  741. if ((cEvents = select(0, &readfds, NULL, &exceptfds, &timeout)) == SOCKET_ERROR)
  742. {
  743. rc = GetLastError();
  744. DBGPRINT(("select() fails with %d: CLOSING DOWN QUEUE\n", rc));
  745. pqr->ExitThread = TRUE;
  746. break;
  747. }
  748. if (cEvents == 0)
  749. {
  750. // timeout, done
  751. break;
  752. }
  753. // handle a new connection if there is one
  754. if (FD_ISSET(pqr->sListener, &readfds))
  755. {
  756. if ((rc = HandleNewJob(pqr)) != NO_ERROR)
  757. {
  758. DBGPRINT(("ERROR - could not open new job - CLOSING DOWN QUEUE\n"));
  759. pqr->ExitThread = TRUE;
  760. break;
  761. }
  762. }
  763. pjr = pqr->PendingJobs;
  764. pjrOrgFirst = NULL;
  765. // since every pjr that succeeds on select goes to the tail of the list, make
  766. // sure we have a way of getting out of this loop! pjrOrgFirst is the way
  767. while(pjr != NULL && pjr != pjrOrgFirst)
  768. {
  769. pjrNext = pjr->NextJob;
  770. if (FD_ISSET(pjr->sJob, &exceptfds))
  771. {
  772. DBGPRINT(("job for user %ws ends\n", pjr->pszUser));
  773. RemoveJob(pjr);
  774. }
  775. else if (FD_ISSET(pjr->sJob, &readfds))
  776. {
  777. // mark the first pjr that's going to be moved to the tail
  778. if (pjrOrgFirst == NULL)
  779. {
  780. pjrOrgFirst = pjr;
  781. }
  782. // Move this job to the end of the queue
  783. MoveJobAtEnd(pqr, pjr);
  784. // HandleRead() will remove pjr if a disconnect happens
  785. HandleRead(pjr);
  786. }
  787. pjr = pjrNext;
  788. }
  789. rc = NO_ERROR;
  790. } while (FALSE);
  791. if (rc != NO_ERROR)
  792. {
  793. ReportWin32Error(rc);
  794. }
  795. }
  796. /*
  797. ** MoveJobAtEnd - Move this job to end of queue.
  798. **
  799. ** This is to ensure TRUE round robin scheduling of jobs within a queue.
  800. ** Since we always start at head of queue at GetNextPAPEvent, we need to
  801. ** do this for any job which got service. The way we achieve this is as
  802. ** follows: Ji will be pushed to the end of the queue.
  803. **
  804. ** Before the change:
  805. **
  806. ** Q -> J1 -> J2 -> ... -> Ji -> Jj -> ... -> Jn -> NULL
  807. **
  808. ** After the change:
  809. **
  810. ** Q -> J1 -> J2 -> ... -> Jj -> ... -> Jn -> Ji -> NULL
  811. **
  812. ** Note that in the boundary conditions of n = 1 OR i = n, it is a NOP i.e.
  813. ** its unlinked and linked back - BIG DEAL !!
  814. */
  815. void
  816. MoveJobAtEnd(PQR pqr, PJR pjr)
  817. {
  818. PJR * ppjob = &pqr->PendingJobs;
  819. BOOL found = FALSE;
  820. for (ppjob = &pqr->PendingJobs;
  821. *ppjob != NULL;
  822. ppjob = &(*ppjob)->NextJob)
  823. {
  824. if (*ppjob == pjr)
  825. {
  826. /* Unlink it from its current position */
  827. *ppjob = pjr->NextJob;
  828. break;
  829. }
  830. }
  831. for (NOTHING;
  832. *ppjob != NULL;
  833. ppjob = &(*ppjob)->NextJob)
  834. {
  835. NOTHING;
  836. }
  837. /* Link job at tail */
  838. *ppjob = pjr;
  839. // and terminate the tail
  840. pjr->NextJob = NULL;
  841. }
  842. void
  843. ReportWin32Error (
  844. DWORD dwError
  845. )
  846. {
  847. LPWSTR pszError = NULL;
  848. DWORD rc = NO_ERROR;
  849. DBGPRINT(("enter ReportWin32Error(%d)\n", dwError));
  850. do
  851. {
  852. if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
  853. FORMAT_MESSAGE_IGNORE_INSERTS |
  854. FORMAT_MESSAGE_FROM_SYSTEM,
  855. NULL,
  856. dwError,
  857. 0,
  858. (LPWSTR)(&pszError),
  859. 128,
  860. NULL) == 0)
  861. {
  862. // Report unknown error
  863. ReportEvent(
  864. hEventLog,
  865. EVENTLOG_WARNING_TYPE,
  866. EVENT_CATEGORY_INTERNAL,
  867. EVENT_MESSAGE_NOT_FOUND,
  868. NULL,
  869. 0,
  870. sizeof(DWORD),
  871. NULL,
  872. &dwError);
  873. }
  874. else
  875. {
  876. // report known error
  877. ReportEvent(hEventLog,
  878. EVENTLOG_WARNING_TYPE,
  879. EVENT_CATEGORY_INTERNAL,
  880. EVENT_SYSTEM_ERROR,
  881. NULL,
  882. 1,
  883. 0,
  884. &pszError,
  885. NULL);
  886. }
  887. } while (FALSE);
  888. if (NULL != pszError)
  889. {
  890. LocalFree(pszError);
  891. }
  892. }