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__set_cancelled
  38. services__exit_reason
  39. services__grab_stdout
  40. services__grab_stderr

   1 /*
   2  * Copyright 2010-2021 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/msg_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(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, "monitor", pcmk__str_casei)) {
 237         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_err("Unknown resource standard: %s", op->standard);
 323         rc = ENOENT;
 324     }
 325 
 326     if (rc != pcmk_rc_ok) {
 327         crm_err("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     CRM_ASSERT(op != NULL);
 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->timeout = timeout;
 419     action->id = strdup(id);
 420     action->params = params;
 421     action->sequence = sequence;
 422     action->cb_data = cb_data;
 423     return action;
 424 }
 425 
 426 /*!
 427  * \brief Set the user and group that an action will execute as
 428  *
 429  * \param[in,out] action  Action to modify
 430  * \param[in]     user    Name of user to execute action as
 431  * \param[in]     group   Name of group to execute action as
 432  *
 433  * \return pcmk_ok on success, -errno otherwise
 434  *
 435  * \note This will have no effect unless the process executing the action runs
 436  *       as root, and the action is not a systemd or upstart action.
 437  *       We could implement this for systemd by adding User= and Group= to
 438  *       [Service] in the override file, but that seems more likely to cause
 439  *       problems than be useful.
 440  */
 441 int
 442 services_action_user(svc_action_t *op, const char *user)
     /* [previous][next][first][last][top][bottom][index][help] */
 443 {
 444     CRM_CHECK((op != NULL) && (user != NULL), return -EINVAL);
 445     return crm_user_lookup(user, &(op->opaque->uid), &(op->opaque->gid));
 446 }
 447 
 448 /*!
 449  * \brief Execute an alert agent action
 450  *
 451  * \param[in] action  Action to execute
 452  * \param[in] cb      Function to call when action completes
 453  *
 454  * \return TRUE if the library will free action, FALSE otherwise
 455  *
 456  * \note If this function returns FALSE, it is the caller's responsibility to
 457  *       free the action with services_action_free().
 458  */
 459 gboolean
 460 services_alert_async(svc_action_t *action, void (*cb)(svc_action_t *op))
     /* [previous][next][first][last][top][bottom][index][help] */
 461 {
 462     action->synchronous = false;
 463     action->opaque->callback = cb;
 464     return services__execute_file(action) == pcmk_rc_ok;
 465 }
 466 
 467 #if SUPPORT_DBUS
 468 /*!
 469  * \internal
 470  * \brief Update operation's pending DBus call, unreferencing old one if needed
 471  *
 472  * \param[in,out] op       Operation to modify
 473  * \param[in]     pending  Pending call to set
 474  */
 475 void
 476 services_set_op_pending(svc_action_t *op, DBusPendingCall *pending)
     /* [previous][next][first][last][top][bottom][index][help] */
 477 {
 478     if (op->opaque->pending && (op->opaque->pending != pending)) {
 479         if (pending) {
 480             crm_info("Lost pending %s DBus call (%p)", op->id, op->opaque->pending);
 481         } else {
 482             crm_trace("Done with pending %s DBus call (%p)", op->id, op->opaque->pending);
 483         }
 484         dbus_pending_call_unref(op->opaque->pending);
 485     }
 486     op->opaque->pending = pending;
 487     if (pending) {
 488         crm_trace("Updated pending %s DBus call (%p)", op->id, pending);
 489     } else {
 490         crm_trace("Cleared pending %s DBus call", op->id);
 491     }
 492 }
 493 #endif
 494 
 495 void
 496 services_action_cleanup(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
 497 {
 498     if ((op == NULL) || (op->opaque == NULL)) {
 499         return;
 500     }
 501 
 502 #if SUPPORT_DBUS
 503     if(op->opaque->timerid != 0) {
 504         crm_trace("Removing timer for call %s to %s", op->action, op->rsc);
 505         g_source_remove(op->opaque->timerid);
 506         op->opaque->timerid = 0;
 507     }
 508 
 509     if(op->opaque->pending) {
 510         if (dbus_pending_call_get_completed(op->opaque->pending)) {
 511             // This should never be the case
 512             crm_warn("Result of %s op %s was unhandled",
 513                      op->standard, op->id);
 514         } else {
 515             crm_debug("Will ignore any result of canceled %s op %s",
 516                       op->standard, op->id);
 517         }
 518         dbus_pending_call_cancel(op->opaque->pending);
 519         services_set_op_pending(op, NULL);
 520     }
 521 #endif
 522 
 523     if (op->opaque->stderr_gsource) {
 524         mainloop_del_fd(op->opaque->stderr_gsource);
 525         op->opaque->stderr_gsource = NULL;
 526     }
 527 
 528     if (op->opaque->stdout_gsource) {
 529         mainloop_del_fd(op->opaque->stdout_gsource);
 530         op->opaque->stdout_gsource = NULL;
 531     }
 532 }
 533 
 534 /*!
 535  * \internal
 536  * \brief Map an actual resource action result to a standard OCF result
 537  *
 538  * \param[in] standard     Agent standard (must not be "service")
 539  * \param[in] action       Action that result is for
 540  * \param[in] exit_status  Actual agent exit status
 541  *
 542  * \return Standard OCF result
 543  */
 544 enum ocf_exitcode
 545 services_result2ocf(const char *standard, const char *action, int exit_status)
     /* [previous][next][first][last][top][bottom][index][help] */
 546 {
 547     if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
 548         return services__ocf2ocf(exit_status);
 549 
 550 #if SUPPORT_SYSTEMD
 551     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD,
 552                             pcmk__str_casei)) {
 553         return services__systemd2ocf(exit_status);
 554 #endif
 555 
 556 #if SUPPORT_UPSTART
 557     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART,
 558                             pcmk__str_casei)) {
 559         return services__upstart2ocf(exit_status);
 560 #endif
 561 
 562 #if SUPPORT_NAGIOS
 563     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS,
 564                             pcmk__str_casei)) {
 565         return services__nagios2ocf(exit_status);
 566 #endif
 567 
 568     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB,
 569                             pcmk__str_casei)) {
 570         return services__lsb2ocf(action, exit_status);
 571 
 572     } else {
 573         crm_warn("Treating result from unknown standard '%s' as OCF",
 574                  ((standard == NULL)? "unspecified" : standard));
 575         return services__ocf2ocf(exit_status);
 576     }
 577 }
 578 
 579 void
 580 services_action_free(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
 581 {
 582     unsigned int i;
 583 
 584     if (op == NULL) {
 585         return;
 586     }
 587 
 588     /* The operation should be removed from all tracking lists by this point.
 589      * If it's not, we have a bug somewhere, so bail. That may lead to a
 590      * memory leak, but it's better than a use-after-free segmentation fault.
 591      */
 592     CRM_CHECK(g_list_find(inflight_ops, op) == NULL, return);
 593     CRM_CHECK(g_list_find(blocked_ops, op) == NULL, return);
 594     CRM_CHECK((recurring_actions == NULL)
 595               || (g_hash_table_lookup(recurring_actions, op->id) == NULL),
 596               return);
 597 
 598     services_action_cleanup(op);
 599 
 600     if (op->opaque->repeat_timer) {
 601         g_source_remove(op->opaque->repeat_timer);
 602         op->opaque->repeat_timer = 0;
 603     }
 604 
 605     free(op->id);
 606     free(op->opaque->exec);
 607 
 608     for (i = 0; i < PCMK__NELEM(op->opaque->args); i++) {
 609         free(op->opaque->args[i]);
 610     }
 611 
 612     free(op->opaque->exit_reason);
 613     free(op->opaque);
 614     free(op->rsc);
 615     free(op->action);
 616 
 617     free(op->standard);
 618     free(op->agent);
 619     free(op->provider);
 620 
 621     free(op->stdout_data);
 622     free(op->stderr_data);
 623 
 624     if (op->params) {
 625         g_hash_table_destroy(op->params);
 626         op->params = NULL;
 627     }
 628 
 629     free(op);
 630 }
 631 
 632 gboolean
 633 cancel_recurring_action(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
 634 {
 635     crm_info("Cancelling %s operation %s", op->standard, op->id);
 636 
 637     if (recurring_actions) {
 638         g_hash_table_remove(recurring_actions, op->id);
 639     }
 640 
 641     if (op->opaque->repeat_timer) {
 642         g_source_remove(op->opaque->repeat_timer);
 643         op->opaque->repeat_timer = 0;
 644     }
 645 
 646     return TRUE;
 647 }
 648 
 649 /*!
 650  * \brief Cancel a recurring action
 651  *
 652  * \param[in] name         Name of resource that operation is for
 653  * \param[in] action       Name of operation to cancel
 654  * \param[in] interval_ms  Interval of operation to cancel
 655  *
 656  * \return TRUE if action was successfully cancelled, FALSE otherwise
 657  */
 658 gboolean
 659 services_action_cancel(const char *name, const char *action, guint interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 660 {
 661     gboolean cancelled = FALSE;
 662     char *id = pcmk__op_key(name, action, interval_ms);
 663     svc_action_t *op = NULL;
 664 
 665     /* We can only cancel a recurring action */
 666     init_recurring_actions();
 667     op = g_hash_table_lookup(recurring_actions, id);
 668     if (op == NULL) {
 669         goto done;
 670     }
 671 
 672     // Tell services__finalize_async_op() not to reschedule the operation
 673     op->cancel = TRUE;
 674 
 675     /* Stop tracking it as a recurring operation, and stop its repeat timer */
 676     cancel_recurring_action(op);
 677 
 678     /* If the op has a PID, it's an in-flight child process, so kill it.
 679      *
 680      * Whether the kill succeeds or fails, the main loop will send the op to
 681      * async_action_complete() (and thus services__finalize_async_op()) when the
 682      * process goes away.
 683      */
 684     if (op->pid != 0) {
 685         crm_info("Terminating in-flight op %s[%d] early because it was cancelled",
 686                  id, op->pid);
 687         cancelled = mainloop_child_kill(op->pid);
 688         if (cancelled == FALSE) {
 689             crm_err("Termination of %s[%d] failed", id, op->pid);
 690         }
 691         goto done;
 692     }
 693 
 694 #if SUPPORT_DBUS
 695     // In-flight systemd and upstart ops don't have a pid
 696     if (inflight_systemd_or_upstart(op)) {
 697         inflight_ops = g_list_remove(inflight_ops, op);
 698 
 699         /* This will cause any result that comes in later to be discarded, so we
 700          * don't call the callback and free the operation twice.
 701          */
 702         services_action_cleanup(op);
 703     }
 704 #endif
 705 
 706     /* The rest of this is essentially equivalent to
 707      * services__finalize_async_op(), minus the handle_blocked_ops() call.
 708      */
 709 
 710     // Report operation as cancelled
 711     services__set_cancelled(op);
 712     if (op->opaque->callback) {
 713         op->opaque->callback(op);
 714     }
 715 
 716     blocked_ops = g_list_remove(blocked_ops, op);
 717     services_action_free(op);
 718     cancelled = TRUE;
 719     // @TODO Initiate handle_blocked_ops() asynchronously
 720 
 721 done:
 722     free(id);
 723     return cancelled;
 724 }
 725 
 726 gboolean
 727 services_action_kick(const char *name, const char *action, guint interval_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 728 {
 729     svc_action_t * op = NULL;
 730     char *id = pcmk__op_key(name, action, interval_ms);
 731 
 732     init_recurring_actions();
 733     op = g_hash_table_lookup(recurring_actions, id);
 734     free(id);
 735 
 736     if (op == NULL) {
 737         return FALSE;
 738     }
 739 
 740 
 741     if (op->pid || inflight_systemd_or_upstart(op)) {
 742         return TRUE;
 743     } else {
 744         if (op->opaque->repeat_timer) {
 745             g_source_remove(op->opaque->repeat_timer);
 746             op->opaque->repeat_timer = 0;
 747         }
 748         recurring_action_timer(op);
 749         return TRUE;
 750     }
 751 
 752 }
 753 
 754 /*!
 755  * \internal
 756  * \brief Add a new recurring operation, checking for duplicates
 757  *
 758  * \param[in] op               Operation to add
 759  *
 760  * \return TRUE if duplicate found (and reschedule), FALSE otherwise
 761  */
 762 static gboolean
 763 handle_duplicate_recurring(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
 764 {
 765     svc_action_t * dup = NULL;
 766 
 767     /* check for duplicates */
 768     dup = g_hash_table_lookup(recurring_actions, op->id);
 769 
 770     if (dup && (dup != op)) {
 771         /* update user data */
 772         if (op->opaque->callback) {
 773             dup->opaque->callback = op->opaque->callback;
 774             dup->cb_data = op->cb_data;
 775             op->cb_data = NULL;
 776         }
 777         /* immediately execute the next interval */
 778         if (dup->pid != 0) {
 779             if (op->opaque->repeat_timer) {
 780                 g_source_remove(op->opaque->repeat_timer);
 781                 op->opaque->repeat_timer = 0;
 782             }
 783             recurring_action_timer(dup);
 784         }
 785         /* free the duplicate */
 786         services_action_free(op);
 787         return TRUE;
 788     }
 789 
 790     return FALSE;
 791 }
 792 
 793 /*!
 794  * \internal
 795  * \brief Execute an action appropriately according to its standard
 796  *
 797  * \param[in] op  Action to execute
 798  *
 799  * \return Standard Pacemaker return code
 800  * \retval EBUSY          Recurring operation could not be initiated
 801  * \retval pcmk_rc_error  Synchronous action failed
 802  * \retval pcmk_rc_ok     Synchronous action succeeded, or asynchronous action
 803  *                        should not be freed (because it already was or is
 804  *                        pending)
 805  *
 806  * \note If the return value for an asynchronous action is not pcmk_rc_ok, the
 807  *       caller is responsible for freeing the action.
 808  */
 809 static int
 810 execute_action(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 811 {
 812 #if SUPPORT_UPSTART
 813     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_UPSTART,
 814                      pcmk__str_casei)) {
 815         return services__execute_upstart(op);
 816     }
 817 #endif
 818 
 819 #if SUPPORT_SYSTEMD
 820     if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD,
 821                      pcmk__str_casei)) {
 822         return services__execute_systemd(op);
 823     }
 824 #endif
 825 
 826     return services__execute_file(op);
 827 }
 828 
 829 void
 830 services_add_inflight_op(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
 831 {
 832     if (op == NULL) {
 833         return;
 834     }
 835 
 836     CRM_ASSERT(op->synchronous == FALSE);
 837 
 838     /* keep track of ops that are in-flight to avoid collisions in the same namespace */
 839     if (op->rsc) {
 840         inflight_ops = g_list_append(inflight_ops, op);
 841     }
 842 }
 843 
 844 /*!
 845  * \internal
 846  * \brief Stop tracking an operation that completed
 847  *
 848  * \param[in] op  Operation to stop tracking
 849  */
 850 void
 851 services_untrack_op(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 852 {
 853     /* Op is no longer in-flight or blocked */
 854     inflight_ops = g_list_remove(inflight_ops, op);
 855     blocked_ops = g_list_remove(blocked_ops, op);
 856 
 857     /* Op is no longer blocking other ops, so check if any need to run */
 858     handle_blocked_ops();
 859 }
 860 
 861 gboolean
 862 services_action_async_fork_notify(svc_action_t * op,
     /* [previous][next][first][last][top][bottom][index][help] */
 863                                   void (*action_callback) (svc_action_t *),
 864                                   void (*action_fork_callback) (svc_action_t *))
 865 {
 866     op->synchronous = false;
 867     if (action_callback) {
 868         op->opaque->callback = action_callback;
 869     }
 870     if (action_fork_callback) {
 871         op->opaque->fork_callback = action_fork_callback;
 872     }
 873 
 874     if (op->interval_ms > 0) {
 875         init_recurring_actions();
 876         if (handle_duplicate_recurring(op) == TRUE) {
 877             /* entry rescheduled, dup freed */
 878             /* exit early */
 879             return TRUE;
 880         }
 881         g_hash_table_replace(recurring_actions, op->id, op);
 882     }
 883 
 884     if (!pcmk_is_set(op->flags, SVC_ACTION_NON_BLOCKED)
 885         && op->rsc && is_op_blocked(op->rsc)) {
 886         blocked_ops = g_list_append(blocked_ops, op);
 887         return TRUE;
 888     }
 889 
 890     return execute_action(op) == pcmk_rc_ok;
 891 }
 892 
 893 gboolean
 894 services_action_async(svc_action_t * op,
     /* [previous][next][first][last][top][bottom][index][help] */
 895                       void (*action_callback) (svc_action_t *))
 896 {
 897     return services_action_async_fork_notify(op, action_callback, NULL);
 898 }
 899 
 900 static gboolean processing_blocked_ops = FALSE;
 901 
 902 gboolean
 903 is_op_blocked(const char *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 904 {
 905     GList *gIter = NULL;
 906     svc_action_t *op = NULL;
 907 
 908     for (gIter = inflight_ops; gIter != NULL; gIter = gIter->next) {
 909         op = gIter->data;
 910         if (pcmk__str_eq(op->rsc, rsc, pcmk__str_casei)) {
 911             return TRUE;
 912         }
 913     }
 914 
 915     return FALSE;
 916 }
 917 
 918 static void
 919 handle_blocked_ops(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 920 {
 921     GList *executed_ops = NULL;
 922     GList *gIter = NULL;
 923     svc_action_t *op = NULL;
 924 
 925     if (processing_blocked_ops) {
 926         /* avoid nested calling of this function */
 927         return;
 928     }
 929 
 930     processing_blocked_ops = TRUE;
 931 
 932     /* n^2 operation here, but blocked ops are incredibly rare. this list
 933      * will be empty 99% of the time. */
 934     for (gIter = blocked_ops; gIter != NULL; gIter = gIter->next) {
 935         op = gIter->data;
 936         if (is_op_blocked(op->rsc)) {
 937             continue;
 938         }
 939         executed_ops = g_list_append(executed_ops, op);
 940         if (execute_action(op) != pcmk_rc_ok) {
 941             /* this can cause this function to be called recursively
 942              * which is why we have processing_blocked_ops static variable */
 943             services__finalize_async_op(op);
 944         }
 945     }
 946 
 947     for (gIter = executed_ops; gIter != NULL; gIter = gIter->next) {
 948         op = gIter->data;
 949         blocked_ops = g_list_remove(blocked_ops, op);
 950     }
 951     g_list_free(executed_ops);
 952 
 953     processing_blocked_ops = FALSE;
 954 }
 955 
 956 /*!
 957  * \internal
 958  * \brief Execute a meta-data action appropriately to standard
 959  *
 960  * \param[in] op  Meta-data action to execute
 961  *
 962  * \return Standard Pacemaker return code
 963  */
 964 static int
 965 execute_metadata_action(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 966 {
 967     const char *class = op->standard;
 968 
 969     if (op->agent == NULL) {
 970         crm_err("meta-data requested without specifying agent");
 971         services__set_result(op, services__generic_error(op),
 972                              PCMK_EXEC_ERROR_FATAL, "Agent not specified");
 973         return EINVAL;
 974     }
 975 
 976     if (class == NULL) {
 977         crm_err("meta-data requested for agent %s without specifying class",
 978                 op->agent);
 979         services__set_result(op, services__generic_error(op),
 980                              PCMK_EXEC_ERROR_FATAL,
 981                              "Agent standard not specified");
 982         return EINVAL;
 983     }
 984 
 985     if (!strcmp(class, PCMK_RESOURCE_CLASS_SERVICE)) {
 986         class = resources_find_service_class(op->agent);
 987     }
 988     if (class == NULL) {
 989         crm_err("meta-data requested for %s, but could not determine class",
 990                 op->agent);
 991         services__set_result(op, services__generic_error(op),
 992                              PCMK_EXEC_ERROR_HARD,
 993                              "Agent standard could not be determined");
 994         return EINVAL;
 995     }
 996 
 997     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
 998         return pcmk_legacy2rc(services__get_lsb_metadata(op->agent,
 999                                                          &op->stdout_data));
1000     }
1001 
1002 #if SUPPORT_NAGIOS
1003     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
1004         return pcmk_legacy2rc(services__get_nagios_metadata(op->agent,
1005                                                             &op->stdout_data));
1006     }
1007 #endif
1008 
1009     return execute_action(op);
1010 }
1011 
1012 gboolean
1013 services_action_sync(svc_action_t * op)
     /* [previous][next][first][last][top][bottom][index][help] */
1014 {
1015     gboolean rc = TRUE;
1016 
1017     if (op == NULL) {
1018         crm_trace("No operation to execute");
1019         return FALSE;
1020     }
1021 
1022     op->synchronous = true;
1023 
1024     if (pcmk__str_eq(op->action, "meta-data", pcmk__str_casei)) {
1025         /* Synchronous meta-data operations are handled specially. Since most
1026          * resource classes don't provide any meta-data, it has to be
1027          * synthesized from available information about the agent.
1028          *
1029          * services_action_async() doesn't treat meta-data actions specially, so
1030          * it will result in an error for classes that don't support the action.
1031          */
1032         rc = (execute_metadata_action(op) == pcmk_rc_ok);
1033     } else {
1034         rc = (execute_action(op) == pcmk_rc_ok);
1035     }
1036     crm_trace(" > " PCMK__OP_FMT ": %s = %d",
1037               op->rsc, op->action, op->interval_ms, op->opaque->exec, op->rc);
1038     if (op->stdout_data) {
1039         crm_trace(" >  stdout: %s", op->stdout_data);
1040     }
1041     if (op->stderr_data) {
1042         crm_trace(" >  stderr: %s", op->stderr_data);
1043     }
1044     return rc;
1045 }
1046 
1047 GList *
1048 get_directory_list(const char *root, gboolean files, gboolean executable)
     /* [previous][next][first][last][top][bottom][index][help] */
1049 {
1050     return services_os_get_directory_list(root, files, executable);
1051 }
1052 
1053 GList *
1054 resources_list_standards(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1055 {
1056     GList *standards = NULL;
1057 
1058     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_OCF));
1059     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_LSB));
1060     standards = g_list_append(standards, strdup(PCMK_RESOURCE_CLASS_SERVICE));
1061 
1062 #if SUPPORT_SYSTEMD
1063     {
1064         GList *agents = systemd_unit_listall();
1065 
1066         if (agents != NULL) {
1067             standards = g_list_append(standards,
1068                                       strdup(PCMK_RESOURCE_CLASS_SYSTEMD));
1069             g_list_free_full(agents, free);
1070         }
1071     }
1072 #endif
1073 
1074 #if SUPPORT_UPSTART
1075     {
1076         GList *agents = upstart_job_listall();
1077 
1078         if (agents != NULL) {
1079             standards = g_list_append(standards,
1080                                       strdup(PCMK_RESOURCE_CLASS_UPSTART));
1081             g_list_free_full(agents, free);
1082         }
1083     }
1084 #endif
1085 
1086 #if SUPPORT_NAGIOS
1087     {
1088         GList *agents = services__list_nagios_agents();
1089 
1090         if (agents != NULL) {
1091             standards = g_list_append(standards,
1092                                       strdup(PCMK_RESOURCE_CLASS_NAGIOS));
1093             g_list_free_full(agents, free);
1094         }
1095     }
1096 #endif
1097 
1098     return standards;
1099 }
1100 
1101 GList *
1102 resources_list_providers(const char *standard)
     /* [previous][next][first][last][top][bottom][index][help] */
1103 {
1104     if (pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider)) {
1105         return resources_os_list_ocf_providers();
1106     }
1107 
1108     return NULL;
1109 }
1110 
1111 GList *
1112 resources_list_agents(const char *standard, const char *provider)
     /* [previous][next][first][last][top][bottom][index][help] */
1113 {
1114     if ((standard == NULL)
1115         || (strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE) == 0)) {
1116 
1117         GList *tmp1;
1118         GList *tmp2;
1119         GList *result = services__list_lsb_agents();
1120 
1121         if (standard == NULL) {
1122             tmp1 = result;
1123             tmp2 = resources_os_list_ocf_agents(NULL);
1124             if (tmp2) {
1125                 result = g_list_concat(tmp1, tmp2);
1126             }
1127         }
1128 #if SUPPORT_SYSTEMD
1129         tmp1 = result;
1130         tmp2 = systemd_unit_listall();
1131         if (tmp2) {
1132             result = g_list_concat(tmp1, tmp2);
1133         }
1134 #endif
1135 
1136 #if SUPPORT_UPSTART
1137         tmp1 = result;
1138         tmp2 = upstart_job_listall();
1139         if (tmp2) {
1140             result = g_list_concat(tmp1, tmp2);
1141         }
1142 #endif
1143 
1144         return result;
1145 
1146     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF) == 0) {
1147         return resources_os_list_ocf_agents(provider);
1148     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
1149         return services__list_lsb_agents();
1150 #if SUPPORT_SYSTEMD
1151     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
1152         return systemd_unit_listall();
1153 #endif
1154 #if SUPPORT_UPSTART
1155     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
1156         return upstart_job_listall();
1157 #endif
1158 #if SUPPORT_NAGIOS
1159     } else if (strcasecmp(standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
1160         return services__list_nagios_agents();
1161 #endif
1162     }
1163 
1164     return NULL;
1165 }
1166 
1167 gboolean
1168 resources_agent_exists(const char *standard, const char *provider, const char *agent)
     /* [previous][next][first][last][top][bottom][index][help] */
