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