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