[LOCALSPL]
[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 _GlobalJobListCompareRoutine
19 *
20 * SKIPLIST_COMPARE_ROUTINE for the Global Job List.
21 * We need the Global Job List to check whether a Job ID is already in use. Consequently, this list is sorted by ID.
22 */
23 static int WINAPI
24 _GlobalJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
25 {
26 PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
27 PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
28
29 return A->dwJobID - B->dwJobID;
30 }
31
32 /**
33 * @name _PrinterJobListCompareRoutine
34 *
35 * SKIPLIST_COMPARE_ROUTINE for the each Printer's Job List.
36 * Jobs in this list are sorted in the desired order of processing.
37 */
38 static int WINAPI
39 _PrinterJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
40 {
41 PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
42 PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
43 int iComparison;
44 FILETIME ftSubmittedA;
45 FILETIME ftSubmittedB;
46 ULARGE_INTEGER uliSubmittedA;
47 ULARGE_INTEGER uliSubmittedB;
48 ULONGLONG ullResult;
49
50 // First compare the priorities to determine the order.
51 // The job with a higher priority shall come first.
52 iComparison = A->dwPriority - B->dwPriority;
53 if (iComparison != 0)
54 return iComparison;
55
56 // Both have the same priority, so go by creation time.
57 // Comparison is done using the MSDN-recommended way for comparing SYSTEMTIMEs.
58 if (!SystemTimeToFileTime(&A->stSubmitted, &ftSubmittedA))
59 {
60 ERR("SystemTimeToFileTime failed for A with error %lu!\n", GetLastError());
61 return 0;
62 }
63
64 if (!SystemTimeToFileTime(&B->stSubmitted, &ftSubmittedB))
65 {
66 ERR("SystemTimeToFileTime failed for B with error %lu!\n", GetLastError());
67 return 0;
68 }
69
70 uliSubmittedA.LowPart = ftSubmittedA.dwLowDateTime;
71 uliSubmittedA.HighPart = ftSubmittedA.dwHighDateTime;
72 uliSubmittedB.LowPart = ftSubmittedB.dwLowDateTime;
73 uliSubmittedB.HighPart = ftSubmittedB.dwHighDateTime;
74 ullResult = uliSubmittedA.QuadPart - uliSubmittedB.QuadPart;
75
76 if (ullResult < 0)
77 return -1;
78 else if (ullResult > 0)
79 return 1;
80
81 return 0;
82 }
83
84 BOOL
85 GetNextJobID(PDWORD dwJobID)
86 {
87 ++_dwLastJobID;
88
89 while (LookupElementSkiplist(&GlobalJobList, &_dwLastJobID, NULL))
90 {
91 // This ID is already taken. Try the next one.
92 ++_dwLastJobID;
93 }
94
95 if (!IS_VALID_JOB_ID(_dwLastJobID))
96 {
97 ERR("Job ID %lu isn't valid!\n", _dwLastJobID);
98 return FALSE;
99 }
100
101 *dwJobID = _dwLastJobID;
102 return TRUE;
103 }
104
105 void
106 InitializeGlobalJobList()
107 {
108 const WCHAR wszPath[] = L"\\PRINTERS\\?????.SHD";
109 const DWORD cchPath = _countof(wszPath) - 1;
110 const DWORD cchFolders = sizeof("\\PRINTERS\\") - 1;
111 const DWORD cchPattern = sizeof("?????") - 1;
112
113 DWORD dwJobID;
114 HANDLE hFind;
115 PLOCAL_JOB pJob = NULL;
116 PWSTR p;
117 WCHAR wszFullPath[MAX_PATH];
118 WIN32_FIND_DATAW FindData;
119
120 // This one is incremented in GetNextJobID.
121 _dwLastJobID = 0;
122
123 // Initialize an empty list for all jobs of all local printers.
124 // We will search it by Job ID (supply a pointer to a DWORD in LookupElementSkiplist).
125 InitializeSkiplist(&GlobalJobList, DllAllocSplMem, _GlobalJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
126
127 // Construct the full path search pattern.
128 CopyMemory(wszFullPath, wszSpoolDirectory, cchSpoolDirectory * sizeof(WCHAR));
129 CopyMemory(&wszFullPath[cchSpoolDirectory], wszPath, (cchPath + 1) * sizeof(WCHAR));
130
131 // Use the search pattern to look for unfinished jobs serialized in shadow files (.SHD)
132 hFind = FindFirstFileW(wszFullPath, &FindData);
133 if (hFind == INVALID_HANDLE_VALUE)
134 {
135 // No unfinished jobs found.
136 goto Cleanup;
137 }
138
139 do
140 {
141 // Skip possible subdirectories.
142 if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
143 continue;
144
145 // Extract the Job ID and verify the file name format at the same time.
146 dwJobID = wcstoul(FindData.cFileName, &p, 10);
147 if (!IS_VALID_JOB_ID(dwJobID))
148 continue;
149
150 if (wcsicmp(p, L".SHD") != 0)
151 continue;
152
153 // This shadow file has a valid name. Construct the full path and try to load it.
154 CopyMemory(&wszFullPath[cchSpoolDirectory + cchFolders], FindData.cFileName, cchPattern);
155 pJob = ReadJobShadowFile(wszFullPath);
156 if (!pJob)
157 continue;
158
159 // Add it to the Global Job List.
160 if (!InsertElementSkiplist(&GlobalJobList, pJob))
161 {
162 ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
163 goto Cleanup;
164 }
165
166 // Add it to the Printer's Job List.
167 if (!InsertElementSkiplist(&pJob->pPrinter->JobList, pJob))
168 {
169 ERR("InsertElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
170 goto Cleanup;
171 }
172 }
173 while (FindNextFileW(hFind, &FindData));
174
175 Cleanup:
176 // Outside the loop
177 if (hFind)
178 FindClose(hFind);
179 }
180
181 void
182 InitializePrinterJobList(PLOCAL_PRINTER pPrinter)
183 {
184 // Initialize an empty list for this printer's jobs.
185 // This one is only for sorting the jobs. If you need to lookup a job, search the GlobalJobList by Job ID.
186 InitializeSkiplist(&pPrinter->JobList, DllAllocSplMem, _PrinterJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
187 }
188
189 BOOL WINAPI
190 LocalAddJob(HANDLE hPrinter, DWORD Level, LPBYTE pData, DWORD cbBuf, LPDWORD pcbNeeded)
191 {
192 const WCHAR wszDoubleBackslash[] = L"\\";
193 const DWORD cchDoubleBackslash = _countof(wszDoubleBackslash) - 1;
194 const WCHAR wszPrintersPath[] = L"\\PRINTERS\\";
195 const DWORD cchPrintersPath = _countof(wszPrintersPath) - 1;
196 const DWORD cchSpl = _countof("?????.SPL") - 1;
197
198 ADDJOB_INFO_1W AddJobInfo1;
199 DWORD cchMachineName;
200 DWORD cchUserName;
201 DWORD dwErrorCode;
202 PBYTE p;
203 PLOCAL_HANDLE pHandle;
204 PLOCAL_JOB pJob;
205 PLOCAL_PRINTER_HANDLE pPrinterHandle;
206 RPC_BINDING_HANDLE hServerBinding = NULL;
207 RPC_WSTR pwszBinding = NULL;
208 RPC_WSTR pwszMachineName = NULL;
209
210 // Check if this is a printer handle.
211 pHandle = (PLOCAL_HANDLE)hPrinter;
212 if (pHandle->HandleType != Printer)
213 {
214 dwErrorCode = ERROR_INVALID_HANDLE;
215 goto Cleanup;
216 }
217
218 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
219
220 // This handle must not have started a job yet!
221 if (pPrinterHandle->pStartedJob)
222 {
223 dwErrorCode = ERROR_INVALID_HANDLE;
224 goto Cleanup;
225 }
226
227 // Check if this is the right structure level.
228 if (Level != 1)
229 {
230 dwErrorCode = ERROR_INVALID_LEVEL;
231 goto Cleanup;
232 }
233
234 // Check if the printer is set to do direct printing.
235 // The Job List isn't used in this case.
236 if (pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT)
237 {
238 dwErrorCode = ERROR_INVALID_ACCESS;
239 goto Cleanup;
240 }
241
242 // Check if the supplied buffer is large enough.
243 *pcbNeeded = sizeof(ADDJOB_INFO_1W) + (cchSpoolDirectory + cchPrintersPath + cchSpl + 1) * sizeof(WCHAR);
244 if (cbBuf < *pcbNeeded)
245 {
246 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
247 goto Cleanup;
248 }
249
250 // Create a new job.
251 pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
252 if (!pJob)
253 {
254 dwErrorCode = GetLastError();
255 ERR("DllAllocSplMem failed with error %lu!\n", dwErrorCode);
256 goto Cleanup;
257 }
258
259 // Reserve an ID for this job.
260 if (!GetNextJobID(&pJob->dwJobID))
261 {
262 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
263 goto Cleanup;
264 }
265
266 // Copy over defaults to the LOCAL_JOB structure.
267 pJob->pPrinter = pPrinterHandle->pPrinter;
268 pJob->pPrintProcessor = pPrinterHandle->pPrinter->pPrintProcessor;
269 pJob->dwPriority = DEF_PRIORITY;
270 pJob->pwszDatatype = AllocSplStr(pPrinterHandle->pwszDatatype);
271 pJob->pwszDocumentName = AllocSplStr(wszDefaultDocumentName);
272 CopyMemory(&pJob->DevMode, &pPrinterHandle->DevMode, sizeof(DEVMODEW));
273 GetSystemTime(&pJob->stSubmitted);
274
275 // Get the user name for the Job.
276 cchUserName = UNLEN + 1;
277 pJob->pwszUserName = DllAllocSplMem(cchUserName * sizeof(WCHAR));
278 if (!GetUserNameW(pJob->pwszUserName, &cchUserName))
279 {
280 dwErrorCode = GetLastError();
281 ERR("GetUserNameW failed with error %lu!\n", dwErrorCode);
282 goto Cleanup;
283 }
284
285 // FIXME: For now, pwszNotifyName equals pwszUserName.
286 pJob->pwszNotifyName = AllocSplStr(pJob->pwszUserName);
287
288 // Get the name of the machine that submitted the Job over RPC.
289 dwErrorCode = RpcBindingServerFromClient(NULL, &hServerBinding);
290 if (dwErrorCode != RPC_S_OK)
291 {
292 ERR("RpcBindingServerFromClient failed with status %lu!\n", dwErrorCode);
293 goto Cleanup;
294 }
295
296 dwErrorCode = RpcBindingToStringBindingW(hServerBinding, &pwszBinding);
297 if (dwErrorCode != RPC_S_OK)
298 {
299 ERR("RpcBindingToStringBindingW failed with status %lu!\n", dwErrorCode);
300 goto Cleanup;
301 }
302
303 dwErrorCode = RpcStringBindingParseW(pwszBinding, NULL, NULL, &pwszMachineName, NULL, NULL);
304 if (dwErrorCode != RPC_S_OK)
305 {
306 ERR("RpcStringBindingParseW failed with status %lu!\n", dwErrorCode);
307 goto Cleanup;
308 }
309
310 cchMachineName = wcslen(pwszMachineName);
311 pJob->pwszMachineName = DllAllocSplMem((cchMachineName + cchDoubleBackslash + 1) * sizeof(WCHAR));
312 CopyMemory(pJob->pwszMachineName, wszDoubleBackslash, cchDoubleBackslash * sizeof(WCHAR));
313 CopyMemory(pJob->pwszMachineName + cchDoubleBackslash, pwszMachineName, (cchMachineName + 1) * sizeof(WCHAR));
314
315 // Add the job to the Global Job List.
316 if (!InsertElementSkiplist(&GlobalJobList, pJob))
317 {
318 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
319 ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
320 goto Cleanup;
321 }
322
323 // Add the job at the end of the Printer's Job List.
324 // As all new jobs are created with default priority, we can be sure that it would always be inserted at the end.
325 if (!InsertTailElementSkiplist(&pJob->pPrinter->JobList, pJob))
326 {
327 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
328 ERR("InsertTailElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
329 goto Cleanup;
330 }
331
332 // Return a proper ADDJOB_INFO_1W structure.
333 AddJobInfo1.JobId = pJob->dwJobID;
334 AddJobInfo1.Path = (PWSTR)(pData + sizeof(ADDJOB_INFO_1W));
335 p = pData;
336 CopyMemory(p, &AddJobInfo1, sizeof(ADDJOB_INFO_1W));
337 p += sizeof(ADDJOB_INFO_1W);
338 CopyMemory(p, wszSpoolDirectory, cchSpoolDirectory);
339 p += cchSpoolDirectory;
340 CopyMemory(p, wszPrintersPath, cchPrintersPath);
341 p += cchPrintersPath;
342 swprintf((PWSTR)p, L"%05lu.SPL", pJob->dwJobID);
343
344 dwErrorCode = ERROR_SUCCESS;
345
346 Cleanup:
347 if (pwszMachineName)
348 RpcStringFreeW(&pwszMachineName);
349
350 if (pwszBinding)
351 RpcStringFreeW(&pwszBinding);
352
353 if (hServerBinding)
354 RpcBindingFree(&hServerBinding);
355
356 SetLastError(dwErrorCode);
357 return (dwErrorCode == ERROR_SUCCESS);
358 }
359
360
361 static DWORD
362 _LocalGetJobLevel1(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PBYTE pOutput, DWORD cbBuf, PDWORD pcbNeeded)
363 {
364 DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
365 DWORD cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
366 DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
367 DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
368 DWORD cbStatus = 0;
369 DWORD cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
370 DWORD dwErrorCode;
371 JOB_INFO_1W JobInfo1 = { 0 };
372 PBYTE pString;
373
374 // A Status Message is optional.
375 if (pJob->pwszStatus)
376 cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
377
378 // Check if the supplied buffer is large enough.
379 *pcbNeeded = sizeof(JOB_INFO_1W) + cbDatatype + cbDocumentName + cbMachineName + cbPrinterName + cbStatus + cbUserName;
380 if (cbBuf < *pcbNeeded)
381 {
382 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
383 goto Cleanup;
384 }
385
386 // Put the strings right after the JOB_INFO_1W structure.
387 pString = pOutput + sizeof(JOB_INFO_1W);
388
389 JobInfo1.pDatatype = (PWSTR)pString;
390 CopyMemory(pString, pJob->pwszDatatype, cbDatatype);
391 pString += cbDatatype;
392
393 JobInfo1.pDocument = (PWSTR)pString;
394 CopyMemory(pString, pJob->pwszDocumentName, cbDocumentName);
395 pString += cbDocumentName;
396
397 JobInfo1.pMachineName = (PWSTR)pString;
398 CopyMemory(pString, pJob->pwszMachineName, cbMachineName);
399 pString += cbMachineName;
400
401 JobInfo1.pPrinterName = (PWSTR)pString;
402 CopyMemory(pString, pJob->pPrinter->pwszPrinterName, cbPrinterName);
403 pString += cbPrinterName;
404
405 if (cbStatus)
406 {
407 JobInfo1.pStatus = (PWSTR)pString;
408 CopyMemory(pString, pJob->pwszStatus, cbStatus);
409 pString += cbStatus;
410 }
411
412 JobInfo1.pUserName = (PWSTR)pString;
413 CopyMemory(pString, pJob->pwszUserName, cbUserName);
414 pString += cbUserName;
415
416 // Fill the structure and copy it as well.
417 JobInfo1.JobId = pJob->dwJobID;
418 JobInfo1.Priority = pJob->dwPriority;
419 JobInfo1.Status = pJob->dwStatus;
420 JobInfo1.TotalPages = pJob->dwTotalPages;
421 CopyMemory(&JobInfo1.Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
422 CopyMemory(pOutput, &JobInfo1, sizeof(JOB_INFO_1W));
423
424 dwErrorCode = ERROR_SUCCESS;
425
426 Cleanup:
427 return dwErrorCode;
428 }
429
430 static DWORD
431 _LocalGetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PBYTE pOutput, DWORD cbBuf, PDWORD pcbNeeded)
432 {
433 DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
434 DWORD cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
435 DWORD cbDriverName = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
436 DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
437 DWORD cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
438 DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
439 DWORD cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
440 DWORD cbPrintProcessorParameters = 0;
441 DWORD cbStatus = 0;
442 DWORD cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
443 DWORD dwErrorCode;
444 FILETIME ftNow;
445 FILETIME ftSubmitted;
446 JOB_INFO_2W JobInfo2 = { 0 };
447 PBYTE pString;
448 ULARGE_INTEGER uliNow;
449 ULARGE_INTEGER uliSubmitted;
450
451 // Print Processor Parameters and Status Message are optional.
452 if (pJob->pwszPrintProcessorParameters)
453 cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
454
455 if (pJob->pwszStatus)
456 cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
457
458 // Check if the supplied buffer is large enough.
459 *pcbNeeded = sizeof(JOB_INFO_2W) + cbDatatype + sizeof(DEVMODEW) + cbDocumentName + cbDriverName + cbMachineName + cbNotifyName + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbStatus + cbUserName;
460 if (cbBuf < *pcbNeeded)
461 {
462 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
463 goto Cleanup;
464 }
465
466 // Put the strings right after the JOB_INFO_2W structure.
467 pString = pOutput + sizeof(JOB_INFO_2W);
468
469 JobInfo2.pDatatype = (PWSTR)pString;
470 CopyMemory(pString, pJob->pwszDatatype, cbDatatype);
471 pString += cbDatatype;
472
473 JobInfo2.pDevMode = (PDEVMODEW)pString;
474 CopyMemory(pString, &pJob->DevMode, sizeof(DEVMODEW));
475 pString += sizeof(DEVMODEW);
476
477 JobInfo2.pDocument = (PWSTR)pString;
478 CopyMemory(pString, pJob->pwszDocumentName, cbDocumentName);
479 pString += cbDocumentName;
480
481 JobInfo2.pDriverName = (PWSTR)pString;
482 CopyMemory(pString, pJob->pPrinter->pwszPrinterDriver, cbDriverName);
483 pString += cbDriverName;
484
485 JobInfo2.pMachineName = (PWSTR)pString;
486 CopyMemory(pString, pJob->pwszMachineName, cbMachineName);
487 pString += cbMachineName;
488
489 JobInfo2.pNotifyName = (PWSTR)pString;
490 CopyMemory(pString, pJob->pwszNotifyName, cbNotifyName);
491 pString += cbNotifyName;
492
493 JobInfo2.pPrinterName = (PWSTR)pString;
494 CopyMemory(pString, pJob->pPrinter->pwszPrinterName, cbPrinterName);
495 pString += cbPrinterName;
496
497 JobInfo2.pPrintProcessor = (PWSTR)pString;
498 CopyMemory(pString, pJob->pPrintProcessor->pwszName, cbPrintProcessor);
499 pString += cbPrintProcessor;
500
501 if (cbPrintProcessorParameters)
502 {
503 JobInfo2.pParameters = (PWSTR)pString;
504 CopyMemory(pString, pJob->pwszPrintProcessorParameters, cbPrintProcessorParameters);
505 pString += cbPrintProcessorParameters;
506 }
507
508 if (cbStatus)
509 {
510 JobInfo2.pStatus = (PWSTR)pString;
511 CopyMemory(pString, pJob->pwszStatus, cbStatus);
512 pString += cbStatus;
513 }
514
515 JobInfo2.pUserName = (PWSTR)pString;
516 CopyMemory(pString, pJob->pwszUserName, cbUserName);
517 pString += cbUserName;
518
519 // Time in JOB_INFO_2W is the number of milliseconds elapsed since the job was submitted. Calculate this time.
520 if (!SystemTimeToFileTime(&pJob->stSubmitted, &ftSubmitted))
521 {
522 ERR("SystemTimeToFileTime failed with error %lu!\n", GetLastError());
523 return FALSE;
524 }
525
526 GetSystemTimeAsFileTime(&ftNow);
527 uliSubmitted.LowPart = ftSubmitted.dwLowDateTime;
528 uliSubmitted.HighPart = ftSubmitted.dwHighDateTime;
529 uliNow.LowPart = ftNow.dwLowDateTime;
530 uliNow.HighPart = ftNow.dwHighDateTime;
531 JobInfo2.Time = (DWORD)((uliNow.QuadPart - uliSubmitted.QuadPart) / 10000);
532
533 // Position in JOB_INFO_2W is the 1-based index of the job in the processing queue.
534 // Retrieve this through the element index of the job in the Printer's Job List.
535 if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &JobInfo2.Position))
536 {
537 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
538 ERR("pJob could not be located in the Printer's Job List!\n");
539 goto Cleanup;
540 }
541
542 // Make the index 1-based.
543 ++JobInfo2.Position;
544
545 // Fill the rest of the structure.
546 JobInfo2.JobId = pJob->dwJobID;
547 JobInfo2.PagesPrinted = pJob->dwPagesPrinted;
548 JobInfo2.Priority = pJob->dwPriority;
549 JobInfo2.StartTime = pJob->dwStartTime;
550 JobInfo2.Status = pJob->dwStatus;
551 JobInfo2.TotalPages = pJob->dwTotalPages;
552 JobInfo2.UntilTime = pJob->dwUntilTime;
553 CopyMemory(&JobInfo2.Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
554
555 // Finally copy the structure to the output pointer.
556 CopyMemory(pOutput, &JobInfo2, sizeof(JOB_INFO_2W));
557 dwErrorCode = ERROR_SUCCESS;
558
559 Cleanup:
560 return dwErrorCode;
561 }
562
563 BOOL WINAPI
564 LocalGetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pOutput, DWORD cbBuf, LPDWORD pcbNeeded)
565 {
566 DWORD dwErrorCode;
567 PLOCAL_HANDLE pHandle;
568 PLOCAL_JOB pJob;
569 PLOCAL_PRINTER_HANDLE pPrinterHandle;
570
571 // Check if this is a printer handle.
572 pHandle = (PLOCAL_HANDLE)hPrinter;
573 if (pHandle->HandleType != Printer)
574 {
575 dwErrorCode = ERROR_INVALID_HANDLE;
576 goto Cleanup;
577 }
578
579 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
580
581 // Get the desired job.
582 pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
583 if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
584 {
585 dwErrorCode = ERROR_INVALID_PARAMETER;
586 goto Cleanup;
587 }
588
589 // The function behaves differently for each level.
590 if (Level == 1)
591 dwErrorCode = _LocalGetJobLevel1(pPrinterHandle, pJob, pOutput, cbBuf, pcbNeeded);
592 else if (Level == 2)
593 dwErrorCode = _LocalGetJobLevel2(pPrinterHandle, pJob, pOutput, cbBuf, pcbNeeded);
594 else
595 dwErrorCode = ERROR_INVALID_LEVEL;
596
597 Cleanup:
598 SetLastError(dwErrorCode);
599 return (dwErrorCode == ERROR_SUCCESS);
600 }
601
602 static DWORD
603 _LocalSetJobLevel1(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_1W pJobInfo)
604 {
605 DWORD dwErrorCode;
606
607 // First check the validity of the input before changing anything.
608 if (!FindDatatype(pJob->pPrintProcessor, pJobInfo->pDatatype))
609 {
610 dwErrorCode = ERROR_INVALID_DATATYPE;
611 goto Cleanup;
612 }
613
614 // Check if the datatype has changed.
615 if (wcscmp(pJob->pwszDatatype, pJobInfo->pDatatype) != 0)
616 {
617 // Use the new value.
618 if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
619 {
620 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
621 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
622 goto Cleanup;
623 }
624 }
625
626 // Check if the document name has changed.
627 if (wcscmp(pJob->pwszDocumentName, pJobInfo->pDocument) != 0)
628 {
629 // Use the new value.
630 if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
631 {
632 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
633 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
634 goto Cleanup;
635 }
636 }
637
638 // Check if the status message has changed.
639 if ((!pJob->pwszStatus && pJobInfo->pStatus) || wcscmp(pJob->pwszStatus, pJobInfo->pStatus) != 0)
640 {
641 // Use the new value.
642 if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
643 {
644 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
645 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
646 goto Cleanup;
647 }
648 }
649
650 // Check if the user name has changed.
651 if (wcscmp(pJob->pwszUserName, pJobInfo->pUserName) != 0)
652 {
653 // The new user name doesn't need to exist, so no additional verification is required.
654
655 // Use the new value.
656 if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
657 {
658 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
659 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
660 goto Cleanup;
661 }
662 }
663
664 // Check if the priority has changed.
665 if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
666 {
667 // Set the new priority.
668 pJob->dwPriority = pJobInfo->Priority;
669
670 // Remove and reinsert the job in the Printer's Job List.
671 // The Compare function will be used to find the right position now considering the new priority.
672 DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
673 InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
674 }
675
676 // Check if the status flags have changed.
677 if (pJob->dwStatus != pJobInfo->Status)
678 {
679 // Only add status flags that make sense.
680 if (pJobInfo->Status & JOB_STATUS_PAUSED)
681 pJob->dwStatus |= JOB_STATUS_PAUSED;
682
683 if (pJobInfo->Status & JOB_STATUS_ERROR)
684 pJob->dwStatus |= JOB_STATUS_ERROR;
685
686 if (pJobInfo->Status & JOB_STATUS_OFFLINE)
687 pJob->dwStatus |= JOB_STATUS_OFFLINE;
688
689 if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
690 pJob->dwStatus |= JOB_STATUS_PAPEROUT;
691 }
692
693 dwErrorCode = ERROR_SUCCESS;
694
695 Cleanup:
696 return dwErrorCode;
697 }
698
699 static DWORD
700 _LocalSetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_2W pJobInfo)
701 {
702 DWORD dwErrorCode;
703 PLOCAL_PRINT_PROCESSOR pPrintProcessor;
704
705 // First check the validity of the input before changing anything.
706 pPrintProcessor = FindPrintProcessor(pJobInfo->pPrintProcessor);
707 if (!pPrintProcessor)
708 {
709 dwErrorCode = ERROR_UNKNOWN_PRINTPROCESSOR;
710 goto Cleanup;
711 }
712
713 if (!FindDatatype(pPrintProcessor, pJobInfo->pDatatype))
714 {
715 dwErrorCode = ERROR_INVALID_DATATYPE;
716 goto Cleanup;
717 }
718
719 // Check if the datatype has changed.
720 if (wcscmp(pJob->pwszDatatype, pJobInfo->pDatatype) != 0)
721 {
722 // Use the new value.
723 if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
724 {
725 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
726 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
727 goto Cleanup;
728 }
729 }
730
731 // Check if the document name has changed.
732 if (wcscmp(pJob->pwszDocumentName, pJobInfo->pDocument) != 0)
733 {
734 // Use the new value.
735 if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
736 {
737 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
738 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
739 goto Cleanup;
740 }
741 }
742
743 // Check if the notify name has changed.
744 if (wcscmp(pJob->pwszNotifyName, pJobInfo->pNotifyName) != 0)
745 {
746 // The new notify name doesn't need to exist, so no additional verification is required.
747
748 // Use the new value.
749 if (!ReallocSplStr(&pJob->pwszNotifyName, pJobInfo->pNotifyName))
750 {
751 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
752 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
753 goto Cleanup;
754 }
755 }
756
757 // Check if the (optional) Print Processor Parameters have changed.
758 if ((!pJob->pwszPrintProcessorParameters && pJobInfo->pParameters) || wcscmp(pJob->pwszPrintProcessorParameters, pJobInfo->pParameters) != 0)
759 {
760 // Use the new value.
761 if (!ReallocSplStr(&pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
762 {
763 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
764 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
765 goto Cleanup;
766 }
767 }
768
769 // Check if the (optional) Status Message has changed.
770 if ((!pJob->pwszStatus && pJobInfo->pStatus) || wcscmp(pJob->pwszStatus, pJobInfo->pStatus) != 0)
771 {
772 // Use the new value.
773 if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
774 {
775 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
776 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
777 goto Cleanup;
778 }
779 }
780
781 // Check if the user name has changed.
782 if (wcscmp(pJob->pwszUserName, pJobInfo->pUserName) != 0)
783 {
784 // The new user name doesn't need to exist, so no additional verification is required.
785
786 // Use the new value.
787 if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
788 {
789 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
790 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
791 goto Cleanup;
792 }
793 }
794
795 // Check if the priority has changed.
796 if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
797 {
798 // Set the new priority.
799 pJob->dwPriority = pJobInfo->Priority;
800
801 // Remove and reinsert the job in the Printer's Job List.
802 // The Compare function will be used to find the right position now considering the new priority.
803 DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
804 InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
805 }
806
807 // Check if the status flags have changed.
808 if (pJob->dwStatus != pJobInfo->Status)
809 {
810 // Only add status flags that make sense.
811 if (pJobInfo->Status & JOB_STATUS_PAUSED)
812 pJob->dwStatus |= JOB_STATUS_PAUSED;
813
814 if (pJobInfo->Status & JOB_STATUS_ERROR)
815 pJob->dwStatus |= JOB_STATUS_ERROR;
816
817 if (pJobInfo->Status & JOB_STATUS_OFFLINE)
818 pJob->dwStatus |= JOB_STATUS_OFFLINE;
819
820 if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
821 pJob->dwStatus |= JOB_STATUS_PAPEROUT;
822 }
823
824 dwErrorCode = ERROR_SUCCESS;
825
826 Cleanup:
827 return dwErrorCode;
828 }
829
830 BOOL WINAPI
831 LocalSetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pJobInfo, DWORD Command)
832 {
833 DWORD dwErrorCode;
834 PLOCAL_HANDLE pHandle;
835 PLOCAL_JOB pJob;
836 PLOCAL_PRINTER_HANDLE pPrinterHandle;
837
838 // Check if this is a printer handle.
839 pHandle = (PLOCAL_HANDLE)hPrinter;
840 if (pHandle->HandleType != Printer)
841 {
842 dwErrorCode = ERROR_INVALID_HANDLE;
843 goto Cleanup;
844 }
845
846 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
847
848 // Get the desired job.
849 pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
850 if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
851 {
852 dwErrorCode = ERROR_INVALID_PARAMETER;
853 goto Cleanup;
854 }
855
856 // Setting job information is handled differently for each level.
857 if (Level)
858 {
859 if (Level == 1)
860 dwErrorCode = _LocalSetJobLevel1(pPrinterHandle, pJob, (PJOB_INFO_1W)pJobInfo);
861 else if (Level == 2)
862 dwErrorCode = _LocalSetJobLevel2(pPrinterHandle, pJob, (PJOB_INFO_2W)pJobInfo);
863 else
864 dwErrorCode = ERROR_INVALID_LEVEL;
865 }
866
867 if (dwErrorCode != ERROR_SUCCESS)
868 goto Cleanup;
869
870 // Perform an additional command if desired.
871 if (Command)
872 {
873 // TODO
874 }
875
876 dwErrorCode = ERROR_SUCCESS;
877
878 Cleanup:
879 SetLastError(dwErrorCode);
880 return (dwErrorCode == ERROR_SUCCESS);
881 }
882
883 PLOCAL_JOB
884 ReadJobShadowFile(PCWSTR pwszFilePath)
885 {
886 DWORD cbFileSize;
887 DWORD cbRead;
888 HANDLE hFile = INVALID_HANDLE_VALUE;
889 PLOCAL_JOB pJob;
890 PLOCAL_JOB pReturnValue = NULL;
891 PLOCAL_PRINTER pPrinter;
892 PSHD_HEADER pShadowFile = NULL;
893 PWSTR pwszPrinterName;
894
895 // Try to open the file.
896 hFile = CreateFileW(pwszFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
897 if (hFile == INVALID_HANDLE_VALUE)
898 {
899 ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
900 goto Cleanup;
901 }
902
903 // Get its file size (small enough for a single DWORD) and allocate memory for all of it.
904 cbFileSize = GetFileSize(hFile, NULL);
905 pShadowFile = DllAllocSplMem(cbFileSize);
906 if (!pShadowFile)
907 {
908 ERR("DllAllocSplMem failed with error %lufor file \"%S\"!\n", GetLastError(), pwszFilePath);
909 goto Cleanup;
910 }
911
912 // Read the entire file.
913 if (!ReadFile(hFile, pShadowFile, cbFileSize, &cbRead, NULL))
914 {
915 ERR("ReadFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
916 goto Cleanup;
917 }
918
919 // Check signature and header size.
920 if (pShadowFile->dwSignature != SHD_WIN2003_SIGNATURE || pShadowFile->cbHeader != sizeof(SHD_HEADER))
921 {
922 ERR("Signature or Header Size mismatch for file \"%S\"!\n", pwszFilePath);
923 goto Cleanup;
924 }
925
926 // Retrieve the associated printer from the list.
927 pwszPrinterName = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrinterName);
928 pPrinter = LookupElementSkiplist(&PrinterList, &pwszPrinterName, NULL);
929 if (!pPrinter)
930 {
931 ERR("Shadow file \"%S\" references a non-existing printer!\n", pwszFilePath);
932 goto Cleanup;
933 }
934
935 // Create a new job structure and copy over the relevant fields.
936 pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
937 if (!pJob)
938 {
939 ERR("DllAllocSplMem failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
940 goto Cleanup;
941 }
942
943 pJob->dwJobID = pShadowFile->dwJobID;
944 pJob->dwTotalPages = pShadowFile->dwTotalPages;
945 pJob->dwPriority = pShadowFile->dwPriority;
946 pJob->pPrinter = pPrinter;
947 pJob->pwszDatatype = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
948 pJob->pwszDocumentName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));
949 pJob->pwszOutputFile = NULL;
950 CopyMemory(&pJob->stSubmitted, &pShadowFile->stSubmitted, sizeof(SYSTEMTIME));
951 CopyMemory(&pJob->DevMode, (PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode), sizeof(DEVMODEW));
952
953 pReturnValue = pJob;
954
955 Cleanup:
956 if (pShadowFile)
957 DllFreeSplMem(pShadowFile);
958
959 if (hFile != INVALID_HANDLE_VALUE)
960 CloseHandle(hFile);
961
962 return pReturnValue;
963 }
964
965 BOOL
966 WriteJobShadowFile(PCWSTR pwszFilePath, const PLOCAL_JOB pJob)
967 {
968 BOOL bReturnValue = FALSE;
969 DWORD cbDatatype;
970 DWORD cbDocumentName;
971 DWORD cbFileSize;
972 DWORD cbPrinterName;
973 DWORD cbWritten;
974 DWORD dwCurrentOffset;
975 HANDLE hFile = INVALID_HANDLE_VALUE;
976 PSHD_HEADER pShadowFile = NULL;
977
978 // Try to open the file.
979 hFile = CreateFileW(pwszFilePath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, NULL);
980 if (hFile == INVALID_HANDLE_VALUE)
981 {
982 ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
983 goto Cleanup;
984 }
985
986 // Compute the total size of the shadow file.
987 cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
988 cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
989 cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
990 cbFileSize = sizeof(SHD_HEADER) + cbPrinterName + cbDatatype + cbDocumentName + sizeof(DEVMODEW);
991
992 // Allocate memory for it.
993 pShadowFile = DllAllocSplMem(cbFileSize);
994 if (!pShadowFile)
995 {
996 ERR("DllAllocSplMem failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
997 goto Cleanup;
998 }
999
1000 // Fill out the shadow file header information.
1001 pShadowFile->dwSignature = SHD_WIN2003_SIGNATURE;
1002 pShadowFile->cbHeader = sizeof(SHD_HEADER);
1003
1004 // Copy the values.
1005 pShadowFile->dwJobID = pJob->dwJobID;
1006 pShadowFile->dwTotalPages = pJob->dwTotalPages;
1007 pShadowFile->dwPriority = pJob->dwPriority;
1008 CopyMemory(&pShadowFile->stSubmitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
1009
1010 // Add the extra values that are stored as offsets in the shadow file.
1011 // The first value begins right after the shadow file header.
1012 dwCurrentOffset = sizeof(SHD_HEADER);
1013
1014 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterName, cbPrinterName);
1015 pShadowFile->offPrinterName = dwCurrentOffset;
1016 dwCurrentOffset += cbPrinterName;
1017
1018 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
1019 pShadowFile->offDatatype = dwCurrentOffset;
1020 dwCurrentOffset += cbDatatype;
1021
1022 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDocumentName, cbDocumentName);
1023 pShadowFile->offDocumentName = dwCurrentOffset;
1024 dwCurrentOffset += cbDocumentName;
1025
1026 CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, &pJob->DevMode, sizeof(DEVMODEW));
1027 pShadowFile->offDevMode = dwCurrentOffset;
1028 dwCurrentOffset += sizeof(DEVMODEW);
1029
1030 // Write the file.
1031 if (!WriteFile(hFile, pShadowFile, cbFileSize, &cbWritten, NULL))
1032 {
1033 ERR("WriteFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1034 goto Cleanup;
1035 }
1036
1037 bReturnValue = TRUE;
1038
1039 Cleanup:
1040 if (pShadowFile)
1041 DllFreeSplMem(pShadowFile);
1042
1043 if (hFile != INVALID_HANDLE_VALUE)
1044 CloseHandle(hFile);
1045
1046 return bReturnValue;
1047 }
1048
1049 BOOL
1050 FreeJob(PLOCAL_JOB pJob)
1051 {
1052 ////////// TODO /////////
1053 /// Add some checks
1054 DllFreeSplStr(pJob->pwszDatatype);
1055 DllFreeSplStr(pJob->pwszDocumentName);
1056 DllFreeSplStr(pJob->pwszOutputFile);
1057 DllFreeSplMem(pJob);
1058
1059 return TRUE;
1060 }