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