Line data Source code
1 : /* Copyright (c) 2003, Roger Dingledine
2 : * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
3 : * Copyright (c) 2007-2021, The Tor Project, Inc. */
4 : /* See LICENSE for licensing information */
5 :
6 : /**
7 : * \file process_unix.c
8 : * \brief Module for working with Unix processes.
9 : **/
10 :
11 : #define PROCESS_UNIX_PRIVATE
12 : #include "lib/intmath/cmp.h"
13 : #include "lib/buf/buffers.h"
14 : #include "lib/net/buffers_net.h"
15 : #include "lib/container/smartlist.h"
16 : #include "lib/evloop/compat_libevent.h"
17 : #include "lib/log/log.h"
18 : #include "lib/log/util_bug.h"
19 : #include "lib/process/process.h"
20 : #include "lib/process/process_unix.h"
21 : #include "lib/process/waitpid.h"
22 : #include "lib/process/env.h"
23 :
24 : #include <stdio.h>
25 :
26 : #ifdef HAVE_STRING_H
27 : #include <string.h>
28 : #endif
29 :
30 : #ifdef HAVE_ERRNO_H
31 : #include <errno.h>
32 : #endif
33 :
34 : #ifdef HAVE_UNISTD_H
35 : #include <unistd.h>
36 : #endif
37 :
38 : #ifdef HAVE_FCNTL_H
39 : #include <fcntl.h>
40 : #endif
41 :
42 : #if defined(HAVE_SYS_PRCTL_H) && defined(__linux__)
43 : #include <sys/prctl.h>
44 : #endif
45 :
46 : #if HAVE_SIGNAL_H
47 : #include <signal.h>
48 : #endif
49 :
50 : #ifndef _WIN32
51 :
52 : /** Maximum number of file descriptors, if we cannot get it via sysconf() */
53 : #define DEFAULT_MAX_FD 256
54 :
55 : /** Internal state for Unix handles. */
56 : struct process_unix_handle_t {
57 : /** Unix File Descriptor. */
58 : int fd;
59 :
60 : /** Have we reached end of file? */
61 : bool reached_eof;
62 :
63 : /** Event structure for libevent. */
64 : struct event *event;
65 :
66 : /** Are we writing? */
67 : bool is_writing;
68 : };
69 :
70 : /** Internal state for our Unix process. */
71 : struct process_unix_t {
72 : /** Standard in handle. */
73 : process_unix_handle_t stdin_handle;
74 :
75 : /** Standard out handle. */
76 : process_unix_handle_t stdout_handle;
77 :
78 : /** Standard error handle. */
79 : process_unix_handle_t stderr_handle;
80 :
81 : /** The process identifier of our process. */
82 : pid_t pid;
83 :
84 : /** Waitpid Callback structure. */
85 : waitpid_callback_t *waitpid;
86 : };
87 :
88 : /** Returns a newly allocated <b>process_unix_t</b>. */
89 : process_unix_t *
90 32 : process_unix_new(void)
91 : {
92 32 : process_unix_t *unix_process;
93 32 : unix_process = tor_malloc_zero(sizeof(process_unix_t));
94 :
95 32 : unix_process->stdin_handle.fd = -1;
96 32 : unix_process->stderr_handle.fd = -1;
97 32 : unix_process->stdout_handle.fd = -1;
98 :
99 32 : return unix_process;
100 : }
101 :
102 : /** Deallocates the given <b>unix_process</b>. */
103 : void
104 29 : process_unix_free_(process_unix_t *unix_process)
105 : {
106 29 : if (! unix_process)
107 : return;
108 :
109 : /* Clean up our waitpid callback. */
110 29 : clear_waitpid_callback(unix_process->waitpid);
111 :
112 : /* FIXME(ahf): Refactor waitpid code? */
113 29 : unix_process->waitpid = NULL;
114 :
115 : /* Close all our file descriptors. */
116 29 : process_unix_close_file_descriptors(unix_process);
117 :
118 29 : tor_event_free(unix_process->stdout_handle.event);
119 29 : tor_event_free(unix_process->stderr_handle.event);
120 29 : tor_event_free(unix_process->stdin_handle.event);
121 :
122 29 : tor_free(unix_process);
123 : }
124 :
125 : /** Executes the given process as a child process of Tor. This function is
126 : * responsible for setting up the child process and run it. This includes
127 : * setting up pipes for interprocess communication, initialize the waitpid
128 : * callbacks, and finally run fork() followed by execve(). Returns
129 : * <b>PROCESS_STATUS_RUNNING</b> upon success. */
130 : process_status_t
131 3 : process_unix_exec(process_t *process)
132 : {
133 3 : static int max_fd = -1;
134 :
135 3 : process_unix_t *unix_process;
136 3 : pid_t pid;
137 3 : int stdin_pipe[2];
138 3 : int stdout_pipe[2];
139 3 : int stderr_pipe[2];
140 3 : int retval, fd;
141 :
142 3 : unix_process = process_get_unix_process(process);
143 :
144 : /* Create standard in pipe. */
145 3 : retval = pipe(stdin_pipe);
146 :
147 3 : if (-1 == retval) {
148 0 : log_warn(LD_PROCESS,
149 : "Unable to create pipe for stdin "
150 : "communication with process: %s",
151 : strerror(errno));
152 :
153 0 : return PROCESS_STATUS_ERROR;
154 : }
155 :
156 : /* Create standard out pipe. */
157 3 : retval = pipe(stdout_pipe);
158 :
159 3 : if (-1 == retval) {
160 0 : log_warn(LD_PROCESS,
161 : "Unable to create pipe for stdout "
162 : "communication with process: %s",
163 : strerror(errno));
164 :
165 : /** Cleanup standard in pipe. */
166 0 : close(stdin_pipe[0]);
167 0 : close(stdin_pipe[1]);
168 :
169 0 : return PROCESS_STATUS_ERROR;
170 : }
171 :
172 : /* Create standard error pipe. */
173 3 : retval = pipe(stderr_pipe);
174 :
175 3 : if (-1 == retval) {
176 0 : log_warn(LD_PROCESS,
177 : "Unable to create pipe for stderr "
178 : "communication with process: %s",
179 : strerror(errno));
180 :
181 : /** Cleanup standard in pipe. */
182 0 : close(stdin_pipe[0]);
183 0 : close(stdin_pipe[1]);
184 :
185 : /** Cleanup standard out pipe. */
186 0 : close(stdout_pipe[0]);
187 0 : close(stdout_pipe[1]);
188 :
189 0 : return PROCESS_STATUS_ERROR;
190 : }
191 :
192 : #ifdef _SC_OPEN_MAX
193 3 : if (-1 == max_fd) {
194 1 : max_fd = (int)sysconf(_SC_OPEN_MAX);
195 :
196 1 : if (max_fd == -1) {
197 0 : max_fd = DEFAULT_MAX_FD;
198 0 : log_warn(LD_PROCESS,
199 : "Cannot find maximum file descriptor, assuming: %d", max_fd);
200 : }
201 : }
202 : #else /* !defined(_SC_OPEN_MAX) */
203 : max_fd = DEFAULT_MAX_FD;
204 : #endif /* defined(_SC_OPEN_MAX) */
205 :
206 3 : pid = fork();
207 :
208 5 : if (0 == pid) {
209 : /* This code is running in the child process context. */
210 :
211 : #if defined(HAVE_SYS_PRCTL_H) && defined(__linux__)
212 : /* Attempt to have the kernel issue a SIGTERM if the parent
213 : * goes away. Certain attributes of the binary being execve()ed
214 : * will clear this during the execve() call, but it's better
215 : * than nothing.
216 : */
217 2 : prctl(PR_SET_PDEATHSIG, SIGTERM);
218 : #endif /* defined(HAVE_SYS_PRCTL_H) && defined(__linux__) */
219 :
220 : /* Link process stdout to the write end of the pipe. */
221 2 : retval = dup2(stdout_pipe[1], STDOUT_FILENO);
222 2 : if (-1 == retval)
223 0 : goto error;
224 :
225 : /* Link process stderr to the write end of the pipe. */
226 2 : retval = dup2(stderr_pipe[1], STDERR_FILENO);
227 2 : if (-1 == retval)
228 0 : goto error;
229 :
230 : /* Link process stdin to the read end of the pipe */
231 2 : retval = dup2(stdin_pipe[0], STDIN_FILENO);
232 2 : if (-1 == retval)
233 0 : goto error;
234 :
235 : /* Close our pipes now after they have been dup2()'ed. */
236 2 : close(stderr_pipe[0]);
237 2 : close(stderr_pipe[1]);
238 2 : close(stdout_pipe[0]);
239 2 : close(stdout_pipe[1]);
240 2 : close(stdin_pipe[0]);
241 2 : close(stdin_pipe[1]);
242 :
243 : /* Close all other fds, including the read end of the pipe. XXX: We should
244 : * now be doing enough FD_CLOEXEC setting to make this needless.
245 : */
246 2046 : for (fd = STDERR_FILENO + 1; fd < max_fd; fd++)
247 2042 : close(fd);
248 :
249 : /* Create the argv value for our new process. */
250 2 : char **argv = process_get_argv(process);
251 :
252 : /* Create the env value for our new process. */
253 2 : process_environment_t *env = process_get_environment(process);
254 :
255 : /* Call the requested program. */
256 2 : execve(argv[0], argv, env->unixoid_environment_block);
257 :
258 : /* If we made it here it is because execve failed :-( */
259 2 : tor_free(argv);
260 2 : process_environment_free(env);
261 :
262 0 : error:
263 0 : fprintf(stderr, "Error from child process: %s", strerror(errno));
264 0 : _exit(1);
265 : }
266 :
267 : /* We are in the parent process. */
268 3 : if (-1 == pid) {
269 0 : log_warn(LD_PROCESS,
270 : "Failed to create child process: %s", strerror(errno));
271 :
272 : /** Cleanup standard in pipe. */
273 0 : close(stdin_pipe[0]);
274 0 : close(stdin_pipe[1]);
275 :
276 : /** Cleanup standard out pipe. */
277 0 : close(stdout_pipe[0]);
278 0 : close(stdout_pipe[1]);
279 :
280 : /** Cleanup standard error pipe. */
281 0 : close(stderr_pipe[0]);
282 0 : close(stderr_pipe[1]);
283 :
284 0 : return PROCESS_STATUS_ERROR;
285 : }
286 :
287 : /* Register our PID. */
288 3 : unix_process->pid = pid;
289 :
290 : /* Setup waitpid callbacks. */
291 3 : unix_process->waitpid = set_waitpid_callback(pid,
292 : process_unix_waitpid_callback,
293 : process);
294 :
295 : /* Handle standard out. */
296 3 : unix_process->stdout_handle.fd = stdout_pipe[0];
297 3 : retval = close(stdout_pipe[1]);
298 :
299 3 : if (-1 == retval) {
300 0 : log_warn(LD_PROCESS, "Failed to close write end of standard out pipe: %s",
301 : strerror(errno));
302 : }
303 :
304 : /* Handle standard error. */
305 3 : unix_process->stderr_handle.fd = stderr_pipe[0];
306 3 : retval = close(stderr_pipe[1]);
307 :
308 3 : if (-1 == retval) {
309 0 : log_warn(LD_PROCESS,
310 : "Failed to close write end of standard error pipe: %s",
311 : strerror(errno));
312 : }
313 :
314 : /* Handle standard in. */
315 3 : unix_process->stdin_handle.fd = stdin_pipe[1];
316 3 : retval = close(stdin_pipe[0]);
317 :
318 3 : if (-1 == retval) {
319 0 : log_warn(LD_PROCESS, "Failed to close read end of standard in pipe: %s",
320 : strerror(errno));
321 : }
322 :
323 : /* Setup our handles. */
324 3 : process_unix_setup_handle(process,
325 : &unix_process->stdout_handle,
326 : EV_READ|EV_PERSIST,
327 : stdout_read_callback);
328 :
329 3 : process_unix_setup_handle(process,
330 : &unix_process->stderr_handle,
331 : EV_READ|EV_PERSIST,
332 : stderr_read_callback);
333 :
334 3 : process_unix_setup_handle(process,
335 : &unix_process->stdin_handle,
336 : EV_WRITE|EV_PERSIST,
337 : stdin_write_callback);
338 :
339 : /* Start reading from standard out and standard error. */
340 3 : process_unix_start_reading(&unix_process->stdout_handle);
341 3 : process_unix_start_reading(&unix_process->stderr_handle);
342 :
343 3 : return PROCESS_STATUS_RUNNING;
344 : }
345 :
346 : /** Terminate the given process. Returns true on success, otherwise false. */
347 : bool
348 1 : process_unix_terminate(process_t *process)
349 : {
350 1 : tor_assert(process);
351 :
352 1 : process_unix_t *unix_process = process_get_unix_process(process);
353 :
354 : /* All running processes should have a waitpid. */
355 1 : if (BUG(unix_process->waitpid == NULL))
356 0 : return false;
357 :
358 1 : bool success = true;
359 :
360 : /* Send a SIGTERM to our child process. */
361 1 : int ret;
362 :
363 1 : ret = kill(unix_process->pid, SIGTERM);
364 :
365 1 : if (ret == -1) {
366 0 : log_warn(LD_PROCESS, "Unable to terminate process: %s",
367 : strerror(errno));
368 0 : success = false;
369 : }
370 :
371 : /* Close all our FD's. */
372 1 : if (! process_unix_close_file_descriptors(unix_process))
373 0 : success = false;
374 :
375 : return success;
376 : }
377 :
378 : /** Returns the unique process identifier for the given <b>process</b>. */
379 : process_pid_t
380 1 : process_unix_get_pid(process_t *process)
381 : {
382 1 : tor_assert(process);
383 :
384 1 : process_unix_t *unix_process = process_get_unix_process(process);
385 1 : return (process_pid_t)unix_process->pid;
386 : }
387 :
388 : /** Write the given <b>buffer</b> as input to the given <b>process</b>'s
389 : * standard input. Returns the number of bytes written. */
390 : int
391 5 : process_unix_write(process_t *process, buf_t *buffer)
392 : {
393 5 : tor_assert(process);
394 5 : tor_assert(buffer);
395 :
396 5 : process_unix_t *unix_process = process_get_unix_process(process);
397 :
398 5 : size_t buffer_flush_len = buf_datalen(buffer);
399 5 : const size_t max_to_write = MIN(PROCESS_MAX_WRITE, buffer_flush_len);
400 :
401 : /* If we have data to write (when buffer_flush_len > 0) and we are not
402 : * currently getting file descriptor events from the kernel, we tell the
403 : * kernel to start notifying us about when we can write to our file
404 : * descriptor and return. */
405 5 : if (buffer_flush_len > 0 && ! unix_process->stdin_handle.is_writing) {
406 1 : process_unix_start_writing(&unix_process->stdin_handle);
407 1 : return 0;
408 : }
409 :
410 : /* We don't have any data to write, but the kernel is currently notifying us
411 : * about whether we are able to write or not. Tell the kernel to stop
412 : * notifying us until we have data to write. */
413 4 : if (buffer_flush_len == 0 && unix_process->stdin_handle.is_writing) {
414 1 : process_unix_stop_writing(&unix_process->stdin_handle);
415 1 : return 0;
416 : }
417 :
418 : /* We have data to write and the kernel have told us to write it. */
419 3 : return buf_flush_to_pipe(buffer,
420 3 : process_get_unix_process(process)->stdin_handle.fd,
421 : max_to_write);
422 : }
423 :
424 : /** Read data from the given process's standard output and put it into
425 : * <b>buffer</b>. Returns the number of bytes read. */
426 : int
427 5 : process_unix_read_stdout(process_t *process, buf_t *buffer)
428 : {
429 5 : tor_assert(process);
430 5 : tor_assert(buffer);
431 :
432 5 : process_unix_t *unix_process = process_get_unix_process(process);
433 :
434 5 : return process_unix_read_handle(process,
435 : &unix_process->stdout_handle,
436 : buffer);
437 : }
438 :
439 : /** Read data from the given process's standard error and put it into
440 : * <b>buffer</b>. Returns the number of bytes read. */
441 : int
442 5 : process_unix_read_stderr(process_t *process, buf_t *buffer)
443 : {
444 5 : tor_assert(process);
445 5 : tor_assert(buffer);
446 :
447 5 : process_unix_t *unix_process = process_get_unix_process(process);
448 :
449 5 : return process_unix_read_handle(process,
450 : &unix_process->stderr_handle,
451 : buffer);
452 : }
453 :
454 : /** This function is called whenever libevent thinks we have data that could be
455 : * read from the child process's standard output. We notify the Process
456 : * subsystem, which is then responsible for calling back to us for doing the
457 : * actual reading of the data. */
458 : STATIC void
459 5 : stdout_read_callback(evutil_socket_t fd, short event, void *data)
460 : {
461 5 : (void)fd;
462 5 : (void)event;
463 :
464 5 : process_t *process = data;
465 5 : tor_assert(process);
466 :
467 5 : process_notify_event_stdout(process);
468 5 : }
469 :
470 : /** This function is called whenever libevent thinks we have data that could be
471 : * read from the child process's standard error. We notify the Process
472 : * subsystem, which is then responsible for calling back to us for doing the
473 : * actual reading of the data. */
474 : STATIC void
475 5 : stderr_read_callback(evutil_socket_t fd, short event, void *data)
476 : {
477 5 : (void)fd;
478 5 : (void)event;
479 :
480 5 : process_t *process = data;
481 5 : tor_assert(process);
482 :
483 5 : process_notify_event_stderr(process);
484 5 : }
485 :
486 : /** This function is called whenever libevent thinks we have data that could be
487 : * written the child process's standard input. We notify the Process subsystem,
488 : * which is then responsible for calling back to us for doing the actual write
489 : * of the data. */
490 : STATIC void
491 1 : stdin_write_callback(evutil_socket_t fd, short event, void *data)
492 : {
493 1 : (void)fd;
494 1 : (void)event;
495 :
496 1 : process_t *process = data;
497 1 : tor_assert(process);
498 :
499 1 : process_notify_event_stdin(process);
500 1 : }
501 :
502 : /** This function tells libevent that we are interested in receiving read
503 : * events from the given <b>handle</b>. */
504 : STATIC void
505 6 : process_unix_start_reading(process_unix_handle_t *handle)
506 : {
507 6 : tor_assert(handle);
508 :
509 6 : if (event_add(handle->event, NULL))
510 0 : log_warn(LD_PROCESS,
511 : "Unable to add libevent event for handle.");
512 6 : }
513 :
514 : /** This function tells libevent that we are no longer interested in receiving
515 : * read events from the given <b>handle</b>. */
516 : STATIC void
517 60 : process_unix_stop_reading(process_unix_handle_t *handle)
518 : {
519 60 : tor_assert(handle);
520 :
521 60 : if (handle->event == NULL)
522 : return;
523 :
524 8 : if (event_del(handle->event))
525 0 : log_warn(LD_PROCESS,
526 : "Unable to delete libevent event for handle.");
527 : }
528 :
529 : /** This function tells libevent that we are interested in receiving write
530 : * events from the given <b>handle</b>. */
531 : STATIC void
532 1 : process_unix_start_writing(process_unix_handle_t *handle)
533 : {
534 1 : tor_assert(handle);
535 :
536 1 : if (event_add(handle->event, NULL))
537 0 : log_warn(LD_PROCESS,
538 : "Unable to add libevent event for handle.");
539 :
540 1 : handle->is_writing = true;
541 1 : }
542 :
543 : /** This function tells libevent that we are no longer interested in receiving
544 : * write events from the given <b>handle</b>. */
545 : STATIC void
546 1 : process_unix_stop_writing(process_unix_handle_t *handle)
547 : {
548 1 : tor_assert(handle);
549 :
550 1 : if (handle->event == NULL)
551 : return;
552 :
553 1 : if (event_del(handle->event))
554 0 : log_warn(LD_PROCESS,
555 : "Unable to delete libevent event for handle.");
556 :
557 1 : handle->is_writing = false;
558 : }
559 :
560 : /** This function is called when the waitpid system have detected that our
561 : * process have terminated. We disable the waitpid system and notify the
562 : * Process subsystem that we have terminated. */
563 : STATIC void
564 3 : process_unix_waitpid_callback(int status, void *data)
565 : {
566 3 : tor_assert(data);
567 :
568 3 : process_t *process = data;
569 3 : process_unix_t *unix_process = process_get_unix_process(process);
570 :
571 : /* Remove our waitpid callback. */
572 3 : clear_waitpid_callback(unix_process->waitpid);
573 3 : unix_process->waitpid = NULL;
574 :
575 : /* Notify our process. */
576 3 : process_notify_event_exit(process, status);
577 :
578 : /* Make sure you don't modify the process after we have called
579 : * process_notify_event_exit() on it, to allow users to process_free() it in
580 : * the exit callback. */
581 3 : }
582 :
583 : /** This function sets the file descriptor in the <b>handle</b> as non-blocking
584 : * and configures the libevent event structure based on the given <b>flags</b>
585 : * to ensure that <b>callback</b> is called whenever we have events on the
586 : * given <b>handle</b>. */
587 : STATIC void
588 9 : process_unix_setup_handle(process_t *process,
589 : process_unix_handle_t *handle,
590 : short flags,
591 : event_callback_fn callback)
592 : {
593 9 : tor_assert(process);
594 9 : tor_assert(handle);
595 9 : tor_assert(callback);
596 :
597 : /* Put our file descriptor into non-blocking mode. */
598 9 : if (fcntl(handle->fd, F_SETFL, O_NONBLOCK) < 0) {
599 0 : log_warn(LD_PROCESS, "Unable mark Unix handle as non-blocking: %s",
600 : strerror(errno));
601 : }
602 :
603 : /* Setup libevent event. */
604 9 : handle->event = tor_event_new(tor_libevent_get_base(),
605 : handle->fd,
606 : flags,
607 : callback,
608 : process);
609 9 : }
610 :
611 : /** This function reads data from the given <b>handle</b> and puts it into
612 : * <b>buffer</b>. Returns the number of bytes read this way. */
613 : STATIC int
614 10 : process_unix_read_handle(process_t *process,
615 : process_unix_handle_t *handle,
616 : buf_t *buffer)
617 : {
618 10 : tor_assert(process);
619 10 : tor_assert(handle);
620 10 : tor_assert(buffer);
621 :
622 10 : int ret = 0;
623 10 : int eof = 0;
624 10 : int error = 0;
625 :
626 10 : ret = buf_read_from_pipe(buffer,
627 : handle->fd,
628 : PROCESS_MAX_READ,
629 : &eof,
630 : &error);
631 :
632 10 : if (error)
633 0 : log_warn(LD_PROCESS,
634 : "Unable to read data: %s", strerror(error));
635 :
636 10 : if (eof) {
637 4 : handle->reached_eof = true;
638 4 : process_unix_stop_reading(handle);
639 : }
640 :
641 10 : return ret;
642 : }
643 :
644 : /** Close the standard in, out, and error handles of the given
645 : * <b>unix_process</b>. */
646 : STATIC bool
647 30 : process_unix_close_file_descriptors(process_unix_t *unix_process)
648 : {
649 30 : tor_assert(unix_process);
650 :
651 30 : int ret;
652 30 : bool success = true;
653 :
654 : /* Stop reading and writing before we close() our
655 : * file descriptors. */
656 30 : if (! unix_process->stdout_handle.reached_eof)
657 28 : process_unix_stop_reading(&unix_process->stdout_handle);
658 :
659 30 : if (! unix_process->stderr_handle.reached_eof)
660 28 : process_unix_stop_reading(&unix_process->stderr_handle);
661 :
662 30 : if (unix_process->stdin_handle.is_writing)
663 0 : process_unix_stop_writing(&unix_process->stdin_handle);
664 :
665 30 : if (unix_process->stdin_handle.fd != -1) {
666 3 : ret = close(unix_process->stdin_handle.fd);
667 3 : if (ret == -1) {
668 0 : log_warn(LD_PROCESS, "Unable to close standard in");
669 0 : success = false;
670 : }
671 :
672 3 : unix_process->stdin_handle.fd = -1;
673 : }
674 :
675 30 : if (unix_process->stdout_handle.fd != -1) {
676 3 : ret = close(unix_process->stdout_handle.fd);
677 3 : if (ret == -1) {
678 0 : log_warn(LD_PROCESS, "Unable to close standard out");
679 0 : success = false;
680 : }
681 :
682 3 : unix_process->stdout_handle.fd = -1;
683 : }
684 :
685 30 : if (unix_process->stderr_handle.fd != -1) {
686 3 : ret = close(unix_process->stderr_handle.fd);
687 3 : if (ret == -1) {
688 0 : log_warn(LD_PROCESS, "Unable to close standard error");
689 0 : success = false;
690 : }
691 :
692 3 : unix_process->stderr_handle.fd = -1;
693 : }
694 :
695 30 : return success;
696 : }
697 :
698 : #endif /* !defined(_WIN32) */
|