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); }
BOOL WINAPI OpenPrinterW(PWSTR pPrinterName, PHANDLE phPrinter, PPRINTER_DEFAULTSW pDefault) { BOOL bReturnValue; HANDLE hPrinter; PLIST_ENTRY pEntry; PSPOOLSS_PRINTER_HANDLE pHandle; PSPOOLSS_PRINT_PROVIDER pPrintProvider; // Sanity checks. if (!pPrinterName || !phPrinter) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // Loop through all Print Providers to find one able to open this Printer. for (pEntry = PrintProviderList.Flink; pEntry != &PrintProviderList; pEntry = pEntry->Flink) { pPrintProvider = CONTAINING_RECORD(pEntry, SPOOLSS_PRINT_PROVIDER, Entry); bReturnValue = pPrintProvider->PrintProvider.fpOpenPrinter(pPrinterName, &hPrinter, pDefault); if (bReturnValue == ROUTER_SUCCESS) { // This Print Provider has opened this Printer. // Store this information and return a handle. pHandle = DllAllocSplMem(sizeof(SPOOLSS_PRINTER_HANDLE)); if (!pHandle) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); return FALSE; } pHandle->pPrintProvider = pPrintProvider; pHandle->hPrinter = hPrinter; *phPrinter = (HANDLE)pHandle; SetLastError(ERROR_SUCCESS); return TRUE; } else if (bReturnValue == ROUTER_STOP_ROUTING) { ERR("A Print Provider returned ROUTER_STOP_ROUTING for Printer \"%S\"!\n", pPrinterName); return FALSE; } } // We found no Print Provider able to open this Printer. return FALSE; }
BOOL WINAPI LocalmonXcvOpenPort(HANDLE hMonitor, PCWSTR pwszObject, ACCESS_MASK GrantedAccess, PHANDLE phXcv) { DWORD cbObject = 0; DWORD dwErrorCode; PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor; PLOCALMON_XCV pXcv; TRACE("LocalmonXcvOpenPort(%p, %S, %lu, %p)\n", hMonitor, pwszObject, GrantedAccess, phXcv); // Sanity checks if (!pLocalmon || !phXcv) { dwErrorCode = ERROR_INVALID_PARAMETER; goto Cleanup; } if (pwszObject) cbObject = (wcslen(pwszObject) + 1) * sizeof(WCHAR); // Create a new LOCALMON_XCV structure and fill the relevant fields. pXcv = DllAllocSplMem(sizeof(LOCALMON_XCV) + cbObject); if (!pXcv) { dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); goto Cleanup; } pXcv->pLocalmon = pLocalmon; pXcv->GrantedAccess = GrantedAccess; if (cbObject) { pXcv->pwszObject = (PWSTR)((PBYTE)pXcv + sizeof(LOCALMON_XCV)); CopyMemory(pXcv->pwszObject, pwszObject, cbObject); } InsertTailList(&pLocalmon->XcvHandles, &pXcv->Entry); // Return it as the Xcv handle. *phXcv = (HANDLE)pXcv; dwErrorCode = ERROR_SUCCESS; Cleanup: SetLastError(dwErrorCode); return (dwErrorCode == ERROR_SUCCESS); }
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; }