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