1/* 2 * Copyright (c) 2006 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#include <syslog.h> 25#include <bsm/libbsm.h> 26#include "RepeatingAutoWake.h" 27#include "PrivateLib.h" 28#include "AutoWakeScheduler.h" 29 30/* 31 * These are the days of the week as provided by 32 * CFAbsoluteTimeGetDayOfWeek() 33 * 34 35enum { 36 kCFMonday = 1, 37 kCFTuesday = 2, 38 kCFWednesday = 3, 39 kCFThursday = 4, 40 kCFFriday = 5, 41 kCFSaturday = 6, 42 kCFSunday = 7 43}; 44 45 * 46 * In the "days of the week" bitmask, this is the key... 47 * 48 49enum { 50 kBitMaskMonday = 0, 51 kBitMaskTuesday = 1, 52 kBitMaskWednesday = 2, 53 kBitMaskThursday = 3, 54 kBitMaskFriday = 4, 55 kBitMaskSaturday = 5, 56 kBitMaskSunday = 6 57}; 58 59*/ 60 61static CFDictionaryRef repeatingPowerOff = 0; 62static CFDictionaryRef repeatingPowerOn = 0; 63 64 65 66static bool 67is_valid_repeating_dictionary(CFDictionaryRef event) 68{ 69 CFNumberRef tmp_num; 70 CFStringRef tmp_str; 71 72 if(NULL == event) return true; 73 74 if(!isA_CFDictionary(event)) return false; 75 76 tmp_num = (CFNumberRef)CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventTimeKey)); 77 if(!isA_CFNumber(tmp_num)) return false; 78 79 tmp_num = (CFNumberRef)CFDictionaryGetValue(event, CFSTR(kIOPMDaysOfWeekKey)); 80 if(!isA_CFNumber(tmp_num)) return false; 81 82 tmp_str = (CFStringRef)CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventTypeKey)); 83 if(!isA_CFString(tmp_str)) return false; 84 85 if( !CFEqual(tmp_str, CFSTR(kIOPMAutoSleep)) 86 && !CFEqual(tmp_str, CFSTR(kIOPMAutoShutdown)) 87 && !CFEqual(tmp_str, CFSTR(kIOPMAutoWakeOrPowerOn)) 88 && !CFEqual(tmp_str, CFSTR(kIOPMAutoPowerOn)) 89 && !CFEqual(tmp_str, CFSTR(kIOPMAutoWake)) 90 && !CFEqual(tmp_str, CFSTR(kIOPMAutoRestart)) ) 91 { 92 return false; 93 } 94 95 return true; 96} 97 98static int 99getRepeatingDictionaryMinutes(CFDictionaryRef event) 100{ 101 int val; 102 CFNumberRef tmp_num; 103 tmp_num = (CFNumberRef)CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventTimeKey)); 104 CFNumberGetValue(tmp_num, kCFNumberIntType, &val); 105 return val; 106} 107 108static int 109getRepeatingDictionaryDayMask(CFDictionaryRef event) 110{ 111 int val; 112 CFNumberRef tmp_num; 113 tmp_num = (CFNumberRef)CFDictionaryGetValue(event, CFSTR(kIOPMDaysOfWeekKey)); 114 CFNumberGetValue(tmp_num, kCFNumberIntType, &val); 115 return val; 116} 117 118static CFStringRef 119getRepeatingDictionaryType(CFDictionaryRef event) 120{ 121 CFStringRef return_string; 122 123 if(!event) { 124 return CFSTR(""); 125 } 126 127 return_string = isA_CFString( CFDictionaryGetValue( 128 event, CFSTR(kIOPMPowerEventTypeKey)) ); 129 130 // prevent unexpected crashes by returning an empty string rather than NULL 131 if(!return_string) { 132 return CFSTR(""); 133 } 134 135 return return_string; 136} 137 138// returns false if event occurs at 8PM and now it's 10PM 139// returns true if event occurs at 8PM, now it's 9AM 140static bool 141upcomingToday(CFDictionaryRef event, int today_cf) 142{ 143 static const int kAllowScheduleWindowSeconds = 5; 144 uint32_t secondsToday; 145 int secondsScheduled; 146 int days_mask; 147 if(!event) return false; 148 149 // Determine if the scheduled event falls on today's day of week 150 days_mask = getRepeatingDictionaryDayMask(event); 151 if(!(days_mask & (1 << (today_cf-1)))) return false; 152 153 // get gregorian date for right now 154 int hour, minute; 155 CFCalendarDecomposeAbsoluteTime(_gregorian(), CFAbsoluteTimeGetCurrent(), "Hm", &hour, &minute); 156 157 secondsToday = 60 * (hour*60 + minute); 158 secondsScheduled = 60 * getRepeatingDictionaryMinutes(event); 159 160 // Initially, we required a 2 minute safety window before scheduling the next 161 // power event. Now, we throw caution to the wind and try a 5 second window. 162 // Lost events will simply be lost events. 163 if(secondsScheduled >= (secondsToday + kAllowScheduleWindowSeconds)) 164 return true; 165 else 166 return false; 167} 168 169// daysUntil 170// returns 0 if the event is upcoming today 171// otherwise returns days until next repeating event, in range 1-7 172static int 173daysUntil(CFDictionaryRef event, int today_cf_day_of_week) 174{ 175 int days_mask = getRepeatingDictionaryDayMask(event); 176 int check = today_cf_day_of_week % 7; 177 178 if(0 == days_mask) return -1; 179 180 if(upcomingToday(event, today_cf_day_of_week)) return 0; 181 182 // Note: CF days start counting at 1, the bit mask starts counting at 0. 183 // Therefore, since we're tossing the CF day of week into a variable (check) 184 // that we're checking the bitmask with, "check" effectively refers 185 // to tomorrow, whlie today_cf_day_of_week refers to today. 186 while(!(days_mask & (1<<check))) 187 { 188 check = (check + 1) % 7; 189 } 190 check -= today_cf_day_of_week; 191 check++; // adjust for CF day of week (1-7) vs. bitmask day of week (0-6) 192 193 // If the target day is next week, but earlier in the week than today, 194 // check will be negative. Mod of negative is bogus, so we add an extra 195 // 7 days to make all work. 196 197 check += 7; 198 check %= 7; 199 if(check == 0) check = 7; 200 201 return check; 202} 203 204 205/* 206 * Copy Events from on-disk file. We should be doing this only 207 * once, at start of the powerd. 208 */ 209static void 210copyScheduledRepeatPowerEvents(void) 211{ 212 SCPreferencesRef prefs; 213 CFDictionaryRef tmp; 214 215 prefs = SCPreferencesCreate(0, 216 CFSTR("PM-configd-AutoWake"), 217 CFSTR(kIOPMAutoWakePrefsPath)); 218 if(!prefs) return; 219 220 if (repeatingPowerOff) CFRelease(repeatingPowerOff); 221 if (repeatingPowerOn) CFRelease(repeatingPowerOn); 222 223 tmp = (CFDictionaryRef)SCPreferencesGetValue(prefs, CFSTR(kIOPMRepeatingPowerOffKey)); 224 if (tmp && isA_CFDictionary(tmp)) 225 repeatingPowerOff = CFDictionaryCreateMutableCopy(0,0,tmp); 226 227 tmp = (CFDictionaryRef)SCPreferencesGetValue(prefs, CFSTR(kIOPMRepeatingPowerOnKey)); 228 if (tmp && isA_CFDictionary(tmp)) 229 repeatingPowerOn = CFDictionaryCreateMutableCopy(0,0,tmp); 230 231 CFRelease(prefs); 232} 233 234/* 235 * Returns a copy of repeat event after changing the repeat date 236 * into next event date. 237 * 238 * Caller is responsible for releasing the copy after use. 239 */ 240__private_extern__ CFDictionaryRef 241copyNextRepeatingEvent(CFStringRef type) 242{ 243 CFDictionaryRef repeatDict = NULL; 244 CFStringRef repeatDictType = NULL; 245 CFMutableDictionaryRef repeatDictCopy = NULL; 246 CFAbsoluteTime ev_time = 0.0; 247 CFAbsoluteTime adjustedForDays = 0.0; 248 CFDateRef ev_date = NULL; 249 int minutes_scheduled = 0; 250 int year = 0; 251 int month = 0; 252 int day = 0; 253 int day_of_week = 0; 254 int myhour = 0; 255 int myminute = 0; 256 int mysecond = 0; 257 int days_until_event = 0; 258 259 /* 260 * 'WakeOrPowerOn' repeat events are returned when caller asks 261 * for 'Wake' events or 'PowerOn' events. 262 * Don't bother to return anything if caller is looking specifically for 263 * WakeOrPowerOn type repeat events. 264 */ 265 if( CFEqual(type, CFSTR(kIOPMAutoSleep)) 266 || CFEqual(type, CFSTR(kIOPMAutoShutdown)) 267 || CFEqual(type, CFSTR(kIOPMAutoRestart)) ) 268 { 269 repeatDict = repeatingPowerOff; 270 } 271 else if ( 272 CFEqual(type, CFSTR(kIOPMAutoPowerOn)) || 273 CFEqual(type, CFSTR(kIOPMAutoWake)) ) 274 { 275 repeatDict = repeatingPowerOn; 276 } 277 else 278 return NULL; 279 280 repeatDictType = getRepeatingDictionaryType(repeatDict); 281 if (CFEqual(type, repeatDictType) || 282 ( (CFEqual(repeatDictType, CFSTR(kIOPMAutoWakeOrPowerOn))) && 283 (CFEqual(type, CFSTR(kIOPMAutoPowerOn)) || CFEqual(type, CFSTR(kIOPMAutoWake))) 284 ) 285 ) 286 { 287 repeatDictCopy = CFDictionaryCreateMutableCopy(0,0,repeatDict); 288 if (!repeatDictCopy) 289 return NULL; 290 291 CFCalendarDecomposeAbsoluteTime(_gregorian(), CFAbsoluteTimeGetCurrent(), "E", &day_of_week); 292 293 // CFCalendarDecomposeAbsoluteTime starts week with Sunday as "1". 294 // IOPMScheduleRepeatingPowerEvent() is defined to start week with Monday as "1". 295 // Reduce day_of_week by 1 to match with week used by IOPMScheduleRepeatingPowerEvent. 296 297 day_of_week = (day_of_week == 1) ? 7 : --day_of_week; 298 days_until_event = daysUntil(repeatDict, day_of_week); 299 300 adjustedForDays = CFAbsoluteTimeGetCurrent(); 301 CFCalendarAddComponents(_gregorian(), &adjustedForDays, 0, "d", days_until_event); 302 CFCalendarDecomposeAbsoluteTime(_gregorian(), adjustedForDays, "yMd", &year, &month, &day); 303 304 minutes_scheduled = getRepeatingDictionaryMinutes(repeatDict); 305 306 myhour = minutes_scheduled/60; 307 myminute = minutes_scheduled%60; 308 mysecond = 0; 309 310 CFCalendarComposeAbsoluteTime(_gregorian(), &ev_time, "yMdHms", year, month, day, myhour, myminute, mysecond); 311 312 ev_date = CFDateCreate(0, ev_time); 313 if (ev_date) { 314 CFDictionarySetValue(repeatDictCopy, CFSTR(kIOPMPowerEventTimeKey), ev_date); 315 CFDictionarySetValue(repeatDictCopy, CFSTR(kIOPMPowerEventAppNameKey), CFSTR(kIOPMRepeatingAppName)); 316 CFRelease(ev_date); 317 } 318 } 319 320 return repeatDictCopy; 321} 322 323 324__private_extern__ void 325RepeatingAutoWake_prime(void) 326{ 327 copyScheduledRepeatPowerEvents( ); 328 329} 330 331static IOReturn 332updateRepeatEventsOnDisk(SCPreferencesRef prefs) 333{ 334 IOReturn ret = kIOReturnSuccess; 335 336 if (repeatingPowerOn) { 337 if(!SCPreferencesSetValue(prefs, CFSTR(kIOPMRepeatingPowerOnKey), repeatingPowerOn)) 338 { 339 ret = kIOReturnError; 340 goto exit; 341 } 342 } 343 else SCPreferencesRemoveValue(prefs, CFSTR(kIOPMRepeatingPowerOnKey)); 344 345 346 if (repeatingPowerOff) { 347 if(!SCPreferencesSetValue(prefs, CFSTR(kIOPMRepeatingPowerOffKey), repeatingPowerOff)) 348 { 349 ret = kIOReturnError; 350 goto exit; 351 } 352 } 353 else SCPreferencesRemoveValue(prefs, CFSTR(kIOPMRepeatingPowerOffKey)); 354 355 if(!SCPreferencesCommitChanges(prefs)) 356 { 357 ret = kIOReturnError; 358 goto exit; 359 } 360 361exit: 362 return ret; 363} 364 365kern_return_t 366_io_pm_schedule_repeat_event 367( 368 mach_port_t server __unused, 369 audit_token_t token, 370 vm_offset_t flatPackage, 371 mach_msg_type_number_t packageLen, 372 int action, 373 int *return_code 374) 375{ 376 CFDictionaryRef events = NULL; 377 CFDictionaryRef offEvents = NULL; 378 CFDictionaryRef onEvents = NULL; 379 CFDataRef dataRef = NULL; 380 uid_t callerEUID; 381 SCPreferencesRef prefs = 0; 382 CFStringRef prevOffType = NULL; 383 CFStringRef prevOnType = NULL; 384 CFStringRef newOffType = NULL; 385 CFStringRef newOnType = NULL; 386 387 388 *return_code = kIOReturnSuccess; 389 390 audit_token_to_au32(token, NULL, &callerEUID, NULL, NULL, NULL, NULL, NULL, NULL); 391 392 dataRef = CFDataCreate(0, (const UInt8 *)flatPackage, packageLen); 393 if (dataRef) { 394 events = (CFDictionaryRef)CFPropertyListCreateWithData(0, dataRef, 0, NULL, NULL); 395 } 396 397 if (!events) { 398 *return_code = kIOReturnBadArgument; 399 goto exit; 400 } 401 offEvents = isA_CFDictionary(CFDictionaryGetValue( 402 events, 403 CFSTR(kIOPMRepeatingPowerOffKey))); 404 onEvents = isA_CFDictionary(CFDictionaryGetValue( 405 events, 406 CFSTR(kIOPMRepeatingPowerOnKey))); 407 408 if( !is_valid_repeating_dictionary(offEvents) 409 || !is_valid_repeating_dictionary(onEvents) ) 410 { 411 syslog(LOG_INFO, "PMCFGD: Invalid formatted repeating power event dictionary\n"); 412 *return_code = kIOReturnBadArgument; 413 goto exit; 414 } 415 416 if((*return_code = createSCSession(&prefs, callerEUID, 1)) != kIOReturnSuccess) 417 goto exit; 418 419 420 /* Need to take a retain on these strings as these dictionaries get released below */ 421 prevOffType = getRepeatingDictionaryType(repeatingPowerOff); CFRetain(prevOffType); 422 prevOnType = getRepeatingDictionaryType(repeatingPowerOn); CFRetain(prevOnType); 423 424 /* 425 * Remove both off & on events first. If off or on event is not set thru this request, 426 * then it is assumed that user is requesting to delete it. 427 */ 428 if (repeatingPowerOff && isA_CFDictionary(repeatingPowerOff)) 429 CFRelease(repeatingPowerOff); 430 if (repeatingPowerOn && isA_CFDictionary(repeatingPowerOn)) 431 CFRelease(repeatingPowerOn); 432 433 repeatingPowerOff = repeatingPowerOn = NULL; 434 435 436 if (offEvents) { 437 repeatingPowerOff = CFDictionaryCreateMutableCopy(0,0,offEvents); 438 } 439 if (onEvents) { 440 repeatingPowerOn = CFDictionaryCreateMutableCopy(0,0,onEvents); 441 } 442 443 444 if ((*return_code = updateRepeatEventsOnDisk(prefs)) != kIOReturnSuccess) 445 goto exit; 446 447 newOffType = getRepeatingDictionaryType(repeatingPowerOff); 448 newOnType = getRepeatingDictionaryType(repeatingPowerOn); 449 450 451 /* 452 * Re-schedule the modified event types in case these new events are earlier 453 * than previously scheduled ones 454 */ 455 schedulePowerEventType(prevOffType); 456 schedulePowerEventType(prevOnType); 457 458 if (!CFEqual(prevOffType, newOffType)) 459 schedulePowerEventType(newOffType); 460 461 if (!CFEqual(prevOnType, newOnType)) 462 schedulePowerEventType(newOnType); 463 464 465exit: 466 if (prevOffType) 467 CFRelease(prevOffType); 468 if (prevOnType) 469 CFRelease(prevOnType); 470 471 if (dataRef) 472 CFRelease(dataRef); 473 if (events) 474 CFRelease(events); 475 destroySCSession(prefs, 1); 476 477 vm_deallocate(mach_task_self(), flatPackage, packageLen); 478 479 return KERN_SUCCESS; 480} 481 482 483kern_return_t 484_io_pm_cancel_repeat_events 485( 486 mach_port_t server __unused, 487 audit_token_t token, 488 int *return_code 489) 490{ 491 492 SCPreferencesRef prefs = 0; 493 uid_t callerEUID; 494 CFStringRef offType = NULL; 495 CFStringRef onType = NULL; 496 497 *return_code = kIOReturnSuccess; 498 499 audit_token_to_au32(token, NULL, &callerEUID, NULL, NULL, NULL, NULL, NULL, NULL); 500 501 if((*return_code = createSCSession(&prefs, callerEUID, 1)) != kIOReturnSuccess) 502 goto exit; 503 504 505 /* Need to take a retain on these strings as these dictionaries get release below */ 506 offType = getRepeatingDictionaryType(repeatingPowerOff); CFRetain(offType); 507 onType = getRepeatingDictionaryType(repeatingPowerOn); CFRetain(onType); 508 509 if (repeatingPowerOff && isA_CFDictionary(repeatingPowerOff)) 510 CFRelease(repeatingPowerOff); 511 if (repeatingPowerOn && isA_CFDictionary(repeatingPowerOn)) 512 CFRelease(repeatingPowerOn); 513 514 repeatingPowerOff = repeatingPowerOn = NULL; 515 516 if ((*return_code = updateRepeatEventsOnDisk(prefs)) != kIOReturnSuccess) 517 goto exit; 518 519 schedulePowerEventType(offType); 520 schedulePowerEventType(onType); 521 522exit: 523 524 if (offType) 525 CFRelease(offType); 526 if (onType) 527 CFRelease(onType); 528 destroySCSession(prefs, 1); 529 530 return KERN_SUCCESS; 531} 532 533__private_extern__ CFDictionaryRef copyRepeatPowerEvents( ) 534{ 535 536 CFMutableDictionaryRef return_dict = NULL; 537 538 return_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, 539 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 540 541 if (repeatingPowerOn && isA_CFDictionary(repeatingPowerOn)) 542 CFDictionaryAddValue(return_dict, CFSTR(kIOPMRepeatingPowerOnKey), repeatingPowerOn); 543 544 if (repeatingPowerOff && isA_CFDictionary(repeatingPowerOff)) 545 CFDictionaryAddValue(return_dict, CFSTR(kIOPMRepeatingPowerOffKey), repeatingPowerOff); 546 547 return return_dict; 548} 549