2 * "$Id: sysman.c 6090 2006-11-14 16:35:27Z 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 * This is the Apple-specific system event code. It works by creating
58 * a worker thread that waits for events from the OS and relays them
59 * to the main thread via a traditional pipe.
63 * Include MacOS-specific headers...
66 # include <IOKit/IOKitLib.h>
67 # include <IOKit/IOMessage.h>
68 # include <IOKit/pwr_mgt/IOPMLib.h>
69 # include <SystemConfiguration/SystemConfiguration.h>
77 # define SYSEVENT_CANSLEEP 0x1 /* Decide whether to allow sleep or not */
78 # define SYSEVENT_WILLSLEEP 0x2 /* Computer will go to sleep */
79 # define SYSEVENT_WOKE 0x4 /* Computer woke from sleep */
80 # define SYSEVENT_NETCHANGED 0x8 /* Network changed */
81 # define SYSEVENT_NAMECHANGED 0x10 /* Computer name changed */
88 typedef struct cupsd_sysevent_s
/*** System event data ****/
90 unsigned char event
; /* Event bit field */
91 io_connect_t powerKernelPort
; /* Power context data */
92 long powerNotificationID
; /* Power event data */
96 typedef struct cupsd_thread_data_s
/*** Thread context data ****/
98 cupsd_sysevent_t sysevent
; /* System event */
99 CFRunLoopTimerRef timerRef
; /* Timer to delay some change *
101 } cupsd_thread_data_t
;
108 static pthread_t SysEventThread
= NULL
;
109 /* Thread to host a runloop */
110 static pthread_mutex_t SysEventThreadMutex
= { 0 };
111 /* Coordinates access to shared gloabals */
112 static pthread_cond_t SysEventThreadCond
= { 0 };
113 /* Thread initialization complete condition */
114 static CFRunLoopRef SysEventRunloop
= NULL
;
115 /* The runloop. Access must be protected! */
116 static CFStringRef ComputerNameKey
= NULL
,
117 /* Computer name key */
118 NetworkGlobalKeyIPv4
= NULL
,
119 /* Network global IPv4 key */
120 NetworkGlobalKeyIPv6
= NULL
,
121 /* Network global IPv6 key */
122 NetworkGlobalKeyDNS
= NULL
,
123 /* Network global DNS key */
126 NetworkInterfaceKeyIPv4
= NULL
,
127 /* Netowrk interface key */
128 NetworkInterfaceKeyIPv6
= NULL
;
129 /* Netowrk interface key */
136 static void *sysEventThreadEntry(void);
137 static void sysEventPowerNotifier(void *context
, io_service_t service
,
138 natural_t messageType
,
139 void *messageArgument
);
140 static void sysEventConfigurationNotifier(SCDynamicStoreRef store
,
141 CFArrayRef changedKeys
,
143 static void sysEventTimerNotifier(CFRunLoopTimerRef timer
, void *context
);
147 * 'cupsdStartSystemMonitor()' - Start monitoring for system change.
151 cupsdStartSystemMonitor(void)
153 int flags
; /* fcntl flags on pipe */
156 if (cupsdOpenPipe(SysEventPipes
))
158 cupsdLogMessage(CUPSD_LOG_ERROR
, "System event monitor pipe() failed - %s!",
163 cupsdLogMessage(CUPSD_LOG_DEBUG2
,
164 "cupsdStartSystemMonitor: Adding fd %d to InputSet...",
166 FD_SET(SysEventPipes
[0], InputSet
);
169 * Set non-blocking mode on the descriptor we will be receiving notification
173 flags
= fcntl(SysEventPipes
[0], F_GETFL
, 0);
174 fcntl(SysEventPipes
[0], F_SETFL
, flags
| O_NONBLOCK
);
177 * Start the thread that runs the runloop...
180 pthread_mutex_init(&SysEventThreadMutex
, NULL
);
181 pthread_cond_init(&SysEventThreadCond
, NULL
);
182 pthread_create(&SysEventThread
, NULL
, (void *(*)())sysEventThreadEntry
, NULL
);
187 * 'cupsdStopSystemMonitor()' - Stop monitoring for system change.
191 cupsdStopSystemMonitor(void)
193 CFRunLoopRef rl
; /* The event handler runloop */
199 * Make sure the thread has completed it's initialization and
200 * stored it's runloop reference in the shared global.
203 pthread_mutex_lock(&SysEventThreadMutex
);
205 if (!SysEventRunloop
)
206 pthread_cond_wait(&SysEventThreadCond
, &SysEventThreadMutex
);
208 rl
= SysEventRunloop
;
209 SysEventRunloop
= NULL
;
211 pthread_mutex_unlock(&SysEventThreadMutex
);
216 pthread_join(SysEventThread
, NULL
);
217 pthread_mutex_destroy(&SysEventThreadMutex
);
218 pthread_cond_destroy(&SysEventThreadCond
);
221 if (SysEventPipes
[0] >= 0)
223 cupsdLogMessage(CUPSD_LOG_DEBUG2
,
224 "cupsdStopSystemMonitor: Removing fd %d from InputSet...",
227 FD_CLR(SysEventPipes
[0], InputSet
);
229 cupsdClosePipe(SysEventPipes
);
235 * 'cupsdUpdateSystemMonitor()' - Update the current system state.
239 cupsdUpdateSystemMonitor(void)
241 int i
; /* Looping var */
242 cupsd_sysevent_t sysevent
; /* The system event */
243 cupsd_printer_t
*p
; /* Printer information */
247 * Drain the event pipe...
250 while (read((int)SysEventPipes
[0], &sysevent
, sizeof(sysevent
))
253 if (sysevent
.event
& SYSEVENT_CANSLEEP
)
256 * If there are active printers that don't have the connecting-to-device
257 * printer-state-reason then cancel the sleep request (i.e. this reason
258 * indicates a job that is not yet connected to the printer)...
261 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
263 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
267 for (i
= 0; i
< p
->num_reasons
; i
++)
268 if (!strcmp(p
->reasons
[i
], "connecting-to-device"))
271 if (!p
->num_reasons
|| i
>= p
->num_reasons
)
278 cupsdLogMessage(CUPSD_LOG_INFO
,
279 "System sleep canceled because printer %s is active",
281 IOCancelPowerChange(sysevent
.powerKernelPort
,
282 sysevent
.powerNotificationID
);
286 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System wants to sleep");
287 IOAllowPowerChange(sysevent
.powerKernelPort
,
288 sysevent
.powerNotificationID
);
292 if (sysevent
.event
& SYSEVENT_WILLSLEEP
)
294 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System going to sleep");
301 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
303 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
305 if (p
->type
& CUPS_PRINTER_REMOTE
)
307 cupsdLogMessage(CUPSD_LOG_DEBUG
,
308 "Deleting remote destination \"%s\"", p
->name
);
309 cupsArraySave(Printers
);
310 cupsdDeletePrinter(p
, 0);
311 cupsArrayRestore(Printers
);
315 cupsdLogMessage(CUPSD_LOG_DEBUG
,
316 "Deregistering local printer \"%s\"", p
->name
);
317 cupsdSendBrowseDelete(p
);
321 IOAllowPowerChange(sysevent
.powerKernelPort
,
322 sysevent
.powerNotificationID
);
325 if (sysevent
.event
& SYSEVENT_WOKE
)
327 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System woke from sleep");
328 IOAllowPowerChange(sysevent
.powerKernelPort
,
329 sysevent
.powerNotificationID
);
334 if (sysevent
.event
& SYSEVENT_NETCHANGED
)
338 cupsdLogMessage(CUPSD_LOG_DEBUG
,
339 "System network configuration changed");
342 * Resetting browse_time before calling cupsdSendBrowseList causes
343 * browse packets to be sent for local shared printers.
346 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
348 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
351 cupsdSendBrowseList();
352 cupsdRestartPolling();
355 cupsdLogMessage(CUPSD_LOG_DEBUG
,
356 "System network configuration changed; "
357 "ignored while sleeping");
360 if (sysevent
.event
& SYSEVENT_NAMECHANGED
)
364 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Computer name changed");
367 * De-register the individual printers...
370 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
372 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
373 cupsdSendBrowseDelete(p
);
376 * Now re-register them...
378 * TODO: This might need updating for MDNS.
381 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
383 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
387 cupsdLogMessage(CUPSD_LOG_DEBUG
,
388 "Computer name changed; ignored while sleeping");
395 * 'sysEventThreadEntry()' - A thread to receive power and computer name
396 * change notifications.
399 static void * /* O - Return status/value */
400 sysEventThreadEntry(void)
402 io_object_t powerNotifierObj
;
403 /* Power notifier object */
404 IONotificationPortRef powerNotifierPort
;
405 /* Power notifier port */
406 SCDynamicStoreRef store
= NULL
;/* System Config dynamic store */
407 CFRunLoopSourceRef powerRLS
= NULL
,/* Power runloop source */
408 storeRLS
= NULL
;/* System Config runloop source */
409 CFStringRef key
[5], /* System Config keys */
410 pattern
[2]; /* System Config patterns */
411 CFArrayRef keys
= NULL
, /* System Config key array*/
412 patterns
= NULL
;/* System Config pattern array */
413 SCDynamicStoreContext storeContext
; /* Dynamic store context */
414 CFRunLoopTimerContext timerContext
; /* Timer context */
415 cupsd_thread_data_t threadData
; /* Thread context data for the *
416 * runloop notifiers */
420 * Register for power state change notifications
423 bzero(&threadData
, sizeof(threadData
));
425 threadData
.sysevent
.powerKernelPort
=
426 IORegisterForSystemPower(&threadData
, &powerNotifierPort
,
427 sysEventPowerNotifier
, &powerNotifierObj
);
429 if (threadData
.sysevent
.powerKernelPort
)
431 powerRLS
= IONotificationPortGetRunLoopSource(powerNotifierPort
);
432 CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS
, kCFRunLoopDefaultMode
);
435 DEBUG_puts("sysEventThreadEntry: error registering for system power "
439 * Register for system configuration change notifications
442 bzero(&storeContext
, sizeof(storeContext
));
443 storeContext
.info
= &threadData
;
445 store
= SCDynamicStoreCreate(NULL
, CFSTR("cupsd"),
446 sysEventConfigurationNotifier
, &storeContext
);
448 if (!ComputerNameKey
)
449 ComputerNameKey
= SCDynamicStoreKeyCreateComputerName(NULL
);
451 if (!NetworkGlobalKeyIPv4
)
452 NetworkGlobalKeyIPv4
=
453 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
454 kSCDynamicStoreDomainState
,
457 if (!NetworkGlobalKeyIPv6
)
458 NetworkGlobalKeyIPv6
=
459 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
460 kSCDynamicStoreDomainState
,
463 if (!NetworkGlobalKeyDNS
)
464 NetworkGlobalKeyDNS
=
465 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
466 kSCDynamicStoreDomainState
,
470 HostNamesKey
= SCDynamicStoreKeyCreateHostNames(NULL
);
472 if (!NetworkInterfaceKeyIPv4
)
473 NetworkInterfaceKeyIPv4
=
474 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL
,
475 kSCDynamicStoreDomainState
,
479 if (!NetworkInterfaceKeyIPv6
)
480 NetworkInterfaceKeyIPv6
=
481 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL
,
482 kSCDynamicStoreDomainState
,
486 if (store
&& ComputerNameKey
&& HostNamesKey
&&
487 NetworkGlobalKeyIPv4
&& NetworkGlobalKeyIPv6
&& NetworkGlobalKeyDNS
&&
488 NetworkInterfaceKeyIPv4
&& NetworkInterfaceKeyIPv6
)
490 key
[0] = ComputerNameKey
;
491 key
[1] = NetworkGlobalKeyIPv4
;
492 key
[2] = NetworkGlobalKeyIPv6
;
493 key
[3] = NetworkGlobalKeyDNS
;
494 key
[4] = HostNamesKey
;
496 pattern
[0] = NetworkInterfaceKeyIPv4
;
497 pattern
[1] = NetworkInterfaceKeyIPv6
;
499 keys
= CFArrayCreate(NULL
, (const void **)key
,
500 sizeof(key
) / sizeof(key
[0]),
501 &kCFTypeArrayCallBacks
);
503 patterns
= CFArrayCreate(NULL
, (const void **)pattern
,
504 sizeof(pattern
) / sizeof(pattern
[0]),
505 &kCFTypeArrayCallBacks
);
507 if (keys
&& patterns
&&
508 SCDynamicStoreSetNotificationKeys(store
, keys
, patterns
))
510 if ((storeRLS
= SCDynamicStoreCreateRunLoopSource(NULL
, store
, 0))
513 CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS
,
514 kCFRunLoopDefaultMode
);
517 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreateRunLoopSource "
518 "failed: %s\n", SCErrorString(SCError())));
521 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreSetNotificationKeys "
522 "failed: %s\n", SCErrorString(SCError())));
525 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreate failed: %s\n",
526 SCErrorString(SCError())));
535 * Set up a timer to delay the wake change notifications.
537 * The initial time is set a decade or so into the future, we'll adjust
541 bzero(&timerContext
, sizeof(timerContext
));
542 timerContext
.info
= &threadData
;
544 threadData
.timerRef
=
545 CFRunLoopTimerCreate(NULL
,
546 CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
547 86400L * 365L * 10L, 0, 0, sysEventTimerNotifier
,
549 CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData
.timerRef
,
550 kCFRunLoopDefaultMode
);
553 * Store our runloop in a global so the main thread can use it to stop us.
556 pthread_mutex_lock(&SysEventThreadMutex
);
558 SysEventRunloop
= CFRunLoopGetCurrent();
560 pthread_cond_signal(&SysEventThreadCond
);
561 pthread_mutex_unlock(&SysEventThreadMutex
);
564 * Disappear into the runloop until it's stopped by the main thread.
570 * Clean up before exiting.
573 if (threadData
.timerRef
)
575 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData
.timerRef
,
576 kCFRunLoopDefaultMode
);
577 CFRelease(threadData
.timerRef
);
580 if (threadData
.sysevent
.powerKernelPort
)
582 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS
,
583 kCFRunLoopDefaultMode
);
584 IODeregisterForSystemPower(&powerNotifierObj
);
585 IOServiceClose(threadData
.sysevent
.powerKernelPort
);
586 IONotificationPortDestroy(powerNotifierPort
);
591 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS
,
592 kCFRunLoopDefaultMode
);
593 CFRunLoopSourceInvalidate(storeRLS
);
605 * 'sysEventPowerNotifier()' - Handle power notification events.
609 sysEventPowerNotifier(
610 void *context
, /* I - Thread context data */
611 io_service_t service
, /* I - Unused service info */
612 natural_t messageType
, /* I - Type of message */
613 void *messageArgument
) /* I - Message data */
615 int sendit
= 1; /* Send event to main thread? *
616 * (0 = no, 1 = yes, 2 = delayed */
617 cupsd_thread_data_t
*threadData
; /* Thread context data */
620 threadData
= (cupsd_thread_data_t
*)context
;
622 (void)service
; /* anti-compiler-warning-code */
626 case kIOMessageCanSystemPowerOff
:
627 case kIOMessageCanSystemSleep
:
628 threadData
->sysevent
.event
|= SYSEVENT_CANSLEEP
;
631 case kIOMessageSystemWillRestart
:
632 case kIOMessageSystemWillPowerOff
:
633 case kIOMessageSystemWillSleep
:
634 threadData
->sysevent
.event
|= SYSEVENT_WILLSLEEP
;
637 case kIOMessageSystemHasPoweredOn
:
639 * Because powered on is followed by a net-changed event, delay
644 threadData
->sysevent
.event
|= SYSEVENT_WOKE
;
647 case kIOMessageSystemWillNotPowerOff
:
648 case kIOMessageSystemWillNotSleep
:
649 #ifdef kIOMessageSystemWillPowerOn
650 case kIOMessageSystemWillPowerOn
:
651 #endif /* kIOMessageSystemWillPowerOn */
658 IOAllowPowerChange(threadData
->sysevent
.powerKernelPort
,
659 (long)messageArgument
);
662 threadData
->sysevent
.powerNotificationID
= (long)messageArgument
;
667 * Send the event to the main thread now.
670 write(SysEventPipes
[1], &threadData
->sysevent
,
671 sizeof(threadData
->sysevent
));
672 threadData
->sysevent
.event
= 0;
677 * Send the event to the main thread after 1 to 2 seconds.
680 CFRunLoopTimerSetNextFireDate(threadData
->timerRef
,
681 CFAbsoluteTimeGetCurrent() + 2);
688 * 'sysEventConfigurationNotifier()' - Computer name changed notification
693 sysEventConfigurationNotifier(
694 SCDynamicStoreRef store
, /* I - System data (unused) */
695 CFArrayRef changedKeys
, /* I - Changed data */
696 void *context
) /* I - Thread context data */
698 cupsd_thread_data_t
*threadData
; /* Thread context data */
701 threadData
= (cupsd_thread_data_t
*)context
;
703 (void)store
; /* anti-compiler-warning-code */
705 CFRange range
= CFRangeMake(0, CFArrayGetCount(changedKeys
));
707 if (CFArrayContainsValue(changedKeys
, range
, ComputerNameKey
))
708 threadData
->sysevent
.event
|= SYSEVENT_NAMECHANGED
;
711 threadData
->sysevent
.event
|= SYSEVENT_NETCHANGED
;
714 * Indicate the network interface list needs updating...
721 * Because we registered for several different kinds of change notifications
722 * this callback usually gets called several times in a row. We use a timer to
723 * de-bounce these so we only end up generating one event for the main thread.
726 CFRunLoopTimerSetNextFireDate(threadData
->timerRef
,
727 CFAbsoluteTimeGetCurrent() + 5);
732 * 'sysEventTimerNotifier()' - Handle delayed event notifications.
736 sysEventTimerNotifier(
737 CFRunLoopTimerRef timer
, /* I - Timer information */
738 void *context
) /* I - Thread context data */
740 cupsd_thread_data_t
*threadData
; /* Thread context data */
743 threadData
= (cupsd_thread_data_t
*)context
;
746 * If an event is still pending send it to the main thread.
749 if (threadData
->sysevent
.event
)
751 write(SysEventPipes
[1], &threadData
->sysevent
,
752 sizeof(threadData
->sysevent
));
753 threadData
->sysevent
.event
= 0;
756 #endif /* __APPLE__ */
760 * End of "$Id: sysman.c 6090 2006-11-14 16:35:27Z mike $".