root/tools/crm_resource_runtime.c

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

DEFINITIONS

This source file includes following definitions.
  1. do_find_resource
  2. cli_resource_search
  3. find_resource_attr
  4. find_matching_attr_resource
  5. cli_resource_update_attribute
  6. cli_resource_delete_attribute
  7. send_lrm_rsc_op
  8. rsc_fail_name
  9. cli_resource_delete
  10. cli_resource_check
  11. cli_resource_fail
  12. generate_resource_params
  13. resource_is_running_on
  14. get_active_resources
  15. subtract_lists
  16. dump_list
  17. display_list
  18. update_working_set_xml
  19. update_working_set_from_cib
  20. update_dataset
  21. max_delay_for_resource
  22. max_delay_in
  23. cli_resource_restart
  24. actions_are_pending
  25. print_pending_actions
  26. wait_till_stable
  27. cli_resource_execute
  28. cli_resource_move
  29. cli_resource_why_without_rsc_and_host
  30. cli_resource_why_with_rsc_and_host
  31. cli_resource_why_without_rsc_with_host
  32. cli_resource_why_with_rsc_without_host
  33. cli_resource_why

   1 
   2 /*
   3  * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
   4  *
   5  * This program is free software; you can redistribute it and/or
   6  * modify it under the terms of the GNU General Public
   7  * License as published by the Free Software Foundation; either
   8  * version 2 of the License, or (at your option) any later version.
   9  *
  10  * This software is distributed in the hope that it will be useful,
  11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13  * General Public License for more details.
  14  *
  15  * You should have received a copy of the GNU General Public
  16  * License along with this library; if not, write to the Free Software
  17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18  */
  19 
  20 #include <crm_resource.h>
  21 
  22 int resource_verbose = 0;
  23 bool do_force = FALSE;
  24 int crmd_replies_needed = 1; /* The welcome message */
  25 
  26 const char *attr_set_type = XML_TAG_ATTR_SETS;
  27 
  28 static int
  29 do_find_resource(const char *rsc, resource_t * the_rsc, pe_working_set_t * data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
  30 {
  31     int found = 0;
  32     GListPtr lpc = NULL;
  33 
  34     for (lpc = the_rsc->running_on; lpc != NULL; lpc = lpc->next) {
  35         node_t *node = (node_t *) lpc->data;
  36 
  37         if (BE_QUIET) {
  38             fprintf(stdout, "%s\n", node->details->uname);
  39         } else {
  40             const char *state = "";
  41 
  42             if (!pe_rsc_is_clone(the_rsc) && the_rsc->fns->state(the_rsc, TRUE) == RSC_ROLE_MASTER) {
  43                 state = "Master";
  44             }
  45             fprintf(stdout, "resource %s is running on: %s %s\n", rsc, node->details->uname, state);
  46         }
  47 
  48         found++;
  49     }
  50 
  51     if (BE_QUIET == FALSE && found == 0) {
  52         fprintf(stderr, "resource %s is NOT running\n", rsc);
  53     }
  54 
  55     return found;
  56 }
  57 
  58 int
  59 cli_resource_search(resource_t *rsc, const char *requested_name,
     /* [previous][next][first][last][top][bottom][index][help] */
  60                     pe_working_set_t *data_set)
  61 {
  62     int found = 0;
  63     resource_t *parent = uber_parent(rsc);
  64 
  65     if (pe_rsc_is_clone(rsc)) {
  66         for (GListPtr iter = rsc->children; iter != NULL; iter = iter->next) {
  67             found += do_find_resource(requested_name, iter->data, data_set);
  68         }
  69 
  70     /* The anonymous clone children's common ID is supplied */
  71     } else if (pe_rsc_is_clone(parent)
  72                && is_not_set(rsc->flags, pe_rsc_unique)
  73                && rsc->clone_name
  74                && safe_str_eq(requested_name, rsc->clone_name)
  75                && safe_str_neq(requested_name, rsc->id)) {
  76 
  77         for (GListPtr iter = parent->children; iter; iter = iter->next) {
  78             found += do_find_resource(requested_name, iter->data, data_set);
  79         }
  80 
  81     } else {
  82         found += do_find_resource(requested_name, rsc, data_set);
  83     }
  84 
  85     return found;
  86 }
  87 
  88 #define XPATH_MAX 1024
  89 
  90 static int
  91 find_resource_attr(cib_t * the_cib, const char *attr, const char *rsc, const char *set_type,
     /* [previous][next][first][last][top][bottom][index][help] */
  92                    const char *set_name, const char *attr_id, const char *attr_name, char **value)
  93 {
  94     int offset = 0;
  95     int rc = pcmk_ok;
  96     xmlNode *xml_search = NULL;
  97     char *xpath_string = NULL;
  98 
  99     if(value) {
 100         *value = NULL;
 101     }
 102 
 103     if(the_cib == NULL) {
 104         return -ENOTCONN;
 105     }
 106 
 107     xpath_string = calloc(1, XPATH_MAX);
 108     offset +=
 109         snprintf(xpath_string + offset, XPATH_MAX - offset, "%s", get_object_path("resources"));
 110 
 111     offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//*[@id=\"%s\"]", rsc);
 112 
 113     if (set_type) {
 114         offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s", set_type);
 115         if (set_name) {
 116             offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "[@id=\"%s\"]", set_name);
 117         }
 118     }
 119 
 120     offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//nvpair[");
 121     if (attr_id) {
 122         offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@id=\"%s\"", attr_id);
 123     }
 124 
 125     if (attr_name) {
 126         if (attr_id) {
 127             offset += snprintf(xpath_string + offset, XPATH_MAX - offset, " and ");
 128         }
 129         offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@name=\"%s\"", attr_name);
 130     }
 131     offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "]");
 132     CRM_LOG_ASSERT(offset > 0);
 133 
 134     rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search,
 135                               cib_sync_call | cib_scope_local | cib_xpath);
 136 
 137     if (rc != pcmk_ok) {
 138         goto bail;
 139     }
 140 
 141     crm_log_xml_debug(xml_search, "Match");
 142     if (xml_has_children(xml_search)) {
 143         xmlNode *child = NULL;
 144 
 145         rc = -EINVAL;
 146         printf("Multiple attributes match name=%s\n", attr_name);
 147 
 148         for (child = __xml_first_child(xml_search); child != NULL; child = __xml_next(child)) {
 149             printf("  Value: %s \t(id=%s)\n",
 150                    crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child));
 151         }
 152 
 153     } else if(value) {
 154         const char *tmp = crm_element_value(xml_search, attr);
 155 
 156         if (tmp) {
 157             *value = strdup(tmp);
 158         }
 159     }
 160 
 161   bail:
 162     free(xpath_string);
 163     free_xml(xml_search);
 164     return rc;
 165 }
 166 
 167 static resource_t *
 168 find_matching_attr_resource(resource_t * rsc, const char * rsc_id, const char * attr_set, const char * attr_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 169                             const char * attr_name, cib_t * cib, const char * cmd)
 170 {
 171     int rc = pcmk_ok;
 172     char *lookup_id = NULL;
 173     char *local_attr_id = NULL;
 174 
 175     if(do_force == TRUE) {
 176         return rsc;
 177 
 178     } else if(rsc->parent) {
 179         switch(rsc->parent->variant) {
 180             case pe_group:
 181                 if (BE_QUIET == FALSE) {
 182                     printf("Performing %s of '%s' for '%s' will not apply to its peers in '%s'\n", cmd, attr_name, rsc_id, rsc->parent->id);
 183                 }
 184                 break;
 185             case pe_master:
 186             case pe_clone:
 187 
 188                 rc = find_resource_attr(cib, XML_ATTR_ID, rsc_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id);
 189                 free(local_attr_id);
 190 
 191                 if(rc != pcmk_ok) {
 192                     rsc = rsc->parent;
 193                     if (BE_QUIET == FALSE) {
 194                         printf("Performing %s of '%s' on '%s', the parent of '%s'\n", cmd, attr_name, rsc->id, rsc_id);
 195                     }
 196                 }
 197                 break;
 198             default:
 199                 break;
 200         }
 201 
 202     } else if (rsc->parent && BE_QUIET == FALSE) {
 203         printf("Forcing %s of '%s' for '%s' instead of '%s'\n", cmd, attr_name, rsc_id, rsc->parent->id);
 204 
 205     } else if(rsc->parent == NULL && rsc->children) {
 206         resource_t *child = rsc->children->data;
 207 
 208         if(child->variant == pe_native) {
 209             lookup_id = clone_strip(child->id); /* Could be a cloned group! */
 210             rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id);
 211 
 212             if(rc == pcmk_ok) {
 213                 rsc = child;
 214                 if (BE_QUIET == FALSE) {
 215                     printf("A value for '%s' already exists in child '%s', performing %s on that instead of '%s'\n", attr_name, lookup_id, cmd, rsc_id);
 216                 }
 217             }
 218 
 219             free(local_attr_id);
 220             free(lookup_id);
 221         }
 222     }
 223 
 224     return rsc;
 225 }
 226 
 227 int
 228 cli_resource_update_attribute(resource_t *rsc, const char *requested_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 229                               const char *attr_set, const char *attr_id,
 230                               const char *attr_name, const char *attr_value,
 231                               bool recursive, cib_t *cib,
 232                               pe_working_set_t *data_set)
 233 {
 234     int rc = pcmk_ok;
 235     static bool need_init = TRUE;
 236 
 237     char *lookup_id = NULL;
 238     char *local_attr_id = NULL;
 239     char *local_attr_set = NULL;
 240 
 241     xmlNode *xml_top = NULL;
 242     xmlNode *xml_obj = NULL;
 243 
 244     bool use_attributes_tag = FALSE;
 245 
 246     if(attr_id == NULL
 247        && do_force == FALSE
 248        && pcmk_ok != find_resource_attr(
 249            cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, NULL, NULL, attr_name, NULL)) {
 250         printf("\n");
 251     }
 252 
 253     if (safe_str_eq(attr_set_type, XML_TAG_ATTR_SETS)) {
 254         if (do_force == FALSE) {
 255             rc = find_resource_attr(cib, XML_ATTR_ID, uber_parent(rsc)->id,
 256                                     XML_TAG_META_SETS, attr_set, attr_id,
 257                                     attr_name, &local_attr_id);
 258             if (rc == pcmk_ok && BE_QUIET == FALSE) {
 259                 printf("WARNING: There is already a meta attribute for '%s' called '%s' (id=%s)\n",
 260                        uber_parent(rsc)->id, attr_name, local_attr_id);
 261                 printf("         Delete '%s' first or use --force to override\n", local_attr_id);
 262             }
 263             free(local_attr_id);
 264             if (rc == pcmk_ok) {
 265                 return -ENOTUNIQ;
 266             }
 267         }
 268 
 269     } else {
 270         rsc = find_matching_attr_resource(rsc, requested_name, attr_set,
 271                                           attr_id, attr_name, cib, "update");
 272     }
 273 
 274     lookup_id = clone_strip(rsc->id); /* Could be a cloned group! */
 275     rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name,
 276                             &local_attr_id);
 277 
 278     if (rc == pcmk_ok) {
 279         crm_debug("Found a match for name=%s: id=%s", attr_name, local_attr_id);
 280         attr_id = local_attr_id;
 281 
 282     } else if (rc != -ENXIO) {
 283         free(lookup_id);
 284         free(local_attr_id);
 285         return rc;
 286 
 287     } else {
 288         const char *value = NULL;
 289         xmlNode *cib_top = NULL;
 290         const char *tag = crm_element_name(rsc->xml);
 291 
 292         cib->cmds->query(cib, "/cib", &cib_top,
 293                               cib_sync_call | cib_scope_local | cib_xpath | cib_no_children);
 294         value = crm_element_value(cib_top, "ignore_dtd");
 295         if (value != NULL) {
 296             use_attributes_tag = TRUE;
 297 
 298         } else {
 299             value = crm_element_value(cib_top, XML_ATTR_VALIDATION);
 300             if (crm_ends_with_ext(value, "-0.6")) {
 301                 use_attributes_tag = TRUE;
 302             }
 303         }
 304         free_xml(cib_top);
 305 
 306         if (attr_set == NULL) {
 307             local_attr_set = crm_concat(lookup_id, attr_set_type, '-');
 308             attr_set = local_attr_set;
 309         }
 310         if (attr_id == NULL) {
 311             local_attr_id = crm_concat(attr_set, attr_name, '-');
 312             attr_id = local_attr_id;
 313         }
 314 
 315         if (use_attributes_tag && safe_str_eq(tag, XML_CIB_TAG_MASTER)) {
 316             tag = "master_slave";       /* use the old name */
 317         }
 318 
 319         xml_top = create_xml_node(NULL, tag);
 320         crm_xml_add(xml_top, XML_ATTR_ID, lookup_id);
 321 
 322         xml_obj = create_xml_node(xml_top, attr_set_type);
 323         crm_xml_add(xml_obj, XML_ATTR_ID, attr_set);
 324 
 325         if (use_attributes_tag) {
 326             xml_obj = create_xml_node(xml_obj, XML_TAG_ATTRS);
 327         }
 328     }
 329 
 330     xml_obj = crm_create_nvpair_xml(xml_obj, attr_id, attr_name, attr_value);
 331     if (xml_top == NULL) {
 332         xml_top = xml_obj;
 333     }
 334 
 335     crm_log_xml_debug(xml_top, "Update");
 336 
 337     rc = cib->cmds->modify(cib, XML_CIB_TAG_RESOURCES, xml_top, cib_options);
 338     if (rc == pcmk_ok && BE_QUIET == FALSE) {
 339         printf("Set '%s' option: id=%s%s%s%s%s=%s\n", lookup_id, local_attr_id,
 340                attr_set ? " set=" : "", attr_set ? attr_set : "",
 341                attr_name ? " name=" : "", attr_name ? attr_name : "", attr_value);
 342     }
 343 
 344     free_xml(xml_top);
 345 
 346     free(lookup_id);
 347     free(local_attr_id);
 348     free(local_attr_set);
 349 
 350     if(recursive && safe_str_eq(attr_set_type, XML_TAG_META_SETS)) {
 351         GListPtr lpc = NULL;
 352 
 353         if(need_init) {
 354             xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input);
 355 
 356             need_init = FALSE;
 357             unpack_constraints(cib_constraints, data_set);
 358 
 359             for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) {
 360                 resource_t *r = (resource_t *) lpc->data;
 361 
 362                 clear_bit(r->flags, pe_rsc_allocating);
 363             }
 364         }
 365 
 366         crm_debug("Looking for dependencies %p", rsc->rsc_cons_lhs);
 367         set_bit(rsc->flags, pe_rsc_allocating);
 368         for (lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
 369             rsc_colocation_t *cons = (rsc_colocation_t *) lpc->data;
 370             resource_t *peer = cons->rsc_lh;
 371 
 372             crm_debug("Checking %s %d", cons->id, cons->score);
 373             if (cons->score > 0 && is_not_set(peer->flags, pe_rsc_allocating)) {
 374                 /* Don't get into colocation loops */
 375                 crm_debug("Setting %s=%s for dependent resource %s", attr_name, attr_value, peer->id);
 376                 cli_resource_update_attribute(peer, peer->id, NULL, NULL,
 377                                               attr_name, attr_value, recursive,
 378                                               cib, data_set);
 379             }
 380         }
 381     }
 382 
 383     return rc;
 384 }
 385 
 386 int
 387 cli_resource_delete_attribute(resource_t *rsc, const char *requested_name,
     /* [previous][next][first][last][top][bottom][index][help] */
 388                               const char *attr_set, const char *attr_id,
 389                               const char *attr_name, cib_t *cib,
 390                               pe_working_set_t *data_set)
 391 {
 392     xmlNode *xml_obj = NULL;
 393 
 394     int rc = pcmk_ok;
 395     char *lookup_id = NULL;
 396     char *local_attr_id = NULL;
 397 
 398     if(attr_id == NULL
 399        && do_force == FALSE
 400        && find_resource_attr(
 401            cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, NULL, NULL, attr_name, NULL) != pcmk_ok) {
 402         printf("\n");
 403     }
 404 
 405     if(safe_str_eq(attr_set_type, XML_TAG_META_SETS)) {
 406         rsc = find_matching_attr_resource(rsc, requested_name, attr_set,
 407                                           attr_id, attr_name, cib, "delete");
 408     }
 409 
 410     lookup_id = clone_strip(rsc->id);
 411     rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name,
 412                             &local_attr_id);
 413 
 414     if (rc == -ENXIO) {
 415         free(lookup_id);
 416         return pcmk_ok;
 417 
 418     } else if (rc != pcmk_ok) {
 419         free(lookup_id);
 420         return rc;
 421     }
 422 
 423     if (attr_id == NULL) {
 424         attr_id = local_attr_id;
 425     }
 426 
 427     xml_obj = crm_create_nvpair_xml(NULL, attr_id, attr_name, NULL);
 428     crm_log_xml_debug(xml_obj, "Delete");
 429 
 430     CRM_ASSERT(cib);
 431     rc = cib->cmds->delete(cib, XML_CIB_TAG_RESOURCES, xml_obj, cib_options);
 432 
 433     if (rc == pcmk_ok && BE_QUIET == FALSE) {
 434         printf("Deleted '%s' option: id=%s%s%s%s%s\n", lookup_id, local_attr_id,
 435                attr_set ? " set=" : "", attr_set ? attr_set : "",
 436                attr_name ? " name=" : "", attr_name ? attr_name : "");
 437     }
 438 
 439     free(lookup_id);
 440     free_xml(xml_obj);
 441     free(local_attr_id);
 442     return rc;
 443 }
 444 
 445 static int
 446 send_lrm_rsc_op(crm_ipc_t * crmd_channel, const char *op,
     /* [previous][next][first][last][top][bottom][index][help] */
 447                 const char *host_uname, const char *rsc_id,
 448                 bool only_failed, pe_working_set_t * data_set)
 449 {
 450     char *our_pid = NULL;
 451     char *key = NULL;
 452     int rc = -ECOMM;
 453     xmlNode *cmd = NULL;
 454     xmlNode *xml_rsc = NULL;
 455     const char *value = NULL;
 456     const char *router_node = host_uname;
 457     xmlNode *params = NULL;
 458     xmlNode *msg_data = NULL;
 459     resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
 460 
 461     if (rsc == NULL) {
 462         CMD_ERR("Resource %s not found", rsc_id);
 463         return -ENXIO;
 464 
 465     } else if (rsc->variant != pe_native) {
 466         CMD_ERR("We can only process primitive resources, not %s", rsc_id);
 467         return -EINVAL;
 468 
 469     } else if (host_uname == NULL) {
 470         CMD_ERR("Please supply a node name with --node");
 471         return -EINVAL;
 472     } else {
 473         node_t *node = pe_find_node(data_set->nodes, host_uname);
 474 
 475         if (node && is_remote_node(node)) {
 476             if (node->details->remote_rsc == NULL || node->details->remote_rsc->running_on == NULL) {
 477                 CMD_ERR("No lrmd connection detected to remote node %s", host_uname);
 478                 return -ENXIO;
 479             }
 480             node = node->details->remote_rsc->running_on->data;
 481             router_node = node->details->uname;
 482         }
 483     }
 484 
 485     key = generate_transition_key(0, getpid(), 0, "xxxxxxxx-xrsc-opxx-xcrm-resourcexxxx");
 486 
 487     msg_data = create_xml_node(NULL, XML_GRAPH_TAG_RSC_OP);
 488     crm_xml_add(msg_data, XML_ATTR_TRANSITION_KEY, key);
 489     free(key);
 490 
 491     crm_xml_add(msg_data, XML_LRM_ATTR_TARGET, host_uname);
 492     if (safe_str_neq(router_node, host_uname)) {
 493         crm_xml_add(msg_data, XML_LRM_ATTR_ROUTER_NODE, router_node);
 494     }
 495 
 496     xml_rsc = create_xml_node(msg_data, XML_CIB_TAG_RESOURCE);
 497     if (rsc->clone_name) {
 498         crm_xml_add(xml_rsc, XML_ATTR_ID, rsc->clone_name);
 499         crm_xml_add(xml_rsc, XML_ATTR_ID_LONG, rsc->id);
 500 
 501     } else {
 502         crm_xml_add(xml_rsc, XML_ATTR_ID, rsc->id);
 503     }
 504 
 505     value = crm_copy_xml_element(rsc->xml, xml_rsc, XML_ATTR_TYPE);
 506     if (value == NULL) {
 507         CMD_ERR("%s has no type!  Aborting...", rsc_id);
 508         return -ENXIO;
 509     }
 510 
 511     value = crm_copy_xml_element(rsc->xml, xml_rsc, XML_AGENT_ATTR_CLASS);
 512     if (value == NULL) {
 513         CMD_ERR("%s has no class!  Aborting...", rsc_id);
 514         return -ENXIO;
 515     }
 516 
 517     crm_copy_xml_element(rsc->xml, xml_rsc, XML_AGENT_ATTR_PROVIDER);
 518 
 519     params = create_xml_node(msg_data, XML_TAG_ATTRS);
 520     crm_xml_add(params, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
 521 
 522     key = crm_meta_name(XML_LRM_ATTR_INTERVAL);
 523     crm_xml_add(params, key, "60000");  /* 1 minute */
 524     free(key);
 525 
 526     our_pid = crm_getpid_s();
 527     cmd = create_request(op, msg_data, router_node, CRM_SYSTEM_CRMD, crm_system_name, our_pid);
 528 
 529 /*      crm_log_xml_warn(cmd, "send_lrm_rsc_op"); */
 530     free_xml(msg_data);
 531 
 532     if (crm_ipc_send(crmd_channel, cmd, 0, 0, NULL) > 0) {
 533         rc = 0;
 534 
 535     } else {
 536         crm_debug("Could not send %s op to the crmd", op);
 537         rc = -ENOTCONN;
 538     }
 539 
 540     free_xml(cmd);
 541     return rc;
 542 }
 543 
 544 /*!
 545  * \internal
 546  * \brief Get resource name as used in failure-related node attributes
 547  *
 548  * \param[in] rsc  Resource to check
 549  *
 550  * \return Newly allocated string containing resource's fail name
 551  * \note The caller is responsible for freeing the result.
 552  */
 553 static inline char *
 554 rsc_fail_name(resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 555 {
 556     const char *name = (rsc->clone_name? rsc->clone_name : rsc->id);
 557 
 558     return is_set(rsc->flags, pe_rsc_unique)? strdup(name) : clone_strip(name);
 559 }
 560 
 561 int
 562 cli_resource_delete(crm_ipc_t *crmd_channel, const char *host_uname,
     /* [previous][next][first][last][top][bottom][index][help] */
 563                     resource_t *rsc, const char *operation,
 564                     const char *interval, pe_working_set_t *data_set)
 565 {
 566     int rc = pcmk_ok;
 567     node_t *node = NULL;
 568     char *rsc_name = NULL;
 569     int attr_options = attrd_opt_none;
 570 
 571     if (rsc == NULL) {
 572         return -ENXIO;
 573 
 574     } else if (rsc->children) {
 575         GListPtr lpc = NULL;
 576 
 577         for (lpc = rsc->children; lpc != NULL; lpc = lpc->next) {
 578             resource_t *child = (resource_t *) lpc->data;
 579 
 580             rc = cli_resource_delete(crmd_channel, host_uname, child, operation,
 581                                      interval, data_set);
 582             if(rc != pcmk_ok) {
 583                 return rc;
 584             }
 585         }
 586         return pcmk_ok;
 587 
 588     } else if (host_uname == NULL) {
 589         GListPtr lpc = NULL;
 590         GListPtr nodes = g_hash_table_get_values(rsc->known_on);
 591 
 592         if(nodes == NULL && do_force) {
 593             nodes = node_list_dup(data_set->nodes, FALSE, FALSE);
 594 
 595         } else if(nodes == NULL && rsc->exclusive_discover) {
 596             GHashTableIter iter;
 597             pe_node_t *node = NULL;
 598 
 599             g_hash_table_iter_init(&iter, rsc->allowed_nodes);
 600             while (g_hash_table_iter_next(&iter, NULL, (void**)&node)) {
 601                 if(node->weight >= 0) {
 602                     nodes = g_list_prepend(nodes, node);
 603                 }
 604             }
 605 
 606         } else if(nodes == NULL) {
 607             nodes = g_hash_table_get_values(rsc->allowed_nodes);
 608         }
 609 
 610         for (lpc = nodes; lpc != NULL; lpc = lpc->next) {
 611             node = (node_t *) lpc->data;
 612 
 613             if (node->details->online) {
 614                 cli_resource_delete(crmd_channel, node->details->uname, rsc,
 615                                     operation, interval, data_set);
 616             }
 617         }
 618 
 619         g_list_free(nodes);
 620         return pcmk_ok;
 621     }
 622 
 623     node = pe_find_node(data_set->nodes, host_uname);
 624 
 625     if (node == NULL) {
 626         printf("Unable to clean up %s because node %s not found\n",
 627                rsc->id, host_uname);
 628         return -ENODEV;
 629     }
 630 
 631     if (!node->details->rsc_discovery_enabled) {
 632         printf("Unable to clean up %s because resource discovery disabled on %s\n",
 633                rsc->id, host_uname);
 634         return -EOPNOTSUPP;
 635     }
 636 
 637     if (crmd_channel == NULL) {
 638         printf("Dry run: skipping clean-up of %s on %s due to CIB_file\n",
 639                rsc->id, host_uname);
 640         return rc;
 641      }
 642 
 643     /* Erase the resource's entire LRM history in the CIB, even if we're only
 644      * clearing a single operation's fail count. If we erased only entries for a
 645      * single operation, we might wind up with a wrong idea of the current
 646      * resource state, and we might not re-probe the resource.
 647      */
 648     rc = send_lrm_rsc_op(crmd_channel, CRM_OP_LRM_DELETE, host_uname, rsc->id,
 649                          TRUE, data_set);
 650     if (rc != pcmk_ok) {
 651         printf("Unable to clean up %s history on %s: %s\n",
 652                rsc->id, host_uname, pcmk_strerror(rc));
 653         return rc;
 654     }
 655     crmd_replies_needed++;
 656 
 657     crm_trace("Processing %d mainloop inputs", crmd_replies_needed);
 658     while(g_main_context_iteration(NULL, FALSE)) {
 659         crm_trace("Processed mainloop input, %d still remaining",
 660                   crmd_replies_needed);
 661     }
 662 
 663     if(crmd_replies_needed < 0) {
 664         crmd_replies_needed = 0;
 665     }
 666 
 667     rsc_name = rsc_fail_name(rsc);
 668     if (is_remote_node(node)) {
 669         attr_options |= attrd_opt_remote;
 670     }
 671     rc = attrd_clear_delegate(NULL, host_uname, rsc_name, operation, interval,
 672                               NULL, attr_options);
 673     if (rc != pcmk_ok) {
 674         printf("Cleaned %s history on %s, but unable to clear failures: %s\n",
 675                rsc->id, host_uname, pcmk_strerror(rc));
 676     } else {
 677         printf("Cleaned up %s on %s\n", rsc->id, host_uname);
 678     }
 679     free(rsc_name);
 680 
 681     return rc;
 682 }
 683 
 684 void
 685 cli_resource_check(cib_t * cib_conn, resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 686 {
 687     int need_nl = 0;
 688     char *role_s = NULL;
 689     char *managed = NULL;
 690     resource_t *parent = uber_parent(rsc);
 691 
 692     find_resource_attr(cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id,
 693                        NULL, NULL, NULL, XML_RSC_ATTR_MANAGED, &managed);
 694 
 695     find_resource_attr(cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id,
 696                        NULL, NULL, NULL, XML_RSC_ATTR_TARGET_ROLE, &role_s);
 697 
 698     if(role_s) {
 699         enum rsc_role_e role = text2role(role_s);
 700         if(role == RSC_ROLE_UNKNOWN) {
 701             // Treated as if unset
 702 
 703         } else if(role == RSC_ROLE_STOPPED) {
 704             printf("\n  * The configuration specifies that '%s' should remain stopped\n", parent->id);
 705             need_nl++;
 706 
 707         } else if(parent->variant == pe_master && role == RSC_ROLE_SLAVE) {
 708             printf("\n  * The configuration specifies that '%s' should not be promoted\n", parent->id);
 709             need_nl++;
 710         }
 711     }
 712 
 713     if(managed && crm_is_true(managed) == FALSE) {
 714         printf("%s  * The configuration prevents the cluster from stopping or starting '%s' (unmanaged)\n", need_nl == 0?"\n":"", parent->id);
 715         need_nl++;
 716     }
 717 
 718     if(need_nl) {
 719         printf("\n");
 720     }
 721 }
 722 
 723 int
 724 cli_resource_fail(crm_ipc_t * crmd_channel, const char *host_uname,
     /* [previous][next][first][last][top][bottom][index][help] */
 725              const char *rsc_id, pe_working_set_t * data_set)
 726 {
 727     crm_warn("Failing: %s", rsc_id);
 728     return send_lrm_rsc_op(crmd_channel, CRM_OP_LRM_FAIL, host_uname, rsc_id, FALSE, data_set);
 729 }
 730 
 731 static GHashTable *
 732 generate_resource_params(resource_t * rsc, pe_working_set_t * data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 733 {
 734     GHashTable *params = NULL;
 735     GHashTable *meta = NULL;
 736     GHashTable *combined = NULL;
 737     GHashTableIter iter;
 738 
 739     if (!rsc) {
 740         crm_err("Resource does not exist in config");
 741         return NULL;
 742     }
 743 
 744     params = crm_str_table_new();
 745     meta = crm_str_table_new();
 746     combined = crm_str_table_new();
 747 
 748     get_rsc_attributes(params, rsc, NULL /* TODO: Pass in local node */ , data_set);
 749     get_meta_attributes(meta, rsc, NULL /* TODO: Pass in local node */ , data_set);
 750 
 751     if (params) {
 752         char *key = NULL;
 753         char *value = NULL;
 754 
 755         g_hash_table_iter_init(&iter, params);
 756         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
 757             g_hash_table_insert(combined, strdup(key), strdup(value));
 758         }
 759         g_hash_table_destroy(params);
 760     }
 761 
 762     if (meta) {
 763         char *key = NULL;
 764         char *value = NULL;
 765 
 766         g_hash_table_iter_init(&iter, meta);
 767         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
 768             char *crm_name = crm_meta_name(key);
 769 
 770             g_hash_table_insert(combined, crm_name, strdup(value));
 771         }
 772         g_hash_table_destroy(meta);
 773     }
 774 
 775     return combined;
 776 }
 777 
 778 static bool resource_is_running_on(resource_t *rsc, const char *host) 
     /* [previous][next][first][last][top][bottom][index][help] */
 779 {
 780     bool found = TRUE;
 781     GListPtr hIter = NULL;
 782     GListPtr hosts = NULL;
 783 
 784     if(rsc == NULL) {
 785         return FALSE;
 786     }
 787 
 788     rsc->fns->location(rsc, &hosts, TRUE);
 789     for (hIter = hosts; host != NULL && hIter != NULL; hIter = hIter->next) {
 790         pe_node_t *node = (pe_node_t *) hIter->data;
 791 
 792         if(strcmp(host, node->details->uname) == 0) {
 793             crm_trace("Resource %s is running on %s\n", rsc->id, host);
 794             goto done;
 795         } else if(strcmp(host, node->details->id) == 0) {
 796             crm_trace("Resource %s is running on %s\n", rsc->id, host);
 797             goto done;
 798         }
 799     }
 800 
 801     if(host != NULL) {
 802         crm_trace("Resource %s is not running on: %s\n", rsc->id, host);
 803         found = FALSE;
 804 
 805     } else if(host == NULL && hosts == NULL) {
 806         crm_trace("Resource %s is not running\n", rsc->id);
 807         found = FALSE;
 808     }
 809 
 810   done:
 811 
 812     g_list_free(hosts);
 813     return found;
 814 }
 815 
 816 /*!
 817  * \internal
 818  * \brief Create a list of all resources active on host from a given list
 819  *
 820  * \param[in] host      Name of host to check whether resources are active
 821  * \param[in] rsc_list  List of resources to check
 822  *
 823  * \return New list of resources from list that are active on host
 824  */
 825 static GList *
 826 get_active_resources(const char *host, GList *rsc_list)
     /* [previous][next][first][last][top][bottom][index][help] */
 827 {
 828     GList *rIter = NULL;
 829     GList *active = NULL;
 830 
 831     for (rIter = rsc_list; rIter != NULL; rIter = rIter->next) {
 832         resource_t *rsc = (resource_t *) rIter->data;
 833 
 834         /* Expand groups to their members, because if we're restarting a member
 835          * other than the first, we can't otherwise tell which resources are
 836          * stopping and starting.
 837          */
 838         if (rsc->variant == pe_group) {
 839             active = g_list_concat(active,
 840                                    get_active_resources(host, rsc->children));
 841         } else if (resource_is_running_on(rsc, host)) {
 842             active = g_list_append(active, strdup(rsc->id));
 843         }
 844     }
 845     return active;
 846 }
 847 
 848 static GList*
 849 subtract_lists(GList *from, GList *items) 
     /* [previous][next][first][last][top][bottom][index][help] */
 850 {
 851     GList *item = NULL;
 852     GList *result = g_list_copy(from);
 853 
 854     for (item = items; item != NULL; item = item->next) {
 855         GList *candidate = NULL;
 856         for (candidate = from; candidate != NULL; candidate = candidate->next) {
 857             crm_info("Comparing %s with %s", candidate->data, item->data);
 858             if(strcmp(candidate->data, item->data) == 0) {
 859                 result = g_list_remove(result, candidate->data);
 860                 break;
 861             }
 862         }
 863     }
 864 
 865     return result;
 866 }
 867 
 868 static void dump_list(GList *items, const char *tag) 
     /* [previous][next][first][last][top][bottom][index][help] */
 869 {
 870     int lpc = 0;
 871     GList *item = NULL;
 872 
 873     for (item = items; item != NULL; item = item->next) {
 874         crm_trace("%s[%d]: %s", tag, lpc, (char*)item->data);
 875         lpc++;
 876     }
 877 }
 878 
 879 static void display_list(GList *items, const char *tag) 
     /* [previous][next][first][last][top][bottom][index][help] */
 880 {
 881     GList *item = NULL;
 882 
 883     for (item = items; item != NULL; item = item->next) {
 884         fprintf(stdout, "%s%s\n", tag, (const char *)item->data);
 885     }
 886 }
 887 
 888 /*!
 889  * \internal
 890  * \brief Upgrade XML to latest schema version and use it as working set input
 891  *
 892  * This also updates the working set timestamp to the current time.
 893  *
 894  * \param[in] data_set   Working set instance to update
 895  * \param[in] xml        XML to use as input
 896  *
 897  * \return pcmk_ok on success, -ENOKEY if unable to upgrade XML
 898  * \note On success, caller is responsible for freeing memory allocated for
 899  *       data_set->now.
 900  * \todo This follows the example of other callers of cli_config_update()
 901  *       and returns -ENOKEY ("Required key not available") if that fails,
 902  *       but perhaps -pcmk_err_schema_validation would be better in that case.
 903  */
 904 int
 905 update_working_set_xml(pe_working_set_t *data_set, xmlNode **xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 906 {
 907     if (cli_config_update(xml, NULL, FALSE) == FALSE) {
 908         return -ENOKEY;
 909     }
 910     data_set->input = *xml;
 911     data_set->now = crm_time_new(NULL);
 912     return pcmk_ok;
 913 }
 914 
 915 /*!
 916  * \internal
 917  * \brief Update a working set's XML input based on a CIB query
 918  *
 919  * \param[in] data_set   Data set instance to initialize
 920  * \param[in] cib        Connection to the CIB
 921  *
 922  * \return pcmk_ok on success, -errno on failure
 923  * \note On success, caller is responsible for freeing memory allocated for
 924  *       data_set->input and data_set->now.
 925  */
 926 static int
 927 update_working_set_from_cib(pe_working_set_t * data_set, cib_t *cib)
     /* [previous][next][first][last][top][bottom][index][help] */
 928 {
 929     xmlNode *cib_xml_copy = NULL;
 930     int rc;
 931 
 932     rc = cib->cmds->query(cib, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
 933     if (rc != pcmk_ok) {
 934         fprintf(stderr, "Could not obtain the current CIB: %s (%d)\n", pcmk_strerror(rc), rc);
 935         return rc;
 936     }
 937     rc = update_working_set_xml(data_set, &cib_xml_copy);
 938     if (rc != pcmk_ok) {
 939         fprintf(stderr, "Could not upgrade the current CIB XML\n");
 940         free_xml(cib_xml_copy);
 941         return rc;
 942     }
 943     return pcmk_ok;
 944 }
 945 
 946 static int
 947 update_dataset(cib_t *cib, pe_working_set_t * data_set, bool simulate)
     /* [previous][next][first][last][top][bottom][index][help] */
 948 {
 949     char *pid = NULL;
 950     char *shadow_file = NULL;
 951     cib_t *shadow_cib = NULL;
 952     int rc;
 953 
 954     cleanup_alloc_calculations(data_set);
 955     rc = update_working_set_from_cib(data_set, cib);
 956     if (rc != pcmk_ok) {
 957         return rc;
 958     }
 959 
 960     if(simulate) {
 961         pid = crm_getpid_s();
 962         shadow_cib = cib_shadow_new(pid);
 963         shadow_file = get_shadow_file(pid);
 964 
 965         if (shadow_cib == NULL) {
 966             fprintf(stderr, "Could not create shadow cib: '%s'\n", pid);
 967             rc = -ENXIO;
 968             goto cleanup;
 969         }
 970 
 971         rc = write_xml_file(data_set->input, shadow_file, FALSE);
 972 
 973         if (rc < 0) {
 974             fprintf(stderr, "Could not populate shadow cib: %s (%d)\n", pcmk_strerror(rc), rc);
 975             goto cleanup;
 976         }
 977 
 978         rc = shadow_cib->cmds->signon(shadow_cib, crm_system_name, cib_command);
 979         if(rc != pcmk_ok) {
 980             fprintf(stderr, "Could not connect to shadow cib: %s (%d)\n", pcmk_strerror(rc), rc);
 981             goto cleanup;
 982         }
 983 
 984         do_calculations(data_set, data_set->input, NULL);
 985         run_simulation(data_set, shadow_cib, NULL, TRUE);
 986         rc = update_dataset(shadow_cib, data_set, FALSE);
 987 
 988     } else {
 989         cluster_status(data_set);
 990     }
 991 
 992   cleanup:
 993     /* Do not free data_set->input here, we need rsc->xml to be valid later on */
 994     cib_delete(shadow_cib);
 995     free(pid);
 996 
 997     if(shadow_file) {
 998         unlink(shadow_file);
 999         free(shadow_file);
1000     }
1001 
1002     return rc;
1003 }
1004 
1005 static int
1006 max_delay_for_resource(pe_working_set_t * data_set, resource_t *rsc) 
     /* [previous][next][first][last][top][bottom][index][help] */
1007 {
1008     int delay = 0;
1009     int max_delay = 0;
1010 
1011     if(rsc && rsc->children) {
1012         GList *iter = NULL;
1013 
1014         for(iter = rsc->children; iter; iter = iter->next) {
1015             resource_t *child = (resource_t *)iter->data;
1016 
1017             delay = max_delay_for_resource(data_set, child);
1018             if(delay > max_delay) {
1019                 double seconds = delay / 1000.0;
1020                 crm_trace("Calculated new delay of %.1fs due to %s", seconds, child->id);
1021                 max_delay = delay;
1022             }
1023         }
1024 
1025     } else if(rsc) {
1026         char *key = crm_strdup_printf("%s_%s_0", rsc->id, RSC_STOP);
1027         action_t *stop = custom_action(rsc, key, RSC_STOP, NULL, TRUE, FALSE, data_set);
1028         const char *value = g_hash_table_lookup(stop->meta, XML_ATTR_TIMEOUT);
1029 
1030         max_delay = crm_int_helper(value, NULL);
1031         pe_free_action(stop);
1032     }
1033 
1034 
1035     return max_delay;
1036 }
1037 
1038 static int
1039 max_delay_in(pe_working_set_t * data_set, GList *resources) 
     /* [previous][next][first][last][top][bottom][index][help] */
1040 {
1041     int max_delay = 0;
1042     GList *item = NULL;
1043 
1044     for (item = resources; item != NULL; item = item->next) {
1045         int delay = 0;
1046         resource_t *rsc = pe_find_resource(data_set->resources, (const char *)item->data);
1047 
1048         delay = max_delay_for_resource(data_set, rsc);
1049 
1050         if(delay > max_delay) {
1051             double seconds = delay / 1000.0;
1052             crm_trace("Calculated new delay of %.1fs due to %s", seconds, rsc->id);
1053             max_delay = delay;
1054         }
1055     }
1056 
1057     return 5 + (max_delay / 1000);
1058 }
1059 
1060 #define waiting_for_starts(d, r, h) ((g_list_length(d) > 0) || \
1061                                     (resource_is_running_on((r), (h)) == FALSE))
1062 
1063 /*!
1064  * \internal
1065  * \brief Restart a resource (on a particular host if requested).
1066  *
1067  * \param[in] rsc        The resource to restart
1068  * \param[in] host       The host to restart the resource on (or NULL for all)
1069  * \param[in] timeout_ms Consider failed if actions do not complete in this time
1070  *                       (specified in milliseconds, but a two-second
1071  *                       granularity is actually used; if 0, a timeout will be
1072  *                       calculated based on the resource timeout)
1073  * \param[in] cib        Connection to the CIB for modifying/checking resource
1074  *
1075  * \return pcmk_ok on success, -errno on failure (exits on certain failures)
1076  */
1077 int
1078 cli_resource_restart(resource_t * rsc, const char *host, int timeout_ms, cib_t * cib)
     /* [previous][next][first][last][top][bottom][index][help] */
1079 {
1080     int rc = 0;
1081     int lpc = 0;
1082     int before = 0;
1083     int step_timeout_s = 0;
1084     int sleep_interval = 2;
1085     int timeout = timeout_ms / 1000;
1086 
1087     bool stop_via_ban = FALSE;
1088     char *rsc_id = NULL;
1089     char *orig_target_role = NULL;
1090 
1091     GList *list_delta = NULL;
1092     GList *target_active = NULL;
1093     GList *current_active = NULL;
1094     GList *restart_target_active = NULL;
1095 
1096     pe_working_set_t data_set;
1097 
1098     if(resource_is_running_on(rsc, host) == FALSE) {
1099         const char *id = rsc->clone_name?rsc->clone_name:rsc->id;
1100         if(host) {
1101             printf("%s is not running on %s and so cannot be restarted\n", id, host);
1102         } else {
1103             printf("%s is not running anywhere and so cannot be restarted\n", id);
1104         }
1105         return -ENXIO;
1106     }
1107 
1108     /* We might set the target-role meta-attribute */
1109     attr_set_type = XML_TAG_META_SETS;
1110 
1111     rsc_id = strdup(rsc->id);
1112     if ((pe_rsc_is_clone(rsc) || pe_bundle_replicas(rsc)) && host) {
1113         stop_via_ban = TRUE;
1114     }
1115 
1116     /*
1117       grab full cib
1118       determine originally active resources
1119       disable or ban
1120       poll cib and watch for affected resources to get stopped
1121       without --timeout, calculate the stop timeout for each step and wait for that
1122       if we hit --timeout or the service timeout, re-enable or un-ban, report failure and indicate which resources we couldn't take down
1123       if everything stopped, re-enable or un-ban
1124       poll cib and watch for affected resources to get started
1125       without --timeout, calculate the start timeout for each step and wait for that
1126       if we hit --timeout or the service timeout, report (different) failure and indicate which resources we couldn't bring back up
1127       report success
1128 
1129       Optimizations:
1130       - use constraints to determine ordered list of affected resources
1131       - Allow a --no-deps option (aka. --force-restart)
1132     */
1133 
1134 
1135     set_working_set_defaults(&data_set);
1136     rc = update_dataset(cib, &data_set, FALSE);
1137     if(rc != pcmk_ok) {
1138         fprintf(stdout, "Could not get new resource list: %s (%d)\n", pcmk_strerror(rc), rc);
1139         free(rsc_id);
1140         return rc;
1141     }
1142 
1143     restart_target_active = get_active_resources(host, data_set.resources);
1144     current_active = get_active_resources(host, data_set.resources);
1145 
1146     dump_list(current_active, "Origin");
1147 
1148     if (stop_via_ban) {
1149         /* Stop the clone or bundle instance by banning it from the host */
1150         BE_QUIET = TRUE;
1151         rc = cli_resource_ban(rsc_id, host, NULL, cib);
1152 
1153     } else {
1154         /* Stop the resource by setting target-role to Stopped.
1155          * Remember any existing target-role so we can restore it later
1156          * (though it only makes any difference if it's Slave).
1157          */
1158         char *lookup_id = clone_strip(rsc->id);
1159 
1160         find_resource_attr(cib, XML_NVPAIR_ATTR_VALUE, lookup_id, NULL, NULL,
1161                            NULL, XML_RSC_ATTR_TARGET_ROLE, &orig_target_role);
1162         free(lookup_id);
1163         rc = cli_resource_update_attribute(rsc, rsc_id, NULL, NULL,
1164                                            XML_RSC_ATTR_TARGET_ROLE,
1165                                            RSC_STOPPED, FALSE, cib, &data_set);
1166     }
1167     if(rc != pcmk_ok) {
1168         fprintf(stderr, "Could not set target-role for %s: %s (%d)\n", rsc_id, pcmk_strerror(rc), rc);
1169         if (current_active) {
1170             g_list_free_full(current_active, free);
1171         }
1172         if (restart_target_active) {
1173             g_list_free_full(restart_target_active, free);
1174         }
1175         free(rsc_id);
1176         return crm_exit(rc);
1177     }
1178 
1179     rc = update_dataset(cib, &data_set, TRUE);
1180     if(rc != pcmk_ok) {
1181         fprintf(stderr, "Could not determine which resources would be stopped\n");
1182         goto failure;
1183     }
1184 
1185     target_active = get_active_resources(host, data_set.resources);
1186     dump_list(target_active, "Target");
1187 
1188     list_delta = subtract_lists(current_active, target_active);
1189     fprintf(stdout, "Waiting for %d resources to stop:\n", g_list_length(list_delta));
1190     display_list(list_delta, " * ");
1191 
1192     step_timeout_s = timeout / sleep_interval;
1193     while(g_list_length(list_delta) > 0) {
1194         before = g_list_length(list_delta);
1195         if(timeout_ms == 0) {
1196             step_timeout_s = max_delay_in(&data_set, list_delta) / sleep_interval;
1197         }
1198 
1199         /* We probably don't need the entire step timeout */
1200         for(lpc = 0; lpc < step_timeout_s && g_list_length(list_delta) > 0; lpc++) {
1201             sleep(sleep_interval);
1202             if(timeout) {
1203                 timeout -= sleep_interval;
1204                 crm_trace("%ds remaining", timeout);
1205             }
1206             rc = update_dataset(cib, &data_set, FALSE);
1207             if(rc != pcmk_ok) {
1208                 fprintf(stderr, "Could not determine which resources were stopped\n");
1209                 goto failure;
1210             }
1211 
1212             if (current_active) {
1213                 g_list_free_full(current_active, free);
1214             }
1215             current_active = get_active_resources(host, data_set.resources);
1216             g_list_free(list_delta);
1217             list_delta = subtract_lists(current_active, target_active);
1218             dump_list(current_active, "Current");
1219             dump_list(list_delta, "Delta");
1220         }
1221 
1222         crm_trace("%d (was %d) resources remaining", g_list_length(list_delta), before);
1223         if(before == g_list_length(list_delta)) {
1224             /* aborted during stop phase, print the contents of list_delta */
1225             fprintf(stderr, "Could not complete shutdown of %s, %d resources remaining\n", rsc_id, g_list_length(list_delta));
1226             display_list(list_delta, " * ");
1227             rc = -ETIME;
1228             goto failure;
1229         }
1230 
1231     }
1232 
1233     if (stop_via_ban) {
1234         rc = cli_resource_clear(rsc_id, host, NULL, cib);
1235 
1236     } else if (orig_target_role) {
1237         rc = cli_resource_update_attribute(rsc, rsc_id, NULL, NULL,
1238                                            XML_RSC_ATTR_TARGET_ROLE,
1239                                            orig_target_role, FALSE, cib,
1240                                            &data_set);
1241         free(orig_target_role);
1242         orig_target_role = NULL;
1243     } else {
1244         rc = cli_resource_delete_attribute(rsc, rsc_id, NULL, NULL,
1245                                            XML_RSC_ATTR_TARGET_ROLE, cib,
1246                                            &data_set);
1247     }
1248 
1249     if(rc != pcmk_ok) {
1250         fprintf(stderr, "Could not unset target-role for %s: %s (%d)\n", rsc_id, pcmk_strerror(rc), rc);
1251         free(rsc_id);
1252         return crm_exit(rc);
1253     }
1254 
1255     if (target_active) {
1256         g_list_free_full(target_active, free);
1257     }
1258     target_active = restart_target_active;
1259     if (list_delta) {
1260         g_list_free(list_delta);
1261     }
1262     list_delta = subtract_lists(target_active, current_active);
1263     fprintf(stdout, "Waiting for %d resources to start again:\n", g_list_length(list_delta));
1264     display_list(list_delta, " * ");
1265 
1266     step_timeout_s = timeout / sleep_interval;
1267     while (waiting_for_starts(list_delta, rsc, host)) {
1268         before = g_list_length(list_delta);
1269         if(timeout_ms == 0) {
1270             step_timeout_s = max_delay_in(&data_set, list_delta) / sleep_interval;
1271         }
1272 
1273         /* We probably don't need the entire step timeout */
1274         for (lpc = 0; (lpc < step_timeout_s) && waiting_for_starts(list_delta, rsc, host); lpc++) {
1275 
1276             sleep(sleep_interval);
1277             if(timeout) {
1278                 timeout -= sleep_interval;
1279                 crm_trace("%ds remaining", timeout);
1280             }
1281 
1282             rc = update_dataset(cib, &data_set, FALSE);
1283             if(rc != pcmk_ok) {
1284                 fprintf(stderr, "Could not determine which resources were started\n");
1285                 goto failure;
1286             }
1287 
1288             if (current_active) {
1289                 g_list_free_full(current_active, free);
1290             }
1291 
1292             /* It's OK if dependent resources moved to a different node,
1293              * so we check active resources on all nodes.
1294              */
1295             current_active = get_active_resources(NULL, data_set.resources);
1296             g_list_free(list_delta);
1297             list_delta = subtract_lists(target_active, current_active);
1298             dump_list(current_active, "Current");
1299             dump_list(list_delta, "Delta");
1300         }
1301 
1302         if(before == g_list_length(list_delta)) {
1303             /* aborted during start phase, print the contents of list_delta */
1304             fprintf(stdout, "Could not complete restart of %s, %d resources remaining\n", rsc_id, g_list_length(list_delta));
1305             display_list(list_delta, " * ");
1306             rc = -ETIME;
1307             goto failure;
1308         }
1309 
1310     }
1311 
1312     rc = pcmk_ok;
1313     goto done;
1314 
1315   failure:
1316     if (stop_via_ban) {
1317         cli_resource_clear(rsc_id, host, NULL, cib);
1318     } else if (orig_target_role) {
1319         cli_resource_update_attribute(rsc, rsc_id, NULL, NULL,
1320                                       XML_RSC_ATTR_TARGET_ROLE,
1321                                       orig_target_role, FALSE, cib, &data_set);
1322         free(orig_target_role);
1323     } else {
1324         cli_resource_delete_attribute(rsc, rsc_id, NULL, NULL,
1325                                       XML_RSC_ATTR_TARGET_ROLE, cib, &data_set);
1326     }
1327 
1328 done:
1329     if (list_delta) {
1330         g_list_free(list_delta);
1331     }
1332     if (current_active) {
1333         g_list_free_full(current_active, free);
1334     }
1335     if (target_active && (target_active != restart_target_active)) {
1336         g_list_free_full(target_active, free);
1337     }
1338     if (restart_target_active) {
1339         g_list_free_full(restart_target_active, free);
1340     }
1341     cleanup_alloc_calculations(&data_set);
1342     free(rsc_id);
1343     return rc;
1344 }
1345 
1346 #define action_is_pending(action) \
1347     ((is_set((action)->flags, pe_action_optional) == FALSE) \
1348     && (is_set((action)->flags, pe_action_runnable) == TRUE) \
1349     && (is_set((action)->flags, pe_action_pseudo) == FALSE))
1350 
1351 /*!
1352  * \internal
1353  * \brief Return TRUE if any actions in a list are pending
1354  *
1355  * \param[in] actions   List of actions to check
1356  *
1357  * \return TRUE if any actions in the list are pending, FALSE otherwise
1358  */
1359 static bool
1360 actions_are_pending(GListPtr actions)
     /* [previous][next][first][last][top][bottom][index][help] */
1361 {
1362     GListPtr action;
1363 
1364     for (action = actions; action != NULL; action = action->next) {
1365         if (action_is_pending((action_t *) action->data)) {
1366             return TRUE;
1367         }
1368     }
1369     return FALSE;
1370 }
1371 
1372 /*!
1373  * \internal
1374  * \brief Print pending actions to stderr
1375  *
1376  * \param[in] actions   List of actions to check
1377  *
1378  * \return void
1379  */
1380 static void
1381 print_pending_actions(GListPtr actions)
     /* [previous][next][first][last][top][bottom][index][help] */
1382 {
1383     GListPtr action;
1384 
1385     fprintf(stderr, "Pending actions:\n");
1386     for (action = actions; action != NULL; action = action->next) {
1387         action_t *a = (action_t *) action->data;
1388 
1389         if (action_is_pending(a)) {
1390             fprintf(stderr, "\tAction %d: %s", a->id, a->uuid);
1391             if (a->node) {
1392                 fprintf(stderr, "\ton %s", a->node->details->uname);
1393             }
1394             fprintf(stderr, "\n");
1395         }
1396     }
1397 }
1398 
1399 /* For --wait, timeout (in seconds) to use if caller doesn't specify one */
1400 #define WAIT_DEFAULT_TIMEOUT_S (60 * 60)
1401 
1402 /* For --wait, how long to sleep between cluster state checks */
1403 #define WAIT_SLEEP_S (2)
1404 
1405 /*!
1406  * \internal
1407  * \brief Wait until all pending cluster actions are complete
1408  *
1409  * This waits until either the CIB's transition graph is idle or a timeout is
1410  * reached.
1411  *
1412  * \param[in] timeout_ms Consider failed if actions do not complete in this time
1413  *                       (specified in milliseconds, but one-second granularity
1414  *                       is actually used; if 0, a default will be used)
1415  * \param[in] cib        Connection to the CIB
1416  *
1417  * \return pcmk_ok on success, -errno on failure
1418  */
1419 int
1420 wait_till_stable(int timeout_ms, cib_t * cib)
     /* [previous][next][first][last][top][bottom][index][help] */
1421 {
1422     pe_working_set_t data_set;
1423     int rc = -1;
1424     int timeout_s = timeout_ms? ((timeout_ms + 999) / 1000) : WAIT_DEFAULT_TIMEOUT_S;
1425     time_t expire_time = time(NULL) + timeout_s;
1426     time_t time_diff;
1427 
1428     set_working_set_defaults(&data_set);
1429     do {
1430 
1431         /* Abort if timeout is reached */
1432         time_diff = expire_time - time(NULL);
1433         if (time_diff > 0) {
1434             crm_info("Waiting up to %d seconds for cluster actions to complete", time_diff);
1435         } else {
1436             print_pending_actions(data_set.actions);
1437             cleanup_alloc_calculations(&data_set);
1438             return -ETIME;
1439         }
1440         if (rc == pcmk_ok) { /* this avoids sleep on first loop iteration */
1441             sleep(WAIT_SLEEP_S);
1442         }
1443 
1444         /* Get latest transition graph */
1445         cleanup_alloc_calculations(&data_set);
1446         rc = update_working_set_from_cib(&data_set, cib);
1447         if (rc != pcmk_ok) {
1448             cleanup_alloc_calculations(&data_set);
1449             return rc;
1450         }
1451         do_calculations(&data_set, data_set.input, NULL);
1452 
1453     } while (actions_are_pending(data_set.actions));
1454 
1455     return pcmk_ok;
1456 }
1457 
1458 int
1459 cli_resource_execute(resource_t *rsc, const char *requested_name,
     /* [previous][next][first][last][top][bottom][index][help] */
1460                      const char *rsc_action, GHashTable *override_hash,
1461                      int timeout_ms, cib_t * cib, pe_working_set_t *data_set)
1462 {
1463     int rc = pcmk_ok;
1464     svc_action_t *op = NULL;
1465     const char *rid = NULL;
1466     const char *rtype = NULL;
1467     const char *rprov = NULL;
1468     const char *rclass = NULL;
1469     const char *action = NULL;
1470     GHashTable *params = NULL;
1471 
1472     if (safe_str_eq(rsc_action, "validate")) {
1473         action = "validate-all";
1474 
1475     } else if (safe_str_eq(rsc_action, "force-check")) {
1476         action = "monitor";
1477 
1478     } else if (safe_str_eq(rsc_action, "force-stop")) {
1479         action = rsc_action+6;
1480 
1481     } else if (safe_str_eq(rsc_action, "force-start")
1482                || safe_str_eq(rsc_action, "force-demote")
1483                || safe_str_eq(rsc_action, "force-promote")) {
1484         action = rsc_action+6;
1485 
1486         if(pe_rsc_is_clone(rsc)) {
1487             rc = cli_resource_search(rsc, requested_name, data_set);
1488             if(rc > 0 && do_force == FALSE) {
1489                 CMD_ERR("It is not safe to %s %s here: the cluster claims it is already active",
1490                         action, rsc->id);
1491                 CMD_ERR("Try setting target-role=stopped first or specifying --force");
1492                 crm_exit(EPERM);
1493             }
1494         }
1495     }
1496 
1497     if(pe_rsc_is_clone(rsc)) {
1498         /* Grab the first child resource in the hope it's not a group */
1499         rsc = rsc->children->data;
1500     }
1501 
1502     if(rsc->variant == pe_group) {
1503         CMD_ERR("Sorry, --%s doesn't support group resources", rsc_action);
1504         crm_exit(EOPNOTSUPP);
1505     }
1506 
1507     rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
1508     rprov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER);
1509     rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE);
1510 
1511     if (safe_str_eq(rclass, PCMK_RESOURCE_CLASS_STONITH)) {
1512         CMD_ERR("Sorry, --%s doesn't support %s resources yet", rsc_action, rclass);
1513         crm_exit(EOPNOTSUPP);
1514     }
1515 
1516     params = generate_resource_params(rsc, data_set);
1517 
1518     /* add meta_timeout env needed by some resource agents */
1519     if (timeout_ms == 0) {
1520         timeout_ms = pe_get_configured_timeout(rsc, action, data_set);
1521     }
1522     g_hash_table_insert(params, strdup("CRM_meta_timeout"),
1523                         crm_strdup_printf("%d", timeout_ms));
1524 
1525     /* add crm_feature_set env needed by some resource agents */
1526     g_hash_table_insert(params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET));
1527 
1528     rid = pe_rsc_is_anon_clone(rsc->parent)? requested_name : rsc->id;
1529 
1530     op = resources_action_create(rid, rclass, rprov, rtype, action, 0,
1531                                  timeout_ms, params, 0);
1532     if (op == NULL) {
1533         /* Re-run with stderr enabled so we can display a sane error message */
1534         crm_enable_stderr(TRUE);
1535         op = resources_action_create(rid, rclass, rprov, rtype, action, 0,
1536                                      timeout_ms, params, 0);
1537 
1538         /* We know op will be NULL, but this makes static analysis happy */
1539         services_action_free(op);
1540 
1541         return crm_exit(EINVAL);
1542     }
1543 
1544 
1545     setenv("HA_debug", resource_verbose > 0 ? "1" : "0", 1);
1546     if(resource_verbose > 1) {
1547         setenv("OCF_TRACE_RA", "1", 1);
1548     }
1549 
1550     if (override_hash) {
1551         GHashTableIter iter;
1552         char *name = NULL;
1553         char *value = NULL;
1554 
1555         g_hash_table_iter_init(&iter, override_hash);
1556         while (g_hash_table_iter_next(&iter, (gpointer *) & name, (gpointer *) & value)) {
1557             printf("Overriding the cluster configuration for '%s' with '%s' = '%s'\n",
1558                    rsc->id, name, value);
1559             g_hash_table_replace(op->params, strdup(name), strdup(value));
1560         }
1561     }
1562 
1563     if (services_action_sync(op)) {
1564         int more, lpc, last;
1565         char *local_copy = NULL;
1566 
1567         if (op->status == PCMK_LRM_OP_DONE) {
1568             printf("Operation %s for %s (%s:%s:%s) returned: '%s' (%d)\n",
1569                    action, rsc->id, rclass, rprov ? rprov : "", rtype,
1570                    services_ocf_exitcode_str(op->rc), op->rc);
1571         } else {
1572             printf("Operation %s for %s (%s:%s:%s) failed: '%s' (%d)\n",
1573                    action, rsc->id, rclass, rprov ? rprov : "", rtype,
1574                    services_lrm_status_str(op->status), op->status);
1575         }
1576 
1577         /* hide output for validate-all if not in verbose */
1578         if (resource_verbose == 0 && safe_str_eq(action, "validate-all"))
1579             goto done;
1580 
1581         if (op->stdout_data) {
1582             local_copy = strdup(op->stdout_data);
1583             more = strlen(local_copy);
1584             last = 0;
1585 
1586             for (lpc = 0; lpc < more; lpc++) {
1587                 if (local_copy[lpc] == '\n' || local_copy[lpc] == 0) {
1588                     local_copy[lpc] = 0;
1589                     printf(" >  stdout: %s\n", local_copy + last);
1590                     last = lpc + 1;
1591                 }
1592             }
1593             free(local_copy);
1594         }
1595         if (op->stderr_data) {
1596             local_copy = strdup(op->stderr_data);
1597             more = strlen(local_copy);
1598             last = 0;
1599 
1600             for (lpc = 0; lpc < more; lpc++) {
1601                 if (local_copy[lpc] == '\n' || local_copy[lpc] == 0) {
1602                     local_copy[lpc] = 0;
1603                     printf(" >  stderr: %s\n", local_copy + last);
1604                     last = lpc + 1;
1605                 }
1606             }
1607             free(local_copy);
1608         }
1609     }
1610   done:
1611     rc = op->rc;
1612     services_action_free(op);
1613     return rc;
1614 }
1615 
1616 int
1617 cli_resource_move(resource_t *rsc, const char *rsc_id, const char *host_name,
     /* [previous][next][first][last][top][bottom][index][help] */
1618                   cib_t *cib, pe_working_set_t *data_set)
1619 {
1620     int rc = -EINVAL;
1621     int count = 0;
1622     node_t *current = NULL;
1623     node_t *dest = pe_find_node(data_set->nodes, host_name);
1624     bool cur_is_dest = FALSE;
1625 
1626     if (scope_master && rsc->variant != pe_master) {
1627         resource_t *p = uber_parent(rsc);
1628         if(p->variant == pe_master) {
1629             CMD_ERR("Using parent '%s' for --move command instead of '%s'.", rsc->id, rsc_id);
1630             rsc_id = p->id;
1631             rsc = p;
1632 
1633         } else {
1634             CMD_ERR("Ignoring '--master' option: not valid for %s resources.",
1635                     get_resource_typename(rsc->variant));
1636             scope_master = FALSE;
1637         }
1638     }
1639 
1640     if(rsc->variant == pe_master) {
1641         GListPtr iter = NULL;
1642 
1643         for(iter = rsc->children; iter; iter = iter->next) {
1644             resource_t *child = (resource_t *)iter->data;
1645             enum rsc_role_e child_role = child->fns->state(child, TRUE);
1646 
1647             if(child_role == RSC_ROLE_MASTER) {
1648                 rsc = child;
1649                 count++;
1650             }
1651         }
1652 
1653         if(scope_master == FALSE && count == 0) {
1654             count = g_list_length(rsc->running_on);
1655         }
1656 
1657     } else if (pe_rsc_is_clone(rsc)) {
1658         count = g_list_length(rsc->running_on);
1659 
1660     } else if (g_list_length(rsc->running_on) > 1) {
1661         CMD_ERR("Resource '%s' not moved: active on multiple nodes", rsc_id);
1662         return rc;
1663     }
1664 
1665     if(dest == NULL) {
1666         CMD_ERR("Error performing operation: node '%s' is unknown", host_name);
1667         return -ENXIO;
1668     }
1669 
1670     if(g_list_length(rsc->running_on) == 1) {
1671         current = rsc->running_on->data;
1672     }
1673 
1674     if(current == NULL) {
1675         /* Nothing to check */
1676 
1677     } else if(scope_master && rsc->fns->state(rsc, TRUE) != RSC_ROLE_MASTER) {
1678         crm_trace("%s is already active on %s but not in correct state", rsc_id, dest->details->uname);
1679     } else if (safe_str_eq(current->details->uname, dest->details->uname)) {
1680         cur_is_dest = TRUE;
1681         if (do_force) {
1682             crm_info("%s is already %s on %s, reinforcing placement with location constraint.",
1683                      rsc_id, scope_master?"promoted":"active", dest->details->uname);
1684         } else {
1685             CMD_ERR("Error performing operation: %s is already %s on %s",
1686                     rsc_id, scope_master?"promoted":"active", dest->details->uname);
1687             return rc;
1688         }
1689     }
1690 
1691     /* Clear any previous constraints for 'dest' */
1692     cli_resource_clear(rsc_id, dest->details->uname, data_set->nodes, cib);
1693 
1694     /* Record an explicit preference for 'dest' */
1695     rc = cli_resource_prefer(rsc_id, dest->details->uname, cib);
1696 
1697     crm_trace("%s%s now prefers node %s%s",
1698               rsc->id, scope_master?" (master)":"", dest->details->uname, do_force?"(forced)":"");
1699 
1700     /* only ban the previous location if current location != destination location.
1701      * it is possible to use -M to enforce a location without regard of where the
1702      * resource is currently located */
1703     if(do_force && (cur_is_dest == FALSE)) {
1704         /* Ban the original location if possible */
1705         if(current) {
1706             (void)cli_resource_ban(rsc_id, current->details->uname, NULL, cib);
1707 
1708         } else if(count > 1) {
1709             CMD_ERR("Resource '%s' is currently %s in %d locations.  One may now move one to %s",
1710                     rsc_id, scope_master?"promoted":"active", count, dest->details->uname);
1711             CMD_ERR("You can prevent '%s' from being %s at a specific location with:"
1712                     " --ban %s--host <name>", rsc_id, scope_master?"promoted":"active", scope_master?"--master ":"");
1713 
1714         } else {
1715             crm_trace("Not banning %s from its current location: not active", rsc_id);
1716         }
1717     }
1718 
1719     return rc;
1720 }
1721 
1722 static void
1723 cli_resource_why_without_rsc_and_host(cib_t *cib_conn,GListPtr resources)
     /* [previous][next][first][last][top][bottom][index][help] */
