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