void FreeJob(PLOCAL_JOB pJob) { PWSTR pwszSHDFile; // 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); }
/** * @name _HandleSetDefaultCommConfig * * Sets the default configuration of a legacy port. Checks for granted SERVER_ACCESS_ADMINISTER access. * You have to supply the port name (with colon!) in XcvOpenPort. * The opposite function is _HandleGetDefaultCommConfig. * * @param pXcv * Pointer to the LOCALMON_XCV structure of the currently opened Xcv port. * * @param pInputData * Pointer to the COMMCONFIG structure that shall be passed to SetDefaultCommConfigW. * * @param pcbOutputNeeded * Pointer to a DWORD that will be zeroed on return. * * @return * An error code indicating success or failure. */ static DWORD _HandleSetDefaultCommConfig(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded) { DWORD dwErrorCode; HANDLE hToken = NULL; LPCOMMCONFIG pCommConfig; PWSTR pwszPortNameWithoutColon = NULL; // Sanity checks // pwszObject needs to be at least 2 characters long to be a port name with a trailing colon. if (!pXcv || !pXcv->pwszObject || !pXcv->pwszObject[0] || !pXcv->pwszObject[1] || !pInputData || !pcbOutputNeeded) { dwErrorCode = ERROR_INVALID_PARAMETER; goto Cleanup; } *pcbOutputNeeded = 0; // This action can only happen at SERVER_ACCESS_ADMINISTER access level. if (!(pXcv->GrantedAccess & SERVER_ACCESS_ADMINISTER)) { dwErrorCode = ERROR_ACCESS_DENIED; goto Cleanup; } // SetDefaultCommConfigW needs the port name without colon. dwErrorCode = GetPortNameWithoutColon(pXcv->pwszObject, &pwszPortNameWithoutColon); if (dwErrorCode != ERROR_SUCCESS) goto Cleanup; // Switch to the SYSTEM context for setting the port configuration. hToken = RevertToPrinterSelf(); if (!hToken) { dwErrorCode = GetLastError(); ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode); goto Cleanup; } // Finally pass the parameters to SetDefaultCommConfigW. pCommConfig = (LPCOMMCONFIG)pInputData; if (!SetDefaultCommConfigW(pwszPortNameWithoutColon, pCommConfig, pCommConfig->dwSize)) { dwErrorCode = GetLastError(); ERR("SetDefaultCommConfigW failed with error %lu!\n", dwErrorCode); goto Cleanup; } dwErrorCode = ERROR_SUCCESS; Cleanup: if (hToken) ImpersonatePrinterClient(hToken); if (pwszPortNameWithoutColon) DllFreeSplMem(pwszPortNameWithoutColon); return dwErrorCode; }
BOOL WINAPI LocalmonXcvClosePort(HANDLE hXcv) { PLOCALMON_XCV pXcv = (PLOCALMON_XCV)hXcv; TRACE("LocalmonXcvClosePort(%p)\n", hXcv); // Sanity checks if (!pXcv) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // Remove it from the list and free the memory. RemoveEntryList(&pXcv->Entry); DllFreeSplMem(pXcv); SetLastError(ERROR_SUCCESS); return TRUE; }
BOOL WINAPI ClosePrinter(HANDLE hPrinter) { BOOL bReturnValue; PSPOOLSS_PRINTER_HANDLE pHandle = (PSPOOLSS_PRINTER_HANDLE)hPrinter; // Sanity checks. if (!pHandle) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // FIXME: Call FindClosePrinterChangeNotification for all created change notifications (according to MSDN). // Call CloseHandle of the Print Provider. bReturnValue = pHandle->pPrintProvider->PrintProvider.fpClosePrinter(pHandle->hPrinter); // Free our handle information. DllFreeSplMem(pHandle); return bReturnValue; }
BOOL InitializePortList() { BOOL bReturnValue; DWORD cbNeeded; DWORD cbPortName; DWORD dwErrorCode; DWORD dwReturned; DWORD i; PLOCAL_PORT pPort; PLOCAL_PRINT_MONITOR pPrintMonitor; PLIST_ENTRY pEntry; PPORT_INFO_1W p; PPORT_INFO_1W pPortInfo1 = NULL; // Initialize an empty list for our Ports. InitializeListHead(&_PortList); // Loop through all Print Monitors. for (pEntry = PrintMonitorList.Flink; pEntry != &PrintMonitorList; pEntry = pEntry->Flink) { // Cleanup from the previous run. if (pPortInfo1) { DllFreeSplMem(pPortInfo1); pPortInfo1 = NULL; } pPrintMonitor = CONTAINING_RECORD(pEntry, LOCAL_PRINT_MONITOR, Entry); // Determine the required buffer size for EnumPorts. if (pPrintMonitor->bIsLevel2) bReturnValue = ((PMONITOR2)pPrintMonitor->pMonitor)->pfnEnumPorts(pPrintMonitor->hMonitor, NULL, 1, NULL, 0, &cbNeeded, &dwReturned); else bReturnValue = ((LPMONITOREX)pPrintMonitor->pMonitor)->Monitor.pfnEnumPorts(NULL, 1, NULL, 0, &cbNeeded, &dwReturned); // Check the returned error code. if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { ERR("Print Monitor \"%S\" failed with error %lu on EnumPorts!\n", pPrintMonitor->pwszName, GetLastError()); continue; } // Allocate a buffer large enough. pPortInfo1 = DllAllocSplMem(cbNeeded); if (!pPortInfo1) { dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); goto Cleanup; } // Get the ports handled by this monitor. if (pPrintMonitor->bIsLevel2) bReturnValue = ((PMONITOR2)pPrintMonitor->pMonitor)->pfnEnumPorts(pPrintMonitor->hMonitor, NULL, 1, (PBYTE)pPortInfo1, cbNeeded, &cbNeeded, &dwReturned); else bReturnValue = ((LPMONITOREX)pPrintMonitor->pMonitor)->Monitor.pfnEnumPorts(NULL, 1, (PBYTE)pPortInfo1, cbNeeded, &cbNeeded, &dwReturned); // Check the return value. if (!bReturnValue) { ERR("Print Monitor \"%S\" failed with error %lu on EnumPorts!\n", pPrintMonitor->pwszName, GetLastError()); continue; } // Loop through all returned ports. p = pPortInfo1; for (i = 0; i < dwReturned; i++) { cbPortName = (wcslen(p->pName) + 1) * sizeof(WCHAR); // Create a new LOCAL_PORT structure for it. pPort = DllAllocSplMem(sizeof(LOCAL_PORT) + cbPortName); if (!pPort) { dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); goto Cleanup; } pPort->pPrintMonitor = pPrintMonitor; pPort->pwszName = (PWSTR)((PBYTE)pPort + sizeof(LOCAL_PORT)); CopyMemory(pPort->pwszName, p->pName, cbPortName); // Insert it into the list and advance to the next port. InsertTailList(&_PortList, &pPort->Entry); p++; } } dwErrorCode = ERROR_SUCCESS; Cleanup: // Inside the loop if (pPortInfo1) DllFreeSplMem(pPortInfo1); SetLastError(dwErrorCode); return (dwErrorCode == ERROR_SUCCESS); }
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; // Create a new job. pJob = DllAllocSplMem(sizeof(LOCAL_JOB)); if (!pJob) { dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); 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 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; // 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 with error %lu for file \"%S\"!\n", GetLastError(), 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; }
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; // 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 with error %lu for file \"%S\"!\n", GetLastError(), 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 with error %lu for file \"%S\"!\n", GetLastError(), 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; }