2 * "$Id: sysman.c 5241 2006-03-07 22:07:44Z mike $"
4 * System management definitions for the Common UNIX Printing System (CUPS).
6 * Copyright 2006 by Easy Software Products.
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE.txt" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636 USA
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
26 * cupsdStartSystemMonitor() - Start monitoring for system change.
27 * cupsdStopSystemMonitor() - Stop monitoring for system change.
28 * cupsdUpdateSystemMonitor() - Update the current system state.
29 * sysEventThreadEntry() - A thread to receive power and computer
30 * name change notifications.
31 * sysEventPowerNotifier() - Handle power notification events.
32 * sysEventConfigurationNotifier() - Computer name changed notification
34 * sysEventTimerNotifier() - Handle delayed event notifications.
39 * Include necessary headers...
46 * Power management is a new addition to CUPS. Right now it is only
47 * implemented on MacOS X, but essentially we use these three functions
48 * to let the OS know when it is OK to put the system to sleep, typically
49 * when we are not in the middle of printing a job.
51 * Once put to sleep, we invalidate all remote printers since it is
52 * common to wake up in a new location.
57 * 'cupsdStartSystemMonitor()' - Start monitoring for system change.
61 cupsdStartSystemMonitor(void)
67 * 'cupsdStopSystemMonitor()' - Stop monitoring for system change.
71 cupsdStopSystemMonitor(void)
77 * 'cupsdUpdateSystemMonitor()' - Update the current system state.
81 cupsdUpdateSystemMonitor(void)
84 #endif /* !__APPLE__ */
89 * This is the Apple-specific system event code. It works by creating
90 * a worker thread that waits for events from the OS and relays them
91 * to the main thread via a traditional pipe.
95 * Include MacOS-specific headers...
98 # include <IOKit/IOKitLib.h>
99 # include <IOKit/IOMessage.h>
100 # include <IOKit/pwr_mgt/IOPMLib.h>
101 # include <SystemConfiguration/SystemConfiguration.h>
102 # include <pthread.h>
109 # define SYSEVENT_CANSLEEP 0x1 /* Decide whether to allow sleep or not */
110 # define SYSEVENT_WILLSLEEP 0x2 /* Computer will go to sleep */
111 # define SYSEVENT_WOKE 0x4 /* Computer woke from sleep */
112 # define SYSEVENT_NETCHANGED 0x8 /* Network changed */
113 # define SYSEVENT_NAMECHANGED 0x10 /* Computer name changed */
120 typedef struct cupsd_sysevent_s
/*** System event data ****/
122 unsigned char event
; /* Event bit field */
123 io_connect_t powerKernelPort
; /* Power context data */
124 long powerNotificationID
; /* Power event data */
128 typedef struct cupsd_thread_data_s
/*** Thread context data ****/
130 cupsd_sysevent_t sysevent
; /* System event */
131 CFRunLoopTimerRef timerRef
; /* Timer to delay some change *
133 } cupsd_thread_data_t
;
140 static pthread_t SysEventThread
= NULL
;
141 /* Thread to host a runloop */
142 static pthread_mutex_t SysEventThreadMutex
= { 0 };
143 /* Coordinates access to shared gloabals */
144 static pthread_cond_t SysEventThreadCond
= { 0 };
145 /* Thread initialization complete condition */
146 static CFRunLoopRef SysEventRunloop
= NULL
;
147 /* The runloop. Access must be protected! */
148 static CFStringRef ComputerNameKey
= NULL
,
149 /* Computer name key */
150 NetworkGlobalKey
= NULL
,
151 /* Network global key */
154 NetworkInterfaceKey
= NULL
;
155 /* Netowrk interface key */
162 static void *sysEventThreadEntry(void);
163 static void sysEventPowerNotifier(void *context
, io_service_t service
,
164 natural_t messageType
,
165 void *messageArgument
);
166 static void sysEventConfigurationNotifier(SCDynamicStoreRef store
,
167 CFArrayRef changedKeys
,
169 static void sysEventTimerNotifier(CFRunLoopTimerRef timer
, void *context
);
173 * 'cupsdStartSystemMonitor()' - Start monitoring for system change.
177 cupsdStartSystemMonitor(void)
179 int flags
; /* fcntl flags on pipe */
182 if (cupsdOpenPipe(SysEventPipes
))
184 cupsdLogMessage(CUPSD_LOG_ERROR
, "System event monitor pipe() failed - %s!",
189 cupsdLogMessage(CUPSD_LOG_DEBUG2
,
190 "cupsdStartSystemMonitor: Adding fd %d to InputSet...",
192 FD_SET(SysEventPipes
[0], InputSet
);
195 * Set non-blocking mode on the descriptor we will be receiving notification
199 flags
= fcntl(SysEventPipes
[0], F_GETFL
, 0);
200 fcntl(SysEventPipes
[0], F_SETFL
, flags
| O_NONBLOCK
);
203 * Start the thread that runs the runloop...
206 pthread_mutex_init(&SysEventThreadMutex
, NULL
);
207 pthread_cond_init(&SysEventThreadCond
, NULL
);
208 pthread_create(&SysEventThread
, NULL
, (void *(*)())sysEventThreadEntry
, NULL
);
213 * 'cupsdStopSystemMonitor()' - Stop monitoring for system change.
217 cupsdStopSystemMonitor(void)
219 CFRunLoopRef rl
; /* The event handler runloop */
225 * Make sure the thread has completed it's initialization and
226 * stored it's runloop reference in the shared global.
229 pthread_mutex_lock(&SysEventThreadMutex
);
231 if (!SysEventRunloop
)
232 pthread_cond_wait(&SysEventThreadCond
, &SysEventThreadMutex
);
234 rl
= SysEventRunloop
;
235 SysEventRunloop
= NULL
;
237 pthread_mutex_unlock(&SysEventThreadMutex
);
242 pthread_join(SysEventThread
, NULL
);
243 pthread_mutex_destroy(&SysEventThreadMutex
);
244 pthread_cond_destroy(&SysEventThreadCond
);
247 if (SysEventPipes
[0] >= 0)
249 cupsdLogMessage(CUPSD_LOG_DEBUG2
,
250 "cupsdStopSystemMonitor: Removing fd %d from InputSet...",
253 FD_CLR(SysEventPipes
[0], InputSet
);
255 cupsdClosePipe(SysEventPipes
);
261 * 'cupsdUpdateSystemMonitor()' - Update the current system state.
265 cupsdUpdateSystemMonitor(void)
267 int i
; /* Looping var */
268 cupsd_sysevent_t sysevent
; /* The system event */
269 cupsd_printer_t
*p
; /* Printer information */
273 * Drain the event pipe...
276 while (read((int)SysEventPipes
[0], &sysevent
, sizeof(sysevent
))
279 if (sysevent
.event
& SYSEVENT_CANSLEEP
)
282 * If there are active printers that don't have the connecting-to-device
283 * printer-state-reason then cancel the sleep request (i.e. this reason
284 * indicates a job that is not yet connected to the printer)...
287 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
289 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
293 for (i
= 0; i
< p
->num_reasons
; i
++)
294 if (!strcmp(p
->reasons
[i
], "connecting-to-device"))
297 if (!p
->num_reasons
|| i
>= p
->num_reasons
)
304 cupsdLogMessage(CUPSD_LOG_INFO
,
305 "System sleep canceled because printer %s is active",
307 IOCancelPowerChange(sysevent
.powerKernelPort
,
308 sysevent
.powerNotificationID
);
312 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System wants to sleep");
313 IOAllowPowerChange(sysevent
.powerKernelPort
,
314 sysevent
.powerNotificationID
);
318 if (sysevent
.event
& SYSEVENT_WILLSLEEP
)
320 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System going to sleep");
327 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
329 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
331 if (p
->type
& CUPS_PRINTER_REMOTE
)
333 cupsdLogMessage(CUPSD_LOG_DEBUG
,
334 "Deleting remote destination \"%s\"", p
->name
);
335 cupsArraySave(Printers
);
336 cupsdDeletePrinter(p
, 0);
337 cupsArrayRestore(Printers
);
341 /* TODO: Possibly update when MDNS support is added? */
342 cupsdLogMessage(CUPSD_LOG_DEBUG
,
343 "Deregistering local printer \"%s\"", p
->name
);
344 cupsdSendBrowseDelete(p
);
348 IOAllowPowerChange(sysevent
.powerKernelPort
,
349 sysevent
.powerNotificationID
);
352 if (sysevent
.event
& SYSEVENT_WOKE
)
354 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System woke from sleep");
355 IOAllowPowerChange(sysevent
.powerKernelPort
,
356 sysevent
.powerNotificationID
);
361 if (sysevent
.event
& SYSEVENT_NETCHANGED
)
365 cupsdLogMessage(CUPSD_LOG_DEBUG
,
366 "System network configuration changed");
369 * Force an update of the list of network interfaces in 2 seconds.
372 NetIFTime
= time(NULL
) - 58;
375 * Resetting browse_time before calling cupsdSendBrowseList causes
376 * browse packets to be sent for local shared printers.
379 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
381 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
384 cupsdSendBrowseList();
387 cupsdLogMessage(CUPSD_LOG_DEBUG
,
388 "System network configuration changed; "
389 "ignored while sleeping");
392 if (sysevent
.event
& SYSEVENT_NAMECHANGED
)
396 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Computer name changed");
399 * De-register the individual printers...
402 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
404 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
405 cupsdSendBrowseDelete(p
);
408 * Now re-register them...
410 * TODO: This might need updating for MDNS.
413 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
415 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
419 cupsdLogMessage(CUPSD_LOG_DEBUG
,
420 "Computer name changed; ignored while sleeping");
427 * 'sysEventThreadEntry()' - A thread to receive power and computer name
428 * change notifications.
431 static void * /* O - Return status/value */
432 sysEventThreadEntry(void)
434 io_object_t powerNotifierObj
;
435 /* Power notifier object */
436 IONotificationPortRef powerNotifierPort
;
437 /* Power notifier port */
438 SCDynamicStoreRef store
= NULL
;/* System Config dynamic store */
439 CFRunLoopSourceRef powerRLS
= NULL
,/* Power runloop source */
440 storeRLS
= NULL
;/* System Config runloop source */
441 CFStringRef key
[3], /* System Config keys */
442 pattern
[1]; /* System Config patterns */
443 CFArrayRef keys
= NULL
, /* System Config key array*/
444 patterns
= NULL
;/* System Config pattern array */
445 SCDynamicStoreContext storeContext
; /* Dynamic store context */
446 CFRunLoopTimerContext timerContext
; /* Timer context */
447 cupsd_thread_data_t threadData
; /* Thread context data for the *
448 * runloop notifiers */
452 * Register for power state change notifications
455 bzero(&threadData
, sizeof(threadData
));
457 threadData
.sysevent
.powerKernelPort
=
458 IORegisterForSystemPower(&threadData
, &powerNotifierPort
,
459 sysEventPowerNotifier
, &powerNotifierObj
);
461 if (threadData
.sysevent
.powerKernelPort
)
463 powerRLS
= IONotificationPortGetRunLoopSource(powerNotifierPort
);
464 CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS
, kCFRunLoopDefaultMode
);
467 DEBUG_puts("sysEventThreadEntry: error registering for system power "
471 * Register for system configuration change notifications
474 bzero(&storeContext
, sizeof(storeContext
));
475 storeContext
.info
= &threadData
;
477 store
= SCDynamicStoreCreate(NULL
, CFSTR("cupsd"),
478 sysEventConfigurationNotifier
, &storeContext
);
480 if (!ComputerNameKey
)
481 ComputerNameKey
= SCDynamicStoreKeyCreateComputerName(NULL
);
483 if (!NetworkGlobalKey
)
485 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
486 kSCDynamicStoreDomainState
,
490 HostNamesKey
= SCDynamicStoreKeyCreateHostNames(NULL
);
492 if (!NetworkInterfaceKey
)
493 NetworkInterfaceKey
=
494 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL
,
495 kSCDynamicStoreDomainState
,
499 if (store
&& ComputerNameKey
&& NetworkGlobalKey
&& HostNamesKey
&&
502 key
[0] = ComputerNameKey
;
503 key
[1] = NetworkGlobalKey
;
504 key
[2] = HostNamesKey
;
505 pattern
[0] = NetworkInterfaceKey
;
507 keys
= CFArrayCreate(NULL
, (const void **)key
,
508 sizeof(key
) / sizeof(key
[0]),
509 &kCFTypeArrayCallBacks
);
510 patterns
= CFArrayCreate(NULL
, (const void **)pattern
,
511 sizeof(pattern
) / sizeof(pattern
[0]),
512 &kCFTypeArrayCallBacks
);
514 if (keys
&& patterns
&&
515 SCDynamicStoreSetNotificationKeys(store
, keys
, patterns
))
517 if ((storeRLS
= SCDynamicStoreCreateRunLoopSource(NULL
, store
, 0))
520 CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS
,
521 kCFRunLoopDefaultMode
);
524 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreateRunLoopSource "
525 "failed: %s\n", SCErrorString(SCError())));
528 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreSetNotificationKeys "
529 "failed: %s\n", SCErrorString(SCError())));
532 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreate failed: %s\n",
533 SCErrorString(SCError())));
542 * Set up a timer to delay the wake change notifications.
544 * The initial time is set a decade or so into the future, we'll adjust
548 bzero(&timerContext
, sizeof(timerContext
));
549 timerContext
.info
= &threadData
;
551 threadData
.timerRef
=
552 CFRunLoopTimerCreate(NULL
,
553 CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
554 86400L * 365L * 10L, 0, 0, sysEventTimerNotifier
,
556 CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData
.timerRef
,
557 kCFRunLoopDefaultMode
);
560 * Store our runloop in a global so the main thread can use it to stop us.
563 pthread_mutex_lock(&SysEventThreadMutex
);
565 SysEventRunloop
= CFRunLoopGetCurrent();
567 pthread_cond_signal(&SysEventThreadCond
);
568 pthread_mutex_unlock(&SysEventThreadMutex
);
571 * Disappear into the runloop until it's stopped by the main thread.
577 * Clean up before exiting.
580 if (threadData
.timerRef
)
582 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData
.timerRef
,
583 kCFRunLoopDefaultMode
);
584 CFRelease(threadData
.timerRef
);
587 if (threadData
.sysevent
.powerKernelPort
)
589 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS
,
590 kCFRunLoopDefaultMode
);
591 IODeregisterForSystemPower(&powerNotifierObj
);
592 IOServiceClose(threadData
.sysevent
.powerKernelPort
);
593 IONotificationPortDestroy(powerNotifierPort
);
598 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS
,
599 kCFRunLoopDefaultMode
);
600 CFRunLoopSourceInvalidate(storeRLS
);
612 * 'sysEventPowerNotifier()' - Handle power notification events.
616 sysEventPowerNotifier(
617 void *context
, /* I - Thread context data */
618 io_service_t service
, /* I - Unused service info */
619 natural_t messageType
, /* I - Type of message */
620 void *messageArgument
) /* I - Message data */
622 int sendit
= 1; /* Send event to main thread? *
623 * (0 = no, 1 = yes, 2 = delayed */
624 cupsd_thread_data_t
*threadData
; /* Thread context data */
627 threadData
= (cupsd_thread_data_t
*)context
;
629 (void)service
; /* anti-compiler-warning-code */
633 case kIOMessageCanSystemPowerOff
:
634 case kIOMessageCanSystemSleep
:
635 threadData
->sysevent
.event
|= SYSEVENT_CANSLEEP
;
638 case kIOMessageSystemWillRestart
:
639 case kIOMessageSystemWillPowerOff
:
640 case kIOMessageSystemWillSleep
:
641 threadData
->sysevent
.event
|= SYSEVENT_WILLSLEEP
;
644 case kIOMessageSystemHasPoweredOn
:
646 * Because powered on is followed by a net-changed event, delay
651 threadData
->sysevent
.event
|= SYSEVENT_WOKE
;
654 case kIOMessageSystemWillNotPowerOff
:
655 case kIOMessageSystemWillNotSleep
:
656 #ifdef kIOMessageSystemWillPowerOn
657 case kIOMessageSystemWillPowerOn
:
658 #endif /* kIOMessageSystemWillPowerOn */
665 IOAllowPowerChange(threadData
->sysevent
.powerKernelPort
,
666 (long)messageArgument
);
669 threadData
->sysevent
.powerNotificationID
= (long)messageArgument
;
674 * Send the event to the main thread now.
677 write(SysEventPipes
[1], &threadData
->sysevent
,
678 sizeof(threadData
->sysevent
));
679 threadData
->sysevent
.event
= 0;
684 * Send the event to the main thread after 1 to 2 seconds.
687 CFRunLoopTimerSetNextFireDate(threadData
->timerRef
,
688 CFAbsoluteTimeGetCurrent() + 2);
695 * 'sysEventConfigurationNotifier()' - Computer name changed notification
700 sysEventConfigurationNotifier(
701 SCDynamicStoreRef store
, /* I - System data (unused) */
702 CFArrayRef changedKeys
, /* I - Changed data */
703 void *context
) /* I - Thread context data */
705 cupsd_thread_data_t
*threadData
; /* Thread context data */
708 threadData
= (cupsd_thread_data_t
*)context
;
710 (void)store
; /* anti-compiler-warning-code */
712 CFRange range
= CFRangeMake(0, CFArrayGetCount(changedKeys
));
714 if (CFArrayContainsValue(changedKeys
, range
, ComputerNameKey
))
715 threadData
->sysevent
.event
|= SYSEVENT_NAMECHANGED
;
717 if (CFArrayContainsValue(changedKeys
, range
, NetworkGlobalKey
) ||
718 CFArrayContainsValue(changedKeys
, range
, HostNamesKey
) ||
719 CFArrayContainsValue(changedKeys
, range
, NetworkInterfaceKey
))
720 threadData
->sysevent
.event
|= SYSEVENT_NETCHANGED
;
723 * Because we registered for several different kinds of change notifications
724 * this callback usually gets called several times in a row. We use a timer to
725 * de-bounce these so we only end up generating one event for the main thread.
728 CFRunLoopTimerSetNextFireDate(threadData
->timerRef
,
729 CFAbsoluteTimeGetCurrent() + 2);
734 * 'sysEventTimerNotifier()' - Handle delayed event notifications.
738 sysEventTimerNotifier(
739 CFRunLoopTimerRef timer
, /* I - Timer information */
740 void *context
) /* I - Thread context data */
742 cupsd_thread_data_t
*threadData
; /* Thread context data */
745 threadData
= (cupsd_thread_data_t
*)context
;
748 * If an event is still pending send it to the main thread.
751 if (threadData
->sysevent
.event
)
753 write(SysEventPipes
[1], &threadData
->sysevent
,
754 sizeof(threadData
->sysevent
));
755 threadData
->sysevent
.event
= 0;
758 #endif /* __APPLE__ */
762 * End of "$Id: sysman.c 5241 2006-03-07 22:07:44Z mike $".