root/lib/services/services.c

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

DEFINITIONS

This source file includes following definitions.
  1. resources_find_service_class
  2. init_recurring_actions
  3. inflight_systemd_or_upstart
  4. expand_resource_class
  5. new_action
  6. required_argument_missing
  7. copy_action_arguments
  8. services__create_resource_action
  9. resources_action_create
  10. services_action_create_generic
  11. services_alert_create
  12. services_action_user
  13. services_alert_async
  14. services_set_op_pending
  15. services_action_cleanup
  16. services_result2ocf
  17. services_action_free
  18. cancel_recurring_action
  19. services_action_cancel
  20. services_action_kick
  21. handle_duplicate_recurring
  22. execute_action
  23. services_add_inflight_op
  24. services_untrack_op
  25. services_action_async_fork_notify
  26. services_action_async
  27. is_op_blocked
  28. handle_blocked_ops
  29. execute_metadata_action
  30. services_action_sync
  31. get_directory_list
  32. resources_list_standards
  33. resources_list_providers
  34. resources_list_agents
  35. resources_agent_exists
  36. services__set_result
  37. services__format_result
  38. services__set_cancelled
  39. services__action_kind
  40. services__exit_reason
  41. services__grab_stdout
  42. services__grab_stderr

   1 /*
   2  * Copyright 2010-2024 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 
  12 #ifndef _GNU_SOURCE
  13 #  define _GNU_SOURCE
  14 #endif
  15 
  16 #include <sys/types.h>
  17 #include <sys/stat.h>
  18 #include <stdio.h>
  19 #include <errno.h>
  20 #include <unistd.h>
  21 #include <dirent.h>
  22 #include <fcntl.h>
  23 
  24 #include <crm/crm.h>
  25 #include <crm/common/mainloop.h>
  26 #include <crm/services.h>
  27 #include <crm/services_internal.h>
  28 #include <crm/stonith-ng.h>
  29 #include <crm/common/xml.h>
  30 #include "services_private.h"
  31 #include "services_ocf.h"
  32 #include "services_lsb.h"
  33 
  34 #if SUPPORT_UPSTART
  35 #  include <upstart.h>
  36 #endif
  37 
  38 #if SUPPORT_SYSTEMD
  39 #  include <systemd.h>
  40 #endif
  41 
  42 #if SUPPORT_NAGIOS
  43 #  include <services_nagios.h>
  44 #endif
  45 
  46 /* TODO: Develop a rollover strategy */
  47 
  48 static int operations = 0;
  49 static GHashTable *recurring_actions = NULL;
  50 
  51 /* ops waiting to run async because of conflicting active
  52  * pending ops */
  53 static GList *blocked_ops = NULL;
  54 
  55 /* ops currently active (in-flight) */
  56 static GList *inflight_ops = NULL;
  57 
  58 static void handle_blocked_ops(void);
  59 
  60 /*!
  61  * \brief Find first service class that can provide a specified agent
  62  *
  63  * \param[in] agent  Name of agent to search for
  64  *
  65  * \return Service class if found, NULL otherwise
  66  *
  67  * \note The priority is LSB, then systemd, then upstart. It would be preferable
  68  *       to put systemd first, but LSB merely requires a file existence check,
  69  *       while systemd requires contacting D-Bus.
  70  */
  71 const char *
  72 resources_find_service_class(const char *agent)
     /* [previous][next][first][last][top][bottom][index][help] */
  73 {
  74     if (services__lsb_agent_exists(agent)) {
  75         return PCMK_RESOURCE_CLASS_LSB;
  76     }
  77 
  78 #if SUPPORT_SYSTEMD
  79     if (systemd_unit_exists(agent)) {
  80         return PCMK_RESOURCE_CLASS_SYSTEMD;
  81     }
  82 #endif
  83 
  84 #if SUPPORT_UPSTART
  85     if (upstart_job_exists(agent)) {
  86         return PCMK_RESOURCE_CLASS_UPSTART;
  87     }
  88 #endif
  89     return NULL;
  90 }
  91 
  92 static inline void
  93 init_recurring_actions(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  94 {
  95     if (recurring_actions == NULL) {
  96         recurring_actions = pcmk__strkey_table(NULL, NULL);
  97     }
  98 }
  99 
 100 /*!
 101  * \internal
 102  * \brief Check whether op is in-flight systemd or upstart op
 103  *
 104  * \param[in] op  Operation to check
 105  *
 106  * \return TRUE if op is in-flight systemd or upstart op
 107  */
 108 static inline gboolean
 109 inflight_systemd_or_upstart(const svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 110 {
 111     return pcmk__strcase_any_of(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
 112                            PCMK_RESOURCE_CLASS_UPSTART, NULL) &&
 113            g_list_find(inflight_ops, op) != NULL;
 114 }
 115 
 116 /*!
 117  * \internal
 118  * \brief Expand "service" alias to an actual resource class
 119  *
 120  * \param[in] rsc       Resource name (for logging only)
 121  * \param[in] standard  Resource class as configured
 122  * \param[in] agent     Agent name to look for
 123  *
 124  * \return Newly allocated string with actual resource class
 125  *
 126  * \note The caller is responsible for calling free() on the result.
 127  */
 128 static char *
 129 expand_resource_class(const char *rsc, const char *standard, const char *agent)
     /* [previous][next][first][last][top][bottom][index][help] */
 130 {
 131     char *expanded_class = NULL;
 132 
 133     if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0) {
 134         const char *found_class = resources_find_service_class(agent);
 135 
 136         if (found_class) {
 137             crm_debug("Found %s agent %s for %s", found_class, agent, rsc);
 138             expanded_class = strdup(found_class);
 139         } else {
 140             crm_info("Assuming resource class lsb for agent %s for %s",
 141                      agent, rsc);
 142             expanded_class = strdup(PCMK_RESOURCE_CLASS_LSB);
 143         }
 144     } else {
 145         expanded_class = strdup(standard);
 146     }
 147     CRM_ASSERT(expanded_class);
 148     return expanded_class;
 149 }
 150 
 151 /*!
 152  * \internal
 153  * \brief Create a simple svc_action_t instance
 154  *
 155  * \return Newly allocated instance (or NULL if not enough memory)
 156  */
 157 static svc_action_t *
 158 new_action(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 159 {
 160     svc_action_t *op = calloc(1, sizeof(svc_action_t));
 161 
 162     if (op == NULL) {
 163         return NULL;
 164     }
 165 
 166     op->opaque = calloc(1, sizeof(svc_action_private_t));
 167     if (op->opaque == NULL) {
 168         free(op);
 169         return NULL;
 170     }
 171 
 172     // Initialize result
 173     services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_UNKNOWN, NULL);
 174     return op;
 175 }
 176 
 177 static bool
 178 required_argument_missing(uint32_t ra_caps, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
 179                           const char *standard, const char *provider,
 180                           const char *agent, const char *action)
 181 {
 182     if (pcmk__str_empty(name)) {
 183         crm_info("Cannot create operation without resource name (bug?)");
 184         return true;
 185     }
 186 
 187     if (pcmk__str_empty(standard)) {
 188         crm_info("Cannot create operation for %s without resource class (bug?)",
 189                  name);
 190         return true;
 191     }
 192 
 193     if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)
 194         && pcmk__str_empty(provider)) {
 195         crm_info("Cannot create operation for %s resource %s "
 196                  "without provider (bug?)", standard, name);
 197         return true;
 198     }
 199 
 200     if (pcmk__str_empty(agent)) {
 201         crm_info("Cannot create operation for %s without agent name (bug?)",
 202                  name);
 203         return true;
 204     }
 205 
 206     if (pcmk__str_empty(action)) {
 207         crm_info("Cannot create operation for %s without action name (bug?)",
 208                  name);
 209         return true;
 210     }
 211     return false;
 212 }
 213 
 214 // \return Standard Pacemaker return code (pcmk_rc_ok or ENOMEM)
 215 static int
 216 copy_action_arguments(svc_action_t *op, uint32_t ra_caps, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
 217                       const char *standard, const char *provider,
 218                       const char *agent, const char *action)
 219 {
 220     op->rsc = strdup(name);
 221     if (op->rsc == NULL) {
 222         return ENOMEM;
 223     }
 224 
 225     op->agent = strdup(agent);
 226     if (op->agent == NULL) {
 227         return ENOMEM;
 228     }
 229 
 230     op->standard = expand_resource_class(name, standard, agent);
 231     if (op->standard == NULL) {
 232         return ENOMEM;
 233     }
 234 
 235     if (pcmk_is_set(ra_caps, pcmk_ra_cap_status)
 236         && pcmk__str_eq(action, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
 237         action = PCMK_ACTION_STATUS;
 238     }
 239     op->action = strdup(action);
 240     if (op->action == NULL) {
 241         return ENOMEM;
 242     }
 243 
 244     if (pcmk_is_set(ra_caps, pcmk_ra_cap_provider)) {
 245         op->provider = strdup(provider);
 246         if (op->provider == NULL) {
 247             return ENOMEM;
 248         }
 249     }
 250     return pcmk_rc_ok;
 251 }
 252 
 253 svc_action_t *
 254 services__create_resource_action(const char *name, const char *standard,
     /* [previous][next][first][last][top][bottom][index][help] */
 255                         const char *provider, const char *agent,
 256                         const char *action, guint interval_ms, int timeout,
 257                         GHashTable *params, enum svc_action_flags flags)
 258 {
 259     svc_action_t *op = NULL;
 260     uint32_t ra_caps = pcmk_get_ra_caps(standard);
 261     int rc = pcmk_rc_ok;
 262 
 263     op = new_action();
 264     if (op == NULL) {
 265         crm_crit("Cannot prepare action: %s", strerror(ENOMEM));
 266         if (params != NULL) {
 267             g_hash_table_destroy(params);
 268         }
 269         return NULL;
 270     }
 271 
 272     op->interval_ms = interval_ms;
 273     op->timeout = timeout;
 274     op->flags = flags;
 275     op->sequence = ++operations;
 276 
 277     // Take ownership of params
 278     if (pcmk_is_set(ra_caps, pcmk_ra_cap_params)) {
 279         op->params = params;
 280     } else if (params != NULL) {
 281         g_hash_table_destroy(params);
 282         params = NULL;
 283     }
 284 
 285     if (required_argument_missing(ra_caps, name, standard, provider, agent,
 286                                   action)) {
 287         services__set_result(op, services__generic_error(op),
 288                              PCMK_EXEC_ERROR_FATAL,
 289                              "Required agent or action information missing");
 290         return op;
 291     }
 292 
 293     op->id = pcmk__op_key(name, action, interval_ms);
 294 
 295     if (copy_action_arguments(op, ra_caps, name, standard, provider, agent,
 296                               action) != pcmk_rc_ok) {
 297         crm_crit("Cannot prepare %s action for %s: %s",
 298                  action, name, strerror(ENOMEM));
 299         services__handle_exec_error(op, ENOMEM);
 300         return op;
 301     }
 302 
 303     if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
 304         rc = services__ocf_prepare(op);
 305 
 306     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
 307         rc = services__lsb_prepare(op);
 308 
 309 #if SUPPORT_SYSTEMD
 310     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
 311         rc = services__systemd_prepare(op);
 312 #endif
 313 #if SUPPORT_UPSTART
 314     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
 315         rc = services__upstart_prepare(op);
 316 #endif
 317 #if SUPPORT_NAGIOS
 318     } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
 319         rc = services__nagios_prepare(op);
 320 #endif
 321     } else {
 322         crm_info("Unknown resource standard: %s", op->standard);
 323         rc = ENOENT;
 324     }
 325 
 326     if (rc != pcmk_rc_ok) {
 327         crm_info("Cannot prepare %s operation for %s: %s",
 328                  action, name, strerror(rc));
 329         services__handle_exec_error(op, rc);
 330     }
 331     return op;
 332 }
 333 
 334 svc_action_t *
 335 resources_action_create(const char *name, const char *standard,
     /* [previous][next][first][last][top][bottom][index][help] */
 336                         const char *provider, const char *agent,
 337                         const char *action, guint interval_ms, int timeout,
 338                         GHashTable *params, enum svc_action_flags flags)
 339 {
 340     svc_action_t *op = services__create_resource_action(name, standard,
 341                             provider, agent, action, interval_ms, timeout,
 342                             params, flags);
 343     if (op == NULL || op->rc != 0) {
 344         services_action_free(op);
 345         return NULL;
 346     } else {
 347         // Preserve public API backward compatibility
 348         op->rc = PCMK_OCF_OK;
 349         op->status = PCMK_EXEC_DONE;
 350 
 351         return op;
 352     }
 353 }
 354 
 355 svc_action_t *
 356 services_action_create_generic(const char *exec, const char *args[])
     /* [previous][next][first][last][top][bottom][index][help] */
 357 {
 358     svc_action_t *op = new_action();
 359 
 360     pcmk__mem_assert(op);
 361 
 362     op->opaque->exec = strdup(exec);
 363     op->opaque->args[0] = strdup(exec);
 364     if ((op->opaque->exec == NULL) || (op->opaque->args[0] == NULL)) {
 365         crm_crit("Cannot prepare action for '%s': %s", exec, strerror(ENOMEM));
 366         services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 367                              strerror(ENOMEM));
 368         return op;
 369     }
 370 
 371     if (args == NULL) {
 372         return op;
 373     }
 374 
 375     for (int cur_arg = 1; args[cur_arg - 1] != NULL; cur_arg++) {
 376 
 377         if (cur_arg == PCMK__NELEM(op->opaque->args)) {
 378             crm_info("Cannot prepare action for '%s': Too many arguments",
 379                      exec);
 380             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR,
 381                                  PCMK_EXEC_ERROR_HARD, "Too many arguments");
 382             break;
 383         }
 384 
 385         op->opaque->args[cur_arg] = strdup(args[cur_arg - 1]);
 386         if (op->opaque->args[cur_arg] == NULL) {
 387             crm_crit("Cannot prepare action for '%s': %s",
 388                      exec, strerror(ENOMEM));
 389             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 390                                  strerror(ENOMEM));
 391             break;
 392         }
 393     }
 394 
 395     return op;
 396 }
 397 
 398 /*!
 399  * \brief Create an alert agent action
 400  *
 401  * \param[in] id        Alert ID
 402  * \param[in] exec      Path to alert agent executable
 403  * \param[in] timeout   Action timeout
 404  * \param[in] params    Parameters to use with action
 405  * \param[in] sequence  Action sequence number
 406  * \param[in] cb_data   Data to pass to callback function
 407  *
 408  * \return New action on success, NULL on error
 409  * \note It is the caller's responsibility to free cb_data.
 410  *       The caller should not free params explicitly.
 411  */
 412 svc_action_t *
 413 services_alert_create(const char *id, const char *exec, int timeout,
     /* [previous][next][first][last][top][bottom][index][help] */
 414                       GHashTable *params, int sequence, void *cb_data)
 415 {
 416     svc_action_t *action = services_action_create_generic(exec, NULL);
 417 
 418     action->id = pcmk__str_copy(id);
 419     action->standard = pcmk__str_copy(PCMK_RESOURCE_CLASS_ALERT);
 420     action->timeout = timeout;
 421     action->params = params;
 422     action->sequence = sequence;
 423     action->cb_data = cb_data;
 424     return action;
 425 }
 426 
 427 /*!
 428  * \brief Set the user and group that an action will execute as
 429  *
 430  * \param[in,out] op      Action to modify
 431  * \param[in]     user    Name of user to execute action as
 432  * \param[in]     group   Name of group to execute action as
 433  *
 434  * \return pcmk_ok on success, -errno otherwise
 435  *
 436  * \note This will have no effect unless the process executing the action runs
 437  *       as root, and the action is not a systemd or upstart action.
 438  *       We could implement this for systemd by adding User= and Group= to
 439  *       [Service] in the override file, but that seems more likely to cause
 440  *       problems than be useful.
 441  */
 442 int
 443 services_action_user(svc_action_t *op, const char *user)
     /* [previous][next][first][last][top][bottom][index][help] */
 444 {
 445     CRM_CHECK((op != NULL) && (user != NULL), return -EINVAL);
 446     return crm_user_lookup(user, &(op->opaque->uid), &(op->opaque->gid));
 447 }
 448 
 449 /*!
 450  * \brief Execute an alert agent action
 451  *
 452  * \param[in,out] action  Action to execute
 453  * \param[in]     cb      Function to call when action completes
 454  *
 455  * \return TRUE if the library will free action, FALSE otherwise
 456  *
 457  * \note If this function returns FALSE, it is the caller's responsibility to
 458  *       free the action with services_action_free(). However, unless someone
 459  *       intentionally creates a recurring alert action, this will never return
 460  *       FALSE.
 461  */
 462 gboolean
 463 services_alert_async(svc_action_t *action, void (*cb)(svc_action_t *op))
     /* [previous][next][first][last][top][bottom][index][help] */
 464 {
 465     action->synchronous = false;
 466     action->opaque->callback = cb;
 467     return services__execute_file(action) == pcmk_rc_ok;
 468 }
 469 
 470 #if HAVE_DBUS
 471 /*!
 472  * \internal
 473  * \brief Update operation's pending DBus call, unreferencing old one if needed
 474  *
 475  * \param[in,out] op       Operation to modify
 476  * \param[in]     pending  Pending call to set
 477  */
 478 void
 479 services_set_op_pending(svc_action_t *op, DBusPendingCall *pending)
     /* [previous][next][first][last][top][bottom][index][help] */
 480 {
 481     if (op->opaque->pending && (op->opaque->pending != pending)) {
 482         if (pending) {
 483             crm_info("Lost pending %s DBus call (%p)", op->id, op->opaque->pending);
 484         } else {
 485             crm_trace("Done with pending %s DBus call (%p)", op->id, op->opaque->pending);
 486         }
 487         dbus_pending_call_unref(op->opaque->pending);
 488     }
 489     op->opaque->pending = pending;
 490     if (pending) {
 491         crm_trace("Updated pending %s DBus call (%p)", op->id, pending);
 492     } else {
 493         crm_trace("Cleared pending %s DBus call", op->id);
 494     }
 495 }
 496 #endif
 497 
 498 void
 499 services_action_cleanup(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
 500 {
 501     if ((op == NULL) || (op->opaque == NULL)) {
 502         return;
 503     }
 504 
 505 #if HAVE_DBUS
 506     if(op->opaque->timerid != 0) {
 507         crm_trace("Removing timer for call %s to %s", op->action, op->rsc);
 508         g_source_remove(op->opaque->timerid);
 509         op->opaque->timerid = 0;
 510     }
 511 
 512     if(op->opaque->pending) {
 513         if (dbus_pending_call_get_completed(op->opaque->pending)) {
 514             // This should never be the case
 515             crm_warn("Result of %s op %s was unhandled",
 516                      op->standard, op->id);
 517         } else {
 518             crm_debug("Will ignore any result of canceled %s op %s",
 519                       op->standard, op->id);
 520         }
 521         dbus_pending_call_cancel(op->opaque->pending);
 522         services_set_op_pending(op, NULL);
 523     }
 524 #endif
 525 
 526     if (op->opaque->stderr_gsource) {
 527         mainloop_del_fd(op->opaque->stderr_gsource);
 528         op->opaque->stderr_gsource = NULL;
 529     }
 530 
 531     if (op->opaque->stdout_gsource) {
 532         mainloop_del_fd(op->opaque->stdout_gsource);
 533         op->opaque->stdout_gsource = NULL;
 534     }
 535 }
 536 
 537 /*!
 538  * \internal
 539  * \brief Map an actual resource action result to a standard OCF result
 540  *
 541  * \param[in] standard     Agent standard (must not be "service")
 542  * \param[in] action       Action that result is for
 543  * \param[in] exit_status  Actual agent exit status
 544  *
 545  * \return Standard OCF result
 546  */
 547 enum ocf_exitcode
 548 services_result2ocf(const char *standard, const char *action, int exit_status)
     /* [previous][next][first][last][top][bottom][index][help] */
 549 {
 550     if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
 551         return services__ocf2ocf(exit_status);
 552 
 553 #if SUPPORT_SYSTEMD
 554     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD,
 555                             pcmk__str_casei)) {
 556         return services__systemd2ocf(exit_status);
 557 #endif
 558 
 559 #if SUPPORT_UPSTART
 560     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART,
 561                             pcmk__str_casei)) {
 562         return services__upstart2ocf(exit_status);
 563 #endif
 564 
 565 #if SUPPORT_NAGIOS
 566     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS,
 567                             pcmk__str_casei)) {
 568         return services__nagios2ocf(exit_status);
 569 #endif
 570 
 571     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB,
 572                             pcmk__str_casei)) {
 573         return services__lsb2ocf(action, exit_status);
 574 
 575     } else {
 576         crm_warn("Treating result from unknown standard '%s' as OCF",
 577                  ((standard == NULL)? "unspecified" : standard));
 578         return services__ocf2ocf(exit_status);
 579     }
 580 }
 581 
 582 void
 583 services_action_free(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
 584 {
 585     unsigned int i;
 586 
 587     if (op == NULL) {
 588         return;
 589     }
 590 
 591     /* The operation should be removed from all tracking lists by this point.
 592      * If it's not, we have a bug somewhere, so bail. That may lead to a
 593      * memory leak, but it's better than a use-after-free segmentation fault.
 594      */
 595     CRM_CHECK(g_list_find(inflight_ops, op) == NULL, return);
 596     CRM_CHECK(g_list_find(blocked_ops, op) == NULL, return);
 597     CRM_CHECK((recurring_actions == NULL)
 598               || (g_hash_table_lookup(recurring_actions, op->id) == NULL),
 599               return);
 600 
 601     services_action_cleanup(op);
 602 
 603     if (op->opaque->repeat_timer) {
 604         g_source_remove(op->opaque->repeat_timer);
 605         op->opaque->repeat_timer = 0;
 606     }
 607 
 608     free(op->id);
 609     free(op->opaque->exec);
 610 
 611     for (i = 0; i < PCMK__NELEM(op->opaque->args); i++) {
 612         free(op->opaque->args[i]);
 613     }
 614 
 615     free(op->opaque->exit_reason);
 616     free(op->opaque);
 617     free(op->rsc);
 618     free(op->action);
 619 
 620     free(op->standard);
 621     free(op->agent);
 622     free(op->provider);
 623 
 624     free(op->stdout_data);
 625     free(op->stderr_data);
 626 
 627     if (op->params) {
 628         g_hash_table_destroy(op->params);
 629         op->params = NULL;
 630     }
 631 
 632     free(op);
 633 }
 634 
 635 gboolean
 636 cancel_recurring_action(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
 637 {
 638     crm_info("Cancelling %s operation %s", op->standard, op->id);
 639 
 640     if (recurring_actions) {
 641         g_hash_table_remove(recurring_actions, op->id);
 642     }
 643 
 644     if (op->opaque->repeat_timer) {
 645         g_source_remove(op->opaque->repeat_timer);
 646         op->opaque->repeat_timer = 0;
 647     }
 648 
 649     return TRUE;
 650 }
 651 
 652 /*!
 653  * \brief Cancel a recurring action
 654  *
 655  * \param[in] name         Name of resource that operation is for
 656  * \param[in] action       Name of operation to cancel
 657  * \param[in] interval_ms  Interval of operation to cancel
 658  *
 659  * \return TRUE if action was successfully cancelled, FALSE otherwise
 660  */
 661 gboolean
 662 services_action_cancel(const char *name, const char *action, guint interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 663 {
 664     gboolean cancelled = FALSE;
 665     char *id = pcmk__op_key(name, action, interval_ms);
 666     svc_action_t *op = NULL;
 667 
 668     /* We can only cancel a recurring action */
 669     init_recurring_actions();
 670     op = g_hash_table_lookup(recurring_actions, id);
 671     if (op == NULL) {
 672         goto done;
 673     }
 674 
 675     // Tell services__finalize_async_op() not to reschedule the operation
 676     op->cancel = TRUE;
 677 
 678     /* Stop tracking it as a recurring operation, and stop its repeat timer */
 679     cancel_recurring_action(op);
 680 
 681     /* If the op has a PID, it's an in-flight child process, so kill it.
 682      *
 683      * Whether the kill succeeds or fails, the main loop will send the op to
 684      * async_action_complete() (and thus services__finalize_async_op()) when the
 685      * process goes away.
 686      */
 687     if (op->pid != 0) {
 688         crm_info("Terminating in-flight op %s[%d] early because it was cancelled",
 689                  id, op->pid);
 690         cancelled = mainloop_child_kill(op->pid);
 691         if (cancelled == FALSE) {
 692             crm_err("Termination of %s[%d] failed", id, op->pid);
 693         }
 694         goto done;
 695     }
 696 
 697 #if HAVE_DBUS
 698     // In-flight systemd and upstart ops don't have a pid
 699     if (inflight_systemd_or_upstart(op)) {
 700         inflight_ops = g_list_remove(inflight_ops, op);
 701 
 702         /* This will cause any result that comes in later to be discarded, so we
 703          * don't call the callback and free the operation twice.
 704          */
 705         services_action_cleanup(op);
 706     }
 707 #endif
 708 
 709     /* The rest of this is essentially equivalent to
 710      * services__finalize_async_op(), minus the handle_blocked_ops() call.
 711      */
 712 
 713     // Report operation as cancelled
 714     services__set_cancelled(op);
 715     if (op->opaque->callback) {
 716         op->opaque->callback(op);
 717     }
 718 
 719     blocked_ops = g_list_remove(blocked_ops, op);
 720     services_action_free(op);
 721     cancelled = TRUE;
 722     // @TODO Initiate handle_blocked_ops() asynchronously
 723 
 724 done:
 725     free(id);
 726     return cancelled;
 727 }
 728 
 729 gboolean
 730 services_action_kick(const char *name, const char *action, guint interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 731 {
 732     svc_action_t * op = NULL;
 733     char *id = pcmk__op_key(name, action, interval_ms);
 734 
 735     init_recurring_actions();
 736     op = g_hash_table_lookup(recurring_actions, id);
 737     free(id);
 738 
 739     if (op == NULL) {
 740         return FALSE;
 741     }
 742 
 743 
 744     if (op->pid || inflight_systemd_or_upstart(op)) {
 745         return TRUE;
 746     } else {
 747         if (op->opaque->repeat_timer) {
 748             g_source_remove(op->opaque->repeat_timer);
 749             op->opaque->repeat_timer = 0;
 750         }
 751         recurring_action_timer(op);
 752         return TRUE;
 753     }
 754 
 755 }
 756 
 757 /*!
 758  * \internal
 759  * \brief Add a new recurring operation, checking for duplicates
 760  *
 761  * \param[in,out] op  Operation to add
 762  *
 763  * \return TRUE if duplicate found (and reschedule), FALSE otherwise
 764  */
 765 static gboolean
 766 handle_duplicate_recurring(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 767 {
 768     svc_action_t * dup = NULL;
 769 
 770     /* check for duplicates */
 771     dup = g_hash_table_lookup(recurring_actions, op->id);
 772 
 773     if (dup && (dup != op)) {
 774         /* update user data */
 775         if (op->opaque->callback) {
 776             dup->opaque->callback = op->opaque->callback;
 777             dup->cb_data = op->cb_data;
 778             op->cb_data = NULL;
 779         }
 780         /* immediately execute the next interval */
 781         if (dup->pid != 0) {
 782             if (op->opaque->repeat_timer) {
 783                 g_source_remove(op->opaque->repeat_timer);
 784                 op->opaque->repeat_timer = 0;
 785             }
 786             recurring_action_timer(dup);
 787         }
 788         /* free the duplicate */
 789         services_action_free(op);
 790         return TRUE;
 791     }
 792 
 793     return FALSE;
 794 }
 795 
 796 /*!
 797  * \internal
 798  * \brief Execute an action appropriately according to its standard
 799  *
 800  * \param[in,out] op  Action to execute
 801  *
 802  * \return Standard Pacemaker return code
 803  * \retval EBUSY          Recurring operation could not be initiated
 804  * \retval pcmk_rc_error  Synchronous action failed
 805  * \retval pcmk_rc_ok     Synchronous action succeeded, or asynchronous action
 806  *                        should not be freed (because it's pending or because
 807  *                        it failed to execute and was already freed)
 808  *
 809  * \note If the return value for an asynchronous action is not pcmk_rc_ok, the
 810  *       caller is responsible for freeing the action.
 811  */
 812 static int
 813 execute_action(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 814 {
 815 #if SUPPORT_UPSTART
 816     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_UPSTART,
 817                      pcmk__str_casei)) {
 818         return services__execute_upstart(op);
 819     }
 820 #endif
 821 
 822 #if SUPPORT_SYSTEMD
 823     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
 824                      pcmk__str_casei)) {
 825         return services__execute_systemd(op);
 826     }
 827 #endif
 828 
 829     return services__execute_file(op);
 830 }
 831 
 832 void
 833 services_add_inflight_op(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
 834 {
 835     if (op == NULL) {
 836         return;
 837     }
 838 
 839     CRM_ASSERT(op->synchronous == FALSE);
 840 
 841     /* keep track of ops that are in-flight to avoid collisions in the same namespace */
 842     if (op->rsc) {
 843         inflight_ops = g_list_append(inflight_ops, op);
 844     }
 845 }
 846 
 847 /*!
 848  * \internal
 849  * \brief Stop tracking an operation that completed
 850  *
 851  * \param[in] op  Operation to stop tracking
 852  */
 853 void
 854 services_untrack_op(const svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 855 {
 856     /* Op is no longer in-flight or blocked */
 857     inflight_ops = g_list_remove(inflight_ops, op);
 858     blocked_ops = g_list_remove(blocked_ops, op);
 859 
 860     /* Op is no longer blocking other ops, so check if any need to run */
 861     handle_blocked_ops();
 862 }
 863 
 864 gboolean
 865 services_action_async_fork_notify(svc_action_t * op,
     /* [previous][next][first][last][top][bottom][index][help] */
 866                                   void (*action_callback) (svc_action_t *),
 867                                   void (*action_fork_callback) (svc_action_t *))
 868 {
 869     CRM_CHECK(op != NULL, return TRUE);
 870 
 871     op->synchronous = false;
 872     if (action_callback != NULL) {
 873         op->opaque->callback = action_callback;
 874     }
 875     if (action_fork_callback != NULL) {
 876         op->opaque->fork_callback = action_fork_callback;
 877     }
 878 
 879     if (op->interval_ms > 0) {
 880         init_recurring_actions();
 881         if (handle_duplicate_recurring(op)) {
 882             /* entry rescheduled, dup freed */
 883             /* exit early */
 884             return TRUE;
 885         }
 886         g_hash_table_replace(recurring_actions, op->id, op);
 887     }
 888 
 889     if (!pcmk_is_set(op->flags, SVC_ACTION_NON_BLOCKED)
 890         && op->rsc && is_op_blocked(op->rsc)) {
 891         blocked_ops = g_list_append(blocked_ops, op);
 892         return TRUE;
 893     }
 894 
 895     return execute_action(op) == pcmk_rc_ok;
 896 }
 897 
 898 gboolean
 899 services_action_async(svc_action_t * op,
     /* [previous][next][first][last][top][bottom][index][help] */
 900                       void (*action_callback) (svc_action_t *))
 901 {
 902     return services_action_async_fork_notify(op, action_callback, NULL);
 903 }
 904 
 905 static gboolean processing_blocked_ops = FALSE;
 906 
 907 gboolean
 908 is_op_blocked(const char *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 909 {
 910     GList *gIter = NULL;
 911     svc_action_t *op = NULL;
 912 
 913     for (gIter = inflight_ops; gIter != NULL; gIter = gIter->next) {
 914         op = gIter->data;
 915         if (pcmk__str_eq(op->rsc, rsc, pcmk__str_casei)) {
 916             return TRUE;
 917         }
 918     }
 919 
 920     return FALSE;
 921 }
 922 
 923 static void
 924 handle_blocked_ops(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 925 {
 926     GList *executed_ops = NULL;
 927     GList *gIter = NULL;
 928     svc_action_t *op = NULL;
 929 
 930     if (processing_blocked_ops) {
 931         /* avoid nested calling of this function */
 932         return;
 933     }
 934 
 935     processing_blocked_ops = TRUE;
 936 
 937     /* n^2 operation here, but blocked ops are incredibly rare. this list
 938      * will be empty 99% of the time. */
 939     for (gIter = blocked_ops; gIter != NULL; gIter = gIter->next) {
 940         op = gIter->data;
 941         if (is_op_blocked(op->rsc)) {
 942             continue;
 943         }
 944         executed_ops = g_list_append(executed_ops, op);
 945         if (execute_action(op) != pcmk_rc_ok) {
 946             /* this can cause this function to be called recursively
 947              * which is why we have processing_blocked_ops static variable */
 948             services__finalize_async_op(op);
 949         }
 950     }
 951 
 952     for (gIter = executed_ops; gIter != NULL; gIter = gIter->next) {
 953         op = gIter->data;
 954         blocked_ops = g_list_remove(blocked_ops, op);
 955     }
 956     g_list_free(executed_ops);
 957 
 958     processing_blocked_ops = FALSE;
 959 }
 960 
 961 /*!
 962  * \internal
 963  * \brief Execute a meta-data action appropriately to standard
 964  *
 965  * \param[in,out] op  Meta-data action to execute
 966  *
 967  * \return Standard Pacemaker return code
 968  */
 969 static int
 970 execute_metadata_action(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 971 {
 972     const char *class = op->standard;
 973 
 974     if (op->agent == NULL) {
 975         crm_info("Meta-data requested without specifying agent");
 976         services__set_result(op, services__generic_error(op),
 977                              PCMK_EXEC_ERROR_FATAL, "Agent not specified");
 978         return EINVAL;
 979     }
 980 
 981     if (class == NULL) {
 982         crm_info("Meta-data requested for agent %s without specifying class",
 983                 op->agent);
 984         services__set_result(op, services__generic_error(op),
 985                              PCMK_EXEC_ERROR_FATAL,
 986                              "Agent standard not specified");
 987         return EINVAL;
 988     }
 989 
 990     if (!strcmp(class, PCMK_RESOURCE_CLASS_SERVICE)) {
 991         class = resources_find_service_class(op->agent);
 992     }
 993     if (class == NULL) {
 994         crm_info("Meta-data requested for %s, but could not determine class",
 995                  op->agent);
 996         services__set_result(op, services__generic_error(op),
 997                              PCMK_EXEC_ERROR_HARD,
 998                              "Agent standard could not be determined");
 999         return EINVAL;
1000     }
1001 
1002     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
1003         return pcmk_legacy2rc(services__get_lsb_metadata(op->agent,
1004                                                          &op->stdout_data));
1005     }
1006 
1007 #if SUPPORT_NAGIOS
1008     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
1009         return pcmk_legacy2rc(services__get_nagios_metadata(op->agent,
1010                                                             &op->stdout_data));
1011     }
1012 #endif
1013 
1014     return execute_action(op);
1015 }
1016 
1017 gboolean
1018 services_action_sync(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
1019 {
1020     gboolean rc = TRUE;
1021 
1022     if (op == NULL) {
1023         crm_trace("No operation to execute");
1024         return FALSE;
1025     }
1026 
1027     op->synchronous = true;
1028 
1029     if (pcmk__str_eq(op->action, PCMK_ACTION_META_DATA, pcmk__str_casei)) {
1030         /* Synchronous meta-data operations are handled specially. Since most
1031          * resource classes don't provide any meta-data, it has to be
1032          * synthesized from available information about the agent.
1033          *
1034          * services_action_async() doesn't treat meta-data actions specially, so
1035          * it will result in an error for classes that don't support the action.
1036          */
1037         rc = (execute_metadata_action(op) == pcmk_rc_ok);
1038     } else {
1039         rc = (execute_action(op) == pcmk_rc_ok);
1040     }
1041     crm_trace(" > " PCMK__OP_FMT ": %s = %d",
1042               op->rsc, op->action, op->interval_ms, op->opaque->exec, op->rc);
1043     if (op->stdout_data) {
1044         crm_trace(" >  stdout: %s", op->stdout_data);
1045     }
1046     if (op->stderr_data) {
1047         crm_trace(" >  stderr: %s", op->stderr_data);
1048     }
1049     return rc;
1050 }
1051 
1052 GList *
1053 get_directory_list(const char *root, gboolean files, gboolean executable)
     /* [previous][next][first][last][top][bottom][index][help] */
1054 {
1055     return services_os_get_directory_list(root, files, executable);
1056 }
1057 
1058 GList *
1059 resources_list_standards(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1060 {
1061     GList *standards = NULL;
1062 
1063     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_OCF));
1064     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_LSB));
1065     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_SERVICE));
1066 
1067 #if SUPPORT_SYSTEMD
1068     {
1069         GList *agents = systemd_unit_listall();
1070 
1071         if (agents != NULL) {
1072             standards = g_list_append(standards,
1073                                       strdup(PCMK_RESOURCE_CLASS_SYSTEMD));
1074             g_list_free_full(agents, free);
1075         }
1076     }
1077 #endif
1078 
1079 #if SUPPORT_UPSTART
1080     {
1081         GList *agents = upstart_job_listall();
1082 
1083         if (agents != NULL) {
1084             standards = g_list_append(standards,
1085                                       strdup(PCMK_RESOURCE_CLASS_UPSTART));
1086             g_list_free_full(agents, free);
1087         }
1088     }
1089 #endif
1090 
1091 #if SUPPORT_NAGIOS
1092     {
1093         GList *agents = services__list_nagios_agents();
1094 
1095         if (agents != NULL) {
1096             standards = g_list_append(standards,
1097                                       strdup(PCMK_RESOURCE_CLASS_NAGIOS));
1098             g_list_free_full(agents, free);
1099         }
1100     }
1101 #endif
1102 
1103     return standards;
1104 }
1105 
1106 GList *
1107 resources_list_providers(const char *standard)
     /* [previous][next][first][last][top][bottom][index][help] */
1108 {
1109     if (pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider)) {
1110         return resources_os_list_ocf_providers();
1111     }
1112 
1113     return NULL;
1114 }
1115 
1116 GList *
1117 resources_list_agents(const char *standard, const char *provider)
     /* [previous][next][first][last][top][bottom][index][help] */
