/* * Portions Copyright (c) 1999-2007 Apple Computer, Inc. All Rights * Reserved. * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. */ /******************************************************************************* * * TTYKeepAwake - active ssh and telnet sessions shall prevent idle sleep. * ******************************************************************************/ #include #include #include #include #include #include "PrivateLib.h" #include "TTYKeepAwake.h" #include "PMSettings.h" #if !TARGET_OS_EMBEDDED #ifndef DEVMAXPATHSIZE #define DEVMAXPATHSIZE 128 #endif // dev_path_t also in kextmanager_types.h but it needs IOKitLib.h :P typedef char dev_path_t[DEVMAXPATHSIZE]; // linked list of ttys typedef struct tl { struct tl *next; dev_path_t ttydev; } ttylist; // Globals static CFRunLoopSourceRef s_source = NULL; static CFMachPortRef s_cfport = NULL; static ttylist *s_activettys = NULL; static int s_token = -1; static uint32_t settingIdleSleepSeconds = 0; static bool settingTTYSPreventSleep = true; // Protos static ttylist *addtty(char *ttyname); static void freettys(void); static bool check_ttys_active( CFArrayRef *preventers ); static void read_logins(CFMachPortRef port, void *m, CFIndex size, void *info); static void cleanup_tty_tracking(void); /* __private_extern__ */ void TTYKeepAwake_prime(void) { CFRunLoopRef rl; mach_port_t mport; int rval = -1; int macherr; rl = CFRunLoopGetCurrent(); if(!rl) goto finish; s_cfport = CFMachPortCreate(nil, read_logins, NULL, false); if (!s_cfport) goto finish; // port refcount: 1 s_source = CFMachPortCreateRunLoopSource(nil, s_cfport, 0); if (!s_source) goto finish; // port AND source refcount: 2 (!) CFRunLoopAddSource(rl, s_source, kCFRunLoopDefaultMode); // NOTIFY_REUSE lets you use your own mach port (vs. creating one) mport = CFMachPortGetPort(s_cfport); if (mport == MACH_PORT_NULL) goto finish; macherr = notify_register_mach_port(UTMPX_CHANGE_NOTIFICATION, &mport, NOTIFY_REUSE, &s_token); if (macherr) { goto finish; } // load up current user list read_logins(NULL, NULL, 0, NULL); // and read initial value for PM settings TTYKeepAwakePrefsHaveChanged(); rval = 0; // hooray finish: if (rval) { cleanup_tty_tracking(); // takes care of s_source, s_token, s_cfport } return; } /* __private_extern__ */ void TTYKeepAwakePrefsHaveChanged( void ) { CFDictionaryRef activePMSettings; CFNumberRef systemIdleNum; CFNumberRef ttysPreventNum; /* Re-read the idle sleep timer if settings change */ activePMSettings = PMSettings_CopyActivePMSettings(); if( activePMSettings ) { systemIdleNum = isA_CFNumber(CFDictionaryGetValue( activePMSettings, CFSTR(kIOPMSystemSleepKey))); if( systemIdleNum ) { // read idle sleep minutes from settings dictionary CFNumberGetValue(systemIdleNum, kCFNumberIntType, &settingIdleSleepSeconds); // and minutes to seconds settingIdleSleepSeconds *= 60; } ttysPreventNum = isA_CFNumber(CFDictionaryGetValue( activePMSettings, CFSTR(kIOPMTTYSPreventSleepKey))); if( ttysPreventNum ) { // read idle sleep minutes from settings dictionary CFNumberGetValue(ttysPreventNum, kCFNumberIntType, &settingTTYSPreventSleep); } CFRelease( activePMSettings ); } } /* __private_extern__ */ bool TTYKeepAwakeSleepWakeNotification ( natural_t messageType ) { CFArrayRef preventers = NULL; CFStringRef preventer_name; if( (0 == settingIdleSleepSeconds) || !settingTTYSPreventSleep ) { /* TTYSPreventSleep is turned off * * or * * unitialized setting for idle sleep timer. Idle sleep timer should never * be set to zero at this point, since an idle sleep is in progress here. */ return false; } if( kIOMessageCanSystemSleep == messageType ) { bool ssh_sessions_active = check_ttys_active( &preventers ); /* diagnostic code only * logs tty sleep prevention to asl log */ if(preventers) { asl_log(NULL, NULL, ASL_LEVEL_ERR, "PowerManagement configd: System Sleep prevented by active remote login session (%d second threshold).\n", settingIdleSleepSeconds); int i, count; count = CFArrayGetCount(preventers); for(i=0; i prevent sleep * return false -> allow sleep */ if ( ssh_sessions_active ) { return true; } } return false; } static ttylist *addtty(char *ttyname) { ttylist *new = malloc(sizeof(ttylist)); if (new) { if (strlcpy(new->ttydev, "/dev/", DEVMAXPATHSIZE) > DEVMAXPATHSIZE || strlcat(new->ttydev, ttyname, DEVMAXPATHSIZE) > DEVMAXPATHSIZE) return NULL; new->next = s_activettys; s_activettys = new; } return new; } static void freettys() { ttylist *next, *delp = s_activettys; while(delp) { next = delp->next; free(delp); delp = next; } s_activettys = NULL; // empty list (terminates next list) } /* * check_ttys_active * * Returns false if the tty's are idle and should not prevent sleep. * Returns true if the tty's are active and should prevent sleep. */ bool check_ttys_active( CFArrayRef *preventers ) { CFMutableArrayRef preventArray = NULL; CFStringRef preventingTTY = NULL; bool rval = false; ttylist *tty = s_activettys; struct stat sb; time_t curtime; if( preventers ) { *preventers = NULL; } // fetch the current time (no sleep blocking if we can't) if ((curtime = time(NULL)) == (time_t)-1) { goto finish; } // note: remember to lock here if other threads can see the list while(tty) { if (stat(tty->ttydev, &sb) == 0) { time_t current_idle_secs; current_idle_secs = curtime - sb.st_atime; if (current_idle_secs < settingIdleSleepSeconds) { /* tty's are active. prevent sleep. */ rval = true; /* Log the name of the preventing TTY * We build a CFArray of active TTYs */ preventingTTY = CFStringCreateWithCString( kCFStringEncodingMacRoman, tty->ttydev, DEVMAXPATHSIZE); if( preventingTTY ) { if( !preventArray ) { preventArray = CFArrayCreateMutable( 0, 0, &kCFTypeArrayCallBacks ); } if( preventArray ) { CFArrayAppendValue( preventArray, preventingTTY ); } CFRelease(preventingTTY); preventingTTY = NULL; } } } tty = tty->next; } finish: if( preventArray ) { if( preventers ) { /* caller to release */ *preventers = preventArray; } else { /* we release */ CFRelease(preventArray); } } return rval; } static void read_logins( CFMachPortRef port __unused, void *m __unused, CFIndex size __unused, void *info __unused) { int result = 1; struct utmpx *ent; freettys(); // no-op if list is empty setutxent(); // reset while((ent = getutxent())) { // OS X only seems to use USER_PROCESS, but LOGIN_PROCESS seems valid if ( ent->ut_type == USER_PROCESS || ent->ut_type == LOGIN_PROCESS) { addtty(ent->ut_line); } } result = 0; endutxent(); } static void cleanup_tty_tracking() { CFRunLoopRef rl = CFRunLoopGetCurrent(); if(!rl) return; if (s_token != -1) { notify_cancel(s_token); s_token = -1; } if (s_source) { CFRunLoopRemoveSource(rl, s_source, kCFRunLoopDefaultMode); CFRelease(s_source); s_source = NULL; } if (s_cfport) { CFMachPortInvalidate(s_cfport); // refcount -> 1; CFRelease(s_cfport); // refcount -> 0; s_cfport = NULL; } freettys(); // no-op on empty list; clears list } #endif /* !TARGET_OS_EMBEDDED */