Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1279 lines
35 KiB

  1. //+----------------------------------------------------------------------------
  2. //
  3. // Job Scheduler service
  4. //
  5. // Microsoft Windows
  6. // Copyright (C) Microsoft Corporation, 1992 - 1996.
  7. //
  8. // File: sch_at.cxx
  9. //
  10. // Contents: scheduler class object methods to support the NetSchedule
  11. // (AT) APIs.
  12. //
  13. // Classes: CSchedule
  14. //
  15. // History: 30-Jan-96 EricB created
  16. //
  17. //-----------------------------------------------------------------------------
  18. #include "..\pch\headers.hxx"
  19. #pragma hdrstop
  20. #include "Sched.hxx"
  21. //
  22. // Forward references
  23. //
  24. VOID
  25. SetDomTrigger2Days(
  26. DWORD dwDaysOfMonth,
  27. WORD wFirstDayToCheck,
  28. WORD wLastDayToCheck,
  29. SYSTEMTIME *pstStart2,
  30. SYSTEMTIME *pstEnd2);
  31. HRESULT
  32. CSchedule::AddAtJobCommon(
  33. const AT_INFO &At,
  34. DWORD *pID,
  35. CJob **ppJob,
  36. WCHAR wszName[],
  37. size_t cchBuff,
  38. WCHAR wszID[]
  39. )
  40. {
  41. HRESULT hr = S_OK;
  42. //
  43. // If the next at id is > 1 but there aren't any at jobs, the id can be
  44. // reset to 1.
  45. //
  46. if (m_dwNextID > 1 && S_FALSE == _AtTaskExists())
  47. {
  48. ResetAtID();
  49. }
  50. //
  51. // Compose a name for the new AT job.
  52. //
  53. StringCchCopy(wszName, cchBuff, m_ptszFolderPath);
  54. StringCchCat(wszName, cchBuff, L"\\" TSZ_AT_JOB_PREFIX);
  55. _itow(m_dwNextID, wszID, 10);
  56. StringCchCat(wszName, cchBuff, wszID);
  57. StringCchCat(wszName, cchBuff, L"." TSZ_JOB);
  58. //
  59. // Create a new job
  60. //
  61. CJob * pJob = CJob::Create();
  62. if (pJob == NULL)
  63. {
  64. ERR_OUT("CSchedule::AddAtJob: CJob::Create", E_OUTOFMEMORY);
  65. return E_OUTOFMEMORY;
  66. }
  67. //
  68. // Convert the AT command line.
  69. //
  70. WCHAR * pwszApp, * pwszParams, wszCommand[MAX_PATH];
  71. //
  72. // net\svcdlls\atcmd\atcmd.c defines MAX_COMMAND_LEN to be 128. This
  73. // should at some point be changed to MAX_PATH.
  74. //
  75. StringCchCopy(wszCommand, MAX_PATH, At.Command);
  76. pwszApp = wszCommand;
  77. //
  78. // The app name and any command line params are all passed in one string,
  79. // At.Command, so separate the app name from the params. Any path to the
  80. // app plus the app name may be quoted. Otherwise, the parameters are
  81. // separated from the app name by white space.
  82. //
  83. if (*pwszApp == L'"')
  84. {
  85. //
  86. // Initial quote found, scan for end quote. The app name passed to
  87. // SetApplicationName should not be quoted.
  88. //
  89. pwszApp++;
  90. pwszParams = pwszApp + 1;
  91. while (TRUE)
  92. {
  93. if (*pwszParams == L'\0')
  94. {
  95. //
  96. // End of string found, no params.
  97. //
  98. pwszParams = NULL;
  99. break;
  100. }
  101. if (*pwszParams == L'"')
  102. {
  103. //
  104. // End quote found.
  105. //
  106. break;
  107. }
  108. pwszParams++;
  109. }
  110. }
  111. else
  112. {
  113. //
  114. // App path/name not quoted, scan for first white space for parameters.
  115. //
  116. pwszParams = wcspbrk(pwszApp, L" \t");
  117. }
  118. if (pwszParams != NULL)
  119. {
  120. // Null terminate app name string.
  121. //
  122. *pwszParams = L'\0';
  123. //
  124. // Move to first char of the parameters.
  125. //
  126. pwszParams++;
  127. //
  128. // Skip any leading white space.
  129. //
  130. while (*pwszParams != L'\0')
  131. {
  132. if (*pwszParams != L' ' && *pwszParams != L'\t')
  133. {
  134. break;
  135. }
  136. pwszParams++;
  137. }
  138. if (*pwszParams == L'\0')
  139. {
  140. //
  141. // No params.
  142. //
  143. pwszParams = NULL;
  144. }
  145. }
  146. hr = pJob->SetApplicationName(pwszApp);
  147. if (FAILED(hr))
  148. {
  149. ERR_OUT("AddAtJob: SetApplicationName", hr);
  150. pJob->Release();
  151. return hr;
  152. }
  153. if (pwszParams != NULL)
  154. {
  155. hr = pJob->SetParameters(pwszParams);
  156. if (FAILED(hr))
  157. {
  158. ERR_OUT("AddAtJob: SetParameters", hr);
  159. pJob->Release();
  160. return hr;
  161. }
  162. }
  163. pJob->m_rgFlags |= JOB_I_FLAG_NET_SCHEDULE | TASK_FLAG_DELETE_WHEN_DONE;
  164. if (!(At.Flags & JOB_NONINTERACTIVE))
  165. {
  166. pJob->m_rgFlags |= TASK_FLAG_INTERACTIVE;
  167. }
  168. pJob->m_hrStatus = SCHED_S_TASK_READY;
  169. WCHAR szComment[SCH_BUF_LEN + 1];
  170. if (LoadString(g_hInstance,
  171. IDS_NETSCHED_COMMENT,
  172. szComment,
  173. SCH_BUF_LEN) > 0)
  174. {
  175. pJob->SetComment(szComment);
  176. }
  177. //
  178. // Convert from NetSchedule representation to Job Scheduler representation
  179. // of the run dates and times.
  180. //
  181. SYSTEMTIME stNow, stStart;
  182. SYSTEMTIME stDomStart1, stDomEnd1, stDomStart2, stDomEnd2;
  183. SYSTEMTIME stDowStart, stDowEnd;
  184. stDomStart2.wDay = 0; // this serves as a flag
  185. GetLocalTime(&stNow);
  186. stStart = stNow;
  187. //
  188. // JobTime is expressed as milliseconds after midnight, so convert to
  189. // minutes.
  190. //
  191. DWORD dwMins = (DWORD)(At.JobTime / JOB_MILLISECONDS_PER_MINUTE);
  192. stStart.wHour = (WORD)(dwMins / JOB_MINS_PER_HOUR);
  193. stStart.wMinute = (WORD)(dwMins % JOB_MINS_PER_HOUR);
  194. stStart.wSecond = stStart.wMilliseconds = 0;
  195. DWORD DaysOfMonth = At.DaysOfMonth;
  196. WORD wFirstDowRunOffset = 0, wFirstDomRunOffset = 0;
  197. TASK_TRIGGER Trigger;
  198. if (At.Flags & JOB_ADD_CURRENT_DATE)
  199. {
  200. //
  201. // The flag is set, so add today as the first run date.
  202. //
  203. DaysOfMonth |= 1 << (stStart.wDay - 1);
  204. }
  205. else
  206. {
  207. if (DaysOfMonth == 0 && At.DaysOfWeek == 0)
  208. {
  209. //
  210. // Neither bitmask is set, so run at the next opportunity.
  211. //
  212. Trigger.TriggerType = TASK_TIME_TRIGGER_ONCE;
  213. if (! IsFirstTimeEarlier(&stNow, &stStart))
  214. {
  215. // Job runs tomorrow
  216. IncrementDay(&stStart);
  217. }
  218. }
  219. }
  220. //
  221. // Set the trigger values and save the new trigger(s).
  222. //
  223. // Initialize the start and end dates in case this is a periodic trigger.
  224. // If it is not periodic, then new start and end dates will overwrite
  225. // these initialization values.
  226. //
  227. Trigger.cbTriggerSize = sizeof(TASK_TRIGGER);
  228. Trigger.Reserved1 = pJob->m_Triggers.GetCount();
  229. Trigger.wBeginYear = stStart.wYear;
  230. Trigger.wBeginMonth = stStart.wMonth;
  231. Trigger.wBeginDay = stStart.wDay;
  232. Trigger.wEndYear = 0;
  233. Trigger.wEndMonth = 0;
  234. Trigger.wEndDay = 0;
  235. Trigger.wStartHour = stStart.wHour;
  236. Trigger.wStartMinute = stStart.wMinute;
  237. Trigger.Reserved2 = 0;
  238. Trigger.wRandomMinutesInterval = 0;
  239. Trigger.rgFlags = (At.Flags & JOB_RUN_PERIODICALLY)
  240. ? 0 : TASK_TRIGGER_FLAG_HAS_END_DATE;
  241. Trigger.MinutesInterval = Trigger.MinutesDuration = 0;
  242. if (DaysOfMonth == 0 && At.DaysOfWeek == 0)
  243. {
  244. // First, zero out the end date flag
  245. Trigger.rgFlags &= ~JOB_RUN_PERIODICALLY;
  246. // This is a TASK_TIME_TRIGGER_ONCE job, and we are ready to commit
  247. hr = pJob->m_Triggers.Add(Trigger);
  248. if (FAILED(hr))
  249. {
  250. ERR_OUT("AddAtJob: m_Triggers.Add Once", hr);
  251. pJob->Release();
  252. return hr;
  253. }
  254. }
  255. if (DaysOfMonth > 0)
  256. {
  257. Trigger.TriggerType = TASK_TIME_TRIGGER_MONTHLYDATE;
  258. Trigger.Type.MonthlyDate.rgfDays = DaysOfMonth;
  259. Trigger.Type.MonthlyDate.rgfMonths = JOB_RGFMONTHS_MAX;
  260. if (!(At.Flags & JOB_RUN_PERIODICALLY))
  261. {
  262. CalcDomTriggerDates(DaysOfMonth,
  263. stNow,
  264. stStart,
  265. &stDomStart1,
  266. &stDomEnd1,
  267. &stDomStart2,
  268. &stDomEnd2);
  269. Trigger.wBeginYear = stDomStart1.wYear;
  270. Trigger.wBeginMonth = stDomStart1.wMonth;
  271. Trigger.wBeginDay = stDomStart1.wDay;
  272. Trigger.wEndYear = stDomEnd1.wYear;
  273. Trigger.wEndMonth = stDomEnd1.wMonth;
  274. Trigger.wEndDay = stDomEnd1.wDay;
  275. }
  276. hr = pJob->m_Triggers.Add(Trigger);
  277. if (FAILED(hr))
  278. {
  279. ERR_OUT("AddAtJob: m_Triggers.Add Dom1", hr);
  280. pJob->Release();
  281. return hr;
  282. }
  283. if (stDomStart2.wDay != 0)
  284. {
  285. Trigger.wBeginYear = stDomStart2.wYear;
  286. Trigger.wBeginMonth = stDomStart2.wMonth;
  287. Trigger.wBeginDay = stDomStart2.wDay;
  288. Trigger.wEndYear = stDomEnd2.wYear;
  289. Trigger.wEndMonth = stDomEnd2.wMonth;
  290. Trigger.wEndDay = stDomEnd2.wDay;
  291. Trigger.Reserved1 = pJob->m_Triggers.GetCount();
  292. hr = pJob->m_Triggers.Add(Trigger);
  293. if (FAILED(hr))
  294. {
  295. ERR_OUT("AddAtJob: m_Triggers.Add Dom2", hr);
  296. pJob->Release();
  297. return hr;
  298. }
  299. }
  300. }
  301. if (At.DaysOfWeek > 0)
  302. {
  303. Trigger.Reserved1 = pJob->m_Triggers.GetCount();
  304. Trigger.TriggerType = TASK_TIME_TRIGGER_WEEKLY;
  305. Trigger.Type.Weekly.WeeksInterval = 1;
  306. //
  307. // Convert AT_INFO DOW to Scheduler DOW:
  308. // Scheduler rgfDaysOfTheWeek: Sunday = bit 0, Monday = bit 1.
  309. // AT_INFO DaysOfWeek: Monday = bit 0, Sunday = bit 6.
  310. //
  311. Trigger.Type.Weekly.rgfDaysOfTheWeek = At.DaysOfWeek << 1;
  312. if (Trigger.Type.Weekly.rgfDaysOfTheWeek & 0x0080)
  313. {
  314. Trigger.Type.Weekly.rgfDaysOfTheWeek &= ~0x0080;
  315. Trigger.Type.Weekly.rgfDaysOfTheWeek |= 1;
  316. }
  317. if (!(At.Flags & JOB_RUN_PERIODICALLY))
  318. {
  319. CalcDowTriggerDate(stNow,
  320. stStart,
  321. &stDowStart,
  322. &stDowEnd);
  323. Trigger.wBeginYear = stDowStart.wYear;
  324. Trigger.wBeginMonth = stDowStart.wMonth;
  325. Trigger.wBeginDay = stDowStart.wDay;
  326. Trigger.wEndYear = stDowEnd.wYear;
  327. Trigger.wEndMonth = stDowEnd.wMonth;
  328. Trigger.wEndDay = stDowEnd.wDay;
  329. }
  330. hr = pJob->m_Triggers.Add(Trigger);
  331. if (FAILED(hr))
  332. {
  333. ERR_OUT("AddAtJob: m_Triggers.Add", hr);
  334. pJob->Release();
  335. return hr;
  336. }
  337. }
  338. // get the AT task maximum run time from the registry
  339. // if the call fails, the default of 72 will be used
  340. DWORD dwMaxRunTime = 0;
  341. if (SUCCEEDED(GetAtTaskMaxHours(&dwMaxRunTime)))
  342. pJob->SetMaxRunTime(dwMaxRunTime);
  343. //
  344. // Set the same flags as in CJob::CreateTrigger. (We should really call
  345. // CreateTrigger in this function instead of creating the trigger ourselves.)
  346. //
  347. pJob->SetFlag(JOB_I_FLAG_PROPERTIES_DIRTY);
  348. pJob->SetTriggersDirty();
  349. //
  350. // Save the new job. Obviously this is one place we definitely don't want
  351. // the AT job flag cleared on save!
  352. //
  353. hr = pJob->SaveWithRetry(wszName,
  354. TRUE,
  355. SAVEP_VARIABLE_LENGTH_DATA |
  356. SAVEP_PRESERVE_NET_SCHEDULE);
  357. if (FAILED(hr))
  358. {
  359. if (hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS))
  360. {
  361. //
  362. // Name collision; someone has renamed a task to match the next
  363. // AT job. Recalc the max AT job ID.
  364. //
  365. GetNextAtID(&m_dwNextID);
  366. StringCchCopy(wszName, cchBuff, m_ptszFolderPath);
  367. StringCchCat(wszName, cchBuff, L"\\" TSZ_AT_JOB_PREFIX);
  368. _itow(m_dwNextID, wszID, 10);
  369. StringCchCat(wszName, cchBuff, wszID);
  370. StringCchCat(wszName, cchBuff, TSZ_DOTJOB);
  371. //
  372. // Now, retry the save.
  373. //
  374. hr = pJob->SaveWithRetry(wszName,
  375. TRUE,
  376. SAVEP_VARIABLE_LENGTH_DATA |
  377. SAVEP_PRESERVE_NET_SCHEDULE);
  378. if (FAILED(hr))
  379. {
  380. ERR_OUT("CSchedule::AddAtJob: Save", hr);
  381. pJob->Release();
  382. return hr;
  383. }
  384. }
  385. else
  386. {
  387. ERR_OUT("CSchedule::AddAtJob: Save", hr);
  388. pJob->Release();
  389. return hr;
  390. }
  391. }
  392. *ppJob = pJob;
  393. return S_OK;
  394. }
  395. //+----------------------------------------------------------------------------
  396. //
  397. // Member: CSchedule::GetAtTaskMaxHours
  398. //
  399. // Synopsis: Check a registry setting to see what max run time value a user
  400. // has specified for AT tasks. If the key is not present or can't
  401. // be opened then interpret as the normal task default of 72.
  402. //
  403. // Arguments: none
  404. //
  405. // Returns: bool
  406. //
  407. // Notes: This method is not exposed to external clients, thus it is not
  408. // part of a public interface.
  409. //-----------------------------------------------------------------------------
  410. HRESULT CSchedule::GetAtTaskMaxHours(DWORD* pdwMaxHours)
  411. {
  412. //
  413. // Open the schedule service key
  414. //
  415. long lErr;
  416. HKEY hSchedKey;
  417. lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SCH_SVC_KEY, 0, KEY_READ | KEY_WRITE, &hSchedKey);
  418. if (lErr != ERROR_SUCCESS)
  419. {
  420. ERR_OUT("RegOpenKeyEx of Scheduler key", lErr);
  421. return(HRESULT_FROM_WIN32(lErr));
  422. }
  423. //
  424. // Get the AtTaskMaxHours setting
  425. //
  426. bool bNeedToUpdate = false;
  427. DWORD dwMaxHours = 0;
  428. DWORD cb = sizeof(DWORD);
  429. lErr = RegQueryValueEx(hSchedKey, SCH_ATTASKMAXHOURS_VALUE, NULL, NULL, (LPBYTE)&dwMaxHours, &cb);
  430. if (lErr != ERROR_SUCCESS)
  431. {
  432. if (lErr != ERROR_FILE_NOT_FOUND)
  433. {
  434. ERR_OUT("Read of AtTaskMaxHours registry value", lErr);
  435. RegCloseKey(hSchedKey);
  436. return(HRESULT_FROM_WIN32(lErr));
  437. }
  438. //
  439. // Need to create the missing registry entry
  440. //
  441. dwMaxHours = DEFAULT_MAXRUNTIME_HOURS;
  442. bNeedToUpdate = true;
  443. }
  444. //
  445. // Correct out-of-bounds stored registry values
  446. //
  447. if (dwMaxHours > 999)
  448. {
  449. dwMaxHours = 999;
  450. bNeedToUpdate = true;
  451. }
  452. if (bNeedToUpdate)
  453. {
  454. lErr = RegSetValueEx(hSchedKey, SCH_ATTASKMAXHOURS_VALUE, 0, REG_DWORD,(CONST BYTE *)&dwMaxHours, sizeof(DWORD));
  455. if (lErr != ERROR_SUCCESS)
  456. {
  457. ERR_OUT("Update of AtTaskMaxHours registry value", lErr);
  458. RegCloseKey(hSchedKey);
  459. return(HRESULT_FROM_WIN32(lErr));
  460. }
  461. }
  462. //
  463. // Convert the stored value to a value that has meaning to the scheduler
  464. //
  465. if (!dwMaxHours)
  466. dwMaxHours = INFINITE; // If value is zero, return infinite (-1) for max run time
  467. else
  468. dwMaxHours *= 3600000; // The stored value is in hours, but the value passed back
  469. // needs to be in milliseconds, so convert
  470. RegCloseKey(hSchedKey);
  471. // Set the value to be passed back
  472. *pdwMaxHours = dwMaxHours;
  473. return S_OK;
  474. }
  475. //+----------------------------------------------------------------------------
  476. //
  477. // Member: CSchedule::AddAtJob
  478. //
  479. // Synopsis: create a downlevel job
  480. //
  481. // Arguments: [At] - reference to an AT_INFO struct
  482. // [pID] - returns the new ID (optional, can be NULL)
  483. //
  484. // Returns: HRESULTS
  485. //
  486. // Notes: This method is not exposed to external clients, thus it is not
  487. // part of a public interface.
  488. //-----------------------------------------------------------------------------
  489. STDMETHODIMP
  490. CSchedule::AddAtJob(const AT_INFO &At, DWORD * pID)
  491. {
  492. TRACE(CSchedule, AddAtJob);
  493. HRESULT hr = S_OK;
  494. CJob *pJob;
  495. WCHAR wszName[MAX_PATH + 1];
  496. WCHAR wszID[SCH_SMBUF_LEN];
  497. hr = AddAtJobCommon(At, pID, &pJob, wszName, MAX_PATH + 1, wszID);
  498. if (FAILED(hr))
  499. {
  500. ERR_OUT("AddAtJob: AddAtJobCommon", hr);
  501. return hr;
  502. }
  503. //
  504. // Free the job object.
  505. //
  506. pJob->Release();
  507. //
  508. // Return the new job's ID and increment the ID counter
  509. //
  510. if (pID != NULL)
  511. {
  512. *pID = m_dwNextID;
  513. }
  514. hr = IncrementAndSaveID();
  515. return hr;
  516. }
  517. //+---------------------------------------------------------------------------
  518. //
  519. // Function: IsMonthBitSet
  520. //
  521. // Synopsis: Returns nonzero if 1-based bit [wDay] in [dwDaysOfMonth] is
  522. // set.
  523. //
  524. // History: 09-26-96 DavidMun Created
  525. //
  526. //----------------------------------------------------------------------------
  527. inline BOOL
  528. IsMonthBitSet(DWORD dwDaysOfMonth, WORD wDay)
  529. {
  530. return dwDaysOfMonth & (1 << (wDay - 1));
  531. }
  532. //+---------------------------------------------------------------------------
  533. //
  534. // Function: CalcDomTriggerDates
  535. //
  536. // Synopsis: Calculate the dates for the start and end of the Day Of Month
  537. // trigger(s).
  538. //
  539. // Arguments: [dwDaysOfMonth] - bit array, bit 0=day 1, etc. At least one
  540. // bit must be set!
  541. // [stNow] - current time
  542. // [stStart] - same as [stNow] but has hour & minute
  543. // values of actual job run time
  544. // [pstStart1] - filled with start date of first trigger
  545. // [pstEnd1] - filled with end date of first trigger
  546. // [pstStart2] - filled with start date of second trigger;
  547. // wDay is 0 if second trigger not needed
  548. // [pstEnd2] - filled with end date of second trigger,
  549. // wDay is 0 if second trigger not needed
  550. //
  551. // Modifies: All output arguments.
  552. //
  553. // History: 09-26-96 DavidMun Created
  554. //
  555. // Notes: Only the month, day, and year values in the output structs
  556. // are used.
  557. //
  558. //----------------------------------------------------------------------------
  559. VOID
  560. CalcDomTriggerDates(
  561. DWORD dwDaysOfMonth,
  562. const SYSTEMTIME &stNow,
  563. const SYSTEMTIME &stStart,
  564. SYSTEMTIME *pstStart1,
  565. SYSTEMTIME *pstEnd1,
  566. SYSTEMTIME *pstStart2,
  567. SYSTEMTIME *pstEnd2)
  568. {
  569. BOOL fDone = FALSE;
  570. WORD wStart1MonthDays;
  571. // assert not all bits below 32 are zero
  572. Win4Assert(dwDaysOfMonth & JOB_RGFDAYS_MAX);
  573. //
  574. // Find the start date for the first DOM trigger.
  575. //
  576. *pstStart1 = stNow;
  577. if (IsFirstTimeEarlier(&stStart, &stNow))
  578. {
  579. // already past run time today
  580. IncrementDay(pstStart1);
  581. }
  582. HRESULT hr = MonthDays(pstStart1->wMonth, pstStart1->wYear, &wStart1MonthDays);
  583. if (FAILED(hr))
  584. {
  585. schAssert(!"Bad systemtime");
  586. return;
  587. }
  588. do
  589. {
  590. while (!IsMonthBitSet(dwDaysOfMonth, pstStart1->wDay) &&
  591. pstStart1->wDay <= wStart1MonthDays)
  592. {
  593. pstStart1->wDay++;
  594. }
  595. //
  596. // now either:
  597. // start1.wDay > wStart1MonthDays or
  598. // bit at start1.wDay is 1
  599. //
  600. if (pstStart1->wDay > wStart1MonthDays)
  601. {
  602. // have to go on to next month to get the first start date
  603. pstStart1->wDay = 1;
  604. IncrementMonth(pstStart1);
  605. MonthDays(pstStart1->wMonth, pstStart1->wYear, &wStart1MonthDays);
  606. }
  607. else
  608. {
  609. fDone = TRUE;
  610. }
  611. } while (!fDone);
  612. //
  613. // Now bit at pstStart1->wDay is on, and pstStart1->wDay is a valid day in
  614. // pstStart1->wMonth, and wStart1MonthDays is the number of days in the
  615. // month pstStart1->wMonth. Next we need to find end1.
  616. //
  617. // end1 is initialized to start1.
  618. //
  619. // If there are any days set before the start day, then end1.wMonth will
  620. // be start1.wMonth + 1, and end1.wDay will be the last of the days that
  621. // is set before start1.wDay, with the restriction that end1.wDay is not
  622. // greater than the number of days in end1.wMonth.
  623. //
  624. *pstEnd1 = *pstStart1;
  625. WORD wDay;
  626. if (pstStart1->wDay > 1)
  627. {
  628. WORD wEnd1Month;
  629. WORD wEnd1MonthDays;
  630. wEnd1Month = pstEnd1->wMonth + 1;
  631. if (wEnd1Month > 12)
  632. {
  633. wEnd1Month = 1;
  634. }
  635. MonthDays(wEnd1Month, pstEnd1->wYear, &wEnd1MonthDays);
  636. WORD wMaxDay = min(pstStart1->wDay - 1, wEnd1MonthDays);
  637. for (wDay = 1; wDay <= wMaxDay; wDay++)
  638. {
  639. if (IsMonthBitSet(dwDaysOfMonth, wDay))
  640. {
  641. pstEnd1->wDay = wDay;
  642. }
  643. }
  644. }
  645. //
  646. // If any day bits were set before start1.wDay then end1.wDay will no
  647. // longer == start1.wDay, and end1 will be referring to the next month.
  648. //
  649. // Otherwise, End1 will remain in the same month as Start1, but will
  650. // need to be set to the last day bit set in the Start1 month.
  651. //
  652. if (pstEnd1->wDay < pstStart1->wDay)
  653. {
  654. IncrementMonth(pstEnd1);
  655. }
  656. else
  657. {
  658. for (wDay = pstStart1->wDay + 1; wDay <= wStart1MonthDays; wDay++)
  659. {
  660. if (IsMonthBitSet(dwDaysOfMonth, wDay))
  661. {
  662. pstEnd1->wDay = wDay;
  663. }
  664. }
  665. }
  666. //
  667. // Now start1 and end1 are set. next, check if there's a need for the
  668. // second trigger. There are two cases where a second trigger is
  669. // required.
  670. //
  671. // Case a: second trigger must fill time between end of first trigger
  672. // and start of first trigger. for example, job is to run on next
  673. // 1, 30, 31 and start1 is 1/31. then end1 will be 2/1, and a second
  674. // trigger must go from 3/30 to 3/30. Note this case can only occur
  675. // if End1.wMonth == February.
  676. //
  677. // Case b: second trigger must fill time somewhere in the 29-31 day range.
  678. // For example, job is to run on next 1-31, and current day is 4/1. so
  679. // start1 is 4/1, end1 is 4/30, then start2 must be 5/31 to 5/31.
  680. //
  681. // As another example, job is to run on next 27, 28, 30, current day is
  682. // 2/28. then start1 is 2/28, end1 is 3/27, start2 is 3/30, end2 is 3/30.
  683. //
  684. // Case b only occurs when there are bits set for days beyond the last day
  685. // of pstStart1->wmonth.
  686. //
  687. //
  688. //
  689. // test if we need case a.
  690. //
  691. if (pstEnd1->wMonth == 2 &&
  692. pstStart1->wDay > 29 &&
  693. pstEnd1->wDay < pstStart1->wDay - 1)
  694. {
  695. //
  696. // There's a gap between end1 and start1. we need the second trigger
  697. // if there are any day bits set in that gap.
  698. //
  699. Win4Assert(pstStart1->wMonth == 1);
  700. Win4Assert(pstEnd1->wDay + 1 <= 30);
  701. *pstStart2 = *pstEnd1;
  702. pstStart2->wMonth = 3;
  703. *pstEnd2 = *pstStart2;
  704. SetDomTrigger2Days(dwDaysOfMonth,
  705. pstEnd1->wDay + 1,
  706. pstStart1->wDay - 1,
  707. pstStart2,
  708. pstEnd2);
  709. }
  710. else if (wStart1MonthDays < 31)
  711. {
  712. //
  713. // we have case b if any bits after the last day in pstStart1->wMonth
  714. // are set.
  715. //
  716. *pstStart2 = *pstEnd1;
  717. if (pstEnd1->wMonth == pstStart1->wMonth)
  718. {
  719. pstStart2->wMonth++;
  720. }
  721. *pstEnd2 = *pstStart2;
  722. SetDomTrigger2Days(dwDaysOfMonth,
  723. wStart1MonthDays + 1,
  724. 31,
  725. pstStart2,
  726. pstEnd2);
  727. }
  728. else
  729. {
  730. // no second trigger
  731. pstStart2->wDay = 0;
  732. pstEnd2->wDay = 0;
  733. }
  734. }
  735. //+---------------------------------------------------------------------------
  736. //
  737. // Function: SetDomTrigger2Days
  738. //
  739. // Synopsis: Set the start and end dates for the second DOM trigger.
  740. //
  741. // Arguments: [dwDaysOfMonth] - bit array, bit 0=day 1, etc. At least
  742. // one bit must be set!
  743. // [wFirstDayToCheck] - 1 based
  744. // [wLastDayToCheck] - 1 based, must be >= [wFirstDayToCheck]
  745. // [pstStart2] - filled with start date of second
  746. // trigger; wDay is 0 if no second
  747. // trigger is required.
  748. // [pstEnd2] - filled with end date of second trigger;
  749. // wDay is 0 if no second trigger is
  750. // required.
  751. //
  752. // Modifies: All out args.
  753. //
  754. // History: 09-26-96 DavidMun Created
  755. //
  756. // Notes: This is a helper function called only by
  757. // CalcDomTriggerDates.
  758. //
  759. //----------------------------------------------------------------------------
  760. VOID
  761. SetDomTrigger2Days(
  762. DWORD dwDaysOfMonth,
  763. WORD wFirstDayToCheck,
  764. WORD wLastDayToCheck,
  765. SYSTEMTIME *pstStart2,
  766. SYSTEMTIME *pstEnd2)
  767. {
  768. WORD wDay;
  769. pstStart2->wDay = 0;
  770. pstEnd2->wDay = 0;
  771. for (wDay = wFirstDayToCheck; wDay <= wLastDayToCheck; wDay++)
  772. {
  773. if (IsMonthBitSet(dwDaysOfMonth, wDay))
  774. {
  775. //
  776. // if the start of the second trigger hasn't been assigned
  777. // yet, assign it. otherwise update the end of the second
  778. // trigger to the current day.
  779. //
  780. if (!pstStart2->wDay)
  781. {
  782. pstStart2->wDay = wDay;
  783. }
  784. else
  785. {
  786. pstEnd2->wDay = wDay;
  787. }
  788. }
  789. }
  790. //
  791. // there may have been only one day on in the gap, so the start and
  792. // end of trigger 2 are the same day.
  793. //
  794. if (pstStart2->wDay && !pstEnd2->wDay)
  795. {
  796. pstEnd2->wDay = pstStart2->wDay;
  797. }
  798. }
  799. //+---------------------------------------------------------------------------
  800. //
  801. // Function: CalcDowTriggerDate
  802. //
  803. // Synopsis: Set the start and end dates for the Day of Week trigger.
  804. //
  805. // Arguments: [stNow] - Current time
  806. // [stStart] - same as [stNow] but with hour and minute of
  807. // actual run time
  808. // [pstStart] - filled with start date
  809. // [pstEnd] - filled with end date
  810. //
  811. // Modifies: *[pstStart], *[pstEnd]
  812. //
  813. // History: 09-26-96 DavidMun Created
  814. //
  815. //----------------------------------------------------------------------------
  816. VOID
  817. CalcDowTriggerDate(
  818. const SYSTEMTIME &stNow,
  819. const SYSTEMTIME &stStart,
  820. SYSTEMTIME *pstStart,
  821. SYSTEMTIME *pstEnd)
  822. {
  823. *pstStart = stNow;
  824. //
  825. // If it's too late for the job to run today, make the start date
  826. // tomorrow.
  827. //
  828. if (IsFirstTimeEarlier(&stStart, &stNow))
  829. {
  830. IncrementDay(pstStart);
  831. }
  832. //
  833. // Make the end date 6 days later than the start date, that way we cover a
  834. // full week and all runs will happen.
  835. //
  836. *pstEnd = *pstStart;
  837. pstEnd->wDay += 6;
  838. WORD wLastDay;
  839. HRESULT hr = MonthDays(pstEnd->wMonth, pstEnd->wYear, &wLastDay);
  840. if (FAILED(hr))
  841. {
  842. schAssert(!"Bad systemtime");
  843. }
  844. else
  845. {
  846. if (pstEnd->wDay > wLastDay)
  847. {
  848. //
  849. // Wrap to the next month.
  850. //
  851. pstEnd->wDay -= wLastDay;
  852. IncrementMonth(pstEnd);
  853. }
  854. }
  855. }
  856. //+----------------------------------------------------------------------------
  857. //
  858. // Member: CSchedule::GetAtJob, private
  859. //
  860. // Synopsis: retrieves a downlevel job's AT info
  861. //
  862. // Arguments: [pwszFileName] - job object's file name
  863. // [pAt] - pointer to an AT_INFO struct
  864. // [pwszCommand] - buffer for the command string
  865. // [pcchCommand] - on input, size of supplied buffer, on output,
  866. // size needed if supplied buffer is too small.
  867. //
  868. // Returns: HRESULTS - ERROR_INSUFFICIENT_BUFFER if too small
  869. // - SCHED_E_NOT_AN_AT_JOB if not an AT job
  870. //
  871. // Notes: This method is not exposed to external clients, thus it is not
  872. // part of a public interface.
  873. //-----------------------------------------------------------------------------
  874. STDMETHODIMP
  875. CSchedule::GetAtJob(LPCTSTR pwszFileName, AT_INFO * pAt, LPWSTR pwszCommand,
  876. DWORD * pcchCommand)
  877. {
  878. TRACE(CSchedule, GetAtJob);
  879. CJob * pJob = CJob::Create();
  880. if (pJob == NULL)
  881. {
  882. return E_OUTOFMEMORY;
  883. }
  884. HRESULT hr = pJob->LoadP(pwszFileName, 0, TRUE, TRUE);
  885. if (FAILED(hr))
  886. {
  887. ERR_OUT("GetAtJob: LoadP", hr);
  888. pJob->Release();
  889. return hr;
  890. }
  891. hr = pJob->GetAtInfo(pAt, pwszCommand, pcchCommand);
  892. if (FAILED(hr))
  893. {
  894. ERR_OUT("GetAtJob: GetAtInfo", hr);
  895. pJob->Release();
  896. return hr;
  897. }
  898. pJob->Release();
  899. return S_OK;
  900. }
  901. //+----------------------------------------------------------------------------
  902. //
  903. // Member: CSchedule::IncrementAndSaveID
  904. //
  905. // Synopsis: Increment the NextJobID value and save it to the registry.
  906. //
  907. //-----------------------------------------------------------------------------
  908. HRESULT
  909. CSchedule::IncrementAndSaveID(void)
  910. {
  911. EnterCriticalSection(&m_CriticalSection);
  912. long lErr;
  913. HKEY hSchedKey;
  914. lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SCH_SVC_KEY, 0, KEY_SET_VALUE,
  915. &hSchedKey);
  916. if (lErr != ERROR_SUCCESS)
  917. {
  918. ERR_OUT("RegOpenKeyEx of Scheduler key", lErr);
  919. LeaveCriticalSection(&m_CriticalSection);
  920. return(HRESULT_FROM_WIN32(lErr));
  921. }
  922. m_dwNextID++;
  923. //
  924. // update the registry entry
  925. //
  926. lErr = RegSetValueEx(hSchedKey, SCH_NEXTATJOBID_VALUE, 0, REG_DWORD,
  927. (CONST BYTE *)&m_dwNextID, sizeof(DWORD));
  928. if (lErr != ERROR_SUCCESS)
  929. {
  930. ERR_OUT("Create of NextAtJobId registry value", lErr);
  931. m_dwNextID--;
  932. RegCloseKey(hSchedKey);
  933. LeaveCriticalSection(&m_CriticalSection);
  934. return(HRESULT_FROM_WIN32(lErr));
  935. }
  936. RegCloseKey(hSchedKey);
  937. LeaveCriticalSection(&m_CriticalSection);
  938. return S_OK;
  939. }
  940. //+----------------------------------------------------------------------------
  941. //
  942. // Member: CSchedule::ResetAtID
  943. //
  944. // Synopsis: Set the next at id value in the registry to 1
  945. //
  946. //-----------------------------------------------------------------------------
  947. HRESULT
  948. CSchedule::ResetAtID(void)
  949. {
  950. HRESULT hr = S_OK;
  951. HKEY hSchedKey = NULL;
  952. EnterCriticalSection(&m_CriticalSection);
  953. m_dwNextID = 1;
  954. do
  955. {
  956. LONG lErr;
  957. //
  958. // Open the schedule service key
  959. //
  960. lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  961. SCH_SVC_KEY,
  962. 0,
  963. KEY_READ | KEY_WRITE,
  964. &hSchedKey);
  965. if (lErr != ERROR_SUCCESS)
  966. {
  967. ERR_OUT("RegOpenKeyEx of Scheduler key", lErr);
  968. hr = HRESULT_FROM_WIN32(lErr);
  969. break;
  970. }
  971. //
  972. // Set the next At Job ID value to 1. If the value is not present,
  973. // it will be created.
  974. //
  975. lErr = RegSetValueEx(hSchedKey,
  976. SCH_NEXTATJOBID_VALUE,
  977. 0,
  978. REG_DWORD,
  979. (CONST BYTE *) &m_dwNextID,
  980. sizeof(m_dwNextID));
  981. if (lErr != ERROR_SUCCESS)
  982. {
  983. ERR_OUT("Create of NextAtJobId registry value", lErr);
  984. hr = HRESULT_FROM_WIN32(lErr);
  985. }
  986. } while (0);
  987. if (hSchedKey)
  988. {
  989. RegCloseKey(hSchedKey);
  990. }
  991. LeaveCriticalSection(&m_CriticalSection);
  992. return hr;
  993. }
  994. //+----------------------------------------------------------------------------
  995. //
  996. // Member: CSchedule::_AtTaskExists, private
  997. //
  998. // Synopsis: Check for existing AT tasks
  999. //
  1000. // Returns: S_OK - an AT task was found
  1001. // S_FALSE - no AT tasks were found
  1002. // E_*
  1003. //
  1004. //-----------------------------------------------------------------------------
  1005. HRESULT
  1006. CSchedule::_AtTaskExists(void)
  1007. {
  1008. WIN32_FIND_DATA fd;
  1009. HANDLE hFileFindContext;
  1010. hFileFindContext = FindFirstFile(g_wszAtJobSearchPath, &fd);
  1011. if (hFileFindContext == INVALID_HANDLE_VALUE)
  1012. {
  1013. return S_FALSE;
  1014. }
  1015. CJob * pJob = CJob::Create();
  1016. if (pJob == NULL)
  1017. {
  1018. return E_OUTOFMEMORY;
  1019. }
  1020. HRESULT hr = S_FALSE;
  1021. DWORD rgFlags;
  1022. do
  1023. {
  1024. if (!IsValidAtFilename(fd.cFileName))
  1025. {
  1026. continue;
  1027. }
  1028. HRESULT hrLoad = LoadAtJob(pJob, fd.cFileName);
  1029. if (FAILED(hrLoad))
  1030. {
  1031. hr = hrLoad;
  1032. break;
  1033. }
  1034. pJob->GetAllFlags(&rgFlags);
  1035. if (rgFlags & JOB_I_FLAG_NET_SCHEDULE)
  1036. {
  1037. hr = S_OK;
  1038. break;
  1039. }
  1040. } while (FindNextFile(hFileFindContext, &fd));
  1041. FindClose(hFileFindContext);
  1042. pJob->Release();
  1043. return hr;
  1044. }
  1045. //+----------------------------------------------------------------------------
  1046. //
  1047. // Member: CSchedule::InitAtID
  1048. //
  1049. // Synopsis: Obtains the current AT task ID from the registry.
  1050. //
  1051. //-----------------------------------------------------------------------------
  1052. HRESULT
  1053. CSchedule::InitAtID(void)
  1054. {
  1055. //
  1056. // Open the schedule service key
  1057. //
  1058. long lErr;
  1059. HKEY hSchedKey;
  1060. lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SCH_SVC_KEY, 0,
  1061. KEY_READ | KEY_WRITE, &hSchedKey);
  1062. if (lErr != ERROR_SUCCESS)
  1063. {
  1064. ERR_OUT("RegOpenKeyEx of Scheduler key", lErr);
  1065. return(HRESULT_FROM_WIN32(lErr));
  1066. }
  1067. //
  1068. // Get the next At Job ID
  1069. //
  1070. DWORD cb = sizeof(DWORD);
  1071. lErr = RegQueryValueEx(hSchedKey, SCH_NEXTATJOBID_VALUE, NULL, NULL,
  1072. (LPBYTE)&m_dwNextID, &cb);
  1073. if (lErr != ERROR_SUCCESS)
  1074. {
  1075. if (lErr != ERROR_FILE_NOT_FOUND)
  1076. {
  1077. ERR_OUT("Read of NextAtJobId registry value", lErr);
  1078. RegCloseKey(hSchedKey);
  1079. return(HRESULT_FROM_WIN32(lErr));
  1080. }
  1081. //
  1082. // Scan AT jobs for value if registry entry absent
  1083. //
  1084. GetNextAtID(&m_dwNextID);
  1085. //
  1086. // Create registry entry
  1087. //
  1088. lErr = RegSetValueEx(hSchedKey, SCH_NEXTATJOBID_VALUE, 0, REG_DWORD,
  1089. (CONST BYTE *)&m_dwNextID, sizeof(DWORD));
  1090. if (lErr != ERROR_SUCCESS)
  1091. {
  1092. ERR_OUT("Create of NextAtJobId registry value", lErr);
  1093. RegCloseKey(hSchedKey);
  1094. return(HRESULT_FROM_WIN32(lErr));
  1095. }
  1096. }
  1097. RegCloseKey(hSchedKey);
  1098. return S_OK;
  1099. }