2 * "$Id: sysman.c 6649 2007-07-11 21:46:42Z mike $"
4 * System management definitions for the Common UNIX Printing System (CUPS).
6 * Copyright 2007 by Apple Inc.
7 * Copyright 2006 by Easy Software Products.
9 * These coded instructions, statements, and computer programs are the
10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file. If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
17 * cupsdStartSystemMonitor() - Start monitoring for system change.
18 * cupsdStopSystemMonitor() - Stop monitoring for system change.
19 * cupsdUpdateSystemMonitor() - Update the current system state.
20 * sysEventThreadEntry() - A thread to receive power and computer
21 * name change notifications.
22 * sysEventPowerNotifier() - Handle power notification events.
23 * sysEventConfigurationNotifier() - Computer name changed notification
25 * sysEventTimerNotifier() - Handle delayed event notifications.
30 * Include necessary headers...
37 * Power management is a new addition to CUPS. Right now it is only
38 * implemented on MacOS X, but essentially we use these three functions
39 * to let the OS know when it is OK to put the system to sleep, typically
40 * when we are not in the middle of printing a job.
42 * Once put to sleep, we invalidate all remote printers since it is
43 * common to wake up in a new location.
48 * This is the Apple-specific system event code. It works by creating
49 * a worker thread that waits for events from the OS and relays them
50 * to the main thread via a traditional pipe.
54 * Include MacOS-specific headers...
57 # include <IOKit/IOKitLib.h>
58 # include <IOKit/IOMessage.h>
59 # include <IOKit/pwr_mgt/IOPMLib.h>
60 # include <SystemConfiguration/SystemConfiguration.h>
68 # define SYSEVENT_CANSLEEP 0x1 /* Decide whether to allow sleep or not */
69 # define SYSEVENT_WILLSLEEP 0x2 /* Computer will go to sleep */
70 # define SYSEVENT_WOKE 0x4 /* Computer woke from sleep */
71 # define SYSEVENT_NETCHANGED 0x8 /* Network changed */
72 # define SYSEVENT_NAMECHANGED 0x10 /* Computer name changed */
79 typedef struct cupsd_sysevent_s
/*** System event data ****/
81 unsigned char event
; /* Event bit field */
82 io_connect_t powerKernelPort
; /* Power context data */
83 long powerNotificationID
; /* Power event data */
87 typedef struct cupsd_thread_data_s
/*** Thread context data ****/
89 cupsd_sysevent_t sysevent
; /* System event */
90 CFRunLoopTimerRef timerRef
; /* Timer to delay some change *
92 } cupsd_thread_data_t
;
99 static pthread_t SysEventThread
= NULL
;
100 /* Thread to host a runloop */
101 static pthread_mutex_t SysEventThreadMutex
= { 0 };
102 /* Coordinates access to shared gloabals */
103 static pthread_cond_t SysEventThreadCond
= { 0 };
104 /* Thread initialization complete condition */
105 static CFRunLoopRef SysEventRunloop
= NULL
;
106 /* The runloop. Access must be protected! */
107 static CFStringRef ComputerNameKey
= NULL
,
108 /* Computer name key */
109 NetworkGlobalKeyIPv4
= NULL
,
110 /* Network global IPv4 key */
111 NetworkGlobalKeyIPv6
= NULL
,
112 /* Network global IPv6 key */
113 NetworkGlobalKeyDNS
= NULL
,
114 /* Network global DNS key */
117 NetworkInterfaceKeyIPv4
= NULL
,
118 /* Netowrk interface key */
119 NetworkInterfaceKeyIPv6
= NULL
;
120 /* Netowrk interface key */
127 static void *sysEventThreadEntry(void);
128 static void sysEventPowerNotifier(void *context
, io_service_t service
,
129 natural_t messageType
,
130 void *messageArgument
);
131 static void sysEventConfigurationNotifier(SCDynamicStoreRef store
,
132 CFArrayRef changedKeys
,
134 static void sysEventTimerNotifier(CFRunLoopTimerRef timer
, void *context
);
138 * 'cupsdStartSystemMonitor()' - Start monitoring for system change.
142 cupsdStartSystemMonitor(void)
144 int flags
; /* fcntl flags on pipe */
147 if (cupsdOpenPipe(SysEventPipes
))
149 cupsdLogMessage(CUPSD_LOG_ERROR
, "System event monitor pipe() failed - %s!",
154 cupsdAddSelect(SysEventPipes
[0], (cupsd_selfunc_t
)cupsdUpdateSystemMonitor
,
158 * Set non-blocking mode on the descriptor we will be receiving notification
162 flags
= fcntl(SysEventPipes
[0], F_GETFL
, 0);
163 fcntl(SysEventPipes
[0], F_SETFL
, flags
| O_NONBLOCK
);
166 * Start the thread that runs the runloop...
169 pthread_mutex_init(&SysEventThreadMutex
, NULL
);
170 pthread_cond_init(&SysEventThreadCond
, NULL
);
171 pthread_create(&SysEventThread
, NULL
, (void *(*)())sysEventThreadEntry
, NULL
);
176 * 'cupsdStopSystemMonitor()' - Stop monitoring for system change.
180 cupsdStopSystemMonitor(void)
182 CFRunLoopRef rl
; /* The event handler runloop */
188 * Make sure the thread has completed it's initialization and
189 * stored it's runloop reference in the shared global.
192 pthread_mutex_lock(&SysEventThreadMutex
);
194 if (!SysEventRunloop
)
195 pthread_cond_wait(&SysEventThreadCond
, &SysEventThreadMutex
);
197 rl
= SysEventRunloop
;
198 SysEventRunloop
= NULL
;
200 pthread_mutex_unlock(&SysEventThreadMutex
);
205 pthread_join(SysEventThread
, NULL
);
206 pthread_mutex_destroy(&SysEventThreadMutex
);
207 pthread_cond_destroy(&SysEventThreadCond
);
210 if (SysEventPipes
[0] >= 0)
212 cupsdRemoveSelect(SysEventPipes
[0]);
213 cupsdClosePipe(SysEventPipes
);
219 * 'cupsdUpdateSystemMonitor()' - Update the current system state.
223 cupsdUpdateSystemMonitor(void)
225 int i
; /* Looping var */
226 cupsd_sysevent_t sysevent
; /* The system event */
227 cupsd_printer_t
*p
; /* Printer information */
231 * Drain the event pipe...
234 while (read((int)SysEventPipes
[0], &sysevent
, sizeof(sysevent
))
237 if (sysevent
.event
& SYSEVENT_CANSLEEP
)
240 * If there are active printers that don't have the connecting-to-device
241 * printer-state-reason then cancel the sleep request (i.e. this reason
242 * indicates a job that is not yet connected to the printer)...
245 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
247 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
251 for (i
= 0; i
< p
->num_reasons
; i
++)
252 if (!strcmp(p
->reasons
[i
], "connecting-to-device"))
255 if (!p
->num_reasons
|| i
>= p
->num_reasons
)
262 cupsdLogMessage(CUPSD_LOG_INFO
,
263 "System sleep canceled because printer %s is active",
265 IOCancelPowerChange(sysevent
.powerKernelPort
,
266 sysevent
.powerNotificationID
);
270 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System wants to sleep");
271 IOAllowPowerChange(sysevent
.powerKernelPort
,
272 sysevent
.powerNotificationID
);
276 if (sysevent
.event
& SYSEVENT_WILLSLEEP
)
278 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System going to sleep");
285 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
287 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
289 if (p
->type
& CUPS_PRINTER_REMOTE
)
291 cupsdLogMessage(CUPSD_LOG_DEBUG
,
292 "Deleting remote destination \"%s\"", p
->name
);
293 cupsArraySave(Printers
);
294 cupsdDeletePrinter(p
, 0);
295 cupsArrayRestore(Printers
);
299 cupsdLogMessage(CUPSD_LOG_DEBUG
,
300 "Deregistering local printer \"%s\"", p
->name
);
301 cupsdDeregisterPrinter(p
, 0);
305 IOAllowPowerChange(sysevent
.powerKernelPort
,
306 sysevent
.powerNotificationID
);
309 if (sysevent
.event
& SYSEVENT_WOKE
)
311 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System woke from sleep");
312 IOAllowPowerChange(sysevent
.powerKernelPort
,
313 sysevent
.powerNotificationID
);
318 if (sysevent
.event
& SYSEVENT_NETCHANGED
)
322 cupsdLogMessage(CUPSD_LOG_DEBUG
,
323 "System network configuration changed");
326 * Resetting browse_time before calling cupsdSendBrowseList causes
327 * browse packets to be sent for local shared printers.
330 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
332 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
335 cupsdSendBrowseList();
336 cupsdRestartPolling();
339 cupsdLogMessage(CUPSD_LOG_DEBUG
,
340 "System network configuration changed; "
341 "ignored while sleeping");
344 if (sysevent
.event
& SYSEVENT_NAMECHANGED
)
348 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Computer name changed");
351 * De-register the individual printers...
354 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
356 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
357 cupsdDeregisterPrinter(p
, 1);
360 * Now re-register them...
363 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
365 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
368 cupsdRegisterPrinter(p
);
372 cupsdLogMessage(CUPSD_LOG_DEBUG
,
373 "Computer name changed; ignored while sleeping");
380 * 'sysEventThreadEntry()' - A thread to receive power and computer name
381 * change notifications.
384 static void * /* O - Return status/value */
385 sysEventThreadEntry(void)
387 io_object_t powerNotifierObj
;
388 /* Power notifier object */
389 IONotificationPortRef powerNotifierPort
;
390 /* Power notifier port */
391 SCDynamicStoreRef store
= NULL
;/* System Config dynamic store */
392 CFRunLoopSourceRef powerRLS
= NULL
,/* Power runloop source */
393 storeRLS
= NULL
;/* System Config runloop source */
394 CFStringRef key
[5], /* System Config keys */
395 pattern
[2]; /* System Config patterns */
396 CFArrayRef keys
= NULL
, /* System Config key array*/
397 patterns
= NULL
;/* System Config pattern array */
398 SCDynamicStoreContext storeContext
; /* Dynamic store context */
399 CFRunLoopTimerContext timerContext
; /* Timer context */
400 cupsd_thread_data_t threadData
; /* Thread context data for the *
401 * runloop notifiers */
405 * Register for power state change notifications
408 bzero(&threadData
, sizeof(threadData
));
410 threadData
.sysevent
.powerKernelPort
=
411 IORegisterForSystemPower(&threadData
, &powerNotifierPort
,
412 sysEventPowerNotifier
, &powerNotifierObj
);
414 if (threadData
.sysevent
.powerKernelPort
)
416 powerRLS
= IONotificationPortGetRunLoopSource(powerNotifierPort
);
417 CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS
, kCFRunLoopDefaultMode
);
420 DEBUG_puts("sysEventThreadEntry: error registering for system power "
424 * Register for system configuration change notifications
427 bzero(&storeContext
, sizeof(storeContext
));
428 storeContext
.info
= &threadData
;
430 store
= SCDynamicStoreCreate(NULL
, CFSTR("cupsd"),
431 sysEventConfigurationNotifier
, &storeContext
);
433 if (!ComputerNameKey
)
434 ComputerNameKey
= SCDynamicStoreKeyCreateComputerName(NULL
);
436 if (!NetworkGlobalKeyIPv4
)
437 NetworkGlobalKeyIPv4
=
438 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
439 kSCDynamicStoreDomainState
,
442 if (!NetworkGlobalKeyIPv6
)
443 NetworkGlobalKeyIPv6
=
444 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
445 kSCDynamicStoreDomainState
,
448 if (!NetworkGlobalKeyDNS
)
449 NetworkGlobalKeyDNS
=
450 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
451 kSCDynamicStoreDomainState
,
455 HostNamesKey
= SCDynamicStoreKeyCreateHostNames(NULL
);
457 if (!NetworkInterfaceKeyIPv4
)
458 NetworkInterfaceKeyIPv4
=
459 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL
,
460 kSCDynamicStoreDomainState
,
464 if (!NetworkInterfaceKeyIPv6
)
465 NetworkInterfaceKeyIPv6
=
466 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL
,
467 kSCDynamicStoreDomainState
,
471 if (store
&& ComputerNameKey
&& HostNamesKey
&&
472 NetworkGlobalKeyIPv4
&& NetworkGlobalKeyIPv6
&& NetworkGlobalKeyDNS
&&
473 NetworkInterfaceKeyIPv4
&& NetworkInterfaceKeyIPv6
)
475 key
[0] = ComputerNameKey
;
476 key
[1] = NetworkGlobalKeyIPv4
;
477 key
[2] = NetworkGlobalKeyIPv6
;
478 key
[3] = NetworkGlobalKeyDNS
;
479 key
[4] = HostNamesKey
;
481 pattern
[0] = NetworkInterfaceKeyIPv4
;
482 pattern
[1] = NetworkInterfaceKeyIPv6
;
484 keys
= CFArrayCreate(NULL
, (const void **)key
,
485 sizeof(key
) / sizeof(key
[0]),
486 &kCFTypeArrayCallBacks
);
488 patterns
= CFArrayCreate(NULL
, (const void **)pattern
,
489 sizeof(pattern
) / sizeof(pattern
[0]),
490 &kCFTypeArrayCallBacks
);
492 if (keys
&& patterns
&&
493 SCDynamicStoreSetNotificationKeys(store
, keys
, patterns
))
495 if ((storeRLS
= SCDynamicStoreCreateRunLoopSource(NULL
, store
, 0))
498 CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS
,
499 kCFRunLoopDefaultMode
);
502 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreateRunLoopSource "
503 "failed: %s\n", SCErrorString(SCError())));
506 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreSetNotificationKeys "
507 "failed: %s\n", SCErrorString(SCError())));
510 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreate failed: %s\n",
511 SCErrorString(SCError())));
520 * Set up a timer to delay the wake change notifications.
522 * The initial time is set a decade or so into the future, we'll adjust
526 bzero(&timerContext
, sizeof(timerContext
));
527 timerContext
.info
= &threadData
;
529 threadData
.timerRef
=
530 CFRunLoopTimerCreate(NULL
,
531 CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
532 86400L * 365L * 10L, 0, 0, sysEventTimerNotifier
,
534 CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData
.timerRef
,
535 kCFRunLoopDefaultMode
);
538 * Store our runloop in a global so the main thread can use it to stop us.
541 pthread_mutex_lock(&SysEventThreadMutex
);
543 SysEventRunloop
= CFRunLoopGetCurrent();
545 pthread_cond_signal(&SysEventThreadCond
);
546 pthread_mutex_unlock(&SysEventThreadMutex
);
549 * Disappear into the runloop until it's stopped by the main thread.
555 * Clean up before exiting.
558 if (threadData
.timerRef
)
560 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData
.timerRef
,
561 kCFRunLoopDefaultMode
);
562 CFRelease(threadData
.timerRef
);
565 if (threadData
.sysevent
.powerKernelPort
)
567 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS
,
568 kCFRunLoopDefaultMode
);
569 IODeregisterForSystemPower(&powerNotifierObj
);
570 IOServiceClose(threadData
.sysevent
.powerKernelPort
);
571 IONotificationPortDestroy(powerNotifierPort
);
576 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS
,
577 kCFRunLoopDefaultMode
);
578 CFRunLoopSourceInvalidate(storeRLS
);
590 * 'sysEventPowerNotifier()' - Handle power notification events.
594 sysEventPowerNotifier(
595 void *context
, /* I - Thread context data */
596 io_service_t service
, /* I - Unused service info */
597 natural_t messageType
, /* I - Type of message */
598 void *messageArgument
) /* I - Message data */
600 int sendit
= 1; /* Send event to main thread? *
601 * (0 = no, 1 = yes, 2 = delayed */
602 cupsd_thread_data_t
*threadData
; /* Thread context data */
605 threadData
= (cupsd_thread_data_t
*)context
;
607 (void)service
; /* anti-compiler-warning-code */
611 case kIOMessageCanSystemPowerOff
:
612 case kIOMessageCanSystemSleep
:
613 threadData
->sysevent
.event
|= SYSEVENT_CANSLEEP
;
616 case kIOMessageSystemWillRestart
:
617 case kIOMessageSystemWillPowerOff
:
618 case kIOMessageSystemWillSleep
:
619 threadData
->sysevent
.event
|= SYSEVENT_WILLSLEEP
;
622 case kIOMessageSystemHasPoweredOn
:
624 * Because powered on is followed by a net-changed event, delay
629 threadData
->sysevent
.event
|= SYSEVENT_WOKE
;
632 case kIOMessageSystemWillNotPowerOff
:
633 case kIOMessageSystemWillNotSleep
:
634 #ifdef kIOMessageSystemWillPowerOn
635 case kIOMessageSystemWillPowerOn
:
636 #endif /* kIOMessageSystemWillPowerOn */
643 IOAllowPowerChange(threadData
->sysevent
.powerKernelPort
,
644 (long)messageArgument
);
647 threadData
->sysevent
.powerNotificationID
= (long)messageArgument
;
652 * Send the event to the main thread now.
655 write(SysEventPipes
[1], &threadData
->sysevent
,
656 sizeof(threadData
->sysevent
));
657 threadData
->sysevent
.event
= 0;
662 * Send the event to the main thread after 1 to 2 seconds.
665 CFRunLoopTimerSetNextFireDate(threadData
->timerRef
,
666 CFAbsoluteTimeGetCurrent() + 2);
673 * 'sysEventConfigurationNotifier()' - Computer name changed notification
678 sysEventConfigurationNotifier(
679 SCDynamicStoreRef store
, /* I - System data (unused) */
680 CFArrayRef changedKeys
, /* I - Changed data */
681 void *context
) /* I - Thread context data */
683 cupsd_thread_data_t
*threadData
; /* Thread context data */
686 threadData
= (cupsd_thread_data_t
*)context
;
688 (void)store
; /* anti-compiler-warning-code */
690 CFRange range
= CFRangeMake(0, CFArrayGetCount(changedKeys
));
692 if (CFArrayContainsValue(changedKeys
, range
, ComputerNameKey
))
693 threadData
->sysevent
.event
|= SYSEVENT_NAMECHANGED
;
696 threadData
->sysevent
.event
|= SYSEVENT_NETCHANGED
;
699 * Indicate the network interface list needs updating...
706 * Because we registered for several different kinds of change notifications
707 * this callback usually gets called several times in a row. We use a timer to
708 * de-bounce these so we only end up generating one event for the main thread.
711 CFRunLoopTimerSetNextFireDate(threadData
->timerRef
,
712 CFAbsoluteTimeGetCurrent() + 5);
717 * 'sysEventTimerNotifier()' - Handle delayed event notifications.
721 sysEventTimerNotifier(
722 CFRunLoopTimerRef timer
, /* I - Timer information */
723 void *context
) /* I - Thread context data */
725 cupsd_thread_data_t
*threadData
; /* Thread context data */
728 threadData
= (cupsd_thread_data_t
*)context
;
731 * If an event is still pending send it to the main thread.
734 if (threadData
->sysevent
.event
)
736 write(SysEventPipes
[1], &threadData
->sysevent
,
737 sizeof(threadData
->sysevent
));
738 threadData
->sysevent
.event
= 0;
741 #endif /* __APPLE__ */
745 * End of "$Id: sysman.c 6649 2007-07-11 21:46:42Z mike $".