1169 {
1170     GList *standards = NULL;
1171     GList *providers = NULL;
1172     GList *iter = NULL;
1173     gboolean rc = FALSE;
1174     gboolean has_providers = FALSE;
1175 
1176     standards = resources_list_standards();
1177     for (iter = standards; iter != NULL; iter = iter->next) {
1178         if (pcmk__str_eq(iter->data, standard, pcmk__str_none)) {
1179             rc = TRUE;
1180             break;
1181         }
1182     }
1183 
1184     if (rc == FALSE) {
1185         goto done;
1186     }
1187 
1188     rc = FALSE;
1189 
1190     has_providers = pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider);
1191     if (has_providers == TRUE && provider != NULL) {
1192         providers = resources_list_providers(standard);
1193         for (iter = providers; iter != NULL; iter = iter->next) {
1194             if (pcmk__str_eq(iter->data, provider, pcmk__str_none)) {
1195                 rc = TRUE;
1196                 break;
1197             }
1198         }
1199     } else if (has_providers == FALSE && provider == NULL) {
1200         rc = TRUE;
1201     }
1202 
1203     if (rc == FALSE) {
1204         goto done;
1205     }
1206 
1207     if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
1208         if (services__lsb_agent_exists(agent)) {
1209             rc = TRUE;
1210 #if SUPPORT_SYSTEMD
1211         } else if (systemd_unit_exists(agent)) {
1212             rc = TRUE;
1213 #endif
1214 
1215 #if SUPPORT_UPSTART
1216         } else if (upstart_job_exists(agent)) {
1217             rc = TRUE;
1218 #endif
1219         } else {
1220             rc = FALSE;
1221         }
1222 
1223     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
1224         rc = services__ocf_agent_exists(provider, agent);
1225 
1226     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei)) {
1227         rc = services__lsb_agent_exists(agent);
1228 
1229 #if SUPPORT_SYSTEMD
1230     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
1231         rc = systemd_unit_exists(agent);
1232 #endif
1233 
1234 #if SUPPORT_UPSTART
1235     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_casei)) {
1236         rc = upstart_job_exists(agent);
1237 #endif
1238 
1239 #if SUPPORT_NAGIOS
1240     } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
1241         rc = services__nagios_agent_exists(agent);
1242 #endif
1243 
1244     } else {
1245         rc = FALSE;
1246     }
1247 
1248 done:
1249     g_list_free(standards);
1250     g_list_free(providers);
1251     return rc;
1252 }
1253 
1254 /*!
1255  * \internal
1256  * \brief Set the result of an action
1257  *
1258  * \param[out] action        Where to set action result
1259  * \param[in]  agent_status  Exit status to set
1260  * \param[in]  exec_status   Execution status to set
1261  * \param[in]  reason        Human-friendly description of event to set
1262  */
1263 void
1264 services__set_result(svc_action_t *action, int agent_status,
     /* [previous][next][first][last][top][bottom][index][help] */
1265                      enum pcmk_exec_status exec_status, const char *reason)
1266 {
1267     if (action == NULL) {
1268         return;
1269     }
1270 
1271     action->rc = agent_status;
1272     action->status = exec_status;
1273 
1274     if (!pcmk__str_eq(action->opaque->exit_reason, reason,
1275                       pcmk__str_none)) {
1276         free(action->opaque->exit_reason);
1277         action->opaque->exit_reason = (reason == NULL)? NULL : strdup(reason);
1278     }
1279 }
1280 
1281 /*!
1282  * \internal
1283  * \brief Set the result of an action to cancelled
1284  *
1285  * \param[out] action        Where to set action result
1286  *
1287  * \note This sets execution status but leaves the exit status unchanged
1288  */
1289 void
1290 services__set_cancelled(svc_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1291 {
1292     if (action != NULL) {
1293         action->status = PCMK_EXEC_CANCELLED;
1294         free(action->opaque->exit_reason);
1295         action->opaque->exit_reason = NULL;
1296     }
1297 }
1298 
1299 /*!
1300  * \internal
1301  * \brief Get the exit reason of an action
1302  *
1303  * \param[in] action  Action to check
1304  *
1305  * \return Action's exit reason (or NULL if none)
1306  */
1307 const char *
1308 services__exit_reason(svc_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1309 {
1310     return action->opaque->exit_reason;
1311 }
1312 
1313 /*!
1314  * \internal
1315  * \brief Steal stdout from an action
1316  *
1317  * \param[in] action  Action whose stdout is desired
1318  *
1319  * \return Action's stdout (which may be NULL)
1320  * \note Upon return, \p action will no longer track the output, so it is the
1321  *       caller's responsibility to free the return value.
1322  */
1323 char *
1324 services__grab_stdout(svc_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1325 {
1326     char *output = action->stdout_data;
1327 
1328     action->stdout_data = NULL;
1329     return output;
1330 }
1331 
1332 /*!
1333  * \internal
1334  * \brief Steal stderr from an action
1335  *
1336  * \param[in] action  Action whose stderr is desired
1337  *
1338  * \return Action's stderr (which may be NULL)
1339  * \note Upon return, \p action will no longer track the output, so it is the
1340  *       caller's responsibility to free the return value.
1341  */
1342 char *
1343 services__grab_stderr(svc_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1344 {
1345     char *output = action->stderr_data;
1346 
1347     action->stderr_data = NULL;
1348     return output;
1349 }

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