root/lib/pengine/common.c

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

DEFINITIONS

This source file includes following definitions.
  1. check_placement_strategy
  2. pe_metadata
  3. verify_pe_options
  4. pe_pref
  5. fail2text
  6. text2task
  7. task2text
  8. role2text
  9. text2role
  10. add_hash_param
  11. pe_node_attribute_calculated
  12. pe_node_attribute_raw

   1 /*
   2  * Copyright 2004-2022 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 #include <crm/crm.h>
  12 #include <crm/msg_xml.h>
  13 #include <crm/common/xml.h>
  14 #include <crm/common/util.h>
  15 
  16 #include <glib.h>
  17 
  18 #include <crm/pengine/internal.h>
  19 
  20 gboolean was_processing_error = FALSE;
  21 gboolean was_processing_warning = FALSE;
  22 
  23 static bool
  24 check_placement_strategy(const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
  25 {
  26     return pcmk__strcase_any_of(value, "default", "utilization", "minimal",
  27                            "balanced", NULL);
  28 }
  29 
  30 static pcmk__cluster_option_t pe_opts[] = {
  31     /* name, old name, type, allowed values,
  32      * default value, validator,
  33      * short description,
  34      * long description
  35      */
  36     {
  37         "no-quorum-policy", NULL, "select", "stop, freeze, ignore, demote, suicide",
  38         "stop", pcmk__valid_quorum,
  39         N_("What to do when the cluster does not have quorum"),
  40         NULL
  41     },
  42     {
  43         "symmetric-cluster", NULL, "boolean", NULL,
  44         "true", pcmk__valid_boolean,
  45         N_("Whether resources can run on any node by default"),
  46         NULL
  47     },
  48     {
  49         "maintenance-mode", NULL, "boolean", NULL,
  50         "false", pcmk__valid_boolean,
  51         N_("Whether the cluster should refrain from monitoring, starting, "
  52             "and stopping resources"),
  53         NULL
  54     },
  55     {
  56         "start-failure-is-fatal", NULL, "boolean", NULL,
  57         "true", pcmk__valid_boolean,
  58         N_("Whether a start failure should prevent a resource from being "
  59             "recovered on the same node"),
  60         N_("When true, the cluster will immediately ban a resource from a node "
  61             "if it fails to start there. When false, the cluster will instead "
  62             "check the resource's fail count against its migration-threshold.")
  63     },
  64     {
  65         "enable-startup-probes", NULL, "boolean", NULL,
  66         "true", pcmk__valid_boolean,
  67         N_("Whether the cluster should check for active resources during start-up"),
  68         NULL
  69     },
  70     {
  71         XML_CONFIG_ATTR_SHUTDOWN_LOCK, NULL, "boolean", NULL,
  72         "false", pcmk__valid_boolean,
  73         N_("Whether to lock resources to a cleanly shut down node"),
  74         N_("When true, resources active on a node when it is cleanly shut down "
  75             "are kept \"locked\" to that node (not allowed to run elsewhere) "
  76             "until they start again on that node after it rejoins (or for at "
  77             "most shutdown-lock-limit, if set). Stonith resources and "
  78             "Pacemaker Remote connections are never locked. Clone and bundle "
  79             "instances and the promoted role of promotable clones are currently"
  80             " never locked, though support could be added in a future release.")
  81     },
  82     {
  83         XML_CONFIG_ATTR_SHUTDOWN_LOCK_LIMIT, NULL, "time", NULL,
  84         "0", pcmk__valid_interval_spec,
  85         N_("Do not lock resources to a cleanly shut down node longer than this"),
  86         N_("If shutdown-lock is true and this is set to a nonzero time duration, "
  87             "shutdown locks will expire after this much time has passed since "
  88             "the shutdown was initiated, even if the node has not rejoined.")
  89     },
  90 
  91     // Fencing-related options
  92     {
  93         "stonith-enabled", NULL, "boolean", NULL,
  94         "true", pcmk__valid_boolean,
  95         "*** Advanced Use Only *** "
  96             "Whether nodes may be fenced as part of recovery",
  97         "If false, unresponsive nodes are immediately assumed to be harmless, "
  98             "and resources that were active on them may be recovered "
  99             "elsewhere. This can result in a \"split-brain\" situation, "
 100             "potentially leading to data loss and/or service unavailability."
 101     },
 102     {
 103         "stonith-action", NULL, "select", "reboot, off, poweroff",
 104         "reboot", pcmk__is_fencing_action,
 105         "Action to send to fence device when a node needs to be fenced "
 106             "(\"poweroff\" is a deprecated alias for \"off\")",
 107         NULL
 108     },
 109     {
 110         "stonith-timeout", NULL, "time", NULL,
 111         "60s", pcmk__valid_interval_spec,
 112         "*** Advanced Use Only *** Unused by Pacemaker",
 113         "This value is not used by Pacemaker, but is kept for backward "
 114             "compatibility, and certain legacy fence agents might use it."
 115     },
 116     {
 117         XML_ATTR_HAVE_WATCHDOG, NULL, "boolean", NULL,
 118         "false", pcmk__valid_boolean,
 119         N_("Whether watchdog integration is enabled"),
 120         "This is set automatically by the cluster according to whether SBD "
 121             "is detected to be in use. User-configured values are ignored. "
 122             "The value `true` is meaningful if diskless SBD is used and "
 123             "`stonith-watchdog-timeout` is nonzero. In that case, if fencing "
 124             "is required, watchdog-based self-fencing will be performed via "
 125             "SBD without requiring a fencing resource explicitly configured."
 126     },
 127     {
 128         "concurrent-fencing", NULL, "boolean", NULL,
 129         PCMK__CONCURRENT_FENCING_DEFAULT, pcmk__valid_boolean,
 130         "Allow performing fencing operations in parallel",
 131         NULL
 132     },
 133     {
 134         "startup-fencing", NULL, "boolean", NULL,
 135         "true", pcmk__valid_boolean,
 136         "*** Advanced Use Only *** Whether to fence unseen nodes at start-up",
 137         "Setting this to false may lead to a \"split-brain\" situation,"
 138             "potentially leading to data loss and/or service unavailability."
 139     },
 140     {
 141         XML_CONFIG_ATTR_PRIORITY_FENCING_DELAY, NULL, "time", NULL,
 142         "0", pcmk__valid_interval_spec,
 143         "Apply fencing delay targeting the lost nodes with the highest total resource priority",
 144         "Apply specified delay for the fencings that are targeting the lost "
 145             "nodes with the highest total resource priority in case we don't "
 146             "have the majority of the nodes in our cluster partition, so that "
 147             "the more significant nodes potentially win any fencing match, "
 148             "which is especially meaningful under split-brain of 2-node "
 149             "cluster. A promoted resource instance takes the base priority + 1 "
 150             "on calculation if the base priority is not 0. Any static/random "
 151             "delays that are introduced by `pcmk_delay_base/max` configured "
 152             "for the corresponding fencing resources will be added to this "
 153             "delay. This delay should be significantly greater than, safely "
 154             "twice, the maximum `pcmk_delay_base/max`. By default, priority "
 155             "fencing delay is disabled."
 156     },
 157 
 158     {
 159         "cluster-delay", NULL, "time", NULL,
 160         "60s", pcmk__valid_interval_spec,
 161         "Maximum time for node-to-node communication",
 162         "The node elected Designated Controller (DC) will consider an action "
 163             "failed if it does not get a response from the node executing the "
 164             "action within this time (after considering the action's own "
 165             "timeout). The \"correct\" value will depend on the speed and "
 166             "load of your network and cluster nodes."
 167     },
 168     {
 169         "batch-limit", NULL, "integer", NULL,
 170         "0", pcmk__valid_number,
 171         "Maximum number of jobs that the cluster may execute in parallel "
 172             "across all nodes",
 173         "The \"correct\" value will depend on the speed and load of your "
 174             "network and cluster nodes. If set to 0, the cluster will "
 175             "impose a dynamically calculated limit when any node has a "
 176             "high load."
 177     },
 178     {
 179         "migration-limit", NULL, "integer", NULL,
 180         "-1", pcmk__valid_number,
 181         "The number of live migration actions that the cluster is allowed "
 182             "to execute in parallel on a node (-1 means no limit)"
 183     },
 184 
 185     /* Orphans and stopping */
 186     {
 187         "stop-all-resources", NULL, "boolean", NULL,
 188         "false", pcmk__valid_boolean,
 189         N_("Whether the cluster should stop all active resources"),
 190         NULL
 191     },
 192     {
 193         "stop-orphan-resources", NULL, "boolean", NULL,
 194         "true", pcmk__valid_boolean,
 195         N_("Whether to stop resources that were removed from the configuration"),
 196         NULL
 197     },
 198     {
 199         "stop-orphan-actions", NULL, "boolean", NULL,
 200         "true", pcmk__valid_boolean,
 201         N_("Whether to cancel recurring actions removed from the configuration"),
 202         NULL
 203     },
 204     {
 205         "remove-after-stop", NULL, "boolean", NULL,
 206         "false", pcmk__valid_boolean,
 207         N_("*** Deprecated *** Whether to remove stopped resources from "
 208             "the executor"),
 209         "Values other than default are poorly tested and potentially dangerous."
 210             " This option will be removed in a future release."
 211     },
 212 
 213     /* Storing inputs */
 214     {
 215         "pe-error-series-max", NULL, "integer", NULL,
 216         "-1", pcmk__valid_number,
 217         "The number of scheduler inputs resulting in errors to save",
 218         "Zero to disable, -1 to store unlimited."
 219     },
 220     {
 221         "pe-warn-series-max",  NULL, "integer", NULL,
 222         "5000", pcmk__valid_number,
 223         "The number of scheduler inputs resulting in warnings to save",
 224         "Zero to disable, -1 to store unlimited."
 225     },
 226     {
 227         "pe-input-series-max", NULL, "integer", NULL,
 228         "4000", pcmk__valid_number,
 229         "The number of scheduler inputs without errors or warnings to save",
 230         "Zero to disable, -1 to store unlimited."
 231     },
 232 
 233     /* Node health */
 234     {
 235         PCMK__OPT_NODE_HEALTH_STRATEGY, NULL, "select",
 236         PCMK__VALUE_NONE ", " PCMK__VALUE_MIGRATE_ON_RED ", "
 237             PCMK__VALUE_ONLY_GREEN ", " PCMK__VALUE_PROGRESSIVE ", "
 238             PCMK__VALUE_CUSTOM,
 239         PCMK__VALUE_NONE, pcmk__validate_health_strategy,
 240         "How cluster should react to node health attributes",
 241         "Requires external entities to create node attributes (named with "
 242             "the prefix \"#health\") with values \"" PCMK__VALUE_RED "\", "
 243             "\"" PCMK__VALUE_YELLOW "\", or \"" PCMK__VALUE_GREEN "\"."
 244     },
 245     {
 246         PCMK__OPT_NODE_HEALTH_BASE, NULL, "integer", NULL,
 247         "0", pcmk__valid_number,
 248         "Base health score assigned to a node",
 249         "Only used when " PCMK__OPT_NODE_HEALTH_STRATEGY " is set to "
 250             PCMK__VALUE_PROGRESSIVE "."
 251     },
 252     {
 253         PCMK__OPT_NODE_HEALTH_GREEN, NULL, "integer", NULL,
 254         "0", pcmk__valid_number,
 255         "The score to use for a node health attribute whose value is \""
 256             PCMK__VALUE_GREEN "\"",
 257         "Only used when " PCMK__OPT_NODE_HEALTH_STRATEGY " is set to "
 258             PCMK__VALUE_CUSTOM " or " PCMK__VALUE_PROGRESSIVE "."
 259     },
 260     {
 261         PCMK__OPT_NODE_HEALTH_YELLOW, NULL, "integer", NULL,
 262         "0", pcmk__valid_number,
 263         "The score to use for a node health attribute whose value is \""
 264             PCMK__VALUE_YELLOW "\"",
 265         "Only used when " PCMK__OPT_NODE_HEALTH_STRATEGY " is set to "
 266             PCMK__VALUE_CUSTOM " or " PCMK__VALUE_PROGRESSIVE "."
 267     },
 268     {
 269         PCMK__OPT_NODE_HEALTH_RED, NULL, "integer", NULL,
 270         "-INFINITY", pcmk__valid_number,
 271         "The score to use for a node health attribute whose value is \""
 272             PCMK__VALUE_RED "\"",
 273         "Only used when " PCMK__OPT_NODE_HEALTH_STRATEGY " is set to "
 274             PCMK__VALUE_CUSTOM " or " PCMK__VALUE_PROGRESSIVE "."
 275     },
 276 
 277     /*Placement Strategy*/
 278     {
 279         "placement-strategy", NULL, "select",
 280         "default, utilization, minimal, balanced",
 281         "default", check_placement_strategy,
 282         "How the cluster should allocate resources to nodes",
 283         NULL
 284     },
 285 };
 286 
 287 void
 288 pe_metadata(pcmk__output_t *out)
     /* [previous][next][first][last][top][bottom][index][help] */
 289 {
 290     const char *desc_short = "Pacemaker scheduler options";
 291     const char *desc_long = "Cluster options used by Pacemaker's scheduler";
 292 
 293     gchar *s = pcmk__format_option_metadata("pacemaker-schedulerd", desc_short,
 294                                             desc_long, pe_opts,
 295                                             PCMK__NELEM(pe_opts));
 296     out->output_xml(out, "metadata", s);
 297     g_free(s);
 298 }
 299 
 300 void
 301 verify_pe_options(GHashTable * options)
     /* [previous][next][first][last][top][bottom][index][help] */
 302 {
 303     pcmk__validate_cluster_options(options, pe_opts, PCMK__NELEM(pe_opts));
 304 }
 305 
 306 const char *
 307 pe_pref(GHashTable * options, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 308 {
 309     return pcmk__cluster_option(options, pe_opts, PCMK__NELEM(pe_opts), name);
 310 }
 311 
 312 const char *
 313 fail2text(enum action_fail_response fail)
     /* [previous][next][first][last][top][bottom][index][help] */
 314 {
 315     const char *result = "<unknown>";
 316 
 317     switch (fail) {
 318         case action_fail_ignore:
 319             result = "ignore";
 320             break;
 321         case action_fail_demote:
 322             result = "demote";
 323             break;
 324         case action_fail_block:
 325             result = "block";
 326             break;
 327         case action_fail_recover:
 328             result = "recover";
 329             break;
 330         case action_fail_migrate:
 331             result = "migrate";
 332             break;
 333         case action_fail_stop:
 334             result = "stop";
 335             break;
 336         case action_fail_fence:
 337             result = "fence";
 338             break;
 339         case action_fail_standby:
 340             result = "standby";
 341             break;
 342         case action_fail_restart_container:
 343             result = "restart-container";
 344             break;
 345         case action_fail_reset_remote:
 346             result = "reset-remote";
 347             break;
 348     }
 349     return result;
 350 }
 351 
 352 enum action_tasks
 353 text2task(const char *task)
     /* [previous][next][first][last][top][bottom][index][help] */
 354 {
 355     if (pcmk__str_eq(task, CRMD_ACTION_STOP, pcmk__str_casei)) {
 356         return stop_rsc;
 357     } else if (pcmk__str_eq(task, CRMD_ACTION_STOPPED, pcmk__str_casei)) {
 358         return stopped_rsc;
 359     } else if (pcmk__str_eq(task, CRMD_ACTION_START, pcmk__str_casei)) {
 360         return start_rsc;
 361     } else if (pcmk__str_eq(task, CRMD_ACTION_STARTED, pcmk__str_casei)) {
 362         return started_rsc;
 363     } else if (pcmk__str_eq(task, CRM_OP_SHUTDOWN, pcmk__str_casei)) {
 364         return shutdown_crm;
 365     } else if (pcmk__str_eq(task, CRM_OP_FENCE, pcmk__str_casei)) {
 366         return stonith_node;
 367     } else if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)) {
 368         return monitor_rsc;
 369     } else if (pcmk__str_eq(task, CRMD_ACTION_NOTIFY, pcmk__str_casei)) {
 370         return action_notify;
 371     } else if (pcmk__str_eq(task, CRMD_ACTION_NOTIFIED, pcmk__str_casei)) {
 372         return action_notified;
 373     } else if (pcmk__str_eq(task, CRMD_ACTION_PROMOTE, pcmk__str_casei)) {
 374         return action_promote;
 375     } else if (pcmk__str_eq(task, CRMD_ACTION_DEMOTE, pcmk__str_casei)) {
 376         return action_demote;
 377     } else if (pcmk__str_eq(task, CRMD_ACTION_PROMOTED, pcmk__str_casei)) {
 378         return action_promoted;
 379     } else if (pcmk__str_eq(task, CRMD_ACTION_DEMOTED, pcmk__str_casei)) {
 380         return action_demoted;
 381     }
 382 #if SUPPORT_TRACING
 383     if (pcmk__str_eq(task, CRMD_ACTION_CANCEL, pcmk__str_casei)) {
 384         return no_action;
 385     } else if (pcmk__str_eq(task, CRMD_ACTION_DELETE, pcmk__str_casei)) {
 386         return no_action;
 387     } else if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)) {
 388         return no_action;
 389     } else if (pcmk__str_eq(task, CRMD_ACTION_MIGRATE, pcmk__str_casei)) {
 390         return no_action;
 391     } else if (pcmk__str_eq(task, CRMD_ACTION_MIGRATED, pcmk__str_casei)) {
 392         return no_action;
 393     }
 394     crm_trace("Unsupported action: %s", task);
 395 #endif
 396 
 397     return no_action;
 398 }
 399 
 400 const char *
 401 task2text(enum action_tasks task)
     /* [previous][next][first][last][top][bottom][index][help] */
 402 {
 403     const char *result = "<unknown>";
 404 
 405     switch (task) {
 406         case no_action:
 407             result = "no_action";
 408             break;
 409         case stop_rsc:
 410             result = CRMD_ACTION_STOP;
 411             break;
 412         case stopped_rsc:
 413             result = CRMD_ACTION_STOPPED;
 414             break;
 415         case start_rsc:
 416             result = CRMD_ACTION_START;
 417             break;
 418         case started_rsc:
 419             result = CRMD_ACTION_STARTED;
 420             break;
 421         case shutdown_crm:
 422             result = CRM_OP_SHUTDOWN;
 423             break;
 424         case stonith_node:
 425             result = CRM_OP_FENCE;
 426             break;
 427         case monitor_rsc:
 428             result = CRMD_ACTION_STATUS;
 429             break;
 430         case action_notify:
 431             result = CRMD_ACTION_NOTIFY;
 432             break;
 433         case action_notified:
 434             result = CRMD_ACTION_NOTIFIED;
 435             break;
 436         case action_promote:
 437             result = CRMD_ACTION_PROMOTE;
 438             break;
 439         case action_promoted:
 440             result = CRMD_ACTION_PROMOTED;
 441             break;
 442         case action_demote:
 443             result = CRMD_ACTION_DEMOTE;
 444             break;
 445         case action_demoted:
 446             result = CRMD_ACTION_DEMOTED;
 447             break;
 448     }
 449 
 450     return result;
 451 }
 452 
 453 const char *
 454 role2text(enum rsc_role_e role)
     /* [previous][next][first][last][top][bottom][index][help] */
 455 {
 456     switch (role) {
 457         case RSC_ROLE_UNKNOWN:
 458             return RSC_ROLE_UNKNOWN_S;
 459         case RSC_ROLE_STOPPED:
 460             return RSC_ROLE_STOPPED_S;
 461         case RSC_ROLE_STARTED:
 462             return RSC_ROLE_STARTED_S;
 463         case RSC_ROLE_UNPROMOTED:
 464 #ifdef PCMK__COMPAT_2_0
 465             return RSC_ROLE_UNPROMOTED_LEGACY_S;
 466 #else
 467             return RSC_ROLE_UNPROMOTED_S;
 468 #endif
 469         case RSC_ROLE_PROMOTED:
 470 #ifdef PCMK__COMPAT_2_0
 471             return RSC_ROLE_PROMOTED_LEGACY_S;
 472 #else
 473             return RSC_ROLE_PROMOTED_S;
 474 #endif
 475     }
 476     CRM_CHECK(role >= RSC_ROLE_UNKNOWN, return RSC_ROLE_UNKNOWN_S);
 477     CRM_CHECK(role < RSC_ROLE_MAX, return RSC_ROLE_UNKNOWN_S);
 478     // coverity[dead_error_line]
 479     return RSC_ROLE_UNKNOWN_S;
 480 }
 481 
 482 enum rsc_role_e
 483 text2role(const char *role)
     /* [previous][next][first][last][top][bottom][index][help] */
 484 {
 485     CRM_ASSERT(role != NULL);
 486     if (pcmk__str_eq(role, RSC_ROLE_STOPPED_S, pcmk__str_casei)) {
 487         return RSC_ROLE_STOPPED;
 488     } else if (pcmk__str_eq(role, RSC_ROLE_STARTED_S, pcmk__str_casei)) {
 489         return RSC_ROLE_STARTED;
 490     } else if (pcmk__strcase_any_of(role, RSC_ROLE_UNPROMOTED_S,
 491                                     RSC_ROLE_UNPROMOTED_LEGACY_S, NULL)) {
 492         return RSC_ROLE_UNPROMOTED;
 493     } else if (pcmk__strcase_any_of(role, RSC_ROLE_PROMOTED_S,
 494                                     RSC_ROLE_PROMOTED_LEGACY_S, NULL)) {
 495         return RSC_ROLE_PROMOTED;
 496     } else if (pcmk__str_eq(role, RSC_ROLE_UNKNOWN_S, pcmk__str_casei)) {
 497         return RSC_ROLE_UNKNOWN;
 498     }
 499     crm_err("Unknown role: %s", role);
 500     return RSC_ROLE_UNKNOWN;
 501 }
 502 
 503 void
 504 add_hash_param(GHashTable * hash, const char *name, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 505 {
 506     CRM_CHECK(hash != NULL, return);
 507 
 508     crm_trace("Adding name='%s' value='%s' to hash table",
 509               pcmk__s(name, "<null>"), pcmk__s(value, "<null>"));
 510     if (name == NULL || value == NULL) {
 511         return;
 512 
 513     } else if (pcmk__str_eq(value, "#default", pcmk__str_casei)) {
 514         return;
 515 
 516     } else if (g_hash_table_lookup(hash, name) == NULL) {
 517         g_hash_table_insert(hash, strdup(name), strdup(value));
 518     }
 519 }
 520 
 521 const char *
 522 pe_node_attribute_calculated(const pe_node_t *node, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
 523                              const pe_resource_t *rsc)
 524 {
 525     const char *source;
 526 
 527     if(node == NULL) {
 528         return NULL;
 529 
 530     } else if(rsc == NULL) {
 531         return g_hash_table_lookup(node->details->attrs, name);
 532     }
 533 
 534     source = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET);
 535     if(source == NULL || !pcmk__str_eq("host", source, pcmk__str_casei)) {
 536         return g_hash_table_lookup(node->details->attrs, name);
 537     }
 538 
 539     /* Use attributes set for the containers location
 540      * instead of for the container itself
 541      *
 542      * Useful when the container is using the host's local
 543      * storage
 544      */
 545 
 546     CRM_ASSERT(node->details->remote_rsc);
 547     CRM_ASSERT(node->details->remote_rsc->container);
 548 
 549     if(node->details->remote_rsc->container->running_on) {
 550         pe_node_t *host = node->details->remote_rsc->container->running_on->data;
 551         pe_rsc_trace(rsc, "%s: Looking for %s on the container host %s",
 552                      rsc->id, name, pe__node_name(host));
 553         return g_hash_table_lookup(host->details->attrs, name);
 554     }
 555 
 556     pe_rsc_trace(rsc, "%s: Not looking for %s on the container host: %s is inactive",
 557                  rsc->id, name, node->details->remote_rsc->container->id);
 558     return NULL;
 559 }
 560 
 561 const char *
 562 pe_node_attribute_raw(const pe_node_t *node, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 563 {
 564     if(node == NULL) {
 565         return NULL;
 566     }
 567     return g_hash_table_lookup(node->details->attrs, name);
 568 }

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