#include <IOKit/pwr_mgt/IOPM.h>
#include "IOSystemConfiguration.h"
#include "IOPMKeys.h"
#include "IOPMLib.h"
#include "IOPMLibPrivate.h"
enum {
kIOPMMaxScheduledEntries = 1000
};
static CFComparisonResult compare_dates(
CFDictionaryRef a1,
CFDictionaryRef a2,
void *c);
static CFAbsoluteTime roundOffDate(
CFAbsoluteTime time);
static CFDictionaryRef _IOPMCreatePowerOnDictionary(
CFAbsoluteTime the_time,
CFStringRef the_id,
CFStringRef type);
static bool inputsValid(
CFDateRef time_to_wake,
CFStringRef my_id,
CFStringRef type);
static bool addEntryAndSetPrefs(
SCPreferencesRef prefs,
CFStringRef type,
CFDictionaryRef package);
static bool removeEntryAndSetPrefs(
SCPreferencesRef prefs,
CFStringRef type,
CFDictionaryRef package);
static IOReturn _setRootDomainProperty(
CFStringRef key,
CFTypeRef val);
static void tellClockController(
CFStringRef command,
CFDateRef power_date);
IOReturn IOPMSchedulePowerEvent(
CFDateRef time_to_wake,
CFStringRef my_id,
CFStringRef type);
IOReturn IOPMCancelScheduledPowerEvent(
CFDateRef time_to_wake,
CFStringRef my_id,
CFStringRef wake_or_restart);
CFArrayRef IOPMCopyScheduledPowerEvents( void );
static CFComparisonResult compare_dates(
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 CFAbsoluteTime roundOffDate(CFAbsoluteTime time)
{
return (CFAbsoluteTime)nearbyint((time - fmod(time, (double)30.0)));
}
static CFDictionaryRef
_IOPMCreatePowerOnDictionary(
CFAbsoluteTime the_time,
CFStringRef the_id,
CFStringRef type)
{
CFMutableDictionaryRef d;
CFDateRef the_date;
the_id = isA_CFString(the_id);
the_time = roundOffDate(the_time);
the_date = CFDateCreate(0, the_time);
d = CFDictionaryCreateMutable(0, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if(!d) return NULL;
CFDictionaryAddValue(d, CFSTR(kIOPMPowerEventTimeKey), the_date);
if(!the_id) the_id = CFSTR("");
CFDictionaryAddValue(d, CFSTR(kIOPMPowerEventAppNameKey), the_id);
CFDictionaryAddValue(d, CFSTR(kIOPMPowerEventTypeKey), type);
CFRelease(the_date);
return d;
}
static bool
inputsValid(
CFDateRef time_to_wake,
CFStringRef my_id __unused,
CFStringRef type)
{
if (!isA_CFDate(time_to_wake)
&& !CFEqual(type, CFSTR(kIOPMAutoWakeScheduleImmediate))
&& !CFEqual(type, CFSTR(kIOPMAutoPowerScheduleImmediate)) )
{
return false;
}
if(!isA_CFString(type)) return false;
if(!(CFEqual(type, CFSTR(kIOPMAutoWake)) ||
CFEqual(type, CFSTR(kIOPMAutoPowerOn)) ||
CFEqual(type, CFSTR(kIOPMAutoWakeOrPowerOn)) ||
CFEqual(type, CFSTR(kIOPMAutoSleep)) ||
CFEqual(type, CFSTR(kIOPMAutoShutdown)) ||
CFEqual(type, CFSTR(kIOPMAutoRestart)) ||
CFEqual(type, CFSTR(kIOPMAutoWakeScheduleImmediate)) ||
CFEqual(type, CFSTR(kIOPMAutoPowerScheduleImmediate)) ||
CFEqual(type, CFSTR(kIOPMAutoWakeRelativeSeconds)) ||
CFEqual(type, CFSTR(kIOPMAutoPowerRelativeSeconds)) ))
{
return false;
}
return true;
}
static bool
addEntryAndSetPrefs(
SCPreferencesRef prefs,
CFStringRef type,
CFDictionaryRef package)
{
CFArrayRef arr = 0;
CFMutableArrayRef new_arr = 0;
bool ret = false;
arr = isA_CFArray(SCPreferencesGetValue(prefs, type));
if(arr)
{
new_arr = CFArrayCreateMutableCopy(0, 0, arr);
CFArrayAppendValue(new_arr, package);
CFArraySortValues(
new_arr, CFRangeMake(0, CFArrayGetCount(new_arr)),
(CFComparatorFunction)compare_dates, 0);
} else
{
new_arr = (CFMutableArrayRef)CFArrayCreate(
0, (const void **)&package,
1, &kCFTypeArrayCallBacks);
}
if(!new_arr)
{
ret = false;
goto exit;
}
if(!SCPreferencesSetValue(prefs, type, new_arr))
{
ret = false;
goto exit;
}
ret = true;
exit:
if(new_arr) CFRelease(new_arr);
return ret;
}
static bool removeEntryAndSetPrefs(
SCPreferencesRef prefs,
CFStringRef type,
CFDictionaryRef package)
{
CFArrayRef arr = 0;
CFMutableArrayRef mut_arr = 0;
CFIndex i;
CFDictionaryRef cancelee = 0;
bool ret = false;
arr = isA_CFArray(SCPreferencesGetValue(prefs, type));
if(!arr)
{
ret = true;
goto exit;
}
i = CFArrayBSearchValues(arr, CFRangeMake(0, CFArrayGetCount(arr)), package,
(CFComparatorFunction)compare_dates, 0);
if( (0 <= i) && (i < CFArrayGetCount(arr)) )
{
cancelee = CFArrayGetValueAtIndex(arr, i);
if(kCFCompareEqualTo == compare_dates(package, cancelee, 0))
{
if( CFEqual(
CFDictionaryGetValue(package, CFSTR(kIOPMPowerEventAppNameKey)),
CFDictionaryGetValue(cancelee, CFSTR(kIOPMPowerEventAppNameKey))
))
{
mut_arr = CFArrayCreateMutableCopy(0, 0, arr);
CFArrayRemoveValueAtIndex(mut_arr, i);
if(!SCPreferencesSetValue(prefs, type, mut_arr))
{
ret = false;
goto exit;
}
ret = true;
}
}
}
exit:
if(mut_arr) CFRelease(mut_arr);
return ret;
}
static IOReturn
_setRootDomainProperty(
CFStringRef key,
CFTypeRef val)
{
io_iterator_t it;
io_registry_entry_t root_domain;
IOReturn ret;
IOServiceGetMatchingServices(
MACH_PORT_NULL,
IOServiceNameMatching("IOPMrootDomain"),
&it);
if(!it) return kIOReturnError;
root_domain = (io_registry_entry_t)IOIteratorNext(it);
if(!root_domain) return kIOReturnError;
ret = IORegistryEntrySetCFProperty(root_domain, key, val);
IOObjectRelease(root_domain);
IOObjectRelease(it);
return ret;
}
static void
tellClockController(
CFStringRef command,
CFDateRef power_date)
{
CFAbsoluteTime now, wake_time;
CFGregorianDate gmt_calendar;
CFTimeZoneRef gmt_tz = NULL;
long int diff_secs;
IOReturn ret;
CFNumberRef seconds_delta = NULL;
IOPMCalendarStruct *cal_date = NULL;
CFMutableDataRef date_data = NULL;
if(!command) goto exit;
date_data = CFDataCreateMutable(NULL, sizeof(IOPMCalendarStruct));
CFDataSetLength(date_data, sizeof(IOPMCalendarStruct));
cal_date = (IOPMCalendarStruct *)CFDataGetBytePtr(date_data);
bzero(cal_date, sizeof(IOPMCalendarStruct));
if(!power_date) {
} else {
wake_time = CFDateGetAbsoluteTime(power_date);
gmt_tz = CFTimeZoneCreateWithTimeIntervalFromGMT(0, 0.0);
gmt_calendar = CFAbsoluteTimeGetGregorianDate(wake_time, gmt_tz);
CFRelease(gmt_tz);
cal_date->second = lround(gmt_calendar.second);
cal_date->minute = gmt_calendar.minute;
cal_date->hour = gmt_calendar.hour;
cal_date->day = gmt_calendar.day;
cal_date->month = gmt_calendar.month;
cal_date->year = gmt_calendar.year;
}
if(CFEqual(command, CFSTR(kIOPMAutoWake))) {
ret = _setRootDomainProperty(
CFSTR(kIOPMSettingAutoWakeCalendarKey),
date_data);
} else {
ret = _setRootDomainProperty(
CFSTR(kIOPMSettingAutoPowerCalendarKey),
date_data);
}
if(kIOReturnSuccess != ret) {
goto exit;
}
if(!power_date) {
diff_secs = 0;
} else {
now = CFAbsoluteTimeGetCurrent();
wake_time = CFDateGetAbsoluteTime(power_date);
diff_secs = lround(wake_time - now);
if(diff_secs < 0) goto exit;
}
seconds_delta = CFNumberCreate(0, kCFNumberLongType, &diff_secs);
if(!seconds_delta) goto exit;
if(CFEqual(command, CFSTR(kIOPMAutoWake))) {
ret = _setRootDomainProperty(
CFSTR(kIOPMSettingAutoWakeSecondsKey),
seconds_delta);
} else {
ret = _setRootDomainProperty(
CFSTR(kIOPMSettingAutoPowerSecondsKey),
seconds_delta);
}
if(kIOReturnSuccess != ret) {
goto exit;
}
exit:
if(date_data) CFRelease(date_data);
if(seconds_delta) CFRelease(seconds_delta);
return;
}
IOReturn IOPMSchedulePowerEvent(
CFDateRef time_to_wake,
CFStringRef my_id,
CFStringRef type)
{
CFDictionaryRef package = 0;
SCPreferencesRef prefs = 0;
IOReturn ret = kIOReturnError;
CFArrayRef tmp_wakeup_arr = NULL;
int total_count = 0;
CFAbsoluteTime abs_time_to_wake;
if(!inputsValid(time_to_wake, my_id, type))
{
ret = kIOReturnBadArgument;
goto exit;
}
if( CFEqual(type, CFSTR(kIOPMAutoWakeScheduleImmediate)) )
{
tellClockController(CFSTR(kIOPMAutoWake), time_to_wake);
ret = kIOReturnSuccess;
goto exit;
} else if( CFEqual(type, CFSTR(kIOPMAutoPowerScheduleImmediate)) )
{
tellClockController(CFSTR(kIOPMAutoPowerOn), time_to_wake);
ret = kIOReturnSuccess;
goto exit;
} else if( CFEqual( type, CFSTR( kIOPMAutoWakeRelativeSeconds) )
|| CFEqual( type, CFSTR( kIOPMAutoPowerRelativeSeconds) ) )
{
CFAbsoluteTime now_secs;
CFAbsoluteTime event_secs;
CFNumberRef diff_secs = NULL;
int diff;
if(time_to_wake)
{
now_secs = CFAbsoluteTimeGetCurrent();
event_secs = CFDateGetAbsoluteTime(time_to_wake);
diff = (int)event_secs - (int)now_secs;
if(diff <= 0)
{
return kIOReturnIsoTooOld;
}
} else {
diff = 0;
}
diff_secs = CFNumberCreate(0, kCFNumberIntType, &diff);
if(!diff_secs) goto exit;
_setRootDomainProperty( type, (CFTypeRef)diff_secs );
CFRelease(diff_secs);
ret = kIOReturnSuccess;
goto exit;
}
abs_time_to_wake = CFDateGetAbsoluteTime(time_to_wake);
if(abs_time_to_wake < (CFAbsoluteTimeGetCurrent() + 30.0))
{
ret = kIOReturnNotReady;
goto exit;
}
package = _IOPMCreatePowerOnDictionary(abs_time_to_wake, my_id, type);
prefs = SCPreferencesCreate( 0, CFSTR("IOKit-AutoWake"),
CFSTR(kIOPMAutoWakePrefsPath));
if(!prefs || !SCPreferencesLock(prefs, true))
{
if(kSCStatusAccessError == SCError())
ret = kIOReturnNotPrivileged;
else ret = kIOReturnError;
goto exit;
}
total_count = 0;
tmp_wakeup_arr = isA_CFArray(
SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoPowerOn)));
if(tmp_wakeup_arr) total_count += CFArrayGetCount(tmp_wakeup_arr);
tmp_wakeup_arr = isA_CFArray(
SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoWake)));
if(tmp_wakeup_arr) total_count += CFArrayGetCount(tmp_wakeup_arr);
tmp_wakeup_arr = isA_CFArray(
SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoWakeOrPowerOn)));
if(tmp_wakeup_arr) total_count += CFArrayGetCount(tmp_wakeup_arr);
tmp_wakeup_arr = isA_CFArray(
SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoSleep)));
if(tmp_wakeup_arr) total_count += CFArrayGetCount(tmp_wakeup_arr);
tmp_wakeup_arr = isA_CFArray(
SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoShutdown)));
if(tmp_wakeup_arr) total_count += CFArrayGetCount(tmp_wakeup_arr);
tmp_wakeup_arr = isA_CFArray(
SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoRestart)));
if(tmp_wakeup_arr) total_count += CFArrayGetCount(tmp_wakeup_arr);
if(total_count >= kIOPMMaxScheduledEntries)
{
ret = kIOReturnNoSpace;
goto exit;
}
addEntryAndSetPrefs(prefs, type, package);
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;
}
ret = kIOReturnSuccess;
exit:
if(package) CFRelease(package);
if(prefs) SCPreferencesUnlock(prefs);
if(prefs) CFRelease(prefs);
return ret;
}
IOReturn IOPMCancelScheduledPowerEvent(
CFDateRef time_to_wake,
CFStringRef my_id,
CFStringRef wake_or_restart)
{
CFDictionaryRef package = 0;
SCPreferencesRef prefs = 0;
bool changed = false;
IOReturn ret = kIOReturnError;
if(!inputsValid(time_to_wake, my_id, wake_or_restart))
{
ret = kIOReturnBadArgument;
goto exit;
}
package = _IOPMCreatePowerOnDictionary(
CFDateGetAbsoluteTime(time_to_wake),
my_id, wake_or_restart);
if(!package) goto exit;
prefs = SCPreferencesCreate( 0, CFSTR("IOKit-AutoWake"),
CFSTR(kIOPMAutoWakePrefsPath));
if(!prefs || !SCPreferencesLock(prefs, true))
{
if(kSCStatusAccessError == SCError())
ret = kIOReturnNotPrivileged;
else ret = kIOReturnError;
goto exit;
}
changed = removeEntryAndSetPrefs(prefs, wake_or_restart, package);
if(changed)
{
if(!SCPreferencesCommitChanges(prefs))
{
ret = kIOReturnError;
goto exit;
} else ret = kIOReturnSuccess;
} else ret = kIOReturnNotFound;
exit:
if(package) CFRelease(package);
if(prefs) SCPreferencesUnlock(prefs);
if(prefs) CFRelease(prefs);
return ret;
}
CFArrayRef IOPMCopyScheduledPowerEvents(void)
{
SCPreferencesRef prefs;
CFArrayRef wake_arr;
CFArrayRef poweron_arr;
CFArrayRef wakeorpoweron_arr;
CFArrayRef sleep_arr;
CFArrayRef shutdown_arr;
CFArrayRef restart_arr;
CFMutableArrayRef new_arr;
prefs = SCPreferencesCreate(0, CFSTR("IOKit-AutoWake"), CFSTR(kIOPMAutoWakePrefsPath));
if(!prefs) return NULL;
wake_arr = isA_CFArray(SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoWake)));
poweron_arr = isA_CFArray(SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoPowerOn)));
wakeorpoweron_arr = isA_CFArray(SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoWakeOrPowerOn)));
sleep_arr = isA_CFArray(SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoSleep)));
shutdown_arr = isA_CFArray(SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoShutdown)));
restart_arr = isA_CFArray(SCPreferencesGetValue(prefs, CFSTR(kIOPMAutoRestart)));
new_arr = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if(wake_arr) {
CFArrayAppendArray(new_arr, wake_arr,
CFRangeMake(0, CFArrayGetCount(wake_arr)));
}
if(poweron_arr) {
CFArrayAppendArray(new_arr, poweron_arr,
CFRangeMake(0, CFArrayGetCount(poweron_arr)));
}
if(wakeorpoweron_arr) {
CFArrayAppendArray(new_arr, wakeorpoweron_arr,
CFRangeMake(0, CFArrayGetCount(wakeorpoweron_arr)));
}
if(sleep_arr) {
CFArrayAppendArray(new_arr, sleep_arr,
CFRangeMake(0, CFArrayGetCount(sleep_arr)));
}
if(shutdown_arr) {
CFArrayAppendArray(new_arr, shutdown_arr,
CFRangeMake(0, CFArrayGetCount(shutdown_arr)));
}
if(restart_arr) {
CFArrayAppendArray(new_arr, restart_arr,
CFRangeMake(0, CFArrayGetCount(restart_arr)));
}
CFRelease(prefs);
if(!new_arr)
{
return NULL;
} else {
if(0 == CFArrayGetCount(new_arr))
{
CFRelease(new_arr);
return NULL;
}
}
return (CFArrayRef)new_arr;
}