]> 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/*
f7faf1f5 2 * "$Id: sysman.c 5305 2006-03-18 03:05:12Z 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
09ec0018 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
a4d04587 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 */
09ec0018 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);
09ec0018 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{
757d2cad 235 int i; /* Looping var */
09ec0018 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 /*
757d2cad 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)...
09ec0018 253 */
254
255 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
256 p;
257 p = (cupsd_printer_t *)cupsArrayNext(Printers))
757d2cad 258 {
09ec0018 259 if (p->job)
757d2cad 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 }
09ec0018 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();
bd7854cb 293 cupsdSaveAllJobs();
09ec0018 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 }
354 else
355 cupsdLogMessage(CUPSD_LOG_DEBUG,
356 "System network configuration changed; "
357 "ignored while sleeping");
358 }
359
360 if (sysevent.event & SYSEVENT_NAMECHANGED)
361 {
362 if (!Sleeping)
363 {
364 cupsdLogMessage(CUPSD_LOG_DEBUG, "Computer name changed");
365
366 /*
367 * De-register the individual printers...
368 */
369
370 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
371 p;
372 p = (cupsd_printer_t *)cupsArrayNext(Printers))
373 cupsdSendBrowseDelete(p);
374
375 /*
376 * Now re-register them...
377 *
378 * TODO: This might need updating for MDNS.
379 */
380
381 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
382 p;
383 p = (cupsd_printer_t *)cupsArrayNext(Printers))
384 p->browse_time = 0;
385 }
386 else
387 cupsdLogMessage(CUPSD_LOG_DEBUG,
388 "Computer name changed; ignored while sleeping");
389 }
390 }
391}
392
393
394/*
395 * 'sysEventThreadEntry()' - A thread to receive power and computer name
396 * change notifications.
397 */
398
399static void * /* O - Return status/value */
400sysEventThreadEntry(void)
401{
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[3], /* System Config keys */
410 pattern[1]; /* 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 */
417
418
419 /*
420 * Register for power state change notifications
421 */
422
423 bzero(&threadData, sizeof(threadData));
424
425 threadData.sysevent.powerKernelPort =
426 IORegisterForSystemPower(&threadData, &powerNotifierPort,
427 sysEventPowerNotifier, &powerNotifierObj);
428
429 if (threadData.sysevent.powerKernelPort)
430 {
431 powerRLS = IONotificationPortGetRunLoopSource(powerNotifierPort);
432 CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
433 }
434 else
435 DEBUG_puts("sysEventThreadEntry: error registering for system power "
436 "notifications");
437
438 /*
439 * Register for system configuration change notifications
440 */
441
442 bzero(&storeContext, sizeof(storeContext));
443 storeContext.info = &threadData;
444
445 store = SCDynamicStoreCreate(NULL, CFSTR("cupsd"),
446 sysEventConfigurationNotifier, &storeContext);
447
448 if (!ComputerNameKey)
449 ComputerNameKey = SCDynamicStoreKeyCreateComputerName(NULL);
450
451 if (!NetworkGlobalKey)
452 NetworkGlobalKey =
453 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
454 kSCDynamicStoreDomainState,
455 kSCEntNetIPv4);
456
457 if (!HostNamesKey)
458 HostNamesKey = SCDynamicStoreKeyCreateHostNames(NULL);
459
460 if (!NetworkInterfaceKey)
461 NetworkInterfaceKey =
462 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
463 kSCDynamicStoreDomainState,
464 kSCCompAnyRegex,
465 kSCEntNetIPv4);
466
467 if (store && ComputerNameKey && NetworkGlobalKey && HostNamesKey &&
468 NetworkInterfaceKey)
469 {
470 key[0] = ComputerNameKey;
471 key[1] = NetworkGlobalKey;
472 key[2] = HostNamesKey;
473 pattern[0] = NetworkInterfaceKey;
474
475 keys = CFArrayCreate(NULL, (const void **)key,
476 sizeof(key) / sizeof(key[0]),
477 &kCFTypeArrayCallBacks);
478 patterns = CFArrayCreate(NULL, (const void **)pattern,
479 sizeof(pattern) / sizeof(pattern[0]),
480 &kCFTypeArrayCallBacks);
481
482 if (keys && patterns &&
483 SCDynamicStoreSetNotificationKeys(store, keys, patterns))
484 {
485 if ((storeRLS = SCDynamicStoreCreateRunLoopSource(NULL, store, 0))
486 != NULL)
487 {
488 CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS,
489 kCFRunLoopDefaultMode);
490 }
491 else
492 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreateRunLoopSource "
493 "failed: %s\n", SCErrorString(SCError())));
494 }
495 else
496 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreSetNotificationKeys "
497 "failed: %s\n", SCErrorString(SCError())));
498 }
499 else
500 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreate failed: %s\n",
501 SCErrorString(SCError())));
502
503 if (keys)
504 CFRelease(keys);
505
506 if (patterns)
507 CFRelease(patterns);
508
509 /*
510 * Set up a timer to delay the wake change notifications.
511 *
512 * The initial time is set a decade or so into the future, we'll adjust
513 * this later.
514 */
515
516 bzero(&timerContext, sizeof(timerContext));
517 timerContext.info = &threadData;
518
519 threadData.timerRef =
520 CFRunLoopTimerCreate(NULL,
521 CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
522 86400L * 365L * 10L, 0, 0, sysEventTimerNotifier,
523 &timerContext);
524 CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData.timerRef,
525 kCFRunLoopDefaultMode);
526
527 /*
528 * Store our runloop in a global so the main thread can use it to stop us.
529 */
530
531 pthread_mutex_lock(&SysEventThreadMutex);
532
533 SysEventRunloop = CFRunLoopGetCurrent();
534
535 pthread_cond_signal(&SysEventThreadCond);
536 pthread_mutex_unlock(&SysEventThreadMutex);
537
538 /*
539 * Disappear into the runloop until it's stopped by the main thread.
540 */
541
542 CFRunLoopRun();
543
544 /*
545 * Clean up before exiting.
546 */
547
548 if (threadData.timerRef)
549 {
550 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData.timerRef,
551 kCFRunLoopDefaultMode);
552 CFRelease(threadData.timerRef);
553 }
554
555 if (threadData.sysevent.powerKernelPort)
556 {
557 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS,
558 kCFRunLoopDefaultMode);
559 IODeregisterForSystemPower(&powerNotifierObj);
560 IOServiceClose(threadData.sysevent.powerKernelPort);
561 IONotificationPortDestroy(powerNotifierPort);
562 }
563
564 if (storeRLS)
565 {
566 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS,
567 kCFRunLoopDefaultMode);
568 CFRunLoopSourceInvalidate(storeRLS);
569 CFRelease(storeRLS);
570 }
571
572 if (store)
573 CFRelease(store);
574
575 pthread_exit(NULL);
576}
577
578
579/*
580 * 'sysEventPowerNotifier()' - Handle power notification events.
581 */
582
583static void
584sysEventPowerNotifier(
585 void *context, /* I - Thread context data */
586 io_service_t service, /* I - Unused service info */
587 natural_t messageType, /* I - Type of message */
588 void *messageArgument) /* I - Message data */
589{
590 int sendit = 1; /* Send event to main thread? *
591 * (0 = no, 1 = yes, 2 = delayed */
592 cupsd_thread_data_t *threadData; /* Thread context data */
593
594
595 threadData = (cupsd_thread_data_t *)context;
596
597 (void)service; /* anti-compiler-warning-code */
598
599 switch (messageType)
600 {
601 case kIOMessageCanSystemPowerOff:
602 case kIOMessageCanSystemSleep:
603 threadData->sysevent.event |= SYSEVENT_CANSLEEP;
604 break;
605
606 case kIOMessageSystemWillRestart:
607 case kIOMessageSystemWillPowerOff:
608 case kIOMessageSystemWillSleep:
609 threadData->sysevent.event |= SYSEVENT_WILLSLEEP;
610 break;
611
612 case kIOMessageSystemHasPoweredOn:
613 /*
614 * Because powered on is followed by a net-changed event, delay
615 * before sending it.
616 */
617
618 sendit = 2;
619 threadData->sysevent.event |= SYSEVENT_WOKE;
620 break;
621
622 case kIOMessageSystemWillNotPowerOff:
623 case kIOMessageSystemWillNotSleep:
4400e98d 624#ifdef kIOMessageSystemWillPowerOn
09ec0018 625 case kIOMessageSystemWillPowerOn:
4400e98d 626#endif /* kIOMessageSystemWillPowerOn */
09ec0018 627 default:
628 sendit = 0;
629 break;
630 }
631
632 if (sendit == 0)
633 IOAllowPowerChange(threadData->sysevent.powerKernelPort,
634 (long)messageArgument);
635 else
636 {
637 threadData->sysevent.powerNotificationID = (long)messageArgument;
638
639 if (sendit == 1)
640 {
641 /*
642 * Send the event to the main thread now.
643 */
644
645 write(SysEventPipes[1], &threadData->sysevent,
646 sizeof(threadData->sysevent));
647 threadData->sysevent.event = 0;
648 }
649 else
650 {
651 /*
652 * Send the event to the main thread after 1 to 2 seconds.
653 */
654
655 CFRunLoopTimerSetNextFireDate(threadData->timerRef,
656 CFAbsoluteTimeGetCurrent() + 2);
657 }
658 }
659}
660
661
662/*
663 * 'sysEventConfigurationNotifier()' - Computer name changed notification
664 * callback.
665 */
666
667static void
668sysEventConfigurationNotifier(
669 SCDynamicStoreRef store, /* I - System data (unused) */
670 CFArrayRef changedKeys, /* I - Changed data */
671 void *context) /* I - Thread context data */
672{
673 cupsd_thread_data_t *threadData; /* Thread context data */
674
675
676 threadData = (cupsd_thread_data_t *)context;
677
678 (void)store; /* anti-compiler-warning-code */
679
680 CFRange range = CFRangeMake(0, CFArrayGetCount(changedKeys));
681
682 if (CFArrayContainsValue(changedKeys, range, ComputerNameKey))
683 threadData->sysevent.event |= SYSEVENT_NAMECHANGED;
684
685 if (CFArrayContainsValue(changedKeys, range, NetworkGlobalKey) ||
686 CFArrayContainsValue(changedKeys, range, HostNamesKey) ||
687 CFArrayContainsValue(changedKeys, range, NetworkInterfaceKey))
688 threadData->sysevent.event |= SYSEVENT_NETCHANGED;
689
690 /*
691 * Because we registered for several different kinds of change notifications
692 * this callback usually gets called several times in a row. We use a timer to
693 * de-bounce these so we only end up generating one event for the main thread.
694 */
695
696 CFRunLoopTimerSetNextFireDate(threadData->timerRef,
697 CFAbsoluteTimeGetCurrent() + 2);
698}
699
700
701/*
702 * 'sysEventTimerNotifier()' - Handle delayed event notifications.
703 */
704
705static void
706sysEventTimerNotifier(
707 CFRunLoopTimerRef timer, /* I - Timer information */
708 void *context) /* I - Thread context data */
709{
710 cupsd_thread_data_t *threadData; /* Thread context data */
711
712
713 threadData = (cupsd_thread_data_t *)context;
714
715 /*
716 * If an event is still pending send it to the main thread.
717 */
718
719 if (threadData->sysevent.event)
720 {
721 write(SysEventPipes[1], &threadData->sysevent,
722 sizeof(threadData->sysevent));
723 threadData->sysevent.event = 0;
724 }
725}
a4d04587 726#endif /* __APPLE__ */
09ec0018 727
728
729/*
f7faf1f5 730 * End of "$Id: sysman.c 5305 2006-03-18 03:05:12Z mike $".
09ec0018 731 */