1 /* 2 Copyright (C) 2002-2010 Karl J. Runge <runge (at) karlrunge.com> 3 All rights reserved. 4 5 This file is part of x11vnc. 6 7 x11vnc is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 2 of the License, or (at 10 your option) any later version. 11 12 x11vnc is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with x11vnc; if not, write to the Free Software 19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA 20 or see <http://www.gnu.org/licenses/>. 21 22 In addition, as a special exception, Karl J. Runge 23 gives permission to link the code of its release of x11vnc with the 24 OpenSSL project's "OpenSSL" library (or with modified versions of it 25 that use the same license as the "OpenSSL" library), and distribute 26 the linked executables. You must obey the GNU General Public License 27 in all respects for all of the code used other than "OpenSSL". If you 28 modify this file, you may extend this exception to your version of the 29 file, but you are not obligated to do so. If you do not wish to do 30 so, delete this exception statement from your version. 31 */ 32 33 /* -- cleanup.c -- */ 34 35 #include "x11vnc.h" 36 #include "xwrappers.h" 37 #include "xdamage.h" 38 #include "remote.h" 39 #include "keyboard.h" 40 #include "scan.h" 41 #include "gui.h" 42 #include "solid.h" 43 #include "unixpw.h" 44 #include "sslcmds.h" 45 #include "sslhelper.h" 46 #include "connections.h" 47 #include "macosx.h" 48 #include "macosxCG.h" 49 #include "avahi.h" 50 #include "screen.h" 51 #include "xrecord.h" 52 #include "xevents.h" 53 #include "uinput.h" 54 55 /* 56 * Exiting and error handling routines 57 */ 58 59 int trapped_xerror = 0; 60 int trapped_xioerror = 0; 61 int trapped_getimage_xerror = 0; 62 int trapped_record_xerror = 0; 63 XErrorEvent *trapped_xerror_event; 64 65 /* XXX CHECK BEFORE RELEASE */ 66 int crash_debug = 0; 67 68 void clean_shm(int quick); 69 void clean_up_exit(int ret); 70 int trap_xerror(Display *d, XErrorEvent *error); 71 int trap_xioerror(Display *d); 72 int trap_getimage_xerror(Display *d, XErrorEvent *error); 73 char *xerror_string(XErrorEvent *error); 74 void initialize_crash_handler(void); 75 void initialize_signals(void); 76 void unset_signals(void); 77 void close_exec_fds(void); 78 int known_sigpipe_mode(char *s); 79 80 81 static int exit_flag = 0; 82 static int exit_sig = 0; 83 84 static void clean_icon_mode(void); 85 static int Xerror(Display *d, XErrorEvent *error); 86 static int XIOerr(Display *d); 87 static void crash_shell_help(void); 88 static void crash_shell(void); 89 static void interrupted (int sig); 90 91 92 void clean_shm(int quick) { 93 int i, cnt = 0; 94 95 /* 96 * to avoid deadlock, etc, under quick=1 we just delete the shm 97 * areas and leave the X stuff hanging. 98 */ 99 if (quick) { 100 shm_delete(&scanline_shm); 101 shm_delete(&fullscreen_shm); 102 shm_delete(&snaprect_shm); 103 } else { 104 shm_clean(&scanline_shm, scanline); 105 shm_clean(&fullscreen_shm, fullscreen); 106 shm_clean(&snaprect_shm, snaprect); 107 } 108 109 /* 110 * Here we have to clean up quite a few shm areas for all 111 * the possible tile row runs (40 for 1280), not as robust 112 * as one might like... sometimes need to run ipcrm(1). 113 */ 114 for(i=1; i<=ntiles_x; i++) { 115 if (i > tile_shm_count) { 116 break; 117 } 118 if (quick) { 119 shm_delete(&tile_row_shm[i]); 120 } else { 121 shm_clean(&tile_row_shm[i], tile_row[i]); 122 } 123 cnt++; 124 if (single_copytile_count && i >= single_copytile_count) { 125 break; 126 } 127 } 128 if (!quiet && cnt > 0) { 129 rfbLog("deleted %d tile_row polling images.\n", cnt); 130 } 131 } 132 133 static void clean_icon_mode(void) { 134 if (icon_mode && icon_mode_fh) { 135 fprintf(icon_mode_fh, "quit\n"); 136 fflush(icon_mode_fh); 137 fclose(icon_mode_fh); 138 icon_mode_fh = NULL; 139 if (icon_mode_file) { 140 unlink(icon_mode_file); 141 icon_mode_file = NULL; 142 } 143 } 144 } 145 146 /* 147 * Normal exiting 148 */ 149 void clean_up_exit(int ret) { 150 static int depth = 0; 151 exit_flag = 1; 152 153 if (depth++ > 2) { 154 exit(ret); 155 } 156 157 if (icon_mode) { 158 clean_icon_mode(); 159 } 160 161 /* remove the shm areas: */ 162 clean_shm(0); 163 164 stop_stunnel(); 165 if (use_openssl) { 166 ssl_helper_pid(0, 0); /* killall */ 167 } 168 169 if (ssh_pid > 0) { 170 kill(ssh_pid, SIGTERM); 171 ssh_pid = 0; 172 } 173 174 #ifdef MACOSX 175 if (client_connect_file) { 176 if (strstr(client_connect_file, "/tmp/x11vnc-macosx-remote") 177 == client_connect_file) { 178 unlink(client_connect_file); 179 } 180 } 181 if (macosx_console) { 182 macosxCG_fini(); 183 } 184 #endif 185 186 if (pipeinput_fh != NULL) { 187 pclose(pipeinput_fh); 188 pipeinput_fh = NULL; 189 } 190 191 shutdown_uinput(); 192 193 if (unix_sock) { 194 if (unix_sock_fd >= 0) { 195 rfbLog("deleting unix sock: %s\n", unix_sock); 196 close(unix_sock_fd); 197 unix_sock_fd = -1; 198 unlink(unix_sock); 199 } 200 } 201 202 if (! dpy) { /* raw_rb hack */ 203 if (rm_flagfile) { 204 unlink(rm_flagfile); 205 rm_flagfile = NULL; 206 } 207 exit(ret); 208 } 209 210 /* X keyboard cleanups */ 211 delete_added_keycodes(0); 212 213 if (clear_mods == 1) { 214 clear_modifiers(0); 215 } else if (clear_mods == 2) { 216 clear_keys(); 217 } else if (clear_mods == 3) { 218 clear_keys(); 219 clear_locks(); 220 } 221 222 if (no_autorepeat) { 223 autorepeat(1, 0); 224 } 225 if (use_solid_bg) { 226 solid_bg(1); 227 } 228 if (ncache || ncache0) { 229 kde_no_animate(1); 230 } 231 X_LOCK; 232 XTestDiscard_wr(dpy); 233 #if LIBVNCSERVER_HAVE_LIBXDAMAGE 234 if (xdamage) { 235 XDamageDestroy(dpy, xdamage); 236 } 237 #endif 238 #if LIBVNCSERVER_HAVE_LIBXTRAP 239 if (trap_ctx) { 240 XEFreeTC(trap_ctx); 241 } 242 #endif 243 /* XXX rdpy_ctrl, etc. cannot close w/o blocking */ 244 XCloseDisplay_wr(dpy); 245 X_UNLOCK; 246 247 fflush(stderr); 248 249 if (rm_flagfile) { 250 unlink(rm_flagfile); 251 rm_flagfile = NULL; 252 } 253 254 if (avahi) { 255 avahi_cleanup(); 256 fflush(stderr); 257 } 258 259 exit(ret); 260 } 261 262 /* X11 error handlers */ 263 264 static XErrorHandler Xerror_def; 265 static XIOErrorHandler XIOerr_def; 266 267 int trap_xerror(Display *d, XErrorEvent *error) { 268 trapped_xerror = 1; 269 trapped_xerror_event = error; 270 271 if (d) {} /* unused vars warning: */ 272 273 return 0; 274 } 275 276 int trap_xioerror(Display *d) { 277 trapped_xioerror = 1; 278 279 if (d) {} /* unused vars warning: */ 280 281 return 0; 282 } 283 284 int trap_getimage_xerror(Display *d, XErrorEvent *error) { 285 trapped_getimage_xerror = 1; 286 trapped_xerror_event = error; 287 288 if (d) {} /* unused vars warning: */ 289 290 return 0; 291 } 292 293 /* Are silly Xorg people removing X_ShmAttach from XShm.h? */ 294 /* INDEED! What stupid, myopic morons... */ 295 /* Maintenance Monkeys busy typing at their keyboards... */ 296 #ifndef X_ShmAttach 297 #define X_ShmAttach 1 298 #endif 299 300 static int Xerror(Display *d, XErrorEvent *error) { 301 X_UNLOCK; 302 303 if (getenv("X11VNC_PRINT_XERROR")) { 304 fprintf(stderr, "Xerror: major_opcode: %d minor_opcode: %d error_code: %d\n", 305 error->request_code, error->minor_code, error->error_code); 306 } 307 308 if (xshm_opcode > 0 && error->request_code == xshm_opcode) { 309 if (error->minor_code == X_ShmAttach) { 310 char *dstr = DisplayString(dpy); 311 fprintf(stderr, "\nX11 MIT Shared Memory Attach failed:\n"); 312 fprintf(stderr, " Is your DISPLAY=%s on a remote machine?\n", dstr); 313 if (strstr(dstr, "localhost:")) { 314 fprintf(stderr, " Note: DISPLAY=localhost:N suggests a SSH X11 redir to a remote machine.\n"); 315 } else if (dstr[0] != ':') { 316 fprintf(stderr, " Note: DISPLAY=hostname:N suggests a remote display.\n"); 317 } 318 fprintf(stderr, " Suggestion, use: x11vnc -display :0 ... for local display :0\n\n"); 319 } 320 } 321 322 interrupted(0); 323 324 if (d) {} /* unused vars warning: */ 325 326 return (*Xerror_def)(d, error); 327 } 328 329 void watch_loop(void); 330 331 static int XIOerr(Display *d) { 332 static int reopen = 0, rmax = 1; 333 X_UNLOCK; 334 335 if (getenv("X11VNC_REOPEN_DISPLAY")) { 336 rmax = atoi(getenv("X11VNC_REOPEN_DISPLAY")); 337 } 338 339 #if !NO_X11 340 if (reopen < rmax && getenv("X11VNC_REOPEN_DISPLAY")) { 341 int db = getenv("X11VNC_REOPEN_DEBUG") ? 1 : 0; 342 int sleepmax = 10, i; 343 Display *save_dpy = dpy; 344 char *dstr = strdup(DisplayString(save_dpy)); 345 reopen++; 346 if (getenv("X11VNC_REOPEN_SLEEP_MAX")) { 347 sleepmax = atoi(getenv("X11VNC_REOPEN_SLEEP_MAX")); 348 } 349 rfbLog("*** XIO error: Trying to reopen[%d/%d] display '%s'\n", reopen, rmax, dstr); 350 rfbLog("*** XIO error: Note the reopened state may be unstable.\n"); 351 for (i=0; i < sleepmax; i++) { 352 usleep (1000 * 1000); 353 dpy = XOpenDisplay_wr(dstr); 354 rfbLog("dpy[%d/%d]: %p\n", i+1, sleepmax, dpy); 355 if (dpy) { 356 break; 357 } 358 } 359 last_open_xdisplay = time(NULL); 360 if (dpy) { 361 rfbLog("*** XIO error: Reopened display '%s' successfully.\n", dstr); 362 if (db) rfbLog("*** XIO error: '%s' 0x%x\n", dstr, dpy); 363 scr = DefaultScreen(dpy); 364 rootwin = RootWindow(dpy, scr); 365 if (db) rfbLog("*** XIO error: disable_grabserver\n"); 366 disable_grabserver(dpy, 0); 367 if (db) rfbLog("*** XIO error: xrecord\n"); 368 zerodisp_xrecord(); 369 initialize_xrecord(); 370 if (db) rfbLog("*** XIO error: xdamage\n"); 371 create_xdamage_if_needed(1); 372 if (db) rfbLog("*** XIO error: do_new_fb\n"); 373 if (using_shm) { 374 if (db) rfbLog("*** XIO error: clean_shm\n"); 375 clean_shm(1); 376 } 377 do_new_fb(1); 378 if (db) rfbLog("*** XIO error: check_xevents\n"); 379 check_xevents(1); 380 381 /* sadly, we can never return... */ 382 if (db) rfbLog("*** XIO error: watch_loop\n"); 383 watch_loop(); 384 clean_up_exit(1); 385 } 386 } 387 #endif 388 389 interrupted(-1); 390 391 if (d) {} /* unused vars warning: */ 392 393 return (*XIOerr_def)(d); 394 } 395 396 static char *xerrors[] = { 397 "Success", 398 "BadRequest", 399 "BadValue", 400 "BadWindow", 401 "BadPixmap", 402 "BadAtom", 403 "BadCursor", 404 "BadFont", 405 "BadMatch", 406 "BadDrawable", 407 "BadAccess", 408 "BadAlloc", 409 "BadColor", 410 "BadGC", 411 "BadIDChoice", 412 "BadName", 413 "BadLength", 414 "BadImplementation", 415 "unknown" 416 }; 417 static int xerrors_max = BadImplementation; 418 419 char *xerror_string(XErrorEvent *error) { 420 int index = -1; 421 if (error) { 422 index = (int) error->error_code; 423 } 424 if (0 <= index && index <= xerrors_max) { 425 return xerrors[index]; 426 } else { 427 return xerrors[xerrors_max+1]; 428 } 429 } 430 431 static char *crash_stack_command1 = NULL; 432 static char *crash_stack_command2 = NULL; 433 static char *crash_debug_command = NULL; 434 435 void initialize_crash_handler(void) { 436 int pid = program_pid; 437 crash_stack_command1 = (char *) malloc(1000); 438 crash_stack_command2 = (char *) malloc(1000); 439 crash_debug_command = (char *) malloc(1000); 440 441 snprintf(crash_stack_command1, 500, "echo where > /tmp/gdb.%d;" 442 " env PATH=$PATH:/usr/local/bin:/usr/sfw/bin:/usr/bin" 443 " gdb -x /tmp/gdb.%d -batch -n %s %d;" 444 " rm -f /tmp/gdb.%d", pid, pid, program_name, pid, pid); 445 snprintf(crash_stack_command2, 500, "pstack %d", program_pid); 446 447 snprintf(crash_debug_command, 500, "gdb %s %d", program_name, pid); 448 } 449 450 static void crash_shell_help(void) { 451 int pid = program_pid; 452 fprintf(stderr, "\n"); 453 fprintf(stderr, " *** Welcome to the x11vnc crash shell! ***\n"); 454 fprintf(stderr, "\n"); 455 fprintf(stderr, "PROGRAM: %s PID: %d\n", program_name, pid); 456 fprintf(stderr, "\n"); 457 fprintf(stderr, "POSSIBLE DEBUGGER COMMAND:\n"); 458 fprintf(stderr, "\n"); 459 fprintf(stderr, " %s\n", crash_debug_command); 460 fprintf(stderr, "\n"); 461 fprintf(stderr, "Press \"q\" to quit.\n"); 462 fprintf(stderr, "Press \"h\" or \"?\" for this help.\n"); 463 fprintf(stderr, "Press \"s\" to try to run some commands to" 464 " show a stack trace (gdb/pstack).\n"); 465 fprintf(stderr, "\n"); 466 fprintf(stderr, "Anything else is passed to -Q query function.\n"); 467 fprintf(stderr, "\n"); 468 } 469 470 static void crash_shell(void) { 471 char qry[1000], cmd[1000], line[1000]; 472 char *str, *p; 473 474 crash_shell_help(); 475 fprintf(stderr, "\ncrash> "); 476 while (fgets(line, 1000, stdin) != NULL) { 477 str = lblanks(line); 478 479 p = str; 480 while(*p) { 481 if (*p == '\n') { 482 *p = '\0'; 483 } 484 p++; 485 } 486 487 if (*str == 'q' && *(str+1) == '\0') { 488 fprintf(stderr, "quiting.\n"); 489 return; 490 } else if (*str == 'h' && *(str+1) == '\0') { 491 crash_shell_help(); 492 } else if (*str == '?' && *(str+1) == '\0') { 493 crash_shell_help(); 494 } else if (*str == 's' && *(str+1) == '\0') { 495 sprintf(cmd, "sh -c '(%s) &'", crash_stack_command1); 496 /* crash */ 497 if (no_external_cmds || !cmd_ok("crash")) { 498 fprintf(stderr, "\nno_external_cmds=%d\n", 499 no_external_cmds); 500 goto crash_prompt; 501 } 502 fprintf(stderr, "\nrunning:\n\t%s\n\n", 503 crash_stack_command1); 504 system(cmd); 505 usleep(1000*1000); 506 507 sprintf(cmd, "sh -c '(%s) &'", crash_stack_command2); 508 fprintf(stderr, "\nrunning:\n\t%s\n\n", 509 crash_stack_command2); 510 system(cmd); 511 usleep(1000*1000); 512 } else { 513 snprintf(qry, 1000, "qry=%s", str); 514 p = process_remote_cmd(qry, 1); 515 fprintf(stderr, "\n\nresult:\n%s\n", p); 516 free(p); 517 } 518 519 crash_prompt: 520 fprintf(stderr, "crash> "); 521 } 522 } 523 524 /* 525 * General problem handler 526 */ 527 static void interrupted (int sig) { 528 exit_sig = sig; 529 if (exit_flag) { 530 fprintf(stderr, "extra[%d] signal: %d\n", exit_flag, sig); 531 exit_flag++; 532 if (use_threads) { 533 usleep2(250 * 1000); 534 } else if (exit_flag <= 2) { 535 return; 536 } 537 if (rm_flagfile) { 538 unlink(rm_flagfile); 539 rm_flagfile = NULL; 540 } 541 exit(4); 542 } 543 exit_flag++; 544 if (sig == 0) { 545 fprintf(stderr, "caught X11 error:\n"); 546 if (crash_debug) { crash_shell(); } 547 } else if (sig == -1) { 548 fprintf(stderr, "caught XIO error:\n"); 549 } else { 550 fprintf(stderr, "caught signal: %d\n", sig); 551 } 552 if (sig == SIGINT) { 553 shut_down = 1; 554 return; 555 } 556 557 if (crash_debug) { 558 crash_shell(); 559 } 560 561 X_UNLOCK; 562 563 if (icon_mode) { 564 clean_icon_mode(); 565 } 566 /* remove the shm areas with quick=1: */ 567 clean_shm(1); 568 569 if (sig == -1) { 570 /* not worth trying any more cleanup, X server probably gone */ 571 if (rm_flagfile) { 572 unlink(rm_flagfile); 573 rm_flagfile = NULL; 574 } 575 exit(3); 576 } 577 578 /* X keyboard cleanups */ 579 delete_added_keycodes(0); 580 581 if (clear_mods == 1) { 582 clear_modifiers(0); 583 } else if (clear_mods == 2) { 584 clear_keys(); 585 } else if (clear_mods == 3) { 586 clear_keys(); 587 clear_locks(); 588 } 589 if (no_autorepeat) { 590 autorepeat(1, 0); 591 } 592 if (use_solid_bg) { 593 solid_bg(1); 594 } 595 if (ncache || ncache0) { 596 kde_no_animate(1); 597 } 598 stop_stunnel(); 599 600 if (crash_debug) { 601 crash_shell(); 602 } 603 604 if (sig) { 605 if (rm_flagfile) { 606 unlink(rm_flagfile); 607 rm_flagfile = NULL; 608 } 609 exit(2); 610 } 611 } 612 613 static void ignore_sigs(char *list) { 614 char *str, *p; 615 int ignore = 1; 616 if (list == NULL || *list == '\0') { 617 return; 618 } 619 str = strdup(list); 620 p = strtok(str, ":,"); 621 622 #define SETSIG(x, y) \ 623 if (strstr(p, x)) { \ 624 if (ignore) { \ 625 signal(y, SIG_IGN); \ 626 } else { \ 627 signal(y, interrupted); \ 628 } \ 629 } 630 631 #ifdef SIG_IGN 632 while (p) { 633 if (!strcmp(p, "ignore")) { 634 ignore = 1; 635 } else if (!strcmp(p, "exit")) { 636 ignore = 0; 637 } 638 /* Take off every 'sig' ;-) */ 639 #ifdef SIGHUP 640 SETSIG("HUP", SIGHUP); 641 #endif 642 #ifdef SIGINT 643 SETSIG("INT", SIGINT); 644 #endif 645 #ifdef SIGQUIT 646 SETSIG("QUIT", SIGQUIT); 647 #endif 648 #ifdef SIGTRAP 649 SETSIG("TRAP", SIGTRAP); 650 #endif 651 #ifdef SIGABRT 652 SETSIG("ABRT", SIGABRT); 653 #endif 654 #ifdef SIGBUS 655 SETSIG("BUS", SIGBUS); 656 #endif 657 #ifdef SIGFPE 658 SETSIG("FPE", SIGFPE); 659 #endif 660 #ifdef SIGSEGV 661 SETSIG("SEGV", SIGSEGV); 662 #endif 663 #ifdef SIGPIPE 664 SETSIG("PIPE", SIGPIPE); 665 #endif 666 #ifdef SIGTERM 667 SETSIG("TERM", SIGTERM); 668 #endif 669 #ifdef SIGUSR1 670 SETSIG("USR1", SIGUSR1); 671 #endif 672 #ifdef SIGUSR2 673 SETSIG("USR2", SIGUSR2); 674 #endif 675 #ifdef SIGCONT 676 SETSIG("CONT", SIGCONT); 677 #endif 678 #ifdef SIGSTOP 679 SETSIG("STOP", SIGSTOP); 680 #endif 681 #ifdef SIGTSTP 682 SETSIG("TSTP", SIGTSTP); 683 #endif 684 p = strtok(NULL, ":,"); 685 } 686 #endif /* SIG_IGN */ 687 free(str); 688 } 689 690 /* signal handlers */ 691 void initialize_signals(void) { 692 signal(SIGHUP, interrupted); 693 signal(SIGINT, interrupted); 694 signal(SIGQUIT, interrupted); 695 signal(SIGABRT, interrupted); 696 signal(SIGTERM, interrupted); 697 signal(SIGBUS, interrupted); 698 signal(SIGSEGV, interrupted); 699 signal(SIGFPE, interrupted); 700 701 if (!sigpipe || *sigpipe == '\0' || !strcmp(sigpipe, "skip")) { 702 ; 703 } else if (strstr(sigpipe, "ignore:") == sigpipe) { 704 ignore_sigs(sigpipe); 705 } else if (strstr(sigpipe, "exit:") == sigpipe) { 706 ignore_sigs(sigpipe); 707 } else if (!strcmp(sigpipe, "ignore")) { 708 #ifdef SIG_IGN 709 signal(SIGPIPE, SIG_IGN); 710 #endif 711 } else if (!strcmp(sigpipe, "exit")) { 712 rfbLog("initialize_signals: will exit on SIGPIPE\n"); 713 signal(SIGPIPE, interrupted); 714 } 715 716 #if NO_X11 717 return; 718 #else 719 X_LOCK; 720 Xerror_def = XSetErrorHandler(Xerror); 721 XIOerr_def = XSetIOErrorHandler(XIOerr); 722 X_UNLOCK; 723 #endif /* NO_X11 */ 724 } 725 726 void unset_signals(void) { 727 signal(SIGHUP, SIG_DFL); 728 signal(SIGINT, SIG_DFL); 729 signal(SIGQUIT, SIG_DFL); 730 signal(SIGABRT, SIG_DFL); 731 signal(SIGTERM, SIG_DFL); 732 signal(SIGBUS, SIG_DFL); 733 signal(SIGSEGV, SIG_DFL); 734 signal(SIGFPE, SIG_DFL); 735 signal(SIGPIPE, SIG_DFL); 736 } 737 738 void close_exec_fds(void) { 739 int fd; 740 #ifdef FD_CLOEXEC 741 for (fd = 3; fd < 64; fd++) { 742 int flags = fcntl(fd, F_GETFD); 743 if (flags != -1) { 744 flags |= FD_CLOEXEC; 745 fcntl(fd, F_SETFD, flags); 746 } 747 } 748 #endif 749 } 750 751 int known_sigpipe_mode(char *s) { 752 /* 753 * skip, ignore, exit 754 */ 755 if (strstr(s, "ignore:") == s) { 756 return 1; 757 } 758 if (strstr(s, "exit:") == s) { 759 return 1; 760 } 761 if (strcmp(s, "skip") && strcmp(s, "ignore") && 762 strcmp(s, "exit")) { 763 return 0; 764 } else { 765 return 1; 766 } 767 } 768 769 770