#include "cupsd.h"
#ifdef __APPLE__
# include <IOKit/IOKitLib.h>
# include <IOKit/IOMessage.h>
# include <IOKit/pwr_mgt/IOPMLib.h>
# include <SystemConfiguration/SystemConfiguration.h>
# include <pthread.h>
# define SYSEVENT_CANSLEEP 0x1
# define SYSEVENT_WILLSLEEP 0x2
# define SYSEVENT_WOKE 0x4
# define SYSEVENT_NETCHANGED 0x8
# define SYSEVENT_NAMECHANGED 0x10
typedef struct cupsd_sysevent_s
{
unsigned char event;
io_connect_t powerKernelPort;
long powerNotificationID;
} cupsd_sysevent_t;
typedef struct cupsd_thread_data_s
{
cupsd_sysevent_t sysevent;
CFRunLoopTimerRef timerRef;
} cupsd_thread_data_t;
static pthread_t SysEventThread = NULL;
static pthread_mutex_t SysEventThreadMutex = { 0 };
static pthread_cond_t SysEventThreadCond = { 0 };
static CFRunLoopRef SysEventRunloop = NULL;
static CFStringRef ComputerNameKey = NULL,
NetworkGlobalKeyIPv4 = NULL,
NetworkGlobalKeyIPv6 = NULL,
NetworkGlobalKeyDNS = NULL,
HostNamesKey = NULL,
NetworkInterfaceKeyIPv4 = NULL,
NetworkInterfaceKeyIPv6 = NULL;
static void *sysEventThreadEntry(void);
static void sysEventPowerNotifier(void *context, io_service_t service,
natural_t messageType,
void *messageArgument);
static void sysEventConfigurationNotifier(SCDynamicStoreRef store,
CFArrayRef changedKeys,
void *context);
static void sysEventTimerNotifier(CFRunLoopTimerRef timer, void *context);
void
cupsdStartSystemMonitor(void)
{
int flags;
if (cupsdOpenPipe(SysEventPipes))
{
cupsdLogMessage(CUPSD_LOG_ERROR, "System event monitor pipe() failed - %s!",
strerror(errno));
return;
}
cupsdAddSelect(SysEventPipes[0], (cupsd_selfunc_t)cupsdUpdateSystemMonitor,
NULL, NULL);
flags = fcntl(SysEventPipes[0], F_GETFL, 0);
fcntl(SysEventPipes[0], F_SETFL, flags | O_NONBLOCK);
pthread_mutex_init(&SysEventThreadMutex, NULL);
pthread_cond_init(&SysEventThreadCond, NULL);
pthread_create(&SysEventThread, NULL, (void *(*)())sysEventThreadEntry, NULL);
}
void
cupsdStopSystemMonitor(void)
{
CFRunLoopRef rl;
if (SysEventThread)
{
pthread_mutex_lock(&SysEventThreadMutex);
if (!SysEventRunloop)
pthread_cond_wait(&SysEventThreadCond, &SysEventThreadMutex);
rl = SysEventRunloop;
SysEventRunloop = NULL;
pthread_mutex_unlock(&SysEventThreadMutex);
if (rl)
CFRunLoopStop(rl);
pthread_join(SysEventThread, NULL);
pthread_mutex_destroy(&SysEventThreadMutex);
pthread_cond_destroy(&SysEventThreadCond);
}
if (SysEventPipes[0] >= 0)
{
cupsdRemoveSelect(SysEventPipes[0]);
cupsdClosePipe(SysEventPipes);
}
}
void
cupsdUpdateSystemMonitor(void)
{
int i;
cupsd_sysevent_t sysevent;
cupsd_printer_t *p;
while (read((int)SysEventPipes[0], &sysevent, sizeof(sysevent))
== sizeof(sysevent))
{
if (sysevent.event & SYSEVENT_CANSLEEP)
{
for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
p;
p = (cupsd_printer_t *)cupsArrayNext(Printers))
{
if (p->job)
{
for (i = 0; i < p->num_reasons; i ++)
if (!strcmp(p->reasons[i], "connecting-to-device"))
break;
if (!p->num_reasons || i >= p->num_reasons)
break;
}
}
if (p)
{
cupsdLogMessage(CUPSD_LOG_INFO,
"System sleep canceled because printer %s is active",
p->name);
IOCancelPowerChange(sysevent.powerKernelPort,
sysevent.powerNotificationID);
}
else
{
cupsdLogMessage(CUPSD_LOG_DEBUG, "System wants to sleep");
IOAllowPowerChange(sysevent.powerKernelPort,
sysevent.powerNotificationID);
}
}
if (sysevent.event & SYSEVENT_WILLSLEEP)
{
cupsdLogMessage(CUPSD_LOG_DEBUG, "System going to sleep");
Sleeping = 1;
cupsdStopAllJobs(0);
cupsdSaveAllJobs();
for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
p;
p = (cupsd_printer_t *)cupsArrayNext(Printers))
{
if (p->type & CUPS_PRINTER_DISCOVERED)
{
cupsdLogMessage(CUPSD_LOG_DEBUG,
"Deleting remote destination \"%s\"", p->name);
cupsArraySave(Printers);
cupsdDeletePrinter(p, 0);
cupsArrayRestore(Printers);
}
else
{
cupsdLogMessage(CUPSD_LOG_DEBUG,
"Deregistering local printer \"%s\"", p->name);
cupsdDeregisterPrinter(p, 0);
}
}
IOAllowPowerChange(sysevent.powerKernelPort,
sysevent.powerNotificationID);
}
if (sysevent.event & SYSEVENT_WOKE)
{
cupsdLogMessage(CUPSD_LOG_DEBUG, "System woke from sleep");
IOAllowPowerChange(sysevent.powerKernelPort,
sysevent.powerNotificationID);
Sleeping = 0;
cupsdCheckJobs();
}
if (sysevent.event & SYSEVENT_NETCHANGED)
{
if (!Sleeping)
{
cupsdLogMessage(CUPSD_LOG_DEBUG,
"System network configuration changed");
for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
p;
p = (cupsd_printer_t *)cupsArrayNext(Printers))
p->browse_time = 0;
cupsdSendBrowseList();
cupsdRestartPolling();
}
else
cupsdLogMessage(CUPSD_LOG_DEBUG,
"System network configuration changed; "
"ignored while sleeping");
}
if (sysevent.event & SYSEVENT_NAMECHANGED)
{
if (!Sleeping)
{
cupsdLogMessage(CUPSD_LOG_DEBUG, "Computer name changed");
for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
p;
p = (cupsd_printer_t *)cupsArrayNext(Printers))
cupsdDeregisterPrinter(p, 1);
for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
p;
p = (cupsd_printer_t *)cupsArrayNext(Printers))
{
p->browse_time = 0;
cupsdRegisterPrinter(p);
}
}
else
cupsdLogMessage(CUPSD_LOG_DEBUG,
"Computer name changed; ignored while sleeping");
}
}
}
static void *
sysEventThreadEntry(void)
{
io_object_t powerNotifierObj;
IONotificationPortRef powerNotifierPort;
SCDynamicStoreRef store = NULL;
CFRunLoopSourceRef powerRLS = NULL,
storeRLS = NULL;
CFStringRef key[5],
pattern[2];
CFArrayRef keys = NULL,
patterns = NULL;
SCDynamicStoreContext storeContext;
CFRunLoopTimerContext timerContext;
cupsd_thread_data_t threadData;
bzero(&threadData, sizeof(threadData));
threadData.sysevent.powerKernelPort =
IORegisterForSystemPower(&threadData, &powerNotifierPort,
sysEventPowerNotifier, &powerNotifierObj);
if (threadData.sysevent.powerKernelPort)
{
powerRLS = IONotificationPortGetRunLoopSource(powerNotifierPort);
CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
}
else
DEBUG_puts("sysEventThreadEntry: error registering for system power "
"notifications");
bzero(&storeContext, sizeof(storeContext));
storeContext.info = &threadData;
store = SCDynamicStoreCreate(NULL, CFSTR("cupsd"),
sysEventConfigurationNotifier, &storeContext);
if (!ComputerNameKey)
ComputerNameKey = SCDynamicStoreKeyCreateComputerName(NULL);
if (!NetworkGlobalKeyIPv4)
NetworkGlobalKeyIPv4 =
SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetIPv4);
if (!NetworkGlobalKeyIPv6)
NetworkGlobalKeyIPv6 =
SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetIPv6);
if (!NetworkGlobalKeyDNS)
NetworkGlobalKeyDNS =
SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetDNS);
if (!HostNamesKey)
HostNamesKey = SCDynamicStoreKeyCreateHostNames(NULL);
if (!NetworkInterfaceKeyIPv4)
NetworkInterfaceKeyIPv4 =
SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetIPv4);
if (!NetworkInterfaceKeyIPv6)
NetworkInterfaceKeyIPv6 =
SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetIPv6);
if (store && ComputerNameKey && HostNamesKey &&
NetworkGlobalKeyIPv4 && NetworkGlobalKeyIPv6 && NetworkGlobalKeyDNS &&
NetworkInterfaceKeyIPv4 && NetworkInterfaceKeyIPv6)
{
key[0] = ComputerNameKey;
key[1] = NetworkGlobalKeyIPv4;
key[2] = NetworkGlobalKeyIPv6;
key[3] = NetworkGlobalKeyDNS;
key[4] = HostNamesKey;
pattern[0] = NetworkInterfaceKeyIPv4;
pattern[1] = NetworkInterfaceKeyIPv6;
keys = CFArrayCreate(NULL, (const void **)key,
sizeof(key) / sizeof(key[0]),
&kCFTypeArrayCallBacks);
patterns = CFArrayCreate(NULL, (const void **)pattern,
sizeof(pattern) / sizeof(pattern[0]),
&kCFTypeArrayCallBacks);
if (keys && patterns &&
SCDynamicStoreSetNotificationKeys(store, keys, patterns))
{
if ((storeRLS = SCDynamicStoreCreateRunLoopSource(NULL, store, 0))
!= NULL)
{
CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS,
kCFRunLoopDefaultMode);
}
else
DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreateRunLoopSource "
"failed: %s\n", SCErrorString(SCError())));
}
else
DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreSetNotificationKeys "
"failed: %s\n", SCErrorString(SCError())));
}
else
DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreate failed: %s\n",
SCErrorString(SCError())));
if (keys)
CFRelease(keys);
if (patterns)
CFRelease(patterns);
bzero(&timerContext, sizeof(timerContext));
timerContext.info = &threadData;
threadData.timerRef =
CFRunLoopTimerCreate(NULL,
CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
86400L * 365L * 10L, 0, 0, sysEventTimerNotifier,
&timerContext);
CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData.timerRef,
kCFRunLoopDefaultMode);
pthread_mutex_lock(&SysEventThreadMutex);
SysEventRunloop = CFRunLoopGetCurrent();
pthread_cond_signal(&SysEventThreadCond);
pthread_mutex_unlock(&SysEventThreadMutex);
CFRunLoopRun();
if (threadData.timerRef)
{
CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData.timerRef,
kCFRunLoopDefaultMode);
CFRelease(threadData.timerRef);
}
if (threadData.sysevent.powerKernelPort)
{
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS,
kCFRunLoopDefaultMode);
IODeregisterForSystemPower(&powerNotifierObj);
IOServiceClose(threadData.sysevent.powerKernelPort);
IONotificationPortDestroy(powerNotifierPort);
}
if (storeRLS)
{
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS,
kCFRunLoopDefaultMode);
CFRunLoopSourceInvalidate(storeRLS);
CFRelease(storeRLS);
}
if (store)
CFRelease(store);
pthread_exit(NULL);
}
static void
sysEventPowerNotifier(
void *context,
io_service_t service,
natural_t messageType,
void *messageArgument)
{
int sendit = 1;
cupsd_thread_data_t *threadData;
threadData = (cupsd_thread_data_t *)context;
(void)service;
switch (messageType)
{
case kIOMessageCanSystemPowerOff:
case kIOMessageCanSystemSleep:
threadData->sysevent.event |= SYSEVENT_CANSLEEP;
break;
case kIOMessageSystemWillRestart:
case kIOMessageSystemWillPowerOff:
case kIOMessageSystemWillSleep:
threadData->sysevent.event |= SYSEVENT_WILLSLEEP;
break;
case kIOMessageSystemHasPoweredOn:
sendit = 2;
threadData->sysevent.event |= SYSEVENT_WOKE;
break;
case kIOMessageSystemWillNotPowerOff:
case kIOMessageSystemWillNotSleep:
#ifdef kIOMessageSystemWillPowerOn
case kIOMessageSystemWillPowerOn:
#endif
default:
sendit = 0;
break;
}
if (sendit == 0)
IOAllowPowerChange(threadData->sysevent.powerKernelPort,
(long)messageArgument);
else
{
threadData->sysevent.powerNotificationID = (long)messageArgument;
if (sendit == 1)
{
write(SysEventPipes[1], &threadData->sysevent,
sizeof(threadData->sysevent));
threadData->sysevent.event = 0;
}
else
{
CFRunLoopTimerSetNextFireDate(threadData->timerRef,
CFAbsoluteTimeGetCurrent() + 2);
}
}
}
static void
sysEventConfigurationNotifier(
SCDynamicStoreRef store,
CFArrayRef changedKeys,
void *context)
{
cupsd_thread_data_t *threadData;
threadData = (cupsd_thread_data_t *)context;
(void)store;
CFRange range = CFRangeMake(0, CFArrayGetCount(changedKeys));
if (CFArrayContainsValue(changedKeys, range, ComputerNameKey))
threadData->sysevent.event |= SYSEVENT_NAMECHANGED;
else
{
threadData->sysevent.event |= SYSEVENT_NETCHANGED;
NetIFUpdate = 1;
}
CFRunLoopTimerSetNextFireDate(threadData->timerRef,
CFAbsoluteTimeGetCurrent() + 5);
}
static void
sysEventTimerNotifier(
CFRunLoopTimerRef timer,
void *context)
{
cupsd_thread_data_t *threadData;
threadData = (cupsd_thread_data_t *)context;
if (threadData->sysevent.event)
{
write(SysEventPipes[1], &threadData->sysevent,
sizeof(threadData->sysevent));
threadData->sysevent.event = 0;
}
}
#endif