reactos/win32ss/printing/providers/localspl/jobs.c
Colin Finck 3b93ba0f31 [LOCALSPL] Fix parameter handling in LocalSetJob and add tests for the few ways we can easily test this function.
Yes, it checks the input handle and doesn't fail if an invalid level is given, because someone may still send a Command.
This also fixes CORE-12794. Thanks for reporting!
2017-12-25 14:30:47 +01:00

1503 lines
48 KiB
C

/*
* PROJECT: ReactOS Local Spooler
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
* PURPOSE: Functions for managing print jobs
* COPYRIGHT: Copyright 2015-2017 Colin Finck (colin@reactos.org)
*/
#include "precomp.h"
// Global Variables
SKIPLIST GlobalJobList;
// Local Variables
static DWORD _dwLastJobID;
// Local Constants
static DWORD dwJobInfo1Offsets[] = {
FIELD_OFFSET(JOB_INFO_1W, pPrinterName),
FIELD_OFFSET(JOB_INFO_1W, pMachineName),
FIELD_OFFSET(JOB_INFO_1W, pUserName),
FIELD_OFFSET(JOB_INFO_1W, pDocument),
FIELD_OFFSET(JOB_INFO_1W, pDatatype),
FIELD_OFFSET(JOB_INFO_1W, pStatus),
MAXDWORD
};
static DWORD dwJobInfo2Offsets[] = {
FIELD_OFFSET(JOB_INFO_2W, pPrinterName),
FIELD_OFFSET(JOB_INFO_2W, pMachineName),
FIELD_OFFSET(JOB_INFO_2W, pUserName),
FIELD_OFFSET(JOB_INFO_2W, pDocument),
FIELD_OFFSET(JOB_INFO_2W, pNotifyName),
FIELD_OFFSET(JOB_INFO_2W, pDatatype),
FIELD_OFFSET(JOB_INFO_2W, pPrintProcessor),
FIELD_OFFSET(JOB_INFO_2W, pParameters),
FIELD_OFFSET(JOB_INFO_2W, pDriverName),
FIELD_OFFSET(JOB_INFO_2W, pStatus),
MAXDWORD
};
/**
* @name _EqualStrings
*
* Returns whether two strings are equal.
* Unlike wcscmp, this function also works with NULL strings.
*
* @param pwszA
* First string to compare.
*
* @param pwszB
* Second string to compare.
*
* @return
* TRUE if the strings are equal, FALSE if they differ.
*/
static __inline BOOL
_EqualStrings(PCWSTR pwszA, PCWSTR pwszB)
{
if (!pwszA && !pwszB)
return TRUE;
if (pwszA && !pwszB)
return FALSE;
if (!pwszA && pwszB)
return FALSE;
return (wcscmp(pwszA, pwszB) == 0);
}
static BOOL
_GetNextJobID(PDWORD dwJobID)
{
++_dwLastJobID;
while (LookupElementSkiplist(&GlobalJobList, &_dwLastJobID, NULL))
{
// This ID is already taken. Try the next one.
++_dwLastJobID;
}
if (!IS_VALID_JOB_ID(_dwLastJobID))
{
ERR("Job ID %lu isn't valid!\n", _dwLastJobID);
return FALSE;
}
*dwJobID = _dwLastJobID;
return TRUE;
}
/**
* @name _GlobalJobListCompareRoutine
*
* SKIPLIST_COMPARE_ROUTINE for the Global Job List.
* We need the Global Job List to check whether a Job ID is already in use. Consequently, this list is sorted by ID.
*/
static int WINAPI
_GlobalJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
{
PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
return A->dwJobID - B->dwJobID;
}
/**
* @name _PrinterJobListCompareRoutine
*
* SKIPLIST_COMPARE_ROUTINE for the each Printer's Job List.
* Jobs in this list are sorted in the desired order of processing.
*/
static int WINAPI
_PrinterJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
{
PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
int iComparison;
FILETIME ftSubmittedA;
FILETIME ftSubmittedB;
ULARGE_INTEGER uliSubmittedA;
ULARGE_INTEGER uliSubmittedB;
ULONGLONG ullResult;
// First compare the priorities to determine the order.
// The job with a higher priority shall come first.
iComparison = A->dwPriority - B->dwPriority;
if (iComparison != 0)
return iComparison;
// Both have the same priority, so go by creation time.
// Comparison is done using the MSDN-recommended way for comparing SYSTEMTIMEs.
if (!SystemTimeToFileTime(&A->stSubmitted, &ftSubmittedA))
{
ERR("SystemTimeToFileTime failed for A with error %lu!\n", GetLastError());
return 0;
}
if (!SystemTimeToFileTime(&B->stSubmitted, &ftSubmittedB))
{
ERR("SystemTimeToFileTime failed for B with error %lu!\n", GetLastError());
return 0;
}
uliSubmittedA.LowPart = ftSubmittedA.dwLowDateTime;
uliSubmittedA.HighPart = ftSubmittedA.dwHighDateTime;
uliSubmittedB.LowPart = ftSubmittedB.dwLowDateTime;
uliSubmittedB.HighPart = ftSubmittedB.dwHighDateTime;
ullResult = uliSubmittedA.QuadPart - uliSubmittedB.QuadPart;
if (ullResult < 0)
return -1;
else if (ullResult > 0)
return 1;
return 0;
}
DWORD
GetJobFilePath(PCWSTR pwszExtension, DWORD dwJobID, PWSTR pwszOutput)
{
TRACE("GetJobFilePath(%S, %lu, %p)\n", pwszExtension, dwJobID, pwszOutput);
if (pwszOutput)
{
CopyMemory(pwszOutput, wszJobDirectory, cchJobDirectory * sizeof(WCHAR));
swprintf(&pwszOutput[cchJobDirectory], L"\\%05lu.%s", dwJobID, pwszExtension);
}
// pwszExtension may be L"SPL" or L"SHD", same length for both!
return (cchJobDirectory + sizeof("\\?????.SPL")) * sizeof(WCHAR);
}
BOOL
InitializeGlobalJobList(void)
{
const WCHAR wszPath[] = L"\\?????.SHD";
const DWORD cchPath = _countof(wszPath) - 1;
DWORD dwErrorCode;
DWORD dwJobID;
HANDLE hFind;
PLOCAL_JOB pJob = NULL;
PWSTR p;
WCHAR wszFullPath[MAX_PATH];
WIN32_FIND_DATAW FindData;
TRACE("InitializeGlobalJobList()\n");
// This one is incremented in _GetNextJobID.
_dwLastJobID = 0;
// Initialize an empty list for all jobs of all local printers.
// We will search it by Job ID (supply a pointer to a DWORD in LookupElementSkiplist).
InitializeSkiplist(&GlobalJobList, DllAllocSplMem, _GlobalJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
// Construct the full path search pattern.
CopyMemory(wszFullPath, wszJobDirectory, cchJobDirectory * sizeof(WCHAR));
CopyMemory(&wszFullPath[cchJobDirectory], wszPath, (cchPath + 1) * sizeof(WCHAR));
// Use the search pattern to look for unfinished jobs serialized in shadow files (.SHD)
hFind = FindFirstFileW(wszFullPath, &FindData);
if (hFind == INVALID_HANDLE_VALUE)
{
// No unfinished jobs found.
dwErrorCode = ERROR_SUCCESS;
goto Cleanup;
}
do
{
// Skip possible subdirectories.
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
continue;
// Extract the Job ID and verify the file name format at the same time.
// This includes all valid names (like "00005.SHD") and excludes invalid ones (like "10ABC.SHD").
dwJobID = wcstoul(FindData.cFileName, &p, 10);
if (!IS_VALID_JOB_ID(dwJobID))
continue;
if (wcsicmp(p, L".SHD") != 0)
continue;
// This shadow file has a valid name. Construct the full path and try to load it.
GetJobFilePath(L"SHD", dwJobID, wszFullPath);
pJob = ReadJobShadowFile(wszFullPath);
if (!pJob)
continue;
// Add it to the Global Job List.
if (!InsertElementSkiplist(&GlobalJobList, pJob))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
goto Cleanup;
}
// Add it to the Printer's Job List.
if (!InsertElementSkiplist(&pJob->pPrinter->JobList, pJob))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("InsertElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
goto Cleanup;
}
}
while (FindNextFileW(hFind, &FindData));
dwErrorCode = ERROR_SUCCESS;
Cleanup:
// Outside the loop
if (hFind)
FindClose(hFind);
SetLastError(dwErrorCode);
return (dwErrorCode == ERROR_SUCCESS);
}
void
InitializePrinterJobList(PLOCAL_PRINTER pPrinter)
{
TRACE("InitializePrinterJobList(%p)\n", pPrinter);
// Initialize an empty list for this printer's jobs.
// This one is only for sorting the jobs. If you need to lookup a job, search the GlobalJobList by Job ID.
InitializeSkiplist(&pPrinter->JobList, DllAllocSplMem, _PrinterJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
}
DWORD WINAPI
CreateJob(PLOCAL_PRINTER_HANDLE pPrinterHandle)
{
const WCHAR wszDoubleBackslash[] = L"\\";
const DWORD cchDoubleBackslash = _countof(wszDoubleBackslash) - 1;
DWORD cchMachineName;
DWORD cchUserName;
DWORD dwErrorCode;
PLOCAL_JOB pJob;
RPC_BINDING_HANDLE hServerBinding = NULL;
RPC_WSTR pwszBinding = NULL;
RPC_WSTR pwszMachineName = NULL;
TRACE("CreateJob(%p)\n", pPrinterHandle);
// Create a new job.
pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
if (!pJob)
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("DllAllocSplMem failed!\n");
goto Cleanup;
}
// Reserve an ID for this job.
if (!_GetNextJobID(&pJob->dwJobID))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
// Copy over defaults to the LOCAL_JOB structure.
pJob->pPrinter = pPrinterHandle->pPrinter;
pJob->pPrintProcessor = pPrinterHandle->pPrinter->pPrintProcessor;
pJob->dwPriority = DEF_PRIORITY;
pJob->dwStatus = JOB_STATUS_SPOOLING;
pJob->pwszDatatype = AllocSplStr(pPrinterHandle->pwszDatatype);
pJob->pwszDocumentName = AllocSplStr(wszDefaultDocumentName);
pJob->pDevMode = DuplicateDevMode(pPrinterHandle->pDevMode);
GetSystemTime(&pJob->stSubmitted);
// Get the user name for the Job.
cchUserName = UNLEN + 1;
pJob->pwszUserName = DllAllocSplMem(cchUserName * sizeof(WCHAR));
if (!GetUserNameW(pJob->pwszUserName, &cchUserName))
{
dwErrorCode = GetLastError();
ERR("GetUserNameW failed with error %lu!\n", dwErrorCode);
goto Cleanup;
}
// FIXME: For now, pwszNotifyName equals pwszUserName.
pJob->pwszNotifyName = AllocSplStr(pJob->pwszUserName);
// Get the name of the machine that submitted the Job over RPC.
dwErrorCode = RpcBindingServerFromClient(NULL, &hServerBinding);
if (dwErrorCode != RPC_S_OK)
{
ERR("RpcBindingServerFromClient failed with status %lu!\n", dwErrorCode);
goto Cleanup;
}
dwErrorCode = RpcBindingToStringBindingW(hServerBinding, &pwszBinding);
if (dwErrorCode != RPC_S_OK)
{
ERR("RpcBindingToStringBindingW failed with status %lu!\n", dwErrorCode);
goto Cleanup;
}
dwErrorCode = RpcStringBindingParseW(pwszBinding, NULL, NULL, &pwszMachineName, NULL, NULL);
if (dwErrorCode != RPC_S_OK)
{
ERR("RpcStringBindingParseW failed with status %lu!\n", dwErrorCode);
goto Cleanup;
}
cchMachineName = wcslen(pwszMachineName);
pJob->pwszMachineName = DllAllocSplMem((cchMachineName + cchDoubleBackslash + 1) * sizeof(WCHAR));
CopyMemory(pJob->pwszMachineName, wszDoubleBackslash, cchDoubleBackslash * sizeof(WCHAR));
CopyMemory(&pJob->pwszMachineName[cchDoubleBackslash], pwszMachineName, (cchMachineName + 1) * sizeof(WCHAR));
// Add the job to the Global Job List.
if (!InsertElementSkiplist(&GlobalJobList, pJob))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
goto Cleanup;
}
// Add the job at the end of the Printer's Job List.
// As all new jobs are created with default priority, we can be sure that it would always be inserted at the end.
if (!InsertTailElementSkiplist(&pJob->pPrinter->JobList, pJob))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("InsertTailElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
goto Cleanup;
}
// We were successful!
pPrinterHandle->bStartedDoc = TRUE;
pPrinterHandle->pJob = pJob;
dwErrorCode = ERROR_SUCCESS;
// Don't let the cleanup routine free this.
pJob = NULL;
Cleanup:
if (pJob)
DllFreeSplMem(pJob);
if (pwszMachineName)
RpcStringFreeW(&pwszMachineName);
if (pwszBinding)
RpcStringFreeW(&pwszBinding);
if (hServerBinding)
RpcBindingFree(&hServerBinding);
return dwErrorCode;
}
BOOL WINAPI
LocalAddJob(HANDLE hPrinter, DWORD Level, PBYTE pData, DWORD cbBuf, PDWORD pcbNeeded)
{
ADDJOB_INFO_1W AddJobInfo1;
DWORD dwErrorCode;
PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
PLOCAL_PRINTER_HANDLE pPrinterHandle;
TRACE("LocalAddJob(%p, %lu, %p, %lu, %p)\n", hPrinter, Level, pData, cbBuf, pcbNeeded);
// Check if this is a printer handle.
if (pHandle->HandleType != HandleType_Printer)
{
dwErrorCode = ERROR_INVALID_HANDLE;
goto Cleanup;
}
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
// This handle must not have started a job yet!
if (pPrinterHandle->pJob)
{
dwErrorCode = ERROR_INVALID_HANDLE;
goto Cleanup;
}
// Check if this is the right structure level.
if (Level != 1)
{
dwErrorCode = ERROR_INVALID_LEVEL;
goto Cleanup;
}
// Check if the printer is set to do direct printing.
// The Job List isn't used in this case.
if (pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT)
{
dwErrorCode = ERROR_INVALID_ACCESS;
goto Cleanup;
}
// Check if the supplied buffer is large enough.
*pcbNeeded = sizeof(ADDJOB_INFO_1W) + GetJobFilePath(L"SPL", 0, NULL);
if (cbBuf < *pcbNeeded)
{
dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
goto Cleanup;
}
// All requirements are met - create a new job.
dwErrorCode = CreateJob(pPrinterHandle);
if (dwErrorCode != ERROR_SUCCESS)
goto Cleanup;
// Mark that this job was started with AddJob (so that it can be scheduled for printing with ScheduleJob).
pPrinterHandle->pJob->bAddedJob = TRUE;
// Return a proper ADDJOB_INFO_1W structure.
AddJobInfo1.JobId = pPrinterHandle->pJob->dwJobID;
AddJobInfo1.Path = (PWSTR)(pData + sizeof(ADDJOB_INFO_1W));
CopyMemory(pData, &AddJobInfo1, sizeof(ADDJOB_INFO_1W));
GetJobFilePath(L"SPL", AddJobInfo1.JobId, AddJobInfo1.Path);
Cleanup:
SetLastError(dwErrorCode);
return (dwErrorCode == ERROR_SUCCESS);
}
static void
_LocalGetJobLevel1(PLOCAL_JOB pJob, PJOB_INFO_1W* ppJobInfo, PBYTE* ppJobInfoEnd, PDWORD pcbNeeded)
{
DWORD cbDatatype;
DWORD cbDocumentName = 0;
DWORD cbMachineName;
DWORD cbPrinterName;
DWORD cbStatus = 0;
DWORD cbUserName = 0;
PWSTR pwszStrings[6];
// Calculate the string lengths.
if (!ppJobInfo)
{
cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
// These values are optional.
if (pJob->pwszDocumentName)
cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
if (pJob->pwszStatus)
cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
if (pJob->pwszUserName)
cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
*pcbNeeded += sizeof(JOB_INFO_1W) + cbDatatype + cbDocumentName + cbMachineName + cbPrinterName + cbStatus + cbUserName;
return;
}
// Set the general fields.
(*ppJobInfo)->JobId = pJob->dwJobID;
(*ppJobInfo)->Status = pJob->dwStatus;
(*ppJobInfo)->Priority = pJob->dwPriority;
(*ppJobInfo)->TotalPages = pJob->dwTotalPages;
(*ppJobInfo)->PagesPrinted = pJob->dwPagesPrinted;
CopyMemory(&(*ppJobInfo)->Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
// Position in JOB_INFO_1W is the 1-based index of the job in the processing queue.
// Retrieve this through the element index of the job in the Printer's Job List.
if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &(*ppJobInfo)->Position))
{
ERR("pJob could not be located in the Printer's Job List!\n");
return;
}
// Make the index 1-based.
++(*ppJobInfo)->Position;
// Set the pPrinterName field.
pwszStrings[0] = pJob->pPrinter->pwszPrinterName;
// Set the pMachineName field.
pwszStrings[1] = pJob->pwszMachineName;
// Set the pUserName field.
pwszStrings[2] = pJob->pwszUserName;
// Set the pDocument field.
pwszStrings[3] = pJob->pwszDocumentName;
// Set the pDatatype field.
pwszStrings[4] = pJob->pwszDatatype;
// Set the pStatus field.
pwszStrings[5] = pJob->pwszStatus;
// Finally copy the structure and advance to the next one in the output buffer.
*ppJobInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppJobInfo), dwJobInfo1Offsets, *ppJobInfoEnd);
(*ppJobInfo)++;
}
static void
_LocalGetJobLevel2(PLOCAL_JOB pJob, PJOB_INFO_2W* ppJobInfo, PBYTE* ppJobInfoEnd, PDWORD pcbNeeded)
{
DWORD cbDatatype;
DWORD cbDevMode;
DWORD cbDocumentName = 0;
DWORD cbDriverName;
DWORD cbMachineName;
DWORD cbNotifyName = 0;
DWORD cbPrinterName;
DWORD cbPrintProcessor;
DWORD cbPrintProcessorParameters = 0;
DWORD cbStatus = 0;
DWORD cbUserName = 0;
FILETIME ftNow;
FILETIME ftSubmitted;
PWSTR pwszStrings[10];
ULARGE_INTEGER uliNow;
ULARGE_INTEGER uliSubmitted;
// Calculate the string lengths.
cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
if (!ppJobInfo)
{
cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
cbDriverName = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
// These values are optional.
if (pJob->pwszDocumentName)
cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
if (pJob->pwszNotifyName)
cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
if (pJob->pwszPrintProcessorParameters)
cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
if (pJob->pwszStatus)
cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
if (pJob->pwszUserName)
cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
*pcbNeeded += sizeof(JOB_INFO_2W) + cbDatatype + cbDevMode + cbDocumentName + cbDriverName + cbMachineName + cbNotifyName + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbStatus + cbUserName;
return;
}
// Set the general fields.
(*ppJobInfo)->JobId = pJob->dwJobID;
(*ppJobInfo)->Status = pJob->dwStatus;
(*ppJobInfo)->Priority = pJob->dwPriority;
(*ppJobInfo)->StartTime = pJob->dwStartTime;
(*ppJobInfo)->UntilTime = pJob->dwUntilTime;
(*ppJobInfo)->TotalPages = pJob->dwTotalPages;
(*ppJobInfo)->PagesPrinted = pJob->dwPagesPrinted;
CopyMemory(&(*ppJobInfo)->Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
// Time in JOB_INFO_2W is the number of milliseconds elapsed since the job was submitted. Calculate this time.
if (!SystemTimeToFileTime(&pJob->stSubmitted, &ftSubmitted))
{
ERR("SystemTimeToFileTime failed with error %lu!\n", GetLastError());
return;
}
GetSystemTimeAsFileTime(&ftNow);
uliSubmitted.LowPart = ftSubmitted.dwLowDateTime;
uliSubmitted.HighPart = ftSubmitted.dwHighDateTime;
uliNow.LowPart = ftNow.dwLowDateTime;
uliNow.HighPart = ftNow.dwHighDateTime;
(*ppJobInfo)->Time = (DWORD)((uliNow.QuadPart - uliSubmitted.QuadPart) / 10000);
// Position in JOB_INFO_2W is the 1-based index of the job in the processing queue.
// Retrieve this through the element index of the job in the Printer's Job List.
if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &(*ppJobInfo)->Position))
{
ERR("pJob could not be located in the Printer's Job List!\n");
return;
}
// Make the index 1-based.
++(*ppJobInfo)->Position;
// FIXME!
FIXME("Setting pSecurityDescriptor and Size to 0 for now!\n");
(*ppJobInfo)->pSecurityDescriptor = NULL;
(*ppJobInfo)->Size = 0;
// Set the pDevMode field (and copy the DevMode).
*ppJobInfoEnd -= cbDevMode;
CopyMemory(*ppJobInfoEnd, pJob->pDevMode, cbDevMode);
(*ppJobInfo)->pDevMode = (PDEVMODEW)(*ppJobInfoEnd);
// Set the pPrinterName field.
pwszStrings[0] = pJob->pPrinter->pwszPrinterName;
// Set the pMachineName field.
pwszStrings[1] = pJob->pwszMachineName;
// Set the pUserName field.
pwszStrings[2] = pJob->pwszUserName;
// Set the pDocument field.
pwszStrings[3] = pJob->pwszDocumentName;
// Set the pNotifyName field.
pwszStrings[4] = pJob->pwszNotifyName;
// Set the pDatatype field.
pwszStrings[5] = pJob->pwszDatatype;
// Set the pPrintProcessor field.
pwszStrings[6] = pJob->pPrintProcessor->pwszName;
// Set the pParameters field.
pwszStrings[7] = pJob->pwszPrintProcessorParameters;
// Set the pDriverName field.
pwszStrings[8] = pJob->pPrinter->pwszPrinterDriver;
// Set the pStatus field.
pwszStrings[9] = pJob->pwszStatus;
// Finally copy the structure and advance to the next one in the output buffer.
*ppJobInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppJobInfo), dwJobInfo2Offsets, *ppJobInfoEnd);
(*ppJobInfo)++;
}
BOOL WINAPI
LocalGetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded)
{
DWORD dwErrorCode;
PBYTE pEnd = &pStart[cbBuf];
PLOCAL_HANDLE pHandle;
PLOCAL_JOB pJob;
PLOCAL_PRINTER_HANDLE pPrinterHandle;
TRACE("LocalGetJob(%p, %lu, %lu, %p, %lu, %p)\n", hPrinter, JobId, Level, pStart, cbBuf, pcbNeeded);
// Check if this is a printer handle.
pHandle = (PLOCAL_HANDLE)hPrinter;
if (pHandle->HandleType != HandleType_Printer)
{
dwErrorCode = ERROR_INVALID_HANDLE;
goto Cleanup;
}
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
// Get the desired job.
pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
{
dwErrorCode = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
if (Level > 2)
{
// The caller supplied an invalid level for GetJob.
dwErrorCode = ERROR_INVALID_LEVEL;
goto Cleanup;
}
// Count the required buffer size.
*pcbNeeded = 0;
if (Level == 1)
_LocalGetJobLevel1(pJob, NULL, NULL, pcbNeeded);
else if (Level == 2)
_LocalGetJobLevel2(pJob, NULL, NULL, pcbNeeded);
// Check if the supplied buffer is large enough.
if (cbBuf < *pcbNeeded)
{
dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
goto Cleanup;
}
// Copy over the Job information.
pEnd = &pStart[*pcbNeeded];
if (Level == 1)
_LocalGetJobLevel1(pJob, (PJOB_INFO_1W*)&pStart, &pEnd, NULL);
else if (Level == 2)
_LocalGetJobLevel2(pJob, (PJOB_INFO_2W*)&pStart, &pEnd, NULL);
dwErrorCode = ERROR_SUCCESS;
Cleanup:
SetLastError(dwErrorCode);
return (dwErrorCode == ERROR_SUCCESS);
}
static DWORD
_LocalSetJobLevel1(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_1W pJobInfo)
{
DWORD dwErrorCode;
// First check the validity of the input before changing anything.
if (!FindDatatype(pJob->pPrintProcessor, pJobInfo->pDatatype))
{
dwErrorCode = ERROR_INVALID_DATATYPE;
goto Cleanup;
}
// Check if the datatype has changed.
if (!_EqualStrings(pJob->pwszDatatype, pJobInfo->pDatatype))
{
// Use the new value.
if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the document name has changed. An empty string is permitted here!
if (!_EqualStrings(pJob->pwszDocumentName, pJobInfo->pDocument))
{
// Use the new value.
if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the status message has changed. An empty string is permitted here!
if (!_EqualStrings(pJob->pwszStatus, pJobInfo->pStatus))
{
// Use the new value.
if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the user name has changed. An empty string is permitted here!
if (!_EqualStrings(pJob->pwszUserName, pJobInfo->pUserName) != 0)
{
// The new user name doesn't need to exist, so no additional verification is required.
// Use the new value.
if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the priority has changed.
if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
{
// Set the new priority.
pJob->dwPriority = pJobInfo->Priority;
// Remove and reinsert the job in the Printer's Job List.
// The Compare function will be used to find the right position now considering the new priority.
DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
}
// Check if the status flags have changed.
if (pJob->dwStatus != pJobInfo->Status)
{
// Only add status flags that make sense.
if (pJobInfo->Status & JOB_STATUS_PAUSED)
pJob->dwStatus |= JOB_STATUS_PAUSED;
if (pJobInfo->Status & JOB_STATUS_ERROR)
pJob->dwStatus |= JOB_STATUS_ERROR;
if (pJobInfo->Status & JOB_STATUS_OFFLINE)
pJob->dwStatus |= JOB_STATUS_OFFLINE;
if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
pJob->dwStatus |= JOB_STATUS_PAPEROUT;
}
dwErrorCode = ERROR_SUCCESS;
Cleanup:
return dwErrorCode;
}
static DWORD
_LocalSetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_2W pJobInfo)
{
DWORD dwErrorCode;
PLOCAL_PRINT_PROCESSOR pPrintProcessor;
// First check the validity of the input before changing anything.
pPrintProcessor = FindPrintProcessor(pJobInfo->pPrintProcessor);
if (!pPrintProcessor)
{
dwErrorCode = ERROR_UNKNOWN_PRINTPROCESSOR;
goto Cleanup;
}
if (!FindDatatype(pPrintProcessor, pJobInfo->pDatatype))
{
dwErrorCode = ERROR_INVALID_DATATYPE;
goto Cleanup;
}
// Check if the datatype has changed.
if (!_EqualStrings(pJob->pwszDatatype, pJobInfo->pDatatype))
{
// Use the new value.
if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the document name has changed. An empty string is permitted here!
if (!_EqualStrings(pJob->pwszDocumentName, pJobInfo->pDocument))
{
// Use the new value.
if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the notify name has changed. An empty string is permitted here!
if (!_EqualStrings(pJob->pwszNotifyName, pJobInfo->pNotifyName))
{
// The new notify name doesn't need to exist, so no additional verification is required.
// Use the new value.
if (!ReallocSplStr(&pJob->pwszNotifyName, pJobInfo->pNotifyName))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the Print Processor Parameters have changed. An empty string is permitted here!
if (!_EqualStrings(pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
{
// Use the new value.
if (!ReallocSplStr(&pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the Status Message has changed. An empty string is permitted here!
if (!_EqualStrings(pJob->pwszStatus, pJobInfo->pStatus))
{
// Use the new value.
if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the user name has changed. An empty string is permitted here!
if (!_EqualStrings(pJob->pwszUserName, pJobInfo->pUserName))
{
// The new user name doesn't need to exist, so no additional verification is required.
// Use the new value.
if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
{
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
goto Cleanup;
}
}
// Check if the priority has changed.
if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
{
// Set the new priority.
pJob->dwPriority = pJobInfo->Priority;
// Remove and reinsert the job in the Printer's Job List.
// The Compare function will be used to find the right position now considering the new priority.
DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
}
// Check if the status flags have changed.
if (pJob->dwStatus != pJobInfo->Status)
{
// Only add status flags that make sense.
if (pJobInfo->Status & JOB_STATUS_PAUSED)
pJob->dwStatus |= JOB_STATUS_PAUSED;
if (pJobInfo->Status & JOB_STATUS_ERROR)
pJob->dwStatus |= JOB_STATUS_ERROR;
if (pJobInfo->Status & JOB_STATUS_OFFLINE)
pJob->dwStatus |= JOB_STATUS_OFFLINE;
if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
pJob->dwStatus |= JOB_STATUS_PAPEROUT;
}
dwErrorCode = ERROR_SUCCESS;
Cleanup:
return dwErrorCode;
}
BOOL WINAPI
LocalSetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pJobInfo, DWORD Command)
{
DWORD dwErrorCode = ERROR_SUCCESS;
PLOCAL_HANDLE pHandle;
PLOCAL_JOB pJob;
PLOCAL_PRINTER_HANDLE pPrinterHandle;
WCHAR wszFullPath[MAX_PATH];
TRACE("LocalSetJob(%p, %lu, %lu, %p, %lu)\n", hPrinter, JobId, Level, pJobInfo, Command);
// Check if this is a printer handle.
pHandle = (PLOCAL_HANDLE)hPrinter;
if (!pHandle || pHandle->HandleType != HandleType_Printer)
{
dwErrorCode = ERROR_INVALID_HANDLE;
goto Cleanup;
}
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
// Get the desired job.
pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
{
dwErrorCode = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
// Set new job information if a valid level was given.
if (Level == 1)
dwErrorCode = _LocalSetJobLevel1(pPrinterHandle, pJob, (PJOB_INFO_1W)pJobInfo);
else if (Level == 2)
dwErrorCode = _LocalSetJobLevel2(pPrinterHandle, pJob, (PJOB_INFO_2W)pJobInfo);
if (dwErrorCode != ERROR_SUCCESS)
goto Cleanup;
// If we do spooled printing, the job information is written down into a shadow file.
if (!(pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT))
{
// Write the job data into the shadow file.
GetJobFilePath(L"SHD", JobId, wszFullPath);
WriteJobShadowFile(wszFullPath, pJob);
}
// Perform an additional command if desired.
if (Command)
{
if (Command == JOB_CONTROL_SENT_TO_PRINTER)
{
// This indicates the end of the Print Job.
// Cancel the Job at the Print Processor.
if (pJob->hPrintProcessor)
pJob->pPrintProcessor->pfnControlPrintProcessor(pJob->hPrintProcessor, JOB_CONTROL_CANCEL);
FreeJob(pJob);
// TODO: All open handles associated with the job need to be invalidated.
// This certainly needs handle tracking...
}
else
{
ERR("Unimplemented SetJob Command: %lu!\n", Command);
}
}
Cleanup:
SetLastError(dwErrorCode);
return (dwErrorCode == ERROR_SUCCESS);
}
BOOL WINAPI
LocalEnumJobs(HANDLE hPrinter, DWORD FirstJob, DWORD NoJobs, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
{
DWORD dwErrorCode;
DWORD i;
PBYTE pEnd;
PLOCAL_HANDLE pHandle;
PLOCAL_JOB pJob;
PSKIPLIST_NODE pFirstJobNode;
PSKIPLIST_NODE pNode;
PLOCAL_PRINTER_HANDLE pPrinterHandle;
TRACE("LocalEnumJobs(%p, %lu, %lu, %lu, %p, %lu, %p, %p)\n", hPrinter, FirstJob, NoJobs, Level, pStart, cbBuf, pcbNeeded, pcReturned);
// Check if this is a printer handle.
pHandle = (PLOCAL_HANDLE)hPrinter;
if (pHandle->HandleType != HandleType_Printer)
{
dwErrorCode = ERROR_INVALID_HANDLE;
goto Cleanup;
}
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
// Check the level.
if (Level > 2)
{
dwErrorCode = ERROR_INVALID_LEVEL;
goto Cleanup;
}
// Begin counting.
*pcbNeeded = 0;
*pcReturned = 0;
// Lookup the node of the first job requested by the caller in the Printer's Job List.
pFirstJobNode = LookupNodeByIndexSkiplist(&pPrinterHandle->pPrinter->JobList, FirstJob);
// Count the required buffer size and the number of jobs.
i = 0;
pNode = pFirstJobNode;
while (i < NoJobs && pNode)
{
pJob = (PLOCAL_JOB)pNode->Element;
if (Level == 1)
_LocalGetJobLevel1(pJob, NULL, NULL, pcbNeeded);
else if (Level == 2)
_LocalGetJobLevel2(pJob, NULL, NULL, pcbNeeded);
// We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
i++;
pNode = pNode->Next[0];
}
// Check if the supplied buffer is large enough.
if (cbBuf < *pcbNeeded)
{
dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
goto Cleanup;
}
// Copy over the Job information.
i = 0;
pNode = pFirstJobNode;
pEnd = &pStart[*pcbNeeded];
while (i < NoJobs && pNode)
{
pJob = (PLOCAL_JOB)pNode->Element;
if (Level == 1)
_LocalGetJobLevel1(pJob, (PJOB_INFO_1W*)&pStart, &pEnd, NULL);
else if (Level == 2)
_LocalGetJobLevel2(pJob, (PJOB_INFO_2W*)&pStart, &pEnd, NULL);
// We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
i++;
pNode = pNode->Next[0];
}
*pcReturned = i;
dwErrorCode = ERROR_SUCCESS;
Cleanup:
SetLastError(dwErrorCode);
return (dwErrorCode == ERROR_SUCCESS);
}
BOOL WINAPI
LocalScheduleJob(HANDLE hPrinter, DWORD dwJobID)
{
DWORD dwAttributes;
DWORD dwErrorCode;
HANDLE hThread;
PLOCAL_JOB pJob;
PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
PLOCAL_PRINTER_HANDLE pPrinterHandle;
WCHAR wszFullPath[MAX_PATH];
TRACE("LocalScheduleJob(%p, %lu)\n", hPrinter, dwJobID);
// Check if this is a printer handle.
if (pHandle->HandleType != HandleType_Printer)
{
dwErrorCode = ERROR_INVALID_HANDLE;
goto Cleanup;
}
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
// Check if the Job ID is valid.
pJob = LookupElementSkiplist(&GlobalJobList, &dwJobID, NULL);
if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
{
dwErrorCode = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
// Check if this Job was started with AddJob.
if (!pJob->bAddedJob)
{
dwErrorCode = ERROR_SPL_NO_ADDJOB;
goto Cleanup;
}
// Construct the full path to the spool file.
GetJobFilePath(L"SPL", dwJobID, wszFullPath);
// Check if it exists.
dwAttributes = GetFileAttributesW(wszFullPath);
if (dwAttributes == INVALID_FILE_ATTRIBUTES || dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
dwErrorCode = ERROR_SPOOL_FILE_NOT_FOUND;
goto Cleanup;
}
// Spooling is finished at this point.
pJob->dwStatus &= ~JOB_STATUS_SPOOLING;
// Write the job data into the shadow file.
wcscpy(wcsrchr(wszFullPath, L'.'), L".SHD");
WriteJobShadowFile(wszFullPath, pJob);
// Create the thread for performing the printing process.
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PrintingThreadProc, pJob, 0, NULL);
if (!hThread)
{
dwErrorCode = GetLastError();
ERR("CreateThread failed with error %lu!\n", dwErrorCode);
goto Cleanup;
}
// We don't need the thread handle. Keeping it open blocks the thread from terminating.
CloseHandle(hThread);
// ScheduleJob has done its job. The rest happens inside the thread.
dwErrorCode = ERROR_SUCCESS;
Cleanup:
SetLastError(dwErrorCode);
return (dwErrorCode == ERROR_SUCCESS);
}
PLOCAL_JOB
ReadJobShadowFile(PCWSTR pwszFilePath)
{
DWORD cbFileSize;
DWORD cbRead;
HANDLE hFile = INVALID_HANDLE_VALUE;
PLOCAL_JOB pJob;
PLOCAL_JOB pReturnValue = NULL;
PLOCAL_PRINTER pPrinter;
PLOCAL_PRINT_PROCESSOR pPrintProcessor;
PSHD_HEADER pShadowFile = NULL;
PWSTR pwszPrinterName;
PWSTR pwszPrintProcessor;
TRACE("ReadJobShadowFile(%S)\n", pwszFilePath);
// Try to open the file.
hFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
goto Cleanup;
}
// Get its file size (small enough for a single DWORD) and allocate memory for all of it.
cbFileSize = GetFileSize(hFile, NULL);
pShadowFile = DllAllocSplMem(cbFileSize);
if (!pShadowFile)
{
ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
goto Cleanup;
}
// Read the entire file.
if (!ReadFile(hFile, pShadowFile, cbFileSize, &cbRead, NULL))
{
ERR("ReadFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
goto Cleanup;
}
// Check signature and header size.
if (pShadowFile->dwSignature != SHD_WIN2003_SIGNATURE || pShadowFile->cbHeader != sizeof(SHD_HEADER))
{
ERR("Signature or Header Size mismatch for file \"%S\"!\n", pwszFilePath);
goto Cleanup;
}
// Retrieve the associated printer from the list.
pwszPrinterName = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrinterName);
pPrinter = LookupElementSkiplist(&PrinterList, &pwszPrinterName, NULL);
if (!pPrinter)
{
ERR("Shadow file \"%S\" references a non-existing printer \"%S\"!\n", pwszFilePath, pwszPrinterName);
goto Cleanup;
}
// Retrieve the associated Print Processor from the list.
pwszPrintProcessor = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessor);
pPrintProcessor = FindPrintProcessor(pwszPrintProcessor);
if (!pPrintProcessor)
{
ERR("Shadow file \"%S\" references a non-existing Print Processor \"%S\"!\n", pwszFilePath, pwszPrintProcessor);
goto Cleanup;
}
// Create a new job structure and copy over the relevant fields.
pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
if (!pJob)
{
ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
goto Cleanup;
}
pJob->dwJobID = pShadowFile->dwJobID;
pJob->dwPriority = pShadowFile->dwPriority;
pJob->dwStartTime = pShadowFile->dwStartTime;
pJob->dwTotalPages = pShadowFile->dwTotalPages;
pJob->dwUntilTime = pShadowFile->dwUntilTime;
pJob->pPrinter = pPrinter;
pJob->pPrintProcessor = pPrintProcessor;
pJob->pDevMode = DuplicateDevMode((PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode));
pJob->pwszDatatype = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
pJob->pwszMachineName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offMachineName));
CopyMemory(&pJob->stSubmitted, &pShadowFile->stSubmitted, sizeof(SYSTEMTIME));
// Copy the optional values.
if (pShadowFile->offDocumentName)
pJob->pwszDocumentName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));
if (pShadowFile->offNotifyName)
pJob->pwszNotifyName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offNotifyName));
if (pShadowFile->offPrintProcessorParameters)
pJob->pwszPrintProcessorParameters = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessorParameters));
if (pShadowFile->offUserName)
pJob->pwszUserName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offUserName));
// Jobs read from shadow files were always added using AddJob.
pJob->bAddedJob = TRUE;
pReturnValue = pJob;
Cleanup:
if (pShadowFile)
DllFreeSplMem(pShadowFile);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
return pReturnValue;
}
BOOL
WriteJobShadowFile(PWSTR pwszFilePath, const PLOCAL_JOB pJob)
{
BOOL bReturnValue = FALSE;
DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
DWORD cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
DWORD cbDocumentName = 0;
DWORD cbFileSize;
DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
DWORD cbNotifyName = 0;
DWORD cbPrinterDriver = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
DWORD cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
DWORD cbPrintProcessorParameters = 0;
DWORD cbUserName = 0;
DWORD cbWritten;
DWORD dwCurrentOffset;
HANDLE hSHDFile = INVALID_HANDLE_VALUE;
HANDLE hSPLFile = INVALID_HANDLE_VALUE;
PSHD_HEADER pShadowFile = NULL;
TRACE("WriteJobShadowFile(%S, %p)\n", pwszFilePath, pJob);
// Try to open the SHD file.
hSHDFile = CreateFileW(pwszFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
if (hSHDFile == INVALID_HANDLE_VALUE)
{
ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
goto Cleanup;
}
// Calculate the lengths of the optional values and the total size of the shadow file.
if (pJob->pwszDocumentName)
cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
if (pJob->pwszNotifyName)
cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
if (pJob->pwszPrintProcessorParameters)
cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
if (pJob->pwszUserName)
cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
cbFileSize = sizeof(SHD_HEADER) + cbDatatype + cbDocumentName + cbDevMode + cbMachineName + cbNotifyName + cbPrinterDriver + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbUserName;
// Allocate memory for it.
pShadowFile = DllAllocSplMem(cbFileSize);
if (!pShadowFile)
{
ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
goto Cleanup;
}
// Fill out the shadow file header information.
pShadowFile->dwSignature = SHD_WIN2003_SIGNATURE;
pShadowFile->cbHeader = sizeof(SHD_HEADER);
// Copy the values.
pShadowFile->dwJobID = pJob->dwJobID;
pShadowFile->dwPriority = pJob->dwPriority;
pShadowFile->dwStartTime = pJob->dwStartTime;
pShadowFile->dwTotalPages = pJob->dwTotalPages;
pShadowFile->dwUntilTime = pJob->dwUntilTime;
CopyMemory(&pShadowFile->stSubmitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
// Determine the file size of the .SPL file
wcscpy(wcsrchr(pwszFilePath, L'.'), L".SPL");
hSPLFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hSPLFile != INVALID_HANDLE_VALUE)
pShadowFile->dwSPLSize = GetFileSize(hSPLFile, NULL);
// Add the extra values that are stored as offsets in the shadow file.
// The first value begins right after the shadow file header.
dwCurrentOffset = sizeof(SHD_HEADER);
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
pShadowFile->offDatatype = dwCurrentOffset;
dwCurrentOffset += cbDatatype;
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pDevMode, cbDevMode);
pShadowFile->offDevMode = dwCurrentOffset;
dwCurrentOffset += cbDevMode;
// offDriverName is only written, but automatically determined through offPrinterName when reading.
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterDriver, cbPrinterDriver);
pShadowFile->offDriverName = dwCurrentOffset;
dwCurrentOffset += cbPrinterDriver;
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszMachineName, cbMachineName);
pShadowFile->offMachineName = dwCurrentOffset;
dwCurrentOffset += cbMachineName;
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterName, cbPrinterName);
pShadowFile->offPrinterName = dwCurrentOffset;
dwCurrentOffset += cbPrinterName;
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrintProcessor->pwszName, cbPrintProcessor);
pShadowFile->offPrintProcessor = dwCurrentOffset;
dwCurrentOffset += cbPrintProcessor;
// Copy the optional values.
if (cbDocumentName)
{
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDocumentName, cbDocumentName);
pShadowFile->offDocumentName = dwCurrentOffset;
dwCurrentOffset += cbDocumentName;
}
if (cbNotifyName)
{
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszNotifyName, cbNotifyName);
pShadowFile->offNotifyName = dwCurrentOffset;
dwCurrentOffset += cbNotifyName;
}
if (cbPrintProcessorParameters)
{
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszPrintProcessorParameters, cbPrintProcessorParameters);
pShadowFile->offPrintProcessorParameters = dwCurrentOffset;
dwCurrentOffset += cbPrintProcessorParameters;
}
if (cbUserName)
{
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszUserName, cbUserName);
pShadowFile->offUserName = dwCurrentOffset;
dwCurrentOffset += cbUserName;
}
// Write the file.
if (!WriteFile(hSHDFile, pShadowFile, cbFileSize, &cbWritten, NULL))
{
ERR("WriteFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
goto Cleanup;
}
bReturnValue = TRUE;
Cleanup:
if (pShadowFile)
DllFreeSplMem(pShadowFile);
if (hSHDFile != INVALID_HANDLE_VALUE)
CloseHandle(hSHDFile);
if (hSPLFile != INVALID_HANDLE_VALUE)
CloseHandle(hSPLFile);
return bReturnValue;
}
void
FreeJob(PLOCAL_JOB pJob)
{
PWSTR pwszSHDFile;
TRACE("FreeJob(%p)\n", pJob);
// Remove the Job from both Job Lists.
DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
DeleteElementSkiplist(&GlobalJobList, pJob);
// Try to delete the corresponding .SHD file.
pwszSHDFile = DllAllocSplMem(GetJobFilePath(L"SHD", 0, NULL));
if (pwszSHDFile && GetJobFilePath(L"SHD", pJob->dwJobID, pwszSHDFile))
DeleteFileW(pwszSHDFile);
// Free memory for the mandatory fields.
DllFreeSplMem(pJob->pDevMode);
DllFreeSplStr(pJob->pwszDatatype);
DllFreeSplStr(pJob->pwszDocumentName);
DllFreeSplStr(pJob->pwszMachineName);
DllFreeSplStr(pJob->pwszNotifyName);
DllFreeSplStr(pJob->pwszUserName);
// Free memory for the optional fields if they are present.
if (pJob->pwszOutputFile)
DllFreeSplStr(pJob->pwszOutputFile);
if (pJob->pwszPrintProcessorParameters)
DllFreeSplStr(pJob->pwszPrintProcessorParameters);
if (pJob->pwszStatus)
DllFreeSplStr(pJob->pwszStatus);
// Finally free the job structure itself.
DllFreeSplMem(pJob);
}