root/fencing/admin.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. try_mainloop_connect
  2. notify_callback
  3. fence_callback
  4. async_fence_helper
  5. mainloop_fencing
  6. handle_level
  7. fence_action_str
  8. print_fence_event
  9. show_history
  10. main

   1 /*
   2  * Copyright (C) 2009 Andrew Beekhof <andrew@beekhof.net>
   3  *
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2 of the License, or (at your option) any later version.
   8  *
   9  * This software is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU General Public
  15  * License along with this library; if not, write to the Free Software
  16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  17  */
  18 
  19 #include <crm_internal.h>
  20 
  21 #include <sys/param.h>
  22 #include <stdio.h>
  23 #include <sys/types.h>
  24 #include <sys/stat.h>
  25 #include <unistd.h>
  26 #include <sys/utsname.h>
  27 
  28 #include <stdlib.h>
  29 #include <string.h>
  30 #include <errno.h>
  31 #include <fcntl.h>
  32 
  33 #include <crm/crm.h>
  34 #include <crm/msg_xml.h>
  35 #include <crm/common/ipc.h>
  36 #include <crm/cluster/internal.h>
  37 #include <crm/common/mainloop.h>
  38 
  39 #include <crm/stonith-ng.h>
  40 #include <crm/cib.h>
  41 #include <crm/pengine/status.h>
  42 
  43 #include <crm/common/xml.h>
  44 
  45 
  46 /* *INDENT-OFF* */
  47 static struct crm_option long_options[] = {
  48     {   "help", no_argument, NULL, '?',
  49         "\tDisplay this text and exit."
  50     },
  51     {   "version", no_argument, NULL, '$',
  52         "\tDisplay version information and exit."
  53     },
  54     {   "verbose", no_argument, NULL, 'V',
  55         "\tIncrease debug output (may be specified multiple times)."
  56     },
  57     {   "quiet", no_argument, NULL, 'q',
  58         "\tBe less descriptive in output."
  59     },
  60 
  61     {   "-spacer-", no_argument, NULL, '-', "\nDevice definition commands:" },
  62 
  63     {   "register", required_argument, NULL, 'R',
  64         "Register the named stonith device. Requires: --agent.\n"
  65         "\t\t\tOptional: any number of --option and/or --env entries."
  66     },
  67     {   "deregister", required_argument, NULL, 'D',
  68         "De-register the named stonith device."
  69     },
  70     {   "register-level", required_argument, NULL, 'r',
  71         "Register a stonith level for the named target,\n"
  72         "\t\t\tspecified as one of NAME, @PATTERN, or ATTR=VALUE.\n"
  73         "\t\t\tRequires: --index and one or more --device entries."
  74     },
  75     {   "deregister-level", required_argument, NULL, 'd',
  76         "Unregister a stonith level for the named target,\n"
  77         "\t\t\tspecified as for --register-level. Requires: --index."
  78     },
  79 
  80     {   "-spacer-", no_argument, NULL, '-', "\nQueries:" },
  81 
  82     {   "list", required_argument, NULL, 'l',
  83         "List devices that can terminate the specified host.\n"
  84         "\t\t\tOptional: --timeout."
  85     },
  86     {   "list-registered", no_argument, NULL, 'L',
  87         "List all registered devices. Optional: --timeout."
  88     },
  89     {   "list-installed", no_argument, NULL, 'I',
  90         "List all installed devices. Optional: --timeout."
  91     },
  92     {   "list-targets", required_argument, NULL, 's',
  93         "List the targets that can be fenced by the\n"
  94         "\t\t\tnamed device. Optional: --timeout."
  95     },
  96     {   "metadata", no_argument, NULL, 'M',
  97         "\tShow agent metadata. Requires: --agent.\n"
  98         "\t\t\tOptional: --timeout."
  99     },
 100     {   "query", required_argument, NULL, 'Q',
 101         "Check the named device's status. Optional: --timeout."
 102     },
 103 
 104     {   "-spacer-", no_argument, NULL, '-', "\nFencing Commands:" },
 105 
 106     {   "fence", required_argument, NULL, 'F',
 107         "Fence named host. Optional: --timeout, --tolerance."
 108     },
 109     {   "unfence", required_argument, NULL, 'U',
 110         "Unfence named host. Optional: --timeout, --tolerance."
 111     },
 112     {   "reboot", required_argument, NULL, 'B',
 113         "Reboot named host. Optional: --timeout, --tolerance."
 114     },
 115     {   "confirm", required_argument, NULL, 'C',
 116         "Tell cluster that named host is now safely down."
 117     },
 118     {   "history", required_argument, NULL, 'H',
 119         "Show last successful fencing operation for named node\n"
 120         "\t\t\t(or '*' for all nodes). Optional: --timeout, --quiet\n"
 121         "\t\t\t(show only the operation's epoch timestamp),\n"
 122         "\t\t\t--verbose (show all recorded and pending operations)."
 123     },
 124     {   "last", required_argument, NULL, 'h',
 125         "Indicate when the named node was last fenced.\n"
 126         "\t\t\tOptional: --as-node-id."
 127     },
 128 
 129     {   "-spacer-", no_argument, NULL, '-', "\nAdditional Options:" },
 130 
 131     {   "agent", required_argument, NULL, 'a',
 132         "The agent to use (for example, fence_xvm;\n"
 133         "\t\t\twith --register, --metadata)."
 134     },
 135     {   "option", required_argument, NULL, 'o',
 136         "Specify a device configuration parameter as NAME=VALUE\n"
 137         "\t\t\t(with --register)."
 138     },
 139     {   "env-option", required_argument, NULL, 'e',
 140         "Specify a device configuration parameter with the\n"
 141         "\t\t\tspecified name, using the value of the\n"
 142         "\t\t\tenvironment variable of the same name prefixed with\n"
 143         "\t\t\tOCF_RESKEY_ (with --register)."
 144     },
 145     {   "tag", required_argument, NULL, 'T',
 146         "Identify fencing operations in logs with the specified\n"
 147         "\t\t\ttag; useful when multiple entities might invoke\n"
 148         "\t\t\tstonith_admin (used with most commands)."
 149     },
 150     {   "device", required_argument, NULL, 'v',
 151         "A device to associate with a given host and\n"
 152         "\t\t\tstonith level (with --register-level)."
 153     },
 154     {   "index", required_argument, NULL, 'i',
 155         "The stonith level (1-9) (with --register-level,\n"
 156         "\t\t\t--deregister-level)."
 157     },
 158     {   "timeout", required_argument, NULL, 't',
 159         "Operation timeout in seconds (default 120;\n"
 160         "\t\t\tused with most commands)."
 161     },
 162     {   "as-node-id", no_argument, NULL, 'n',
 163         "(Advanced) The supplied node is the corosync node ID\n"
 164         "\t\t\t(with --last)."
 165     },
 166     {   "tolerance", required_argument, NULL,   0,
 167         "(Advanced) Do nothing if an equivalent --fence request\n"
 168         "\t\t\tsucceeded less than this many seconds earlier\n"
 169         "\t\t\t(with --fence, --unfence, --reboot)."
 170     },
 171 
 172     {   "list-all", no_argument, NULL, 'L', NULL, pcmk_option_hidden },
 173     { 0, 0, 0, 0 }
 174 };
 175 /* *INDENT-ON* */
 176 
 177 int st_opts = st_opt_sync_call | st_opt_allow_suicide;
 178 
 179 GMainLoop *mainloop = NULL;
 180 struct {
 181     stonith_t *st;
 182     const char *target;
 183     const char *action;
 184     char *name;
 185     int timeout;
 186     int tolerance;
 187     int rc;
 188 } async_fence_data;
 189 
 190 static int
 191 try_mainloop_connect(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 192 {
 193     stonith_t *st = async_fence_data.st;
 194     int tries = 10;
 195     int i = 0;
 196     int rc = 0;
 197 
 198     for (i = 0; i < tries; i++) {
 199         crm_debug("Connecting as %s", async_fence_data.name);
 200         rc = st->cmds->connect(st, async_fence_data.name, NULL);
 201 
 202         if (!rc) {
 203             crm_debug("stonith client connection established");
 204             return 0;
 205         } else {
 206             crm_debug("stonith client connection failed");
 207         }
 208         sleep(1);
 209     }
 210 
 211     crm_err("Could not connect to stonithd.");
 212     return -1;
 213 }
 214 
 215 static void
 216 notify_callback(stonith_t * st, stonith_event_t * e)
     /* [previous][next][first][last][top][bottom][index][help] */
 217 {
 218     if (e->result != pcmk_ok) {
 219         return;
 220     }
 221 
 222     if (safe_str_eq(async_fence_data.target, e->target) &&
 223         safe_str_eq(async_fence_data.action, e->action)) {
 224 
 225         async_fence_data.rc = e->result;
 226         g_main_loop_quit(mainloop);
 227     }
 228 }
 229 
 230 static void
 231 fence_callback(stonith_t * stonith, stonith_callback_data_t * data)
     /* [previous][next][first][last][top][bottom][index][help] */
 232 {
 233     async_fence_data.rc = data->rc;
 234 
 235     g_main_loop_quit(mainloop);
 236 }
 237 
 238 static gboolean
 239 async_fence_helper(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 240 {
 241     stonith_t *st = async_fence_data.st;
 242     int call_id = 0;
 243 
 244     if (try_mainloop_connect()) {
 245         g_main_loop_quit(mainloop);
 246         return TRUE;
 247     }
 248 
 249     st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, notify_callback);
 250 
 251     call_id = st->cmds->fence(st,
 252                               st_opt_allow_suicide,
 253                               async_fence_data.target,
 254                               async_fence_data.action,
 255                               async_fence_data.timeout, async_fence_data.tolerance);
 256 
 257     if (call_id < 0) {
 258         g_main_loop_quit(mainloop);
 259         return TRUE;
 260     }
 261 
 262     st->cmds->register_callback(st,
 263                                 call_id,
 264                                 async_fence_data.timeout,
 265                                 st_opt_timeout_updates, NULL, "callback", fence_callback);
 266 
 267     return TRUE;
 268 }
 269 
 270 static int
 271 mainloop_fencing(stonith_t * st, const char *target, const char *action, int timeout, int tolerance)
     /* [previous][next][first][last][top][bottom][index][help] */
 272 {
 273     crm_trigger_t *trig;
 274 
 275     async_fence_data.st = st;
 276     async_fence_data.target = target;
 277     async_fence_data.action = action;
 278     async_fence_data.timeout = timeout;
 279     async_fence_data.tolerance = tolerance;
 280     async_fence_data.rc = -1;
 281 
 282     trig = mainloop_add_trigger(G_PRIORITY_HIGH, async_fence_helper, NULL);
 283     mainloop_set_trigger(trig);
 284 
 285     mainloop = g_main_new(FALSE);
 286     g_main_run(mainloop);
 287 
 288     return async_fence_data.rc;
 289 }
 290 
 291 static int
 292 handle_level(stonith_t *st, char *target, int fence_level,
     /* [previous][next][first][last][top][bottom][index][help] */
 293              stonith_key_value_t *devices, bool added)
 294 {
 295     char *node = NULL;
 296     char *pattern = NULL;
 297     char *name = NULL;
 298     char *value = strchr(target, '=');
 299 
 300     /* Determine if targeting by attribute, node name pattern or node name */
 301     if (value != NULL)  {
 302         name = target;
 303         *value++ = '\0';
 304     } else if (*target == '@') {
 305         pattern = target + 1;
 306     } else {
 307         node = target;
 308     }
 309 
 310     /* Register or unregister level as appropriate */
 311     if (added) {
 312         return st->cmds->register_level_full(st, st_opts, node, pattern,
 313                                              name, value, fence_level,
 314                                              devices);
 315     }
 316     return st->cmds->remove_level_full(st, st_opts, node, pattern,
 317                                        name, value, fence_level);
 318 }
 319 
 320 static char *
 321 fence_action_str(const char *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 322 {
 323     char *str = NULL;
 324 
 325     if (action == NULL) {
 326         str = strdup("unknown");
 327     } else if (action[0] == 'o') { // on, off
 328         str = crm_concat("turn", action, ' ');
 329     } else {
 330         str = strdup(action);
 331     }
 332     return str;
 333 }
 334 
 335 static void
 336 print_fence_event(stonith_history_t *event)
     /* [previous][next][first][last][top][bottom][index][help] */
 337 {
 338     char *action_s = fence_action_str(event->action);
 339     time_t complete = event->completed;
 340 
 341     printf("%s was able to %s node %s on behalf of %s from %s at %s\n",
 342            (event->delegate? event->delegate : "This node"), action_s,
 343            event->target, event->client, event->origin, ctime(&complete));
 344     free(action_s);
 345 }
 346 
 347 static int
 348 show_history(stonith_t *st, const char *target, int timeout, int quiet,
     /* [previous][next][first][last][top][bottom][index][help] */
 349              int verbose)
 350 {
 351     stonith_history_t *history, *hp, *latest = NULL;
 352     int rc = 0;
 353 
 354     rc = st->cmds->history(st, st_opts,
 355                            (safe_str_eq(target, "*")? NULL : target),
 356                            &history, timeout);
 357     for (hp = history; hp; hp = hp->next) {
 358         char *action_s = NULL;
 359         time_t complete = hp->completed;
 360 
 361         if (hp->state == st_done) {
 362             latest = hp;
 363         }
 364 
 365         if (quiet || !verbose) {
 366             continue;
 367         }
 368 
 369         if (hp->state == st_failed) {
 370             action_s = fence_action_str(hp->action);
 371             printf("%s failed to %s node %s on behalf of %s from %s at %s\n",
 372                    hp->delegate ? hp->delegate : "We", action_s, hp->target,
 373                    hp->client, hp->origin, ctime(&complete));
 374 
 375         } else if (hp->state == st_done) {
 376             print_fence_event(latest);
 377 
 378         } else {
 379             /* ocf:pacemaker:controld depends on "wishes to" being
 380              * in this output, when used with older versions of DLM
 381              * that don't report stateful_merge_wait
 382              */
 383             action_s = fence_action_str(hp->action);
 384             printf("%s at %s wishes to %s node %s - %d %d\n",
 385                    hp->client, hp->origin, action_s, hp->target, hp->state, hp->completed);
 386         }
 387 
 388         free(action_s);
 389     }
 390 
 391     if (latest) {
 392         if (quiet) {
 393             printf("%d\n", latest->completed);
 394         } else if (!verbose) { // already printed if verbose
 395             print_fence_event(latest);
 396         }
 397     }
 398     return rc;
 399 }
 400 
 401 int
 402 main(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 403 {
 404     int flag;
 405     int rc = 0;
 406     int quiet = 0;
 407     int verbose = 0;
 408     int argerr = 0;
 409     int timeout = 120;
 410     int option_index = 0;
 411     int fence_level = 0;
 412     int no_connect = 0;
 413     int tolerance = 0;
 414     int as_nodeid = FALSE;
 415 
 416     char *name = NULL;
 417     char *value = NULL;
 418     char *target = NULL;
 419     char *lists = NULL;
 420     const char *agent = NULL;
 421     const char *device = NULL;
 422     const char *longname = NULL;
 423 
 424     char action = 0;
 425     stonith_t *st = NULL;
 426     stonith_key_value_t *params = NULL;
 427     stonith_key_value_t *devices = NULL;
 428     stonith_key_value_t *dIter = NULL;
 429 
 430     crm_log_cli_init("stonith_admin");
 431     crm_set_options(NULL, "<command> [<options>]", long_options,
 432                     "access the Pacemaker fencing API");
 433 
 434     async_fence_data.name = strdup(crm_system_name);
 435 
 436     while (1) {
 437         flag = crm_get_option_long(argc, argv, &option_index, &longname);
 438         if (flag == -1)
 439             break;
 440 
 441         switch (flag) {
 442             case 'V':
 443                 verbose = 1;
 444                 crm_bump_log_level(argc, argv);
 445                 break;
 446             case '$':
 447             case '?':
 448                 crm_help(flag, EX_OK);
 449                 break;
 450             case 'I':
 451                 no_connect = 1;
 452                 /* fall through */
 453             case 'L':
 454                 action = flag;
 455                 break;
 456             case 'q':
 457                 quiet = 1;
 458                 break;
 459             case 'Q':
 460             case 'R':
 461             case 'D':
 462             case 's':
 463                 action = flag;
 464                 device = optarg;
 465                 break;
 466             case 'T':
 467                 free(async_fence_data.name);
 468                 async_fence_data.name = crm_strdup_printf("%s.%s", crm_system_name, optarg);
 469                 break;
 470             case 'a':
 471                 agent = optarg;
 472                 break;
 473             case 'l':
 474                 target = optarg;
 475                 action = 'L';
 476                 break;
 477             case 'M':
 478                 no_connect = 1;
 479                 action = flag;
 480                 break;
 481             case 't':
 482                 timeout = crm_atoi(optarg, NULL);
 483                 break;
 484             case 'B':
 485             case 'F':
 486             case 'U':
 487                 /* using mainloop here */
 488                 no_connect = 1;
 489                 /* fall through */
 490             case 'C':
 491                 /* Always log the input arguments */
 492                 crm_log_args(argc, argv);
 493                 target = optarg;
 494                 action = flag;
 495                 break;
 496             case 'n':
 497                 as_nodeid = TRUE;
 498                 break;
 499             case 'h':
 500             case 'H':
 501             case 'r':
 502             case 'd':
 503                 target = optarg;
 504                 action = flag;
 505                 break;
 506             case 'i':
 507                 fence_level = crm_atoi(optarg, NULL);
 508                 break;
 509             case 'v':
 510                 devices = stonith_key_value_add(devices, NULL, optarg);
 511                 break;
 512             case 'o':
 513                 crm_info("Scanning: -o %s", optarg);
 514                 rc = sscanf(optarg, "%m[^=]=%m[^=]", &name, &value);
 515                 if (rc != 2) {
 516                     crm_err("Invalid option: -o %s", optarg);
 517                     ++argerr;
 518                 } else {
 519                     crm_info("Got: '%s'='%s'", name, value);
 520                     params = stonith_key_value_add(params, name, value);
 521                 }
 522                 free(value); value = NULL;
 523                 free(name); name = NULL;
 524                 break;
 525             case 'e':
 526                 {
 527                     char *key = crm_concat("OCF_RESKEY", optarg, '_');
 528                     const char *env = getenv(key);
 529 
 530                     if (env == NULL) {
 531                         crm_err("Invalid option: -e %s", optarg);
 532                         ++argerr;
 533                     } else {
 534                         crm_info("Got: '%s'='%s'", optarg, env);
 535                         params = stonith_key_value_add(params, optarg, env);
 536                     }
 537                     free(key);
 538                 }
 539                 break;
 540             case 0:
 541                 if (safe_str_eq("tolerance", longname)) {
 542                     tolerance = crm_get_msec(optarg) / 1000;    /* Send in seconds */
 543                 }
 544                 break;
 545             default:
 546                 ++argerr;
 547                 break;
 548         }
 549     }
 550 
 551     if (optind > argc) {
 552         ++argerr;
 553     }
 554 
 555     if (argerr) {
 556         crm_help('?', EX_USAGE);
 557     }
 558 
 559     st = stonith_api_new();
 560 
 561     if (!no_connect) {
 562         rc = st->cmds->connect(st, async_fence_data.name, NULL);
 563         if (rc < 0) {
 564             goto done;
 565         }
 566     }
 567 
 568     switch (action) {
 569         case 'I':
 570             rc = st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices, timeout);
 571             for (dIter = devices; dIter; dIter = dIter->next) {
 572                 fprintf(stdout, " %s\n", dIter->value);
 573             }
 574             if (rc == 0) {
 575                 fprintf(stderr, "No devices found\n");
 576 
 577             } else if (rc > 0) {
 578                 fprintf(stderr, "%d devices found\n", rc);
 579                 rc = 0;
 580             }
 581             stonith_key_value_freeall(devices, 1, 1);
 582             break;
 583         case 'L':
 584             rc = st->cmds->query(st, st_opts, target, &devices, timeout);
 585             for (dIter = devices; dIter; dIter = dIter->next) {
 586                 fprintf(stdout, " %s\n", dIter->value);
 587             }
 588             if (rc == 0) {
 589                 fprintf(stderr, "No devices found\n");
 590             } else if (rc > 0) {
 591                 fprintf(stderr, "%d devices found\n", rc);
 592                 rc = 0;
 593             }
 594             stonith_key_value_freeall(devices, 1, 1);
 595             break;
 596         case 'Q':
 597             rc = st->cmds->monitor(st, st_opts, device, timeout);
 598             if (rc < 0) {
 599                 rc = st->cmds->list(st, st_opts, device, NULL, timeout);
 600             }
 601             break;
 602         case 's':
 603             rc = st->cmds->list(st, st_opts, device, &lists, timeout);
 604             if (rc == 0) {
 605                 if (lists) {
 606                     char *source = lists, *dest = lists; 
 607 
 608                     while (*dest) {
 609                         if ((*dest == '\\') && (*(dest+1) == 'n')) {
 610                             *source = '\n';
 611                             dest++;
 612                             dest++;
 613                             source++;
 614                         } else if ((*dest == ',') || (*dest == ';')) {
 615                             dest++;
 616                         } else {
 617                             *source = *dest;
 618                             dest++;
 619                             source++;
 620                         }
 621 
 622                         if (!(*dest)) {
 623                             *source = 0;
 624                         }
 625                     }
 626                     fprintf(stdout, "%s", lists);
 627                     free(lists);
 628                 }
 629             } else {
 630                 fprintf(stderr, "List command returned error. rc : %d\n", rc);
 631             }
 632             break;
 633         case 'R':
 634             rc = st->cmds->register_device(st, st_opts, device, "stonith-ng", agent, params);
 635             break;
 636         case 'D':
 637             rc = st->cmds->remove_device(st, st_opts, device);
 638             break;
 639         case 'd':
 640         case 'r':
 641             rc = handle_level(st, target, fence_level, devices, action == 'r');
 642             break;
 643         case 'M':
 644             if (agent == NULL) {
 645                 printf("Please specify an agent to query using -a,--agent [value]\n");
 646                 return -1;
 647             } else {
 648                 char *buffer = NULL;
 649 
 650                 rc = st->cmds->metadata(st, st_opt_sync_call, agent, NULL, &buffer, timeout);
 651                 if (rc == pcmk_ok) {
 652                     printf("%s\n", buffer);
 653                 }
 654                 free(buffer);
 655             }
 656             break;
 657         case 'C':
 658             rc = st->cmds->confirm(st, st_opts, target);
 659             break;
 660         case 'B':
 661             rc = mainloop_fencing(st, target, "reboot", timeout, tolerance);
 662             break;
 663         case 'F':
 664             rc = mainloop_fencing(st, target, "off", timeout, tolerance);
 665             break;
 666         case 'U':
 667             rc = mainloop_fencing(st, target, "on", timeout, tolerance);
 668             break;
 669         case 'h':
 670             {
 671                 time_t when = 0;
 672 
 673                 if(as_nodeid) {
 674                     uint32_t nodeid = atol(target);
 675                     when = stonith_api_time(nodeid, NULL, FALSE);
 676                 } else {
 677                     when = stonith_api_time(0, target, FALSE);
 678                 }
 679                 if(when) {
 680                     printf("Node %s last kicked at: %s\n", target, ctime(&when));
 681                 } else {
 682                     printf("Node %s has never been kicked\n", target);
 683                 }
 684             }
 685             break;
 686         case 'H':
 687             rc = show_history(st, target, timeout, quiet, verbose);
 688             break;
 689     }
 690 
 691   done:
 692     free(async_fence_data.name);
 693     crm_info("Command returned: %s (%d)", pcmk_strerror(rc), rc);
 694 
 695     stonith_key_value_freeall(params, 1, 1);
 696     st->cmds->disconnect(st);
 697     stonith_api_delete(st);
 698 
 699     return rc;
 700 }

/* [previous][next][first][last][top][bottom][index][help] */