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

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