//+---------------------------------------------------------------------------- // // Job Object Handler // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1996. // // File: persist.cxx // // Contents: persistent storage methods // // Classes: CJob (continued) // // Interfaces: IPersist, IPersistFile // // History: 24-May-95 EricB created // 19-Jul-97 AnirudhS Rewrote SaveP and LoadP to minimize calls // to ReadFile, WriteFile and LocalAlloc // //----------------------------------------------------------------------------- #include "..\pch\headers.hxx" #pragma hdrstop #include // needed only for use in some schasserts #include "job.hxx" #include "defines.hxx" #include "SASecRPC.h" // Get/SetAccountInformation RPC definition. #include // defined in security.cxx HRESULT SetTaskFileSecurity(LPCWSTR pwszTaskPath, BOOL fIsATTask); #define JOB_SIGNATURE_VERSION 1 // data version we write #define JOB_SIGNATURE_CLIENT_VERSION 1 // software version we are #define JOB_SIGNATURE_MIN_CLIENT_VERSION 1 // min s/w version that can // read data we write struct JOB_SIGNATURE_HEADER { WORD wSignatureVersion; WORD wMinClientVersion; }; void GenerateUniqueID(GUID * pUuid); BOOL ReadString(CInputBuffer * pBuf, LPWSTR *ppwsz); // // This array of members is used to iterate through the string fields // of a CJob that are initially held in m_MainBlock. // LPWSTR CJob::* const CJob::s_StringField[] = { &CJob::m_pwszApplicationName, &CJob::m_pwszParameters, &CJob::m_pwszWorkingDirectory, &CJob::m_pwszCreator, &CJob::m_pwszComment }; // IPersist method //+---------------------------------------------------------------------------- // // Member: CJob::IPersist::GetClassID // // Synopsis: supplies VBScript class object CLSID // //----------------------------------------------------------------------------- STDMETHODIMP CJob::GetClassID(CLSID * pClsID) { TRACE(CJob, GetClassID); *pClsID = CLSID_CTask; return S_OK; } // IPersistFile methods //+---------------------------------------------------------------------------- // // Member: CJob::IPersistFile::IsDirty // // Synopsis: checks for changes since it was last saved // //----------------------------------------------------------------------------- STDMETHODIMP CJob::IsDirty(void) { TRACE3(CJob,IsDirty); return (IsFlagSet(JOB_I_FLAG_PROPERTIES_DIRTY) || IsFlagSet(JOB_I_FLAG_TRIGGERS_DIRTY) || IsFlagSet(JOB_I_FLAG_SET_ACCOUNT_INFO)) ? S_OK : S_FALSE; } //+---------------------------------------------------------------------------- // // Member: CJob::IPersistFile::Load // // Synopsis: loads the job object indicated by the given filename // // Arguments: [pwszFileName] - name of the job object file // [dwMode] - open mode, currently ignored. // // Notes: All OLE32 strings are UNICODE, including the filename passed // in the IPersistFile methods. //----------------------------------------------------------------------------- STDMETHODIMP CJob::Load(LPCOLESTR pwszFileName, DWORD dwMode) { return LoadP(pwszFileName, dwMode, TRUE, TRUE); } //+---------------------------------------------------------------------------- // // Member: CJob::LoadP, private // // Synopsis: private load method that takes a TCHAR filename // // Arguments: [ptszFileName] - name of the job object file // [dwMode] - open mode, currently ignored. // [fRemember] - save the file name? // [fAllData] - if TRUE, load the entire job. If false, load // only the fixed length data at the head of the // job file + the job command. // //----------------------------------------------------------------------------- HRESULT CJob::LoadP( LPCTSTR ptszFileName, DWORD dwMode, BOOL fRemember, BOOL fAllData ) { TRACE3(CJob, LoadP); HRESULT hr = S_OK; BYTE * HeapBlock = NULL; //WCHAR tszFileName[MAX_PATH + 1] = L""; // // check the file name // //if (!CheckFileName((LPOLESTR)ptszFileName, tszFileName, NULL)) //{ // return E_INVALIDARG; //} // // Save the file name? // if (fRemember) { LPTSTR ptsz = new TCHAR[lstrlen(ptszFileName) + 1]; if (!ptsz) { ERR_OUT("CJob::LoadP", E_OUTOFMEMORY); return E_OUTOFMEMORY; } if (m_ptszFileName) { delete m_ptszFileName; } m_ptszFileName = ptsz; StringCchCopy(m_ptszFileName, lstrlen(ptszFileName) + 1, ptszFileName); } // // Open the file. // HANDLE hFile = NULL; hr = OpenFileWithRetry(ptszFileName, GENERIC_READ, FILE_SHARE_READ, &hFile); if (FAILED(hr)) { ERR_OUT("CJob::LoadP, file open", hr); return hr; } if (FILE_TYPE_DISK != GetFileType(hFile)) { CloseHandle(hFile); ERR_OUT("CJob::Load, Not a disk file", E_ACCESSDENIED); return E_ACCESSDENIED; } if (fRemember) { m_fFileCreated = TRUE; } // // Get the file size. // DWORD dwFileSize; { DWORD dwFileSizeHigh; DWORD dwError; dwFileSize = GetFileSize(hFile, &dwFileSizeHigh); if (dwFileSize == 0xFFFFFFFF && (dwError = GetLastError()) != NO_ERROR) { hr = HRESULT_FROM_WIN32(dwError); ERR_OUT("CJob::Load, GetFileSize", hr); goto Cleanup; } if (dwFileSizeHigh > 0) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); schDebugOut((DEB_ERROR, "CJob::Load: file too big, SizeHigh = %u\n", dwFileSizeHigh)); goto Cleanup; } } if (dwFileSize < sizeof(FIXDLEN_DATA)) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); schDebugOut((DEB_ERROR, "CJob::Load: file too small, size = %u\n", dwFileSize)); goto Cleanup; } // // Read the entire file into memory. // HeapBlock = new BYTE[dwFileSize]; if (HeapBlock == NULL) { hr = E_OUTOFMEMORY; ERR_OUT("CJob::Load, buffer alloc", hr); goto Cleanup; } DWORD dwBytesRead; if (!ReadFile(hFile, HeapBlock, dwFileSize, &dwBytesRead, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("CJob::Load, read file", hr); delete [] HeapBlock; goto Cleanup; } if (dwBytesRead != dwFileSize) { hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); ERR_OUT("CJob::Load bytes read", hr); delete [] HeapBlock; goto Cleanup; } // // Close the file. // CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; // // Free old values of properties that we are about to read, and switch // to the new heap block. // FreeProperties(); m_MainBlock.Set(HeapBlock, dwFileSize); // // Get the fixed length Job data. // (We already verified that the file is at least as big as FIXDLEN_DATA.) // { // scope flData and CInputBuffer to avoid "initialization skipped" error const FIXDLEN_DATA& flData = *(FIXDLEN_DATA *)&HeapBlock[0]; CInputBuffer Buf(&HeapBlock[sizeof FIXDLEN_DATA], &HeapBlock[dwFileSize]); // // Check the version. // // Here is where, in future versions, we would look at the version // properties to determine how to read the job properties. // For now, though, we just reject old versions as being invalid. // #ifdef IN_THE_FUTURE if (m_wVersion != flData.wVersion) { fNewVersion = TRUE; // // Add version specific processing here. // } #else // !IN_THE_FUTURE if (m_wFileObjVer != flData.wFileObjVer) { hr = SCHED_E_UNKNOWN_OBJECT_VERSION; ERR_OUT("CJob::Load invalid object version", 0); goto Cleanup; } #endif // !IN_THE_FUTURE //schDebugOut((DEB_TRACE, "Load: job object version: %d.%d, build %d\n", // HIBYTE(flData.wVersion), LOBYTE(flData.wVersion), // flData.wFileObjVer)); m_wVersion = flData.wVersion; m_wFileObjVer = flData.wFileObjVer; m_uuidJob = flData.uuidJob; m_wTriggerOffset = flData.wTriggerOffset; m_wErrorRetryCount = flData.wErrorRetryCount; m_wErrorRetryInterval = flData.wErrorRetryInterval; m_wIdleWait = flData.wIdleWait; m_wIdleDeadline = flData.wIdleDeadline; m_dwPriority = flData.dwPriority; m_dwMaxRunTime = flData.dwMaxRunTime; m_ExitCode = flData.ExitCode; m_hrStatus = flData.hrStatus; m_rgFlags = flData.rgFlags; m_stMostRecentRunTime = flData.stMostRecentRunTime; hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); if (!Buf.Read(&m_cRunningInstances, sizeof m_cRunningInstances)) { ERR_OUT("CJob::Load, read Running Instance Count", 0); goto Cleanup; } // // Get the variable length Job properties. // // In all cases, retrieve the job application name. // (Notice that flData.wAppNameLenOffset is not honored. We have to // leave it this way now for backward compatibility.) // if (!ReadString(&Buf, &m_pwszApplicationName)) { goto Cleanup; } // // If a full load, retrieve the rest of the variable length data, // including the triggers. // if (fAllData) { // Strings if (!(ReadString(&Buf, &m_pwszParameters) && ReadString(&Buf, &m_pwszWorkingDirectory) && ReadString(&Buf, &m_pwszCreator) && ReadString(&Buf, &m_pwszComment) )) { goto Cleanup; } // User Data size if (!Buf.Read(&m_cbTaskData, sizeof m_cbTaskData)) { goto Cleanup; } // User Data if (m_cbTaskData == 0) { m_pbTaskData = NULL; } else { m_pbTaskData = Buf.CurrentPosition(); if (!Buf.Advance(m_cbTaskData)) { goto Cleanup; } } // Size of reserved data if (!Buf.Read(&m_cReserved, sizeof m_cReserved)) { goto Cleanup; } // Reserved data // // If there is reserved data, it must begin with a structure // that we recognize. In this version we recognize the // TASKRESERVED1 structure. // m_pbReserved = NULL; if (m_cReserved == 0) { // // There is no reserved data. Initialize the members that // we would have read from the reserved data to defaults. // m_hrStartError = SCHED_S_TASK_HAS_NOT_RUN; m_rgTaskFlags = 0; } else if (m_cReserved < sizeof(TASKRESERVED1)) { ERR_OUT("CJob::Load, invalid reserved data", hr); m_cReserved = 0; goto Cleanup; } else { m_pbReserved = Buf.CurrentPosition(); if (!Buf.Advance(m_cReserved)) { goto Cleanup; } // // Copy the portion of the Reserved Data that we understand // into private data members. // It may not be aligned properly, so use CopyMemory. // TASKRESERVED1 Reserved; CopyMemory(&Reserved, m_pbReserved, sizeof Reserved); m_hrStartError = Reserved.hrStartError; m_rgTaskFlags = Reserved.rgTaskFlags; } // // Load trigger data. // hr = this->_LoadTriggersFromBuffer(&Buf); if (FAILED(hr)) { ERR_OUT("Loading triggers from storage", hr); goto Cleanup; } // // If there is more data after the triggers, it must begin with a // signature header and a signature. If there is less data than // that, treat it as though the file has no signature. // If a signature is present, but its "minimum client version" is // greater than our version, treat it as though the file has no // signature. // JOB_SIGNATURE_HEADER SignHead; if (Buf.Read(&SignHead, sizeof SignHead) && SignHead.wMinClientVersion <= JOB_SIGNATURE_CLIENT_VERSION) { m_pbSignature = Buf.CurrentPosition(); if (!Buf.Advance(SIGNATURE_SIZE)) { schDebugOut((DEB_ERROR, "CJob::Load: si too small, ignoring\n")); m_pbSignature = NULL; } } // else m_pbSignature was set to NULL in FreeProperties } } // end CInputBuffer and flData scope hr = S_OK; Cleanup: // // Close the file. // if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); } return hr; } //+---------------------------------------------------------------------------- // // Member: CJob::SaveP, private // // Synopsis: saves the object to storage, takes a TCHAR file name // // Arguments: [ptszFileName] - if null, save to the previously loaded file. // [fRemember] - if TRUE, the object becomes associated with // the new filename. // [flOptions] - can have the following bits set: // SAVEP_VARIABLE_LENGTH_DATA: // if set, saves all job data except the running // instance count. If not set, saves // only the fixed length data at the beginning // of the job. The file must already exist (the // filename must be NULL) in this case. // // SAVEP_RUNNING_INSTANCE_COUNT: // if set, the running instance count is saved // // SAVEP_PRESERVE_NET_SCHEDULE: // if NOT set, the JOB_I_FLAG_NET_SCHEDULE flag // is automatically cleared. // // Returns: HRESULT codes. // //----------------------------------------------------------------------------- HRESULT CJob::SaveP(LPCTSTR ptszFileName, BOOL fRemember, ULONG flOptions) { TRACE3(CJob, SaveP); HRESULT hr = S_OK; HANDLE hFile = NULL; BOOL fSetSecurity = FALSE; // // Decide which name to save the file as. Use the one passed in if // there is one, otherwise use the previously remembered one. // LPCTSTR ptszFileToSaveAs = ptszFileName ? ptszFileName : m_ptszFileName; if (!(ptszFileToSaveAs && *ptszFileToSaveAs)) { // // Can't do a save if there is no file name. // return E_INVALIDARG; } // // Figure out whether we will create a new file or open an existing one. // If using the passed in filename, then this is a save-as (or if // fRemember is false, a save-copy-as) operation and a new file must // be created. // If using the previously remembered filename, then a new file must // be created iff the file wasn't saved before. // DWORD dwDisposition; DWORD dwAttributes; if (!(ptszFileToSaveAs == m_ptszFileName && m_fFileCreated)) { dwDisposition = CREATE_NEW; if (!(flOptions & SAVEP_VARIABLE_LENGTH_DATA)) { // // Creating a new file is only valid if all the data is to be // saved. Otherwise we would end up with a partial (invalid) file. // return E_INVALIDARG; } dwAttributes = FILE_ATTRIBUTE_NORMAL; if (IsFlagSet(TASK_FLAG_HIDDEN)) { dwAttributes = FILE_ATTRIBUTE_HIDDEN; } // // Always write running instance count on file create. // flOptions |= SAVEP_RUNNING_INSTANCE_COUNT; // // On file creation, generate a unique ID for this job. // Done for Win95 as well as NT. // GenerateUniqueID(&m_uuidJob); // // Set security on file creation. This is done after all writes // have succeeded and the file has been closed. // fSetSecurity = TRUE; } else { // // This is a save to an existing file. // dwDisposition = OPEN_EXISTING; dwAttributes = GetFileAttributes(m_ptszFileName); if (dwAttributes == -1) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("CJob::Save, GetFileAttributes", hr); return hr; } DWORD dwOrgAttributes = dwAttributes; // // Remove the read-only attribute. // dwAttributes &= ~FILE_ATTRIBUTE_READONLY; // // If the hidden flag is set and the hidden attribute has // not been set yet, set it now. // if (IsFlagSet(TASK_FLAG_HIDDEN)) { dwAttributes |= FILE_ATTRIBUTE_HIDDEN; } if (dwAttributes != dwOrgAttributes) { SetFileAttributes(m_ptszFileName, dwAttributes); } } // // Create/Open the file. // hFile = CreateFile(ptszFileToSaveAs, GENERIC_WRITE, 0, NULL, dwDisposition, dwAttributes | FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (hFile == INVALID_HANDLE_VALUE) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("CJob::Save, file open/create", hr); return hr; } if (FILE_TYPE_DISK != GetFileType(hFile)) { CloseHandle(hFile); ERR_OUT("CJob::Save, Not a disk file", E_ACCESSDENIED); return E_ACCESSDENIED; } if (fRemember || ptszFileToSaveAs == m_ptszFileName) { m_fFileCreated = TRUE; } // // Initialize the creator property if not already done so. // Set it to the caller's username. // if (m_pwszCreator == NULL) { // // Other than out of memory, if an error occurs, leave the // creator field alone. // TCHAR tszUserName[MAX_USERNAME + 1]; DWORD ccUserName = MAX_USERNAME + 1; if (GetUserName(tszUserName, &ccUserName)) { // NB : GetUserName returned char count includes the null // character. // m_pwszCreator = new WCHAR[ccUserName]; if (m_pwszCreator == NULL) { hr = E_OUTOFMEMORY; CHECK_HRESULT(hr); goto ErrExit; } StringCchCopy(m_pwszCreator, ccUserName, tszUserName); } } { // scope to avoid "initialization skipped" error BOOL fUpdateJobState = FALSE; // // The disabled flag takes precedence over other states except running. // if (IsFlagSet(TASK_FLAG_DISABLED)) { if (!IsStatus(SCHED_S_TASK_RUNNING)) { SetStatus(SCHED_S_TASK_DISABLED); } } else { if (IsStatus(SCHED_S_TASK_DISABLED)) { // // UpdateJobState will set the correct status if no longer // disabled. // fUpdateJobState = TRUE; } } // // Check to see if the triggers are dirty. If so, update the job status // and flags before writing it out. // if (IsFlagSet(JOB_I_FLAG_TRIGGERS_DIRTY)) { fUpdateJobState = TRUE; } // // The flag value JOB_I_FLAG_RUN_PROP_CHANGE is never written to disk. // Instead, the need for a wait list rebuild is signalled by clearing // JOB_I_FLAG_NO_RUN_PROP_CHANGE. If JOB_I_FLAG_RUN_PROP_CHANGE were to be // written to disk, then CheckDir would see this bit set and do a wait // list rebuild, but it would also need to clear the bit to prevent // successive wait list rebuilds. However, the write to clear the bit // would cause an additional change notification and CheckDir call. So, to // avoid this thrashing, we signal a wait list rebuild by the absence of // the JOB_I_FLAG_NO_RUN_PROP_CHANGE bit. // if (IsFlagSet(JOB_I_FLAG_RUN_PROP_CHANGE)) { ClearFlag(JOB_I_FLAG_RUN_PROP_CHANGE); ClearFlag(JOB_I_FLAG_NO_RUN_PROP_CHANGE); fUpdateJobState = TRUE; } if (fUpdateJobState) { UpdateJobState(FALSE); } // // Regenerate a unique id for the job (a GUID) when the application // changes. This is done for security reasons. // // NB : We need to do this for Win95 as well as NT since we may be // editing an NT job from a Win95 machine. // if (IsFlagSet(JOB_I_FLAG_APPNAME_CHANGE)) { GenerateUniqueID(&m_uuidJob); ClearFlag(JOB_I_FLAG_APPNAME_CHANGE); } if (!(flOptions & SAVEP_PRESERVE_NET_SCHEDULE)) { ClearFlag(JOB_I_FLAG_NET_SCHEDULE); } // // Save Job fixed length data. // // Allocate a stack buffer which will be sufficient if we are not // saving variable length data. // Note that sizeof flStruct > sizeof FIXDLEN_DATA + sizeof WORD, // due to struct packing requirements, so don't use sizeof flStruct // to compute offsets. // struct FLSTRUCT { FIXDLEN_DATA flData; WORD cRunningInstances; } flStruct; // This code depends on flData and cRunningInstances being contiguous schAssert(FIELD_OFFSET(FLSTRUCT, cRunningInstances) == sizeof FIXDLEN_DATA); flStruct.flData.wVersion = m_wVersion; flStruct.flData.wFileObjVer = m_wFileObjVer; flStruct.flData.uuidJob = m_uuidJob; flStruct.flData.wAppNameLenOffset = sizeof(FIXDLEN_DATA) + sizeof(m_cRunningInstances); flStruct.flData.wTriggerOffset = m_wTriggerOffset; flStruct.flData.wErrorRetryCount = m_wErrorRetryCount; flStruct.flData.wErrorRetryInterval = m_wErrorRetryInterval; flStruct.flData.wIdleWait = m_wIdleWait; flStruct.flData.wIdleDeadline = m_wIdleDeadline; flStruct.flData.dwPriority = m_dwPriority; flStruct.flData.dwMaxRunTime = m_dwMaxRunTime; flStruct.flData.ExitCode = m_ExitCode; flStruct.flData.hrStatus = m_hrStatus; // // Don't save the dirty & set account information flags. // flStruct.flData.rgFlags = m_rgFlags & ~NON_PERSISTED_JOB_FLAGS; flStruct.flData.stMostRecentRunTime = m_stMostRecentRunTime; flStruct.cRunningInstances = m_cRunningInstances; // // Compute the number of bytes to write to the file. This will be // the size of the intermediate buffer to be allocated. // BYTE * pSource = (BYTE *) &flStruct; DWORD cbToWrite = sizeof(flStruct.flData); // // Save the running instance data only if (a) the file is being created, // or (b) the SAVEP_RUNNING_INSTANCE_COUNT option is set. // If SAVEP_VARIABLE_LENGTH_DATA is set, allocate temporary space for the // running instance count, regardless of whether we are going to write it. // if (flOptions & (SAVEP_RUNNING_INSTANCE_COUNT | SAVEP_VARIABLE_LENGTH_DATA)) { cbToWrite += sizeof(flStruct.cRunningInstances); } if (flOptions & SAVEP_VARIABLE_LENGTH_DATA) { // // Add the space needed to write the strings and their lengths. // Save the lengths for use later. // WORD acStringLen[ARRAY_LEN(s_StringField)]; // array of string lengths cbToWrite += sizeof acStringLen; for (int i = 0; i < ARRAY_LEN(acStringLen); i++) { LPWSTR pwsz = this->*s_StringField[i]; acStringLen[i] = (pwsz && *pwsz) ? wcslen(pwsz) + 1 : 0; cbToWrite += acStringLen[i] * sizeof WCHAR; } // // Add the space needed to write the user data, the reserved data // and their lengths. // if (m_cReserved < sizeof TASKRESERVED1) { schAssert(m_cReserved == 0); m_cReserved = sizeof TASKRESERVED1; } cbToWrite += sizeof(m_cbTaskData) + m_cbTaskData + sizeof(m_cReserved) + m_cReserved; // // cbToWrite is now the offset to the trigger data. Save it. // schAssert(cbToWrite <= MAXUSHORT); // BUGBUG Do NOT just assert. // Also return a "limit exceeded" error. flStruct.flData.wTriggerOffset = m_wTriggerOffset = (WORD) cbToWrite; // // Add the space needed to write the triggers and their count. // WORD cTriggers = m_Triggers.GetCount(); cbToWrite += sizeof cTriggers + cTriggers * sizeof TASK_TRIGGER; // // If the job has a signature, add the space needed to write it. // if (m_pbSignature != NULL) { cbToWrite += sizeof(JOB_SIGNATURE_HEADER) + SIGNATURE_SIZE; } // // We have now computed the required space. Allocate a buffer of // that size. // pSource = new BYTE[cbToWrite]; if (pSource == NULL) { hr = E_OUTOFMEMORY; goto ErrExit; } // // Copy data into the buffer. // BYTE * pCurrent = pSource; // current write position #define WRITE_DATA(pSrc, cbSize) \ CopyMemory(pCurrent, (pSrc), (cbSize)); \ pCurrent += (cbSize); // FIXDLEN_DATA and Running Instance Count WRITE_DATA(&flStruct, sizeof flStruct.flData + sizeof flStruct.cRunningInstances); // Strings for (i = 0; i < ARRAY_LEN(acStringLen); i++) { schAssert(POINTER_IS_ALIGNED(pCurrent, ALIGN_WORD)); *(WORD *) pCurrent = acStringLen[i]; pCurrent += sizeof WORD; WRITE_DATA(this->*s_StringField[i], acStringLen[i] * sizeof WCHAR); } // User data schAssert(POINTER_IS_ALIGNED(pCurrent, ALIGN_WORD)); *(WORD *) pCurrent = m_cbTaskData; pCurrent += sizeof WORD; WRITE_DATA(m_pbTaskData, m_cbTaskData); // Note that pCurrent may no longer be WORD-aligned // Reserved data WRITE_DATA(&m_cReserved, sizeof m_cReserved); // Copy private members into the reserved data block to save TASKRESERVED1 Reserved1 = { m_hrStartError, m_rgTaskFlags }; if (m_pbReserved == NULL) { schAssert(m_cReserved == sizeof Reserved1); WRITE_DATA(&Reserved1, m_cReserved); } else { schAssert(m_cReserved >= sizeof Reserved1); CopyMemory(m_pbReserved, &Reserved1, sizeof Reserved1); WRITE_DATA(m_pbReserved, m_cReserved); } // Triggers WRITE_DATA(&cTriggers, sizeof cTriggers); WRITE_DATA(m_Triggers.GetArray(), sizeof TASK_TRIGGER * cTriggers); // Signature if (m_pbSignature != NULL) { JOB_SIGNATURE_HEADER SignHead; SignHead.wSignatureVersion = JOB_SIGNATURE_VERSION; SignHead.wMinClientVersion = JOB_SIGNATURE_MIN_CLIENT_VERSION; WRITE_DATA(&SignHead, sizeof SignHead); WRITE_DATA(m_pbSignature, SIGNATURE_SIZE); } #undef WRITE_DATA schAssert(pCurrent == pSource + cbToWrite); } // // Actually write the data to the file // if ((flOptions & SAVEP_VARIABLE_LENGTH_DATA) && !(flOptions & SAVEP_RUNNING_INSTANCE_COUNT)) { // // Write FIXDLEN_DATA, skip over the running instance count, and // write the variable length data // DWORD cbWritten; if (!WriteFile(hFile, pSource, sizeof FIXDLEN_DATA, &cbWritten, NULL) || cbWritten != sizeof FIXDLEN_DATA) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("CJob::Save, write of Job fixed length data", hr); goto Cleanup; } DWORD dwNewPos = SetFilePointer(hFile, sizeof(m_cRunningInstances), NULL, FILE_CURRENT); if (dwNewPos == 0xffffffff) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("CJob::Save, moving past running instance count", hr); goto Cleanup; } cbToWrite -= (sizeof FIXDLEN_DATA + sizeof m_cRunningInstances); if (!WriteFile(hFile, pSource + (sizeof FIXDLEN_DATA + sizeof m_cRunningInstances), cbToWrite, &cbWritten, NULL) || cbWritten != cbToWrite) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("CJob::Save, write of Job variable length data", hr); goto Cleanup; } } else { // // We can do it all in a single WriteFile. // DWORD cbWritten; if (!WriteFile(hFile, pSource, cbToWrite, &cbWritten, NULL) || cbWritten != cbToWrite) { hr = HRESULT_FROM_WIN32(GetLastError()); ERR_OUT("CJob::Save, write of Job", hr); goto Cleanup; } } if ((flOptions & SAVEP_VARIABLE_LENGTH_DATA) && !SetEndOfFile(hFile)) { ERR_OUT("CJob::Save, SetEOF", HRESULT_FROM_WIN32(GetLastError())); } Cleanup: CloseHandle(hFile); hFile = NULL; if (pSource != (BYTE *) &flStruct) { delete [] pSource; } if (FAILED(hr)) { goto ErrExit; } } // end scope // // Notify the shell of the changes. // if (ptszFileName != NULL) { SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, ptszFileName, NULL); } else { SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, m_ptszFileName, NULL); } // // If doing a Save-As, save the new filename. // if (fRemember && ptszFileName != NULL) { delete m_ptszFileName; m_ptszFileName = new TCHAR[lstrlen(ptszFileName) + 1]; if (!m_ptszFileName) { ERR_OUT("CJob::SaveP", E_OUTOFMEMORY); hr = E_OUTOFMEMORY; goto ErrExit; } StringCchCopy(m_ptszFileName, lstrlen(ptszFileName) + 1, ptszFileName); } if (ptszFileName == NULL || fRemember) { // // BUGBUG: this is not strictly accurate. There could be a dirty // string prop that wouldn't be saved during a light save // (SAVEP_VARIABLE_LENGTH_DATA not specified). This source of // potential error could be alleviated by breaking // JOB_I_FLAG_PROPERTIES_DIRTY into two flags: // JOB_I_FLAG_FIXED_PROPS_DIRTY & JOB_I_FLAG_VAR_PROPS_DIRTY // ClearFlag(JOB_I_FLAG_PROPERTIES_DIRTY); if (flOptions & SAVEP_VARIABLE_LENGTH_DATA) { ClearFlag(JOB_I_FLAG_TRIGGERS_DIRTY); } } // // Set default privileges on the file. This is done only for new // files created as a result of save. // /********************************** This ** SHOULD BE ** redundant. Inherits from container. if (fSetSecurity) { // // NB : Logic prior to CreateFile guarantees the file name // will not be NULL. // hr = SetTaskFileSecurity(ptszFileToSaveAs, this->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE)); if (FAILED(hr)) { goto ErrExit; } } ************************************/ return S_OK; ErrExit: if (hFile != NULL) CloseHandle(hFile); if (dwDisposition == CREATE_NEW) { if (!DeleteFile(ptszFileToSaveAs)) { ERR_OUT("CJob::SaveP: DeleteFile", GetLastError()); } } return hr; } //+--------------------------------------------------------------------------- // // Member: CJob::SaveWithRetry, private // // Synopsis: Use CJob::SaveP to save the job object to disk with failure retry. // // Arguments: [ptszFileName] - See CJob::SaveP // [fRemember] - See CJob::SaveP // [flOptions] - See CJob::SaveP // // Notes: This method is called everywhere SaveP was originally called to // add robustness in the event the file is already opened. // //---------------------------------------------------------------------------- HRESULT CJob::SaveWithRetry(LPCTSTR ptszFileName, BOOL fRemember, ULONG flOptions) { HRESULT hr; // // Write the updated values to the job object. If there are sharing // violations, retry up to two times. // for (int i = 0; i < 3; i++) { hr = SaveP(ptszFileName, fRemember, flOptions); if (SUCCEEDED(hr)) { return S_OK; } if (hr != HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)) { // // If we have a failure other than sharing violation, we will // retry anyway after reporting the error. // ERR_OUT("SaveWithRetry", hr); } // // Wait semi-pseudo-random amount of time before trying again. // Sleep(250 + (rand() % 250)); } return hr; } //+---------------------------------------------------------------------------- // // Member: CJob::IPersistFile::SaveCompleted // // Synopsis: indicates the caller has saved the file with a call to // IPersistFile::Save and is finished working with it // //----------------------------------------------------------------------------- STDMETHODIMP CJob::SaveCompleted(LPCOLESTR pwszFileName) { TRACE(CJob, SaveCompleted); return S_OK; } //+---------------------------------------------------------------------------- // // Member: CJob::IPersistFile::GetCurFile // // Synopsis: supplies either the absolute path of the currently loaded // script file or the default filename prompt, if there is no // currently-associated file // //----------------------------------------------------------------------------- STDMETHODIMP CJob::GetCurFile(LPOLESTR * ppwszFileName) { TRACE(CJob, GetCurFile); HRESULT hr; TCHAR * ptszName, tszDefaultName[SCH_SMBUF_LEN]; WCHAR * pwszName, * pwszBuf = NULL; if (!m_ptszFileName || m_ptszFileName[0] == TEXT('\0')) { // // No file currently loaded, return default prompt 'cause that is // what the OLE spec says to do. // StringCchCopy(tszDefaultName, SCH_SMBUF_LEN, TEXT("*.") TSZ_JOB); ptszName = tszDefaultName; hr = S_FALSE; } else { ptszName = m_ptszFileName; hr = S_OK; } pwszName = ptszName; int size = wcslen(pwszName); LPOLESTR pwz; pwz = (LPOLESTR)CoTaskMemAlloc((size + 1) * sizeof(WCHAR)); if (!pwz) { *ppwszFileName = NULL; return E_OUTOFMEMORY; } StringCchCopy(pwz, size + 1, pwszName); *ppwszFileName = pwz; return hr; } //+---------------------------------------------------------------------------- // // Member: CJob::FreeProperties // // Synopsis: Frees variable length property memory // //----------------------------------------------------------------------------- void CJob::FreeProperties(void) { for (int iProperty = 0; iProperty < ARRAY_LEN(s_StringField); iProperty++) { DELETE_CJOB_FIELD(this->*s_StringField[iProperty]) } DELETE_CJOB_FIELD(m_pbTaskData) m_cbTaskData = 0; DELETE_CJOB_FIELD(m_pbReserved) m_cReserved = 0; DELETE_CJOB_FIELD(m_pbSignature) m_pbSignature = 0; } //+---------------------------------------------------------------------------- // // Function: ReadString // // Synopsis: Reads a wide char string in from an in-memory buffer // //----------------------------------------------------------------------------- BOOL ReadString(CInputBuffer * pBuf, LPWSTR *ppwsz) { schAssert(POINTER_IS_ALIGNED(pBuf->CurrentPosition(), ALIGN_WORD)); schAssert(*ppwsz == NULL); // // Read the string length // WORD cch; if (!pBuf->Read(&cch, sizeof cch)) { ERR_OUT("ReadString, file lacks string length", 0); return FALSE; } if (cch != 0) { LPWSTR pwsz = (LPWSTR) pBuf->CurrentPosition(); // // The string length mustn't exceed the buffer size // if (!pBuf->Advance(cch * sizeof WCHAR)) { ERR_OUT("ReadString, string overruns file size", 0); return FALSE; } // // Verify null termination // if (pwsz[cch-1] != L'\0') { ERR_OUT("ReadString, string not null terminated", 0); return FALSE; } *ppwsz = pwsz; } return TRUE; } //+---------------------------------------------------------------------------- // // Function: GenerateUniqueID // // Synopsis: Intialize the UUID passed to a unique ID. On NT, UuidCreate // initializes it. If UuidCreate fails, default to our custom // ID generation code which is used always on Win95. // // Arguments: [pUuid] -- Ptr to UUID to initialize. // // Returns: None. // // Notes: None. // //----------------------------------------------------------------------------- void GenerateUniqueID(GUID * pUuid) { schAssert(pUuid != NULL); // // Call UuidCreate only on NT. If this should fail, drop down to // our own id generation. // if (UuidCreate(pUuid) == RPC_S_OK) { return; } // // Must generate our own unique id. // // Set Data 1 to the windows tick count. // pUuid->Data1 = GetTickCount(); // // Set Data2 & Data3 to the current system time milliseconds // and seconds values respectively. // SYSTEMTIME systime; GetSystemTime(&systime); pUuid->Data2 = systime.wMilliseconds; pUuid->Data3 = systime.wSecond; // // Write the passed uuid ptr address into the first 4 bytes of // Data4. Then write the current system time minute value into // the following 2. The remaining 2 we'll leave as-is. // CopyMemory(&pUuid->Data4, &pUuid, sizeof(GUID *)); CopyMemory((&pUuid->Data4) + sizeof(GUID *), &systime.wMinute, sizeof(systime.wMinute)); }