1/* Copyright libuv project contributors. All rights reserved. 2 * 3 * Permission is hereby granted, free of charge, to any person obtaining a copy 4 * of this software and associated documentation files (the "Software"), to 5 * deal in the Software without restriction, including without limitation the 6 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 * sell copies of the Software, and to permit persons to whom the Software is 8 * furnished to do so, subject to the following conditions: 9 * 10 * The above copyright notice and this permission notice shall be included in 11 * all copies or substantial portions of the Software. 12 * 13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 * IN THE SOFTWARE. 20 */ 21 22/* These tests are Unix only. */ 23#ifndef _WIN32 24 25#include <unistd.h> 26#include <sys/wait.h> 27#include <sys/socket.h> 28#include <string.h> 29 30#include "uv.h" 31#include "task.h" 32 33static int timer_cb_called; 34static int socket_cb_called; 35 36static void timer_cb(uv_timer_t* timer) { 37 timer_cb_called++; 38 uv_close((uv_handle_t*) timer, NULL); 39} 40 41 42static int socket_cb_read_fd; 43static int socket_cb_read_size; 44static char socket_cb_read_buf[1024]; 45 46 47static void socket_cb(uv_poll_t* poll, int status, int events) { 48 ssize_t cnt; 49 socket_cb_called++; 50 ASSERT(0 == status); 51 printf("Socket cb got events %d\n", events); 52 ASSERT(UV_READABLE == (events & UV_READABLE)); 53 if (socket_cb_read_fd) { 54 cnt = read(socket_cb_read_fd, socket_cb_read_buf, socket_cb_read_size); 55 ASSERT(cnt == socket_cb_read_size); 56 } 57 uv_close((uv_handle_t*) poll, NULL); 58} 59 60 61static void run_timer_loop_once(void) { 62 uv_loop_t* loop; 63 uv_timer_t timer_handle; 64 65 loop = uv_default_loop(); 66 67 timer_cb_called = 0; /* Reset for the child. */ 68 69 ASSERT(0 == uv_timer_init(loop, &timer_handle)); 70 ASSERT(0 == uv_timer_start(&timer_handle, timer_cb, 1, 0)); 71 ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT)); 72 ASSERT(1 == timer_cb_called); 73} 74 75 76static void assert_wait_child(pid_t child_pid) { 77 pid_t waited_pid; 78 int child_stat; 79 80 waited_pid = waitpid(child_pid, &child_stat, 0); 81 printf("Waited pid is %d with status %d\n", waited_pid, child_stat); 82 if (waited_pid == -1) { 83 perror("Failed to wait"); 84 } 85 ASSERT(child_pid == waited_pid); 86 ASSERT(WIFEXITED(child_stat)); /* Clean exit, not a signal. */ 87 ASSERT(!WIFSIGNALED(child_stat)); 88 ASSERT(0 == WEXITSTATUS(child_stat)); 89} 90 91 92TEST_IMPL(fork_timer) { 93 /* Timers continue to work after we fork. */ 94 95 /* 96 * Establish the loop before we fork to make sure that it 97 * has state to get reset after the fork. 98 */ 99 pid_t child_pid; 100 101 run_timer_loop_once(); 102 child_pid = fork(); 103 ASSERT(child_pid != -1); 104 105 if (child_pid != 0) { 106 /* parent */ 107 assert_wait_child(child_pid); 108 } else { 109 /* child */ 110 ASSERT(0 == uv_loop_fork(uv_default_loop())); 111 run_timer_loop_once(); 112 } 113 114 MAKE_VALGRIND_HAPPY(); 115 return 0; 116} 117 118 119TEST_IMPL(fork_socketpair) { 120 /* A socket opened in the parent and accept'd in the 121 child works after a fork. */ 122 pid_t child_pid; 123 int socket_fds[2]; 124 uv_poll_t poll_handle; 125 126 /* Prime the loop. */ 127 run_timer_loop_once(); 128 129 ASSERT(0 == socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds)); 130 131 /* Create the server watcher in the parent, use it in the child. */ 132 ASSERT(0 == uv_poll_init(uv_default_loop(), &poll_handle, socket_fds[0])); 133 134 child_pid = fork(); 135 ASSERT(child_pid != -1); 136 137 if (child_pid != 0) { 138 /* parent */ 139 ASSERT(3 == send(socket_fds[1], "hi\n", 3, 0)); 140 assert_wait_child(child_pid); 141 } else { 142 /* child */ 143 ASSERT(0 == uv_loop_fork(uv_default_loop())); 144 ASSERT(0 == socket_cb_called); 145 ASSERT(0 == uv_poll_start(&poll_handle, UV_READABLE, socket_cb)); 146 printf("Going to run the loop in the child\n"); 147 ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); 148 ASSERT(1 == socket_cb_called); 149 } 150 151 MAKE_VALGRIND_HAPPY(); 152 return 0; 153} 154 155 156TEST_IMPL(fork_socketpair_started) { 157 /* A socket opened in the parent and accept'd in the 158 child works after a fork, even if the watcher was already 159 started, and then stopped in the parent. */ 160 pid_t child_pid; 161 int socket_fds[2]; 162 int sync_pipe[2]; 163 char sync_buf[1]; 164 uv_poll_t poll_handle; 165 166 ASSERT(0 == pipe(sync_pipe)); 167 168 /* Prime the loop. */ 169 run_timer_loop_once(); 170 171 ASSERT(0 == socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds)); 172 173 /* Create and start the server watcher in the parent, use it in the child. */ 174 ASSERT(0 == uv_poll_init(uv_default_loop(), &poll_handle, socket_fds[0])); 175 ASSERT(0 == uv_poll_start(&poll_handle, UV_READABLE, socket_cb)); 176 177 /* Run the loop AFTER the poll watcher is registered to make sure it 178 gets passed to the kernel. Use NOWAIT and expect a non-zero 179 return to prove the poll watcher is active. 180 */ 181 ASSERT(1 == uv_run(uv_default_loop(), UV_RUN_NOWAIT)); 182 183 child_pid = fork(); 184 ASSERT(child_pid != -1); 185 186 if (child_pid != 0) { 187 /* parent */ 188 ASSERT(0 == uv_poll_stop(&poll_handle)); 189 uv_close((uv_handle_t*)&poll_handle, NULL); 190 ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); 191 ASSERT(0 == socket_cb_called); 192 ASSERT(1 == write(sync_pipe[1], "1", 1)); /* alert child */ 193 ASSERT(3 == send(socket_fds[1], "hi\n", 3, 0)); 194 195 ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); 196 ASSERT(0 == socket_cb_called); 197 198 assert_wait_child(child_pid); 199 } else { 200 /* child */ 201 printf("Child is %d\n", getpid()); 202 ASSERT(1 == read(sync_pipe[0], sync_buf, 1)); /* wait for parent */ 203 ASSERT(0 == uv_loop_fork(uv_default_loop())); 204 ASSERT(0 == socket_cb_called); 205 206 printf("Going to run the loop in the child\n"); 207 socket_cb_read_fd = socket_fds[0]; 208 socket_cb_read_size = 3; 209 ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); 210 ASSERT(1 == socket_cb_called); 211 printf("Buf %s\n", socket_cb_read_buf); 212 ASSERT(0 == strcmp("hi\n", socket_cb_read_buf)); 213 } 214 215 MAKE_VALGRIND_HAPPY(); 216 return 0; 217} 218 219 220static int fork_signal_cb_called; 221 222void fork_signal_to_child_cb(uv_signal_t* handle, int signum) 223{ 224 fork_signal_cb_called = signum; 225 uv_close((uv_handle_t*)handle, NULL); 226} 227 228 229TEST_IMPL(fork_signal_to_child) { 230 /* A signal handler installed before forking 231 is run only in the child when the child is signalled. */ 232 uv_signal_t signal_handle; 233 pid_t child_pid; 234 int sync_pipe[2]; 235 char sync_buf[1]; 236 237 fork_signal_cb_called = 0; /* reset */ 238 239 ASSERT(0 == pipe(sync_pipe)); 240 241 /* Prime the loop. */ 242 run_timer_loop_once(); 243 244 ASSERT(0 == uv_signal_init(uv_default_loop(), &signal_handle)); 245 ASSERT(0 == uv_signal_start(&signal_handle, fork_signal_to_child_cb, SIGUSR1)); 246 247 child_pid = fork(); 248 ASSERT(child_pid != -1); 249 250 if (child_pid != 0) { 251 /* parent */ 252 ASSERT(1 == read(sync_pipe[0], sync_buf, 1)); /* wait for child */ 253 ASSERT(0 == kill(child_pid, SIGUSR1)); 254 /* Run the loop, make sure we don't get the signal. */ 255 printf("Running loop in parent\n"); 256 uv_unref((uv_handle_t*)&signal_handle); 257 ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_NOWAIT)); 258 ASSERT(0 == fork_signal_cb_called); 259 printf("Waiting for child in parent\n"); 260 assert_wait_child(child_pid); 261 } else { 262 /* child */ 263 ASSERT(0 == uv_loop_fork(uv_default_loop())); 264 ASSERT(1 == write(sync_pipe[1], "1", 1)); /* alert parent */ 265 /* Get the signal. */ 266 ASSERT(0 != uv_loop_alive(uv_default_loop())); 267 printf("Running loop in child\n"); 268 ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_ONCE)); 269 ASSERT(SIGUSR1 == fork_signal_cb_called); 270 } 271 272 MAKE_VALGRIND_HAPPY(); 273 return 0; 274} 275 276 277TEST_IMPL(fork_signal_to_child_closed) { 278 /* A signal handler installed before forking 279 doesn't get received anywhere when the child is signalled, 280 but isnt running the loop. */ 281 uv_signal_t signal_handle; 282 pid_t child_pid; 283 int sync_pipe[2]; 284 int sync_pipe2[2]; 285 char sync_buf[1]; 286 int r; 287 288 fork_signal_cb_called = 0; /* reset */ 289 290 ASSERT(0 == pipe(sync_pipe)); 291 ASSERT(0 == pipe(sync_pipe2)); 292 293 /* Prime the loop. */ 294 run_timer_loop_once(); 295 296 ASSERT(0 == uv_signal_init(uv_default_loop(), &signal_handle)); 297 ASSERT(0 == uv_signal_start(&signal_handle, fork_signal_to_child_cb, SIGUSR1)); 298 299 child_pid = fork(); 300 ASSERT(child_pid != -1); 301 302 if (child_pid != 0) { 303 /* parent */ 304 printf("Wating on child in parent\n"); 305 ASSERT(1 == read(sync_pipe[0], sync_buf, 1)); /* wait for child */ 306 printf("Parent killing child\n"); 307 ASSERT(0 == kill(child_pid, SIGUSR1)); 308 /* Run the loop, make sure we don't get the signal. */ 309 printf("Running loop in parent\n"); 310 uv_unref((uv_handle_t*)&signal_handle); /* so the loop can exit; 311 we *shouldn't* get any signals */ 312 run_timer_loop_once(); /* but while we share a pipe, we do, so 313 have something active. */ 314 ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_ONCE)); 315 printf("Signal in parent %d\n", fork_signal_cb_called); 316 ASSERT(0 == fork_signal_cb_called); 317 ASSERT(1 == write(sync_pipe2[1], "1", 1)); /* alert child */ 318 printf("Waiting for child in parent\n"); 319 assert_wait_child(child_pid); 320 } else { 321 /* Child. Our signal handler should still be installed. */ 322 ASSERT(0 == uv_loop_fork(uv_default_loop())); 323 printf("Checking loop in child\n"); 324 ASSERT(0 != uv_loop_alive(uv_default_loop())); 325 printf("Alerting parent in child\n"); 326 ASSERT(1 == write(sync_pipe[1], "1", 1)); /* alert parent */ 327 /* Don't run the loop. Wait for the parent to call us */ 328 printf("Waiting on parent in child\n"); 329 /* Wait for parent. read may fail if the parent tripped an ASSERT 330 and exited, so this ASSERT is generous. 331 */ 332 r = read(sync_pipe2[0], sync_buf, 1); 333 ASSERT(-1 <= r && r <= 1); 334 ASSERT(0 == fork_signal_cb_called); 335 printf("Exiting child \n"); 336 /* Note that we're deliberately not running the loop 337 * in the child, and also not closing the loop's handles, 338 * so the child default loop can't be cleanly closed. 339 * We need to explicitly exit to avoid an automatic failure 340 * in that case. 341 */ 342 exit(0); 343 } 344 345 MAKE_VALGRIND_HAPPY(); 346 return 0; 347} 348 349 350static void create_file(const char* name) { 351 int r; 352 uv_file file; 353 uv_fs_t req; 354 355 r = uv_fs_open(NULL, &req, name, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR, NULL); 356 ASSERT(r >= 0); 357 file = r; 358 uv_fs_req_cleanup(&req); 359 r = uv_fs_close(NULL, &req, file, NULL); 360 ASSERT(r == 0); 361 uv_fs_req_cleanup(&req); 362} 363 364 365static void touch_file(const char* name) { 366 int r; 367 uv_file file; 368 uv_fs_t req; 369 uv_buf_t buf; 370 371 r = uv_fs_open(NULL, &req, name, O_RDWR, 0, NULL); 372 ASSERT(r >= 0); 373 file = r; 374 uv_fs_req_cleanup(&req); 375 376 buf = uv_buf_init("foo", 4); 377 r = uv_fs_write(NULL, &req, file, &buf, 1, -1, NULL); 378 ASSERT(r >= 0); 379 uv_fs_req_cleanup(&req); 380 381 r = uv_fs_close(NULL, &req, file, NULL); 382 ASSERT(r == 0); 383 uv_fs_req_cleanup(&req); 384} 385 386 387static int timer_cb_touch_called; 388 389static void timer_cb_touch(uv_timer_t* timer) { 390 uv_close((uv_handle_t*)timer, NULL); 391 touch_file("watch_file"); 392 timer_cb_touch_called++; 393} 394 395 396static int fs_event_cb_called; 397 398static void fs_event_cb_file_current_dir(uv_fs_event_t* handle, 399 const char* filename, 400 int events, 401 int status) { 402 ASSERT(fs_event_cb_called == 0); 403 ++fs_event_cb_called; 404 ASSERT(status == 0); 405#if defined(__APPLE__) || defined(__linux__) 406 ASSERT(strcmp(filename, "watch_file") == 0); 407#else 408 ASSERT(filename == NULL || strcmp(filename, "watch_file") == 0); 409#endif 410 uv_close((uv_handle_t*)handle, NULL); 411} 412 413 414static void assert_watch_file_current_dir(uv_loop_t* const loop, int file_or_dir) { 415 uv_timer_t timer; 416 uv_fs_event_t fs_event; 417 int r; 418 419 /* Setup */ 420 remove("watch_file"); 421 create_file("watch_file"); 422 423 r = uv_fs_event_init(loop, &fs_event); 424 ASSERT(r == 0); 425 /* watching a dir is the only way to get fsevents involved on apple 426 platforms */ 427 r = uv_fs_event_start(&fs_event, 428 fs_event_cb_file_current_dir, 429 file_or_dir == 1 ? "." : "watch_file", 430 0); 431 ASSERT(r == 0); 432 433 r = uv_timer_init(loop, &timer); 434 ASSERT(r == 0); 435 436 r = uv_timer_start(&timer, timer_cb_touch, 100, 0); 437 ASSERT(r == 0); 438 439 ASSERT(timer_cb_touch_called == 0); 440 ASSERT(fs_event_cb_called == 0); 441 442 uv_run(loop, UV_RUN_DEFAULT); 443 444 ASSERT(timer_cb_touch_called == 1); 445 ASSERT(fs_event_cb_called == 1); 446 447 /* Cleanup */ 448 remove("watch_file"); 449 fs_event_cb_called = 0; 450 timer_cb_touch_called = 0; 451 uv_run(loop, UV_RUN_DEFAULT); /* flush pending closes */ 452} 453 454 455#define FS_TEST_FILE 0 456#define FS_TEST_DIR 1 457 458static int _do_fork_fs_events_child(int file_or_dir) { 459 /* basic fsevents work in the child after a fork */ 460 pid_t child_pid; 461 uv_loop_t loop; 462 463 /* Watch in the parent, prime the loop and/or threads. */ 464 assert_watch_file_current_dir(uv_default_loop(), file_or_dir); 465 child_pid = fork(); 466 ASSERT(child_pid != -1); 467 468 if (child_pid != 0) { 469 /* parent */ 470 assert_wait_child(child_pid); 471 } else { 472 /* child */ 473 /* Ee can watch in a new loop, but dirs only work 474 if we're on linux. */ 475#if defined(__APPLE__) 476 file_or_dir = FS_TEST_FILE; 477#endif 478 printf("Running child\n"); 479 uv_loop_init(&loop); 480 printf("Child first watch\n"); 481 assert_watch_file_current_dir(&loop, file_or_dir); 482 ASSERT(0 == uv_loop_close(&loop)); 483 printf("Child second watch default loop\n"); 484 /* Ee can watch in the default loop. */ 485 ASSERT(0 == uv_loop_fork(uv_default_loop())); 486 /* On some platforms (OS X), if we don't update the time now, 487 * the timer cb fires before the event loop enters uv__io_poll, 488 * instead of after, meaning we don't see the change! This may be 489 * a general race. 490 */ 491 uv_update_time(uv_default_loop()); 492 assert_watch_file_current_dir(uv_default_loop(), file_or_dir); 493 494 /* We can close the parent loop successfully too. This is 495 especially important on Apple platforms where if we're not 496 careful trying to touch the CFRunLoop, even just to shut it 497 down, that we allocated in the FS_TEST_DIR case would crash. */ 498 ASSERT(0 == uv_loop_close(uv_default_loop())); 499 500 printf("Exiting child \n"); 501 } 502 503 MAKE_VALGRIND_HAPPY(); 504 return 0; 505 506} 507 508 509TEST_IMPL(fork_fs_events_child) { 510#if defined(NO_FS_EVENTS) 511 RETURN_SKIP(NO_FS_EVENTS); 512#endif 513 return _do_fork_fs_events_child(FS_TEST_FILE); 514} 515 516 517TEST_IMPL(fork_fs_events_child_dir) { 518#if defined(NO_FS_EVENTS) 519 RETURN_SKIP(NO_FS_EVENTS); 520#endif 521#if defined(__APPLE__) || defined (__linux__) 522 return _do_fork_fs_events_child(FS_TEST_DIR); 523#else 524 /* You can't spin up a cfrunloop thread on an apple platform 525 and then fork. See 526 http://objectivistc.tumblr.com/post/16187948939/you-must-exec-a-core-foundation-fork-safety-tale 527 */ 528 return 0; 529#endif 530} 531 532 533TEST_IMPL(fork_fs_events_file_parent_child) { 534#if defined(NO_FS_EVENTS) 535 RETURN_SKIP(NO_FS_EVENTS); 536#endif 537#if defined(__sun) || defined(_AIX) || defined(__MVS__) 538 /* It's not possible to implement this without additional 539 * bookkeeping on SunOS. For AIX it is possible, but has to be 540 * written. See https://github.com/libuv/libuv/pull/846#issuecomment-287170420 541 * TODO: On z/OS, we need to open another message queue and subscribe to the 542 * same events as the parent. 543 */ 544 return 0; 545#else 546 /* Establishing a started fs events watcher in the parent should 547 still work in the child. */ 548 uv_timer_t timer; 549 uv_fs_event_t fs_event; 550 int r; 551 pid_t child_pid; 552 uv_loop_t* loop; 553 554 loop = uv_default_loop(); 555 556 /* Setup */ 557 remove("watch_file"); 558 create_file("watch_file"); 559 560 r = uv_fs_event_init(loop, &fs_event); 561 ASSERT(r == 0); 562 r = uv_fs_event_start(&fs_event, 563 fs_event_cb_file_current_dir, 564 "watch_file", 565 0); 566 ASSERT(r == 0); 567 568 r = uv_timer_init(loop, &timer); 569 ASSERT(r == 0); 570 571 child_pid = fork(); 572 ASSERT(child_pid != -1); 573 if (child_pid != 0) { 574 /* parent */ 575 assert_wait_child(child_pid); 576 } else { 577 /* child */ 578 printf("Running child\n"); 579 ASSERT(0 == uv_loop_fork(loop)); 580 581 r = uv_timer_start(&timer, timer_cb_touch, 100, 0); 582 ASSERT(r == 0); 583 584 ASSERT(timer_cb_touch_called == 0); 585 ASSERT(fs_event_cb_called == 0); 586 printf("Running loop in child \n"); 587 uv_run(loop, UV_RUN_DEFAULT); 588 589 ASSERT(timer_cb_touch_called == 1); 590 ASSERT(fs_event_cb_called == 1); 591 592 /* Cleanup */ 593 remove("watch_file"); 594 fs_event_cb_called = 0; 595 timer_cb_touch_called = 0; 596 uv_run(loop, UV_RUN_DEFAULT); /* Flush pending closes. */ 597 } 598 599 600 MAKE_VALGRIND_HAPPY(); 601 return 0; 602#endif 603} 604 605 606static int work_cb_count; 607static int after_work_cb_count; 608 609 610static void work_cb(uv_work_t* req) { 611 work_cb_count++; 612} 613 614 615static void after_work_cb(uv_work_t* req, int status) { 616 ASSERT(status == 0); 617 after_work_cb_count++; 618} 619 620 621static void assert_run_work(uv_loop_t* const loop) { 622 uv_work_t work_req; 623 int r; 624 625 ASSERT(work_cb_count == 0); 626 ASSERT(after_work_cb_count == 0); 627 printf("Queue in %d\n", getpid()); 628 r = uv_queue_work(loop, &work_req, work_cb, after_work_cb); 629 ASSERT(r == 0); 630 printf("Running in %d\n", getpid()); 631 uv_run(loop, UV_RUN_DEFAULT); 632 633 ASSERT(work_cb_count == 1); 634 ASSERT(after_work_cb_count == 1); 635 636 /* cleanup */ 637 work_cb_count = 0; 638 after_work_cb_count = 0; 639} 640 641 642#ifndef __MVS__ 643TEST_IMPL(fork_threadpool_queue_work_simple) { 644 /* The threadpool works in a child process. */ 645 646 pid_t child_pid; 647 uv_loop_t loop; 648 649 /* Prime the pool and default loop. */ 650 assert_run_work(uv_default_loop()); 651 652 child_pid = fork(); 653 ASSERT(child_pid != -1); 654 655 if (child_pid != 0) { 656 /* Parent. We can still run work. */ 657 assert_run_work(uv_default_loop()); 658 assert_wait_child(child_pid); 659 } else { 660 /* Child. We can work in a new loop. */ 661 printf("Running child in %d\n", getpid()); 662 uv_loop_init(&loop); 663 printf("Child first watch\n"); 664 assert_run_work(&loop); 665 uv_loop_close(&loop); 666 printf("Child second watch default loop\n"); 667 /* We can work in the default loop. */ 668 ASSERT(0 == uv_loop_fork(uv_default_loop())); 669 assert_run_work(uv_default_loop()); 670 printf("Exiting child \n"); 671 } 672 673 674 MAKE_VALGRIND_HAPPY(); 675 return 0; 676} 677#endif /* !__MVS__ */ 678 679#else 680 681typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ 682 683#endif /* !_WIN32 */ 684