[SCHEDSVC] Improvements to the scheduler service
[reactos.git] / base / services / schedsvc / job.c
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
35
36 /* FUNCTIONS *****************************************************************/
37
38 DWORD
39 GetNextJobTimeout(VOID)
40 {
41 FILETIME FileTime;
42 SYSTEMTIME SystemTime;
43 ULARGE_INTEGER CurrentTime, Timeout;
44 PJOB pNextJob;
45
46 if (IsListEmpty(&StartListHead))
47 {
48 TRACE("No job in list! Wait until next update.\n");
49 return INFINITE;
50 }
51
52 pNextJob = CONTAINING_RECORD((&StartListHead)->Flink, JOB, StartEntry);
53
54 FileTime.dwLowDateTime = pNextJob->StartTime.u.LowPart;
55 FileTime.dwHighDateTime = pNextJob->StartTime.u.HighPart;
56 FileTimeToSystemTime(&FileTime, &SystemTime);
57
58 TRACE("Start next job (%lu) at %02hu:%02hu %02hu.%02hu.%hu\n",
59 pNextJob->JobId, SystemTime.wHour, SystemTime.wMinute,
60 SystemTime.wDay, SystemTime.wMonth, SystemTime.wYear);
61
62 GetLocalTime(&SystemTime);
63 SystemTimeToFileTime(&SystemTime, &FileTime);
64
65 CurrentTime.u.LowPart = FileTime.dwLowDateTime;
66 CurrentTime.u.HighPart = FileTime.dwHighDateTime;
67
68 if (CurrentTime.QuadPart >= pNextJob->StartTime.QuadPart)
69 {
70 TRACE("Next event has already gone by!\n");
71 return 0;
72 }
73
74 Timeout.QuadPart = (pNextJob->StartTime.QuadPart - CurrentTime.QuadPart) / 10000;
75 if (Timeout.u.HighPart != 0)
76 {
77 TRACE("Event happens too far in the future!\n");
78 return INFINITE;
79 }
80
81 TRACE("Timeout: %lu\n", Timeout.u.LowPart);
82 return Timeout.u.LowPart;
83 }
84
85
86 static
87 VOID
88 GetJobName(
89 HKEY hJobsKey,
90 PWSTR pszJobName)
91 {
92 WCHAR szNameBuffer[JOB_NAME_LENGTH];
93 FILETIME SystemTime;
94 ULONG ulSeed, ulValue;
95 HKEY hKey;
96 LONG lError;
97
98 GetSystemTimeAsFileTime(&SystemTime);
99 ulSeed = SystemTime.dwLowDateTime;
100 for (;;)
101 {
102 ulValue = RtlRandomEx(&ulSeed);
103 swprintf(szNameBuffer, L"%08lx", ulValue);
104
105 hKey = NULL;
106 lError = RegOpenKeyEx(hJobsKey,
107 szNameBuffer,
108 0,
109 KEY_READ,
110 &hKey);
111 if (lError != ERROR_SUCCESS)
112 {
113 wcscpy(pszJobName, szNameBuffer);
114 return;
115 }
116
117 RegCloseKey(hKey);
118 }
119 }
120
121
122 LONG
123 SaveJob(
124 _In_ PJOB pJob)
125 {
126 SCHEDULE Schedule;
127 HKEY hJobsKey = NULL, hJobKey = NULL;
128 LONG lError;
129
130 TRACE("SaveJob()\n");
131
132 lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
133 L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
134 0,
135 NULL,
136 REG_OPTION_NON_VOLATILE,
137 KEY_WRITE,
138 NULL,
139 &hJobsKey,
140 NULL);
141 if (lError != ERROR_SUCCESS)
142 goto done;
143
144 GetJobName(hJobsKey, pJob->Name);
145
146 lError = RegCreateKeyExW(hJobsKey,
147 pJob->Name,
148 0,
149 NULL,
150 REG_OPTION_NON_VOLATILE,
151 KEY_WRITE,
152 NULL,
153 &hJobKey,
154 NULL);
155 if (lError != ERROR_SUCCESS)
156 goto done;
157
158 Schedule.JobTime = pJob->JobTime;
159 Schedule.DaysOfMonth = pJob->DaysOfMonth;
160 Schedule.DaysOfWeek = pJob->DaysOfWeek;
161 Schedule.Flags = pJob->Flags;
162
163 lError = RegSetValueEx(hJobKey,
164 L"Schedule",
165 0,
166 REG_BINARY,
167 (PBYTE)&Schedule,
168 sizeof(Schedule));
169 if (lError != ERROR_SUCCESS)
170 goto done;
171
172 lError = RegSetValueEx(hJobKey,
173 L"Command",
174 0,
175 REG_SZ,
176 (PBYTE)pJob->Command,
177 (wcslen(pJob->Command) + 1) * sizeof(WCHAR));
178 if (lError != ERROR_SUCCESS)
179 goto done;
180
181 done:
182 if (hJobKey != NULL)
183 RegCloseKey(hJobKey);
184
185 if (hJobsKey != NULL)
186 RegCloseKey(hJobsKey);
187
188 return lError;
189 }
190
191
192 LONG
193 DeleteJob(
194 _In_ PJOB pJob)
195 {
196 HKEY hJobsKey = NULL;
197 LONG lError;
198
199 TRACE("DeleteJob()\n");
200
201 lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
202 L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
203 0,
204 NULL,
205 REG_OPTION_NON_VOLATILE,
206 KEY_WRITE,
207 NULL,
208 &hJobsKey,
209 NULL);
210 if (lError != ERROR_SUCCESS)
211 goto done;
212
213 lError = RegDeleteKey(hJobsKey,
214 pJob->Name);
215 if (lError != ERROR_SUCCESS)
216 goto done;
217
218 done:
219 if (hJobsKey != NULL)
220 RegCloseKey(hJobsKey);
221
222 return lError;
223 }
224
225
226 LONG
227 LoadJobs(VOID)
228 {
229 SCHEDULE Schedule;
230 WCHAR szNameBuffer[JOB_NAME_LENGTH];
231 DWORD dwNameLength, dwIndex, dwSize;
232 HKEY hJobsKey = NULL, hJobKey = NULL;
233 PJOB pJob = NULL;
234 LONG lError;
235
236 TRACE("LoadJobs()\n");
237
238 lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
239 L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
240 0,
241 NULL,
242 REG_OPTION_NON_VOLATILE,
243 KEY_READ,
244 NULL,
245 &hJobsKey,
246 NULL);
247 if (lError != ERROR_SUCCESS)
248 goto done;
249
250 for (dwIndex = 0; dwIndex < 1000; dwIndex++)
251 {
252 dwNameLength = JOB_NAME_LENGTH;
253 lError = RegEnumKeyEx(hJobsKey,
254 dwIndex,
255 szNameBuffer,
256 &dwNameLength,
257 NULL,
258 NULL,
259 NULL,
260 NULL);
261 if (lError != ERROR_SUCCESS)
262 {
263 lError = ERROR_SUCCESS;
264 break;
265 }
266
267 TRACE("KeyName: %S\n", szNameBuffer);
268
269 lError = RegOpenKeyEx(hJobsKey,
270 szNameBuffer,
271 0,
272 KEY_READ,
273 &hJobKey);
274 if (lError != ERROR_SUCCESS)
275 break;
276
277 dwSize = sizeof(SCHEDULE);
278 lError = RegQueryValueEx(hJobKey,
279 L"Schedule",
280 NULL,
281 NULL,
282 (PBYTE)&Schedule,
283 &dwSize);
284 if (lError == ERROR_SUCCESS)
285 {
286 dwSize = 0;
287 RegQueryValueEx(hJobKey,
288 L"Command",
289 NULL,
290 NULL,
291 NULL,
292 &dwSize);
293 if (dwSize != 0)
294 {
295 /* Allocate a new job object */
296 pJob = HeapAlloc(GetProcessHeap(),
297 HEAP_ZERO_MEMORY,
298 sizeof(JOB) + dwSize - sizeof(WCHAR));
299 if (pJob == NULL)
300 {
301 lError = ERROR_OUTOFMEMORY;
302 break;
303 }
304
305 lError = RegQueryValueEx(hJobKey,
306 L"Command",
307 NULL,
308 NULL,
309 (PBYTE)pJob->Command,
310 &dwSize);
311 if (lError != ERROR_SUCCESS)
312 break;
313
314 wcscpy(pJob->Name, szNameBuffer);
315 pJob->JobTime = Schedule.JobTime;
316 pJob->DaysOfMonth = Schedule.DaysOfMonth;
317 pJob->DaysOfWeek = Schedule.DaysOfWeek;
318 pJob->Flags = Schedule.Flags;
319
320 /* Acquire the job list lock exclusively */
321 RtlAcquireResourceExclusive(&JobListLock, TRUE);
322
323 /* Assign a new job ID */
324 pJob->JobId = dwNextJobId++;
325 dwJobCount++;
326
327 /* Append the new job to the job list */
328 InsertTailList(&JobListHead, &pJob->JobEntry);
329
330 /* Calculate the next start time */
331 CalculateNextStartTime(pJob);
332
333 /* Insert the job into the start list */
334 InsertJobIntoStartList(&StartListHead, pJob);
335 #if 0
336 DumpStartList(&StartListHead);
337 #endif
338
339 /* Release the job list lock */
340 RtlReleaseResource(&JobListLock);
341
342 pJob = NULL;
343 }
344 }
345
346 RegCloseKey(hJobKey);
347 hJobKey = NULL;
348 }
349
350 done:
351 if (pJob != NULL)
352 HeapFree(GetProcessHeap(), 0, pJob);
353
354 if (hJobKey != NULL)
355 RegCloseKey(hJobKey);
356
357 if (hJobsKey != NULL)
358 RegCloseKey(hJobsKey);
359
360 return lError;
361 }
362
363
364 static
365 WORD
366 DaysOfMonth(
367 WORD wMonth,
368 WORD wYear)
369 {
370 WORD wDaysArray[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
371
372 if (wMonth == 2 && wYear % 4 == 0 && wYear % 400 != 0)
373 return 29;
374
375 return wDaysArray[wMonth];
376 }
377
378
379 VOID
380 CalculateNextStartTime(
381 _In_ PJOB pJob)
382 {
383 SYSTEMTIME StartTime;
384 FILETIME FileTime;
385 DWORD_PTR Now;
386
387 TRACE("CalculateNextStartTime(%p)\n", pJob);
388
389 GetLocalTime(&StartTime);
390
391 Now = (DWORD_PTR)StartTime.wHour * 3600000 +
392 (DWORD_PTR)StartTime.wMinute * 60000;
393
394 StartTime.wMilliseconds = 0;
395 StartTime.wSecond = 0;
396 StartTime.wHour = (WORD)(pJob->JobTime / 3600000);
397 StartTime.wMinute = (WORD)((pJob->JobTime % 3600000) / 60000);
398
399 /* Start the job tomorrow */
400 if (Now > pJob->JobTime)
401 {
402 if (StartTime.wDay + 1 > DaysOfMonth(StartTime.wMonth, StartTime.wYear))
403 {
404 if (StartTime.wMonth == 12)
405 {
406 StartTime.wDay = 1;
407 StartTime.wMonth = 1;
408 StartTime.wYear++;
409 }
410 else
411 {
412 StartTime.wDay = 1;
413 StartTime.wMonth++;
414 }
415 }
416 else
417 {
418 StartTime.wDay++;
419 }
420 }
421
422 TRACE("Next start: %02hu:%02hu %02hu.%02hu.%hu\n", StartTime.wHour,
423 StartTime.wMinute, StartTime.wDay, StartTime.wMonth, StartTime.wYear);
424
425 SystemTimeToFileTime(&StartTime, &FileTime);
426 pJob->StartTime.u.LowPart = FileTime.dwLowDateTime;
427 pJob->StartTime.u.HighPart = FileTime.dwHighDateTime;
428 }
429
430
431 VOID
432 InsertJobIntoStartList(
433 _In_ PLIST_ENTRY StartListHead,
434 _In_ PJOB pJob)
435 {
436 PLIST_ENTRY CurrentEntry, PreviousEntry;
437 PJOB CurrentJob;
438
439 if (IsListEmpty(StartListHead))
440 {
441 InsertHeadList(StartListHead, &pJob->StartEntry);
442 return;
443 }
444
445 CurrentEntry = StartListHead->Flink;
446 while (CurrentEntry != StartListHead)
447 {
448 CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry);
449
450 if ((CurrentEntry == StartListHead->Flink) &&
451 (pJob->StartTime.QuadPart < CurrentJob->StartTime.QuadPart))
452 {
453 /* Insert before the first entry */
454 InsertHeadList(StartListHead, &pJob->StartEntry);
455 return;
456 }
457
458 if (pJob->StartTime.QuadPart < CurrentJob->StartTime.QuadPart)
459 {
460 /* Insert between the previous and the current entry */
461 PreviousEntry = CurrentEntry->Blink;
462 pJob->StartEntry.Blink = PreviousEntry;
463 pJob->StartEntry.Flink = CurrentEntry;
464 PreviousEntry->Flink = &pJob->StartEntry;
465 CurrentEntry->Blink = &pJob->StartEntry;
466 return;
467 }
468
469 if ((CurrentEntry->Flink == StartListHead) &&
470 (pJob->StartTime.QuadPart >= CurrentJob->StartTime.QuadPart))
471 {
472 /* Insert after the last entry */
473 InsertTailList(StartListHead, &pJob->StartEntry);
474 return;
475 }
476
477 CurrentEntry = CurrentEntry->Flink;
478 }
479 }
480
481
482 VOID
483 DumpStartList(
484 _In_ PLIST_ENTRY StartListHead)
485 {
486 PLIST_ENTRY CurrentEntry;
487 PJOB CurrentJob;
488
489 CurrentEntry = StartListHead->Flink;
490 while (CurrentEntry != StartListHead)
491 {
492 CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry);
493
494 TRACE("%3lu: %016I64x\n", CurrentJob->JobId, CurrentJob->StartTime.QuadPart);
495
496 CurrentEntry = CurrentEntry->Flink;
497 }
498 }
499
500 /* EOF */