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 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 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 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 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 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 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 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 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