]> 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/*
757d2cad 2 * "$Id: sysman.c 5241 2006-03-07 22:07:44Z 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{
757d2cad 267 int i; /* Looping var */
09ec0018 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 /*
757d2cad 282 * If there are active printers that don't have the connecting-to-device
283 * printer-state-reason then cancel the sleep request (i.e. this reason
284 * indicates a job that is not yet connected to the printer)...
09ec0018 285 */
286
287 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
288 p;
289 p = (cupsd_printer_t *)cupsArrayNext(Printers))
757d2cad 290 {
09ec0018 291 if (p->job)
757d2cad 292 {
293 for (i = 0; i < p->num_reasons; i ++)
294 if (!strcmp(p->reasons[i], "connecting-to-device"))
295 break;
296
297 if (!p->num_reasons || i >= p->num_reasons)
298 break;
299 }
300 }
09ec0018 301
302 if (p)
303 {
304 cupsdLogMessage(CUPSD_LOG_INFO,
305 "System sleep canceled because printer %s is active",
306 p->name);
307 IOCancelPowerChange(sysevent.powerKernelPort,
308 sysevent.powerNotificationID);
309 }
310 else
311 {
312 cupsdLogMessage(CUPSD_LOG_DEBUG, "System wants to sleep");
313 IOAllowPowerChange(sysevent.powerKernelPort,
314 sysevent.powerNotificationID);
315 }
316 }
317
318 if (sysevent.event & SYSEVENT_WILLSLEEP)
319 {
320 cupsdLogMessage(CUPSD_LOG_DEBUG, "System going to sleep");
321
322 Sleeping = 1;
323
324 cupsdStopAllJobs();
bd7854cb 325 cupsdSaveAllJobs();
09ec0018 326
327 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
328 p;
329 p = (cupsd_printer_t *)cupsArrayNext(Printers))
330 {
331 if (p->type & CUPS_PRINTER_REMOTE)
332 {
333 cupsdLogMessage(CUPSD_LOG_DEBUG,
334 "Deleting remote destination \"%s\"", p->name);
335 cupsArraySave(Printers);
336 cupsdDeletePrinter(p, 0);
337 cupsArrayRestore(Printers);
338 }
339 else
340 {
341 /* TODO: Possibly update when MDNS support is added? */
342 cupsdLogMessage(CUPSD_LOG_DEBUG,
343 "Deregistering local printer \"%s\"", p->name);
344 cupsdSendBrowseDelete(p);
345 }
346 }
347
348 IOAllowPowerChange(sysevent.powerKernelPort,
349 sysevent.powerNotificationID);
350 }
351
352 if (sysevent.event & SYSEVENT_WOKE)
353 {
354 cupsdLogMessage(CUPSD_LOG_DEBUG, "System woke from sleep");
355 IOAllowPowerChange(sysevent.powerKernelPort,
356 sysevent.powerNotificationID);
357 Sleeping = 0;
358 cupsdCheckJobs();
359 }
360
361 if (sysevent.event & SYSEVENT_NETCHANGED)
362 {
363 if (!Sleeping)
364 {
365 cupsdLogMessage(CUPSD_LOG_DEBUG,
366 "System network configuration changed");
367
368 /*
369 * Force an update of the list of network interfaces in 2 seconds.
370 */
371
372 NetIFTime = time(NULL) - 58;
373
374 /*
375 * Resetting browse_time before calling cupsdSendBrowseList causes
376 * browse packets to be sent for local shared printers.
377 */
378
379 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
380 p;
381 p = (cupsd_printer_t *)cupsArrayNext(Printers))
382 p->browse_time = 0;
383
384 cupsdSendBrowseList();
385 }
386 else
387 cupsdLogMessage(CUPSD_LOG_DEBUG,
388 "System network configuration changed; "
389 "ignored while sleeping");
390 }
391
392 if (sysevent.event & SYSEVENT_NAMECHANGED)
393 {
394 if (!Sleeping)
395 {
396 cupsdLogMessage(CUPSD_LOG_DEBUG, "Computer name changed");
397
398 /*
399 * De-register the individual printers...
400 */
401
402 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
403 p;
404 p = (cupsd_printer_t *)cupsArrayNext(Printers))
405 cupsdSendBrowseDelete(p);
406
407 /*
408 * Now re-register them...
409 *
410 * TODO: This might need updating for MDNS.
411 */
412
413 for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
414 p;
415 p = (cupsd_printer_t *)cupsArrayNext(Printers))
416 p->browse_time = 0;
417 }
418 else
419 cupsdLogMessage(CUPSD_LOG_DEBUG,
420 "Computer name changed; ignored while sleeping");
421 }
422 }
423}
424
425
426/*
427 * 'sysEventThreadEntry()' - A thread to receive power and computer name
428 * change notifications.
429 */
430
431static void * /* O - Return status/value */
432sysEventThreadEntry(void)
433{
434 io_object_t powerNotifierObj;
435 /* Power notifier object */
436 IONotificationPortRef powerNotifierPort;
437 /* Power notifier port */
438 SCDynamicStoreRef store = NULL;/* System Config dynamic store */
439 CFRunLoopSourceRef powerRLS = NULL,/* Power runloop source */
440 storeRLS = NULL;/* System Config runloop source */
441 CFStringRef key[3], /* System Config keys */
442 pattern[1]; /* System Config patterns */
443 CFArrayRef keys = NULL, /* System Config key array*/
444 patterns = NULL;/* System Config pattern array */
445 SCDynamicStoreContext storeContext; /* Dynamic store context */
446 CFRunLoopTimerContext timerContext; /* Timer context */
447 cupsd_thread_data_t threadData; /* Thread context data for the *
448 * runloop notifiers */
449
450
451 /*
452 * Register for power state change notifications
453 */
454
455 bzero(&threadData, sizeof(threadData));
456
457 threadData.sysevent.powerKernelPort =
458 IORegisterForSystemPower(&threadData, &powerNotifierPort,
459 sysEventPowerNotifier, &powerNotifierObj);
460
461 if (threadData.sysevent.powerKernelPort)
462 {
463 powerRLS = IONotificationPortGetRunLoopSource(powerNotifierPort);
464 CFRunLoopAddSource(CFRunLoopGetCurrent(), powerRLS, kCFRunLoopDefaultMode);
465 }
466 else
467 DEBUG_puts("sysEventThreadEntry: error registering for system power "
468 "notifications");
469
470 /*
471 * Register for system configuration change notifications
472 */
473
474 bzero(&storeContext, sizeof(storeContext));
475 storeContext.info = &threadData;
476
477 store = SCDynamicStoreCreate(NULL, CFSTR("cupsd"),
478 sysEventConfigurationNotifier, &storeContext);
479
480 if (!ComputerNameKey)
481 ComputerNameKey = SCDynamicStoreKeyCreateComputerName(NULL);
482
483 if (!NetworkGlobalKey)
484 NetworkGlobalKey =
485 SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
486 kSCDynamicStoreDomainState,
487 kSCEntNetIPv4);
488
489 if (!HostNamesKey)
490 HostNamesKey = SCDynamicStoreKeyCreateHostNames(NULL);
491
492 if (!NetworkInterfaceKey)
493 NetworkInterfaceKey =
494 SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
495 kSCDynamicStoreDomainState,
496 kSCCompAnyRegex,
497 kSCEntNetIPv4);
498
499 if (store && ComputerNameKey && NetworkGlobalKey && HostNamesKey &&
500 NetworkInterfaceKey)
501 {
502 key[0] = ComputerNameKey;
503 key[1] = NetworkGlobalKey;
504 key[2] = HostNamesKey;
505 pattern[0] = NetworkInterfaceKey;
506
507 keys = CFArrayCreate(NULL, (const void **)key,
508 sizeof(key) / sizeof(key[0]),
509 &kCFTypeArrayCallBacks);
510 patterns = CFArrayCreate(NULL, (const void **)pattern,
511 sizeof(pattern) / sizeof(pattern[0]),
512 &kCFTypeArrayCallBacks);
513
514 if (keys && patterns &&
515 SCDynamicStoreSetNotificationKeys(store, keys, patterns))
516 {
517 if ((storeRLS = SCDynamicStoreCreateRunLoopSource(NULL, store, 0))
518 != NULL)
519 {
520 CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLS,
521 kCFRunLoopDefaultMode);
522 }
523 else
524 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreateRunLoopSource "
525 "failed: %s\n", SCErrorString(SCError())));
526 }
527 else
528 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreSetNotificationKeys "
529 "failed: %s\n", SCErrorString(SCError())));
530 }
531 else
532 DEBUG_printf(("sysEventThreadEntry: SCDynamicStoreCreate failed: %s\n",
533 SCErrorString(SCError())));
534
535 if (keys)
536 CFRelease(keys);
537
538 if (patterns)
539 CFRelease(patterns);
540
541 /*
542 * Set up a timer to delay the wake change notifications.
543 *
544 * The initial time is set a decade or so into the future, we'll adjust
545 * this later.
546 */
547
548 bzero(&timerContext, sizeof(timerContext));
549 timerContext.info = &threadData;
550
551 threadData.timerRef =
552 CFRunLoopTimerCreate(NULL,
553 CFAbsoluteTimeGetCurrent() + (86400L * 365L * 10L),
554 86400L * 365L * 10L, 0, 0, sysEventTimerNotifier,
555 &timerContext);
556 CFRunLoopAddTimer(CFRunLoopGetCurrent(), threadData.timerRef,
557 kCFRunLoopDefaultMode);
558
559 /*
560 * Store our runloop in a global so the main thread can use it to stop us.
561 */
562
563 pthread_mutex_lock(&SysEventThreadMutex);
564
565 SysEventRunloop = CFRunLoopGetCurrent();
566
567 pthread_cond_signal(&SysEventThreadCond);
568 pthread_mutex_unlock(&SysEventThreadMutex);
569
570 /*
571 * Disappear into the runloop until it's stopped by the main thread.
572 */
573
574 CFRunLoopRun();
575
576 /*
577 * Clean up before exiting.
578 */
579
580 if (threadData.timerRef)
581 {
582 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), threadData.timerRef,
583 kCFRunLoopDefaultMode);
584 CFRelease(threadData.timerRef);
585 }
586
587 if (threadData.sysevent.powerKernelPort)
588 {
589 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerRLS,
590 kCFRunLoopDefaultMode);
591 IODeregisterForSystemPower(&powerNotifierObj);
592 IOServiceClose(threadData.sysevent.powerKernelPort);
593 IONotificationPortDestroy(powerNotifierPort);
594 }
595
596 if (storeRLS)
597 {
598 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), storeRLS,
599 kCFRunLoopDefaultMode);
600 CFRunLoopSourceInvalidate(storeRLS);
601 CFRelease(storeRLS);
602 }
603
604 if (store)
605 CFRelease(store);
606
607 pthread_exit(NULL);
608}
609
610
611/*
612 * 'sysEventPowerNotifier()' - Handle power notification events.
613 */
614
615static void
616sysEventPowerNotifier(
617 void *context, /* I - Thread context data */
618 io_service_t service, /* I - Unused service info */
619 natural_t messageType, /* I - Type of message */
620 void *messageArgument) /* I - Message data */
621{
622 int sendit = 1; /* Send event to main thread? *
623 * (0 = no, 1 = yes, 2 = delayed */
624 cupsd_thread_data_t *threadData; /* Thread context data */
625
626
627 threadData = (cupsd_thread_data_t *)context;
628
629 (void)service; /* anti-compiler-warning-code */
630
631 switch (messageType)
632 {
633 case kIOMessageCanSystemPowerOff:
634 case kIOMessageCanSystemSleep:
635 threadData->sysevent.event |= SYSEVENT_CANSLEEP;
636 break;
637
638 case kIOMessageSystemWillRestart:
639 case kIOMessageSystemWillPowerOff:
640 case kIOMessageSystemWillSleep:
641 threadData->sysevent.event |= SYSEVENT_WILLSLEEP;
642 break;
643
644 case kIOMessageSystemHasPoweredOn:
645 /*
646 * Because powered on is followed by a net-changed event, delay
647 * before sending it.
648 */
649
650 sendit = 2;
651 threadData->sysevent.event |= SYSEVENT_WOKE;
652 break;
653
654 case kIOMessageSystemWillNotPowerOff:
655 case kIOMessageSystemWillNotSleep:
4400e98d 656#ifdef kIOMessageSystemWillPowerOn
09ec0018 657 case kIOMessageSystemWillPowerOn:
4400e98d 658#endif /* kIOMessageSystemWillPowerOn */
09ec0018 659 default:
660 sendit = 0;
661 break;
662 }
663
664 if (sendit == 0)
665 IOAllowPowerChange(threadData->sysevent.powerKernelPort,
666 (long)messageArgument);
667 else
668 {
669 threadData->sysevent.powerNotificationID = (long)messageArgument;
670
671 if (sendit == 1)
672 {
673 /*
674 * Send the event to the main thread now.
675 */
676
677 write(SysEventPipes[1], &threadData->sysevent,
678 sizeof(threadData->sysevent));
679 threadData->sysevent.event = 0;
680 }
681 else
682 {
683 /*
684 * Send the event to the main thread after 1 to 2 seconds.
685 */
686
687 CFRunLoopTimerSetNextFireDate(threadData->timerRef,
688 CFAbsoluteTimeGetCurrent() + 2);
689 }
690 }
691}
692
693
694/*
695 * 'sysEventConfigurationNotifier()' - Computer name changed notification
696 * callback.
697 */
698
699static void
700sysEventConfigurationNotifier(
701 SCDynamicStoreRef store, /* I - System data (unused) */
702 CFArrayRef changedKeys, /* I - Changed data */
703 void *context) /* I - Thread context data */
704{
705 cupsd_thread_data_t *threadData; /* Thread context data */
706
707
708 threadData = (cupsd_thread_data_t *)context;
709
710 (void)store; /* anti-compiler-warning-code */
711
712 CFRange range = CFRangeMake(0, CFArrayGetCount(changedKeys));
713
714 if (CFArrayContainsValue(changedKeys, range, ComputerNameKey))
715 threadData->sysevent.event |= SYSEVENT_NAMECHANGED;
716
717 if (CFArrayContainsValue(changedKeys, range, NetworkGlobalKey) ||
718 CFArrayContainsValue(changedKeys, range, HostNamesKey) ||
719 CFArrayContainsValue(changedKeys, range, NetworkInterfaceKey))
720 threadData->sysevent.event |= SYSEVENT_NETCHANGED;
721
722 /*
723 * Because we registered for several different kinds of change notifications
724 * this callback usually gets called several times in a row. We use a timer to
725 * de-bounce these so we only end up generating one event for the main thread.
726 */
727
728 CFRunLoopTimerSetNextFireDate(threadData->timerRef,
729 CFAbsoluteTimeGetCurrent() + 2);
730}
731
732
733/*
734 * 'sysEventTimerNotifier()' - Handle delayed event notifications.
735 */
736
737static void
738sysEventTimerNotifier(
739 CFRunLoopTimerRef timer, /* I - Timer information */
740 void *context) /* I - Thread context data */
741{
742 cupsd_thread_data_t *threadData; /* Thread context data */
743
744
745 threadData = (cupsd_thread_data_t *)context;
746
747 /*
748 * If an event is still pending send it to the main thread.
749 */
750
751 if (threadData->sysevent.event)
752 {
753 write(SysEventPipes[1], &threadData->sysevent,
754 sizeof(threadData->sysevent));
755 threadData->sysevent.event = 0;
756 }
757}
a4d04587 758#endif /* __APPLE__ */
09ec0018 759
760
761/*
757d2cad 762 * End of "$Id: sysman.c 5241 2006-03-07 22:07:44Z mike $".
09ec0018 763 */