1118 {
1119     if ((standard == NULL)
1120         || (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0)) {
1121 
1122         GList *tmp1;
1123         GList *tmp2;
1124         GList *result = services__list_lsb_agents();
1125 
1126         if (standard == NULL) {
1127             tmp1 = result;
1128             tmp2 = resources_os_list_ocf_agents(NULL);
1129             if (tmp2) {
1130                 result = g_list_concat(tmp1, tmp2);
1131             }
1132         }
1133 #if SUPPORT_SYSTEMD
1134         tmp1 = result;
1135         tmp2 = systemd_unit_listall();
1136         if (tmp2) {
1137             result = g_list_concat(tmp1, tmp2);
1138         }
1139 #endif
1140 
1141 #if SUPPORT_UPSTART
1142         tmp1 = result;
1143         tmp2 = upstart_job_listall();
1144         if (tmp2) {
1145             result = g_list_concat(tmp1, tmp2);
1146         }
1147 #endif
1148 
1149         return result;
1150 
1151     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
1152         return resources_os_list_ocf_agents(provider);
1153     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
1154         return services__list_lsb_agents();
1155 #if SUPPORT_SYSTEMD
1156     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
1157         return systemd_unit_listall();
1158 #endif
1159 #if SUPPORT_UPSTART
1160     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
1161         return upstart_job_listall();
1162 #endif
1163 #if SUPPORT_NAGIOS
1164     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
1165         return services__list_nagios_agents();
1166 #endif
1167     }
1168 
1169     return NULL;
1170 }
1171 
1172 gboolean
1173 resources_agent_exists(const char *standard, const char *provider, const char *agent)
     /* [previous][next][first][last][top][bottom][index][help] */
