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