AutoWakeScheduler.c [plain text]
#include <syslog.h>
#include <bsm/libbsm.h>
#include "PrivateLib.h"
#include "AutoWakeScheduler.h"
#include "RepeatingAutoWake.h"
#include "PMAssertions.h"
enum {
kIOWakeTimer = 0,
kIOPowerOnTimer = 1,
kIOSleepTimer = 2,
kIOShutdownTimer = 3
};
enum {
kIOPMMaxScheduledEntries = 1000
};
#if TARGET_OS_EMBEDDED
#define MIN_SCHEDULE_TIME (5.0)
#else
#define MIN_SCHEDULE_TIME (10.0)
#endif
typedef void (*powerEventCallout)(CFDictionaryRef);
struct PowerEventBehavior {
CFMutableArrayRef array;
CFDictionaryRef currentEvent;
CFRunLoopTimerRef timer;
CFStringRef title;
struct PowerEventBehavior *sharedEvents;
powerEventCallout timerExpirationCallout;
powerEventCallout scheduleNextCallout;
powerEventCallout noScheduledEventCallout;
};
typedef struct PowerEventBehavior PowerEventBehavior;
PowerEventBehavior sleepBehavior;
PowerEventBehavior shutdownBehavior;
PowerEventBehavior restartBehavior;
PowerEventBehavior wakeBehavior;
PowerEventBehavior poweronBehavior;
PowerEventBehavior wakeorpoweronBehavior;
static uint32_t activeEventCnt = 0;
enum {
kBehaviorsCount = 6
};
PowerEventBehavior *behaviors[] =
{
&sleepBehavior,
&shutdownBehavior,
&restartBehavior,
&wakeBehavior,
&poweronBehavior,
&wakeorpoweronBehavior
};
static bool isEntryValidAndFuturistic(CFDictionaryRef, CFDateRef);
static void schedulePowerEvent(PowerEventBehavior *);
static bool purgePastEvents(PowerEventBehavior *);
static void copyScheduledPowerChangeArrays(void);
static CFDictionaryRef copyEarliestUpcoming(PowerEventBehavior *);
static CFDateRef _getScheduledEventDate(CFDictionaryRef);
static CFArrayRef copyMergedEventArray(PowerEventBehavior *,
PowerEventBehavior *);
static CFComparisonResult compareEvDates(CFDictionaryRef,
CFDictionaryRef, void *);
void poweronScheduleCallout(CFDictionaryRef);
void wakeScheduleCallout(CFDictionaryRef);
void wakeNoScheduledEventCallout(CFDictionaryRef);
void wakeTimerExpiredCallout(CFDictionaryRef);
void poweronTimerExpiredCallout(CFDictionaryRef);
void sleepTimerExpiredCallout(CFDictionaryRef);
void shutdownTimerExpiredCallout(CFDictionaryRef);
void restartTimerExpiredCallout(CFDictionaryRef);
#pragma mark -
#pragma mark AutoWakeScheduler
void
removeEventsByAppName(PowerEventBehavior *behave, CFStringRef appName)
{
int count, j;
CFDictionaryRef cancelee = 0;
if ( (behave->array == NULL) ||
(count = CFArrayGetCount(behave->array)) == 0)
return;
for (j = count-1; j >= 0; j--)
{
cancelee = CFArrayGetValueAtIndex(behave->array, j);
if( CFEqual(
CFDictionaryGetValue(cancelee, CFSTR(kIOPMPowerEventAppNameKey)), appName
))
{
if (behave->currentEvent && CFEqual(cancelee, behave->currentEvent)) {
CFRelease(behave->currentEvent);
behave->currentEvent = NULL;
}
CFArrayRemoveValueAtIndex(behave->array, j);
activeEventCnt--;
}
}
}
__private_extern__ void
AutoWake_prime(void)
{
PowerEventBehavior *this_behavior;
int i;
for(i=0; i<kBehaviorsCount; i++)
{
this_behavior = behaviors[i];
bzero(this_behavior, sizeof(PowerEventBehavior));
}
wakeBehavior.title = CFSTR(kIOPMAutoWake);
poweronBehavior.title = CFSTR(kIOPMAutoPowerOn);
wakeorpoweronBehavior.title = CFSTR(kIOPMAutoWakeOrPowerOn);
sleepBehavior.title = CFSTR(kIOPMAutoSleep);
shutdownBehavior.title = CFSTR(kIOPMAutoShutdown);
restartBehavior.title = CFSTR(kIOPMAutoRestart);
wakeBehavior.timerExpirationCallout = wakeTimerExpiredCallout;
poweronBehavior.timerExpirationCallout = poweronTimerExpiredCallout;
sleepBehavior.timerExpirationCallout = sleepTimerExpiredCallout;
shutdownBehavior.timerExpirationCallout = shutdownTimerExpiredCallout;
restartBehavior.timerExpirationCallout = restartTimerExpiredCallout;
poweronBehavior.scheduleNextCallout = poweronScheduleCallout;
poweronBehavior.noScheduledEventCallout = poweronScheduleCallout;
wakeBehavior.sharedEvents =
poweronBehavior.sharedEvents = &wakeorpoweronBehavior;
copyScheduledPowerChangeArrays();
RepeatingAutoWake_prime();
for(i=0; i<kBehaviorsCount; i++)
{
this_behavior = behaviors[i];
if(!this_behavior) continue;
purgePastEvents(this_behavior);
removeEventsByAppName(this_behavior, CFSTR(kIOPMRepeatingAppName));
if (!CFEqual(this_behavior->title, CFSTR(kIOPMAutoWakeOrPowerOn)))
schedulePowerEvent(this_behavior);
}
return;
}
__private_extern__ void AutoWakeCapabilitiesNotification(
IOPMSystemPowerStateCapabilities old_cap,
IOPMSystemPowerStateCapabilities new_cap)
{
int i;
if (CAPABILITY_BIT_CHANGED(new_cap, old_cap, kIOPMSystemPowerStateCapabilityCPU))
{
if (BIT_IS_SET(new_cap, kIOPMSystemPowerStateCapabilityCPU))
{
for(i=0; i<kBehaviorsCount; i++)
{
if(behaviors[i]) {
purgePastEvents(behaviors[i]);
if (!CFEqual(behaviors[i]->title, CFSTR(kIOPMAutoWakeOrPowerOn)))
schedulePowerEvent(behaviors[i]);
}
}
} else {
schedulePowerEvent(&wakeBehavior);
}
}
}
static void
handleTimerExpiration(CFRunLoopTimerRef blah, void *info)
{
PowerEventBehavior *behave = (PowerEventBehavior *)info;
if(!behave) return;
if (behave->timer) {
CFRelease(behave->timer);
behave->timer = 0;
}
if( behave->timerExpirationCallout ) {
(*behave->timerExpirationCallout)(behave->currentEvent);
}
if (behave->currentEvent)
CFRelease(behave->currentEvent);
behave->currentEvent = NULL;
schedulePowerEvent(behave);
return;
}
static void
schedulePowerEvent(PowerEventBehavior *behave)
{
static CFRunLoopTimerContext tmr_context = {0,0,0,0,0};
CFAbsoluteTime fire_time = 0.0;
CFDictionaryRef upcoming = NULL;
CFDateRef temp_date = NULL;
if(behave->timer)
{
CFRunLoopTimerInvalidate(behave->timer);
CFRelease(behave->timer);
behave->timer = 0;
}
upcoming = copyEarliestUpcoming(behave);
if(!upcoming)
{
if (behave->noScheduledEventCallout) {
(*behave->noScheduledEventCallout)(NULL);
}
return;
}
if ( behave->scheduleNextCallout ) {
(*behave->scheduleNextCallout)(upcoming);
}
if (behave->currentEvent) {
CFRelease(behave->currentEvent);
}
behave->currentEvent = (CFDictionaryRef)upcoming;
tmr_context.info = (void *)behave;
temp_date = _getScheduledEventDate(upcoming);
if(!temp_date) goto exit;
fire_time = CFDateGetAbsoluteTime(temp_date);
behave->timer = CFRunLoopTimerCreate(0, fire_time, 0.0, 0,
0, handleTimerExpiration, &tmr_context);
if(behave->timer)
{
CFRunLoopAddTimer( CFRunLoopGetCurrent(),
behave->timer,
kCFRunLoopDefaultMode);
}
exit:
return;
}
__private_extern__ void
schedulePowerEventType(CFStringRef type)
{
int i;
if (CFEqual(type, CFSTR(kIOPMAutoWakeOrPowerOn))) {
schedulePowerEvent(&wakeBehavior);
schedulePowerEvent(&poweronBehavior);
return;
}
for(i=0; i<kBehaviorsCount; i++) {
if (CFEqual(type, behaviors[i]->title))
break;
}
if (i >= kBehaviorsCount) {
return;
}
schedulePowerEvent(behaviors[i]);
}
__private_extern__ CFTimeInterval getEarliestRequestAutoWake(void)
{
CFDictionaryRef one_event = NULL;
CFDateRef event_date = NULL;
CFTimeInterval absTime = 0.0;
if (!(one_event = copyEarliestUpcoming(&wakeBehavior))) {
return 0.0;
}
if (!(event_date = _getScheduledEventDate(one_event))) {
return 0.0;
}
absTime = CFDateGetAbsoluteTime(event_date);
CFRelease(one_event);
return absTime;
}
#pragma mark -
#pragma mark PowerOn
void poweronScheduleCallout(CFDictionaryRef event)
{
IOPMSchedulePowerEvent( event ? _getScheduledEventDate(event) : NULL,
NULL, CFSTR(kIOPMAutoPowerScheduleImmediate) );
return;
}
void poweronTimerExpiredCallout(CFDictionaryRef event __unused)
{
return;
}
#pragma mark -
#pragma mark Wake
void wakeTimerExpiredCallout(CFDictionaryRef event __unused)
{
#if !TARGET_OS_EMBEDDED
CFMutableDictionaryRef assertionDescription = NULL;
assertionDescription = _IOPMAssertionDescriptionCreate(
kIOPMAssertionUserIsActive,
CFSTR("com.apple.powermanagement.wakeschedule"),
NULL, CFSTR("Waking screen for scheduled system wake"), NULL,
2, kIOPMAssertionTimeoutActionRelease);
InternalCreateAssertion(assertionDescription, NULL);
CFRelease(assertionDescription);
#endif
}
#pragma mark -
#pragma mark Sleep
void sleepTimerExpiredCallout(CFDictionaryRef event __unused)
{
_askNicelyThenSleepSystem();
}
#pragma mark -
#pragma mark Shutdown
void shutdownTimerExpiredCallout(CFDictionaryRef event __unused)
{
_askNicelyThenShutdownSystem();
}
#pragma mark -
#pragma mark Restart
void restartTimerExpiredCallout(CFDictionaryRef event __unused)
{
_askNicelyThenRestartSystem();
}
#pragma mark -
#pragma mark Utility
static bool
isEntryValidAndFuturistic(CFDictionaryRef wakeup_dict, CFDateRef date_now)
{
CFDateRef wakeup_date;
bool ret = true;
wakeup_dict = isA_CFDictionary(wakeup_dict);
if(!wakeup_dict)
{
ret = false;
} else
{
wakeup_date = isA_CFDate(CFDictionaryGetValue(wakeup_dict,
CFSTR(kIOPMPowerEventTimeKey)));
if( !wakeup_date
|| (kCFCompareLessThan == CFDateCompare(wakeup_date, date_now, 0)))
{
ret = false;
}
}
return ret;
}
static bool
purgePastEvents(PowerEventBehavior *behave)
{
CFDateRef date_now;
CFDictionaryRef event;
bool ret;
if( !behave
|| !behave->title
|| !behave->array
|| (0 == CFArrayGetCount(behave->array)))
{
return true;
}
date_now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
while(0 < CFArrayGetCount(behave->array))
{
event = CFArrayGetValueAtIndex(behave->array, 0);
if (isEntryValidAndFuturistic(event, date_now) )
break;
CFArrayRemoveValueAtIndex(behave->array, 0);
activeEventCnt--;
}
CFRelease(date_now);
ret = true;
return ret;
}
static void
copyScheduledPowerChangeArrays(void)
{
CFArrayRef tmp;
SCPreferencesRef prefs;
PowerEventBehavior *this_behavior;
int i;
prefs = SCPreferencesCreate(0,
CFSTR("PM-configd-AutoWake"),
CFSTR(kIOPMAutoWakePrefsPath));
if(!prefs) return;
activeEventCnt = 0;
for(i=0; i<kBehaviorsCount; i++)
{
this_behavior = behaviors[i];
if(this_behavior->array) {
CFRelease(this_behavior->array);
this_behavior->array = NULL;
}
tmp = isA_CFArray(SCPreferencesGetValue(prefs, this_behavior->title));
if(tmp && (0 < CFArrayGetCount(tmp))) {
this_behavior->array = CFArrayCreateMutableCopy(0, 0, tmp);
activeEventCnt += CFArrayGetCount(tmp);
} else {
this_behavior->array = NULL;
}
}
CFRelease(prefs);
}
static CFDictionaryRef
copyEarliestUpcoming(PowerEventBehavior *b)
{
CFArrayRef arr = NULL;
CFDateRef now = NULL;
CFDictionaryRef the_result = NULL;
CFDictionaryRef repeatEvent = NULL;
int i, count;
CFComparisonResult eq;
if(!b) return NULL;
if(b->sharedEvents) {
arr = copyMergedEventArray(b, b->sharedEvents);
} else {
arr = b->array;
}
if (arr && (count = CFArrayGetCount(arr)) != 0) {
now = CFDateCreate(0, CFAbsoluteTimeGetCurrent() + MIN_SCHEDULE_TIME);
i = 0;
while( (i < count)
&& !isEntryValidAndFuturistic(CFArrayGetValueAtIndex(arr, i), now) )
{
i++;
}
CFRelease(now);
if(i < count)
{
the_result = CFArrayGetValueAtIndex(arr, i);
CFRetain(the_result);
}
}
repeatEvent = copyNextRepeatingEvent(b->title);
if (repeatEvent)
{
eq = compareEvDates(repeatEvent, the_result, 0);
if((kCFCompareLessThan == eq) || (kCFCompareEqualTo == eq))
{
if (the_result) CFRelease(the_result);
the_result = repeatEvent;
}
else
{
CFRelease(repeatEvent);
}
}
if(arr && b->sharedEvents) CFRelease(arr);
return the_result;
}
static CFComparisonResult
compareEvDates(
CFDictionaryRef a1,
CFDictionaryRef a2,
void *c __unused)
{
CFDateRef d1, d2;
a1 = isA_CFDictionary(a1);
a2 = isA_CFDictionary(a2);
if(!a1) return kCFCompareGreaterThan;
else if(!a2) return kCFCompareLessThan;
d1 = isA_CFDate(CFDictionaryGetValue(a1, CFSTR(kIOPMPowerEventTimeKey)));
d2 = isA_CFDate(CFDictionaryGetValue(a2, CFSTR(kIOPMPowerEventTimeKey)));
if(!d1) return kCFCompareGreaterThan;
else if(!d2) return kCFCompareLessThan;
return CFDateCompare(d1, d2, 0);
}
static CFArrayRef
copyMergedEventArray(
PowerEventBehavior *a,
PowerEventBehavior *b)
{
CFMutableArrayRef merged;
int bcount;
CFRange rng;
if(!a || !b) return NULL;
if(!a->array && !b->array) return NULL;
if(!a->array) return CFRetain(b->array);
if(!b->array) return CFRetain(a->array);
merged = CFArrayCreateMutableCopy(0, 0, a->array);
bcount = CFArrayGetCount(b->array);
rng = CFRangeMake(0, bcount);
CFArrayAppendArray(merged, b->array, rng);
bcount = CFArrayGetCount(merged);
rng = CFRangeMake(0, bcount);
CFArraySortValues(merged, rng, (CFComparatorFunction)compareEvDates, 0);
return merged;
}
static CFDateRef
_getScheduledEventDate(CFDictionaryRef event)
{
return isA_CFDate(CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventTimeKey)));
}
__private_extern__ IOReturn
createSCSession(SCPreferencesRef *prefs, uid_t euid, int lock)
{
IOReturn ret = kIOReturnSuccess;
#if TARGET_OS_EMBEDDED
*prefs = SCPreferencesCreate( 0, CFSTR("PM-configd-AutoWake"),
CFSTR(kIOPMAutoWakePrefsPath));
#else
if (euid == 0)
*prefs = SCPreferencesCreate( 0, CFSTR("PM-configd-AutoWake"),
CFSTR(kIOPMAutoWakePrefsPath));
else
{
ret = kIOReturnNotPrivileged;
goto exit;
}
#endif
if(!(*prefs))
{
if(kSCStatusAccessError == SCError())
ret = kIOReturnNotPrivileged;
else ret = kIOReturnError;
goto exit;
}
if (lock && !SCPreferencesLock(*prefs, true))
{
ret = kIOReturnError;
goto exit;
}
exit:
return ret;
}
__private_extern__ void
destroySCSession(SCPreferencesRef prefs, int unlock)
{
if (prefs) {
if(unlock) SCPreferencesUnlock(prefs);
CFRelease(prefs);
}
}
static void
addEvent(PowerEventBehavior *behave, CFDictionaryRef event)
{
if (isA_CFArray(behave->array)) {
purgePastEvents(behave);
CFArrayAppendValue(behave->array, event);
CFArraySortValues(
behave->array, CFRangeMake(0, CFArrayGetCount(behave->array)),
(CFComparatorFunction)compareEvDates, 0);
}
else {
behave->array = CFArrayCreateMutable(
0, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(behave->array, event);
}
activeEventCnt++;
}
static IOReturn
updateToDisk(SCPreferencesRef prefs, PowerEventBehavior *behavior, CFStringRef type)
{
IOReturn ret = kIOReturnSuccess;
if(!SCPreferencesSetValue(prefs, type, behavior->array))
{
ret = kIOReturnError;
goto exit;
}
SCPreferencesSetValue(prefs, CFSTR("WARNING"),
CFSTR("Do not edit this file by hand. It must remain in sorted-by-date order."));
if(!SCPreferencesCommitChanges(prefs))
{
ret = kIOReturnError;
goto exit;
}
exit:
return ret;
}
static bool
removeEvent(PowerEventBehavior *behave, CFDictionaryRef event)
{
int count, i, j;
CFComparisonResult eq;
CFDictionaryRef cancelee = 0;
if (!behave->array || !isA_CFArray(behave->array))
return false;
count = CFArrayGetCount(behave->array);
for (i = 0; i < count; i++)
{
cancelee = CFArrayGetValueAtIndex(behave->array, i);
eq = compareEvDates(event, cancelee, 0);
if(kCFCompareLessThan == eq)
{
break;
}
else if(kCFCompareEqualTo == eq)
{
if( CFEqual(
CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventAppNameKey)),
CFDictionaryGetValue(cancelee, CFSTR(kIOPMPowerEventAppNameKey))
))
{
for (j = 0; j < kBehaviorsCount; j++)
if (behaviors[j]->currentEvent && CFEqual(cancelee, behaviors[j]->currentEvent)) {
CFRelease(behaviors[j]->currentEvent);
behaviors[j]->currentEvent = NULL;
}
CFArrayRemoveValueAtIndex(behave->array, i);
activeEventCnt--;
return true;
}
}
}
return false;
}
kern_return_t
_io_pm_schedule_power_event
(
mach_port_t server __unused,
audit_token_t token,
vm_offset_t flatPackage,
mach_msg_type_number_t packageLen,
int action,
int *return_code
)
{
CFDictionaryRef event = NULL;
CFDataRef dataRef = NULL;
CFStringRef type = NULL;
SCPreferencesRef prefs = 0;
uid_t callerEUID;
int i;
*return_code = kIOReturnSuccess;
audit_token_to_au32(token, NULL, &callerEUID, NULL, NULL, NULL, NULL, NULL, NULL);
if (activeEventCnt >= kIOPMMaxScheduledEntries) {
*return_code = kIOReturnNoSpace;
goto exit;
}
dataRef = CFDataCreate(0, (const UInt8 *)flatPackage, packageLen);
if (dataRef) {
event = (CFDictionaryRef)CFPropertyListCreateWithData(0, dataRef, 0, NULL, NULL);
}
if (!event) {
*return_code = kIOReturnBadArgument;
goto exit;
}
type = CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventTypeKey) );
if (!type) {
*return_code = kIOReturnBadArgument;
goto exit;
}
for(i=0; i<kBehaviorsCount; i++) {
if (CFEqual(type, behaviors[i]->title))
break;
}
if (i >= kBehaviorsCount) {
*return_code = kIOReturnBadArgument;
goto exit;
}
if((*return_code = createSCSession(&prefs, callerEUID, 1)) != kIOReturnSuccess)
goto exit;
if (action == 1) {
addEvent(behaviors[i], event);
if ((*return_code = updateToDisk(prefs, behaviors[i], type)) != kIOReturnSuccess) {
removeEvent(behaviors[i], event);
goto exit;
}
}
else {
if (!removeEvent(behaviors[i], event)) {
*return_code = kIOReturnNotFound;
goto exit;
}
updateToDisk(prefs, behaviors[i], type);
}
if (CFEqual(type, CFSTR(kIOPMAutoWakeOrPowerOn))) {
schedulePowerEvent(&wakeBehavior);
schedulePowerEvent(&poweronBehavior);
}
else {
schedulePowerEvent(behaviors[i]);
}
exit:
destroySCSession(prefs, 1);
if (dataRef)
CFRelease(dataRef);
if (event)
CFRelease(event);
vm_deallocate(mach_task_self(), flatPackage, packageLen);
return KERN_SUCCESS;
}
__private_extern__ CFArrayRef copyScheduledPowerEvents(void)
{
CFMutableArrayRef powerEvents = NULL;
PowerEventBehavior *this_behavior;
int i, bcount;
CFRange rng;
powerEvents = CFArrayCreateMutable( 0, 0, &kCFTypeArrayCallBacks);
for(i=0; i<kBehaviorsCount; i++) {
this_behavior = behaviors[i];
if(this_behavior->array && isA_CFArray(this_behavior->array)) {
bcount = CFArrayGetCount(this_behavior->array);
rng = CFRangeMake(0, bcount);
CFArrayAppendArray(powerEvents, this_behavior->array, rng);
}
}
return powerEvents;
}