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