2 * "$Id: sysman.c 7928 2008-09-10 22:14:22Z mike $"
4 * System management definitions for the Common UNIX Printing System (CUPS).
6 * Copyright 2007-2008 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 * cupsdCleanDirty() - Write dirty config and state files.
18 * cupsdMarkDirty() - Mark config or state files as needing a
20 * cupsdSetBusyState() - Let the system know when we are busy
22 * cupsdStartSystemMonitor() - Start monitoring for system change.
23 * cupsdStopSystemMonitor() - Stop monitoring for system change.
24 * sysEventThreadEntry() - A thread to receive power and computer
25 * name change notifications.
26 * sysEventPowerNotifier() - Handle power notification events.
27 * sysEventConfigurationNotifier() - Computer name changed notification
29 * sysEventTimerNotifier() - Handle delayed event notifications.
30 * sysUpdate() - Update the current system state.
35 * Include necessary headers...
42 * The system management functions cover disk and power management which
43 * are primarily used on portable computers.
45 * Disk management involves delaying the write of certain configuration
46 * and state files to minimize the number of times the disk has to spin
49 * Power management support is currently only implemented on MacOS X, but
50 * essentially we use four functions to let the OS know when it is OK to
51 * put the system to idle sleep, typically when we are not in the middle of
54 * Once put to sleep, we invalidate all remote printers since it is common
55 * to wake up in a new location/on a new wireless network.
60 * 'cupsdCleanDirty()' - Write dirty config and state files.
66 if (DirtyFiles
& CUPSD_DIRTY_PRINTERS
)
67 cupsdSaveAllPrinters();
69 if (DirtyFiles
& CUPSD_DIRTY_CLASSES
)
70 cupsdSaveAllClasses();
72 if (DirtyFiles
& CUPSD_DIRTY_REMOTE
)
73 cupsdSaveRemoteCache();
75 if (DirtyFiles
& CUPSD_DIRTY_PRINTCAP
)
78 if (DirtyFiles
& CUPSD_DIRTY_JOBS
)
80 cupsd_job_t
*job
; /* Current job */
84 for (job
= (cupsd_job_t
*)cupsArrayFirst(Jobs
);
86 job
= (cupsd_job_t
*)cupsArrayNext(Jobs
))
91 if (DirtyFiles
& CUPSD_DIRTY_SUBSCRIPTIONS
)
92 cupsdSaveAllSubscriptions();
94 DirtyFiles
= CUPSD_DIRTY_NONE
;
102 * 'cupsdMarkDirty()' - Mark config or state files as needing a write.
106 cupsdMarkDirty(int what
) /* I - What file(s) are dirty? */
108 if (what
== CUPSD_DIRTY_PRINTCAP
&& !Printcap
)
114 DirtyCleanTime
= time(NULL
) + DirtyCleanInterval
;
121 * 'cupsdSetBusyState()' - Let the system know when we are busy doing something.
125 cupsdSetBusyState(void)
127 int newbusy
; /* New busy state */
128 static int busy
= 0; /* Current busy state */
129 static const char * const busy_text
[] =
130 { /* Text for busy states */
134 "Printing jobs and dirty files",
136 "Active clients and dirty files",
137 "Active clients and printing jobs",
138 "Active clients, printing jobs, and dirty files"
142 newbusy
= (DirtyCleanTime
? 1 : 0) |
143 (cupsArrayCount(PrintingJobs
) ? 2 : 0) |
144 (cupsArrayCount(ActiveClients
) ? 4 : 0);
150 cupsdLogMessage(CUPSD_LOG_DEBUG
, "cupsdSetBusyState: %s", busy_text
[busy
]);
157 * This is the Apple-specific system event code. It works by creating
158 * a worker thread that waits for events from the OS and relays them
159 * to the main thread via a traditional pipe.
163 * Include MacOS-specific headers...
166 # include <IOKit/IOKitLib.h>
167 # include <IOKit/IOMessage.h>
168 # include <IOKit/pwr_mgt/IOPMLib.h>
169 # include <SystemConfiguration/SystemConfiguration.h>
170 # include <pthread.h>
177 # define SYSEVENT_CANSLEEP 0x1 /* Decide whether to allow sleep or not */
178 # define SYSEVENT_WILLSLEEP 0x2 /* Computer will go to sleep */
179 # define SYSEVENT_WOKE 0x4 /* Computer woke from sleep */
180 # define SYSEVENT_NETCHANGED 0x8 /* Network changed */
181 # define SYSEVENT_NAMECHANGED 0x10 /* Computer name changed */
188 typedef struct cupsd_sysevent_s
/*** System event data ****/
190 unsigned char event
; /* Event bit field */
191 io_connect_t powerKernelPort
; /* Power context data */
192 long powerNotificationID
; /* Power event data */
196 typedef struct cupsd_thread_data_s
/*** Thread context data ****/
198 cupsd_sysevent_t sysevent
; /* System event */
199 CFRunLoopTimerRef timerRef
; /* Timer to delay some change *
201 } cupsd_thread_data_t
;
208 static pthread_t SysEventThread
= NULL
;
209 /* Thread to host a runloop */
210 static pthread_mutex_t SysEventThreadMutex
= { 0 };
211 /* Coordinates access to shared gloabals */
212 static pthread_cond_t SysEventThreadCond
= { 0 };
213 /* Thread initialization complete condition */
214 static CFRunLoopRef SysEventRunloop
= NULL
;
215 /* The runloop. Access must be protected! */
216 static CFStringRef ComputerNameKey
= NULL
,
217 /* Computer name key */
218 NetworkGlobalKeyIPv4
= NULL
,
219 /* Network global IPv4 key */
220 NetworkGlobalKeyIPv6
= NULL
,
221 /* Network global IPv6 key */
222 NetworkGlobalKeyDNS
= NULL
,
223 /* Network global DNS key */
226 NetworkInterfaceKeyIPv4
= NULL
,
227 /* Netowrk interface key */
228 NetworkInterfaceKeyIPv6
= NULL
;
229 /* Netowrk interface key */
236 static void *sysEventThreadEntry(void);
237 static void sysEventPowerNotifier(void *context
, io_service_t service
,
238 natural_t messageType
,
239 void *messageArgument
);
240 static void sysEventConfigurationNotifier(SCDynamicStoreRef store
,
241 CFArrayRef changedKeys
,
243 static void sysEventTimerNotifier(CFRunLoopTimerRef timer
, void *context
);
244 static void sysUpdate(void);
248 * 'cupsdStartSystemMonitor()' - Start monitoring for system change.
252 cupsdStartSystemMonitor(void)
254 int flags
; /* fcntl flags on pipe */
257 if (cupsdOpenPipe(SysEventPipes
))
259 cupsdLogMessage(CUPSD_LOG_ERROR
, "System event monitor pipe() failed - %s!",
264 cupsdAddSelect(SysEventPipes
[0], (cupsd_selfunc_t
)sysUpdate
, NULL
, NULL
);
267 * Set non-blocking mode on the descriptor we will be receiving notification
271 flags
= fcntl(SysEventPipes
[0], F_GETFL
, 0);
272 fcntl(SysEventPipes
[0], F_SETFL
, flags
| O_NONBLOCK
);
275 * Start the thread that runs the runloop...
278 pthread_mutex_init(&SysEventThreadMutex
, NULL
);
279 pthread_cond_init(&SysEventThreadCond
, NULL
);
280 pthread_create(&SysEventThread
, NULL
, (void *(*)())sysEventThreadEntry
, NULL
);
285 * 'cupsdStopSystemMonitor()' - Stop monitoring for system change.
289 cupsdStopSystemMonitor(void)
291 CFRunLoopRef rl
; /* The event handler runloop */
297 * Make sure the thread has completed it's initialization and
298 * stored it's runloop reference in the shared global.
301 pthread_mutex_lock(&SysEventThreadMutex
);
303 if (!SysEventRunloop
)
304 pthread_cond_wait(&SysEventThreadCond
, &SysEventThreadMutex
);
306 rl
= SysEventRunloop
;
307 SysEventRunloop
= NULL
;
309 pthread_mutex_unlock(&SysEventThreadMutex
);
314 pthread_join(SysEventThread
, NULL
);
315 pthread_mutex_destroy(&SysEventThreadMutex
);
316 pthread_cond_destroy(&SysEventThreadCond
);
319 if (SysEventPipes
[0] >= 0)
321 cupsdRemoveSelect(SysEventPipes
[0]);
322 cupsdClosePipe(SysEventPipes
);
328 * 'sysEventThreadEntry()' - A thread to receive power and computer name
329 * change notifications.
332 static void * /* O - Return status/value */
333 sysEventThreadEntry(void)
335 io_object_t powerNotifierObj
;
336 /* Power notifier object */
337 IONotificationPortRef powerNotifierPort
;
338 /* Power notifier port */
339 SCDynamicStoreRef store
= NULL
;/* System Config dynamic store */
340 CFRunLoopSourceRef powerRLS
= NULL
,/* Power runloop source */
341 storeRLS
= NULL
;/* System Config runloop source */
342 CFStringRef key
[5], /* System Config keys */
343 pattern
[2]; /* System Config patterns */
344 CFArrayRef keys
= NULL
, /* System Config key array*/
345 patterns
= NULL
;/* System Config pattern array */
346 SCDynamicStoreContext storeContext
; /* Dynamic store context */
347 CFRunLoopTimerContext timerContext
; /* Timer context */
348 cupsd_thread_data_t threadData
; /* Thread context data for the *
349 * runloop notifiers */
353 * Register for power state change notifications
356 bzero(&threadData
, sizeof(threadData
));
358 threadData
.sysevent
.powerKernelPort
=
359 IORegisterForSystemPower(&threadData
, &powerNotifierPort
,
360 sysEventPowerNotifier
, &powerNotifierObj
);
362 if (threadData
.sysevent
.powerKernelPort
)
364 powerRLS
= IONotificationPortGetRunLoopSource(powerNotifierPort
);
365 CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS
, kCFRunLoopDefaultMode
);
368 DEBUG_puts("sysEventThreadEntry: error registering for system power "
372 * Register for system configuration change notifications
375 bzero(&storeContext
, sizeof(storeContext
));
376 storeContext
.info
= &threadData
;
378 store
= SCDynamicStoreCreate(NULL
, CFSTR("cupsd"),
379 sysEventConfigurationNotifier
, &storeContext
);
381 if (!ComputerNameKey
)
382 ComputerNameKey
= SCDynamicStoreKeyCreateComputerName(NULL
);
384 if (!NetworkGlobalKeyIPv4
)
385 NetworkGlobalKeyIPv4
=
386 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
387 kSCDynamicStoreDomainState
,
390 if (!NetworkGlobalKeyIPv6
)
391 NetworkGlobalKeyIPv6
=
392 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
393 kSCDynamicStoreDomainState
,
396 if (!NetworkGlobalKeyDNS
)
397 NetworkGlobalKeyDNS
=
398 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
399 kSCDynamicStoreDomainState
,
403 HostNamesKey
= SCDynamicStoreKeyCreateHostNames(NULL
);
405 if (!NetworkInterfaceKeyIPv4
)
406 NetworkInterfaceKeyIPv4
=
407 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL
,
408 kSCDynamicStoreDomainState
,
412 if (!NetworkInterfaceKeyIPv6
)
413 NetworkInterfaceKeyIPv6
=
414 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL
,
415 kSCDynamicStoreDomainState
,
419 if (store
&& ComputerNameKey
&& HostNamesKey
&&
420 NetworkGlobalKeyIPv4
&& NetworkGlobalKeyIPv6
&& NetworkGlobalKeyDNS
&&
421 NetworkInterfaceKeyIPv4
&& NetworkInterfaceKeyIPv6
)
423 key
[0] = ComputerNameKey
;
424 key
[1] = NetworkGlobalKeyIPv4
;
425 key
[2] = NetworkGlobalKeyIPv6
;
426 key
[3] = NetworkGlobalKeyDNS
;
427 key
[4] = HostNamesKey
;
429 pattern
[0] = NetworkInterfaceKeyIPv4
;
430 pattern
[1] = NetworkInterfaceKeyIPv6
;
432 keys
= CFArrayCreate(NULL
, (const void **)key
,
433 sizeof(key
) / sizeof(key
[0]),
434 &kCFTypeArrayCallBacks
);
436 patterns
= CFArrayCreate(NULL
, (const void **)pattern
,
437 sizeof(pattern
) / sizeof(pattern
[0]),
438 &kCFTypeArrayCallBacks
);
440 if (keys
&& patterns
&&
441 SCDynamicStoreSetNotificationKeys(store
, keys
, patterns
))
443 if ((storeRLS
= SCDynamicStoreCreateRunLoopSource(NULL
, store
, 0))
446 CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS
,
447 kCFRunLoopDefaultMode
);
450 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreateRunLoopSource "
451 "failed: %s\n", SCErrorString(SCError())));
454 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreSetNotificationKeys "
455 "failed: %s\n", SCErrorString(SCError())));
458 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreate failed: %s\n",
459 SCErrorString(SCError())));
468 * Set up a timer to delay the wake change notifications.
470 * The initial time is set a decade or so into the future, we'll adjust
474 bzero(&timerContext
, sizeof(timerContext
));
475 timerContext
.info
= &threadData
;
477 threadData
.timerRef
=
478 CFRunLoopTimerCreate(NULL
,
479 CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
480 86400L * 365L * 10L, 0, 0, sysEventTimerNotifier
,
482 CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData
.timerRef
,
483 kCFRunLoopDefaultMode
);
486 * Store our runloop in a global so the main thread can use it to stop us.
489 pthread_mutex_lock(&SysEventThreadMutex
);
491 SysEventRunloop
= CFRunLoopGetCurrent();
493 pthread_cond_signal(&SysEventThreadCond
);
494 pthread_mutex_unlock(&SysEventThreadMutex
);
497 * Disappear into the runloop until it's stopped by the main thread.
503 * Clean up before exiting.
506 if (threadData
.timerRef
)
508 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData
.timerRef
,
509 kCFRunLoopDefaultMode
);
510 CFRelease(threadData
.timerRef
);
513 if (threadData
.sysevent
.powerKernelPort
)
515 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS
,
516 kCFRunLoopDefaultMode
);
517 IODeregisterForSystemPower(&powerNotifierObj
);
518 IOServiceClose(threadData
.sysevent
.powerKernelPort
);
519 IONotificationPortDestroy(powerNotifierPort
);
524 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS
,
525 kCFRunLoopDefaultMode
);
526 CFRunLoopSourceInvalidate(storeRLS
);
538 * 'sysEventPowerNotifier()' - Handle power notification events.
542 sysEventPowerNotifier(
543 void *context
, /* I - Thread context data */
544 io_service_t service
, /* I - Unused service info */
545 natural_t messageType
, /* I - Type of message */
546 void *messageArgument
) /* I - Message data */
548 int sendit
= 1; /* Send event to main thread? *
549 * (0 = no, 1 = yes, 2 = delayed */
550 cupsd_thread_data_t
*threadData
; /* Thread context data */
553 threadData
= (cupsd_thread_data_t
*)context
;
555 (void)service
; /* anti-compiler-warning-code */
559 case kIOMessageCanSystemPowerOff
:
560 case kIOMessageCanSystemSleep
:
561 threadData
->sysevent
.event
|= SYSEVENT_CANSLEEP
;
564 case kIOMessageSystemWillRestart
:
565 case kIOMessageSystemWillPowerOff
:
566 case kIOMessageSystemWillSleep
:
567 threadData
->sysevent
.event
|= SYSEVENT_WILLSLEEP
;
570 case kIOMessageSystemHasPoweredOn
:
572 * Because powered on is followed by a net-changed event, delay
577 threadData
->sysevent
.event
|= SYSEVENT_WOKE
;
580 case kIOMessageSystemWillNotPowerOff
:
581 case kIOMessageSystemWillNotSleep
:
582 #ifdef kIOMessageSystemWillPowerOn
583 case kIOMessageSystemWillPowerOn
:
584 #endif /* kIOMessageSystemWillPowerOn */
591 IOAllowPowerChange(threadData
->sysevent
.powerKernelPort
,
592 (long)messageArgument
);
595 threadData
->sysevent
.powerNotificationID
= (long)messageArgument
;
600 * Send the event to the main thread now.
603 write(SysEventPipes
[1], &threadData
->sysevent
,
604 sizeof(threadData
->sysevent
));
605 threadData
->sysevent
.event
= 0;
610 * Send the event to the main thread after 1 to 2 seconds.
613 CFRunLoopTimerSetNextFireDate(threadData
->timerRef
,
614 CFAbsoluteTimeGetCurrent() + 2);
621 * 'sysEventConfigurationNotifier()' - Computer name changed notification
626 sysEventConfigurationNotifier(
627 SCDynamicStoreRef store
, /* I - System data (unused) */
628 CFArrayRef changedKeys
, /* I - Changed data */
629 void *context
) /* I - Thread context data */
631 cupsd_thread_data_t
*threadData
; /* Thread context data */
634 threadData
= (cupsd_thread_data_t
*)context
;
636 (void)store
; /* anti-compiler-warning-code */
638 CFRange range
= CFRangeMake(0, CFArrayGetCount(changedKeys
));
640 if (CFArrayContainsValue(changedKeys
, range
, ComputerNameKey
))
641 threadData
->sysevent
.event
|= SYSEVENT_NAMECHANGED
;
644 threadData
->sysevent
.event
|= SYSEVENT_NETCHANGED
;
647 * Indicate the network interface list needs updating...
654 * Because we registered for several different kinds of change notifications
655 * this callback usually gets called several times in a row. We use a timer to
656 * de-bounce these so we only end up generating one event for the main thread.
659 CFRunLoopTimerSetNextFireDate(threadData
->timerRef
,
660 CFAbsoluteTimeGetCurrent() + 5);
665 * 'sysEventTimerNotifier()' - Handle delayed event notifications.
669 sysEventTimerNotifier(
670 CFRunLoopTimerRef timer
, /* I - Timer information */
671 void *context
) /* I - Thread context data */
673 cupsd_thread_data_t
*threadData
; /* Thread context data */
676 threadData
= (cupsd_thread_data_t
*)context
;
679 * If an event is still pending send it to the main thread.
682 if (threadData
->sysevent
.event
)
684 write(SysEventPipes
[1], &threadData
->sysevent
,
685 sizeof(threadData
->sysevent
));
686 threadData
->sysevent
.event
= 0;
692 * 'sysUpdate()' - Update the current system state.
698 int i
; /* Looping var */
699 cupsd_sysevent_t sysevent
; /* The system event */
700 cupsd_printer_t
*p
; /* Printer information */
704 * Drain the event pipe...
707 while (read((int)SysEventPipes
[0], &sysevent
, sizeof(sysevent
))
710 if (sysevent
.event
& SYSEVENT_CANSLEEP
)
713 * If there are active printers that don't have the connecting-to-device
714 * printer-state-reason then cancel the sleep request (i.e. this reason
715 * indicates a job that is not yet connected to the printer)...
718 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
720 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
724 for (i
= 0; i
< p
->num_reasons
; i
++)
725 if (!strcmp(p
->reasons
[i
], "connecting-to-device"))
728 if (!p
->num_reasons
|| i
>= p
->num_reasons
)
735 cupsdLogMessage(CUPSD_LOG_INFO
,
736 "System sleep canceled because printer %s is active",
738 IOCancelPowerChange(sysevent
.powerKernelPort
,
739 sysevent
.powerNotificationID
);
743 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System wants to sleep");
744 IOAllowPowerChange(sysevent
.powerKernelPort
,
745 sysevent
.powerNotificationID
);
749 if (sysevent
.event
& SYSEVENT_WILLSLEEP
)
751 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System going to sleep");
757 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
759 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
761 if (p
->type
& CUPS_PRINTER_DISCOVERED
)
763 cupsdLogMessage(CUPSD_LOG_DEBUG
,
764 "Deleting remote destination \"%s\"", p
->name
);
765 cupsArraySave(Printers
);
766 cupsdDeletePrinter(p
, 0);
767 cupsArrayRestore(Printers
);
771 cupsdLogMessage(CUPSD_LOG_DEBUG
,
772 "Deregistering local printer \"%s\"", p
->name
);
773 cupsdDeregisterPrinter(p
, 0);
779 IOAllowPowerChange(sysevent
.powerKernelPort
,
780 sysevent
.powerNotificationID
);
783 if (sysevent
.event
& SYSEVENT_WOKE
)
785 cupsdLogMessage(CUPSD_LOG_DEBUG
, "System woke from sleep");
786 IOAllowPowerChange(sysevent
.powerKernelPort
,
787 sysevent
.powerNotificationID
);
792 if (sysevent
.event
& SYSEVENT_NETCHANGED
)
796 cupsdLogMessage(CUPSD_LOG_DEBUG
,
797 "System network configuration changed");
800 * Resetting browse_time before calling cupsdSendBrowseList causes
801 * browse packets to be sent for local shared printers.
804 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
806 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
809 cupsdSendBrowseList();
810 cupsdRestartPolling();
813 cupsdLogMessage(CUPSD_LOG_DEBUG
,
814 "System network configuration changed; "
815 "ignored while sleeping");
818 if (sysevent
.event
& SYSEVENT_NAMECHANGED
)
822 cupsdLogMessage(CUPSD_LOG_DEBUG
, "Computer name changed");
825 * De-register the individual printers...
828 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
830 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
831 cupsdDeregisterPrinter(p
, 1);
834 * Update the computer name...
837 cupsdUpdateDNSSDName();
840 * Now re-register them...
843 for (p
= (cupsd_printer_t
*)cupsArrayFirst(Printers
);
845 p
= (cupsd_printer_t
*)cupsArrayNext(Printers
))
848 cupsdRegisterPrinter(p
);
852 cupsdLogMessage(CUPSD_LOG_DEBUG
,
853 "Computer name changed; ignored while sleeping");
857 #endif /* __APPLE__ */
861 * End of "$Id: sysman.c 7928 2008-09-10 22:14:22Z mike $".