]> git.ipfire.org Git - thirdparty/cups.git/blame - scheduler/sysman.c
Load cups into easysw/current.
[thirdparty/cups.git] / scheduler / sysman.c
CommitLineData
09ec0018 1/*
4400e98d 2 * "$Id: sysman.c 5049 2006-02-02 14:50:57Z mike $"
09ec0018 3 *
4 * System management definitions for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 2006 by Easy Software Products.
7 *
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
13 * at:
14 *
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636 USA
19 *
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
23 *
24 * Contents:
25 *
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
33 * callback.
34 * sysEventTimerNotifier() - Handle delayed event notifications.
35 */
36
37
38/*
39 * Include necessary headers...
40 */
41
42#include "cupsd.h"
43
44
45/*
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.
50 *
51 * Once put to sleep, we invalidate all remote printers since it is
52 * common to wake up in a new location.
53 */
54
55#ifndef __APPLE__
56/*
57 * 'cupsdStartSystemMonitor()' - Start monitoring for system change.
58 */
59
60void
61cupsdStartSystemMonitor(void)
62{
63}
64
65
66/*
67 * 'cupsdStopSystemMonitor()' - Stop monitoring for system change.
68 */
69
70void
71cupsdStopSystemMonitor(void)
72{
73}
74
75
76/*
77 * 'cupsdUpdateSystemMonitor()' - Update the current system state.
78 */
79
80void
81cupsdUpdateSystemMonitor(void)
82{
83}
84#endif /* !__APPLE__ */
85
86
87#ifdef __APPLE__
88/*
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.
92 */
93
94/*
95 * Include MacOS-specific headers...
96 */
97
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>
103
104
105/*
106 * Constants...
107 */
108
a4d04587 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 */
09ec0018 114
115
116/*
117 * Structures...
118 */
119
120typedef struct cupsd_sysevent_s /*** System event data ****/
121{
122 unsigned char event; /* Event bit field */
123 io_connect_t powerKernelPort; /* Power context data */
124 long powerNotificationID; /* Power event data */
125} cupsd_sysevent_t;
126
127
128typedef struct cupsd_thread_data_s /*** Thread context data ****/
129{
130 cupsd_sysevent_t sysevent; /* System event */
131 CFRunLoopTimerRef timerRef; /* Timer to delay some change *
132 * notifications */
133} cupsd_thread_data_t;
134
135
136/*
137 * Local globals...
138 */
139
140static pthread_t SysEventThread = NULL;
141 /* Thread to host a runloop */
142static pthread_mutex_t SysEventThreadMutex = { 0 };
143 /* Coordinates access to shared gloabals */
144static pthread_cond_t SysEventThreadCond = { 0 };
145 /* Thread initialization complete condition */
146static CFRunLoopRef SysEventRunloop = NULL;
147 /* The runloop. Access must be protected! */
148static CFStringRef ComputerNameKey = NULL,
149 /* Computer name key */
150 NetworkGlobalKey = NULL,
151 /* Network global key */
152 HostNamesKey = NULL,
153 /* Host name key */
154 NetworkInterfaceKey = NULL;
155 /* Netowrk interface key */
156
157
158/*
159 * Local functions...
160 */
161
162static void *sysEventThreadEntry(void);
163static void sysEventPowerNotifier(void *context, io_service_t service,
164 natural_t messageType,
165 void *messageArgument);
166static void sysEventConfigurationNotifier(SCDynamicStoreRef store,
167 CFArrayRef changedKeys,
168 void *context);
169static void sysEventTimerNotifier(CFRunLoopTimerRef timer, void *context);
09ec0018 170
171
172/*
173 * 'cupsdStartSystemMonitor()' - Start monitoring for system change.
174 */
175
176void
177cupsdStartSystemMonitor(void)
178{
179 int flags; /* fcntl flags on pipe */
180
181
182 if (cupsdOpenPipe(SysEventPipes))
183 {
184 cupsdLogMessage(CUPSD_LOG_ERROR, "System event monitor pipe() failed - %s!",
185 strerror(errno));
186 return;
187 }
188
189 cupsdLogMessage(CUPSD_LOG_DEBUG2,
190 "cupsdStartSystemMonitor: Adding fd %d to InputSet...",
191 SysEventPipes[0]);
192 FD_SET(SysEventPipes[0], InputSet);
193
194 /*
195 * Set non-blocking mode on the descriptor we will be receiving notification
196 * events on.
197 */
198
199 flags = fcntl(SysEventPipes[0], F_GETFL, 0);
200 fcntl(SysEventPipes[0], F_SETFL, flags | O_NONBLOCK);
201
202 /*
203 * Start the thread that runs the runloop...
204 */
205
206 pthread_mutex_init(&SysEventThreadMutex, NULL);
207 pthread_cond_init(&SysEventThreadCond, NULL);
208 pthread_create(&SysEventThread, NULL, (void *(*)())sysEventThreadEntry, NULL);
209}
210
211
212/*
213 * 'cupsdStopSystemMonitor()' - Stop monitoring for system change.
214 */
215
216void
217cupsdStopSystemMonitor(void)
218{
219 CFRunLoopRef rl; /* The event handler runloop */
220
221
222 if (SysEventThread)
223 {
224 /*
225 * Make sure the thread has completed it's initialization and
226 * stored it's runloop reference in the shared global.
227 */
228
229 pthread_mutex_lock(&SysEventThreadMutex);
230
231 if (!SysEventRunloop)
232 pthread_cond_wait(&SysEventThreadCond, &SysEventThreadMutex);
233
234 rl = SysEventRunloop;
235 SysEventRunloop = NULL;
236
237 pthread_mutex_unlock(&SysEventThreadMutex);
238
239 if (rl)
240 CFRunLoopStop(rl);
241
242 pthread_join(SysEventThread, NULL);
243 pthread_mutex_destroy(&SysEventThreadMutex);
244 pthread_cond_destroy(&SysEventThreadCond);
245 }
246
247 if (SysEventPipes[0] >= 0)
248 {
249 cupsdLogMessage(CUPSD_LOG_DEBUG2,
250 "cupsdStopSystemMonitor: Removing fd %d from InputSet...",
251 SysEventPipes[0]);
252
253 FD_CLR(SysEventPipes[0], InputSet);
254
255 cupsdClosePipe(SysEventPipes);
256 }
257}
258
259
260/*
261 * 'cupsdUpdateSystemMonitor()' - Update the current system state.
262 */
263
264void
265cupsdUpdateSystemMonitor(void)
266{
267 cupsd_sysevent_t sysevent; /* The system event */
268 cupsd_printer_t *p; /* Printer information */
269
270
271 /*
272 * Drain the event pipe...
273 */
274
275 while (read((int)SysEventPipes[0], &sysevent, sizeof(sysevent))
276 == sizeof(sysevent))
277 {
278 if (sysevent.event & SYSEVENT_CANSLEEP)
279 {
280 /*
281 * If there are any active printers cancel the sleep request...
282 */
283
284 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
285 p;
286 p = (cupsd_printer_t *)cupsArrayNext(Printers))
287 if (p->job)
288 break;
289
290 if (p)
291 {
292 cupsdLogMessage(CUPSD_LOG_INFO,
293 "System sleep canceled because printer %s is active",
294 p->name);
295 IOCancelPowerChange(sysevent.powerKernelPort,
296 sysevent.powerNotificationID);
297 }
298 else
299 {
300 cupsdLogMessage(CUPSD_LOG_DEBUG, "System wants to sleep");
301 IOAllowPowerChange(sysevent.powerKernelPort,
302 sysevent.powerNotificationID);
303 }
304 }
305
306 if (sysevent.event & SYSEVENT_WILLSLEEP)
307 {
308 cupsdLogMessage(CUPSD_LOG_DEBUG, "System going to sleep");
309
310 Sleeping = 1;
311
312 cupsdStopAllJobs();
313
314 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
315 p;
316 p = (cupsd_printer_t *)cupsArrayNext(Printers))
317 {
318 if (p->type & CUPS_PRINTER_REMOTE)
319 {
320 cupsdLogMessage(CUPSD_LOG_DEBUG,
321 "Deleting remote destination \"%s\"", p->name);
322 cupsArraySave(Printers);
323 cupsdDeletePrinter(p, 0);
324 cupsArrayRestore(Printers);
325 }
326 else
327 {
328 /* TODO: Possibly update when MDNS support is added? */
329 cupsdLogMessage(CUPSD_LOG_DEBUG,
330 "Deregistering local printer \"%s\"", p->name);
331 cupsdSendBrowseDelete(p);
332 }
333 }
334
335 IOAllowPowerChange(sysevent.powerKernelPort,
336 sysevent.powerNotificationID);
337 }
338
339 if (sysevent.event & SYSEVENT_WOKE)
340 {
341 cupsdLogMessage(CUPSD_LOG_DEBUG, "System woke from sleep");
342 IOAllowPowerChange(sysevent.powerKernelPort,
343 sysevent.powerNotificationID);
344 Sleeping = 0;
345 cupsdCheckJobs();
346 }
347
348 if (sysevent.event & SYSEVENT_NETCHANGED)
349 {
350 if (!Sleeping)
351 {
352 cupsdLogMessage(CUPSD_LOG_DEBUG,
353 "System network configuration changed");
354
355 /*
356 * Force an update of the list of network interfaces in 2 seconds.
357 */
358
359 NetIFTime = time(NULL) - 58;
360
361 /*
362 * Resetting browse_time before calling cupsdSendBrowseList causes
363 * browse packets to be sent for local shared printers.
364 */
365
366 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
367 p;
368 p = (cupsd_printer_t *)cupsArrayNext(Printers))
369 p->browse_time = 0;
370
371 cupsdSendBrowseList();
372 }
373 else
374 cupsdLogMessage(CUPSD_LOG_DEBUG,
375 "System network configuration changed; "
376 "ignored while sleeping");
377 }
378
379 if (sysevent.event & SYSEVENT_NAMECHANGED)
380 {
381 if (!Sleeping)
382 {
383 cupsdLogMessage(CUPSD_LOG_DEBUG, "Computer name changed");
384
385 /*
386 * De-register the individual printers...
387 */
388
389 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
390 p;
391 p = (cupsd_printer_t *)cupsArrayNext(Printers))
392 cupsdSendBrowseDelete(p);
393
394 /*
395 * Now re-register them...
396 *
397 * TODO: This might need updating for MDNS.
398 */
399
400 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
401 p;
402 p = (cupsd_printer_t *)cupsArrayNext(Printers))
403 p->browse_time = 0;
404 }
405 else
406 cupsdLogMessage(CUPSD_LOG_DEBUG,
407 "Computer name changed; ignored while sleeping");
408 }
409 }
410}
411
412
413/*
414 * 'sysEventThreadEntry()' - A thread to receive power and computer name
415 * change notifications.
416 */
417
418static void * /* O - Return status/value */
419sysEventThreadEntry(void)
420{
421 io_object_t powerNotifierObj;
422 /* Power notifier object */
423 IONotificationPortRef powerNotifierPort;
424 /* Power notifier port */
425 SCDynamicStoreRef store = NULL;/* System Config dynamic store */
426 CFRunLoopSourceRef powerRLS = NULL,/* Power runloop source */
427 storeRLS = NULL;/* System Config runloop source */
428 CFStringRef key[3], /* System Config keys */
429 pattern[1]; /* System Config patterns */
430 CFArrayRef keys = NULL, /* System Config key array*/
431 patterns = NULL;/* System Config pattern array */
432 SCDynamicStoreContext storeContext; /* Dynamic store context */
433 CFRunLoopTimerContext timerContext; /* Timer context */
434 cupsd_thread_data_t threadData; /* Thread context data for the *
435 * runloop notifiers */
436
437
438 /*
439 * Register for power state change notifications
440 */
441
442 bzero(&threadData, sizeof(threadData));
443
444 threadData.sysevent.powerKernelPort =
445 IORegisterForSystemPower(&threadData, &powerNotifierPort,
446 sysEventPowerNotifier, &powerNotifierObj);
447
448 if (threadData.sysevent.powerKernelPort)
449 {
450 powerRLS = IONotificationPortGetRunLoopSource(powerNotifierPort);
451 CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
452 }
453 else
454 DEBUG_puts("sysEventThreadEntry: error registering for system power "
455 "notifications");
456
457 /*
458 * Register for system configuration change notifications
459 */
460
461 bzero(&storeContext, sizeof(storeContext));
462 storeContext.info = &threadData;
463
464 store = SCDynamicStoreCreate(NULL, CFSTR("cupsd"),
465 sysEventConfigurationNotifier, &storeContext);
466
467 if (!ComputerNameKey)
468 ComputerNameKey = SCDynamicStoreKeyCreateComputerName(NULL);
469
470 if (!NetworkGlobalKey)
471 NetworkGlobalKey =
472 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
473 kSCDynamicStoreDomainState,
474 kSCEntNetIPv4);
475
476 if (!HostNamesKey)
477 HostNamesKey = SCDynamicStoreKeyCreateHostNames(NULL);
478
479 if (!NetworkInterfaceKey)
480 NetworkInterfaceKey =
481 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
482 kSCDynamicStoreDomainState,
483 kSCCompAnyRegex,
484 kSCEntNetIPv4);
485
486 if (store && ComputerNameKey && NetworkGlobalKey && HostNamesKey &&
487 NetworkInterfaceKey)
488 {
489 key[0] = ComputerNameKey;
490 key[1] = NetworkGlobalKey;
491 key[2] = HostNamesKey;
492 pattern[0] = NetworkInterfaceKey;
493
494 keys = CFArrayCreate(NULL, (const void **)key,
495 sizeof(key) / sizeof(key[0]),
496 &kCFTypeArrayCallBacks);
497 patterns = CFArrayCreate(NULL, (const void **)pattern,
498 sizeof(pattern) / sizeof(pattern[0]),
499 &kCFTypeArrayCallBacks);
500
501 if (keys && patterns &&
502 SCDynamicStoreSetNotificationKeys(store, keys, patterns))
503 {
504 if ((storeRLS = SCDynamicStoreCreateRunLoopSource(NULL, store, 0))
505 != NULL)
506 {
507 CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS,
508 kCFRunLoopDefaultMode);
509 }
510 else
511 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreateRunLoopSource "
512 "failed: %s\n", SCErrorString(SCError())));
513 }
514 else
515 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreSetNotificationKeys "
516 "failed: %s\n", SCErrorString(SCError())));
517 }
518 else
519 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreate failed: %s\n",
520 SCErrorString(SCError())));
521
522 if (keys)
523 CFRelease(keys);
524
525 if (patterns)
526 CFRelease(patterns);
527
528 /*
529 * Set up a timer to delay the wake change notifications.
530 *
531 * The initial time is set a decade or so into the future, we'll adjust
532 * this later.
533 */
534
535 bzero(&timerContext, sizeof(timerContext));
536 timerContext.info = &threadData;
537
538 threadData.timerRef =
539 CFRunLoopTimerCreate(NULL,
540 CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
541 86400L * 365L * 10L, 0, 0, sysEventTimerNotifier,
542 &timerContext);
543 CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData.timerRef,
544 kCFRunLoopDefaultMode);
545
546 /*
547 * Store our runloop in a global so the main thread can use it to stop us.
548 */
549
550 pthread_mutex_lock(&SysEventThreadMutex);
551
552 SysEventRunloop = CFRunLoopGetCurrent();
553
554 pthread_cond_signal(&SysEventThreadCond);
555 pthread_mutex_unlock(&SysEventThreadMutex);
556
557 /*
558 * Disappear into the runloop until it's stopped by the main thread.
559 */
560
561 CFRunLoopRun();
562
563 /*
564 * Clean up before exiting.
565 */
566
567 if (threadData.timerRef)
568 {
569 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData.timerRef,
570 kCFRunLoopDefaultMode);
571 CFRelease(threadData.timerRef);
572 }
573
574 if (threadData.sysevent.powerKernelPort)
575 {
576 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS,
577 kCFRunLoopDefaultMode);
578 IODeregisterForSystemPower(&powerNotifierObj);
579 IOServiceClose(threadData.sysevent.powerKernelPort);
580 IONotificationPortDestroy(powerNotifierPort);
581 }
582
583 if (storeRLS)
584 {
585 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS,
586 kCFRunLoopDefaultMode);
587 CFRunLoopSourceInvalidate(storeRLS);
588 CFRelease(storeRLS);
589 }
590
591 if (store)
592 CFRelease(store);
593
594 pthread_exit(NULL);
595}
596
597
598/*
599 * 'sysEventPowerNotifier()' - Handle power notification events.
600 */
601
602static void
603sysEventPowerNotifier(
604 void *context, /* I - Thread context data */
605 io_service_t service, /* I - Unused service info */
606 natural_t messageType, /* I - Type of message */
607 void *messageArgument) /* I - Message data */
608{
609 int sendit = 1; /* Send event to main thread? *
610 * (0 = no, 1 = yes, 2 = delayed */
611 cupsd_thread_data_t *threadData; /* Thread context data */
612
613
614 threadData = (cupsd_thread_data_t *)context;
615
616 (void)service; /* anti-compiler-warning-code */
617
618 switch (messageType)
619 {
620 case kIOMessageCanSystemPowerOff:
621 case kIOMessageCanSystemSleep:
622 threadData->sysevent.event |= SYSEVENT_CANSLEEP;
623 break;
624
625 case kIOMessageSystemWillRestart:
626 case kIOMessageSystemWillPowerOff:
627 case kIOMessageSystemWillSleep:
628 threadData->sysevent.event |= SYSEVENT_WILLSLEEP;
629 break;
630
631 case kIOMessageSystemHasPoweredOn:
632 /*
633 * Because powered on is followed by a net-changed event, delay
634 * before sending it.
635 */
636
637 sendit = 2;
638 threadData->sysevent.event |= SYSEVENT_WOKE;
639 break;
640
641 case kIOMessageSystemWillNotPowerOff:
642 case kIOMessageSystemWillNotSleep:
4400e98d 643#ifdef kIOMessageSystemWillPowerOn
09ec0018 644 case kIOMessageSystemWillPowerOn:
4400e98d 645#endif /* kIOMessageSystemWillPowerOn */
09ec0018 646 default:
647 sendit = 0;
648 break;
649 }
650
651 if (sendit == 0)
652 IOAllowPowerChange(threadData->sysevent.powerKernelPort,
653 (long)messageArgument);
654 else
655 {
656 threadData->sysevent.powerNotificationID = (long)messageArgument;
657
658 if (sendit == 1)
659 {
660 /*
661 * Send the event to the main thread now.
662 */
663
664 write(SysEventPipes[1], &threadData->sysevent,
665 sizeof(threadData->sysevent));
666 threadData->sysevent.event = 0;
667 }
668 else
669 {
670 /*
671 * Send the event to the main thread after 1 to 2 seconds.
672 */
673
674 CFRunLoopTimerSetNextFireDate(threadData->timerRef,
675 CFAbsoluteTimeGetCurrent() + 2);
676 }
677 }
678}
679
680
681/*
682 * 'sysEventConfigurationNotifier()' - Computer name changed notification
683 * callback.
684 */
685
686static void
687sysEventConfigurationNotifier(
688 SCDynamicStoreRef store, /* I - System data (unused) */
689 CFArrayRef changedKeys, /* I - Changed data */
690 void *context) /* I - Thread context data */
691{
692 cupsd_thread_data_t *threadData; /* Thread context data */
693
694
695 threadData = (cupsd_thread_data_t *)context;
696
697 (void)store; /* anti-compiler-warning-code */
698
699 CFRange range = CFRangeMake(0, CFArrayGetCount(changedKeys));
700
701 if (CFArrayContainsValue(changedKeys, range, ComputerNameKey))
702 threadData->sysevent.event |= SYSEVENT_NAMECHANGED;
703
704 if (CFArrayContainsValue(changedKeys, range, NetworkGlobalKey) ||
705 CFArrayContainsValue(changedKeys, range, HostNamesKey) ||
706 CFArrayContainsValue(changedKeys, range, NetworkInterfaceKey))
707 threadData->sysevent.event |= SYSEVENT_NETCHANGED;
708
709 /*
710 * Because we registered for several different kinds of change notifications
711 * this callback usually gets called several times in a row. We use a timer to
712 * de-bounce these so we only end up generating one event for the main thread.
713 */
714
715 CFRunLoopTimerSetNextFireDate(threadData->timerRef,
716 CFAbsoluteTimeGetCurrent() + 2);
717}
718
719
720/*
721 * 'sysEventTimerNotifier()' - Handle delayed event notifications.
722 */
723
724static void
725sysEventTimerNotifier(
726 CFRunLoopTimerRef timer, /* I - Timer information */
727 void *context) /* I - Thread context data */
728{
729 cupsd_thread_data_t *threadData; /* Thread context data */
730
731
732 threadData = (cupsd_thread_data_t *)context;
733
734 /*
735 * If an event is still pending send it to the main thread.
736 */
737
738 if (threadData->sysevent.event)
739 {
740 write(SysEventPipes[1], &threadData->sysevent,
741 sizeof(threadData->sysevent));
742 threadData->sysevent.event = 0;
743 }
744}
a4d04587 745#endif /* __APPLE__ */
09ec0018 746
747
748/*
4400e98d 749 * End of "$Id: sysman.c 5049 2006-02-02 14:50:57Z mike $".
09ec0018 750 */