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