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 ReScheduleJob( 89 PJOB pJob) 90 { 91 /* Remove the job from the start list */ 92 RemoveEntryList(&pJob->StartEntry); 93 94 /* Non-periodical job, remove it */ 95 if ((pJob->Flags & JOB_RUN_PERIODICALLY) == 0) 96 { 97 /* Remove the job from the registry */ 98 DeleteJob(pJob); 99 100 /* Remove the job from the job list */ 101 RemoveEntryList(&pJob->JobEntry); 102 dwJobCount--; 103 104 /* Free the job object */ 105 HeapFree(GetProcessHeap(), 0, pJob); 106 return; 107 } 108 109 /* Calculate the next start time */ 110 CalculateNextStartTime(pJob); 111 112 /* Insert the job into the start list again */ 113 InsertJobIntoStartList(&StartListHead, pJob); 114 #if 0 115 DumpStartList(&StartListHead); 116 #endif 117 } 118 119 120 VOID 121 RunNextJob(VOID) 122 { 123 PROCESS_INFORMATION ProcessInformation; 124 STARTUPINFOW StartupInfo; 125 BOOL bRet; 126 PJOB pNextJob; 127 128 if (IsListEmpty(&StartListHead)) 129 { 130 ERR("No job in list!\n"); 131 return; 132 } 133 134 pNextJob = CONTAINING_RECORD((&StartListHead)->Flink, JOB, StartEntry); 135 136 TRACE("Run job %ld: %S\n", pNextJob->JobId, pNextJob->Command); 137 138 ZeroMemory(&StartupInfo, sizeof(StartupInfo)); 139 StartupInfo.cb = sizeof(StartupInfo); 140 StartupInfo.lpTitle = pNextJob->Command; 141 StartupInfo.dwFlags = STARTF_USESHOWWINDOW; 142 StartupInfo.wShowWindow = SW_SHOWDEFAULT; 143 144 if ((pNextJob->Flags & JOB_NONINTERACTIVE) == 0) 145 { 146 StartupInfo.dwFlags |= STARTF_INHERITDESKTOP; 147 StartupInfo.lpDesktop = L"WinSta0\\Default"; 148 } 149 150 bRet = CreateProcessW(NULL, 151 pNextJob->Command, 152 NULL, 153 NULL, 154 FALSE, 155 CREATE_NEW_CONSOLE, 156 NULL, 157 NULL, 158 &StartupInfo, 159 &ProcessInformation); 160 if (bRet == FALSE) 161 { 162 ERR("CreateProcessW() failed (Error %lu)\n", GetLastError()); 163 } 164 else 165 { 166 CloseHandle(ProcessInformation.hThread); 167 CloseHandle(ProcessInformation.hProcess); 168 } 169 170 ReScheduleJob(pNextJob); 171 } 172 173 174 static 175 VOID 176 GetJobName( 177 HKEY hJobsKey, 178 PWSTR pszJobName) 179 { 180 WCHAR szNameBuffer[JOB_NAME_LENGTH]; 181 FILETIME SystemTime; 182 ULONG ulSeed, ulValue; 183 HKEY hKey; 184 LONG lError; 185 186 GetSystemTimeAsFileTime(&SystemTime); 187 ulSeed = SystemTime.dwLowDateTime; 188 for (;;) 189 { 190 ulValue = RtlRandomEx(&ulSeed); 191 swprintf(szNameBuffer, L"%08lx", ulValue); 192 193 hKey = NULL; 194 lError = RegOpenKeyEx(hJobsKey, 195 szNameBuffer, 196 0, 197 KEY_READ, 198 &hKey); 199 if (lError != ERROR_SUCCESS) 200 { 201 wcscpy(pszJobName, szNameBuffer); 202 return; 203 } 204 205 RegCloseKey(hKey); 206 } 207 } 208 209 210 LONG 211 SaveJob( 212 _In_ PJOB pJob) 213 { 214 SCHEDULE Schedule; 215 HKEY hJobsKey = NULL, hJobKey = NULL; 216 LONG lError; 217 218 TRACE("SaveJob()\n"); 219 220 lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE, 221 L"System\\CurrentControlSet\\Services\\Schedule\\Jobs", 222 0, 223 NULL, 224 REG_OPTION_NON_VOLATILE, 225 KEY_WRITE, 226 NULL, 227 &hJobsKey, 228 NULL); 229 if (lError != ERROR_SUCCESS) 230 goto done; 231 232 GetJobName(hJobsKey, pJob->Name); 233 234 lError = RegCreateKeyExW(hJobsKey, 235 pJob->Name, 236 0, 237 NULL, 238 REG_OPTION_NON_VOLATILE, 239 KEY_WRITE, 240 NULL, 241 &hJobKey, 242 NULL); 243 if (lError != ERROR_SUCCESS) 244 goto done; 245 246 Schedule.JobTime = pJob->JobTime; 247 Schedule.DaysOfMonth = pJob->DaysOfMonth; 248 Schedule.DaysOfWeek = pJob->DaysOfWeek; 249 Schedule.Flags = pJob->Flags; 250 251 lError = RegSetValueEx(hJobKey, 252 L"Schedule", 253 0, 254 REG_BINARY, 255 (PBYTE)&Schedule, 256 sizeof(Schedule)); 257 if (lError != ERROR_SUCCESS) 258 goto done; 259 260 lError = RegSetValueEx(hJobKey, 261 L"Command", 262 0, 263 REG_SZ, 264 (PBYTE)pJob->Command, 265 (wcslen(pJob->Command) + 1) * sizeof(WCHAR)); 266 if (lError != ERROR_SUCCESS) 267 goto done; 268 269 done: 270 if (hJobKey != NULL) 271 RegCloseKey(hJobKey); 272 273 if (hJobsKey != NULL) 274 RegCloseKey(hJobsKey); 275 276 return lError; 277 } 278 279 280 LONG 281 DeleteJob( 282 _In_ PJOB pJob) 283 { 284 HKEY hJobsKey = NULL; 285 LONG lError; 286 287 TRACE("DeleteJob()\n"); 288 289 lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE, 290 L"System\\CurrentControlSet\\Services\\Schedule\\Jobs", 291 0, 292 NULL, 293 REG_OPTION_NON_VOLATILE, 294 KEY_WRITE, 295 NULL, 296 &hJobsKey, 297 NULL); 298 if (lError != ERROR_SUCCESS) 299 goto done; 300 301 lError = RegDeleteKey(hJobsKey, 302 pJob->Name); 303 if (lError != ERROR_SUCCESS) 304 goto done; 305 306 done: 307 if (hJobsKey != NULL) 308 RegCloseKey(hJobsKey); 309 310 return lError; 311 } 312 313 314 LONG 315 LoadJobs(VOID) 316 { 317 SCHEDULE Schedule; 318 WCHAR szNameBuffer[JOB_NAME_LENGTH]; 319 DWORD dwNameLength, dwIndex, dwSize; 320 HKEY hJobsKey = NULL, hJobKey = NULL; 321 PJOB pJob = NULL; 322 LONG lError; 323 324 TRACE("LoadJobs()\n"); 325 326 lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE, 327 L"System\\CurrentControlSet\\Services\\Schedule\\Jobs", 328 0, 329 NULL, 330 REG_OPTION_NON_VOLATILE, 331 KEY_READ, 332 NULL, 333 &hJobsKey, 334 NULL); 335 if (lError != ERROR_SUCCESS) 336 goto done; 337 338 for (dwIndex = 0; dwIndex < 1000; dwIndex++) 339 { 340 dwNameLength = JOB_NAME_LENGTH; 341 lError = RegEnumKeyEx(hJobsKey, 342 dwIndex, 343 szNameBuffer, 344 &dwNameLength, 345 NULL, 346 NULL, 347 NULL, 348 NULL); 349 if (lError != ERROR_SUCCESS) 350 { 351 lError = ERROR_SUCCESS; 352 break; 353 } 354 355 TRACE("KeyName: %S\n", szNameBuffer); 356 357 lError = RegOpenKeyEx(hJobsKey, 358 szNameBuffer, 359 0, 360 KEY_READ, 361 &hJobKey); 362 if (lError != ERROR_SUCCESS) 363 break; 364 365 dwSize = sizeof(SCHEDULE); 366 lError = RegQueryValueEx(hJobKey, 367 L"Schedule", 368 NULL, 369 NULL, 370 (PBYTE)&Schedule, 371 &dwSize); 372 if (lError == ERROR_SUCCESS) 373 { 374 dwSize = 0; 375 RegQueryValueEx(hJobKey, 376 L"Command", 377 NULL, 378 NULL, 379 NULL, 380 &dwSize); 381 if (dwSize != 0) 382 { 383 /* Allocate a new job object */ 384 pJob = HeapAlloc(GetProcessHeap(), 385 HEAP_ZERO_MEMORY, 386 sizeof(JOB) + dwSize - sizeof(WCHAR)); 387 if (pJob == NULL) 388 { 389 lError = ERROR_OUTOFMEMORY; 390 break; 391 } 392 393 lError = RegQueryValueEx(hJobKey, 394 L"Command", 395 NULL, 396 NULL, 397 (PBYTE)pJob->Command, 398 &dwSize); 399 if (lError != ERROR_SUCCESS) 400 break; 401 402 wcscpy(pJob->Name, szNameBuffer); 403 pJob->JobTime = Schedule.JobTime; 404 pJob->DaysOfMonth = Schedule.DaysOfMonth; 405 pJob->DaysOfWeek = Schedule.DaysOfWeek; 406 pJob->Flags = Schedule.Flags; 407 408 /* Acquire the job list lock exclusively */ 409 RtlAcquireResourceExclusive(&JobListLock, TRUE); 410 411 /* Assign a new job ID */ 412 pJob->JobId = dwNextJobId++; 413 dwJobCount++; 414 415 /* Append the new job to the job list */ 416 InsertTailList(&JobListHead, &pJob->JobEntry); 417 418 /* Calculate the next start time */ 419 CalculateNextStartTime(pJob); 420 421 /* Insert the job into the start list */ 422 InsertJobIntoStartList(&StartListHead, pJob); 423 #if 0 424 DumpStartList(&StartListHead); 425 #endif 426 427 /* Release the job list lock */ 428 RtlReleaseResource(&JobListLock); 429 430 pJob = NULL; 431 } 432 } 433 434 RegCloseKey(hJobKey); 435 hJobKey = NULL; 436 } 437 438 done: 439 if (pJob != NULL) 440 HeapFree(GetProcessHeap(), 0, pJob); 441 442 if (hJobKey != NULL) 443 RegCloseKey(hJobKey); 444 445 if (hJobsKey != NULL) 446 RegCloseKey(hJobsKey); 447 448 return lError; 449 } 450 451 452 static 453 WORD 454 DaysOfMonth( 455 WORD wMonth, 456 WORD wYear) 457 { 458 WORD wDaysArray[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 459 460 if (wMonth == 2 && wYear % 4 == 0 && wYear % 400 != 0) 461 return 29; 462 463 return wDaysArray[wMonth]; 464 } 465 466 467 VOID 468 CalculateNextStartTime( 469 _In_ PJOB pJob) 470 { 471 SYSTEMTIME StartTime; 472 FILETIME FileTime; 473 DWORD_PTR Now; 474 475 TRACE("CalculateNextStartTime(%p)\n", pJob); 476 477 GetLocalTime(&StartTime); 478 479 Now = (DWORD_PTR)StartTime.wHour * 3600000 + 480 (DWORD_PTR)StartTime.wMinute * 60000; 481 482 StartTime.wMilliseconds = 0; 483 StartTime.wSecond = 0; 484 StartTime.wHour = (WORD)(pJob->JobTime / 3600000); 485 StartTime.wMinute = (WORD)((pJob->JobTime % 3600000) / 60000); 486 487 if (pJob->DaysOfMonth != 0) 488 { 489 FIXME("Support DaysOfMonth!\n"); 490 } 491 else if (pJob->DaysOfWeek != 0) 492 { 493 FIXME("Support DaysOfWeek!\n"); 494 } 495 else 496 { 497 /* Start the job tomorrow */ 498 if (Now > pJob->JobTime) 499 { 500 if (StartTime.wDay + 1 > DaysOfMonth(StartTime.wMonth, StartTime.wYear)) 501 { 502 if (StartTime.wMonth == 12) 503 { 504 StartTime.wDay = 1; 505 StartTime.wMonth = 1; 506 StartTime.wYear++; 507 } 508 else 509 { 510 StartTime.wDay = 1; 511 StartTime.wMonth++; 512 } 513 } 514 else 515 { 516 StartTime.wDay++; 517 } 518 } 519 } 520 521 TRACE("Next start: %02hu:%02hu %02hu.%02hu.%hu\n", StartTime.wHour, 522 StartTime.wMinute, StartTime.wDay, StartTime.wMonth, StartTime.wYear); 523 524 SystemTimeToFileTime(&StartTime, &FileTime); 525 pJob->StartTime.u.LowPart = FileTime.dwLowDateTime; 526 pJob->StartTime.u.HighPart = FileTime.dwHighDateTime; 527 } 528 529 530 VOID 531 InsertJobIntoStartList( 532 _In_ PLIST_ENTRY StartListHead, 533 _In_ PJOB pJob) 534 { 535 PLIST_ENTRY CurrentEntry, PreviousEntry; 536 PJOB CurrentJob; 537 538 if (IsListEmpty(StartListHead)) 539 { 540 InsertHeadList(StartListHead, &pJob->StartEntry); 541 return; 542 } 543 544 CurrentEntry = StartListHead->Flink; 545 while (CurrentEntry != StartListHead) 546 { 547 CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry); 548 549 if ((CurrentEntry == StartListHead->Flink) && 550 (pJob->StartTime.QuadPart < CurrentJob->StartTime.QuadPart)) 551 { 552 /* Insert before the first entry */ 553 InsertHeadList(StartListHead, &pJob->StartEntry); 554 return; 555 } 556 557 if (pJob->StartTime.QuadPart < CurrentJob->StartTime.QuadPart) 558 { 559 /* Insert between the previous and the current entry */ 560 PreviousEntry = CurrentEntry->Blink; 561 pJob->StartEntry.Blink = PreviousEntry; 562 pJob->StartEntry.Flink = CurrentEntry; 563 PreviousEntry->Flink = &pJob->StartEntry; 564 CurrentEntry->Blink = &pJob->StartEntry; 565 return; 566 } 567 568 if ((CurrentEntry->Flink == StartListHead) && 569 (pJob->StartTime.QuadPart >= CurrentJob->StartTime.QuadPart)) 570 { 571 /* Insert after the last entry */ 572 InsertTailList(StartListHead, &pJob->StartEntry); 573 return; 574 } 575 576 CurrentEntry = CurrentEntry->Flink; 577 } 578 } 579 580 581 VOID 582 DumpStartList( 583 _In_ PLIST_ENTRY StartListHead) 584 { 585 PLIST_ENTRY CurrentEntry; 586 PJOB CurrentJob; 587 588 CurrentEntry = StartListHead->Flink; 589 while (CurrentEntry != StartListHead) 590 { 591 CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry); 592 593 TRACE("%3lu: %016I64x\n", CurrentJob->JobId, CurrentJob->StartTime.QuadPart); 594 595 CurrentEntry = CurrentEntry->Flink; 596 } 597 } 598 599 /* EOF */ 600