root/lib/services/systemd.c

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

DEFINITIONS

This source file includes following definitions.
  1. services__systemd_prepare
  2. services__systemd2ocf
  3. systemd_new_method
  4. systemd_send
  5. systemd_send_recv
  6. systemd_call_simple_method
  7. subscribe_to_signals
  8. systemd_init
  9. systemd_get_property
  10. systemd_cleanup
  11. systemd_unit_extension
  12. systemd_unit_name
  13. systemd_daemon_reload_complete
  14. systemd_daemon_reload
  15. set_result_from_method_error
  16. execute_after_loadunit
  17. loadunit_completed
  18. invoke_unit_by_name
  19. sort_str
  20. systemd_unit_listall
  21. systemd_unit_exists
  22. systemd_unit_metadata
  23. process_unit_method_reply
  24. job_removed_filter
  25. finalize_async_action_dbus
  26. unit_method_complete
  27. get_override_dir
  28. append_override_basename
  29. systemd_create_override
  30. systemd_remove_override
  31. parse_status_result
  32. invoke_unit_by_path
  33. systemd_timeout_callback
  34. services__execute_systemd

   1 /*
   2  * Copyright 2012-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 #include <crm/crm.h>
  12 #include <crm/common/xml.h>
  13 #include <crm/services.h>
  14 #include <crm/services_internal.h>
  15 #include <crm/common/mainloop.h>
  16 
  17 #include <inttypes.h>               // PRIu32
  18 #include <stdbool.h>
  19 #include <stdint.h>                 // uint32_t
  20 #include <stdio.h>                  // fopen(), NULL, etc.
  21 #include <sys/stat.h>
  22 #include <gio/gio.h>
  23 #include <services_private.h>
  24 #include <systemd.h>
  25 #include <dbus/dbus.h>
  26 #include <pcmk-dbus.h>
  27 
  28 static void invoke_unit_by_path(svc_action_t *op, const char *unit);
  29 
  30 /* Systemd D-Bus interface
  31  * https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.systemd1.html
  32  */
  33 #define BUS_NAME         "org.freedesktop.systemd1"
  34 #define BUS_NAME_MANAGER BUS_NAME ".Manager"
  35 #define BUS_NAME_UNIT    BUS_NAME ".Unit"
  36 #define BUS_PATH         "/org/freedesktop/systemd1"
  37 
  38 /*!
  39  * \internal
  40  * \brief Prepare a systemd action
  41  *
  42  * \param[in,out] op  Action to prepare
  43  *
  44  * \return Standard Pacemaker return code
  45  */
  46 int
  47 services__systemd_prepare(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
  48 {
  49     op->opaque->exec = strdup("systemd-dbus");
  50     if (op->opaque->exec == NULL) {
  51         return ENOMEM;
  52     }
  53     return pcmk_rc_ok;
  54 }
  55 
  56 /*!
  57  * \internal
  58  * \brief Map a systemd result to a standard OCF result
  59  *
  60  * \param[in] exit_status  Systemd result
  61  *
  62  * \return Standard OCF result
  63  */
  64 enum ocf_exitcode
  65 services__systemd2ocf(int exit_status)
     /* [previous][next][first][last][top][bottom][index][help] */
  66 {
  67     // This library uses OCF codes for systemd actions
  68     return (enum ocf_exitcode) exit_status;
  69 }
  70 
  71 static inline DBusMessage *
  72 systemd_new_method(const char *method)
     /* [previous][next][first][last][top][bottom][index][help] */
  73 {
  74     crm_trace("Calling: %s on " BUS_NAME_MANAGER, method);
  75     return dbus_message_new_method_call(BUS_NAME, BUS_PATH, BUS_NAME_MANAGER,
  76                                         method);
  77 }
  78 
  79 /*
  80  * Functions to manage a static DBus connection
  81  */
  82 
  83 static DBusConnection* systemd_proxy = NULL;
  84 
  85 static inline DBusPendingCall *
  86 systemd_send(DBusMessage *msg,
     /* [previous][next][first][last][top][bottom][index][help] */
  87              void(*done)(DBusPendingCall *pending, void *user_data),
  88              void *user_data, int timeout)
  89 {
  90     return pcmk_dbus_send(msg, systemd_proxy, done, user_data, timeout);
  91 }
  92 
  93 static inline DBusMessage *
  94 systemd_send_recv(DBusMessage *msg, DBusError *error, int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
  95 {
  96     return pcmk_dbus_send_recv(msg, systemd_proxy, error, timeout);
  97 }
  98 
  99 /*!
 100  * \internal
 101  * \brief Send a method to systemd without arguments, and wait for reply
 102  *
 103  * \param[in] method  Method to send
 104  *
 105  * \return Systemd reply on success, NULL (and error will be logged) otherwise
 106  *
 107  * \note The caller must call dbus_message_unref() on the reply after
 108  *       handling it.
 109  */
 110 static DBusMessage *
 111 systemd_call_simple_method(const char *method)
     /* [previous][next][first][last][top][bottom][index][help] */
 112 {
 113     DBusMessage *msg = NULL;
 114     DBusMessage *reply = NULL;
 115     DBusError error;
 116 
 117     /* Don't call systemd_init() here, because that calls this */
 118     CRM_CHECK(systemd_proxy, return NULL);
 119 
 120     msg = systemd_new_method(method);
 121 
 122     if (msg == NULL) {
 123         crm_err("Could not create message to send %s to systemd", method);
 124         return NULL;
 125     }
 126 
 127     dbus_error_init(&error);
 128     reply = systemd_send_recv(msg, &error, DBUS_TIMEOUT_USE_DEFAULT);
 129     dbus_message_unref(msg);
 130 
 131     if (dbus_error_is_set(&error)) {
 132         crm_err("Could not send %s to systemd: %s (%s)",
 133                 method, error.message, error.name);
 134         dbus_error_free(&error);
 135         return NULL;
 136 
 137     } else if (reply == NULL) {
 138         crm_err("Could not send %s to systemd: no reply received", method);
 139         return NULL;
 140     }
 141 
 142     return reply;
 143 }
 144 
 145 /*!
 146  * \internal
 147  * \brief Subscribe to D-Bus signals from systemd
 148  *
 149  * Systemd does not broadcast signal messages unless at least one client has
 150  * called the \c Subscribe() method. Also, a D-Bus client ignores broadcast
 151  * messages unless an appropriate match rule is set, so we set one here.
 152  *
 153  * \return Standard Pacemaker return code
 154  */
 155 static int
 156 subscribe_to_signals(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 157 {
 158     const char *match_rule = "type='signal',"
 159                              "sender='" BUS_NAME "',"
 160                              "interface='" BUS_NAME_MANAGER "',"
 161                              "path='" BUS_PATH "'";
 162     DBusMessage *reply = NULL;
 163     DBusError error;
 164 
 165     /* Tell D-Bus to accept signal messages from systemd.
 166      * https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
 167      */
 168     dbus_error_init(&error);
 169     dbus_bus_add_match(systemd_proxy, match_rule, &error);
 170 
 171     if (dbus_error_is_set(&error)) {
 172         crm_err("Could not listen for systemd DBus signals: %s " QB_XS " (%s)",
 173                 error.message, error.name);
 174         dbus_error_free(&error);
 175         return ECOMM;
 176     }
 177 
 178     // Tell systemd to broadcast signals
 179     reply = systemd_call_simple_method("Subscribe");
 180     if (reply == NULL) {
 181         dbus_bus_remove_match(systemd_proxy, match_rule, &error);
 182         return ECOMM;
 183     }
 184 
 185     dbus_message_unref(reply);
 186     return pcmk_rc_ok;
 187 }
 188 
 189 static bool
 190 systemd_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 191 {
 192     static int need_init = 1;
 193     // https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
 194 
 195     if (systemd_proxy
 196         && dbus_connection_get_is_connected(systemd_proxy) == FALSE) {
 197         crm_warn("Connection to System DBus is closed. Reconnecting...");
 198         pcmk_dbus_disconnect(systemd_proxy);
 199         systemd_proxy = NULL;
 200         need_init = 1;
 201     }
 202 
 203     if (need_init) {
 204         need_init = 0;
 205         systemd_proxy = pcmk_dbus_connect();
 206 
 207         if (subscribe_to_signals() != pcmk_rc_ok) {
 208             pcmk_dbus_disconnect(systemd_proxy);
 209             systemd_proxy = NULL;
 210         }
 211     }
 212 
 213     return (systemd_proxy != NULL);
 214 }
 215 
 216 static inline char *
 217 systemd_get_property(const char *unit, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
 218                      void (*callback)(const char *name, const char *value, void *userdata),
 219                      void *userdata, DBusPendingCall **pending, int timeout)
 220 {
 221     return systemd_proxy?
 222            pcmk_dbus_get_property(systemd_proxy, BUS_NAME, unit, BUS_NAME_UNIT,
 223                                   name, callback, userdata, pending, timeout)
 224            : NULL;
 225 }
 226 
 227 void
 228 systemd_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 229 {
 230     if (systemd_proxy) {
 231         pcmk_dbus_disconnect(systemd_proxy);
 232         systemd_proxy = NULL;
 233     }
 234 }
 235 
 236 /*
 237  * end of systemd_proxy functions
 238  */
 239 
 240 /*!
 241  * \internal
 242  * \brief Check whether a file name represents a manageable systemd unit
 243  *
 244  * \param[in] name  File name to check
 245  *
 246  * \return Pointer to "dot" before filename extension if so, NULL otherwise
 247  */
 248 static const char *
 249 systemd_unit_extension(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 250 {
 251     if (name) {
 252         const char *dot = strrchr(name, '.');
 253 
 254         if (dot && (!strcmp(dot, ".service")
 255                     || !strcmp(dot, ".socket")
 256                     || !strcmp(dot, ".mount")
 257                     || !strcmp(dot, ".timer")
 258                     || !strcmp(dot, ".path"))) {
 259             return dot;
 260         }
 261     }
 262     return NULL;
 263 }
 264 
 265 static char *
 266 systemd_unit_name(const char *name, bool add_instance_name)
     /* [previous][next][first][last][top][bottom][index][help] */
 267 {
 268     const char *dot = NULL;
 269 
 270     if (pcmk__str_empty(name)) {
 271         return NULL;
 272     }
 273 
 274     /* Services that end with an @ sign are systemd templates.  They expect an
 275      * instance name to follow the service name.  If no instance name was
 276      * provided, just add "pacemaker" to the string as the instance name.  It
 277      * doesn't seem to matter for purposes of looking up whether a service
 278      * exists or not.
 279      *
 280      * A template can be specified either with or without the unit extension,
 281      * so this block handles both cases.
 282      */
 283     dot = systemd_unit_extension(name);
 284 
 285     if (dot) {
 286         if (dot != name && *(dot-1) == '@') {
 287             return crm_strdup_printf("%.*spacemaker%s",
 288                                      (int) (dot - name), name, dot);
 289         } else {
 290             return pcmk__str_copy(name);
 291         }
 292 
 293     } else if (add_instance_name && *(name+strlen(name)-1) == '@') {
 294         return crm_strdup_printf("%spacemaker.service", name);
 295 
 296     } else {
 297         return crm_strdup_printf("%s.service", name);
 298     }
 299 }
 300 
 301 static void
 302 systemd_daemon_reload_complete(DBusPendingCall *pending, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 303 {
 304     DBusError error;
 305     DBusMessage *reply = NULL;
 306     unsigned int reload_count = GPOINTER_TO_UINT(user_data);
 307 
 308     dbus_error_init(&error);
 309     if(pending) {
 310         reply = dbus_pending_call_steal_reply(pending);
 311     }
 312 
 313     if (pcmk_dbus_find_error(pending, reply, &error)) {
 314         crm_warn("Could not issue systemd reload %d: %s",
 315                  reload_count, error.message);
 316         dbus_error_free(&error);
 317 
 318     } else {
 319         crm_trace("Reload %d complete", reload_count);
 320     }
 321 
 322     if(pending) {
 323         dbus_pending_call_unref(pending);
 324     }
 325     if(reply) {
 326         dbus_message_unref(reply);
 327     }
 328 }
 329 
 330 static bool
 331 systemd_daemon_reload(int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
 332 {
 333     static unsigned int reload_count = 0;
 334     DBusMessage *msg = systemd_new_method("Reload");
 335 
 336     reload_count++;
 337     pcmk__assert(msg != NULL);
 338     systemd_send(msg, systemd_daemon_reload_complete,
 339                  GUINT_TO_POINTER(reload_count), timeout);
 340     dbus_message_unref(msg);
 341 
 342     return TRUE;
 343 }
 344 
 345 /*!
 346  * \internal
 347  * \brief Set an action result based on a method error
 348  *
 349  * \param[in,out] op     Action to set result for
 350  * \param[in]     error  Method error
 351  */
 352 static void
 353 set_result_from_method_error(svc_action_t *op, const DBusError *error)
     /* [previous][next][first][last][top][bottom][index][help] */
 354 {
 355     services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 356                          "Unable to invoke systemd DBus method");
 357 
 358     if (strstr(error->name, "org.freedesktop.systemd1.InvalidName")
 359         || strstr(error->name, "org.freedesktop.systemd1.LoadFailed")
 360         || strstr(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
 361 
 362         if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
 363             crm_trace("Masking systemd stop failure (%s) for %s "
 364                       "because unknown service can be considered stopped",
 365                       error->name, pcmk__s(op->rsc, "unknown resource"));
 366             services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
 367             return;
 368         }
 369 
 370         services__format_result(op, PCMK_OCF_NOT_INSTALLED,
 371                                PCMK_EXEC_NOT_INSTALLED,
 372                                "systemd unit %s not found", op->agent);
 373 
 374     /* If systemd happens to be re-executing by `systemctl daemon-reexec` at the
 375      * same time, dbus gives an error with the name
 376      * `org.freedesktop.DBus.Error.NoReply` and the message "Message recipient
 377      * disconnected from message bus without replying".
 378      * Consider the monitor pending rather than return an error yet, so that it
 379      * can retry with another iteration.
 380      */
 381     } else if (pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR,
 382                                 PCMK_ACTION_STATUS, NULL)
 383                && strstr(error->name, DBUS_ERROR_NO_REPLY)
 384                && strstr(error->message, "disconnected")) {
 385         services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
 386     }
 387 
 388     crm_info("DBus request for %s of systemd unit %s%s%s failed: %s",
 389              op->action, op->agent,
 390              ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""),
 391              error->message);
 392 }
 393 
 394 /*!
 395  * \internal
 396  * \brief Extract unit path from LoadUnit reply, and execute action
 397  *
 398  * \param[in]     reply  LoadUnit reply
 399  * \param[in,out] op     Action to execute (or NULL to just return path)
 400  *
 401  * \return DBus object path for specified unit if successful (only valid for
 402  *         lifetime of \p reply), otherwise NULL
 403  */
 404 static const char *
 405 execute_after_loadunit(DBusMessage *reply, svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 406 {
 407     const char *path = NULL;
 408     DBusError error;
 409 
 410     /* path here is not used other than as a non-NULL flag to indicate that a
 411      * request was indeed sent
 412      */
 413     if (pcmk_dbus_find_error((void *) &path, reply, &error)) {
 414         if (op != NULL) {
 415             set_result_from_method_error(op, &error);
 416         }
 417         dbus_error_free(&error);
 418 
 419     } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
 420                                      __func__, __LINE__)) {
 421         if (op != NULL) {
 422             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 423                                  "systemd DBus method had unexpected reply");
 424             crm_info("Could not load systemd unit %s for %s: "
 425                      "DBus reply has unexpected type", op->agent, op->id);
 426         } else {
 427             crm_info("Could not load systemd unit: "
 428                      "DBus reply has unexpected type");
 429         }
 430 
 431     } else {
 432         dbus_message_get_args (reply, NULL,
 433                                DBUS_TYPE_OBJECT_PATH, &path,
 434                                DBUS_TYPE_INVALID);
 435     }
 436 
 437     if (op != NULL) {
 438         if (path != NULL) {
 439             invoke_unit_by_path(op, path);
 440 
 441         } else if (!(op->synchronous)) {
 442             if (!pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR,
 443                                   PCMK_ACTION_STATUS, NULL)
 444                 || op->status != PCMK_EXEC_PENDING) {
 445                 services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 446                                         "No DBus object found for systemd unit %s",
 447                                         op->agent);
 448             }
 449             services__finalize_async_op(op);
 450         }
 451     }
 452 
 453     return path;
 454 }
 455 
 456 /*!
 457  * \internal
 458  * \brief Execute a systemd action after its LoadUnit completes
 459  *
 460  * \param[in,out] pending    If not NULL, DBus call associated with LoadUnit
 461  * \param[in,out] user_data  Action to execute
 462  */
 463 static void
 464 loadunit_completed(DBusPendingCall *pending, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 465 {
 466     DBusMessage *reply = NULL;
 467     svc_action_t *op = user_data;
 468 
 469     crm_trace("LoadUnit result for %s arrived", op->id);
 470 
 471     // Grab the reply
 472     if (pending != NULL) {
 473         reply = dbus_pending_call_steal_reply(pending);
 474     }
 475 
 476     // The call is no longer pending
 477     CRM_LOG_ASSERT(pending == op->opaque->pending);
 478     services_set_op_pending(op, NULL);
 479 
 480     // Execute the desired action based on the reply
 481     execute_after_loadunit(reply, user_data);
 482     if (reply != NULL) {
 483         dbus_message_unref(reply);
 484     }
 485 }
 486 
 487 /*!
 488  * \internal
 489  * \brief Execute a systemd action, given the unit name
 490  *
 491  * \param[in]     arg_name  Unit name (possibly without ".service" extension)
 492  * \param[in,out] op        Action to execute (if NULL, just get object path)
 493  * \param[out]    path      If non-NULL and \p op is NULL or synchronous, where
 494  *                          to store DBus object path for specified unit
 495  *
 496  * \return Standard Pacemaker return code (for NULL \p op, pcmk_rc_ok means unit
 497  *         was found; for synchronous actions, pcmk_rc_ok means unit was
 498  *         executed, with the actual result stored in \p op; for asynchronous
 499  *         actions, pcmk_rc_ok means action was initiated)
 500  * \note It is the caller's responsibility to free the path.
 501  */
 502 static int
 503 invoke_unit_by_name(const char *arg_name, svc_action_t *op, char **path)
     /* [previous][next][first][last][top][bottom][index][help] */
 504 {
 505     DBusMessage *msg;
 506     DBusMessage *reply = NULL;
 507     DBusPendingCall *pending = NULL;
 508     char *name = NULL;
 509 
 510     if (pcmk__str_empty(arg_name)) {
 511         return EINVAL;
 512     }
 513 
 514     if (!systemd_init()) {
 515         if (op != NULL) {
 516             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 517                                  "No DBus connection");
 518         }
 519         return ENOTCONN;
 520     }
 521 
 522     /* Create a LoadUnit DBus method (equivalent to GetUnit if already loaded),
 523      * which makes the unit usable via further DBus methods.
 524      *
 525      * <method name="LoadUnit">
 526      *  <arg name="name" type="s" direction="in"/>
 527      *  <arg name="unit" type="o" direction="out"/>
 528      * </method>
 529      */
 530     msg = systemd_new_method("LoadUnit");
 531     pcmk__assert(msg != NULL);
 532 
 533     // Add the (expanded) unit name as the argument
 534     name = systemd_unit_name(arg_name,
 535                              (op == NULL)
 536                              || pcmk__str_eq(op->action, PCMK_ACTION_META_DATA,
 537                                              pcmk__str_none));
 538     CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name,
 539                                             DBUS_TYPE_INVALID));
 540     free(name);
 541 
 542     if ((op == NULL) || op->synchronous) {
 543         // For synchronous ops, wait for a reply and extract the result
 544         const char *unit = NULL;
 545         int rc = pcmk_rc_ok;
 546 
 547         reply = systemd_send_recv(msg, NULL,
 548                                   (op? op->timeout : DBUS_TIMEOUT_USE_DEFAULT));
 549         dbus_message_unref(msg);
 550 
 551         unit = execute_after_loadunit(reply, op);
 552         if (unit == NULL) {
 553             rc = ENOENT;
 554             if (path != NULL) {
 555                 *path = NULL;
 556             }
 557         } else if (path != NULL) {
 558             *path = strdup(unit);
 559             if (*path == NULL) {
 560                 rc = ENOMEM;
 561             }
 562         }
 563 
 564         if (reply != NULL) {
 565             dbus_message_unref(reply);
 566         }
 567         return rc;
 568     }
 569 
 570     // For asynchronous ops, initiate the LoadUnit call and return
 571     pending = systemd_send(msg, loadunit_completed, op, op->timeout);
 572     if (pending == NULL) {
 573         services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 574                              "Unable to send DBus message");
 575         dbus_message_unref(msg);
 576         return ECOMM;
 577     }
 578 
 579     // LoadUnit was successfully initiated
 580     services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
 581     services_set_op_pending(op, pending);
 582     dbus_message_unref(msg);
 583     return pcmk_rc_ok;
 584 }
 585 
 586 /*!
 587  * \internal
 588  * \brief Compare two strings alphabetically (case-insensitive)
 589  *
 590  * \param[in] a  First string to compare
 591  * \param[in] b  Second string to compare
 592  *
 593  * \return 0 if strings are equal, -1 if a < b, 1 if a > b
 594  *
 595  * \note Usable as a GCompareFunc with g_list_sort().
 596  *       NULL is considered less than non-NULL.
 597  */
 598 static gint
 599 sort_str(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
 600 {
 601     if (!a && !b) {
 602         return 0;
 603     } else if (!a) {
 604         return -1;
 605     } else if (!b) {
 606         return 1;
 607     }
 608     return strcasecmp(a, b);
 609 }
 610 
 611 GList *
 612 systemd_unit_listall(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 613 {
 614     int nfiles = 0;
 615     GList *units = NULL;
 616     DBusMessageIter args;
 617     DBusMessageIter unit;
 618     DBusMessageIter elem;
 619     DBusMessage *reply = NULL;
 620 
 621     if (!systemd_init()) {
 622         return NULL;
 623     }
 624 
 625 /*
 626         "  <method name=\"ListUnitFiles\">\n"                               \
 627         "   <arg name=\"files\" type=\"a(ss)\" direction=\"out\"/>\n" \
 628         "  </method>\n"                                                 \
 629 */
 630 
 631     reply = systemd_call_simple_method("ListUnitFiles");
 632     if (reply == NULL) {
 633         return NULL;
 634     }
 635     if (!dbus_message_iter_init(reply, &args)) {
 636         crm_err("Could not list systemd unit files: systemd reply has no arguments");
 637         dbus_message_unref(reply);
 638         return NULL;
 639     }
 640     if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY,
 641                               __func__, __LINE__)) {
 642         crm_err("Could not list systemd unit files: systemd reply has invalid arguments");
 643         dbus_message_unref(reply);
 644         return NULL;
 645     }
 646 
 647     dbus_message_iter_recurse(&args, &unit);
 648     for (; dbus_message_iter_get_arg_type(&unit) != DBUS_TYPE_INVALID;
 649         dbus_message_iter_next(&unit)) {
 650 
 651         DBusBasicValue value;
 652         const char *match = NULL;
 653         char *unit_name = NULL;
 654         char *basename = NULL;
 655 
 656         if(!pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_STRUCT, __func__, __LINE__)) {
 657             crm_warn("Skipping systemd reply argument with unexpected type");
 658             continue;
 659         }
 660 
 661         dbus_message_iter_recurse(&unit, &elem);
 662         if(!pcmk_dbus_type_check(reply, &elem, DBUS_TYPE_STRING, __func__, __LINE__)) {
 663             crm_warn("Skipping systemd reply argument with no string");
 664             continue;
 665         }
 666 
 667         dbus_message_iter_get_basic(&elem, &value);
 668         if (value.str == NULL) {
 669             crm_debug("ListUnitFiles reply did not provide a string");
 670             continue;
 671         }
 672         crm_trace("DBus ListUnitFiles listed: %s", value.str);
 673 
 674         match = systemd_unit_extension(value.str);
 675         if (match == NULL) {
 676             // This is not a unit file type we know how to manage
 677             crm_debug("ListUnitFiles entry '%s' is not supported as resource",
 678                       value.str);
 679             continue;
 680         }
 681 
 682         // ListUnitFiles returns full path names, we just want base name
 683         basename = strrchr(value.str, '/');
 684         if (basename) {
 685             basename = basename + 1;
 686         } else {
 687             basename = value.str;
 688         }
 689 
 690         if (!strcmp(match, ".service")) {
 691             // Service is the "default" unit type, so strip it
 692             unit_name = strndup(basename, match - basename);
 693         } else {
 694             unit_name = strdup(basename);
 695         }
 696 
 697         nfiles++;
 698         units = g_list_prepend(units, unit_name);
 699     }
 700 
 701     dbus_message_unref(reply);
 702 
 703     crm_trace("Found %d manageable systemd unit files", nfiles);
 704     units = g_list_sort(units, sort_str);
 705     return units;
 706 }
 707 
 708 bool
 709 systemd_unit_exists(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 710 {
 711     char *path = NULL;
 712     char *state = NULL;
 713     int rc = false;
 714 
 715     /* Note: Makes a blocking dbus calls
 716      * Used by resources_find_service_class() when resource class=service
 717      */
 718     if ((invoke_unit_by_name(name, NULL, &path) != pcmk_rc_ok)
 719         || (path == NULL)) {
 720         goto done;
 721     }
 722 
 723     /* A successful LoadUnit is not sufficient to determine the unit's
 724      * existence; it merely means the LoadUnit request received a reply.
 725      * We must make another blocking call to check the LoadState property.
 726      */
 727     state = systemd_get_property(path, "LoadState", NULL, NULL, NULL,
 728                                  DBUS_TIMEOUT_USE_DEFAULT);
 729     rc = pcmk__str_any_of(state, "loaded", "masked", NULL);
 730 
 731 done:
 732     free(path);
 733     free(state);
 734     return rc;
 735 }
 736 
 737 // @TODO Use XML string constants and maybe a real XML object
 738 #define METADATA_FORMAT                                                        \
 739     "<?xml " PCMK_XA_VERSION "=\"1.0\"?>\n"                                    \
 740     "<" PCMK_XE_RESOURCE_AGENT " "                                             \
 741         PCMK_XA_NAME "=\"%s\" "                                                \
 742         PCMK_XA_VERSION "=\"" PCMK_DEFAULT_AGENT_VERSION "\">\n"               \
 743     "  <" PCMK_XE_VERSION ">1.1</" PCMK_XE_VERSION ">\n"                       \
 744     "  <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">\n"       \
 745     "    %s\n"                                                                 \
 746     "  </" PCMK_XE_LONGDESC ">\n"                                              \
 747     "  <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">"        \
 748         "systemd unit file for %s"                                             \
 749       "</" PCMK_XE_SHORTDESC ">\n"                                             \
 750     "  <" PCMK_XE_PARAMETERS "/>\n"                                            \
 751     "  <" PCMK_XE_ACTIONS ">\n"                                                \
 752     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_START "\""       \
 753                            " " PCMK_META_TIMEOUT "=\"100s\" />\n"              \
 754     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STOP "\""        \
 755                            " " PCMK_META_TIMEOUT "=\"100s\" />\n"              \
 756     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STATUS "\""      \
 757                            " " PCMK_META_TIMEOUT "=\"100s\" />\n"              \
 758     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_MONITOR "\""     \
 759                            " " PCMK_META_TIMEOUT "=\"100s\""                   \
 760                            " " PCMK_META_INTERVAL "=\"60s\" />\n"              \
 761     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_META_DATA "\""   \
 762                            " " PCMK_META_TIMEOUT "=\"5s\" />\n"                \
 763     "  </" PCMK_XE_ACTIONS ">\n"                                               \
 764     "  <" PCMK_XE_SPECIAL " " PCMK_XA_TAG "=\"systemd\"/>\n"                   \
 765     "</" PCMK_XE_RESOURCE_AGENT ">\n"
 766 
 767 static char *
 768 systemd_unit_metadata(const char *name, int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
 769 {
 770     char *meta = NULL;
 771     char *desc = NULL;
 772     char *path = NULL;
 773 
 774     if (invoke_unit_by_name(name, NULL, &path) == pcmk_rc_ok) {
 775         /* TODO: Worth a making blocking call for? Probably not. Possibly if cached. */
 776         desc = systemd_get_property(path, "Description", NULL, NULL, NULL,
 777                                     timeout);
 778     } else {
 779         desc = crm_strdup_printf("Systemd unit file for %s", name);
 780     }
 781 
 782     if (pcmk__xml_needs_escape(desc, pcmk__xml_escape_text)) {
 783         gchar *escaped = pcmk__xml_escape(desc, pcmk__xml_escape_text);
 784 
 785         meta = crm_strdup_printf(METADATA_FORMAT, name, escaped, name);
 786         g_free(escaped);
 787 
 788     } else {
 789         meta = crm_strdup_printf(METADATA_FORMAT, name, desc, name);
 790     }
 791 
 792     free(desc);
 793     free(path);
 794     return meta;
 795 }
 796 
 797 /*!
 798  * \internal
 799  * \brief Determine result of method from reply
 800  *
 801  * \param[in]     reply  Reply to start, stop, or restart request
 802  * \param[in,out] op     Action that was executed
 803  */
 804 static void
 805 process_unit_method_reply(DBusMessage *reply, svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 806 {
 807     bool start_stop = pcmk__strcase_any_of(op->action, PCMK_ACTION_START,
 808                                            PCMK_ACTION_STOP, NULL);
 809     DBusError error;
 810 
 811     dbus_error_init(&error);
 812 
 813     /* The first use of error here is not used other than as a non-NULL flag to
 814      * indicate that a request was indeed sent
 815      */
 816     if (pcmk_dbus_find_error((void *) &error, reply, &error)) {
 817         set_result_from_method_error(op, &error);
 818         dbus_error_free(&error);
 819 
 820     } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
 821                                      __func__, __LINE__)) {
 822         const char *reason = "systemd D-Bus method had unexpected reply";
 823 
 824         crm_info("DBus request for %s of %s succeeded but "
 825                  "return type was unexpected",
 826                  op->action, pcmk__s(op->rsc, "unknown resource"));
 827 
 828         if (!op->synchronous && start_stop) {
 829             /* The start or stop job is enqueued but is not complete. We need a
 830              * job path to detect completion in job_removed_filter().
 831              */
 832             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 833                                  reason);
 834 
 835         } else {
 836             /* Something weird happened, but the action is finished and there
 837              * was no D-Bus error. So call it a success.
 838              */
 839             services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, reason);
 840         }
 841 
 842     } else {
 843         const char *path = NULL;
 844 
 845         dbus_message_get_args(reply, NULL,
 846                               DBUS_TYPE_OBJECT_PATH, &path,
 847                               DBUS_TYPE_INVALID);
 848 
 849         crm_debug("DBus request for %s of %s using %s succeeded",
 850                   op->action, pcmk__s(op->rsc, "unknown resource"), path);
 851 
 852         if (!op->synchronous && start_stop) {
 853             // Should be set to unknown/pending already
 854             services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
 855             pcmk__str_update(&(op->opaque->job_path), path);
 856 
 857         } else {
 858             services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
 859         }
 860     }
 861 }
 862 
 863 /*!
 864  * \internal
 865  * \brief Process a systemd \c JobRemoved signal for a given service action
 866  *
 867  * This filter is expected to be added with \c finalize_async_action_dbus() as
 868  * the \c free_data_function. Then if \p message is a \c JobRemoved signal for
 869  * the action specified by \p user_data, the action's result is set, the filter
 870  * is removed, and the action is finalized.
 871  *
 872  * \param[in,out] connection  D-Bus connection
 873  * \param[in]     message     D-Bus message
 874  * \param[in,out] user_data   Service action (\c svc_action_t)
 875  *
 876  * \retval \c DBUS_HANDLER_RESULT_HANDLED if \p message is a \c JobRemoved
 877  *         signal for \p user_data
 878  * \retval \c DBUS_HANDLER_RESULT_NOT_YET_HANDLED otherwise (on error, if
 879  *         \p message is not a \c JobRemoved signal, or if the signal is for
 880  *         some other action's job)
 881  */
 882 static DBusHandlerResult
 883 job_removed_filter(DBusConnection *connection, DBusMessage *message,
     /* [previous][next][first][last][top][bottom][index][help] */
 884                    void *user_data)
 885 {
 886     svc_action_t *action = user_data;
 887     const char *action_name = NULL;
 888     uint32_t job_id = 0;
 889     const char *bus_path = NULL;
 890     const char *unit_name = NULL;
 891     const char *result = NULL;
 892     DBusError error;
 893 
 894     CRM_CHECK((connection != NULL) && (message != NULL),
 895               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
 896 
 897     // action should always be set when the filter is added
 898     if ((action == NULL)
 899         || !dbus_message_is_signal(message, BUS_NAME_MANAGER, "JobRemoved")) {
 900         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 901     }
 902 
 903     dbus_error_init(&error);
 904     if (!dbus_message_get_args(message, &error,
 905                                DBUS_TYPE_UINT32, &job_id,
 906                                DBUS_TYPE_OBJECT_PATH, &bus_path,
 907                                DBUS_TYPE_STRING, &unit_name,
 908                                DBUS_TYPE_STRING, &result,
 909                                DBUS_TYPE_INVALID)) {
 910         crm_err("Could not interpret systemd DBus signal: %s " QB_XS " (%s)",
 911                 error.message, error.name);
 912         dbus_error_free(&error);
 913         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 914     }
 915 
 916     if (!pcmk__str_eq(bus_path, action->opaque->job_path, pcmk__str_none)) {
 917         // This filter is not for this job
 918         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 919     }
 920 
 921     action_name = pcmk__s(action->action, "(unknown)");
 922 
 923     crm_trace("Setting %s result for %s (JobRemoved id=%" PRIu32 ", result=%s",
 924               action_name, unit_name, job_id, result);
 925 
 926     if (pcmk__str_eq(result, "done", pcmk__str_none)) {
 927         services__set_result(action, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
 928 
 929     } else if (pcmk__str_eq(result, "timeout", pcmk__str_none)) {
 930         services__format_result(action, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_TIMEOUT,
 931                                 "systemd %s job for %s timed out",
 932                                 action_name, unit_name);
 933 
 934     } else {
 935         services__format_result(action, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 936                                 "systemd %s job for %s failed with result '%s'",
 937                                 action_name, unit_name, result);
 938     }
 939 
 940     /* This instance of the filter was specifically for the given action.
 941      *
 942      * The action gets finalized by services__finalize_async_op() via the
 943      * filter's free_data_function.
 944      */
 945     dbus_connection_remove_filter(systemd_proxy, job_removed_filter, action);
 946     return DBUS_HANDLER_RESULT_HANDLED;
 947 }
 948 
 949 /*!
 950  * \internal
 951  * \brief \c DBusFreeFunction wrapper for \c services__finalize_async_op()
 952  *
 953  * \param[in,out] action  Asynchronous service action to finalize
 954  */
 955 static void
 956 finalize_async_action_dbus(void *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 957 {
 958     services__finalize_async_op((svc_action_t *) action);
 959 }
 960 
 961 /*!
 962  * \internal
 963  * \brief Process the completion of an asynchronous unit start, stop, or restart
 964  *
 965  * \param[in,out] pending    If not NULL, DBus call associated with request
 966  * \param[in,out] user_data  Action that was executed
 967  */
 968 static void
 969 unit_method_complete(DBusPendingCall *pending, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 970 {
 971     DBusMessage *reply = NULL;
 972     svc_action_t *op = user_data;
 973 
 974     crm_trace("Result for %s arrived", op->id);
 975 
 976     // Grab the reply
 977     if (pending != NULL) {
 978         reply = dbus_pending_call_steal_reply(pending);
 979     }
 980 
 981     // The call is no longer pending
 982     CRM_LOG_ASSERT(pending == op->opaque->pending);
 983     services_set_op_pending(op, NULL);
 984 
 985     process_unit_method_reply(reply, op);
 986 
 987     if (reply != NULL) {
 988         dbus_message_unref(reply);
 989     }
 990 
 991     if ((op->status == PCMK_EXEC_PENDING)
 992         && pcmk__strcase_any_of(op->action, PCMK_ACTION_START, PCMK_ACTION_STOP,
 993                                 NULL)) {
 994         /* Start and stop method calls return when the job is enqueued, not when
 995          * it's complete. Start and stop actions must be finalized after the job
 996          * is complete, because the action callback function may use it. We add
 997          * a message filter to process the JobRemoved signal, which indicates
 998          * completion.
 999          *
1000          * The filter takes ownership of op, which will be finalized when the
1001          * filter is later removed.
1002          */
1003         if (dbus_connection_add_filter(systemd_proxy, job_removed_filter, op,
1004                                        finalize_async_action_dbus)) {
1005             return;
1006         }
1007         crm_err("Could not add D-Bus filter for systemd JobRemoved signals");
1008         services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1009                              "Failed to add D-Bus filter for systemd "
1010                              "JobRemoved signal");
1011     }
1012     services__finalize_async_op(op);
1013 }
1014 
1015 /* When the cluster manages a systemd resource, we create a unit file override
1016  * to order the service "before" pacemaker. The "before" relationship won't
1017  * actually be used, since systemd won't ever start the resource -- we're
1018  * interested in the reverse shutdown ordering it creates, to ensure that
1019  * systemd doesn't stop the resource at shutdown while pacemaker is still
1020  * running.
1021  *
1022  * @TODO Add start timeout
1023  */
1024 #define SYSTEMD_UNIT_OVERRIDE_TEMPLATE                      \
1025     "[Unit]\n"                                              \
1026     "Description=Cluster Controlled %s\n"                   \
1027     "Before=pacemaker.service pacemaker_remote.service\n"
1028 
1029 #define SYSTEMD_SERVICE_OVERRIDE                            \
1030     "\n"                                                    \
1031     "[Service]\n"                                           \
1032     "Restart=no\n"
1033 
1034 /*!
1035  * \internal
1036  * \brief Get runtime drop-in directory path for a systemd unit
1037  *
1038  * \param[in] unit_name  Systemd unit (with extension)
1039  *
1040  * \return Drop-in directory path
1041  */
1042 static GString *
1043 get_override_dir(const char *unit_name)
     /* [previous][next][first][last][top][bottom][index][help] */
1044 {
1045     GString *buf = g_string_sized_new(128);
1046 
1047     pcmk__g_strcat(buf, "/run/systemd/system/", unit_name, ".d", NULL);
1048     return buf;
1049 }
1050 
1051 /*!
1052  * \internal
1053  * \brief Append systemd override filename to a directory path
1054  *
1055  * \param[in,out] buf  Buffer containing directory path to append to
1056  */
1057 static inline void
1058 append_override_basename(GString *buf)
     /* [previous][next][first][last][top][bottom][index][help] */
1059 {
1060     g_string_append(buf, "/50-pacemaker.conf");
1061 }
1062 
1063 /*!
1064  * \internal
1065  * \brief Create a runtime override file for a systemd unit
1066  *
1067  * The systemd daemon is then reloaded. This file does not survive a reboot.
1068  *
1069  * \param[in] agent    Systemd resource agent
1070  * \param[in] timeout  Timeout for systemd daemon reload
1071  *
1072  * \return Standard Pacemaker return code
1073  *
1074  * \note Any configuration in \c /etc takes precedence over our drop-in.
1075  * \todo Document this in Pacemaker Explained or Administration?
1076  */
1077 static int
1078 systemd_create_override(const char *agent, int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
1079 {
1080     char *unit_name = NULL;
1081     GString *filename = NULL;
1082     GString *override = NULL;
1083     FILE *fp = NULL;
1084     int fd = 0;
1085     int rc = pcmk_rc_ok;
1086 
1087     unit_name = systemd_unit_name(agent, false);
1088     CRM_CHECK(!pcmk__str_empty(unit_name),
1089               rc = EINVAL; goto done);
1090 
1091     filename = get_override_dir(unit_name);
1092     rc = pcmk__build_path(filename->str, 0755);
1093     if (rc != pcmk_rc_ok) {
1094         crm_err("Could not create systemd override directory %s: %s",
1095                 filename->str, pcmk_rc_str(rc));
1096         goto done;
1097     }
1098 
1099     append_override_basename(filename);
1100     fp = fopen(filename->str, "w");
1101     if (fp == NULL) {
1102         rc = errno;
1103         crm_err("Cannot open systemd override file %s for writing: %s",
1104                 filename->str, pcmk_rc_str(rc));
1105         goto done;
1106     }
1107 
1108     // Ensure the override file is world-readable (avoid systemd warning in log)
1109     fd = fileno(fp);
1110     if ((fd < 0) || (fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0)) {
1111         rc = errno;
1112         crm_err("Failed to set permissions on systemd override file %s: %s",
1113                 filename->str, pcmk_rc_str(rc));
1114         goto done;
1115     }
1116 
1117     override = g_string_sized_new(2 * sizeof(SYSTEMD_UNIT_OVERRIDE_TEMPLATE));
1118     g_string_printf(override, SYSTEMD_UNIT_OVERRIDE_TEMPLATE, unit_name);
1119     if (pcmk__ends_with_ext(unit_name, ".service")) {
1120         g_string_append(override, SYSTEMD_SERVICE_OVERRIDE);
1121     }
1122 
1123     if (fputs(override->str, fp) == EOF) {
1124         rc = EIO;
1125         crm_err("Cannot write to systemd override file %s", filename->str);
1126     }
1127 
1128 done:
1129     if (fp != NULL) {
1130         fclose(fp);
1131     }
1132 
1133     if (rc == pcmk_rc_ok) {
1134         // @TODO Make sure the reload succeeds
1135         systemd_daemon_reload(timeout);
1136 
1137     } else if (fp != NULL) {
1138         // File was created, so remove it
1139         unlink(filename->str);
1140     }
1141 
1142     free(unit_name);
1143 
1144     // coverity[check_after_deref : FALSE]
1145     if (filename != NULL) {
1146         g_string_free(filename, TRUE);
1147     }
1148     if (override != NULL) {
1149         g_string_free(override, TRUE);
1150     }
1151     return rc;
1152 }
1153 
1154 static void
1155 systemd_remove_override(const char *agent, int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
1156 {
1157     char *unit_name = systemd_unit_name(agent, false);
1158     GString *filename = NULL;
1159 
1160     CRM_CHECK(!pcmk__str_empty(unit_name), goto done);
1161 
1162     filename = get_override_dir(unit_name);
1163     append_override_basename(filename);
1164 
1165     if (unlink(filename->str) < 0) {
1166         int rc = errno;
1167 
1168         if (rc != ENOENT) {
1169             // Stop may be called when already stopped, which is fine
1170             crm_warn("Cannot remove systemd override file %s: %s",
1171                      filename->str, pcmk_rc_str(rc));
1172         }
1173 
1174     } else {
1175         systemd_daemon_reload(timeout);
1176     }
1177 
1178 done:
1179     free(unit_name);
1180 
1181     // coverity[check_after_deref : FALSE]
1182     if (filename != NULL) {
1183         g_string_free(filename, TRUE);
1184     }
1185 }
1186 
1187 /*!
1188  * \internal
1189  * \brief Parse result of systemd status check
1190  *
1191  * Set a status action's exit status and execution status based on a DBus
1192  * property check result, and finalize the action if asynchronous.
1193  *
1194  * \param[in]     name      DBus interface name for property that was checked
1195  * \param[in]     state     Property value
1196  * \param[in,out] userdata  Status action that check was done for
1197  */
1198 static void
1199 parse_status_result(const char *name, const char *state, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
1200 {
1201     svc_action_t *op = userdata;
1202 
1203     crm_trace("Resource %s has %s='%s'",
1204               pcmk__s(op->rsc, "(unspecified)"), name,
1205               pcmk__s(state, "<null>"));
1206 
1207     if (pcmk__str_eq(state, "active", pcmk__str_none)) {
1208         services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
1209 
1210     } else if (pcmk__str_eq(state, "reloading", pcmk__str_none)) {
1211         services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
1212 
1213     } else if (pcmk__str_eq(state, "activating", pcmk__str_none)) {
1214         services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
1215 
1216     } else if (pcmk__str_eq(state, "deactivating", pcmk__str_none)) {
1217         services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
1218 
1219     } else {
1220         services__set_result(op, PCMK_OCF_NOT_RUNNING, PCMK_EXEC_DONE, state);
1221     }
1222 
1223     if (!(op->synchronous)) {
1224         services_set_op_pending(op, NULL);
1225         services__finalize_async_op(op);
1226     }
1227 }
1228 
1229 /*!
1230  * \internal
1231  * \brief Invoke a systemd unit, given its DBus object path
1232  *
1233  * \param[in,out] op    Action to execute
1234  * \param[in]     unit  DBus object path of systemd unit to invoke
1235  */
1236 static void
1237 invoke_unit_by_path(svc_action_t *op, const char *unit)
     /* [previous][next][first][last][top][bottom][index][help] */
1238 {
1239     const char *method = NULL;
1240     DBusMessage *msg = NULL;
1241     DBusMessage *reply = NULL;
1242 
1243     if (pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR, PCMK_ACTION_STATUS,
1244                          NULL)) {
1245         DBusPendingCall *pending = NULL;
1246         char *state;
1247 
1248         state = systemd_get_property(unit, "ActiveState",
1249                                      (op->synchronous? NULL : parse_status_result),
1250                                      op, (op->synchronous? NULL : &pending),
1251                                      op->timeout);
1252         if (op->synchronous) {
1253             parse_status_result("ActiveState", state, op);
1254             free(state);
1255 
1256         } else if (pending == NULL) { // Could not get ActiveState property
1257             services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1258                                     "Could not get state for unit %s from DBus",
1259                                     op->agent);
1260             services__finalize_async_op(op);
1261 
1262         } else {
1263             services_set_op_pending(op, pending);
1264         }
1265         return;
1266 
1267     } else if (pcmk__str_eq(op->action, PCMK_ACTION_START, pcmk__str_none)) {
1268         int rc = pcmk_rc_ok;
1269 
1270         method = "StartUnit";
1271         rc = systemd_create_override(op->agent, op->timeout);
1272         if (rc != pcmk_rc_ok) {
1273             services__format_result(op, pcmk_rc2ocf(rc), PCMK_EXEC_ERROR,
1274                                     "Failed to create systemd override file "
1275                                     "for %s",
1276                                     pcmk__s(op->agent, "(unspecified)"));
1277             if (!(op->synchronous)) {
1278                 services__finalize_async_op(op);
1279             }
1280             return;
1281         }
1282 
1283     } else if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_none)) {
1284         method = "StopUnit";
1285         systemd_remove_override(op->agent, op->timeout);
1286 
1287     } else if (pcmk__str_eq(op->action, "restart", pcmk__str_none)) {
1288         method = "RestartUnit";
1289 
1290     } else {
1291         services__format_result(op, PCMK_OCF_UNIMPLEMENT_FEATURE,
1292                                 PCMK_EXEC_ERROR,
1293                                 "Action %s not implemented "
1294                                 "for systemd resources",
1295                                 pcmk__s(op->action, "(unspecified)"));
1296         if (!(op->synchronous)) {
1297             services__finalize_async_op(op);
1298         }
1299         return;
1300     }
1301 
1302     crm_trace("Calling %s for unit path %s%s%s",
1303               method, unit,
1304               ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""));
1305 
1306     msg = systemd_new_method(method);
1307     pcmk__assert(msg != NULL);
1308 
1309     /* (ss) */
1310     {
1311         const char *replace_s = "replace";
1312         char *name = systemd_unit_name(op->agent,
1313                                        pcmk__str_eq(op->action,
1314                                                     PCMK_ACTION_META_DATA,
1315                                                     pcmk__str_none));
1316 
1317         CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID));
1318         CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &replace_s, DBUS_TYPE_INVALID));
1319 
1320         free(name);
1321     }
1322 
1323     if (op->synchronous) {
1324         reply = systemd_send_recv(msg, NULL, op->timeout);
1325         dbus_message_unref(msg);
1326         process_unit_method_reply(reply, op);
1327         if (reply != NULL) {
1328             dbus_message_unref(reply);
1329         }
1330 
1331     } else {
1332         DBusPendingCall *pending = systemd_send(msg, unit_method_complete, op,
1333                                                 op->timeout);
1334 
1335         dbus_message_unref(msg);
1336         if (pending == NULL) {
1337             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1338                                  "Unable to send DBus message");
1339             services__finalize_async_op(op);
1340 
1341         } else {
1342             services_set_op_pending(op, pending);
1343         }
1344     }
1345 }
1346 
1347 static gboolean
1348 systemd_timeout_callback(gpointer p)
     /* [previous][next][first][last][top][bottom][index][help] */
