[LOCALSPL] Fix parameter handling in LocalSetJob and add tests for the few ways we...
[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 = ERROR_SUCCESS;
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 || 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 // Set new job information if a valid level was given.
993 if (Level == 1)
994 dwErrorCode = _LocalSetJobLevel1(pPrinterHandle, pJob, (PJOB_INFO_1W)pJobInfo);
995 else if (Level == 2)
996 dwErrorCode = _LocalSetJobLevel2(pPrinterHandle, pJob, (PJOB_INFO_2W)pJobInfo);
997
998 if (dwErrorCode != ERROR_SUCCESS)
999 goto Cleanup;
1000
1001 // If we do spooled printing, the job information is written down into a shadow file.
1002 if (!(pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT))
1003 {
1004 // Write the job data into the shadow file.
1005 GetJobFilePath(L"SHD", JobId, wszFullPath);
1006 WriteJobShadowFile(wszFullPath, pJob);
1007 }
1008
1009 // Perform an additional command if desired.
1010 if (Command)
1011 {
1012 if (Command == JOB_CONTROL_SENT_TO_PRINTER)
1013 {
1014 // This indicates the end of the Print Job.
1015
1016 // Cancel the Job at the Print Processor.
1017 if (pJob->hPrintProcessor)
1018 pJob->pPrintProcessor->pfnControlPrintProcessor(pJob->hPrintProcessor, JOB_CONTROL_CANCEL);
1019
1020 FreeJob(pJob);
1021
1022 // TODO: All open handles associated with the job need to be invalidated.
1023 // This certainly needs handle tracking...
1024 }
1025 else
1026 {
1027 ERR("Unimplemented SetJob Command: %lu!\n", Command);
1028 }
1029 }
1030
1031 Cleanup:
1032 SetLastError(dwErrorCode);
1033 return (dwErrorCode == ERROR_SUCCESS);
1034 }
1035
1036 BOOL WINAPI
1037 LocalEnumJobs(HANDLE hPrinter, DWORD FirstJob, DWORD NoJobs, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
1038 {
1039 DWORD dwErrorCode;
1040 DWORD i;
1041 PBYTE pEnd;
1042 PLOCAL_HANDLE pHandle;
1043 PLOCAL_JOB pJob;
1044 PSKIPLIST_NODE pFirstJobNode;
1045 PSKIPLIST_NODE pNode;
1046 PLOCAL_PRINTER_HANDLE pPrinterHandle;
1047
1048 TRACE("LocalEnumJobs(%p, %lu, %lu, %lu, %p, %lu, %p, %p)\n", hPrinter, FirstJob, NoJobs, Level, pStart, cbBuf, pcbNeeded, pcReturned);
1049
1050 // Check if this is a printer handle.
1051 pHandle = (PLOCAL_HANDLE)hPrinter;
1052 if (pHandle->HandleType != HandleType_Printer)
1053 {
1054 dwErrorCode = ERROR_INVALID_HANDLE;
1055 goto Cleanup;
1056 }
1057
1058 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
1059
1060 // Check the level.
1061 if (Level > 2)
1062 {
1063 dwErrorCode = ERROR_INVALID_LEVEL;
1064 goto Cleanup;
1065 }
1066
1067 // Begin counting.
1068 *pcbNeeded = 0;
1069 *pcReturned = 0;
1070
1071 // Lookup the node of the first job requested by the caller in the Printer's Job List.
1072 pFirstJobNode = LookupNodeByIndexSkiplist(&pPrinterHandle->pPrinter->JobList, FirstJob);
1073
1074 // Count the required buffer size and the number of jobs.
1075 i = 0;
1076 pNode = pFirstJobNode;
1077
1078 while (i < NoJobs && pNode)
1079 {
1080 pJob = (PLOCAL_JOB)pNode->Element;
1081
1082 if (Level == 1)
1083 _LocalGetJobLevel1(pJob, NULL, NULL, pcbNeeded);
1084 else if (Level == 2)
1085 _LocalGetJobLevel2(pJob, NULL, NULL, pcbNeeded);
1086
1087 // We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
1088 i++;
1089 pNode = pNode->Next[0];
1090 }
1091
1092 // Check if the supplied buffer is large enough.
1093 if (cbBuf < *pcbNeeded)
1094 {
1095 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
1096 goto Cleanup;
1097 }
1098
1099 // Copy over the Job information.
1100 i = 0;
1101 pNode = pFirstJobNode;
1102 pEnd = &pStart[*pcbNeeded];
1103
1104 while (i < NoJobs && pNode)
1105 {
1106 pJob = (PLOCAL_JOB)pNode->Element;
1107
1108 if (Level == 1)
1109 _LocalGetJobLevel1(pJob, (PJOB_INFO_1W*)&pStart, &pEnd, NULL);
1110 else if (Level == 2)
1111 _LocalGetJobLevel2(pJob, (PJOB_INFO_2W*)&pStart, &pEnd, NULL);
1112
1113 // We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
1114 i++;
1115 pNode = pNode->Next[0];
1116 }
1117
1118 *pcReturned = i;
1119 dwErrorCode = ERROR_SUCCESS;
1120
1121 Cleanup:
1122 SetLastError(dwErrorCode);
1123 return (dwErrorCode == ERROR_SUCCESS);
1124 }
1125
1126 BOOL WINAPI
1127 LocalScheduleJob(HANDLE hPrinter, DWORD dwJobID)
1128 {
1129 DWORD dwAttributes;
1130 DWORD dwErrorCode;
1131 HANDLE hThread;
1132 PLOCAL_JOB pJob;
1133 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
1134 PLOCAL_PRINTER_HANDLE pPrinterHandle;
1135 WCHAR wszFullPath[MAX_PATH];
1136
1137 TRACE("LocalScheduleJob(%p, %lu)\n", hPrinter, dwJobID);
1138
1139 // Check if this is a printer handle.
1140 if (pHandle->HandleType != HandleType_Printer)
1141 {
1142 dwErrorCode = ERROR_INVALID_HANDLE;
1143 goto Cleanup;
1144 }
1145
1146 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
1147
1148 // Check if the Job ID is valid.
1149 pJob = LookupElementSkiplist(&GlobalJobList, &dwJobID, NULL);
1150 if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
1151 {
1152 dwErrorCode = ERROR_INVALID_PARAMETER;
1153 goto Cleanup;
1154 }
1155
1156 // Check if this Job was started with AddJob.
1157 if (!pJob->bAddedJob)
1158 {
1159 dwErrorCode = ERROR_SPL_NO_ADDJOB;
1160 goto Cleanup;
1161 }
1162
1163 // Construct the full path to the spool file.
1164 GetJobFilePath(L"SPL", dwJobID, wszFullPath);
1165
1166 // Check if it exists.
1167 dwAttributes = GetFileAttributesW(wszFullPath);
1168 if (dwAttributes == INVALID_FILE_ATTRIBUTES || dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
1169 {
1170 dwErrorCode = ERROR_SPOOL_FILE_NOT_FOUND;
1171 goto Cleanup;
1172 }
1173
1174 // Spooling is finished at this point.
1175 pJob->dwStatus &= ~JOB_STATUS_SPOOLING;
1176
1177 // Write the job data into the shadow file.
1178 wcscpy(wcsrchr(wszFullPath, L'.'), L".SHD");
1179 WriteJobShadowFile(wszFullPath, pJob);
1180
1181 // Create the thread for performing the printing process.
1182 hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PrintingThreadProc, pJob, 0, NULL);
1183 if (!hThread)
1184 {
1185 dwErrorCode = GetLastError();
1186 ERR("CreateThread failed with error %lu!\n", dwErrorCode);
1187 goto Cleanup;
1188 }
1189
1190 // We don't need the thread handle. Keeping it open blocks the thread from terminating.
1191 CloseHandle(hThread);
1192
1193 // ScheduleJob has done its job. The rest happens inside the thread.
1194 dwErrorCode = ERROR_SUCCESS;
1195
1196 Cleanup:
1197 SetLastError(dwErrorCode);
1198 return (dwErrorCode == ERROR_SUCCESS);
1199 }
1200
1201 PLOCAL_JOB
1202 ReadJobShadowFile(PCWSTR pwszFilePath)
1203 {
1204 DWORD cbFileSize;
1205 DWORD cbRead;
1206 HANDLE hFile = INVALID_HANDLE_VALUE;
1207 PLOCAL_JOB pJob;
1208 PLOCAL_JOB pReturnValue = NULL;
1209 PLOCAL_PRINTER pPrinter;
1210 PLOCAL_PRINT_PROCESSOR pPrintProcessor;
1211 PSHD_HEADER pShadowFile = NULL;
1212 PWSTR pwszPrinterName;
1213 PWSTR pwszPrintProcessor;
1214
1215 TRACE("ReadJobShadowFile(%S)\n", pwszFilePath);
1216
1217 // Try to open the file.
1218 hFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
1219 if (hFile == INVALID_HANDLE_VALUE)
1220 {
1221 ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1222 goto Cleanup;
1223 }
1224
1225 // Get its file size (small enough for a single DWORD) and allocate memory for all of it.
1226 cbFileSize = GetFileSize(hFile, NULL);
1227 pShadowFile = DllAllocSplMem(cbFileSize);
1228 if (!pShadowFile)
1229 {
1230 ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
1231 goto Cleanup;
1232 }
1233
1234 // Read the entire file.
1235 if (!ReadFile(hFile, pShadowFile, cbFileSize, &cbRead, NULL))
1236 {
1237 ERR("ReadFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1238 goto Cleanup;
1239 }
1240
1241 // Check signature and header size.
1242 if (pShadowFile->dwSignature != SHD_WIN2003_SIGNATURE || pShadowFile->cbHeader != sizeof(SHD_HEADER))
1243 {
1244 ERR("Signature or Header Size mismatch for file \"%S\"!\n", pwszFilePath);
1245 goto Cleanup;
1246 }
1247
1248 // Retrieve the associated printer from the list.
1249 pwszPrinterName = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrinterName);
1250 pPrinter = LookupElementSkiplist(&PrinterList, &pwszPrinterName, NULL);
1251 if (!pPrinter)
1252 {
1253 ERR("Shadow file \"%S\" references a non-existing printer \"%S\"!\n", pwszFilePath, pwszPrinterName);
1254 goto Cleanup;
1255 }
1256
1257 // Retrieve the associated Print Processor from the list.
1258 pwszPrintProcessor = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessor);
1259 pPrintProcessor = FindPrintProcessor(pwszPrintProcessor);
1260 if (!pPrintProcessor)
1261 {
1262 ERR("Shadow file \"%S\" references a non-existing Print Processor \"%S\"!\n", pwszFilePath, pwszPrintProcessor);
1263 goto Cleanup;
1264 }
1265
1266 // Create a new job structure and copy over the relevant fields.
1267 pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
1268 if (!pJob)
1269 {
1270 ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
1271 goto Cleanup;
1272 }
1273
1274 pJob->dwJobID = pShadowFile->dwJobID;
1275 pJob->dwPriority = pShadowFile->dwPriority;
1276 pJob->dwStartTime = pShadowFile->dwStartTime;
1277 pJob->dwTotalPages = pShadowFile->dwTotalPages;
1278 pJob->dwUntilTime = pShadowFile->dwUntilTime;
1279 pJob->pPrinter = pPrinter;
1280 pJob->pPrintProcessor = pPrintProcessor;
1281 pJob->pDevMode = DuplicateDevMode((PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode));
1282 pJob->pwszDatatype = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
1283 pJob->pwszMachineName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offMachineName));
1284 CopyMemory(&pJob->stSubmitted, &pShadowFile->stSubmitted, sizeof(SYSTEMTIME));
1285
1286 // Copy the optional values.
1287 if (pShadowFile->offDocumentName)
1288 pJob->pwszDocumentName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));
1289
1290 if (pShadowFile->offNotifyName)
1291 pJob->pwszNotifyName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offNotifyName));
1292
1293 if (pShadowFile->offPrintProcessorParameters)
1294 pJob->pwszPrintProcessorParameters = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessorParameters));
1295
1296 if (pShadowFile->offUserName)
1297 pJob->pwszUserName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offUserName));
1298
1299 // Jobs read from shadow files were always added using AddJob.
1300 pJob->bAddedJob = TRUE;
1301
1302 pReturnValue = pJob;
1303
1304 Cleanup:
1305 if (pShadowFile)
1306 DllFreeSplMem(pShadowFile);
1307
1308 if (hFile != INVALID_HANDLE_VALUE)
1309 CloseHandle(hFile);
1310
1311 return pReturnValue;
1312 }
1313
1314 BOOL
1315 WriteJobShadowFile(PWSTR pwszFilePath, const PLOCAL_JOB pJob)
1316 {
1317 BOOL bReturnValue = FALSE;
1318 DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
1319 DWORD cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
1320 DWORD cbDocumentName = 0;
1321 DWORD cbFileSize;
1322 DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
1323 DWORD cbNotifyName = 0;
1324 DWORD cbPrinterDriver = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
1325 DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
1326 DWORD cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
1327 DWORD cbPrintProcessorParameters = 0;
1328 DWORD cbUserName = 0;
1329 DWORD cbWritten;
1330 DWORD dwCurrentOffset;
1331 HANDLE hSHDFile = INVALID_HANDLE_VALUE;
1332 HANDLE hSPLFile = INVALID_HANDLE_VALUE;
1333 PSHD_HEADER pShadowFile = NULL;
1334
1335 TRACE("WriteJobShadowFile(%S, %p)\n", pwszFilePath, pJob);
1336
1337 // Try to open the SHD file.
1338 hSHDFile = CreateFileW(pwszFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
1339 if (hSHDFile == INVALID_HANDLE_VALUE)
1340 {
1341 ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1342 goto Cleanup;
1343 }
1344
1345 // Calculate the lengths of the optional values and the total size of the shadow file.
1346 if (pJob->pwszDocumentName)
1347 cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
1348
1349 if (pJob->pwszNotifyName)
1350 cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
1351
1352 if (pJob->pwszPrintProcessorParameters)
1353 cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
1354
1355 if (pJob->pwszUserName)
1356 cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
1357
1358 cbFileSize = sizeof(SHD_HEADER) + cbDatatype + cbDocumentName + cbDevMode + cbMachineName + cbNotifyName + cbPrinterDriver + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbUserName;
1359
1360 // Allocate memory for it.
1361 pShadowFile = DllAllocSplMem(cbFileSize);
1362 if (!pShadowFile)
1363 {
1364 ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
1365 goto Cleanup;
1366 }
1367
1368 // Fill out the shadow file header information.
1369 pShadowFile->dwSignature = SHD_WIN2003_SIGNATURE;
1370 pShadowFile->cbHeader = sizeof(SHD_HEADER);
1371
1372 // Copy the values.
1373 pShadowFile->dwJobID = pJob->dwJobID;
1374 pShadowFile->dwPriority = pJob->dwPriority;
1375 pShadowFile->dwStartTime = pJob->dwStartTime;
1376 pShadowFile->dwTotalPages = pJob->dwTotalPages;
1377 pShadowFile->dwUntilTime = pJob->dwUntilTime;
1378 CopyMemory(&pShadowFile->stSubmitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
1379
1380 // Determine the file size of the .SPL file
1381 wcscpy(wcsrchr(pwszFilePath, L'.'), L".SPL");
1382 hSPLFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
1383 if (hSPLFile != INVALID_HANDLE_VALUE)
1384 pShadowFile->dwSPLSize = GetFileSize(hSPLFile, NULL);
1385
1386 // Add the extra values that are stored as offsets in the shadow file.
1387 // The first value begins right after the shadow file header.
1388 dwCurrentOffset = sizeof(SHD_HEADER);
1389
1390 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
1391 pShadowFile->offDatatype = dwCurrentOffset;
1392 dwCurrentOffset += cbDatatype;
1393
1394 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pDevMode, cbDevMode);
1395 pShadowFile->offDevMode = dwCurrentOffset;
1396 dwCurrentOffset += cbDevMode;
1397
1398 // offDriverName is only written, but automatically determined through offPrinterName when reading.
1399 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterDriver, cbPrinterDriver);
1400 pShadowFile->offDriverName = dwCurrentOffset;
1401 dwCurrentOffset += cbPrinterDriver;
1402
1403 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszMachineName, cbMachineName);
1404 pShadowFile->offMachineName = dwCurrentOffset;
1405 dwCurrentOffset += cbMachineName;
1406
1407 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterName, cbPrinterName);
1408 pShadowFile->offPrinterName = dwCurrentOffset;
1409 dwCurrentOffset += cbPrinterName;
1410
1411 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrintProcessor->pwszName, cbPrintProcessor);
1412 pShadowFile->offPrintProcessor = dwCurrentOffset;
1413 dwCurrentOffset += cbPrintProcessor;
1414
1415 // Copy the optional values.
1416 if (cbDocumentName)
1417 {
1418 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDocumentName, cbDocumentName);
1419 pShadowFile->offDocumentName = dwCurrentOffset;
1420 dwCurrentOffset += cbDocumentName;
1421 }
1422
1423 if (cbNotifyName)
1424 {
1425 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszNotifyName, cbNotifyName);
1426 pShadowFile->offNotifyName = dwCurrentOffset;
1427 dwCurrentOffset += cbNotifyName;
1428 }
1429
1430 if (cbPrintProcessorParameters)
1431 {
1432 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszPrintProcessorParameters, cbPrintProcessorParameters);
1433 pShadowFile->offPrintProcessorParameters = dwCurrentOffset;
1434 dwCurrentOffset += cbPrintProcessorParameters;
1435 }
1436
1437 if (cbUserName)
1438 {
1439 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszUserName, cbUserName);
1440 pShadowFile->offUserName = dwCurrentOffset;
1441 dwCurrentOffset += cbUserName;
1442 }
1443
1444 // Write the file.
1445 if (!WriteFile(hSHDFile, pShadowFile, cbFileSize, &cbWritten, NULL))
1446 {
1447 ERR("WriteFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1448 goto Cleanup;
1449 }
1450
1451 bReturnValue = TRUE;
1452
1453 Cleanup:
1454 if (pShadowFile)
1455 DllFreeSplMem(pShadowFile);
1456
1457 if (hSHDFile != INVALID_HANDLE_VALUE)
1458 CloseHandle(hSHDFile);
1459
1460 if (hSPLFile != INVALID_HANDLE_VALUE)
1461 CloseHandle(hSPLFile);
1462
1463 return bReturnValue;
1464 }
1465
1466 void
1467 FreeJob(PLOCAL_JOB pJob)
1468 {
1469 PWSTR pwszSHDFile;
1470
1471 TRACE("FreeJob(%p)\n", pJob);
1472
1473 // Remove the Job from both Job Lists.
1474 DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
1475 DeleteElementSkiplist(&GlobalJobList, pJob);
1476
1477 // Try to delete the corresponding .SHD file.
1478 pwszSHDFile = DllAllocSplMem(GetJobFilePath(L"SHD", 0, NULL));
1479 if (pwszSHDFile && GetJobFilePath(L"SHD", pJob->dwJobID, pwszSHDFile))
1480 DeleteFileW(pwszSHDFile);
1481
1482 // Free memory for the mandatory fields.
1483 DllFreeSplMem(pJob->pDevMode);
1484 DllFreeSplStr(pJob->pwszDatatype);
1485 DllFreeSplStr(pJob->pwszDocumentName);
1486 DllFreeSplStr(pJob->pwszMachineName);
1487 DllFreeSplStr(pJob->pwszNotifyName);
1488 DllFreeSplStr(pJob->pwszUserName);
1489
1490 // Free memory for the optional fields if they are present.
1491 if (pJob->pwszOutputFile)
1492 DllFreeSplStr(pJob->pwszOutputFile);
1493
1494 if (pJob->pwszPrintProcessorParameters)
1495 DllFreeSplStr(pJob->pwszPrintProcessorParameters);
1496
1497 if (pJob->pwszStatus)
1498 DllFreeSplStr(pJob->pwszStatus);
1499
1500 // Finally free the job structure itself.
1501 DllFreeSplMem(pJob);
1502 }