]> sourceware.org Git - newlib-cygwin.git/blob - winsup/cygserver/cygserver.cc
* cygserver.cc (print_usage): Align output to utilities in utils
[newlib-cygwin.git] / winsup / cygserver / cygserver.cc
1 /* cygserver.cc
2
3 Copyright 2001, 2002, 2003, 2004, 2005, 2007, 2011 Red Hat Inc.
4
5 Written by Egor Duda <deo@logos-m.ru>
6
7 This file is part of Cygwin.
8
9 This software is a copyrighted work licensed under the terms of the
10 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
11 details. */
12
13 #ifdef __OUTSIDE_CYGWIN__
14 #include "woutsup.h"
15
16 #include <sys/types.h>
17
18 #include <assert.h>
19 #include <errno.h>
20 #include <ctype.h>
21 #include <getopt.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "cygwin_version.h"
29
30 #include "cygserver.h"
31 #include "process.h"
32 #include "transport.h"
33
34 #include "cygserver_ipc.h"
35 #include "cygserver_msg.h"
36 #include "cygserver_sem.h"
37
38 #define DEF_CONFIG_FILE "" SYSCONFDIR "/cygserver.conf"
39
40 #define SERVER_VERSION "1.20"
41
42 GENERIC_MAPPING access_mapping;
43
44 static bool
45 setup_privileges ()
46 {
47 BOOL rc, ret_val;
48 HANDLE hToken = NULL;
49 TOKEN_PRIVILEGES sPrivileges;
50
51 rc = OpenProcessToken (GetCurrentProcess () , TOKEN_ALL_ACCESS , &hToken) ;
52 if (!rc)
53 {
54 debug ("error opening process token (%lu)", GetLastError ());
55 return false;
56 }
57 rc = LookupPrivilegeValue (NULL, SE_DEBUG_NAME, &sPrivileges.Privileges[0].Luid);
58 if (!rc)
59 {
60 debug ("error getting privilege luid (%lu)", GetLastError ());
61 ret_val = false;
62 goto out;
63 }
64 sPrivileges.PrivilegeCount = 1 ;
65 sPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED ;
66 rc = AdjustTokenPrivileges (hToken, FALSE, &sPrivileges, 0, NULL, NULL) ;
67 if (!rc)
68 {
69 debug ("error adjusting privilege level. (%lu)", GetLastError ());
70 ret_val = false;
71 goto out;
72 }
73
74 access_mapping.GenericRead = FILE_READ_DATA;
75 access_mapping.GenericWrite = FILE_WRITE_DATA;
76 access_mapping.GenericExecute = 0;
77 access_mapping.GenericAll = FILE_READ_DATA | FILE_WRITE_DATA;
78
79 ret_val = true;
80
81 out:
82 CloseHandle (hToken);
83 return ret_val;
84 }
85
86 int
87 check_and_dup_handle (HANDLE from_process, HANDLE to_process,
88 HANDLE from_process_token,
89 DWORD access,
90 HANDLE from_handle,
91 HANDLE *to_handle_ptr, BOOL bInheritHandle = FALSE)
92 {
93 HANDLE local_handle = NULL;
94 int ret_val = EACCES;
95 char sd_buf [1024];
96 PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR) &sd_buf;
97 DWORD bytes_needed;
98 PRIVILEGE_SET ps;
99 DWORD ps_len = sizeof (ps);
100 BOOL status;
101
102 if (from_process != GetCurrentProcess ())
103 {
104 if (!DuplicateHandle (from_process, from_handle,
105 GetCurrentProcess (), &local_handle,
106 0, bInheritHandle,
107 DUPLICATE_SAME_ACCESS))
108 {
109 log (LOG_ERR, "error getting handle(%u) to server (%lu)",
110 (unsigned int)from_handle, GetLastError ());
111 goto out;
112 }
113 } else
114 local_handle = from_handle;
115
116 if (!GetKernelObjectSecurity (local_handle,
117 (OWNER_SECURITY_INFORMATION
118 | GROUP_SECURITY_INFORMATION
119 | DACL_SECURITY_INFORMATION),
120 sd, sizeof (sd_buf), &bytes_needed))
121 {
122 log (LOG_ERR, "error getting handle SD (%lu)", GetLastError ());
123 goto out;
124 }
125
126 MapGenericMask (&access, &access_mapping);
127
128 if (!AccessCheck (sd, from_process_token, access, &access_mapping,
129 &ps, &ps_len, &access, &status))
130 {
131 log (LOG_ERR, "error checking access rights (%lu)",
132 GetLastError ());
133 goto out;
134 }
135
136 if (!status)
137 {
138 log (LOG_ERR, "access to object denied");
139 goto out;
140 }
141
142 if (!DuplicateHandle (from_process, from_handle,
143 to_process, to_handle_ptr,
144 access, bInheritHandle, 0))
145 {
146 log (LOG_ERR, "error getting handle to client (%lu)", GetLastError ());
147 goto out;
148 }
149
150 debug ("Duplicated %p to %p", from_handle, *to_handle_ptr);
151
152 ret_val = 0;
153
154 out:
155 if (local_handle && from_process != GetCurrentProcess ())
156 CloseHandle (local_handle);
157
158 return (ret_val);
159 }
160
161 /*
162 * client_request_attach_tty::serve ()
163 */
164
165 void
166 client_request_attach_tty::serve (transport_layer_base *const conn,
167 process_cache *)
168 {
169 assert (conn);
170
171 assert (!error_code ());
172
173 if (msglen () != sizeof (req))
174 {
175 log (LOG_ERR, "bad request body length: expecting %lu bytes, got %lu",
176 sizeof (req), msglen ());
177 error_code (EINVAL);
178 msglen (0);
179 return;
180 }
181
182 msglen (0); // Until we fill in some fields.
183
184 debug ("pid %ld:(%p,%p) -> pid %ld", req.master_pid, req.from_master,
185 req.to_master, req.pid);
186
187 debug ("opening process %ld", req.master_pid);
188
189 const HANDLE from_process_handle =
190 OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.master_pid);
191
192 if (!from_process_handle)
193 {
194 log (LOG_ERR, "error opening `from' process, error = %lu",
195 GetLastError ());
196 error_code (EACCES);
197 return;
198 }
199
200 debug ("opening process %ld", req.pid);
201
202 const HANDLE to_process_handle =
203 OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.pid);
204
205 if (!to_process_handle)
206 {
207 log (LOG_ERR, "error opening `to' process, error = %lu",
208 GetLastError ());
209 CloseHandle (from_process_handle);
210 error_code (EACCES);
211 return;
212 }
213
214 debug ("Impersonating client");
215 if (!conn->impersonate_client ())
216 {
217 CloseHandle (from_process_handle);
218 CloseHandle (to_process_handle);
219 error_code (EACCES);
220 return;
221 }
222
223 HANDLE token_handle = NULL;
224
225 debug ("about to open thread token");
226 const DWORD rc = OpenThreadToken (GetCurrentThread (),
227 TOKEN_QUERY,
228 TRUE,
229 &token_handle);
230
231 debug ("opened thread token, rc=%lu", rc);
232 if (!conn->revert_to_self ())
233 {
234 CloseHandle (from_process_handle);
235 CloseHandle (to_process_handle);
236 error_code (EACCES);
237 return;
238 }
239
240 if (!rc)
241 {
242 log (LOG_ERR, "error opening thread token, error = %lu",
243 GetLastError ());
244 CloseHandle (from_process_handle);
245 CloseHandle (to_process_handle);
246 error_code (EACCES);
247 return;
248 }
249
250 // From this point on, a reply body is returned to the client.
251
252 const HANDLE from_master = req.from_master;
253 const HANDLE to_master = req.to_master;
254
255 req.from_master = NULL;
256 req.to_master = NULL;
257
258 msglen (sizeof (req));
259
260 if (from_master)
261 if (check_and_dup_handle (from_process_handle, to_process_handle,
262 token_handle,
263 GENERIC_READ,
264 from_master,
265 &req.from_master, TRUE) != 0)
266 {
267 log (LOG_ERR, "error duplicating from_master handle, error = %lu",
268 GetLastError ());
269 error_code (EACCES);
270 }
271
272 if (to_master)
273 if (check_and_dup_handle (from_process_handle, to_process_handle,
274 token_handle,
275 GENERIC_WRITE,
276 to_master,
277 &req.to_master, TRUE) != 0)
278 {
279 log (LOG_ERR, "error duplicating to_master handle, error = %lu",
280 GetLastError ());
281 error_code (EACCES);
282 }
283
284 CloseHandle (from_process_handle);
285 CloseHandle (to_process_handle);
286 CloseHandle (token_handle);
287
288 debug ("%lu(%lu, %lu) -> %lu(%lu,%lu)",
289 req.master_pid, from_master, to_master,
290 req.pid, req.from_master, req.to_master);
291
292 return;
293 }
294
295 void
296 client_request_get_version::serve (transport_layer_base *, process_cache *)
297 {
298 assert (!error_code ());
299
300 if (msglen ())
301 log (LOG_ERR, "unexpected request body ignored: %lu bytes", msglen ());
302
303 msglen (sizeof (version));
304
305 version.major = CYGWIN_SERVER_VERSION_MAJOR;
306 version.api = CYGWIN_SERVER_VERSION_API;
307 version.minor = CYGWIN_SERVER_VERSION_MINOR;
308 version.patch = CYGWIN_SERVER_VERSION_PATCH;
309 }
310
311 class server_request : public queue_request
312 {
313 public:
314 server_request (transport_layer_base *const conn, process_cache *const cache)
315 : _conn (conn), _cache (cache)
316 {}
317
318 virtual ~server_request ()
319 {
320 delete _conn;
321 }
322
323 virtual void process ()
324 {
325 client_request::handle_request (_conn, _cache);
326 }
327
328 private:
329 transport_layer_base *const _conn;
330 process_cache *const _cache;
331 };
332
333 class server_submission_loop : public queue_submission_loop
334 {
335 public:
336 server_submission_loop (threaded_queue *const queue,
337 transport_layer_base *const transport,
338 process_cache *const cache)
339 : queue_submission_loop (queue, false),
340 _transport (transport),
341 _cache (cache)
342 {
343 assert (_transport);
344 assert (_cache);
345 }
346
347 private:
348 transport_layer_base *const _transport;
349 process_cache *const _cache;
350
351 virtual void request_loop ();
352 };
353
354 /* FIXME: this is a little ugly. What we really want is to wait on
355 * two objects: one for the pipe/socket, and one for being told to
356 * shutdown. Otherwise this will stay a problem (we won't actually
357 * shutdown until the request _AFTER_ the shutdown request. And
358 * sending ourselves a request is ugly
359 */
360 void
361 server_submission_loop::request_loop ()
362 {
363 /* I'd like the accepting thread's priority to be above any "normal"
364 * thread in the system to avoid overflowing the listen queue (for
365 * sockets; similar issues exist for named pipes); but, for example,
366 * a normal priority thread in a foregrounded process is boosted to
367 * THREAD_PRIORITY_HIGHEST (AFAICT). Thus try to set the current
368 * thread's priority to a level one above that. This fails on
369 * win9x/ME so assume any failure in that call is due to that and
370 * simply call again at one priority level lower.
371 * FIXME: This looks weird and is an issue on NT, too. Per MSDN,
372 * THREAD_PRIORITY_HIGHEST + 1 is only a valid priority level if
373 * the priority class is set to REALTIME_PRIORITY_CLASS.
374 */
375 if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST + 1))
376 if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST))
377 debug ("failed to raise accept thread priority, error = %lu",
378 GetLastError ());
379
380 while (_running)
381 {
382 bool recoverable = false;
383 transport_layer_base *const conn = _transport->accept (&recoverable);
384 if (!conn && !recoverable)
385 {
386 log (LOG_ERR, "fatal error on IPC transport: closing down");
387 return;
388 }
389 // EINTR probably implies a shutdown request; so back off for a
390 // moment to let the main thread take control, otherwise the
391 // server spins here receiving EINTR repeatedly since the signal
392 // handler in the main thread doesn't get a chance to be called.
393 if (!conn && errno == EINTR)
394 {
395 if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_NORMAL))
396 debug ("failed to reset thread priority, error = %lu",
397 GetLastError ());
398
399 Sleep (0);
400 if (!SetThreadPriority (GetCurrentThread (),
401 THREAD_PRIORITY_HIGHEST + 1))
402 if (!SetThreadPriority (GetCurrentThread (),
403 THREAD_PRIORITY_HIGHEST))
404 debug ("failed to raise thread priority, error = %lu",
405 GetLastError ());
406 }
407 if (conn)
408 _queue->add (new server_request (conn, _cache));
409 }
410 }
411
412 client_request_shutdown::client_request_shutdown ()
413 : client_request (CYGSERVER_REQUEST_SHUTDOWN)
414 {
415 }
416
417 void
418 client_request_shutdown::serve (transport_layer_base *, process_cache *)
419 {
420 assert (!error_code ());
421
422 if (msglen ())
423 log (LOG_ERR, "unexpected request body ignored: %lu bytes", msglen ());
424
425 /* FIXME: link upwards, and then this becomes a trivial method call to
426 * only shutdown _this queue_
427 */
428
429 kill (getpid (), SIGINT);
430
431 msglen (0);
432 }
433
434 static sig_atomic_t shutdown_server = false;
435
436 static void
437 handle_signal (const int signum)
438 {
439 /* any signal makes us die :} */
440
441 shutdown_server = true;
442 }
443
444 /*
445 * print_usage ()
446 */
447
448 static void
449 print_usage (const char *const pgm)
450 {
451 log (LOG_NOTICE, "Usage: %s [OPTIONS]\n"
452 "\n"
453 "Cygwin background service daemon\n"
454 "\n"
455 "Configuration option:\n"
456 "\n"
457 " -f, --config-file <file> Use <file> as config file. Default is\n"
458 " " DEF_CONFIG_FILE "\n"
459 "\n"
460 "Performance options:\n"
461 "\n"
462 " -c, --cleanup-threads <num> Number of cleanup threads to use.\n"
463 " -p, --process-cache <num> Size of process cache.\n"
464 " -r, --request-threads <num> Number of request threads to use.\n"
465 "\n"
466 "Logging options:\n"
467 "\n"
468 " -d, --debug Log debug messages to stderr.\n"
469 " -e, --stderr Log to stderr (default if stderr is a tty).\n"
470 " -E, --no-stderr Don't log to stderr (see -y, -Y options).\n"
471 " -l, --log-level <level> Verbosity of logging (1..7). Default: 6\n"
472 " -y, --syslog Log to syslog (default if stderr is no tty).\n"
473 " -Y, --no-syslog Don't log to syslog (See -e, -E options).\n"
474 "\n"
475 "Support options:\n"
476 "\n"
477 " -m, --no-sharedmem Don't start XSI Shared Memory support.\n"
478 " -q, --no-msgqueues Don't start XSI Message Queue support.\n"
479 " -s, --no-semaphores Don't start XSI Semaphore support.\n"
480 "\n"
481 "Miscellaneous:\n"
482 "\n"
483 " -S, --shutdown Shutdown the daemon.\n"
484 " -h, --help Output usage information and exit.\n"
485 " -V, --version Output version information and exit.\n"
486 , pgm);
487 }
488
489 /*
490 * print_version ()
491 */
492
493 static void
494 print_version ()
495 {
496 log (LOG_INFO,
497 "cygserver (cygwin) %d.%d.%d\n"
498 "Cygwin background service daemon\n"
499 "Copyright (C) 2001 - %s Red Hat, Inc.\n"
500 "This is free software; see the source for copying conditions. There is NO\n"
501 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.",
502 CYGWIN_VERSION_DLL_MAJOR / 1000,
503 CYGWIN_VERSION_DLL_MAJOR % 1000,
504 CYGWIN_VERSION_DLL_MINOR,
505 strrchr (__DATE__, ' ') + 1);
506 }
507
508 /*
509 * main ()
510 */
511
512 int
513 main (const int argc, char *argv[])
514 {
515 const struct option longopts[] = {
516 {"cleanup-threads", required_argument, NULL, 'c'},
517 {"debug", no_argument, NULL, 'd'},
518 {"stderr", no_argument, NULL, 'e'},
519 {"no-stderr", no_argument, NULL, 'E'},
520 {"config-file", required_argument, NULL, 'f'},
521 {"help", no_argument, NULL, 'h'},
522 {"log-level", required_argument, NULL, 'l'},
523 {"no-sharedmem", no_argument, NULL, 'm'},
524 {"process-cache", required_argument, NULL, 'p'},
525 {"no-msgqueues", no_argument, NULL, 'q'},
526 {"request-threads", required_argument, NULL, 'r'},
527 {"no-semaphores", no_argument, NULL, 's'},
528 {"shutdown", no_argument, NULL, 'S'},
529 {"version", no_argument, NULL, 'V'},
530 {"syslog", no_argument, NULL, 'y'},
531 {"no-syslog", no_argument, NULL, 'Y'},
532 {0, no_argument, NULL, 0}
533 };
534
535 const char opts[] = "c:deEf:hl:mp:qr:sSVyY";
536
537 long cleanup_threads = 0;
538 long request_threads = 0;
539 long process_cache_size = 0;
540 bool shutdown = false;
541 const char *config_file = DEF_CONFIG_FILE;
542 bool force_config_file = false;
543 tun_bool_t option_log_stderr = TUN_UNDEF;
544 tun_bool_t option_log_syslog = TUN_UNDEF;
545
546 char *c = NULL;
547
548 /* Check if we have a terminal. If so, default to stderr logging,
549 otherwise default to syslog logging. This must be done early
550 to allow default logging already in option processing state. */
551 openlog ("cygserver", LOG_PID, LOG_KERN);
552 if (isatty (2))
553 log_stderr = TUN_TRUE;
554 else
555 log_syslog = TUN_TRUE;
556
557 int opt;
558
559 securityinit ();
560
561 opterr = 0;
562 while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF)
563 switch (opt)
564 {
565 case 'c':
566 c = NULL;
567 cleanup_threads = strtol (optarg, &c, 10);
568 if (cleanup_threads <= 0 || cleanup_threads > 32 || (c && *c))
569 panic ("Number of cleanup threads must be between 1 and 32");
570 break;
571
572 case 'd':
573 log_debug = TUN_TRUE;
574 break;
575
576 case 'e':
577 option_log_stderr = TUN_TRUE;
578 break;
579
580 case 'E':
581 option_log_stderr = TUN_FALSE;
582 break;
583
584 case 'f':
585 config_file = optarg;
586 force_config_file = true;
587 break;
588
589 case 'h':
590 print_usage (getprogname ());
591 return 0;
592
593 case 'l':
594 c = NULL;
595 log_level = strtoul (optarg, &c, 10);
596 if (!log_level || log_level > 7 || (c && *c))
597 panic ("Log level must be between 1 and 7");
598 break;
599
600 case 'm':
601 support_sharedmem = TUN_FALSE;
602 break;
603
604 case 'p':
605 c = NULL;
606 process_cache_size = strtol (optarg, &c, 10);
607 if (process_cache_size <= 0 || process_cache_size > 310 || (c && *c))
608 panic ("Size of process cache must be between 1 and 310");
609 break;
610
611 case 'q':
612 support_msgqueues = TUN_FALSE;
613 break;
614
615 case 'r':
616 c = NULL;
617 request_threads = strtol (optarg, &c, 10);
618 if (request_threads <= 0 || request_threads > 310 || (c && *c))
619 panic ("Number of request threads must be between 1 and 310");
620 break;
621
622 case 's':
623 support_semaphores = TUN_FALSE;
624 break;
625
626 case 'S':
627 shutdown = true;
628 break;
629
630 case 'V':
631 print_version ();
632 return 0;
633
634 case 'y':
635 option_log_syslog = TUN_TRUE;
636 break;
637
638 case 'Y':
639 option_log_syslog = TUN_FALSE;
640 break;
641
642 case '?':
643 panic ("unknown option -- %c\n"
644 "Try `%s --help' for more information.", optopt, getprogname ());
645 }
646
647 if (optind != argc)
648 panic ("Too many arguments");
649
650 if (shutdown)
651 {
652 /* Setting `cygserver_running' stops the request code making a
653 * version request, which is not much to the point.
654 */
655 cygserver_running = CYGSERVER_OK;
656
657 client_request_shutdown req;
658
659 if (req.make_request () == -1 || req.error_code ())
660 panic("Shutdown request failed: %s", strerror (req.error_code ()));
661
662 // FIXME: It would be nice to wait here for the daemon to exit.
663
664 return 0;
665 }
666
667 SIGHANDLE (SIGHUP);
668 SIGHANDLE (SIGINT);
669 SIGHANDLE (SIGTERM);
670
671 tunable_param_init (config_file, force_config_file);
672
673 loginit (option_log_stderr, option_log_syslog);
674
675 log (LOG_INFO, "daemon starting up");
676
677 if (!cleanup_threads)
678 TUNABLE_INT_FETCH ("kern.srv.cleanup_threads", &cleanup_threads);
679 if (!cleanup_threads)
680 cleanup_threads = 2;
681
682 if (!request_threads)
683 TUNABLE_INT_FETCH ("kern.srv.request_threads", &request_threads);
684 if (!request_threads)
685 request_threads = 10;
686
687 if (!process_cache_size)
688 TUNABLE_INT_FETCH ("kern.srv.process_cache_size", &process_cache_size);
689 if (!process_cache_size)
690 process_cache_size = 62;
691
692 if (support_sharedmem == TUN_UNDEF)
693 TUNABLE_BOOL_FETCH ("kern.srv.sharedmem", &support_sharedmem);
694 if (support_sharedmem == TUN_UNDEF)
695 support_sharedmem = TUN_TRUE;
696
697 if (support_msgqueues == TUN_UNDEF)
698 TUNABLE_BOOL_FETCH ("kern.srv.msgqueues", &support_msgqueues);
699 if (support_msgqueues == TUN_UNDEF)
700 support_msgqueues = TUN_TRUE;
701
702 if (support_semaphores == TUN_UNDEF)
703 TUNABLE_BOOL_FETCH ("kern.srv.semaphores", &support_semaphores);
704 if (support_semaphores == TUN_UNDEF)
705 support_semaphores = TUN_TRUE;
706
707 if (!setup_privileges ())
708 panic ("Setting process privileges failed.");
709
710 ipcinit ();
711
712 /*XXXXX*/
713 threaded_queue request_queue (request_threads);
714
715 transport_layer_base *const transport = create_server_transport ();
716 assert (transport);
717
718 process_cache cache (process_cache_size, cleanup_threads);
719
720 server_submission_loop submission_loop (&request_queue, transport, &cache);
721
722 request_queue.add_submission_loop (&submission_loop);
723
724 if (transport->listen () == -1)
725 return 1;
726
727 cache.start ();
728
729 request_queue.start ();
730
731 log (LOG_NOTICE, "Initialization complete. Waiting for requests.");
732
733 /* TODO: wait on multiple objects - the thread handle for each
734 * request loop + all the process handles. This should be done by
735 * querying the request_queue and the process cache for all their
736 * handles, and then waiting for (say) 30 seconds. after that we
737 * recreate the list of handles to wait on, and wait again. the
738 * point of all this abstraction is that we can trivially server
739 * both sockets and pipes simply by making a new transport, and then
740 * calling request_queue.process_requests (transport2);
741 */
742 /* WaitForMultipleObjects abort && request_queue && process_queue && signal
743 -- if signal event then retrigger it
744 */
745 while (!shutdown_server && request_queue.running () && cache.running ())
746 {
747 pause ();
748 if (ipcunload ())
749 {
750 shutdown_server = false;
751 log (LOG_WARNING, "Shutdown request received but ignored. "
752 "Dependent processes still running.");
753 }
754 }
755
756 log (LOG_INFO, "Shutdown request received - new requests will be denied");
757 request_queue.stop ();
758 log (LOG_INFO, "All pending requests processed");
759 delete transport;
760 log (LOG_INFO, "No longer accepting requests - cygwin will operate in daemonless mode");
761 cache.stop ();
762 log (LOG_INFO, "All outstanding process-cache activities completed");
763 log (LOG_NOTICE, "Shutdown finished.");
764
765 return 0;
766 }
767 #endif /* __OUTSIDE_CYGWIN__ */
This page took 0.088492 seconds and 5 git commands to generate.