1/*
2 * "$Id: sysman.c 11105 2013-07-08 12:28:32Z msweet $"
3 *
4 *   System management functions for the CUPS scheduler.
5 *
6 *   Copyright 2007-2013 by Apple Inc.
7 *   Copyright 2006 by Easy Software Products.
8 *
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/".
14 *
15 * Contents:
16 *
17 *   cupsdCleanDirty()               - Write dirty config and state files.
18 *   cupsdMarkDirty()                - Mark config or state files as needing a
19 *                                     write.
20 *   cupsdSetBusyState()             - Let the system know when we are busy
21 *                                     doing something.
22 *   cupsdAllowSleep()               - Tell the OS it is now OK to sleep.
23 *   cupsdStartSystemMonitor()       - Start monitoring for system change.
24 *   cupsdStopSystemMonitor()        - Stop monitoring for system change.
25 *   sysEventThreadEntry()           - A thread to receive power and computer
26 *                                     name change notifications.
27 *   sysEventPowerNotifier()         - Handle power notification events.
28 *   sysEventConfigurationNotifier() - Computer name changed notification
29 *                                     callback.
30 *   sysEventTimerNotifier()         - Handle delayed event notifications.
31 *   sysUpdate()                     - Update the current system state.
32 */
33
34
35/*
36 * Include necessary headers...
37 */
38
39#include "cupsd.h"
40#ifdef HAVE_VPROC_TRANSACTION_BEGIN
41#  include <vproc.h>
42#endif /* HAVE_VPROC_TRANSACTION_BEGIN */
43#ifdef __APPLE__
44#  include <IOKit/pwr_mgt/IOPMLib.h>
45#  ifdef HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H
46#    include <IOKit/pwr_mgt/IOPMLibPrivate.h>
47#  else
48#    define kIOPMAssertionTypeDenySystemSleep CFSTR("DenySystemSleep")
49#  endif /* HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H */
50#endif /* __APPLE__ */
51
52
53/*
54 * The system management functions cover disk and power management which
55 * are primarily used on portable computers.
56 *
57 * Disk management involves delaying the write of certain configuration
58 * and state files to minimize the number of times the disk has to spin
59 * up.
60 *
61 * Power management support is currently only implemented on MacOS X, but
62 * essentially we use four functions to let the OS know when it is OK to
63 * put the system to sleep, typically when we are not in the middle of
64 * printing a job.
65 *
66 * Once put to sleep, we invalidate all remote printers since it is common
67 * to wake up in a new location/on a new wireless network.
68 */
69
70/*
71 * Local globals...
72 */
73
74#if defined(kIOPMAssertionTypeDenySystemSleep) || defined(kIOPMAssertNetworkClientActive)
75static IOPMAssertionID	keep_awake = 0;	/* Keep the system awake while printing */
76#endif /* kIOPMAssertionTypeDenySystemSleep || kIOPMAssertNetworkClientActive */
77
78
79/*
80 * 'cupsdCleanDirty()' - Write dirty config and state files.
81 */
82
83void
84cupsdCleanDirty(void)
85{
86  if (DirtyFiles & CUPSD_DIRTY_PRINTERS)
87    cupsdSaveAllPrinters();
88
89  if (DirtyFiles & CUPSD_DIRTY_CLASSES)
90    cupsdSaveAllClasses();
91
92  if (DirtyFiles & CUPSD_DIRTY_PRINTCAP)
93    cupsdWritePrintcap();
94
95  if (DirtyFiles & CUPSD_DIRTY_JOBS)
96  {
97    cupsd_job_t	*job;			/* Current job */
98
99    cupsdSaveAllJobs();
100
101    for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
102         job;
103	 job = (cupsd_job_t *)cupsArrayNext(Jobs))
104      if (job->dirty)
105        cupsdSaveJob(job);
106  }
107
108  if (DirtyFiles & CUPSD_DIRTY_SUBSCRIPTIONS)
109    cupsdSaveAllSubscriptions();
110
111  DirtyFiles     = CUPSD_DIRTY_NONE;
112  DirtyCleanTime = 0;
113
114  cupsdSetBusyState();
115}
116
117
118/*
119 * 'cupsdMarkDirty()' - Mark config or state files as needing a write.
120 */
121
122void
123cupsdMarkDirty(int what)		/* I - What file(s) are dirty? */
124{
125  cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdMarkDirty(%c%c%c%c%c)",
126		  (what & CUPSD_DIRTY_PRINTERS) ? 'P' : '-',
127		  (what & CUPSD_DIRTY_CLASSES) ? 'C' : '-',
128		  (what & CUPSD_DIRTY_PRINTCAP) ? 'p' : '-',
129		  (what & CUPSD_DIRTY_JOBS) ? 'J' : '-',
130		  (what & CUPSD_DIRTY_SUBSCRIPTIONS) ? 'S' : '-');
131
132  if (what == CUPSD_DIRTY_PRINTCAP && !Printcap)
133    return;
134
135  DirtyFiles |= what;
136
137  if (!DirtyCleanTime)
138    DirtyCleanTime = time(NULL) + DirtyCleanInterval;
139
140  cupsdSetBusyState();
141}
142
143
144/*
145 * 'cupsdSetBusyState()' - Let the system know when we are busy doing something.
146 */
147
148void
149cupsdSetBusyState(void)
150{
151  int			i;		/* Looping var */
152  cupsd_job_t		*job;		/* Current job */
153  cupsd_printer_t	*p;		/* Current printer */
154  int			newbusy;	/* New busy state */
155  static int		busy = 0;	/* Current busy state */
156  static const char * const busy_text[] =
157  {					/* Text for busy states */
158    "Not busy",
159    "Dirty files",
160    "Printing jobs",
161    "Printing jobs and dirty files",
162    "Active clients",
163    "Active clients and dirty files",
164    "Active clients and printing jobs",
165    "Active clients, printing jobs, and dirty files"
166  };
167#ifdef HAVE_VPROC_TRANSACTION_BEGIN
168  static vproc_transaction_t vtran = 0;	/* Current busy transaction */
169#endif /* HAVE_VPROC_TRANSACTION_BEGIN */
170
171
172 /*
173  * Figure out how busy we are...
174  */
175
176  newbusy = (DirtyCleanTime ? 1 : 0) |
177	    (cupsArrayCount(ActiveClients) ? 4 : 0);
178
179  for (job = (cupsd_job_t *)cupsArrayFirst(PrintingJobs);
180       job;
181       job = (cupsd_job_t *)cupsArrayNext(PrintingJobs))
182  {
183    if ((p = job->printer) != NULL)
184    {
185      for (i = 0; i < p->num_reasons; i ++)
186	if (!strcmp(p->reasons[i], "connecting-to-device"))
187	  break;
188
189      if (!p->num_reasons || i >= p->num_reasons)
190	break;
191    }
192  }
193
194  if (job)
195    newbusy |= 2;
196
197  cupsdLogMessage(CUPSD_LOG_DEBUG,
198                  "cupsdSetBusyState: newbusy=\"%s\", busy=\"%s\"",
199                  busy_text[newbusy], busy_text[busy]);
200
201 /*
202  * Manage state changes...
203  */
204
205  if (newbusy != busy)
206  {
207    busy = newbusy;
208
209#ifdef HAVE_VPROC_TRANSACTION_BEGIN
210    if (busy && !vtran)
211      vtran = vproc_transaction_begin(NULL);
212    else if (!busy && vtran)
213    {
214      vproc_transaction_end(NULL, vtran);
215      vtran = 0;
216    }
217#endif /* HAVE_VPROC_TRANSACTION_BEGIN */
218  }
219
220#if defined(kIOPMAssertionTypeDenySystemSleep) || defined(kIOPMAssertNetworkClientActive)
221  if (cupsArrayCount(PrintingJobs) > 0 && !keep_awake)
222  {
223#  ifdef kIOPMAssertNetworkClientActive
224    cupsdLogMessage(CUPSD_LOG_DEBUG, "Asserting NetworkClientActive.");
225
226    IOPMAssertionCreateWithName(kIOPMAssertNetworkClientActive,
227				kIOPMAssertionLevelOn,
228				CFSTR("org.cups.cupsd"), &keep_awake);
229
230#  else
231    cupsdLogMessage(CUPSD_LOG_DEBUG, "Asserting DenySystemSleep.");
232
233    IOPMAssertionCreateWithName(kIOPMAssertionTypeDenySystemSleep,
234				kIOPMAssertionLevelOn,
235				CFSTR("org.cups.cupsd"), &keep_awake);
236
237#  endif /* kIOPMAssertNetworkClientActive */
238  }
239  else if (cupsArrayCount(PrintingJobs) == 0 && keep_awake)
240  {
241    cupsdLogMessage(CUPSD_LOG_DEBUG, "Releasing power assertion.");
242    IOPMAssertionRelease(keep_awake);
243    keep_awake = 0;
244  }
245#endif /* kIOPMAssertionTypeDenySystemSleep || kIOPMAssertNetworkClientActive */
246}
247
248
249#ifdef __APPLE__
250/*
251 * This is the Apple-specific system event code.  It works by creating
252 * a worker thread that waits for events from the OS and relays them
253 * to the main thread via a traditional pipe.
254 */
255
256/*
257 * Include MacOS-specific headers...
258 */
259
260#  include <IOKit/IOKitLib.h>
261#  include <IOKit/IOMessage.h>
262#  include <IOKit/pwr_mgt/IOPMLib.h>
263#  include <SystemConfiguration/SystemConfiguration.h>
264#  include <pthread.h>
265
266
267/*
268 * Constants...
269 */
270
271#  define SYSEVENT_CANSLEEP	0x1	/* Decide whether to allow sleep or not */
272#  define SYSEVENT_WILLSLEEP	0x2	/* Computer will go to sleep */
273#  define SYSEVENT_WOKE		0x4	/* Computer woke from sleep */
274#  define SYSEVENT_NETCHANGED	0x8	/* Network changed */
275#  define SYSEVENT_NAMECHANGED	0x10	/* Computer name changed */
276
277
278/*
279 * Structures...
280 */
281
282typedef struct cupsd_sysevent_s		/*** System event data ****/
283{
284  unsigned char	event;			/* Event bit field */
285  io_connect_t	powerKernelPort;	/* Power context data */
286  long		powerNotificationID;	/* Power event data */
287} cupsd_sysevent_t;
288
289
290typedef struct cupsd_thread_data_s	/*** Thread context data  ****/
291{
292  cupsd_sysevent_t	sysevent;	/* System event */
293  CFRunLoopTimerRef	timerRef;	/* Timer to delay some change *
294					 * notifications              */
295} cupsd_thread_data_t;
296
297
298/*
299 * Local globals...
300 */
301
302static pthread_t	SysEventThread = NULL;
303					/* Thread to host a runloop */
304static pthread_mutex_t	SysEventThreadMutex = { 0 };
305					/* Coordinates access to shared gloabals */
306static pthread_cond_t	SysEventThreadCond = { 0 };
307					/* Thread initialization complete condition */
308static CFRunLoopRef	SysEventRunloop = NULL;
309					/* The runloop. Access must be protected! */
310static CFStringRef	ComputerNameKey = NULL,
311					/* Computer name key */
312			BTMMKey = NULL,	/* Back to My Mac key */
313			NetworkGlobalKeyIPv4 = NULL,
314					/* Network global IPv4 key */
315			NetworkGlobalKeyIPv6 = NULL,
316					/* Network global IPv6 key */
317			NetworkGlobalKeyDNS = NULL,
318					/* Network global DNS key */
319			HostNamesKey = NULL,
320					/* Host name key */
321			NetworkInterfaceKeyIPv4 = NULL,
322					/* Netowrk interface key */
323			NetworkInterfaceKeyIPv6 = NULL;
324					/* Netowrk interface key */
325static cupsd_sysevent_t	LastSysEvent;	/* Last system event (for delayed sleep) */
326
327
328/*
329 * Local functions...
330 */
331
332static void	*sysEventThreadEntry(void);
333static void	sysEventPowerNotifier(void *context, io_service_t service,
334		                      natural_t messageType,
335				      void *messageArgument);
336static void	sysEventConfigurationNotifier(SCDynamicStoreRef store,
337		                              CFArrayRef changedKeys,
338					      void *context);
339static void	sysEventTimerNotifier(CFRunLoopTimerRef timer, void *context);
340static void	sysUpdate(void);
341
342
343/*
344 * 'cupsdAllowSleep()' - Tell the OS it is now OK to sleep.
345 */
346
347void
348cupsdAllowSleep(void)
349{
350  cupsdCleanDirty();
351
352  IOAllowPowerChange(LastSysEvent.powerKernelPort,
353		     LastSysEvent.powerNotificationID);
354}
355
356
357/*
358 * 'cupsdStartSystemMonitor()' - Start monitoring for system change.
359 */
360
361void
362cupsdStartSystemMonitor(void)
363{
364  int	flags;				/* fcntl flags on pipe */
365
366
367  if (cupsdOpenPipe(SysEventPipes))
368  {
369    cupsdLogMessage(CUPSD_LOG_ERROR, "System event monitor pipe() failed - %s!",
370                    strerror(errno));
371    return;
372  }
373
374  cupsdAddSelect(SysEventPipes[0], (cupsd_selfunc_t)sysUpdate, NULL, NULL);
375
376 /*
377  * Set non-blocking mode on the descriptor we will be receiving notification
378  * events on.
379  */
380
381  flags = fcntl(SysEventPipes[0], F_GETFL, 0);
382  fcntl(SysEventPipes[0], F_SETFL, flags | O_NONBLOCK);
383
384 /*
385  * Start the thread that runs the runloop...
386  */
387
388  pthread_mutex_init(&SysEventThreadMutex, NULL);
389  pthread_cond_init(&SysEventThreadCond, NULL);
390  pthread_create(&SysEventThread, NULL, (void *(*)())sysEventThreadEntry, NULL);
391}
392
393
394/*
395 * 'cupsdStopSystemMonitor()' - Stop monitoring for system change.
396 */
397
398void
399cupsdStopSystemMonitor(void)
400{
401  CFRunLoopRef	rl;			/* The event handler runloop */
402
403
404  if (SysEventThread)
405  {
406   /*
407    * Make sure the thread has completed it's initialization and
408    * stored it's runloop reference in the shared global.
409    */
410
411    pthread_mutex_lock(&SysEventThreadMutex);
412
413    if (!SysEventRunloop)
414      pthread_cond_wait(&SysEventThreadCond, &SysEventThreadMutex);
415
416    rl              = SysEventRunloop;
417    SysEventRunloop = NULL;
418
419    pthread_mutex_unlock(&SysEventThreadMutex);
420
421    if (rl)
422      CFRunLoopStop(rl);
423
424    pthread_join(SysEventThread, NULL);
425    pthread_mutex_destroy(&SysEventThreadMutex);
426    pthread_cond_destroy(&SysEventThreadCond);
427  }
428
429  if (SysEventPipes[0] >= 0)
430  {
431    cupsdRemoveSelect(SysEventPipes[0]);
432    cupsdClosePipe(SysEventPipes);
433  }
434}
435
436
437/*
438 * 'sysEventThreadEntry()' - A thread to receive power and computer name
439 *                           change notifications.
440 */
441
442static void *				/* O - Return status/value */
443sysEventThreadEntry(void)
444{
445  io_object_t		powerNotifierObj;
446					/* Power notifier object */
447  IONotificationPortRef powerNotifierPort;
448					/* Power notifier port */
449  SCDynamicStoreRef	store    = NULL;/* System Config dynamic store */
450  CFRunLoopSourceRef	powerRLS = NULL,/* Power runloop source */
451			storeRLS = NULL;/* System Config runloop source */
452  CFStringRef		key[6],		/* System Config keys */
453			pattern[2];	/* System Config patterns */
454  CFArrayRef		keys = NULL,	/* System Config key array*/
455			patterns = NULL;/* System Config pattern array */
456  SCDynamicStoreContext	storeContext;	/* Dynamic store context */
457  CFRunLoopTimerContext timerContext;	/* Timer context */
458  cupsd_thread_data_t	threadData;	/* Thread context data for the *
459					 * runloop notifiers           */
460
461
462 /*
463  * Register for power state change notifications
464  */
465
466  bzero(&threadData, sizeof(threadData));
467
468  threadData.sysevent.powerKernelPort =
469      IORegisterForSystemPower(&threadData, &powerNotifierPort,
470                               sysEventPowerNotifier, &powerNotifierObj);
471
472  if (threadData.sysevent.powerKernelPort)
473  {
474    powerRLS = IONotificationPortGetRunLoopSource(powerNotifierPort);
475    CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
476  }
477  else
478    DEBUG_puts("sysEventThreadEntry: error registering for system power "
479               "notifications");
480
481 /*
482  * Register for system configuration change notifications
483  */
484
485  bzero(&storeContext, sizeof(storeContext));
486  storeContext.info = &threadData;
487
488  store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("cupsd"),
489                               sysEventConfigurationNotifier, &storeContext);
490
491  if (!ComputerNameKey)
492    ComputerNameKey = SCDynamicStoreKeyCreateComputerName(kCFAllocatorDefault);
493
494  if (!BTMMKey)
495    BTMMKey = SCDynamicStoreKeyCreate(kCFAllocatorDefault,
496                                      CFSTR("Setup:/Network/BackToMyMac"));
497
498  if (!NetworkGlobalKeyIPv4)
499    NetworkGlobalKeyIPv4 =
500        SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault,
501                                                   kSCDynamicStoreDomainState,
502						   kSCEntNetIPv4);
503
504  if (!NetworkGlobalKeyIPv6)
505    NetworkGlobalKeyIPv6 =
506        SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault,
507                                                   kSCDynamicStoreDomainState,
508						   kSCEntNetIPv6);
509
510  if (!NetworkGlobalKeyDNS)
511    NetworkGlobalKeyDNS =
512	SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault,
513						   kSCDynamicStoreDomainState,
514						   kSCEntNetDNS);
515
516  if (!HostNamesKey)
517    HostNamesKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault);
518
519  if (!NetworkInterfaceKeyIPv4)
520    NetworkInterfaceKeyIPv4 =
521        SCDynamicStoreKeyCreateNetworkInterfaceEntity(kCFAllocatorDefault,
522	                                              kSCDynamicStoreDomainState,
523						      kSCCompAnyRegex,
524						      kSCEntNetIPv4);
525
526  if (!NetworkInterfaceKeyIPv6)
527    NetworkInterfaceKeyIPv6 =
528        SCDynamicStoreKeyCreateNetworkInterfaceEntity(kCFAllocatorDefault,
529	                                              kSCDynamicStoreDomainState,
530						      kSCCompAnyRegex,
531						      kSCEntNetIPv6);
532
533  if (store && ComputerNameKey && HostNamesKey &&
534      NetworkGlobalKeyIPv4 && NetworkGlobalKeyIPv6 && NetworkGlobalKeyDNS &&
535      NetworkInterfaceKeyIPv4 && NetworkInterfaceKeyIPv6)
536  {
537    key[0]     = ComputerNameKey;
538    key[1]     = BTMMKey;
539    key[2]     = NetworkGlobalKeyIPv4;
540    key[3]     = NetworkGlobalKeyIPv6;
541    key[4]     = NetworkGlobalKeyDNS;
542    key[5]     = HostNamesKey;
543
544    pattern[0] = NetworkInterfaceKeyIPv4;
545    pattern[1] = NetworkInterfaceKeyIPv6;
546
547    keys     = CFArrayCreate(kCFAllocatorDefault, (const void **)key,
548			     sizeof(key) / sizeof(key[0]),
549			     &kCFTypeArrayCallBacks);
550
551    patterns = CFArrayCreate(kCFAllocatorDefault, (const void **)pattern,
552                             sizeof(pattern) / sizeof(pattern[0]),
553			     &kCFTypeArrayCallBacks);
554
555    if (keys && patterns &&
556        SCDynamicStoreSetNotificationKeys(store, keys, patterns))
557    {
558      if ((storeRLS = SCDynamicStoreCreateRunLoopSource(kCFAllocatorDefault,
559                                                        store, 0)) != NULL)
560      {
561	CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS,
562	                   kCFRunLoopDefaultMode);
563      }
564      else
565	DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreateRunLoopSource "
566	              "failed: %s\n", SCErrorString(SCError())));
567    }
568    else
569      DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreSetNotificationKeys "
570                    "failed: %s\n", SCErrorString(SCError())));
571  }
572  else
573    DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreate failed: %s\n",
574                  SCErrorString(SCError())));
575
576  if (keys)
577    CFRelease(keys);
578
579  if (patterns)
580    CFRelease(patterns);
581
582 /*
583  * Set up a timer to delay the wake change notifications.
584  *
585  * The initial time is set a decade or so into the future, we'll adjust
586  * this later.
587  */
588
589  bzero(&timerContext, sizeof(timerContext));
590  timerContext.info = &threadData;
591
592  threadData.timerRef =
593      CFRunLoopTimerCreate(kCFAllocatorDefault,
594                           CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
595			   86400L * 365L * 10L, 0, 0, sysEventTimerNotifier,
596			   &timerContext);
597  CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData.timerRef,
598                    kCFRunLoopDefaultMode);
599
600 /*
601  * Store our runloop in a global so the main thread can use it to stop us.
602  */
603
604  pthread_mutex_lock(&SysEventThreadMutex);
605
606  SysEventRunloop = CFRunLoopGetCurrent();
607
608  pthread_cond_signal(&SysEventThreadCond);
609  pthread_mutex_unlock(&SysEventThreadMutex);
610
611 /*
612  * Disappear into the runloop until it's stopped by the main thread.
613  */
614
615  CFRunLoopRun();
616
617 /*
618  * Clean up before exiting.
619  */
620
621  if (threadData.timerRef)
622  {
623    CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData.timerRef,
624                         kCFRunLoopDefaultMode);
625    CFRelease(threadData.timerRef);
626  }
627
628  if (threadData.sysevent.powerKernelPort)
629  {
630    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS,
631                          kCFRunLoopDefaultMode);
632    IODeregisterForSystemPower(&powerNotifierObj);
633    IOServiceClose(threadData.sysevent.powerKernelPort);
634    IONotificationPortDestroy(powerNotifierPort);
635  }
636
637  if (storeRLS)
638  {
639    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS,
640                          kCFRunLoopDefaultMode);
641    CFRunLoopSourceInvalidate(storeRLS);
642    CFRelease(storeRLS);
643  }
644
645  if (store)
646    CFRelease(store);
647
648  pthread_exit(NULL);
649}
650
651
652/*
653 * 'sysEventPowerNotifier()' - Handle power notification events.
654 */
655
656static void
657sysEventPowerNotifier(
658    void         *context,		/* I - Thread context data */
659    io_service_t service,		/* I - Unused service info */
660    natural_t    messageType,		/* I - Type of message */
661    void         *messageArgument)	/* I - Message data */
662{
663  int			sendit = 1;	/* Send event to main thread?    *
664					 * (0 = no, 1 = yes, 2 = delayed */
665  cupsd_thread_data_t	*threadData;	/* Thread context data */
666
667
668  threadData = (cupsd_thread_data_t *)context;
669
670  (void)service;			/* anti-compiler-warning-code */
671
672  switch (messageType)
673  {
674    case kIOMessageCanSystemPowerOff:
675    case kIOMessageCanSystemSleep:
676	threadData->sysevent.event |= SYSEVENT_CANSLEEP;
677	break;
678
679    case kIOMessageSystemWillRestart:
680    case kIOMessageSystemWillPowerOff:
681    case kIOMessageSystemWillSleep:
682	threadData->sysevent.event |= SYSEVENT_WILLSLEEP;
683	break;
684
685    case kIOMessageSystemHasPoweredOn:
686       /*
687	* Because powered on is followed by a net-changed event, delay
688	* before sending it.
689	*/
690
691        sendit = 2;
692	threadData->sysevent.event |= SYSEVENT_WOKE;
693	break;
694
695    case kIOMessageSystemWillNotPowerOff:
696    case kIOMessageSystemWillNotSleep:
697#  ifdef kIOMessageSystemWillPowerOn
698    case kIOMessageSystemWillPowerOn:
699#  endif /* kIOMessageSystemWillPowerOn */
700    default:
701	sendit = 0;
702	break;
703  }
704
705  if (sendit == 0)
706    IOAllowPowerChange(threadData->sysevent.powerKernelPort,
707                       (long)messageArgument);
708  else
709  {
710    threadData->sysevent.powerNotificationID = (long)messageArgument;
711
712    if (sendit == 1)
713    {
714     /*
715      * Send the event to the main thread now.
716      */
717
718      write(SysEventPipes[1], &threadData->sysevent,
719	    sizeof(threadData->sysevent));
720      threadData->sysevent.event = 0;
721    }
722    else
723    {
724     /*
725      * Send the event to the main thread after 1 to 2 seconds.
726      */
727
728      CFRunLoopTimerSetNextFireDate(threadData->timerRef,
729                                    CFAbsoluteTimeGetCurrent() + 2);
730    }
731  }
732}
733
734
735/*
736 * 'sysEventConfigurationNotifier()' - Network configuration change notification
737 *                                     callback.
738 */
739
740static void
741sysEventConfigurationNotifier(
742    SCDynamicStoreRef store,		/* I - System data (unused) */
743    CFArrayRef        changedKeys,	/* I - Changed data */
744    void              *context)		/* I - Thread context data */
745{
746  cupsd_thread_data_t	*threadData;	/* Thread context data */
747
748
749  threadData = (cupsd_thread_data_t *)context;
750
751  (void)store;				/* anti-compiler-warning-code */
752
753  CFRange range = CFRangeMake(0, CFArrayGetCount(changedKeys));
754
755  if (CFArrayContainsValue(changedKeys, range, ComputerNameKey) ||
756      CFArrayContainsValue(changedKeys, range, BTMMKey))
757    threadData->sysevent.event |= SYSEVENT_NAMECHANGED;
758  else
759  {
760    threadData->sysevent.event |= SYSEVENT_NETCHANGED;
761
762   /*
763    * Indicate the network interface list needs updating...
764    */
765
766    NetIFUpdate = 1;
767  }
768
769 /*
770  * Because we registered for several different kinds of change notifications
771  * this callback usually gets called several times in a row. We use a timer to
772  * de-bounce these so we only end up generating one event for the main thread.
773  */
774
775  CFRunLoopTimerSetNextFireDate(threadData->timerRef,
776  				CFAbsoluteTimeGetCurrent() + 5);
777}
778
779
780/*
781 * 'sysEventTimerNotifier()' - Handle delayed event notifications.
782 */
783
784static void
785sysEventTimerNotifier(
786    CFRunLoopTimerRef timer,		/* I - Timer information */
787    void              *context)		/* I - Thread context data */
788{
789  cupsd_thread_data_t	*threadData;	/* Thread context data */
790
791
792  (void)timer;
793
794  threadData = (cupsd_thread_data_t *)context;
795
796 /*
797  * If an event is still pending send it to the main thread.
798  */
799
800  if (threadData->sysevent.event)
801  {
802    write(SysEventPipes[1], &threadData->sysevent,
803          sizeof(threadData->sysevent));
804    threadData->sysevent.event = 0;
805  }
806}
807
808
809/*
810 * 'sysUpdate()' - Update the current system state.
811 */
812
813static void
814sysUpdate(void)
815{
816  int			i;		/* Looping var */
817  cupsd_sysevent_t	sysevent;	/* The system event */
818  cupsd_printer_t	*p;		/* Printer information */
819
820
821 /*
822  * Drain the event pipe...
823  */
824
825  while (read((int)SysEventPipes[0], &sysevent, sizeof(sysevent))
826             == sizeof(sysevent))
827  {
828    if (sysevent.event & SYSEVENT_CANSLEEP)
829    {
830     /*
831      * If there are active printers that don't have the connecting-to-device
832      * printer-state-reason then cancel the sleep request (i.e. this reason
833      * indicates a job that is not yet connected to the printer)...
834      */
835
836      for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
837           p;
838	   p = (cupsd_printer_t *)cupsArrayNext(Printers))
839      {
840        if (p->job)
841        {
842	  for (i = 0; i < p->num_reasons; i ++)
843	    if (!strcmp(p->reasons[i], "connecting-to-device"))
844	      break;
845
846	  if (!p->num_reasons || i >= p->num_reasons)
847	    break;
848        }
849      }
850
851      if (p)
852      {
853        cupsdLogMessage(CUPSD_LOG_INFO,
854	                "System sleep canceled because printer %s is active",
855	                p->name);
856        IOCancelPowerChange(sysevent.powerKernelPort,
857	                    sysevent.powerNotificationID);
858      }
859      else
860      {
861	cupsdLogMessage(CUPSD_LOG_DEBUG, "System wants to sleep");
862        IOAllowPowerChange(sysevent.powerKernelPort,
863	                   sysevent.powerNotificationID);
864      }
865    }
866
867    if (sysevent.event & SYSEVENT_WILLSLEEP)
868    {
869      cupsdLogMessage(CUPSD_LOG_DEBUG, "System going to sleep");
870
871      Sleeping = 1;
872
873      for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
874           p;
875	   p = (cupsd_printer_t *)cupsArrayNext(Printers))
876      {
877	cupsdLogMessage(CUPSD_LOG_DEBUG,
878			"Deregistering local printer \"%s\"", p->name);
879	cupsdDeregisterPrinter(p, 0);
880      }
881
882      cupsdCleanDirty();
883
884#ifdef kIOPMAssertionTypeDenySystemSleep
885     /*
886      * Remove our assertion as needed since the user wants the system to
887      * sleep (different than idle sleep)...
888      */
889
890      if (keep_awake)
891      {
892	cupsdLogMessage(CUPSD_LOG_DEBUG, "Releasing dark wake assertion.");
893	IOPMAssertionRelease(keep_awake);
894	keep_awake = 0;
895      }
896#endif /* kIOPMAssertionTypeDenySystemSleep */
897
898     /*
899      * If we have no printing jobs, allow the power change immediately.
900      * Otherwise set the SleepJobs time to 15 seconds in the future when
901      * we'll take more drastic measures...
902      */
903
904      if (cupsArrayCount(PrintingJobs) == 0)
905	IOAllowPowerChange(sysevent.powerKernelPort,
906			   sysevent.powerNotificationID);
907      else
908      {
909       /*
910	* If there are active printers that don't have the connecting-to-device
911	* printer-state-reason then delay the sleep request (i.e. this reason
912	* indicates a job that is not yet connected to the printer)...
913	*/
914
915	for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
916	     p;
917	     p = (cupsd_printer_t *)cupsArrayNext(Printers))
918	{
919	  if (p->job)
920	  {
921	    for (i = 0; i < p->num_reasons; i ++)
922	      if (!strcmp(p->reasons[i], "connecting-to-device"))
923		break;
924
925	    if (!p->num_reasons || i >= p->num_reasons)
926	      break;
927	  }
928	}
929
930	if (p)
931	{
932	  LastSysEvent = sysevent;
933	  SleepJobs    = time(NULL) + 10;
934	}
935	else
936	{
937	  IOAllowPowerChange(sysevent.powerKernelPort,
938			     sysevent.powerNotificationID);
939	}
940      }
941    }
942
943    if (sysevent.event & SYSEVENT_WOKE)
944    {
945      cupsdLogMessage(CUPSD_LOG_DEBUG, "System woke from sleep");
946      IOAllowPowerChange(sysevent.powerKernelPort,
947                         sysevent.powerNotificationID);
948      Sleeping = 0;
949
950#ifdef kIOPMAssertionTypeDenySystemSleep
951      if (cupsArrayCount(PrintingJobs) > 0 && !keep_awake)
952      {
953	cupsdLogMessage(CUPSD_LOG_DEBUG, "Asserting dark wake.");
954	IOPMAssertionCreateWithName(kIOPMAssertionTypeDenySystemSleep,
955				    kIOPMAssertionLevelOn,
956				    CFSTR("org.cups.cupsd"), &keep_awake);
957      }
958#endif /* kIOPMAssertionTypeDenySystemSleep */
959
960      cupsdCheckJobs();
961    }
962
963    if (sysevent.event & SYSEVENT_NETCHANGED)
964    {
965      if (!Sleeping)
966        cupsdLogMessage(CUPSD_LOG_DEBUG,
967	                "System network configuration changed");
968      else
969        cupsdLogMessage(CUPSD_LOG_DEBUG,
970	                "System network configuration changed; "
971			"ignored while sleeping");
972    }
973
974    if (sysevent.event & SYSEVENT_NAMECHANGED)
975    {
976      if (!Sleeping)
977      {
978        cupsdLogMessage(CUPSD_LOG_DEBUG,
979	                "Computer name or BTMM domains changed");
980
981       /*
982	* De-register the individual printers...
983	*/
984
985	for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
986	     p;
987	     p = (cupsd_printer_t *)cupsArrayNext(Printers))
988	  cupsdDeregisterPrinter(p, 1);
989
990#  if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
991       /*
992        * Update the computer name and BTMM domain list...
993	*/
994
995	cupsdUpdateDNSSDName();
996#  endif /* HAVE_DNSSD || HAVE_AVAHI */
997
998       /*
999	* Now re-register them...
1000	*/
1001
1002	for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
1003	     p;
1004	     p = (cupsd_printer_t *)cupsArrayNext(Printers))
1005	  cupsdRegisterPrinter(p);
1006      }
1007      else
1008        cupsdLogMessage(CUPSD_LOG_DEBUG,
1009	                "Computer name or BTMM domains changed; ignored while "
1010			"sleeping");
1011    }
1012  }
1013}
1014#endif	/* __APPLE__ */
1015
1016
1017/*
1018 * End of "$Id: sysman.c 11105 2013-07-08 12:28:32Z msweet $".
1019 */
1020