#if !TARGET_OS_EMBEDDED
#include <sys/syslog.h>
#include <syslog.h>
#include "PrivateLib.h"
#include "PSLowPower.h"
#include "PMSettings.h"
#define kHaltEnabled 0
#define kHaltValue 1
typedef struct {
int haltafter[2];
int haltremain[2];
int haltpercent[2];
} threshold_struct;
#ifndef _IOKIT_PM_IOUPSPRIVATE_H_
Boolean IOUPSMIGServerIsRunning(mach_port_t * bootstrap_port_ref, mach_port_t * upsd_port_ref);
IOReturn IOUPSSendCommand(mach_port_t connect, int upsID, CFDictionaryRef command);
IOReturn IOUPSGetEvent(mach_port_t connect, int upsID, CFDictionaryRef *event);
IOReturn IOUPSGetCapabilities(mach_port_t connect, int upsID, CFSetRef *capabilities);
#endif
static const int _delayedRemovePowerMinutes = 3;
static const int _delayBeforeStartupMinutes = 4;
static CFAbsoluteTime _switchedToUPSPowerTime = 0.0;
static threshold_struct *_thresh;
#if HAVE_CF_USER_NOTIFICATION
static CFUserNotificationRef _UPSAlert = NULL;
#endif
static int _upsSupports(CFNumberRef whichUPS, CFStringRef command);
static int _upsCommand(CFNumberRef whichUPS, CFStringRef command, int arg);
static int _threshEnabled(CFDictionaryRef dynamo);
static int _threshValue(CFDictionaryRef dynamo);
static int _minutesSpentOnUPSPower(void);
static int _secondsSpentOnUPSPower(void);
static bool _weManageUPSPower(void);
static void _getUPSShutdownThresholdsFromDisk(threshold_struct *thresho);
static void _itIsLaterNow(CFRunLoopTimerRef tmr, void *info);
static void _reEvaluatePowerSourcesLater(int seconds);
static void _doPowerEmergencyShutdown(CFNumberRef ups_id);
enum {
_kIOUPSInternalPowerBit,
_kIOUPSExternalPowerBit
};
__private_extern__ void PSLowPower_prime(void)
{
_thresh = (threshold_struct *)malloc(sizeof(threshold_struct));
_getUPSShutdownThresholdsFromDisk(_thresh);
return;
}
__private_extern__ void
PSLowPowerPrefsHaveChanged(void)
{
if(_thresh)
{
_getUPSShutdownThresholdsFromDisk(_thresh);
}
}
__private_extern__ void
PSLowPowerPSChange(CFTypeRef ps_blob)
{
CFTypeRef ups = NULL;
CFDictionaryRef ups_info = 0;
int t1, t2;
CFNumberRef n1, n2;
int minutes_remaining;
int percent_remaining;
CFBooleanRef isPresent;
static int last_ups_power_source = _kIOUPSExternalPowerBit;
CFStringRef power_source = 0;
CFNumberRef ups_id = 0;
if(!_weManageUPSPower()) {
goto _exit_PowerSourcesHaveChanged_;
}
if((ups = IOPSGetActiveUPS(ps_blob)))
{
ups_info = isA_CFDictionary(IOPSGetPowerSourceDescription(ps_blob, ups));
if(!ups_info) goto _exit_PowerSourcesHaveChanged_;
ups_id = isA_CFNumber(CFDictionaryGetValue(ups_info, CFSTR(kIOPSPowerSourceIDKey)));
if(!ups_id) goto _exit_PowerSourcesHaveChanged_;
isPresent = isA_CFBoolean(CFDictionaryGetValue(ups_info, CFSTR(kIOPSIsPresentKey)));
if(!isPresent || !CFBooleanGetValue(isPresent))
{
#if HAVE_CF_USER_NOTIFICATION
if(_UPSAlert)
{
CFUserNotificationCancel(_UPSAlert);
_UPSAlert = 0;
}
#endif
goto _exit_PowerSourcesHaveChanged_;
}
power_source = isA_CFString(CFDictionaryGetValue(ups_info, CFSTR(kIOPSPowerSourceStateKey)));
if(!power_source || !CFEqual(power_source, CFSTR(kIOPSBatteryPowerValue)))
{
#if HAVE_CF_USER_NOTIFICATION
if(_UPSAlert)
{
CFUserNotificationCancel(_UPSAlert);
_UPSAlert = 0;
}
#endif
goto _exit_PowerSourcesHaveChanged_;
}
if(_kIOUPSExternalPowerBit == last_ups_power_source)
{
_switchedToUPSPowerTime = CFAbsoluteTimeGetCurrent();
if( _thresh->haltafter[kHaltEnabled] ) {
_reEvaluatePowerSourcesLater(5 + (60*_thresh->haltafter[kHaltValue]));
}
#if HAVE_CF_USER_NOTIFICATION
if(!_UPSAlert) _UPSAlert = _copyUPSWarning();
#endif
}
if(kCFBooleanTrue == IOPSPowerSourceSupported(ps_blob, CFSTR(kIOPMBatteryPowerKey)))
{
goto _exit_PowerSourcesHaveChanged_;
}
if(_secondsSpentOnUPSPower() < 10) {
_reEvaluatePowerSourcesLater(10);
goto _exit_PowerSourcesHaveChanged_;
}
n1 = isA_CFNumber(CFDictionaryGetValue(ups_info, CFSTR(kIOPSCurrentCapacityKey)));
n2 = isA_CFNumber(CFDictionaryGetValue(ups_info, CFSTR(kIOPSMaxCapacityKey)));
if(n1 && n2)
{
if( CFNumberGetValue(n1, kCFNumberIntType, &t1) &&
CFNumberGetValue(n2, kCFNumberIntType, &t2)) {
percent_remaining = (int)(100.0* ((double)t1) / ((double)t2) );
if( _thresh->haltpercent[kHaltEnabled] ) {
if( percent_remaining <= _thresh->haltpercent[kHaltValue] ) {
_doPowerEmergencyShutdown(ups_id);
}
}
}
}
n1 = isA_CFNumber(CFDictionaryGetValue(ups_info, CFSTR(kIOPSTimeToEmptyKey)));
if(n1)
{
if(CFNumberGetValue(n1, kCFNumberIntType, &minutes_remaining))
{
if( _thresh->haltremain[kHaltEnabled] ) {
if( minutes_remaining <= _thresh->haltremain[kHaltValue] ) {
_doPowerEmergencyShutdown(ups_id);
}
}
}
}
if( _thresh->haltafter[kHaltEnabled] ) {
if(_minutesSpentOnUPSPower() >= _thresh->haltafter[kHaltValue]) {
_doPowerEmergencyShutdown(ups_id);
}
}
} else {
#if HAVE_CF_USER_NOTIFICATION
if(_UPSAlert)
{
CFUserNotificationCancel(_UPSAlert);
_UPSAlert = 0;
}
#endif
}
_exit_PowerSourcesHaveChanged_:
if(power_source && CFEqual(power_source, CFSTR(kIOPSBatteryPowerValue))) {
last_ups_power_source = _kIOUPSInternalPowerBit;
} else {
last_ups_power_source = _kIOUPSExternalPowerBit;
}
return;
}
static void
_doPowerEmergencyShutdown(CFNumberRef ups_id)
{
static int _alreadyShuttingDown = 0;
CFDictionaryRef _ESSettings = NULL;
char *shutdown_argv[3];
CFNumberRef auto_restart;
IOReturn error;
bool upsRestart = false;
int restart_setting;
if(_alreadyShuttingDown)
return;
_alreadyShuttingDown = 1;
syslog(LOG_INFO, "Performing emergency UPS low power shutdown now");
_ESSettings = PMSettings_CopyActivePMSettings();
if(!_ESSettings)
goto shutdown;
auto_restart = isA_CFNumber(CFDictionaryGetValue(_ESSettings, CFSTR(kIOPMRestartOnPowerLossKey)));
if(auto_restart) {
CFNumberGetValue(auto_restart, kCFNumberIntType, &restart_setting);
} else {
restart_setting = 0;
}
upsRestart = restart_setting ? true:false;
if(!upsRestart)
{
goto shutdown;
}
if(_upsSupports(ups_id, CFSTR(kIOPSCommandDelayedRemovePowerKey)))
{
syslog(LOG_INFO, "System will restart when external power is restored to UPS.");
error = _upsCommand(ups_id,
CFSTR(kIOPSCommandStartupDelayKey),
_delayBeforeStartupMinutes);
if(kIOReturnSuccess != error)
{
syslog(LOG_INFO, "UPS Emergency shutdown: error 0x%08x requesting UPS startup delay of %d minutes\n",
error, _delayBeforeStartupMinutes);
goto shutdown;
}
error = _upsCommand(ups_id, CFSTR(kIOPSCommandDelayedRemovePowerKey), _delayedRemovePowerMinutes);
if(kIOReturnSuccess != error)
{
syslog(LOG_INFO, "UPS Emergency shutdown: error 0x%08x communicating shutdown time to UPS\n", error);
goto shutdown;
}
}
shutdown:
if (_ESSettings) {
CFRelease(_ESSettings);
}
shutdown_argv[0] = (char *)"/usr/libexec/upsshutdown";
if(upsRestart) {
shutdown_argv[1] = (char *)"WaitForUPS";
shutdown_argv[2] = NULL;
} else {
shutdown_argv[1] = NULL;
}
_SCDPluginExecCommand(0, 0, 0, 0,
"/usr/libexec/upsshutdown", shutdown_argv);
}
static int
_upsSupports(CFNumberRef whichUPS, CFStringRef command)
{
#ifdef STANDALONE
return true;
#else
mach_port_t bootstrap_port = MACH_PORT_NULL;
mach_port_t connect = MACH_PORT_NULL;
int prop_supported;
CFSetRef cap_set;
int _id;
IOReturn ret;
if (!IOUPSMIGServerIsRunning(&bootstrap_port, &connect))
{
return 0;
}
if(whichUPS) CFNumberGetValue(whichUPS, kCFNumberIntType, &_id);
else _id = 0;
ret = IOUPSGetCapabilities(connect, _id, &cap_set);
if(kIOReturnSuccess != ret) return false;
prop_supported = (int)CFSetContainsValue(cap_set, command);
CFRelease(cap_set);
return prop_supported;
#endif
}
static IOReturn
_upsCommand(CFNumberRef whichUPS, CFStringRef command, int arg)
{
#ifdef STANDALONE
return kIOReturnSuccess;
#else
CFMutableDictionaryRef command_dict;
IOReturn ret = kIOReturnNoMemory;
mach_port_t bootstrap_port = MACH_PORT_NULL;
mach_port_t connect = MACH_PORT_NULL;
CFNumberRef minutes = NULL;
int _id;
if (!IOUPSMIGServerIsRunning(&bootstrap_port, &connect))
{
return kIOReturnNoDevice;
}
if(whichUPS)
CFNumberGetValue(whichUPS, kCFNumberIntType, &_id);
else
_id = 0;
command_dict = CFDictionaryCreateMutable(kCFAllocatorDefault,
0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if(!command_dict)
goto exit;
minutes = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &arg);
if(!minutes)
goto exit;
CFDictionarySetValue(command_dict, command, minutes);
ret = IOUPSSendCommand(connect, _id, command_dict);
exit:
if (minutes)
CFRelease(minutes);
if (command_dict)
CFRelease(command_dict);
return ret;
#endif
}
static int
_threshEnabled(CFDictionaryRef dynamo)
{
return (kCFBooleanTrue == CFDictionaryGetValue(dynamo, CFSTR(kIOUPSShutdownLevelEnabledKey)));
}
static int
_threshValue(CFDictionaryRef dynamo)
{
int val = 0;
CFNumberRef cfnum;
cfnum = isA_CFNumber(CFDictionaryGetValue(dynamo, CFSTR(kIOUPSShutdownLevelValueKey)));
if(cfnum)
CFNumberGetValue(cfnum, kCFNumberIntType, &val);
return val;
}
static bool
_weManageUPSPower(void)
{
static CFStringRef ups_claimed = NULL;
SCDynamicStoreRef ds_ref = NULL;
CFTypeRef temp;
bool ret_val = true;
if(!ups_claimed) {
ups_claimed = SCDynamicStoreKeyCreate(kCFAllocatorDefault, CFSTR("%@%@"), kSCDynamicStoreDomainState, CFSTR(kIOPSUPSManagementClaimed));
}
if( ups_claimed && ds_ref &&
(temp = SCDynamicStoreCopyValue(_getSharedPMDynamicStore(), ups_claimed)) )
{
if(kCFBooleanTrue == temp)
ret_val = false;
CFRelease(temp);
}
return ret_val;
}
static void
_getUPSShutdownThresholdsFromDisk(threshold_struct *thresho)
{
CFDictionaryRef happytown = IOPMCopyUPSShutdownLevels(CFSTR(kIOPMDefaultUPSThresholds));
CFDictionaryRef d;
if(!thresho) goto exit;
thresho->haltafter[kHaltEnabled] = 0;
thresho->haltremain[kHaltEnabled] = 0;
thresho->haltpercent[kHaltEnabled] = 0;
if(!isA_CFDictionary(happytown)) goto exit;
if((d = isA_CFDictionary(CFDictionaryGetValue(happytown,
CFSTR(kIOUPSShutdownAtLevelKey)))))
{
thresho->haltpercent[kHaltEnabled] = _threshEnabled(d);
thresho->haltpercent[kHaltValue] = _threshValue(d);
}
if((d = isA_CFDictionary(CFDictionaryGetValue(happytown,
CFSTR(kIOUPSShutdownAfterMinutesOn)))))
{
thresho->haltafter[kHaltEnabled] = _threshEnabled(d);
thresho->haltafter[kHaltValue] = _threshValue(d);
}
if((d = isA_CFDictionary(CFDictionaryGetValue(happytown,
CFSTR(kIOUPSShutdownAtMinutesLeft)))))
{
thresho->haltremain[kHaltEnabled] = _threshEnabled(d);
thresho->haltremain[kHaltValue] = _threshValue(d);
}
exit:
if(happytown) CFRelease(happytown);
return;
}
static void
_itIsLaterNow(CFRunLoopTimerRef tmr, void *info)
{
CFTypeRef snap = IOPSCopyPowerSourcesInfo();
PSLowPowerPSChange(snap);
CFRelease(snap);
return;
}
static void
_reEvaluatePowerSourcesLater(int seconds)
{
CFRunLoopTimerRef later_tmr;
CFAbsoluteTime when;
when = CFAbsoluteTimeGetCurrent() + (CFTimeInterval)seconds;
later_tmr = CFRunLoopTimerCreate(kCFAllocatorDefault,
when, 0.0, 0, 0, &_itIsLaterNow, 0);
if(later_tmr) {
CFRunLoopAddTimer(CFRunLoopGetCurrent(), later_tmr, kCFRunLoopDefaultMode);
CFRelease(later_tmr);
}
return;
}
static int
_minutesSpentOnUPSPower(void)
{
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
return (int)((now - _switchedToUPSPowerTime)/60);
}
static int
_secondsSpentOnUPSPower(void)
{
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
return (int)((now - _switchedToUPSPowerTime));
}
#endif