1724 {
1725     GListPtr lpc = NULL;
1726     GListPtr hosts = NULL;
1727 
1728     for (lpc = resources; lpc != NULL; lpc = lpc->next) {
1729         resource_t *rsc = (resource_t *) lpc->data;
1730         rsc->fns->location(rsc, &hosts, TRUE);
1731 
1732         if (hosts == NULL) {
1733             printf("Resource %s is not running\n", rsc->id);
1734         } else {
1735             printf("Resource %s is running\n", rsc->id);
1736         }
1737 
1738         cli_resource_check(cib_conn, rsc);
1739         g_list_free(hosts);
1740         hosts = NULL;
1741      }
1742 
1743 }
1744 
1745 static void
1746 cli_resource_why_with_rsc_and_host(cib_t *cib_conn, GListPtr resources,
     /* [previous][next][first][last][top][bottom][index][help] */
1747                                    resource_t *rsc, const char *host_uname)
1748 {
1749     if (resource_is_running_on(rsc, host_uname)) {
1750         printf("Resource %s is running on host %s\n",rsc->id,host_uname);
1751     } else {
1752         printf("Resource %s is not running on host %s\n", rsc->id, host_uname);
1753     }
1754     cli_resource_check(cib_conn, rsc);
1755 }
1756 
1757 static void
1758 cli_resource_why_without_rsc_with_host(cib_t *cib_conn,GListPtr resources,node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
1759 {
1760     const char* host_uname =  node->details->uname;
1761     GListPtr allResources = node->details->allocated_rsc;
1762     GListPtr activeResources = node->details->running_rsc;
1763     GListPtr unactiveResources = subtract_lists(allResources,activeResources);
1764     GListPtr lpc = NULL;
1765 
1766     for (lpc = activeResources; lpc != NULL; lpc = lpc->next) {
1767         resource_t *rsc = (resource_t *) lpc->data;
1768         printf("Resource %s is running on host %s\n",rsc->id,host_uname);
1769         cli_resource_check(cib_conn,rsc);
1770     }
1771 
1772     for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) {
1773         resource_t *rsc = (resource_t *) lpc->data;
1774         printf("Resource %s is assigned to host %s but not running\n",
1775                rsc->id, host_uname);
1776         cli_resource_check(cib_conn,rsc);
1777      }
1778 
1779      g_list_free(allResources);
1780      g_list_free(activeResources);
1781      g_list_free(unactiveResources);
1782 }
1783 
1784 static void
1785 cli_resource_why_with_rsc_without_host(cib_t *cib_conn, GListPtr resources,
     /* [previous][next][first][last][top][bottom][index][help] */
1786                                        resource_t *rsc)
1787 {
1788     GListPtr hosts = NULL;
1789 
1790     rsc->fns->location(rsc, &hosts, TRUE);
1791     printf("Resource %s is %srunning\n", rsc->id, (hosts? "" : "not "));
1792     cli_resource_check(cib_conn, rsc);
1793     g_list_free(hosts);
1794 }
1795 
1796 void cli_resource_why(cib_t *cib_conn, GListPtr resources, resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
1797                       node_t *node)
1798 {
1799     const char *host_uname = (node == NULL)? NULL : node->details->uname;
1800 
1801     if ((rsc == NULL) && (host_uname == NULL)) {
1802         cli_resource_why_without_rsc_and_host(cib_conn, resources);
1803 
1804     } else if ((rsc != NULL) && (host_uname != NULL)) {
1805         cli_resource_why_with_rsc_and_host(cib_conn, resources, rsc,
1806                                            host_uname);
1807 
1808     } else if ((rsc == NULL) && (host_uname != NULL)) {
1809         cli_resource_why_without_rsc_with_host(cib_conn, resources, node);
1810 
1811     } else if ((rsc != NULL) && (host_uname == NULL)) {
1812         cli_resource_why_with_rsc_without_host(cib_conn, resources, rsc);
1813     }
1814 }

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