1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Services
4 * FILE: base/services/schedsvc/job.c
5 * PURPOSE: Scheduling service
6 * PROGRAMMER: Eric Kohl <eric.kohl@reactos.org>
7 */
8
9 /* INCLUDES *****************************************************************/
10
11 #include "precomp.h"
12
13 WINE_DEFAULT_DEBUG_CHANNEL(schedsvc);
14
15
16 /* GLOBALS ******************************************************************/
17
18 typedef struct _SCHEDULE
19 {
20 DWORD JobTime;
21 DWORD DaysOfMonth;
22 UCHAR DaysOfWeek;
23 UCHAR Flags;
24 WORD Reserved;
25 } SCHEDULE, PSCHEDULE;
26
27 DWORD dwNextJobId = 0;
28 DWORD dwJobCount = 0;
29 LIST_ENTRY JobListHead;
30 RTL_RESOURCE JobListLock;
31
32 LIST_ENTRY StartListHead;
33 RTL_RESOURCE StartListLock;
34 FILETIME NextJobStartTime;
35 BOOL bValidNextJobStartTime = FALSE;
36
37
38 static WORD wDaysArray[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
39
40
41 /* FUNCTIONS *****************************************************************/
42
43 VOID
GetNextJobTimeout(HANDLE hTimer)44 GetNextJobTimeout(HANDLE hTimer)
45 {
46 PLIST_ENTRY CurrentEntry;
47 FILETIME DueTime;
48 PJOB CurrentJob;
49
50 bValidNextJobStartTime = FALSE;
51 CurrentEntry = JobListHead.Flink;
52 while (CurrentEntry != &JobListHead)
53 {
54 CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, JobEntry);
55
56 if (bValidNextJobStartTime == FALSE)
57 {
58 CopyMemory(&NextJobStartTime, &CurrentJob->StartTime, sizeof(FILETIME));
59 bValidNextJobStartTime = TRUE;
60 }
61 else
62 {
63 if (CompareFileTime(&NextJobStartTime, &CurrentJob->StartTime) > 0)
64 CopyMemory(&NextJobStartTime, &CurrentJob->StartTime, sizeof(FILETIME));
65 }
66
67 CurrentEntry = CurrentEntry->Flink;
68 }
69
70 if (bValidNextJobStartTime == FALSE)
71 {
72 TRACE("No valid job!\n");
73 return;
74 }
75
76 LocalFileTimeToFileTime(&DueTime, &NextJobStartTime);
77
78 SetWaitableTimer(hTimer,
79 (PLARGE_INTEGER)&DueTime,
80 0,
81 NULL,
82 NULL,
83 TRUE);
84 }
85
86 #if 0
87 static
88 VOID
89 ReScheduleJob(
90 PJOB pJob)
91 {
92 /* Remove the job from the start list */
93 RemoveEntryList(&pJob->StartEntry);
94
95 /* Non-periodical job, remove it */
96 if ((pJob->Flags & JOB_RUN_PERIODICALLY) == 0)
97 {
98 /* Remove the job from the registry */
99 DeleteJob(pJob);
100
101 /* Remove the job from the job list */
102 RemoveEntryList(&pJob->JobEntry);
103 dwJobCount--;
104
105 /* Free the job object */
106 HeapFree(GetProcessHeap(), 0, pJob);
107 return;
108 }
109
110 /* Calculate the next start time */
111 CalculateNextStartTime(pJob);
112
113 /* Insert the job into the start list again */
114 InsertJobIntoStartList(&StartListHead, pJob);
115 #if 0
116 DumpStartList(&StartListHead);
117 #endif
118 }
119 #endif
120
121 VOID
RunCurrentJobs(VOID)122 RunCurrentJobs(VOID)
123 {
124 PROCESS_INFORMATION ProcessInformation;
125 STARTUPINFOW StartupInfo;
126 PLIST_ENTRY CurrentEntry;
127 PJOB CurrentJob;
128 BOOL bRet;
129
130 CurrentEntry = JobListHead.Flink;
131 while (CurrentEntry != &JobListHead)
132 {
133 CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, JobEntry);
134
135 if (CompareFileTime(&NextJobStartTime, &CurrentJob->StartTime) == 0)
136 {
137 TRACE("Run job %ld: %S\n", CurrentJob->JobId, CurrentJob->Command);
138
139 ZeroMemory(&StartupInfo, sizeof(StartupInfo));
140 StartupInfo.cb = sizeof(StartupInfo);
141 StartupInfo.lpTitle = CurrentJob->Command;
142 StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
143 StartupInfo.wShowWindow = SW_SHOWDEFAULT;
144
145 if ((CurrentJob->Flags & JOB_NONINTERACTIVE) == 0)
146 {
147 StartupInfo.dwFlags |= STARTF_INHERITDESKTOP;
148 StartupInfo.lpDesktop = L"WinSta0\\Default";
149 }
150
151 bRet = CreateProcessW(NULL,
152 CurrentJob->Command,
153 NULL,
154 NULL,
155 FALSE,
156 CREATE_NEW_CONSOLE,
157 NULL,
158 NULL,
159 &StartupInfo,
160 &ProcessInformation);
161 if (bRet == FALSE)
162 {
163 ERR("CreateProcessW() failed (Error %lu)\n", GetLastError());
164 }
165 else
166 {
167 CloseHandle(ProcessInformation.hThread);
168 CloseHandle(ProcessInformation.hProcess);
169 }
170 }
171
172 CurrentEntry = CurrentEntry->Flink;
173 }
174 }
175
176
177 static
178 VOID
GetJobName(HKEY hJobsKey,PWSTR pszJobName)179 GetJobName(
180 HKEY hJobsKey,
181 PWSTR pszJobName)
182 {
183 WCHAR szNameBuffer[JOB_NAME_LENGTH];
184 FILETIME SystemTime;
185 ULONG ulSeed, ulValue;
186 HKEY hKey;
187 LONG lError;
188
189 GetSystemTimeAsFileTime(&SystemTime);
190 ulSeed = SystemTime.dwLowDateTime;
191 for (;;)
192 {
193 ulValue = RtlRandomEx(&ulSeed);
194 swprintf(szNameBuffer, L"%08lx", ulValue);
195
196 hKey = NULL;
197 lError = RegOpenKeyEx(hJobsKey,
198 szNameBuffer,
199 0,
200 KEY_READ,
201 &hKey);
202 if (lError != ERROR_SUCCESS)
203 {
204 wcscpy(pszJobName, szNameBuffer);
205 return;
206 }
207
208 RegCloseKey(hKey);
209 }
210 }
211
212
213 LONG
SaveJob(_In_ PJOB pJob)214 SaveJob(
215 _In_ PJOB pJob)
216 {
217 SCHEDULE Schedule;
218 HKEY hJobsKey = NULL, hJobKey = NULL;
219 LONG lError;
220
221 TRACE("SaveJob()\n");
222
223 lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
224 L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
225 0,
226 NULL,
227 REG_OPTION_NON_VOLATILE,
228 KEY_WRITE,
229 NULL,
230 &hJobsKey,
231 NULL);
232 if (lError != ERROR_SUCCESS)
233 goto done;
234
235 GetJobName(hJobsKey, pJob->Name);
236
237 lError = RegCreateKeyExW(hJobsKey,
238 pJob->Name,
239 0,
240 NULL,
241 REG_OPTION_NON_VOLATILE,
242 KEY_WRITE,
243 NULL,
244 &hJobKey,
245 NULL);
246 if (lError != ERROR_SUCCESS)
247 goto done;
248
249 Schedule.JobTime = pJob->JobTime;
250 Schedule.DaysOfMonth = pJob->DaysOfMonth;
251 Schedule.DaysOfWeek = pJob->DaysOfWeek;
252 Schedule.Flags = pJob->Flags;
253
254 lError = RegSetValueEx(hJobKey,
255 L"Schedule",
256 0,
257 REG_BINARY,
258 (PBYTE)&Schedule,
259 sizeof(Schedule));
260 if (lError != ERROR_SUCCESS)
261 goto done;
262
263 lError = RegSetValueEx(hJobKey,
264 L"Command",
265 0,
266 REG_SZ,
267 (PBYTE)pJob->Command,
268 (wcslen(pJob->Command) + 1) * sizeof(WCHAR));
269 if (lError != ERROR_SUCCESS)
270 goto done;
271
272 done:
273 if (hJobKey != NULL)
274 RegCloseKey(hJobKey);
275
276 if (hJobsKey != NULL)
277 RegCloseKey(hJobsKey);
278
279 return lError;
280 }
281
282
283 LONG
DeleteJob(_In_ PJOB pJob)284 DeleteJob(
285 _In_ PJOB pJob)
286 {
287 HKEY hJobsKey = NULL;
288 LONG lError;
289
290 TRACE("DeleteJob()\n");
291
292 lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
293 L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
294 0,
295 NULL,
296 REG_OPTION_NON_VOLATILE,
297 KEY_WRITE,
298 NULL,
299 &hJobsKey,
300 NULL);
301 if (lError != ERROR_SUCCESS)
302 goto done;
303
304 lError = RegDeleteKey(hJobsKey,
305 pJob->Name);
306 if (lError != ERROR_SUCCESS)
307 goto done;
308
309 done:
310 if (hJobsKey != NULL)
311 RegCloseKey(hJobsKey);
312
313 return lError;
314 }
315
316
317 LONG
LoadJobs(VOID)318 LoadJobs(VOID)
319 {
320 SCHEDULE Schedule;
321 WCHAR szNameBuffer[JOB_NAME_LENGTH];
322 DWORD dwNameLength, dwIndex, dwSize;
323 HKEY hJobsKey = NULL, hJobKey = NULL;
324 PJOB pJob = NULL;
325 LONG lError;
326
327 TRACE("LoadJobs()\n");
328
329 lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
330 L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
331 0,
332 NULL,
333 REG_OPTION_NON_VOLATILE,
334 KEY_READ,
335 NULL,
336 &hJobsKey,
337 NULL);
338 if (lError != ERROR_SUCCESS)
339 goto done;
340
341 for (dwIndex = 0; dwIndex < 1000; dwIndex++)
342 {
343 dwNameLength = JOB_NAME_LENGTH;
344 lError = RegEnumKeyEx(hJobsKey,
345 dwIndex,
346 szNameBuffer,
347 &dwNameLength,
348 NULL,
349 NULL,
350 NULL,
351 NULL);
352 if (lError != ERROR_SUCCESS)
353 {
354 lError = ERROR_SUCCESS;
355 break;
356 }
357
358 TRACE("KeyName: %S\n", szNameBuffer);
359
360 lError = RegOpenKeyEx(hJobsKey,
361 szNameBuffer,
362 0,
363 KEY_READ,
364 &hJobKey);
365 if (lError != ERROR_SUCCESS)
366 break;
367
368 dwSize = sizeof(SCHEDULE);
369 lError = RegQueryValueEx(hJobKey,
370 L"Schedule",
371 NULL,
372 NULL,
373 (PBYTE)&Schedule,
374 &dwSize);
375 if (lError == ERROR_SUCCESS)
376 {
377 dwSize = 0;
378 RegQueryValueEx(hJobKey,
379 L"Command",
380 NULL,
381 NULL,
382 NULL,
383 &dwSize);
384 if (dwSize != 0)
385 {
386 /* Allocate a new job object */
387 pJob = HeapAlloc(GetProcessHeap(),
388 HEAP_ZERO_MEMORY,
389 sizeof(JOB) + dwSize - sizeof(WCHAR));
390 if (pJob == NULL)
391 {
392 lError = ERROR_OUTOFMEMORY;
393 break;
394 }
395
396 lError = RegQueryValueEx(hJobKey,
397 L"Command",
398 NULL,
399 NULL,
400 (PBYTE)pJob->Command,
401 &dwSize);
402 if (lError != ERROR_SUCCESS)
403 break;
404
405 wcscpy(pJob->Name, szNameBuffer);
406 pJob->JobTime = Schedule.JobTime;
407 pJob->DaysOfMonth = Schedule.DaysOfMonth;
408 pJob->DaysOfWeek = Schedule.DaysOfWeek;
409 pJob->Flags = Schedule.Flags;
410
411 /* Acquire the job list lock exclusively */
412 RtlAcquireResourceExclusive(&JobListLock, TRUE);
413
414 /* Assign a new job ID */
415 pJob->JobId = dwNextJobId++;
416 dwJobCount++;
417
418 /* Append the new job to the job list */
419 InsertTailList(&JobListHead, &pJob->JobEntry);
420
421 /* Calculate the next start time */
422 CalculateNextStartTime(pJob);
423
424 #if 0
425 DumpStartList(&StartListHead);
426 #endif
427
428 /* Release the job list lock */
429 RtlReleaseResource(&JobListLock);
430
431 pJob = NULL;
432 }
433 }
434
435 RegCloseKey(hJobKey);
436 hJobKey = NULL;
437 }
438
439 done:
440 if (pJob != NULL)
441 HeapFree(GetProcessHeap(), 0, pJob);
442
443 if (hJobKey != NULL)
444 RegCloseKey(hJobKey);
445
446 if (hJobsKey != NULL)
447 RegCloseKey(hJobsKey);
448
449 return lError;
450 }
451
452
453 static
454 WORD
DaysOfMonth(WORD wMonth,WORD wYear)455 DaysOfMonth(
456 WORD wMonth,
457 WORD wYear)
458 {
459 if (wMonth == 2 && wYear % 4 == 0 && wYear % 400 != 0)
460 return 29;
461
462 return wDaysArray[wMonth];
463 }
464
465
466 VOID
CalculateNextStartTime(_In_ PJOB pJob)467 CalculateNextStartTime(
468 _In_ PJOB pJob)
469 {
470 SYSTEMTIME CurrentSystemTime, StartSystemTime;
471 FILETIME StartFileTime;
472 WORD wDaysOffset, wTempOffset, i, wJobDayOfWeek, wJobDayOfMonth;
473 DWORD_PTR CurrentTimeMs;
474 BOOL bDaysOffsetValid;
475 ULARGE_INTEGER LocalStartTime;
476
477 TRACE("CalculateNextStartTime(%p)\n", pJob);
478 TRACE("JobTime: %lu\n", pJob->JobTime);
479 TRACE("DaysOfWeek: 0x%x\n", pJob->DaysOfWeek);
480 TRACE("DaysOfMonth: 0x%x\n", pJob->DaysOfMonth);
481
482 GetLocalTime(&CurrentSystemTime);
483
484 CurrentTimeMs = (DWORD_PTR)CurrentSystemTime.wHour * 3600000 +
485 (DWORD_PTR)CurrentSystemTime.wMinute * 60000;
486
487 bDaysOffsetValid = FALSE;
488 wDaysOffset = 0;
489 if ((pJob->DaysOfWeek == 0) && (pJob->DaysOfMonth == 0))
490 {
491 if (CurrentTimeMs >= pJob->JobTime)
492 {
493 TRACE("Tomorrow!\n");
494 wDaysOffset = 1;
495 }
496
497 bDaysOffsetValid = TRUE;
498 }
499 else
500 {
501 if (pJob->DaysOfWeek != 0)
502 {
503 TRACE("DaysOfWeek!\n");
504 for (i = 0; i < 7; i++)
505 {
506 if (pJob->DaysOfWeek & (1 << i))
507 {
508 /* Adjust the range */
509 wJobDayOfWeek = (i + 1) % 7;
510 TRACE("wJobDayOfWeek: %hu\n", wJobDayOfWeek);
511 TRACE("CurrentSystemTime.wDayOfWeek: %hu\n", CurrentSystemTime.wDayOfWeek);
512
513 /* Calculate the days offset */
514 if ((CurrentSystemTime.wDayOfWeek > wJobDayOfWeek ) ||
515 ((CurrentSystemTime.wDayOfWeek == wJobDayOfWeek) && (CurrentTimeMs >= pJob->JobTime)))
516 {
517 wTempOffset = 7 - CurrentSystemTime.wDayOfWeek + wJobDayOfWeek;
518 TRACE("wTempOffset: %hu\n", wTempOffset);
519 }
520 else
521 {
522 wTempOffset = wJobDayOfWeek - CurrentSystemTime.wDayOfWeek;
523 TRACE("wTempOffset: %hu\n", wTempOffset);
524 }
525
526 /* Use the smallest offset */
527 if (bDaysOffsetValid == FALSE)
528 {
529 wDaysOffset = wTempOffset;
530 bDaysOffsetValid = TRUE;
531 }
532 else
533 {
534 if (wTempOffset < wDaysOffset)
535 wDaysOffset = wTempOffset;
536 }
537 }
538 }
539 }
540
541 if (pJob->DaysOfMonth != 0)
542 {
543 FIXME("Support DaysOfMonth!\n");
544 for (i = 0; i < 31; i++)
545 {
546 if (pJob->DaysOfMonth & (1 << i))
547 {
548 /* Adjust the range */
549 wJobDayOfMonth = i + 1;
550 FIXME("wJobDayOfMonth: %hu\n", wJobDayOfMonth);
551 FIXME("CurrentSystemTime.wDay: %hu\n", CurrentSystemTime.wDay);
552
553 if ((CurrentSystemTime.wDay > wJobDayOfMonth) ||
554 ((CurrentSystemTime.wDay == wJobDayOfMonth) && (CurrentTimeMs >= pJob->JobTime)))
555 {
556 wTempOffset = DaysOfMonth(CurrentSystemTime.wMonth, CurrentSystemTime.wYear) -
557 CurrentSystemTime.wDay + wJobDayOfMonth;
558 FIXME("wTempOffset: %hu\n", wTempOffset);
559 }
560 else
561 {
562 wTempOffset = wJobDayOfMonth - CurrentSystemTime.wDay;
563 FIXME("wTempOffset: %hu\n", wTempOffset);
564 }
565
566 /* Use the smallest offset */
567 if (bDaysOffsetValid == FALSE)
568 {
569 wDaysOffset = wTempOffset;
570 bDaysOffsetValid = TRUE;
571 }
572 else
573 {
574 if (wTempOffset < wDaysOffset)
575 wDaysOffset = wTempOffset;
576 }
577 }
578 }
579 }
580 }
581
582 TRACE("wDaysOffset: %hu\n", wDaysOffset);
583
584 CopyMemory(&StartSystemTime, &CurrentSystemTime, sizeof(SYSTEMTIME));
585
586 StartSystemTime.wMilliseconds = 0;
587 StartSystemTime.wSecond = 0;
588 StartSystemTime.wHour = (WORD)(pJob->JobTime / 3600000);
589 StartSystemTime.wMinute = (WORD)((pJob->JobTime % 3600000) / 60000);
590
591 SystemTimeToFileTime(&StartSystemTime, &StartFileTime);
592
593 LocalStartTime.u.LowPart = StartFileTime.dwLowDateTime;
594 LocalStartTime.u.HighPart = StartFileTime.dwHighDateTime;
595 if (bDaysOffsetValid && wDaysOffset != 0)
596 {
597 LocalStartTime.QuadPart += ((ULONGLONG)wDaysOffset * 24 * 60 * 60 * 10000);
598 }
599
600 pJob->StartTime.dwLowDateTime = LocalStartTime.u.LowPart;
601 pJob->StartTime.dwHighDateTime = LocalStartTime.u.HighPart;
602 }
603
604 #if 0
605 VOID
606 DumpStartList(
607 _In_ PLIST_ENTRY StartListHead)
608 {
609 PLIST_ENTRY CurrentEntry;
610 PJOB CurrentJob;
611
612 CurrentEntry = JobListHead->Flink;
613 while (CurrentEntry != &JobListHead)
614 {
615 CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry);
616
617 TRACE("%3lu: %016I64x\n", CurrentJob->JobId, CurrentJob->StartTime.QuadPart);
618
619 CurrentEntry = CurrentEntry->Flink;
620 }
621 }
622 #endif
623 /* EOF */
624