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__copy_result
  38. services__format_result
  39. services__set_cancelled
  40. services__action_kind
  41. services__exit_reason
  42. services__grab_stdout
  43. services__grab_stderr

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

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