0c9e040fc24284144ab85e084db000ef44b78ede
[reactos.git] / win32ss / printing / providers / localspl / jobs.c
1 /*
2 * PROJECT: ReactOS Local Spooler
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Functions for managing print jobs
5 * COPYRIGHT: Copyright 2015-2017 Colin Finck (colin@reactos.org)
6 */
7
8 #include "precomp.h"
9
10 // Global Variables
11 SKIPLIST GlobalJobList;
12
13 // Local Variables
14 static DWORD _dwLastJobID;
15
16 // Local Constants
17 static DWORD dwJobInfo1Offsets[] = {
18 FIELD_OFFSET(JOB_INFO_1W, pPrinterName),
19 FIELD_OFFSET(JOB_INFO_1W, pMachineName),
20 FIELD_OFFSET(JOB_INFO_1W, pUserName),
21 FIELD_OFFSET(JOB_INFO_1W, pDocument),
22 FIELD_OFFSET(JOB_INFO_1W, pDatatype),
23 FIELD_OFFSET(JOB_INFO_1W, pStatus),
24 MAXDWORD
25 };
26
27 static DWORD dwJobInfo2Offsets[] = {
28 FIELD_OFFSET(JOB_INFO_2W, pPrinterName),
29 FIELD_OFFSET(JOB_INFO_2W, pMachineName),
30 FIELD_OFFSET(JOB_INFO_2W, pUserName),
31 FIELD_OFFSET(JOB_INFO_2W, pDocument),
32 FIELD_OFFSET(JOB_INFO_2W, pNotifyName),
33 FIELD_OFFSET(JOB_INFO_2W, pDatatype),
34 FIELD_OFFSET(JOB_INFO_2W, pPrintProcessor),
35 FIELD_OFFSET(JOB_INFO_2W, pParameters),
36 FIELD_OFFSET(JOB_INFO_2W, pDriverName),
37 FIELD_OFFSET(JOB_INFO_2W, pStatus),
38 MAXDWORD
39 };
40
41
42 /**
43 * @name _EqualStrings
44 *
45 * Returns whether two strings are equal.
46 * Unlike wcscmp, this function also works with NULL strings.
47 *
48 * @param pwszA
49 * First string to compare.
50 *
51 * @param pwszB
52 * Second string to compare.
53 *
54 * @return
55 * TRUE if the strings are equal, FALSE if they differ.
56 */
57 static __inline BOOL
58 _EqualStrings(PCWSTR pwszA, PCWSTR pwszB)
59 {
60 if (!pwszA && !pwszB)
61 return TRUE;
62
63 if (pwszA && !pwszB)
64 return FALSE;
65
66 if (!pwszA && pwszB)
67 return FALSE;
68
69 return (wcscmp(pwszA, pwszB) == 0);
70 }
71
72 static BOOL
73 _GetNextJobID(PDWORD dwJobID)
74 {
75 ++_dwLastJobID;
76
77 while (LookupElementSkiplist(&GlobalJobList, &_dwLastJobID, NULL))
78 {
79 // This ID is already taken. Try the next one.
80 ++_dwLastJobID;
81 }
82
83 if (!IS_VALID_JOB_ID(_dwLastJobID))
84 {
85 ERR("Job ID %lu isn't valid!\n", _dwLastJobID);
86 return FALSE;
87 }
88
89 *dwJobID = _dwLastJobID;
90 return TRUE;
91 }
92
93 /**
94 * @name _GlobalJobListCompareRoutine
95 *
96 * SKIPLIST_COMPARE_ROUTINE for the Global Job List.
97 * We need the Global Job List to check whether a Job ID is already in use. Consequently, this list is sorted by ID.
98 */
99 static int WINAPI
100 _GlobalJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
101 {
102 PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
103 PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
104
105 return A->dwJobID - B->dwJobID;
106 }
107
108 /**
109 * @name _PrinterJobListCompareRoutine
110 *
111 * SKIPLIST_COMPARE_ROUTINE for the each Printer's Job List.
112 * Jobs in this list are sorted in the desired order of processing.
113 */
114 static int WINAPI
115 _PrinterJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
116 {
117 PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
118 PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
119 int iComparison;
120 FILETIME ftSubmittedA;
121 FILETIME ftSubmittedB;
122 ULARGE_INTEGER uliSubmittedA;
123 ULARGE_INTEGER uliSubmittedB;
124 ULONGLONG ullResult;
125
126 // First compare the priorities to determine the order.
127 // The job with a higher priority shall come first.
128 iComparison = A->dwPriority - B->dwPriority;
129 if (iComparison != 0)
130 return iComparison;
131
132 // Both have the same priority, so go by creation time.
133 // Comparison is done using the MSDN-recommended way for comparing SYSTEMTIMEs.
134 if (!SystemTimeToFileTime(&A->stSubmitted, &ftSubmittedA))
135 {
136 ERR("SystemTimeToFileTime failed for A with error %lu!\n", GetLastError());
137 return 0;
138 }
139
140 if (!SystemTimeToFileTime(&B->stSubmitted, &ftSubmittedB))
141 {
142 ERR("SystemTimeToFileTime failed for B with error %lu!\n", GetLastError());
143 return 0;
144 }
145
146 uliSubmittedA.LowPart = ftSubmittedA.dwLowDateTime;
147 uliSubmittedA.HighPart = ftSubmittedA.dwHighDateTime;
148 uliSubmittedB.LowPart = ftSubmittedB.dwLowDateTime;
149 uliSubmittedB.HighPart = ftSubmittedB.dwHighDateTime;
150 ullResult = uliSubmittedA.QuadPart - uliSubmittedB.QuadPart;
151
152 if (ullResult < 0)
153 return -1;
154 else if (ullResult > 0)
155 return 1;
156
157 return 0;
158 }
159
160 DWORD
161 GetJobFilePath(PCWSTR pwszExtension, DWORD dwJobID, PWSTR pwszOutput)
162 {
163 TRACE("GetJobFilePath(%S, %lu, %p)\n", pwszExtension, dwJobID, pwszOutput);
164
165 if (pwszOutput)
166 {
167 CopyMemory(pwszOutput, wszJobDirectory, cchJobDirectory * sizeof(WCHAR));
168 swprintf(&pwszOutput[cchJobDirectory], L"\\%05lu.%s", dwJobID, pwszExtension);
169 }
170
171 // pwszExtension may be L"SPL" or L"SHD", same length for both!
172 return (cchJobDirectory + sizeof("\\?????.SPL")) * sizeof(WCHAR);
173 }
174
175 BOOL
176 InitializeGlobalJobList(void)
177 {
178 const WCHAR wszPath[] = L"\\?????.SHD";
179 const DWORD cchPath = _countof(wszPath) - 1;
180
181 DWORD dwErrorCode;
182 DWORD dwJobID;
183 HANDLE hFind;
184 PLOCAL_JOB pJob = NULL;
185 PWSTR p;
186 WCHAR wszFullPath[MAX_PATH];
187 WIN32_FIND_DATAW FindData;
188
189 TRACE("InitializeGlobalJobList()\n");
190
191 // This one is incremented in _GetNextJobID.
192 _dwLastJobID = 0;
193
194 // Initialize an empty list for all jobs of all local printers.
195 // We will search it by Job ID (supply a pointer to a DWORD in LookupElementSkiplist).
196 InitializeSkiplist(&GlobalJobList, DllAllocSplMem, _GlobalJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
197
198 // Construct the full path search pattern.
199 CopyMemory(wszFullPath, wszJobDirectory, cchJobDirectory * sizeof(WCHAR));
200 CopyMemory(&wszFullPath[cchJobDirectory], wszPath, (cchPath + 1) * sizeof(WCHAR));
201
202 // Use the search pattern to look for unfinished jobs serialized in shadow files (.SHD)
203 hFind = FindFirstFileW(wszFullPath, &FindData);
204 if (hFind == INVALID_HANDLE_VALUE)
205 {
206 // No unfinished jobs found.
207 dwErrorCode = ERROR_SUCCESS;
208 goto Cleanup;
209 }
210
211 do
212 {
213 // Skip possible subdirectories.
214 if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
215 continue;
216
217 // Extract the Job ID and verify the file name format at the same time.
218 // This includes all valid names (like "00005.SHD") and excludes invalid ones (like "10ABC.SHD").
219 dwJobID = wcstoul(FindData.cFileName, &p, 10);
220 if (!IS_VALID_JOB_ID(dwJobID))
221 continue;
222
223 if (wcsicmp(p, L".SHD") != 0)
224 continue;
225
226 // This shadow file has a valid name. Construct the full path and try to load it.
227 GetJobFilePath(L"SHD", dwJobID, wszFullPath);
228 pJob = ReadJobShadowFile(wszFullPath);
229 if (!pJob)
230 continue;
231
232 // Add it to the Global Job List.
233 if (!InsertElementSkiplist(&GlobalJobList, pJob))
234 {
235 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
236 ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
237 goto Cleanup;
238 }
239
240 // Add it to the Printer's Job List.
241 if (!InsertElementSkiplist(&pJob->pPrinter->JobList, pJob))
242 {
243 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
244 ERR("InsertElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
245 goto Cleanup;
246 }
247 }
248 while (FindNextFileW(hFind, &FindData));
249
250 dwErrorCode = ERROR_SUCCESS;
251
252 Cleanup:
253 // Outside the loop
254 if (hFind)
255 FindClose(hFind);
256
257 SetLastError(dwErrorCode);
258 return (dwErrorCode == ERROR_SUCCESS);
259 }
260
261 void
262 InitializePrinterJobList(PLOCAL_PRINTER pPrinter)
263 {
264 TRACE("InitializePrinterJobList(%p)\n", pPrinter);
265
266 // Initialize an empty list for this printer's jobs.
267 // This one is only for sorting the jobs. If you need to lookup a job, search the GlobalJobList by Job ID.
268 InitializeSkiplist(&pPrinter->JobList, DllAllocSplMem, _PrinterJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
269 }
270
271 DWORD WINAPI
272 CreateJob(PLOCAL_PRINTER_HANDLE pPrinterHandle)
273 {
274 const WCHAR wszDoubleBackslash[] = L"\\";
275 const DWORD cchDoubleBackslash = _countof(wszDoubleBackslash) - 1;
276
277 DWORD cchMachineName;
278 DWORD cchUserName;
279 DWORD dwErrorCode;
280 PLOCAL_JOB pJob;
281 RPC_BINDING_HANDLE hServerBinding = NULL;
282 RPC_WSTR pwszBinding = NULL;
283 RPC_WSTR pwszMachineName = NULL;
284
285 TRACE("CreateJob(%p)\n", pPrinterHandle);
286
287 // Create a new job.
288 pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
289 if (!pJob)
290 {
291 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
292 ERR("DllAllocSplMem failed!\n");
293 goto Cleanup;
294 }
295
296 // Reserve an ID for this job.
297 if (!_GetNextJobID(&pJob->dwJobID))
298 {
299 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
300 goto Cleanup;
301 }
302
303 // Copy over defaults to the LOCAL_JOB structure.
304 pJob->pPrinter = pPrinterHandle->pPrinter;
305 pJob->pPrintProcessor = pPrinterHandle->pPrinter->pPrintProcessor;
306 pJob->dwPriority = DEF_PRIORITY;
307 pJob->dwStatus = JOB_STATUS_SPOOLING;
308 pJob->pwszDatatype = AllocSplStr(pPrinterHandle->pwszDatatype);
309 pJob->pwszDocumentName = AllocSplStr(wszDefaultDocumentName);
310 pJob->pDevMode = DuplicateDevMode(pPrinterHandle->pDevMode);
311 GetSystemTime(&pJob->stSubmitted);
312
313 // Get the user name for the Job.
314 cchUserName = UNLEN + 1;
315 pJob->pwszUserName = DllAllocSplMem(cchUserName * sizeof(WCHAR));
316 if (!GetUserNameW(pJob->pwszUserName, &cchUserName))
317 {
318 dwErrorCode = GetLastError();
319 ERR("GetUserNameW failed with error %lu!\n", dwErrorCode);
320 goto Cleanup;
321 }
322
323 // FIXME: For now, pwszNotifyName equals pwszUserName.
324 pJob->pwszNotifyName = AllocSplStr(pJob->pwszUserName);
325
326 // Get the name of the machine that submitted the Job over RPC.
327 dwErrorCode = RpcBindingServerFromClient(NULL, &hServerBinding);
328 if (dwErrorCode != RPC_S_OK)
329 {
330 ERR("RpcBindingServerFromClient failed with status %lu!\n", dwErrorCode);
331 goto Cleanup;
332 }
333
334 dwErrorCode = RpcBindingToStringBindingW(hServerBinding, &pwszBinding);
335 if (dwErrorCode != RPC_S_OK)
336 {
337 ERR("RpcBindingToStringBindingW failed with status %lu!\n", dwErrorCode);
338 goto Cleanup;
339 }
340
341 dwErrorCode = RpcStringBindingParseW(pwszBinding, NULL, NULL, &pwszMachineName, NULL, NULL);
342 if (dwErrorCode != RPC_S_OK)
343 {
344 ERR("RpcStringBindingParseW failed with status %lu!\n", dwErrorCode);
345 goto Cleanup;
346 }
347
348 cchMachineName = wcslen(pwszMachineName);
349 pJob->pwszMachineName = DllAllocSplMem((cchMachineName + cchDoubleBackslash + 1) * sizeof(WCHAR));
350 CopyMemory(pJob->pwszMachineName, wszDoubleBackslash, cchDoubleBackslash * sizeof(WCHAR));
351 CopyMemory(&pJob->pwszMachineName[cchDoubleBackslash], pwszMachineName, (cchMachineName + 1) * sizeof(WCHAR));
352
353 // Add the job to the Global Job List.
354 if (!InsertElementSkiplist(&GlobalJobList, pJob))
355 {
356 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
357 ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
358 goto Cleanup;
359 }
360
361 // Add the job at the end of the Printer's Job List.
362 // As all new jobs are created with default priority, we can be sure that it would always be inserted at the end.
363 if (!InsertTailElementSkiplist(&pJob->pPrinter->JobList, pJob))
364 {
365 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
366 ERR("InsertTailElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
367 goto Cleanup;
368 }
369
370 // We were successful!
371 pPrinterHandle->bStartedDoc = TRUE;
372 pPrinterHandle->pJob = pJob;
373 dwErrorCode = ERROR_SUCCESS;
374
375 // Don't let the cleanup routine free this.
376 pJob = NULL;
377
378 Cleanup:
379 if (pJob)
380 DllFreeSplMem(pJob);
381
382 if (pwszMachineName)
383 RpcStringFreeW(&pwszMachineName);
384
385 if (pwszBinding)
386 RpcStringFreeW(&pwszBinding);
387
388 if (hServerBinding)
389 RpcBindingFree(&hServerBinding);
390
391 return dwErrorCode;
392 }
393
394 BOOL WINAPI
395 LocalAddJob(HANDLE hPrinter, DWORD Level, PBYTE pData, DWORD cbBuf, PDWORD pcbNeeded)
396 {
397 ADDJOB_INFO_1W AddJobInfo1;
398 DWORD dwErrorCode;
399 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
400 PLOCAL_PRINTER_HANDLE pPrinterHandle;
401
402 TRACE("LocalAddJob(%p, %lu, %p, %lu, %p)\n", hPrinter, Level, pData, cbBuf, pcbNeeded);
403
404 // Check if this is a printer handle.
405 if (pHandle->HandleType != HandleType_Printer)
406 {
407 dwErrorCode = ERROR_INVALID_HANDLE;
408 goto Cleanup;
409 }
410
411 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
412
413 // This handle must not have started a job yet!
414 if (pPrinterHandle->pJob)
415 {
416 dwErrorCode = ERROR_INVALID_HANDLE;
417 goto Cleanup;
418 }
419
420 // Check if this is the right structure level.
421 if (Level != 1)
422 {
423 dwErrorCode = ERROR_INVALID_LEVEL;
424 goto Cleanup;
425 }
426
427 // Check if the printer is set to do direct printing.
428 // The Job List isn't used in this case.
429 if (pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT)
430 {
431 dwErrorCode = ERROR_INVALID_ACCESS;
432 goto Cleanup;
433 }
434
435 // Check if the supplied buffer is large enough.
436 *pcbNeeded = sizeof(ADDJOB_INFO_1W) + GetJobFilePath(L"SPL", 0, NULL);
437 if (cbBuf < *pcbNeeded)
438 {
439 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
440 goto Cleanup;
441 }
442
443 // All requirements are met - create a new job.
444 dwErrorCode = CreateJob(pPrinterHandle);
445 if (dwErrorCode != ERROR_SUCCESS)
446 goto Cleanup;
447
448 // Mark that this job was started with AddJob (so that it can be scheduled for printing with ScheduleJob).
449 pPrinterHandle->pJob->bAddedJob = TRUE;
450
451 // Return a proper ADDJOB_INFO_1W structure.
452 AddJobInfo1.JobId = pPrinterHandle->pJob->dwJobID;
453 AddJobInfo1.Path = (PWSTR)(pData + sizeof(ADDJOB_INFO_1W));
454
455 CopyMemory(pData, &AddJobInfo1, sizeof(ADDJOB_INFO_1W));
456 GetJobFilePath(L"SPL", AddJobInfo1.JobId, AddJobInfo1.Path);
457
458 Cleanup:
459 SetLastError(dwErrorCode);
460 return (dwErrorCode == ERROR_SUCCESS);
461 }
462
463
464 static void
465 _LocalGetJobLevel1(PLOCAL_JOB pJob, PJOB_INFO_1W* ppJobInfo, PBYTE* ppJobInfoEnd, PDWORD pcbNeeded)
466 {
467 DWORD cbDatatype;
468 DWORD cbDocumentName = 0;
469 DWORD cbMachineName;
470 DWORD cbPrinterName;
471 DWORD cbStatus = 0;
472 DWORD cbUserName = 0;
473 PWSTR pwszStrings[6];
474
475 // Calculate the string lengths.
476 if (!ppJobInfo)
477 {
478 cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
479 cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
480 cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
481
482 // These values are optional.
483 if (pJob->pwszDocumentName)
484 cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
485
486 if (pJob->pwszStatus)
487 cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
488
489 if (pJob->pwszUserName)
490 cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
491
492 *pcbNeeded += sizeof(JOB_INFO_1W) + cbDatatype + cbDocumentName + cbMachineName + cbPrinterName + cbStatus + cbUserName;
493 return;
494 }
495
496 // Set the general fields.
497 (*ppJobInfo)->JobId = pJob->dwJobID;
498 (*ppJobInfo)->Status = pJob->dwStatus;
499 (*ppJobInfo)->Priority = pJob->dwPriority;
500 (*ppJobInfo)->TotalPages = pJob->dwTotalPages;
501 (*ppJobInfo)->PagesPrinted = pJob->dwPagesPrinted;
502 CopyMemory(&(*ppJobInfo)->Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
503
504 // Position in JOB_INFO_1W is the 1-based index of the job in the processing queue.
505 // Retrieve this through the element index of the job in the Printer's Job List.
506 if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &(*ppJobInfo)->Position))
507 {
508 ERR("pJob could not be located in the Printer's Job List!\n");
509 return;
510 }
511
512 // Make the index 1-based.
513 ++(*ppJobInfo)->Position;
514
515 // Set the pPrinterName field.
516 pwszStrings[0] = pJob->pPrinter->pwszPrinterName;
517
518 // Set the pMachineName field.
519 pwszStrings[1] = pJob->pwszMachineName;
520
521 // Set the pUserName field.
522 pwszStrings[2] = pJob->pwszUserName;
523
524 // Set the pDocument field.
525 pwszStrings[3] = pJob->pwszDocumentName;
526
527 // Set the pDatatype field.
528 pwszStrings[4] = pJob->pwszDatatype;
529
530 // Set the pStatus field.
531 pwszStrings[5] = pJob->pwszStatus;
532
533 // Finally copy the structure and advance to the next one in the output buffer.
534 *ppJobInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppJobInfo), dwJobInfo1Offsets, *ppJobInfoEnd);
535 (*ppJobInfo)++;
536 }
537
538 static void
539 _LocalGetJobLevel2(PLOCAL_JOB pJob, PJOB_INFO_2W* ppJobInfo, PBYTE* ppJobInfoEnd, PDWORD pcbNeeded)
540 {
541 DWORD cbDatatype;
542 DWORD cbDevMode;
543 DWORD cbDocumentName = 0;
544 DWORD cbDriverName;
545 DWORD cbMachineName;
546 DWORD cbNotifyName = 0;
547 DWORD cbPrinterName;
548 DWORD cbPrintProcessor;
549 DWORD cbPrintProcessorParameters = 0;
550 DWORD cbStatus = 0;
551 DWORD cbUserName = 0;
552 FILETIME ftNow;
553 FILETIME ftSubmitted;
554 PWSTR pwszStrings[10];
555 ULARGE_INTEGER uliNow;
556 ULARGE_INTEGER uliSubmitted;
557
558 // Calculate the string lengths.
559 cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
560
561 if (!ppJobInfo)
562 {
563 cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
564 cbDriverName = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
565 cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
566 cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
567 cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
568
569 // These values are optional.
570 if (pJob->pwszDocumentName)
571 cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
572
573 if (pJob->pwszNotifyName)
574 cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
575
576 if (pJob->pwszPrintProcessorParameters)
577 cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
578
579 if (pJob->pwszStatus)
580 cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
581
582 if (pJob->pwszUserName)
583 cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
584
585 *pcbNeeded += sizeof(JOB_INFO_2W) + cbDatatype + cbDevMode + cbDocumentName + cbDriverName + cbMachineName + cbNotifyName + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbStatus + cbUserName;
586 return;
587 }
588
589 // Set the general fields.
590 (*ppJobInfo)->JobId = pJob->dwJobID;
591 (*ppJobInfo)->Status = pJob->dwStatus;
592 (*ppJobInfo)->Priority = pJob->dwPriority;
593 (*ppJobInfo)->StartTime = pJob->dwStartTime;
594 (*ppJobInfo)->UntilTime = pJob->dwUntilTime;
595 (*ppJobInfo)->TotalPages = pJob->dwTotalPages;
596 (*ppJobInfo)->PagesPrinted = pJob->dwPagesPrinted;
597 CopyMemory(&(*ppJobInfo)->Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
598
599 // Time in JOB_INFO_2W is the number of milliseconds elapsed since the job was submitted. Calculate this time.
600 if (!SystemTimeToFileTime(&pJob->stSubmitted, &ftSubmitted))
601 {
602 ERR("SystemTimeToFileTime failed with error %lu!\n", GetLastError());
603 return;
604 }
605
606 GetSystemTimeAsFileTime(&ftNow);
607 uliSubmitted.LowPart = ftSubmitted.dwLowDateTime;
608 uliSubmitted.HighPart = ftSubmitted.dwHighDateTime;
609 uliNow.LowPart = ftNow.dwLowDateTime;
610 uliNow.HighPart = ftNow.dwHighDateTime;
611 (*ppJobInfo)->Time = (DWORD)((uliNow.QuadPart - uliSubmitted.QuadPart) / 10000);
612
613 // Position in JOB_INFO_2W is the 1-based index of the job in the processing queue.
614 // Retrieve this through the element index of the job in the Printer's Job List.
615 if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &(*ppJobInfo)->Position))
616 {
617 ERR("pJob could not be located in the Printer's Job List!\n");
618 return;
619 }
620
621 // Make the index 1-based.
622 ++(*ppJobInfo)->Position;
623
624 // FIXME!
625 FIXME("Setting pSecurityDescriptor and Size to 0 for now!\n");
626 (*ppJobInfo)->pSecurityDescriptor = NULL;
627 (*ppJobInfo)->Size = 0;
628
629 // Set the pDevMode field (and copy the DevMode).
630 *ppJobInfoEnd -= cbDevMode;
631 CopyMemory(*ppJobInfoEnd, pJob->pDevMode, cbDevMode);
632 (*ppJobInfo)->pDevMode = (PDEVMODEW)(*ppJobInfoEnd);
633
634 // Set the pPrinterName field.
635 pwszStrings[0] = pJob->pPrinter->pwszPrinterName;
636
637 // Set the pMachineName field.
638 pwszStrings[1] = pJob->pwszMachineName;
639
640 // Set the pUserName field.
641 pwszStrings[2] = pJob->pwszUserName;
642
643 // Set the pDocument field.
644 pwszStrings[3] = pJob->pwszDocumentName;
645
646 // Set the pNotifyName field.
647 pwszStrings[4] = pJob->pwszNotifyName;
648
649 // Set the pDatatype field.
650 pwszStrings[5] = pJob->pwszDatatype;
651
652 // Set the pPrintProcessor field.
653 pwszStrings[6] = pJob->pPrintProcessor->pwszName;
654
655 // Set the pParameters field.
656 pwszStrings[7] = pJob->pwszPrintProcessorParameters;
657
658 // Set the pDriverName field.
659 pwszStrings[8] = pJob->pPrinter->pwszPrinterDriver;
660
661 // Set the pStatus field.
662 pwszStrings[9] = pJob->pwszStatus;
663
664 // Finally copy the structure and advance to the next one in the output buffer.
665 *ppJobInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppJobInfo), dwJobInfo2Offsets, *ppJobInfoEnd);
666 (*ppJobInfo)++;
667 }
668
669 BOOL WINAPI
670 LocalGetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded)
671 {
672 DWORD dwErrorCode;
673 PBYTE pEnd = &pStart[cbBuf];
674 PLOCAL_HANDLE pHandle;
675 PLOCAL_JOB pJob;
676 PLOCAL_PRINTER_HANDLE pPrinterHandle;
677
678 TRACE("LocalGetJob(%p, %lu, %lu, %p, %lu, %p)\n", hPrinter, JobId, Level, pStart, cbBuf, pcbNeeded);
679
680 // Check if this is a printer handle.
681 pHandle = (PLOCAL_HANDLE)hPrinter;
682 if (pHandle->HandleType != HandleType_Printer)
683 {
684 dwErrorCode = ERROR_INVALID_HANDLE;
685 goto Cleanup;
686 }
687
688 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
689
690 // Get the desired job.
691 pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
692 if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
693 {
694 dwErrorCode = ERROR_INVALID_PARAMETER;
695 goto Cleanup;
696 }
697
698 if (Level > 2)
699 {
700 // The caller supplied an invalid level for GetJob.
701 dwErrorCode = ERROR_INVALID_LEVEL;
702 goto Cleanup;
703 }
704
705 // Count the required buffer size.
706 *pcbNeeded = 0;
707
708 if (Level == 1)
709 _LocalGetJobLevel1(pJob, NULL, NULL, pcbNeeded);
710 else if (Level == 2)
711 _LocalGetJobLevel2(pJob, NULL, NULL, pcbNeeded);
712
713 // Check if the supplied buffer is large enough.
714 if (cbBuf < *pcbNeeded)
715 {
716 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
717 goto Cleanup;
718 }
719
720 // Copy over the Job information.
721 pEnd = &pStart[*pcbNeeded];
722
723 if (Level == 1)
724 _LocalGetJobLevel1(pJob, (PJOB_INFO_1W*)&pStart, &pEnd, NULL);
725 else if (Level == 2)
726 _LocalGetJobLevel2(pJob, (PJOB_INFO_2W*)&pStart, &pEnd, NULL);
727
728 dwErrorCode = ERROR_SUCCESS;
729
730 Cleanup:
731 SetLastError(dwErrorCode);
732 return (dwErrorCode == ERROR_SUCCESS);
733 }
734
735 static DWORD
736 _LocalSetJobLevel1(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_1W pJobInfo)
737 {
738 DWORD dwErrorCode;
739
740 // First check the validity of the input before changing anything.
741 if (!FindDatatype(pJob->pPrintProcessor, pJobInfo->pDatatype))
742 {
743 dwErrorCode = ERROR_INVALID_DATATYPE;
744 goto Cleanup;
745 }
746
747 // Check if the datatype has changed.
748 if (!_EqualStrings(pJob->pwszDatatype, pJobInfo->pDatatype))
749 {
750 // Use the new value.
751 if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
752 {
753 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
754 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
755 goto Cleanup;
756 }
757 }
758
759 // Check if the document name has changed. An empty string is permitted here!
760 if (!_EqualStrings(pJob->pwszDocumentName, pJobInfo->pDocument))
761 {
762 // Use the new value.
763 if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
764 {
765 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
766 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
767 goto Cleanup;
768 }
769 }
770
771 // Check if the status message has changed. An empty string is permitted here!
772 if (!_EqualStrings(pJob->pwszStatus, pJobInfo->pStatus))
773 {
774 // Use the new value.
775 if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
776 {
777 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
778 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
779 goto Cleanup;
780 }
781 }
782
783 // Check if the user name has changed. An empty string is permitted here!
784 if (!_EqualStrings(pJob->pwszUserName, pJobInfo->pUserName) != 0)
785 {
786 // The new user name doesn't need to exist, so no additional verification is required.
787
788 // Use the new value.
789 if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
790 {
791 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
792 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
793 goto Cleanup;
794 }
795 }
796
797 // Check if the priority has changed.
798 if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
799 {
800 // Set the new priority.
801 pJob->dwPriority = pJobInfo->Priority;
802
803 // Remove and reinsert the job in the Printer's Job List.
804 // The Compare function will be used to find the right position now considering the new priority.
805 DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
806 InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
807 }
808
809 // Check if the status flags have changed.
810 if (pJob->dwStatus != pJobInfo->Status)
811 {
812 // Only add status flags that make sense.
813 if (pJobInfo->Status & JOB_STATUS_PAUSED)
814 pJob->dwStatus |= JOB_STATUS_PAUSED;
815
816 if (pJobInfo->Status & JOB_STATUS_ERROR)
817 pJob->dwStatus |= JOB_STATUS_ERROR;
818
819 if (pJobInfo->Status & JOB_STATUS_OFFLINE)
820 pJob->dwStatus |= JOB_STATUS_OFFLINE;
821
822 if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
823 pJob->dwStatus |= JOB_STATUS_PAPEROUT;
824 }
825
826 dwErrorCode = ERROR_SUCCESS;
827
828 Cleanup:
829 return dwErrorCode;
830 }
831
832 static DWORD
833 _LocalSetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_2W pJobInfo)
834 {
835 DWORD dwErrorCode;
836 PLOCAL_PRINT_PROCESSOR pPrintProcessor;
837
838 // First check the validity of the input before changing anything.
839 pPrintProcessor = FindPrintProcessor(pJobInfo->pPrintProcessor);
840 if (!pPrintProcessor)
841 {
842 dwErrorCode = ERROR_UNKNOWN_PRINTPROCESSOR;
843 goto Cleanup;
844 }
845
846 if (!FindDatatype(pPrintProcessor, pJobInfo->pDatatype))
847 {
848 dwErrorCode = ERROR_INVALID_DATATYPE;
849 goto Cleanup;
850 }
851
852 // Check if the datatype has changed.
853 if (!_EqualStrings(pJob->pwszDatatype, pJobInfo->pDatatype))
854 {
855 // Use the new value.
856 if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
857 {
858 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
859 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
860 goto Cleanup;
861 }
862 }
863
864 // Check if the document name has changed. An empty string is permitted here!
865 if (!_EqualStrings(pJob->pwszDocumentName, pJobInfo->pDocument))
866 {
867 // Use the new value.
868 if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
869 {
870 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
871 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
872 goto Cleanup;
873 }
874 }
875
876 // Check if the notify name has changed. An empty string is permitted here!
877 if (!_EqualStrings(pJob->pwszNotifyName, pJobInfo->pNotifyName))
878 {
879 // The new notify name doesn't need to exist, so no additional verification is required.
880
881 // Use the new value.
882 if (!ReallocSplStr(&pJob->pwszNotifyName, pJobInfo->pNotifyName))
883 {
884 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
885 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
886 goto Cleanup;
887 }
888 }
889
890 // Check if the Print Processor Parameters have changed. An empty string is permitted here!
891 if (!_EqualStrings(pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
892 {
893 // Use the new value.
894 if (!ReallocSplStr(&pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
895 {
896 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
897 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
898 goto Cleanup;
899 }
900 }
901
902 // Check if the Status Message has changed. An empty string is permitted here!
903 if (!_EqualStrings(pJob->pwszStatus, pJobInfo->pStatus))
904 {
905 // Use the new value.
906 if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
907 {
908 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
909 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
910 goto Cleanup;
911 }
912 }
913
914 // Check if the user name has changed. An empty string is permitted here!
915 if (!_EqualStrings(pJob->pwszUserName, pJobInfo->pUserName))
916 {
917 // The new user name doesn't need to exist, so no additional verification is required.
918
919 // Use the new value.
920 if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
921 {
922 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
923 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
924 goto Cleanup;
925 }
926 }
927
928 // Check if the priority has changed.
929 if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
930 {
931 // Set the new priority.
932 pJob->dwPriority = pJobInfo->Priority;
933
934 // Remove and reinsert the job in the Printer's Job List.
935 // The Compare function will be used to find the right position now considering the new priority.
936 DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
937 InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
938 }
939
940 // Check if the status flags have changed.
941 if (pJob->dwStatus != pJobInfo->Status)
942 {
943 // Only add status flags that make sense.
944 if (pJobInfo->Status & JOB_STATUS_PAUSED)
945 pJob->dwStatus |= JOB_STATUS_PAUSED;
946
947 if (pJobInfo->Status & JOB_STATUS_ERROR)
948 pJob->dwStatus |= JOB_STATUS_ERROR;
949
950 if (pJobInfo->Status & JOB_STATUS_OFFLINE)
951 pJob->dwStatus |= JOB_STATUS_OFFLINE;
952
953 if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
954 pJob->dwStatus |= JOB_STATUS_PAPEROUT;
955 }
956
957 dwErrorCode = ERROR_SUCCESS;
958
959 Cleanup:
960 return dwErrorCode;
961 }
962
963 BOOL WINAPI
964 LocalSetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pJobInfo, DWORD Command)
965 {
966 DWORD dwErrorCode;
967 PLOCAL_HANDLE pHandle;
968 PLOCAL_JOB pJob;
969 PLOCAL_PRINTER_HANDLE pPrinterHandle;
970 WCHAR wszFullPath[MAX_PATH];
971
972 TRACE("LocalSetJob(%p, %lu, %lu, %p, %lu)\n", hPrinter, JobId, Level, pJobInfo, Command);
973
974 // Check if this is a printer handle.
975 pHandle = (PLOCAL_HANDLE)hPrinter;
976 if (pHandle->HandleType != HandleType_Printer)
977 {
978 dwErrorCode = ERROR_INVALID_HANDLE;
979 goto Cleanup;
980 }
981
982 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
983
984 // Get the desired job.
985 pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
986 if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
987 {
988 dwErrorCode = ERROR_INVALID_PARAMETER;
989 goto Cleanup;
990 }
991
992 // Setting job information is handled differently for each level.
993 if (Level)
994 {
995 if (Level == 1)
996 dwErrorCode = _LocalSetJobLevel1(pPrinterHandle, pJob, (PJOB_INFO_1W)pJobInfo);
997 else if (Level == 2)
998 dwErrorCode = _LocalSetJobLevel2(pPrinterHandle, pJob, (PJOB_INFO_2W)pJobInfo);
999 else
1000 dwErrorCode = ERROR_INVALID_LEVEL;
1001 }
1002
1003 if (dwErrorCode != ERROR_SUCCESS)
1004 goto Cleanup;
1005
1006 // If we do spooled printing, the job information is written down into a shadow file.
1007 if (!(pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT))
1008 {
1009 // Write the job data into the shadow file.
1010 GetJobFilePath(L"SHD", JobId, wszFullPath);
1011 WriteJobShadowFile(wszFullPath, pJob);
1012 }
1013
1014 // Perform an additional command if desired.
1015 if (Command)
1016 {
1017 if (Command == JOB_CONTROL_SENT_TO_PRINTER)
1018 {
1019 // This indicates the end of the Print Job.
1020
1021 // Cancel the Job at the Print Processor.
1022 if (pJob->hPrintProcessor)
1023 pJob->pPrintProcessor->pfnControlPrintProcessor(pJob->hPrintProcessor, JOB_CONTROL_CANCEL);
1024
1025 FreeJob(pJob);
1026
1027 // TODO: All open handles associated with the job need to be invalidated.
1028 // This certainly needs handle tracking...
1029 }
1030 else
1031 {
1032 ERR("Unimplemented SetJob Command: %lu!\n", Command);
1033 }
1034 }
1035
1036 dwErrorCode = ERROR_SUCCESS;
1037
1038 Cleanup:
1039 SetLastError(dwErrorCode);
1040 return (dwErrorCode == ERROR_SUCCESS);
1041 }
1042
1043 BOOL WINAPI
1044 LocalEnumJobs(HANDLE hPrinter, DWORD FirstJob, DWORD NoJobs, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
1045 {
1046 DWORD dwErrorCode;
1047 DWORD i;
1048 PBYTE pEnd;
1049 PLOCAL_HANDLE pHandle;
1050 PLOCAL_JOB pJob;
1051 PSKIPLIST_NODE pFirstJobNode;
1052 PSKIPLIST_NODE pNode;
1053 PLOCAL_PRINTER_HANDLE pPrinterHandle;
1054
1055 TRACE("LocalEnumJobs(%p, %lu, %lu, %lu, %p, %lu, %p, %p)\n", hPrinter, FirstJob, NoJobs, Level, pStart, cbBuf, pcbNeeded, pcReturned);
1056
1057 // Check if this is a printer handle.
1058 pHandle = (PLOCAL_HANDLE)hPrinter;
1059 if (pHandle->HandleType != HandleType_Printer)
1060 {
1061 dwErrorCode = ERROR_INVALID_HANDLE;
1062 goto Cleanup;
1063 }
1064
1065 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
1066
1067 // Check the level.
1068 if (Level > 2)
1069 {
1070 dwErrorCode = ERROR_INVALID_LEVEL;
1071 goto Cleanup;
1072 }
1073
1074 // Begin counting.
1075 *pcbNeeded = 0;
1076 *pcReturned = 0;
1077
1078 // Lookup the node of the first job requested by the caller in the Printer's Job List.
1079 pFirstJobNode = LookupNodeByIndexSkiplist(&pPrinterHandle->pPrinter->JobList, FirstJob);
1080
1081 // Count the required buffer size and the number of jobs.
1082 i = 0;
1083 pNode = pFirstJobNode;
1084
1085 while (i < NoJobs && pNode)
1086 {
1087 pJob = (PLOCAL_JOB)pNode->Element;
1088
1089 if (Level == 1)
1090 _LocalGetJobLevel1(pJob, NULL, NULL, pcbNeeded);
1091 else if (Level == 2)
1092 _LocalGetJobLevel2(pJob, NULL, NULL, pcbNeeded);
1093
1094 // We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
1095 i++;
1096 pNode = pNode->Next[0];
1097 }
1098
1099 // Check if the supplied buffer is large enough.
1100 if (cbBuf < *pcbNeeded)
1101 {
1102 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
1103 goto Cleanup;
1104 }
1105
1106 // Copy over the Job information.
1107 i = 0;
1108 pNode = pFirstJobNode;
1109 pEnd = &pStart[*pcbNeeded];
1110
1111 while (i < NoJobs && pNode)
1112 {
1113 pJob = (PLOCAL_JOB)pNode->Element;
1114
1115 if (Level == 1)
1116 _LocalGetJobLevel1(pJob, (PJOB_INFO_1W*)&pStart, &pEnd, NULL);
1117 else if (Level == 2)
1118 _LocalGetJobLevel2(pJob, (PJOB_INFO_2W*)&pStart, &pEnd, NULL);
1119
1120 // We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
1121 i++;
1122 pNode = pNode->Next[0];
1123 }
1124
1125 *pcReturned = i;
1126 dwErrorCode = ERROR_SUCCESS;
1127
1128 Cleanup:
1129 SetLastError(dwErrorCode);
1130 return (dwErrorCode == ERROR_SUCCESS);
1131 }
1132
1133 BOOL WINAPI
1134 LocalScheduleJob(HANDLE hPrinter, DWORD dwJobID)
1135 {
1136 DWORD dwAttributes;
1137 DWORD dwErrorCode;
1138 HANDLE hThread;
1139 PLOCAL_JOB pJob;
1140 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
1141 PLOCAL_PRINTER_HANDLE pPrinterHandle;
1142 WCHAR wszFullPath[MAX_PATH];
1143
1144 TRACE("LocalScheduleJob(%p, %lu)\n", hPrinter, dwJobID);
1145
1146 // Check if this is a printer handle.
1147 if (pHandle->HandleType != HandleType_Printer)
1148 {
1149 dwErrorCode = ERROR_INVALID_HANDLE;
1150 goto Cleanup;
1151 }
1152
1153 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
1154
1155 // Check if the Job ID is valid.
1156 pJob = LookupElementSkiplist(&GlobalJobList, &dwJobID, NULL);
1157 if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
1158 {
1159 dwErrorCode = ERROR_INVALID_PARAMETER;
1160 goto Cleanup;
1161 }
1162
1163 // Check if this Job was started with AddJob.
1164 if (!pJob->bAddedJob)
1165 {
1166 dwErrorCode = ERROR_SPL_NO_ADDJOB;
1167 goto Cleanup;
1168 }
1169
1170 // Construct the full path to the spool file.
1171 GetJobFilePath(L"SPL", dwJobID, wszFullPath);
1172
1173 // Check if it exists.
1174 dwAttributes = GetFileAttributesW(wszFullPath);
1175 if (dwAttributes == INVALID_FILE_ATTRIBUTES || dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
1176 {
1177 dwErrorCode = ERROR_SPOOL_FILE_NOT_FOUND;
1178 goto Cleanup;
1179 }
1180
1181 // Spooling is finished at this point.
1182 pJob->dwStatus &= ~JOB_STATUS_SPOOLING;
1183
1184 // Write the job data into the shadow file.
1185 wcscpy(wcsrchr(wszFullPath, L'.'), L".SHD");
1186 WriteJobShadowFile(wszFullPath, pJob);
1187
1188 // Create the thread for performing the printing process.
1189 hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PrintingThreadProc, pJob, 0, NULL);
1190 if (!hThread)
1191 {
1192 dwErrorCode = GetLastError();
1193 ERR("CreateThread failed with error %lu!\n", dwErrorCode);
1194 goto Cleanup;
1195 }
1196
1197 // We don't need the thread handle. Keeping it open blocks the thread from terminating.
1198 CloseHandle(hThread);
1199
1200 // ScheduleJob has done its job. The rest happens inside the thread.
1201 dwErrorCode = ERROR_SUCCESS;
1202
1203 Cleanup:
1204 SetLastError(dwErrorCode);
1205 return (dwErrorCode == ERROR_SUCCESS);
1206 }
1207
1208 PLOCAL_JOB
1209 ReadJobShadowFile(PCWSTR pwszFilePath)
1210 {
1211 DWORD cbFileSize;
1212 DWORD cbRead;
1213 HANDLE hFile = INVALID_HANDLE_VALUE;
1214 PLOCAL_JOB pJob;
1215 PLOCAL_JOB pReturnValue = NULL;
1216 PLOCAL_PRINTER pPrinter;
1217 PLOCAL_PRINT_PROCESSOR pPrintProcessor;
1218 PSHD_HEADER pShadowFile = NULL;
1219 PWSTR pwszPrinterName;
1220 PWSTR pwszPrintProcessor;
1221
1222 TRACE("ReadJobShadowFile(%S)\n", pwszFilePath);
1223
1224 // Try to open the file.
1225 hFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
1226 if (hFile == INVALID_HANDLE_VALUE)
1227 {
1228 ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1229 goto Cleanup;
1230 }
1231
1232 // Get its file size (small enough for a single DWORD) and allocate memory for all of it.
1233 cbFileSize = GetFileSize(hFile, NULL);
1234 pShadowFile = DllAllocSplMem(cbFileSize);
1235 if (!pShadowFile)
1236 {
1237 ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
1238 goto Cleanup;
1239 }
1240
1241 // Read the entire file.
1242 if (!ReadFile(hFile, pShadowFile, cbFileSize, &cbRead, NULL))
1243 {
1244 ERR("ReadFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1245 goto Cleanup;
1246 }
1247
1248 // Check signature and header size.
1249 if (pShadowFile->dwSignature != SHD_WIN2003_SIGNATURE || pShadowFile->cbHeader != sizeof(SHD_HEADER))
1250 {
1251 ERR("Signature or Header Size mismatch for file \"%S\"!\n", pwszFilePath);
1252 goto Cleanup;
1253 }
1254
1255 // Retrieve the associated printer from the list.
1256 pwszPrinterName = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrinterName);
1257 pPrinter = LookupElementSkiplist(&PrinterList, &pwszPrinterName, NULL);
1258 if (!pPrinter)
1259 {
1260 ERR("Shadow file \"%S\" references a non-existing printer \"%S\"!\n", pwszFilePath, pwszPrinterName);
1261 goto Cleanup;
1262 }
1263
1264 // Retrieve the associated Print Processor from the list.
1265 pwszPrintProcessor = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessor);
1266 pPrintProcessor = FindPrintProcessor(pwszPrintProcessor);
1267 if (!pPrintProcessor)
1268 {
1269 ERR("Shadow file \"%S\" references a non-existing Print Processor \"%S\"!\n", pwszFilePath, pwszPrintProcessor);
1270 goto Cleanup;
1271 }
1272
1273 // Create a new job structure and copy over the relevant fields.
1274 pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
1275 if (!pJob)
1276 {
1277 ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
1278 goto Cleanup;
1279 }
1280
1281 pJob->dwJobID = pShadowFile->dwJobID;
1282 pJob->dwPriority = pShadowFile->dwPriority;
1283 pJob->dwStartTime = pShadowFile->dwStartTime;
1284 pJob->dwTotalPages = pShadowFile->dwTotalPages;
1285 pJob->dwUntilTime = pShadowFile->dwUntilTime;
1286 pJob->pPrinter = pPrinter;
1287 pJob->pPrintProcessor = pPrintProcessor;
1288 pJob->pDevMode = DuplicateDevMode((PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode));
1289 pJob->pwszDatatype = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
1290 pJob->pwszMachineName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offMachineName));
1291 CopyMemory(&pJob->stSubmitted, &pShadowFile->stSubmitted, sizeof(SYSTEMTIME));
1292
1293 // Copy the optional values.
1294 if (pShadowFile->offDocumentName)
1295 pJob->pwszDocumentName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));
1296
1297 if (pShadowFile->offNotifyName)
1298 pJob->pwszNotifyName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offNotifyName));
1299
1300 if (pShadowFile->offPrintProcessorParameters)
1301 pJob->pwszPrintProcessorParameters = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessorParameters));
1302
1303 if (pShadowFile->offUserName)
1304 pJob->pwszUserName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offUserName));
1305
1306 // Jobs read from shadow files were always added using AddJob.
1307 pJob->bAddedJob = TRUE;
1308
1309 pReturnValue = pJob;
1310
1311 Cleanup:
1312 if (pShadowFile)
1313 DllFreeSplMem(pShadowFile);
1314
1315 if (hFile != INVALID_HANDLE_VALUE)
1316 CloseHandle(hFile);
1317
1318 return pReturnValue;
1319 }
1320
1321 BOOL
1322 WriteJobShadowFile(PWSTR pwszFilePath, const PLOCAL_JOB pJob)
1323 {
1324 BOOL bReturnValue = FALSE;
1325 DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
1326 DWORD cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
1327 DWORD cbDocumentName = 0;
1328 DWORD cbFileSize;
1329 DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
1330 DWORD cbNotifyName = 0;
1331 DWORD cbPrinterDriver = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
1332 DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
1333 DWORD cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
1334 DWORD cbPrintProcessorParameters = 0;
1335 DWORD cbUserName = 0;
1336 DWORD cbWritten;
1337 DWORD dwCurrentOffset;
1338 HANDLE hSHDFile = INVALID_HANDLE_VALUE;
1339 HANDLE hSPLFile = INVALID_HANDLE_VALUE;
1340 PSHD_HEADER pShadowFile = NULL;
1341
1342 TRACE("WriteJobShadowFile(%S, %p)\n", pwszFilePath, pJob);
1343
1344 // Try to open the SHD file.
1345 hSHDFile = CreateFileW(pwszFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
1346 if (hSHDFile == INVALID_HANDLE_VALUE)
1347 {
1348 ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1349 goto Cleanup;
1350 }
1351
1352 // Calculate the lengths of the optional values and the total size of the shadow file.
1353 if (pJob->pwszDocumentName)
1354 cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
1355
1356 if (pJob->pwszNotifyName)
1357 cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
1358
1359 if (pJob->pwszPrintProcessorParameters)
1360 cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
1361
1362 if (pJob->pwszUserName)
1363 cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
1364
1365 cbFileSize = sizeof(SHD_HEADER) + cbDatatype + cbDocumentName + cbDevMode + cbMachineName + cbNotifyName + cbPrinterDriver + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbUserName;
1366
1367 // Allocate memory for it.
1368 pShadowFile = DllAllocSplMem(cbFileSize);
1369 if (!pShadowFile)
1370 {
1371 ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
1372 goto Cleanup;
1373 }
1374
1375 // Fill out the shadow file header information.
1376 pShadowFile->dwSignature = SHD_WIN2003_SIGNATURE;
1377 pShadowFile->cbHeader = sizeof(SHD_HEADER);
1378
1379 // Copy the values.
1380 pShadowFile->dwJobID = pJob->dwJobID;
1381 pShadowFile->dwPriority = pJob->dwPriority;
1382 pShadowFile->dwStartTime = pJob->dwStartTime;
1383 pShadowFile->dwTotalPages = pJob->dwTotalPages;
1384 pShadowFile->dwUntilTime = pJob->dwUntilTime;
1385 CopyMemory(&pShadowFile->stSubmitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
1386
1387 // Determine the file size of the .SPL file
1388 wcscpy(wcsrchr(pwszFilePath, L'.'), L".SPL");
1389 hSPLFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
1390 if (hSPLFile != INVALID_HANDLE_VALUE)
1391 pShadowFile->dwSPLSize = GetFileSize(hSPLFile, NULL);
1392
1393 // Add the extra values that are stored as offsets in the shadow file.
1394 // The first value begins right after the shadow file header.
1395 dwCurrentOffset = sizeof(SHD_HEADER);
1396
1397 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
1398 pShadowFile->offDatatype = dwCurrentOffset;
1399 dwCurrentOffset += cbDatatype;
1400
1401 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pDevMode, cbDevMode);
1402 pShadowFile->offDevMode = dwCurrentOffset;
1403 dwCurrentOffset += cbDevMode;
1404
1405 // offDriverName is only written, but automatically determined through offPrinterName when reading.
1406 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterDriver, cbPrinterDriver);
1407 pShadowFile->offDriverName = dwCurrentOffset;
1408 dwCurrentOffset += cbPrinterDriver;
1409
1410 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszMachineName, cbMachineName);
1411 pShadowFile->offMachineName = dwCurrentOffset;
1412 dwCurrentOffset += cbMachineName;
1413
1414 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterName, cbPrinterName);
1415 pShadowFile->offPrinterName = dwCurrentOffset;
1416 dwCurrentOffset += cbPrinterName;
1417
1418 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrintProcessor->pwszName, cbPrintProcessor);
1419 pShadowFile->offPrintProcessor = dwCurrentOffset;
1420 dwCurrentOffset += cbPrintProcessor;
1421
1422 // Copy the optional values.
1423 if (cbDocumentName)
1424 {
1425 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDocumentName, cbDocumentName);
1426 pShadowFile->offDocumentName = dwCurrentOffset;
1427 dwCurrentOffset += cbDocumentName;
1428 }
1429
1430 if (cbNotifyName)
1431 {
1432 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszNotifyName, cbNotifyName);
1433 pShadowFile->offNotifyName = dwCurrentOffset;
1434 dwCurrentOffset += cbNotifyName;
1435 }
1436
1437 if (cbPrintProcessorParameters)
1438 {
1439 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszPrintProcessorParameters, cbPrintProcessorParameters);
1440 pShadowFile->offPrintProcessorParameters = dwCurrentOffset;
1441 dwCurrentOffset += cbPrintProcessorParameters;
1442 }
1443
1444 if (cbUserName)
1445 {
1446 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszUserName, cbUserName);
1447 pShadowFile->offUserName = dwCurrentOffset;
1448 dwCurrentOffset += cbUserName;
1449 }
1450
1451 // Write the file.
1452 if (!WriteFile(hSHDFile, pShadowFile, cbFileSize, &cbWritten, NULL))
1453 {
1454 ERR("WriteFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1455 goto Cleanup;
1456 }
1457
1458 bReturnValue = TRUE;
1459
1460 Cleanup:
1461 if (pShadowFile)
1462 DllFreeSplMem(pShadowFile);
1463
1464 if (hSHDFile != INVALID_HANDLE_VALUE)
1465 CloseHandle(hSHDFile);
1466
1467 if (hSPLFile != INVALID_HANDLE_VALUE)
1468 CloseHandle(hSPLFile);
1469
1470 return bReturnValue;
1471 }
1472
1473 void
1474 FreeJob(PLOCAL_JOB pJob)
1475 {
1476 PWSTR pwszSHDFile;
1477
1478 TRACE("FreeJob(%p)\n", pJob);
1479
1480 // Remove the Job from both Job Lists.
1481 DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
1482 DeleteElementSkiplist(&GlobalJobList, pJob);
1483
1484 // Try to delete the corresponding .SHD file.
1485 pwszSHDFile = DllAllocSplMem(GetJobFilePath(L"SHD", 0, NULL));
1486 if (pwszSHDFile && GetJobFilePath(L"SHD", pJob->dwJobID, pwszSHDFile))
1487 DeleteFileW(pwszSHDFile);
1488
1489 // Free memory for the mandatory fields.
1490 DllFreeSplMem(pJob->pDevMode);
1491 DllFreeSplStr(pJob->pwszDatatype);
1492 DllFreeSplStr(pJob->pwszDocumentName);
1493 DllFreeSplStr(pJob->pwszMachineName);
1494 DllFreeSplStr(pJob->pwszNotifyName);
1495 DllFreeSplStr(pJob->pwszUserName);
1496
1497 // Free memory for the optional fields if they are present.
1498 if (pJob->pwszOutputFile)
1499 DllFreeSplStr(pJob->pwszOutputFile);
1500
1501 if (pJob->pwszPrintProcessorParameters)
1502 DllFreeSplStr(pJob->pwszPrintProcessorParameters);
1503
1504 if (pJob->pwszStatus)
1505 DllFreeSplStr(pJob->pwszStatus);
1506
1507 // Finally free the job structure itself.
1508 DllFreeSplMem(pJob);
1509 }