1349 {
1350     svc_action_t * op = p;
1351 
1352     op->opaque->timerid = 0;
1353     crm_info("%s action for systemd unit %s named '%s' timed out",
1354              op->action, op->agent, op->rsc);
1355     services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_TIMEOUT,
1356                             "%s action for systemd unit %s "
1357                             "did not complete in time", op->action, op->agent);
1358 
1359     if (op->opaque->job_path != NULL) {
1360         // A filter owns this op
1361         dbus_connection_remove_filter(systemd_proxy, job_removed_filter, op);
1362 
1363     } else {
1364         services__finalize_async_op(op);
1365     }
1366     return FALSE;
1367 }
1368 
1369 /*!
1370  * \internal
1371  * \brief Execute a systemd action
1372  *
1373  * \param[in,out] op  Action to execute
1374  *
1375  * \return Standard Pacemaker return code
1376  * \retval EBUSY          Recurring operation could not be initiated
1377  * \retval pcmk_rc_error  Synchronous action failed
1378  * \retval pcmk_rc_ok     Synchronous action succeeded, or asynchronous action
1379  *                        should not be freed (because it's pending or because
1380  *                        it failed to execute and was already freed)
1381  *
1382  * \note If the return value for an asynchronous action is not pcmk_rc_ok, the
1383  *       caller is responsible for freeing the action.
1384  */
1385 int
1386 services__execute_systemd(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
1387 {
1388     pcmk__assert(op != NULL);
1389 
1390     if (pcmk__str_empty(op->action) || pcmk__str_empty(op->agent)) {
1391         services__set_result(op, PCMK_OCF_NOT_CONFIGURED, PCMK_EXEC_ERROR_FATAL,
1392                              "Bug in action caller");
1393         goto done;
1394     }
1395 
1396     if (!systemd_init()) {
1397         services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1398                              "No DBus connection");
1399         goto done;
1400     }
1401 
1402     crm_debug("Performing %ssynchronous %s op on systemd unit %s%s%s",
1403               (op->synchronous? "" : "a"), op->action, op->agent,
1404               ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""));
1405 
1406     if (pcmk__str_eq(op->action, PCMK_ACTION_META_DATA, pcmk__str_casei)) {
1407         op->stdout_data = systemd_unit_metadata(op->agent, op->timeout);
1408         services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
1409         goto done;
1410     }
1411 
1412     /* invoke_unit_by_name() should always override these values, which are here
1413      * just as a fail-safe in case there are any code paths that neglect to
1414      */
1415     services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1416                          "Bug in service library");
1417 
1418     if (invoke_unit_by_name(op->agent, op, NULL) == pcmk_rc_ok) {
1419         // @TODO Why plus 5000? No explanation in fccd046.
1420         op->opaque->timerid = pcmk__create_timer(op->timeout + 5000,
1421                                                  systemd_timeout_callback, op);
1422         services_add_inflight_op(op);
1423         return pcmk_rc_ok;
1424     }
1425 
1426 done:
1427     if (op->synchronous) {
1428         return (op->rc == PCMK_OCF_OK)? pcmk_rc_ok : pcmk_rc_error;
1429     } else {
1430         return services__finalize_async_op(op);
1431     }
1432 }

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