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