source: trunk/Cocoa/Pester/Source/PSPowerManager.m@ 118

Last change on this file since 118 was 118, checked in by Nicholas Riley, 21 years ago

Broken, to-be-removed authorization implementation

File size: 12.7 KB
RevLine 
[53]1//
2// PSPowerManager.m
3// Pester
4//
5// Created by Nicholas Riley on Mon Dec 23 2002.
6// Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "PSPowerManager.h"
[118]10#import "wakein.h"
[53]11
12#import <IOKit/pwr_mgt/IOPMLib.h>
13#import <IOKit/IOMessage.h>
14#import <CoreFoundation/CoreFoundation.h>
15
[118]16// MoreIsBetter interfaces
17#include "MoreUNIX.h"
18#include "MoreSecurity.h"
19#include "MoreCFQ.h"
20
21// exceptions
22NSString * const PSPowerManagerException = @"PSPowerManagerException";
23
[53]24/*
25 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
26 *
27 * @APPLE_LICENSE_HEADER_START@
28 *
29 * The contents of this file constitute Original Code as defined in and
30 * are subject to the Apple Public Source License Version 1.1 (the
31 * "License"). You may not use this file except in compliance with the
32 * License. Please obtain a copy of the License at
33 * http://www.apple.com/publicsource and read it before using this file.
34 *
35 * This Original Code and all software distributed under the License are
36 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
37 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
38 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
39 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
40 * License for the specific language governing rights and limitations
41 * under the License.
42 *
43 * @APPLE_LICENSE_HEADER_END@
44 */
45
46/* Sample code to set an automatic wakeup timer to wake machines from sleep.
47
48When a machine is asleep, most hardware (including the processor) is
49powered off. The PMU chip is one of the few things left powered on, and it's
50able to generate a wakeup event on a timer.
51This code shows how to set the wakeup timer within the PMU.
52*/
53
54// From autowake.cpp:
55
56// #define kAppleVIAUserClientMagicCookie 0x101face // or 0x101beef -- for PMU
57// #define kAppleVIAUserClientMagicCookie 0x101beef // or 0x101face -- for PMU
58
59// The difference is 101beef is only for superusers and 101face works for
60// non-privileged users. I have not determined which calls are only available
61// for superusers
62
[118]63// njr 2003.03.12: both seem to be interchangeable now, and the sleep time is
64// no longer settable as non-root.
[53]65
[118]66#ifdef WAKEIN
67#define PMU_MAGIC_PASSWORD 0x0101BEEF
68#else
69#define PMU_MAGIC_PASSWORD 0x0101FACE
70#endif
71
[53]72/* ==========================================
73* Close a device user client
74* =========================================== */
75static kern_return_t
76closeDevice(io_connect_t con)
77{
78 kern_return_t ret = IOServiceClose(con);
79
80 NSCAssert1(ret == kIOReturnSuccess, @"closeDevice: IOServiceClose returned an error of type %08lx", (unsigned long)ret);
81
82 return ret;
83}
84
85/* ==========================================
86* Open an IORegistry device user client
87* =========================================== */
88static void
89openDevice(io_object_t obj, unsigned int type, io_connect_t * con)
90{
91 kern_return_t ret = IOServiceOpen(obj, mach_task_self(), type, con);
92
93 NSCAssert1(ret == kIOReturnSuccess, @"openDevice: IOServiceOpen returned an error of type %08lx", (unsigned long)ret);
94}
95
96/* ===========================================
97* Changes the string for a registry
98* property.
99* =========================================== */
100void
101writeDataProperty(io_object_t handle, CFStringRef name,
102 unsigned char * bytes, unsigned int size)
103{
104 kern_return_t kr = kIOReturnNoMemory;
105 CFDataRef data;
106 CFMutableDictionaryRef dict = 0;
107
108 data = CFDataCreate(kCFAllocatorDefault, bytes, size);
109 NSCAssert(data != NULL, @"writeDataProperty: CFDataCreate failed");
110 [(NSData *)data autorelease];
111
112 dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
113 NSCAssert(data != NULL, @"writeDataProperty: CFDictionaryCreateMutable failed");
114 [(NSMutableDictionary *)dict autorelease];
115
116 CFDictionarySetValue(dict, name, data);
117 kr = IOConnectSetCFProperties(handle, dict);
118 NSCAssert1(kr == KERN_SUCCESS, @"writeDataProperty: IOConnectSetCFProperties returned an error of type %08lx", (unsigned long)kr);
119}
120
121/* ==========================================
122* Write a data property to the PMU driver
123* Arguments
124* pmuReference - the IORegistry device to write to
125* propertyName - Name of the property to write to
126* data - Data to write
127* dataSize - Data size
128* =========================================== */
129void
130writePMUProperty(io_object_t pmuReference, CFStringRef propertyName, void *data, size_t dataSize)
131{
132 io_connect_t conObj;
133 openDevice(pmuReference, PMU_MAGIC_PASSWORD, &conObj);
134 writeDataProperty(conObj, propertyName, (unsigned char *)data, dataSize);
135 closeDevice(conObj);
136}
137
138
139/* ==========================================
140* Look through the registry and search for an
141* IONetworkInterface objects with the given
142* name.
143* If a match is found, the object is returned.
144* =========================================== */
145
146io_service_t
147getInterfaceWithName(mach_port_t masterPort, char *className)
148{
149 io_service_t obj;
150
151 obj = IOServiceGetMatchingService(masterPort, IOServiceMatching(className));
152
153 NSCAssert(obj != NULL, @"getInterfaceWithName: IOServiceGetMatchingService returned NULL");
154
155 return obj;
156}
157
158/* ==========================================
159* Find the PMU in the IORegistry
160* =========================================== */
161io_service_t
162openPMUComPort(void)
163{
164 static mach_port_t masterPort;
165 kern_return_t kr;
166
167 // Get a master port to talk with the mach_kernel
168 kr = IOMasterPort(bootstrap_port, &masterPort);
169 NSCAssert1(kr == KERN_SUCCESS, @"openPMUComPort: IOMasterPort returned an error of type %08lx", (unsigned long)kr);
170
171 return getInterfaceWithName(masterPort, "ApplePMU");
172}
173
174
175/* ==========================================
176* Release our reference to the PMU in the IORegistry
177* =========================================== */
178void
179closePMUComPort(io_object_t pmuRef)
180{
181 IOObjectRelease(pmuRef);
182}
183
184@implementation PSPowerManager
185
186+ (BOOL)autoWakeSupported;
187{
188 io_service_t pmuReference = openPMUComPort();
189 if (pmuReference == NULL) return NO;
190 closePMUComPort(pmuReference);
[118]191 NS_DURING
192 [self authorize];
193 NS_HANDLER
194 return NO; // XXX display error?
195 NS_ENDHANDLER
[53]196 return YES;
197}
198
[103]199+ (io_service_t)_pmuReference;
200{
201 io_service_t pmuReference = openPMUComPort();
202 NSAssert(pmuReference != NULL, NSLocalizedString(@"Couldn't find PMU in IORegistry. This computer may not support automatic wake from sleep.", "Assertion message: couldn't open ApplePMU"));
[105]203 return pmuReference;
[103]204}
205
[53]206+ (NSDate *)wakeTime;
207{
[103]208 io_service_t pmuReference = [self _pmuReference];
[53]209 NSNumber *autoWakeTime;
210 unsigned long long rawWakeTime;
211
212 autoWakeTime = (NSNumber *)IORegistryEntryCreateCFProperty(pmuReference, CFSTR("AutoWake"), NULL, 0);
213 closePMUComPort(pmuReference);
214
215 if (autoWakeTime == nil) return nil;
216 rawWakeTime = [autoWakeTime unsignedLongLongValue];
217 if (rawWakeTime == 0) return nil;
218 // XXX no idea what the epoch is supposed to be, but this works...
[118]219 return [NSDate dateWithTimeIntervalSinceReferenceDate: rawWakeTime - [[NSTimeZone systemTimeZone] secondsFromGMT] - 18446744072475736320LLU];
[53]220}
221
[118]222+ (void)_execWakeToolWithRequestDictionary:(NSDictionary *)request;
[53]223{
[118]224 AuthorizationRef auth = NULL;
225 NSException *exception = NULL;
226
227 NS_DURING
228 CFURLRef tool = NULL;
229 Boolean toolFound;
230 OSStatus err = MoreSecCopyHelperToolURLAndCheckBundled(
231 CFBundleGetMainBundle(), CFSTR("wakeinTemplate"), kApplicationSupportFolderType, CFSTR("Pester"), CFSTR("wakein"), &tool, &toolFound);
232 if (err != noErr) [NSException raise: PSPowerManagerException format: NSLocalizedString(@"Can't set up timed wake from sleep: unable to copy helper tool to Application Support folder (error %u)", "MoreSecCopyHelperToolURLAndCheckBundled failure"), err];
233 [(NSURL *)tool autorelease];
234
235 // if we've found the tool (and it's setuid root), still get an AuthorizationRef, but don't bother to obtain additional rights
236 err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, toolFound ? kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed : kAuthorizationFlagDefaults, &auth);
237 if (err != noErr) [NSException raise: PSPowerManagerException format: NSLocalizedString(@"Can't set up timed wake from sleep: AuthorizationCreate failed (error %u)", "AuthorizationCreate failure"), err];
238
239 NSDictionary *response;
240 err = MoreSecExecuteRequestInHelperTool(tool, auth, (CFDictionaryRef)request, (CFDictionaryRef *)&response);
241 [response autorelease];
242 if (err != noErr) [NSException raise: PSPowerManagerException format: NSLocalizedString(@"Can't set up timed wake from sleep: can't obtain response from helper tool (error %u)", "MoreSecExecuteRequestInHelperTool failure"), err];
243
244 NSLog(@"%@", response);
245
246 NSString *wakeinException = [response objectForKey: kPesterWakeException];
247 if (wakeinException != nil) [NSException raise: PSPowerManagerException format: NSLocalizedString(@"Can't set up timed wake from sleep: helper tool reported the error '%@'", "kPesterWakeException"), wakeinException];
248
249 err = MoreSecGetErrorFromResponse((CFDictionaryRef)response);
250 if (err != noErr) [NSException raise: PSPowerManagerException format: NSLocalizedString(@"Can't set up timed wake from sleep: helper tool reported an error of type %u", "MoreSecGetErrorFromResponse"), err];
251
252 NS_HANDLER
253 exception = localException;
254 NS_ENDHANDLER
255
256 if (auth != NULL) AuthorizationFree(auth, kAuthorizationFlagDestroyRights);
257 if (exception != NULL) [exception raise];
258}
259
260+ (void)authorize;
261{
262 [self _execWakeToolWithRequestDictionary: [NSDictionary dictionary]];
263}
264
265+ (void)setWakeInterval:(unsigned long)wakeInterval;
266{
267#ifdef WAKEIN
[103]268 io_service_t pmuReference = [self _pmuReference];
[118]269 NSLog(@"writePMUProperty[%u] 0x%lX AutoWake = %lu", pmuReference, PMU_MAGIC_PASSWORD, wakeInterval);
270 writePMUProperty(pmuReference, CFSTR("AutoWake"), (unsigned long *)&wakeInterval, sizeof(wakeInterval));
[53]271
272 closePMUComPort(pmuReference);
[118]273#else
274 [self _execWakeToolWithRequestDictionary: [NSDictionary dictionaryWithObject: [NSNumber numberWithUnsignedLong: wakeInterval] forKey: kPesterWakeTime]];
275#endif
[53]276}
277
[118]278+ (void)setWakeTime:(NSDate *)time overrideIfEarlier:(BOOL)override;
279{
280 unsigned long wakeInterval;
281
282 if (time == nil) {
283 wakeInterval = 0;
284 override = YES;
285 } else {
286 wakeInterval = [time timeIntervalSinceNow];
287 if (wakeInterval == 0) wakeInterval++; // 0 will disable
288 if (!override) {
289 NSDate *wakeTime = [self wakeTime];
290 override = (wakeTime == nil || [wakeTime compare: time] == NSOrderedDescending);
291 }
292 }
293
294 if (override) {
295 [self setWakeInterval: wakeInterval];
296 }
297}
298
[53]299+ (void)clearWakeTime;
300{
[118]301 [self setWakeTime: nil overrideIfEarlier: YES];
[53]302}
303
304// modified from RegisterForSleep sample code
305
306- (void)_messageReceived:(natural_t)messageType withArgument:(void *)messageArgument;
307{
308 switch (messageType) {
309 case kIOMessageSystemWillSleep:
[61]310 if ([delegate respondsToSelector: @selector(powerManagerWillDemandSleep:)]) {
311 [delegate powerManagerWillDemandSleep: self];
312 IOAllowPowerChange(root_port, (long)messageArgument);
313 }
[53]314 break;
315 case kIOMessageCanSystemSleep:
[61]316 if ([delegate respondsToSelector: @selector(powerManagerShouldIdleSleep:)]) {
317 if ([delegate powerManagerShouldIdleSleep: self]) {
[53]318 IOAllowPowerChange(root_port, (long)messageArgument);
319 } else {
320 IOCancelPowerChange(root_port, (long)messageArgument);
321 }
322 }
323 break;
324 case kIOMessageSystemHasPoweredOn:
325 if ([delegate respondsToSelector: @selector(powerManagerDidWake:)])
326 [delegate powerManagerDidWake: self];
327 break;
328 }
329}
330
331void
332powerCallback(void *refCon, io_service_t service, natural_t messageType, void *messageArgument)
333{
334 [(PSPowerManager *)refCon _messageReceived: messageType withArgument: messageArgument];
335}
336
337- (id)initWithDelegate:(id)aDelegate;
338{
339 if ( (self = [super init]) != nil) {
340 IONotificationPortRef notificationPort;
341
342 delegate = [aDelegate retain];
343 root_port = IORegisterForSystemPower(self, &notificationPort, powerCallback, &notifier);
344 NSAssert(root_port != NULL, @"IORegisterForSystemPower failed");
345
346 CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notificationPort), kCFRunLoopDefaultMode);
347 }
348 return self;
349}
350
351- (void)dealloc;
352{
353 IODeregisterForSystemPower(&notifier);
354 [delegate release];
355 [super dealloc];
356}
357
358@end
Note: See TracBrowser for help on using the repository browser.