1174 {
1175     GList *standards = NULL;
1176     GList *providers = NULL;
1177     GList *iter = NULL;
1178     gboolean rc = FALSE;
1179     gboolean has_providers = FALSE;
1180 
1181     standards = resources_list_standards();
1182     for (iter = standards; iter != NULL; iter = iter->next) {
1183         if (pcmk__str_eq(iter->data, standard, pcmk__str_none)) {
1184             rc = TRUE;
1185             break;
1186         }
1187     }
1188 
1189     if (rc == FALSE) {
1190         goto done;
1191     }
1192 
1193     rc = FALSE;
1194 
1195     has_providers = pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider);
1196     if (has_providers == TRUE && provider != NULL) {
1197         providers = resources_list_providers(standard);
1198         for (iter = providers; iter != NULL; iter = iter->next) {
1199             if (pcmk__str_eq(iter->data, provider, pcmk__str_none)) {
1200                 rc = TRUE;
1201                 break;
1202             }
1203         }
1204     } else if (has_providers == FALSE && provider == NULL) {
1205         rc = TRUE;
1206     }
1207 
1208     if (rc == FALSE) {
1209         goto done;
1210     }
1211 
1212     if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
1213         if (services__lsb_agent_exists(agent)) {
1214             rc = TRUE;
1215 #if SUPPORT_SYSTEMD
1216         } else if (systemd_unit_exists(agent)) {
1217             rc = TRUE;
1218 #endif
1219 
1220 #if SUPPORT_UPSTART
1221         } else if (upstart_job_exists(agent)) {
1222             rc = TRUE;
1223 #endif
1224         } else {
1225             rc = FALSE;
1226         }
1227 
1228     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
1229         rc = services__ocf_agent_exists(provider, agent);
1230 
1231     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
1232         rc = services__lsb_agent_exists(agent);
1233 
1234 #if SUPPORT_SYSTEMD
1235     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
1236         rc = systemd_unit_exists(agent);
1237 #endif
1238 
1239 #if SUPPORT_UPSTART
1240     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_casei)) {
1241         rc = upstart_job_exists(agent);
1242 #endif
1243 
1244 #if SUPPORT_NAGIOS
1245     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
1246         rc = services__nagios_agent_exists(agent);
1247 #endif
1248 
1249     } else {
1250         rc = FALSE;
1251     }
1252 
1253 done:
1254     g_list_free(standards);
1255     g_list_free(providers);
1256     return rc;
1257 }
1258 
1259 /*!
1260  * \internal
1261  * \brief Set the result of an action
1262  *
1263  * \param[out] action        Where to set action result
1264  * \param[in]  agent_status  Exit status to set
1265  * \param[in]  exec_status   Execution status to set
1266  * \param[in]  reason        Human-friendly description of event to set
1267  */
1268 void
1269 services__set_result(svc_action_t *action, int agent_status,
     /* [previous][next][first][last][top][bottom][index][help] */
1270                      enum pcmk_exec_status exec_status, const char *reason)
1271 {
1272     if (action == NULL) {
1273         return;
1274     }
1275 
1276     action->rc = agent_status;
1277     action->status = exec_status;
1278 
1279     if (!pcmk__str_eq(action->opaque->exit_reason, reason,
1280                       pcmk__str_none)) {
1281         free(action->opaque->exit_reason);
1282         action->opaque->exit_reason = (reason == NULL)? NULL : strdup(reason);
1283     }
1284 }
1285 
1286 /*!
1287  * \internal
1288  * \brief Set the result of an action, with a formatted exit reason
1289  *
1290  * \param[out] action        Where to set action result
1291  * \param[in]  agent_status  Exit status to set
1292  * \param[in]  exec_status   Execution status to set
1293  * \param[in]  format        printf-style format for a human-friendly
1294  *                           description of reason for result
1295  * \param[in]  ...           arguments for \p format
1296  */
1297 void
1298 services__format_result(svc_action_t *action, int agent_status,
     /* [previous][next][first][last][top][bottom][index][help] */
1299                         enum pcmk_exec_status exec_status,
1300                         const char *format, ...)
1301 {
1302     va_list ap;
1303     int len = 0;
1304     char *reason = NULL;
1305 
1306     if (action == NULL) {
1307         return;
1308     }
1309 
1310     action->rc = agent_status;
1311     action->status = exec_status;
1312 
1313     if (format != NULL) {
1314         va_start(ap, format);
1315         len = vasprintf(&reason, format, ap);
1316         CRM_ASSERT(len > 0);
1317         va_end(ap);
1318     }
1319     free(action->opaque->exit_reason);
1320     action->opaque->exit_reason = reason;
1321 }
1322 
1323 /*!
1324  * \internal
1325  * \brief Set the result of an action to cancelled
1326  *
1327  * \param[out] action        Where to set action result
1328  *
1329  * \note This sets execution status but leaves the exit status unchanged
1330  */
1331 void
1332 services__set_cancelled(svc_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1333 {
1334     if (action != NULL) {
1335         action->status = PCMK_EXEC_CANCELLED;
1336         free(action->opaque->exit_reason);
1337         action->opaque->exit_reason = NULL;
1338     }
1339 }
1340 
1341 /*!
1342  * \internal
1343  * \brief Get a readable description of what an action is for
1344  *
1345  * \param[in] action  Action to check
1346  *
1347  * \return Readable name for the kind of \p action
1348  */
1349 const char *
1350 services__action_kind(const svc_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1351 {
1352     if ((action == NULL) || (action->standard == NULL)) {
1353         return "Process";
1354     } else if (pcmk__str_eq(action->standard, PCMK_RESOURCE_CLASS_STONITH,
1355                             pcmk__str_none)) {
1356         return "Fence agent";
1357     } else if (pcmk__str_eq(action->standard, PCMK_RESOURCE_CLASS_ALERT,
1358                             pcmk__str_none)) {
1359         return "Alert agent";
1360     } else {
1361         return "Resource agent";
1362     }
1363 }
1364 
1365 /*!
1366  * \internal
1367  * \brief Get the exit reason of an action
1368  *
1369  * \param[in] action  Action to check
1370  *
1371  * \return Action's exit reason (or NULL if none)
1372  */
1373 const char *
1374 services__exit_reason(const svc_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1375 {
1376     return action->opaque->exit_reason;
1377 }
1378 
1379 /*!
1380  * \internal
1381  * \brief Steal stdout from an action
1382  *
1383  * \param[in,out] action  Action whose stdout is desired
1384  *
1385  * \return Action's stdout (which may be NULL)
1386  * \note Upon return, \p action will no longer track the output, so it is the
1387  *       caller's responsibility to free the return value.
1388  */
1389 char *
1390 services__grab_stdout(svc_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1391 {
1392     char *output = action->stdout_data;
1393 
1394     action->stdout_data = NULL;
1395     return output;
1396 }
1397 
1398 /*!
1399  * \internal
1400  * \brief Steal stderr from an action
1401  *
1402  * \param[in,out] action  Action whose stderr is desired
1403  *
1404  * \return Action's stderr (which may be NULL)
1405  * \note Upon return, \p action will no longer track the output, so it is the
1406  *       caller's responsibility to free the return value.
1407  */
1408 char *
1409 services__grab_stderr(svc_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1410 {
1411     char *output = action->stderr_data;
1412 
1413     action->stderr_data = NULL;
1414     return output;
1415 }

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