1/* 2 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23/* 24 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. 25 * 26 * HISTORY 27 * 28 * 30-Jan-03 ebold created 29 * 30 */ 31 32#include <syslog.h> 33#include <bsm/libbsm.h> 34#include "PrivateLib.h" 35#include "AutoWakeScheduler.h" 36#include "RepeatingAutoWake.h" 37#include "PMAssertions.h" 38 39enum { 40 kIOWakeTimer = 0, 41 kIOPowerOnTimer = 1, 42 kIOSleepTimer = 2, 43 kIOShutdownTimer = 3 44}; 45 46enum { 47 kIOPMMaxScheduledEntries = 1000 48}; 49 50#if TARGET_OS_EMBEDDED 51static CFAbsoluteTime gMinScheduleTime = 5.0; 52#define MIN_SCHEDULE_TIME (gMinScheduleTime) 53#else 54#define MIN_SCHEDULE_TIME (0.0) 55#endif 56 57typedef void (*powerEventCallout)(CFDictionaryRef); 58 59/* 60 * We use one PowerEventBehavior struct per-type of schedule power event 61 * sleep/wake/power/shutdown/wakeORpower/restart. 62 * The struct contains special behavior per-type. 63 */ 64struct PowerEventBehavior { 65 // These values change to reflect the state of current 66 // and upcoming power events 67 CFMutableArrayRef array; 68 CFDictionaryRef currentEvent; 69 CFRunLoopTimerRef timer; 70 71 CFStringRef title; 72 73 // wake and poweron sharedEvents pointer points to wakeorpoweron struct 74 struct PowerEventBehavior *sharedEvents; 75 76 // Callouts will be defined at startup time and not modified after that 77 powerEventCallout timerExpirationCallout; 78 powerEventCallout scheduleNextCallout; 79 powerEventCallout noScheduledEventCallout; 80}; 81typedef struct PowerEventBehavior PowerEventBehavior; 82 83/* 84 * Global structs tracking behaviors & current state 85 */ 86PowerEventBehavior sleepBehavior; 87PowerEventBehavior shutdownBehavior; 88PowerEventBehavior restartBehavior; 89PowerEventBehavior wakeBehavior; 90PowerEventBehavior poweronBehavior; 91PowerEventBehavior wakeorpoweronBehavior; 92 93static uint32_t activeEventCnt = 0; 94enum { 95 kBehaviorsCount = 6 96}; 97 98/* 99 * Stick pointers to them in an array for safekeeping 100 */ 101PowerEventBehavior *behaviors[] = 102{ 103 &sleepBehavior, 104 &shutdownBehavior, 105 &restartBehavior, 106 &wakeBehavior, 107 &poweronBehavior, 108 &wakeorpoweronBehavior 109}; 110 111/* 112 * forwards 113 */ 114static bool isEntryValidAndFuturistic(CFDictionaryRef, CFDateRef); 115static void schedulePowerEvent(PowerEventBehavior *); 116static bool purgePastEvents(PowerEventBehavior *); 117static void copyScheduledPowerChangeArrays(void); 118static CFDictionaryRef copyEarliestUpcoming(PowerEventBehavior *); 119static CFDateRef _getScheduledEventDate(CFDictionaryRef); 120static CFArrayRef copyMergedEventArray(PowerEventBehavior *, 121 PowerEventBehavior *); 122static CFComparisonResult compareEvDates(CFDictionaryRef, 123 CFDictionaryRef, void *); 124 125void poweronScheduleCallout(CFDictionaryRef); 126 127void wakeTimerExpiredCallout(CFDictionaryRef); 128void sleepTimerExpiredCallout(CFDictionaryRef); 129void shutdownTimerExpiredCallout(CFDictionaryRef); 130void restartTimerExpiredCallout(CFDictionaryRef); 131 132/* AutoWakeScheduler overview 133 * 134 * PURPOSE 135 * Set wake and power on timers to automatically power on the machine at a 136 * user/app requested time. 137 * Requests come via IOKit/pwr_mgt/IOPMLib.h:IOPMSchedulePowerEvent() 138 * 139 * We schedule requests to the responsible kernel driver by calling setProperties on IOPMrootDomain. 140 * The IOPMrootDomain kernel entity routes all requests to the appropriate 141 * controller. 142 * 143 * POWER ON 144 * Every time we set a power on time, we also start a software timer to fire at the same time. 145 * (in scheduleShutdownTime()) 146 * If the software timer fires, then the machine was not powered off and we should find the 147 * next power on date and schedule that. (in handleTimerPowerOnReset()) 148 * If the machine is powered off, the software timer won't fire and the timer hardware will power the machine. 149 * 150 * WAKE 151 * Wake is simpler than power on, since we get a notification on the way to sleep. 152 * At going-to-sleep time we scan the wake_arr CFArray for the next upcoming 153 * wakeup time (in AutoWakeSleepWakeNotification()) 154 * 155 * LOADING NEW AUTOWAKEUP TIMES 156 * Via SCPreferences notifications 157 * 158 * PURGING OLD TIMES 159 * In memory events with old timestamo gets purged at boot, at wakeup and when new event is added. 160 * But, we don't update on-disk contents unless a new event is being added or existing event is deleted 161 * thru IOKit. 162 * So to minimize disk access we only purge when we think the disk is "up" anyway. 163 */ 164#pragma mark - 165#pragma mark AutoWakeScheduler 166 167/* 168 * Deletes events with specific appName in the given behavior array. 169 * 170 * The event array pointer gets modified. 171 */ 172static void 173removeEventsByAppName(PowerEventBehavior *behave, CFStringRef appName) 174{ 175 CFIndex count, j; 176 CFDictionaryRef cancelee = 0; 177 178 179 if ( (behave->array == NULL) || 180 (count = CFArrayGetCount(behave->array)) == 0) 181 return; 182 for (j = count-1; j >= 0; j--) 183 { 184 cancelee = CFArrayGetValueAtIndex(behave->array, j); 185 if( CFEqual( 186 CFDictionaryGetValue(cancelee, CFSTR(kIOPMPowerEventAppNameKey)), appName 187 )) 188 { 189 // This is the one to cancel 190 if (behave->currentEvent && CFEqual(cancelee, behave->currentEvent)) { 191 CFRelease(behave->currentEvent); 192 behave->currentEvent = NULL; 193 } 194 195 CFArrayRemoveValueAtIndex(behave->array, j); 196 activeEventCnt--; 197 } 198 } 199 200} 201 202 203__private_extern__ void 204AutoWake_prime(void) 205{ 206 PowerEventBehavior *this_behavior; 207 int i; 208 209 // clear out behavior structs for good measure 210 for(i=0; i<kBehaviorsCount; i++) 211 { 212 this_behavior = behaviors[i]; 213 bzero(this_behavior, sizeof(PowerEventBehavior)); 214 } 215 216 wakeBehavior.title = CFSTR(kIOPMAutoWake); 217 poweronBehavior.title = CFSTR(kIOPMAutoPowerOn); 218 wakeorpoweronBehavior.title = CFSTR(kIOPMAutoWakeOrPowerOn); 219 sleepBehavior.title = CFSTR(kIOPMAutoSleep); 220 shutdownBehavior.title = CFSTR(kIOPMAutoShutdown); 221 restartBehavior.title = CFSTR(kIOPMAutoRestart); 222 223 // Initialize powerevent callouts per-behavior 224 // note: wakeorpoweronBehavior does not have callouts of its own, by design 225 wakeBehavior.timerExpirationCallout = wakeTimerExpiredCallout; 226 sleepBehavior.timerExpirationCallout = sleepTimerExpiredCallout; 227 shutdownBehavior.timerExpirationCallout = shutdownTimerExpiredCallout; 228 restartBehavior.timerExpirationCallout = restartTimerExpiredCallout; 229 230 // schedulePowerEvent callouts 231 poweronBehavior.scheduleNextCallout = poweronScheduleCallout; 232 poweronBehavior.noScheduledEventCallout = poweronScheduleCallout; 233 234 // Use this "sharedEvents" linkage to later merge wakeorpoweron events 235 // from wakeorpoweron into runtime wake and poweron queues. 236 wakeBehavior.sharedEvents = 237 poweronBehavior.sharedEvents = &wakeorpoweronBehavior; 238 239 240 // system bootup; read prefs from disk 241 copyScheduledPowerChangeArrays(); 242 243 RepeatingAutoWake_prime(); 244 245 for(i=0; i<kBehaviorsCount; i++) 246 { 247 this_behavior = behaviors[i]; 248 if(!this_behavior) continue; 249 250 // purge past wakeup and restart times 251 purgePastEvents(this_behavior); 252 253 // purge any repeat events in these arrays. 254 // Repeat events were saved into these arrays on disk previously 255 // We don't do it anymore 256 removeEventsByAppName(this_behavior, CFSTR(kIOPMRepeatingAppName)); 257 258 // schedule next power changes 259 if (!CFEqual(this_behavior->title, CFSTR(kIOPMAutoWakeOrPowerOn))) 260 schedulePowerEvent(this_behavior); 261 } 262 263 264 return; 265} 266 267/* 268 * Sleep/wake 269 * 270 */ 271 272__private_extern__ void AutoWakeCapabilitiesNotification( 273 IOPMSystemPowerStateCapabilities old_cap, 274 IOPMSystemPowerStateCapabilities new_cap) 275{ 276 int i; 277 278 if (CAPABILITY_BIT_CHANGED(new_cap, old_cap, kIOPMSystemPowerStateCapabilityCPU)) 279 { 280 if (BIT_IS_SET(new_cap, kIOPMSystemPowerStateCapabilityCPU)) 281 { 282 // scan for past-wakeup events, yank 'em from the queue 283 for(i=0; i<kBehaviorsCount; i++) 284 { 285 if(behaviors[i]) { 286 purgePastEvents(behaviors[i]); 287 288 if (!CFEqual(behaviors[i]->title, CFSTR(kIOPMAutoWakeOrPowerOn))) 289 schedulePowerEvent(behaviors[i]); 290 } 291 } 292 } else { 293 // Going to sleep 294 schedulePowerEvent(&wakeBehavior); 295 } 296 297 } 298} 299 300__private_extern__ void AutoWakeCalendarChange(void) 301{ 302 /*The time has changed, so lets assume that all of our previously scheduled 303 * sleep & wake events are invalid. 304 */ 305 PowerEventBehavior *this_behavior; 306 int i; 307 308 for(i=0; i<kBehaviorsCount; i++) 309 { 310 this_behavior = behaviors[i]; 311 if (this_behavior 312 && !CFEqual(this_behavior->title, CFSTR(kIOPMAutoWakeOrPowerOn))) 313 { 314 schedulePowerEvent(this_behavior); 315 } 316 } 317} 318 319/* 320 * Required behaviors at timer expiration: 321 * 322 * on wake: 323 * - system is obviously already awake if we're alive to receive this message. 324 * post a NULL HID event to wake display. 325 * on poweron: 326 * - system is obviously already on if we're alive to receive this message. 327 * schedule the next poweron event in the queue. 328 * on sleep: 329 * - ask loginwindow to put up UI warning user of impending sleep 330 * on shutdown: 331 * - ask loginwindow to put up UI warning user of impending shutdown 332 */ 333 334static void 335handleTimerExpiration(CFRunLoopTimerRef blah, void *info) 336{ 337 PowerEventBehavior *behave = (PowerEventBehavior *)info; 338 339 if(!behave) return; 340 341 if (behave->timer) { 342 CFRelease(behave->timer); 343 behave->timer = 0; 344 } 345 346 if( behave->timerExpirationCallout ) { 347 (*behave->timerExpirationCallout)(behave->currentEvent); 348 } 349 350 if (behave->currentEvent) 351 CFRelease(behave->currentEvent); 352 behave->currentEvent = NULL; 353 354 // Schedule the next event 355 schedulePowerEvent(behave); 356 357 return; 358} 359 360/* 361 * Required behaviors at event scheduling time: 362 * 363 * on wake and/or poweron: 364 * - transmit expected wake/on time to underlying hardware that will wake 365 * the system when appropriate. 366 * 367 */ 368 369static void 370schedulePowerEvent(PowerEventBehavior *behave) 371{ 372 static CFRunLoopTimerContext tmr_context = {0,0,0,0,0}; 373 CFAbsoluteTime fire_time = 0.0; 374 CFDictionaryRef upcoming = NULL; 375 CFDateRef temp_date = NULL; 376 377 if(behave->timer) 378 { 379 CFRunLoopTimerInvalidate(behave->timer); 380 CFRelease(behave->timer); 381 behave->timer = 0; 382 } 383 384 // find upcoming time 385 upcoming = copyEarliestUpcoming(behave); 386 if(!upcoming) 387 { 388 // No scheduled events 389 if (behave->noScheduledEventCallout) { 390 (*behave->noScheduledEventCallout)(NULL); 391 } 392 return; 393 } 394 395 /* 396 * Perform any necessary actions at schedulePowerEvent time 397 */ 398 if ( behave->scheduleNextCallout ) { 399 (*behave->scheduleNextCallout)(upcoming); 400 } 401 402 if (behave->currentEvent) { 403 CFRelease(behave->currentEvent); 404 } 405 406 behave->currentEvent = (CFDictionaryRef)upcoming; 407 tmr_context.info = (void *)behave; 408 409 temp_date = _getScheduledEventDate(upcoming); 410 if(!temp_date) goto exit; 411 412 fire_time = CFDateGetAbsoluteTime(temp_date); 413 414 behave->timer = CFRunLoopTimerCreate(0, fire_time, 0.0, 0, 415 0, handleTimerExpiration, &tmr_context); 416 417 if(behave->timer) 418 { 419 CFRunLoopAddTimer( CFRunLoopGetCurrent(), 420 behave->timer, 421 kCFRunLoopDefaultMode); 422 } 423 424exit: 425 return; 426} 427 428__private_extern__ void 429schedulePowerEventType(CFStringRef type) 430{ 431 int i; 432 433 if (CFEqual(type, CFSTR(kIOPMAutoWakeOrPowerOn))) { 434 /* 435 * If this is a 'WakeOrPowerOn' event, schedule 436 * this for both wakeBehavior & poweronBehavior 437 */ 438 schedulePowerEvent(&wakeBehavior); 439 schedulePowerEvent(&poweronBehavior); 440 return; 441 } 442 443 for(i=0; i<kBehaviorsCount; i++) { 444 if (CFEqual(type, behaviors[i]->title)) 445 break; 446 } 447 if (i >= kBehaviorsCount) { 448 return; 449 } 450 451 schedulePowerEvent(behaviors[i]); 452} 453 454__private_extern__ CFTimeInterval getEarliestRequestAutoWake(void) 455{ 456 CFDictionaryRef one_event = NULL; 457 CFDateRef event_date = NULL; 458 CFTimeInterval absTime = 0.0; 459 460 if (!(one_event = copyEarliestUpcoming(&wakeBehavior))) { 461 return 0.0; 462 } 463 if (!(event_date = _getScheduledEventDate(one_event))) { 464 return 0.0; 465 } 466 absTime = CFDateGetAbsoluteTime(event_date); 467 CFRelease(one_event); 468 return absTime; 469} 470 471/****************************************************************************** 472 ****************************************************************************** 473 * Event type-specific callouts 474 ****************************************************************************** 475 ******************************************************************************/ 476 477/* 478 * poweron 479 */ 480#pragma mark - 481#pragma mark PowerOn 482 483void poweronScheduleCallout(CFDictionaryRef event) 484{ 485 IOPMSchedulePowerEvent( event ? _getScheduledEventDate(event) : NULL, 486 NULL, CFSTR(kIOPMAutoPowerScheduleImmediate) ); 487 return; 488} 489 490 491 492/* 493 * wake 494 */ 495#pragma mark - 496#pragma mark Wake 497 498 499 500void wakeTimerExpiredCallout(CFDictionaryRef event __unused) 501{ 502 503#if !TARGET_OS_EMBEDDED 504 CFMutableDictionaryRef assertionDescription = NULL; 505 506 assertionDescription = _IOPMAssertionDescriptionCreate( 507 kIOPMAssertionUserIsActive, 508 CFSTR("com.apple.powermanagement.wakeschedule"), 509 NULL, CFSTR("Waking screen for scheduled system wake"), NULL, 510 2, kIOPMAssertionTimeoutActionRelease); 511 512 InternalCreateAssertion(assertionDescription, NULL); 513 514 CFRelease(assertionDescription); 515#endif 516 517 518} 519 520/* 521 * sleep 522 */ 523#pragma mark - 524#pragma mark Sleep 525 526void sleepTimerExpiredCallout(CFDictionaryRef event __unused) 527{ 528 _askNicelyThenSleepSystem(); 529} 530 531/* 532 * shutdown 533 */ 534#pragma mark - 535#pragma mark Shutdown 536 537void shutdownTimerExpiredCallout(CFDictionaryRef event __unused) 538{ 539 _askNicelyThenShutdownSystem(); 540} 541 542/* 543 * restart 544 */ 545#pragma mark - 546#pragma mark Restart 547 548void restartTimerExpiredCallout(CFDictionaryRef event __unused) 549{ 550 _askNicelyThenRestartSystem(); 551} 552 553 554#pragma mark - 555#pragma mark Utility 556 557/****************************************************************************** 558 ****************************************************************************** 559 * Utility functions from here on out 560 ****************************************************************************** 561 ******************************************************************************/ 562 563 564/* 565 * 566 * isEntryValidAndFuturistic 567 * Returns true if the CFDictionary is validly formed 568 * AND if the date is in the future 569 * Returns false if anything about the dictionary is invalid 570 * OR if the CFDate is prior to the current time 571 * 572 */ 573static bool 574isEntryValidAndFuturistic(CFDictionaryRef wakeup_dict, CFDateRef date_now) 575{ 576 CFDateRef wakeup_date; 577 bool ret = true; 578 579 wakeup_dict = isA_CFDictionary(wakeup_dict); 580 if(!wakeup_dict) 581 { 582 // bogus entry! 583 ret = false; 584 } else 585 { 586 // valid entry 587 wakeup_date = isA_CFDate(CFDictionaryGetValue(wakeup_dict, 588 CFSTR(kIOPMPowerEventTimeKey))); 589 if( !wakeup_date 590 || (kCFCompareLessThan == CFDateCompare(wakeup_date, date_now, 0))) 591 { 592 // date is too early 593 ret = false; 594 } 595 // otherwise date is after now, and ret = true 596 } 597 598 return ret; 599} 600 601/* 602 * 603 * Purge past wakeup times 604 * Does not care whether its operating on wakeup or poweron array. 605 * Just purges all entries with a time < now 606 * returns true on success, false on any failure 607 * 608 */ 609static bool 610purgePastEvents(PowerEventBehavior *behave) 611{ 612 CFDateRef date_now; 613 CFDictionaryRef event; 614 bool ret; 615 616 617 if( !behave 618 || !behave->title 619 || !behave->array 620 || (0 == CFArrayGetCount(behave->array))) 621 { 622 return true; 623 } 624 625 date_now = CFDateCreate(0, CFAbsoluteTimeGetCurrent()); 626 627 // Loop over the array and remove any values that are in the past. 628 // Since array is sorted by date already, we stop once we reach an event 629 // scheduled in the future. 630 // Do not try to optimize the CFArrayGetCount out of the while loop; this value may 631 // change during loop execution. 632 while(0 < CFArrayGetCount(behave->array)) 633 { 634 event = CFArrayGetValueAtIndex(behave->array, 0); 635 if (isEntryValidAndFuturistic(event, date_now) ) 636 break; 637 638 // Remove entry from the array - its time has past 639 // The rest of the array will shift down to fill index 0 640 CFArrayRemoveValueAtIndex(behave->array, 0); 641 activeEventCnt--; 642 } 643 644 CFRelease(date_now); 645 646 ret = true; 647 648 return ret; 649} 650 651 652/* 653 * 654 * copySchedulePowerChangeArrays 655 * 656 */ 657static void 658copyScheduledPowerChangeArrays(void) 659{ 660#if !TARGET_OS_EMBEDDED 661 CFArrayRef tmp; 662 SCPreferencesRef prefs; 663 PowerEventBehavior *this_behavior; 664 int i; 665 666 prefs = SCPreferencesCreate(0, 667 CFSTR("PM-configd-AutoWake"), 668 CFSTR(kIOPMAutoWakePrefsPath)); 669 if(!prefs) return; 670 671 activeEventCnt = 0; 672 // Loop through all sleep, wake, shutdown powerbehaviors 673 for(i=0; i<kBehaviorsCount; i++) 674 { 675 this_behavior = behaviors[i]; 676 677 if(this_behavior->array) { 678 CFRelease(this_behavior->array); 679 this_behavior->array = NULL; 680 } 681 682 tmp = isA_CFArray(SCPreferencesGetValue(prefs, this_behavior->title)); 683 if(tmp && (0 < CFArrayGetCount(tmp))) { 684 this_behavior->array = CFArrayCreateMutableCopy(0, 0, tmp); 685 activeEventCnt += CFArrayGetCount(tmp); 686 } else { 687 this_behavior->array = NULL; 688 } 689 } 690 691 692 693 CFRelease(prefs); 694 695#endif 696} 697 698/* 699 * 700 * Find earliest upcoming wakeup time 701 * 702 * For non-repeat events, a reference to event dictionary is provided. 703 * For repeat events, a new event dictionary is created. Caller has to take 704 * care to release that dictionary eventually. 705 */ 706static CFDictionaryRef 707copyEarliestUpcoming(PowerEventBehavior *b) 708{ 709 CFArrayRef arr = NULL; 710 CFDateRef now = NULL; 711 CFDictionaryRef the_result = NULL; 712 CFDictionaryRef repeatEvent = NULL; 713 CFIndex i, count; 714 CFComparisonResult eq; 715 716 if(!b) return NULL; 717 718 // wake and poweron types get merged with wakeorpoweron array 719 if(b->sharedEvents) { 720 721 // musst release arr later 722 arr = copyMergedEventArray(b, b->sharedEvents); 723 724 } else { 725 arr = b->array; 726 } 727 728 // If the array is NULL, we have no work to do. 729 if (arr && (count = CFArrayGetCount(arr)) != 0) { 730 731 732 now = CFDateCreate(0, CFAbsoluteTimeGetCurrent() + MIN_SCHEDULE_TIME); 733 734 // iterate through all past entries, stopping at one occurring 735 // >MIN_SCHEDULE_TIME seconds in the future, or at the end of the array 736 i = 0; 737 while( (i < count) 738 && !isEntryValidAndFuturistic(CFArrayGetValueAtIndex(arr, i), now) ) 739 { 740 i++; 741 } 742 CFRelease(now); 743 744 if(i < count) 745 { 746 the_result = CFArrayGetValueAtIndex(arr, i); 747 CFRetain(the_result); 748 } 749 } 750 751 // Compare against the repeat event, if there is any 752 repeatEvent = copyNextRepeatingEvent(b->title); 753 if (repeatEvent) 754 { 755 eq = compareEvDates(repeatEvent, the_result, 0); 756 if((kCFCompareLessThan == eq) || (kCFCompareEqualTo == eq)) 757 { 758 // repeatEvent <= the_result 759 if (the_result) CFRelease(the_result); 760 the_result = repeatEvent; 761 // In this case, repeatEvent is released in the 762 // event expiration handler 763 } 764 else 765 { 766 CFRelease(repeatEvent); 767 } 768 } 769 770 if(arr && b->sharedEvents) CFRelease(arr); 771 772 return the_result; 773} 774 775/* 776 * 777 * comapareEvDates() - internal sorting helper for copyMergedEventArray() 778 * 779 */ 780 static CFComparisonResult 781compareEvDates( 782 CFDictionaryRef a1, 783 CFDictionaryRef a2, 784 void *c __unused) 785{ 786 CFDateRef d1, d2; 787 a1 = isA_CFDictionary(a1); 788 a2 = isA_CFDictionary(a2); 789 if(!a1) return kCFCompareGreaterThan; 790 else if(!a2) return kCFCompareLessThan; 791 792 d1 = isA_CFDate(CFDictionaryGetValue(a1, CFSTR(kIOPMPowerEventTimeKey))); 793 d2 = isA_CFDate(CFDictionaryGetValue(a2, CFSTR(kIOPMPowerEventTimeKey))); 794 if(!d1) return kCFCompareGreaterThan; 795 else if(!d2) return kCFCompareLessThan; 796 797 return CFDateCompare(d1, d2, 0); 798} 799 800/* 801 * 802 * copyMergedEventArray 803 * 804 * Takes two PowerEventBehavior*, merges their CFArray array members into one 805 * mutable array, sorted by date. 806 */ 807static CFArrayRef 808copyMergedEventArray( 809 PowerEventBehavior *a, 810 PowerEventBehavior *b) 811{ 812 CFMutableArrayRef merged; 813 CFIndex bcount; 814 CFRange rng; 815 816 if(!a || !b) return NULL; 817 if(!a->array && !b->array) return NULL; 818 if(!a->array) return CFRetain(b->array); 819 if(!b->array) return CFRetain(a->array); 820 821 // merge! 822 merged = CFArrayCreateMutableCopy(0, 0, a->array); 823 824 bcount = CFArrayGetCount(b->array); 825 rng = CFRangeMake(0, bcount); 826 CFArrayAppendArray(merged, b->array, rng); 827 828 // sort! 829 // We sort using the same compare_dates function used in IOKitUser 830 // pwr_mgt/IOPMAutoWake.c. Arrays must be sorted identically to how 831 // they would be there. 832 bcount = CFArrayGetCount(merged); 833 rng = CFRangeMake(0, bcount); 834 CFArraySortValues(merged, rng, (CFComparatorFunction)compareEvDates, 0); 835 836 // caller must release 837 return merged; 838} 839 840 841 842 843 844static CFDateRef 845_getScheduledEventDate(CFDictionaryRef event) 846{ 847 return isA_CFDate(CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventTimeKey))); 848} 849 850 851 852__private_extern__ IOReturn 853createSCSession(SCPreferencesRef *prefs, uid_t euid, int lock) 854{ 855 IOReturn ret = kIOReturnSuccess; 856 857#if !TARGET_OS_EMBEDDED 858 859 if (euid == 0) 860 *prefs = SCPreferencesCreate( 0, CFSTR("PM-configd-AutoWake"), 861 CFSTR(kIOPMAutoWakePrefsPath)); 862 else 863 { 864 ret = kIOReturnNotPrivileged; 865 goto exit; 866 } 867 868 if(!(*prefs)) 869 { 870 if(kSCStatusAccessError == SCError()) 871 ret = kIOReturnNotPrivileged; 872 else ret = kIOReturnError; 873 goto exit; 874 } 875 876 if (lock && !SCPreferencesLock(*prefs, true)) 877 { 878 ret = kIOReturnError; 879 goto exit; 880 } 881 882 883exit: 884#endif 885 return ret; 886} 887 888__private_extern__ void 889destroySCSession(SCPreferencesRef prefs, int unlock) 890{ 891 892#if !TARGET_OS_EMBEDDED 893 if (prefs) { 894 if(unlock) SCPreferencesUnlock(prefs); 895 CFRelease(prefs); 896 } 897#endif 898} 899 900static void 901addEvent(PowerEventBehavior *behave, CFDictionaryRef event) 902{ 903 if (isA_CFArray(behave->array)) { 904 905 // First clear off any expired events 906 purgePastEvents(behave); 907 908 CFArrayAppendValue(behave->array, event); 909 910 // XXX: Manual sorting is probably better than using CFArraySortValues() 911 // Element is being added to already sorted array 912 CFArraySortValues( 913 behave->array, CFRangeMake(0, CFArrayGetCount(behave->array)), 914 (CFComparatorFunction)compareEvDates, 0); 915 } 916 else { 917 behave->array = CFArrayCreateMutable( 918 0, 0, &kCFTypeArrayCallBacks); 919 CFArrayAppendValue(behave->array, event); 920 } 921 activeEventCnt++; 922 923} 924 925 926static IOReturn 927updateToDisk(SCPreferencesRef prefs, PowerEventBehavior *behavior, CFStringRef type) 928{ 929 IOReturn ret = kIOReturnSuccess; 930#if !TARGET_OS_EMBEDDED 931 932 if(!SCPreferencesSetValue(prefs, type, behavior->array)) 933 { 934 ret = kIOReturnError; 935 goto exit; 936 } 937 938 // Add a warning to the file 939 SCPreferencesSetValue(prefs, CFSTR("WARNING"), 940 CFSTR("Do not edit this file by hand. It must remain in sorted-by-date order.")); 941 // 942 // commit the SCPreferences file out to disk 943 if(!SCPreferencesCommitChanges(prefs)) 944 { 945 ret = kIOReturnError; 946 goto exit; 947 } 948exit: 949#endif 950 return ret; 951} 952 953 954static bool 955removeEvent(PowerEventBehavior *behave, CFDictionaryRef event) 956{ 957 958 CFIndex count, i; 959 int j; 960 CFComparisonResult eq; 961 CFDictionaryRef cancelee = 0; 962 963 if (!behave->array || !isA_CFArray(behave->array)) 964 return false; 965 966 count = CFArrayGetCount(behave->array); 967 for (i = 0; i < count; i++) 968 { 969 cancelee = CFArrayGetValueAtIndex(behave->array, i); 970 eq = compareEvDates(event, cancelee, 0); 971 if(kCFCompareLessThan == eq) 972 { 973 // fail, date to cancel < date at index 974 break; 975 } 976 else if(kCFCompareEqualTo == eq) 977 { 978 // We have confirmation on the dates and types being equal. Check id. 979 if( CFEqual( 980 CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventAppNameKey)), 981 CFDictionaryGetValue(cancelee, CFSTR(kIOPMPowerEventAppNameKey)) 982 )) 983 { 984 // This is the one to cancel. 985 // First check if cancelee is the current scheduled event 986 // If so, delete currentEvent field. Caller will take care of 987 // re-scheduling the next event 988 for (j = 0; j < kBehaviorsCount; j++) 989 if (behaviors[j]->currentEvent && CFEqual(cancelee, behaviors[j]->currentEvent)) { 990 CFRelease(behaviors[j]->currentEvent); 991 behaviors[j]->currentEvent = NULL; 992 } 993 994 CFArrayRemoveValueAtIndex(behave->array, i); 995 activeEventCnt--; 996 return true; 997 } 998 } 999 } 1000 1001 return false; 1002} 1003 1004 1005 1006 1007/* MIG entry point to schedule a power event */ 1008kern_return_t 1009_io_pm_schedule_power_event 1010( 1011 mach_port_t server __unused, 1012 audit_token_t token, 1013 vm_offset_t flatPackage, 1014 mach_msg_type_number_t packageLen, 1015 int action, 1016 int *return_code 1017) 1018{ 1019 1020 CFDictionaryRef event = NULL; 1021 CFDataRef dataRef = NULL; 1022 CFStringRef type = NULL; 1023 SCPreferencesRef prefs = 0; 1024 uid_t callerEUID; 1025 int i; 1026 1027 *return_code = kIOReturnSuccess; 1028 1029 audit_token_to_au32(token, NULL, &callerEUID, NULL, NULL, NULL, NULL, NULL, NULL); 1030 1031 if (activeEventCnt >= kIOPMMaxScheduledEntries) { 1032 *return_code = kIOReturnNoSpace; 1033 goto exit; 1034 } 1035 1036 dataRef = CFDataCreate(0, (const UInt8 *)flatPackage, packageLen); 1037 if (dataRef) { 1038 event = (CFDictionaryRef)CFPropertyListCreateWithData(0, dataRef, 0, NULL, NULL); 1039 } 1040 1041 if (!event) { 1042 *return_code = kIOReturnBadArgument; 1043 goto exit; 1044 } 1045 1046 type = CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventTypeKey) ); 1047 if (!type) { 1048 *return_code = kIOReturnBadArgument; 1049 goto exit; 1050 } 1051 1052 for(i=0; i<kBehaviorsCount; i++) { 1053 if (CFEqual(type, behaviors[i]->title)) 1054 break; 1055 } 1056 if (i >= kBehaviorsCount) { 1057 *return_code = kIOReturnBadArgument; 1058 goto exit; 1059 } 1060 1061 //who = CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventAppNameKey)); 1062 1063 //asl_log(0, 0, ASL_LEVEL_ERR, "Sched event type: %s by %s\n", CFStringGetCStringPtr(type,kCFStringEncodingMacRoman ), 1064 // CFStringGetCStringPtr( who, kCFStringEncodingMacRoman)); 1065 1066 if((*return_code = createSCSession(&prefs, callerEUID, 1)) != kIOReturnSuccess) 1067 goto exit; 1068 1069 if (action == 1) { 1070 1071 /* Add event to in-memory array */ 1072 addEvent(behaviors[i], event); 1073 1074 /* Commit changes to disk */ 1075 if ((*return_code = updateToDisk(prefs, behaviors[i], type)) != kIOReturnSuccess) { 1076 removeEvent(behaviors[i], event); 1077 goto exit; 1078 } 1079 } 1080 else { 1081 /* Remove event from in-memory array */ 1082 if (!removeEvent(behaviors[i], event)) { 1083 *return_code = kIOReturnNotFound; 1084 goto exit; 1085 } 1086 1087 /* Update to disk. Ignore the failure; */ 1088 updateToDisk(prefs, behaviors[i], type); 1089 } 1090 /* Schedule the power event */ 1091 if (CFEqual(type, CFSTR(kIOPMAutoWakeOrPowerOn))) { 1092 /* 1093 * If this is a 'WakeOrPowerOn' event, schedule 1094 * this for both wakeBehavior & poweronBehavior 1095 */ 1096 schedulePowerEvent(&wakeBehavior); 1097 schedulePowerEvent(&poweronBehavior); 1098 } 1099 else { 1100 schedulePowerEvent(behaviors[i]); 1101 } 1102 1103 1104exit: 1105 destroySCSession(prefs, 1); 1106 if (dataRef) 1107 CFRelease(dataRef); 1108 1109 if (event) 1110 CFRelease(event); 1111 1112 vm_deallocate(mach_task_self(), flatPackage, packageLen); 1113 1114 return KERN_SUCCESS; 1115} 1116 1117__private_extern__ CFArrayRef copyScheduledPowerEvents(void) 1118{ 1119 1120 CFMutableArrayRef powerEvents = NULL; 1121 PowerEventBehavior *this_behavior; 1122 int i; 1123 CFIndex bcount; 1124 CFRange rng; 1125 1126 powerEvents = CFArrayCreateMutable( 0, 0, &kCFTypeArrayCallBacks); 1127 for(i=0; i<kBehaviorsCount; i++) { 1128 this_behavior = behaviors[i]; 1129 1130 if(this_behavior->array && isA_CFArray(this_behavior->array)) { 1131 bcount = CFArrayGetCount(this_behavior->array); 1132 rng = CFRangeMake(0, bcount); 1133 CFArrayAppendArray(powerEvents, this_behavior->array, rng); 1134 } 1135 } 1136 1137 return powerEvents; 1138} 1139 1140 1141 1142