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. systemd_init
  8. systemd_get_property
  9. systemd_cleanup
  10. systemd_unit_extension
  11. systemd_service_name
  12. systemd_daemon_reload_complete
  13. systemd_daemon_reload
  14. set_result_from_method_error
  15. execute_after_loadunit
  16. loadunit_completed
  17. invoke_unit_by_name
  18. sort_str
  19. systemd_unit_listall
  20. systemd_unit_exists
  21. systemd_unit_metadata
  22. process_unit_method_reply
  23. unit_method_complete
  24. create_world_readable
  25. create_override_dir
  26. get_override_filename
  27. systemd_create_override
  28. systemd_remove_override
  29. parse_status_result
  30. invoke_unit_by_path
  31. systemd_timeout_callback
  32. services__execute_systemd

   1 /*
   2  * Copyright 2012-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 #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 <sys/stat.h>
  18 #include <gio/gio.h>
  19 #include <services_private.h>
  20 #include <systemd.h>
  21 #include <dbus/dbus.h>
  22 #include <pcmk-dbus.h>
  23 
  24 static void invoke_unit_by_path(svc_action_t *op, const char *unit);
  25 
  26 #define BUS_NAME         "org.freedesktop.systemd1"
  27 #define BUS_NAME_MANAGER BUS_NAME ".Manager"
  28 #define BUS_NAME_UNIT    BUS_NAME ".Unit"
  29 #define BUS_PATH         "/org/freedesktop/systemd1"
  30 
  31 /*!
  32  * \internal
  33  * \brief Prepare a systemd action
  34  *
  35  * \param[in,out] op  Action to prepare
  36  *
  37  * \return Standard Pacemaker return code
  38  */
  39 int
  40 services__systemd_prepare(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
  41 {
  42     op->opaque->exec = strdup("systemd-dbus");
  43     if (op->opaque->exec == NULL) {
  44         return ENOMEM;
  45     }
  46     return pcmk_rc_ok;
  47 }
  48 
  49 /*!
  50  * \internal
  51  * \brief Map a systemd result to a standard OCF result
  52  *
  53  * \param[in] exit_status  Systemd result
  54  *
  55  * \return Standard OCF result
  56  */
  57 enum ocf_exitcode
  58 services__systemd2ocf(int exit_status)
     /* [previous][next][first][last][top][bottom][index][help] */
  59 {
  60     // This library uses OCF codes for systemd actions
  61     return (enum ocf_exitcode) exit_status;
  62 }
  63 
  64 static inline DBusMessage *
  65 systemd_new_method(const char *method)
     /* [previous][next][first][last][top][bottom][index][help] */
  66 {
  67     crm_trace("Calling: %s on " BUS_NAME_MANAGER, method);
  68     return dbus_message_new_method_call(BUS_NAME, BUS_PATH, BUS_NAME_MANAGER,
  69                                         method);
  70 }
  71 
  72 /*
  73  * Functions to manage a static DBus connection
  74  */
  75 
  76 static DBusConnection* systemd_proxy = NULL;
  77 
  78 static inline DBusPendingCall *
  79 systemd_send(DBusMessage *msg,
     /* [previous][next][first][last][top][bottom][index][help] */
  80              void(*done)(DBusPendingCall *pending, void *user_data),
  81              void *user_data, int timeout)
  82 {
  83     return pcmk_dbus_send(msg, systemd_proxy, done, user_data, timeout);
  84 }
  85 
  86 static inline DBusMessage *
  87 systemd_send_recv(DBusMessage *msg, DBusError *error, int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
  88 {
  89     return pcmk_dbus_send_recv(msg, systemd_proxy, error, timeout);
  90 }
  91 
  92 /*!
  93  * \internal
  94  * \brief Send a method to systemd without arguments, and wait for reply
  95  *
  96  * \param[in] method  Method to send
  97  *
  98  * \return Systemd reply on success, NULL (and error will be logged) otherwise
  99  *
 100  * \note The caller must call dbus_message_unref() on the reply after
 101  *       handling it.
 102  */
 103 static DBusMessage *
 104 systemd_call_simple_method(const char *method)
     /* [previous][next][first][last][top][bottom][index][help] */
 105 {
 106     DBusMessage *msg = systemd_new_method(method);
 107     DBusMessage *reply = NULL;
 108     DBusError error;
 109 
 110     /* Don't call systemd_init() here, because that calls this */
 111     CRM_CHECK(systemd_proxy, return NULL);
 112 
 113     if (msg == NULL) {
 114         crm_err("Could not create message to send %s to systemd", method);
 115         return NULL;
 116     }
 117 
 118     dbus_error_init(&error);
 119     reply = systemd_send_recv(msg, &error, DBUS_TIMEOUT_USE_DEFAULT);
 120     dbus_message_unref(msg);
 121 
 122     if (dbus_error_is_set(&error)) {
 123         crm_err("Could not send %s to systemd: %s (%s)",
 124                 method, error.message, error.name);
 125         dbus_error_free(&error);
 126         return NULL;
 127 
 128     } else if (reply == NULL) {
 129         crm_err("Could not send %s to systemd: no reply received", method);
 130         return NULL;
 131     }
 132 
 133     return reply;
 134 }
 135 
 136 static gboolean
 137 systemd_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 138 {
 139     static int need_init = 1;
 140     // https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
 141 
 142     if (systemd_proxy
 143         && dbus_connection_get_is_connected(systemd_proxy) == FALSE) {
 144         crm_warn("Connection to System DBus is closed. Reconnecting...");
 145         pcmk_dbus_disconnect(systemd_proxy);
 146         systemd_proxy = NULL;
 147         need_init = 1;
 148     }
 149 
 150     if (need_init) {
 151         need_init = 0;
 152         systemd_proxy = pcmk_dbus_connect();
 153     }
 154     if (systemd_proxy == NULL) {
 155         return FALSE;
 156     }
 157     return TRUE;
 158 }
 159 
 160 static inline char *
 161 systemd_get_property(const char *unit, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
 162                      void (*callback)(const char *name, const char *value, void *userdata),
 163                      void *userdata, DBusPendingCall **pending, int timeout)
 164 {
 165     return systemd_proxy?
 166            pcmk_dbus_get_property(systemd_proxy, BUS_NAME, unit, BUS_NAME_UNIT,
 167                                   name, callback, userdata, pending, timeout)
 168            : NULL;
 169 }
 170 
 171 void
 172 systemd_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 173 {
 174     if (systemd_proxy) {
 175         pcmk_dbus_disconnect(systemd_proxy);
 176         systemd_proxy = NULL;
 177     }
 178 }
 179 
 180 /*
 181  * end of systemd_proxy functions
 182  */
 183 
 184 /*!
 185  * \internal
 186  * \brief Check whether a file name represents a manageable systemd unit
 187  *
 188  * \param[in] name  File name to check
 189  *
 190  * \return Pointer to "dot" before filename extension if so, NULL otherwise
 191  */
 192 static const char *
 193 systemd_unit_extension(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 194 {
 195     if (name) {
 196         const char *dot = strrchr(name, '.');
 197 
 198         if (dot && (!strcmp(dot, ".service")
 199                     || !strcmp(dot, ".socket")
 200                     || !strcmp(dot, ".mount")
 201                     || !strcmp(dot, ".timer")
 202                     || !strcmp(dot, ".path"))) {
 203             return dot;
 204         }
 205     }
 206     return NULL;
 207 }
 208 
 209 static char *
 210 systemd_service_name(const char *name, bool add_instance_name)
     /* [previous][next][first][last][top][bottom][index][help] */
 211 {
 212     const char *dot = NULL;
 213 
 214     if (pcmk__str_empty(name)) {
 215         return NULL;
 216     }
 217 
 218     /* Services that end with an @ sign are systemd templates.  They expect an
 219      * instance name to follow the service name.  If no instance name was
 220      * provided, just add "pacemaker" to the string as the instance name.  It
 221      * doesn't seem to matter for purposes of looking up whether a service
 222      * exists or not.
 223      *
 224      * A template can be specified either with or without the unit extension,
 225      * so this block handles both cases.
 226      */
 227     dot = systemd_unit_extension(name);
 228 
 229     if (dot) {
 230         if (dot != name && *(dot-1) == '@') {
 231             char *s = NULL;
 232 
 233             if (asprintf(&s, "%.*spacemaker%s", (int) (dot-name), name, dot) == -1) {
 234                 /* If asprintf fails, just return name. */
 235                 return strdup(name);
 236             }
 237 
 238             return s;
 239         } else {
 240             return strdup(name);
 241         }
 242 
 243     } else if (add_instance_name && *(name+strlen(name)-1) == '@') {
 244         return crm_strdup_printf("%spacemaker.service", name);
 245 
 246     } else {
 247         return crm_strdup_printf("%s.service", name);
 248     }
 249 }
 250 
 251 static void
 252 systemd_daemon_reload_complete(DBusPendingCall *pending, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 253 {
 254     DBusError error;
 255     DBusMessage *reply = NULL;
 256     unsigned int reload_count = GPOINTER_TO_UINT(user_data);
 257 
 258     dbus_error_init(&error);
 259     if(pending) {
 260         reply = dbus_pending_call_steal_reply(pending);
 261     }
 262 
 263     if (pcmk_dbus_find_error(pending, reply, &error)) {
 264         crm_warn("Could not issue systemd reload %d: %s",
 265                  reload_count, error.message);
 266         dbus_error_free(&error);
 267 
 268     } else {
 269         crm_trace("Reload %d complete", reload_count);
 270     }
 271 
 272     if(pending) {
 273         dbus_pending_call_unref(pending);
 274     }
 275     if(reply) {
 276         dbus_message_unref(reply);
 277     }
 278 }
 279 
 280 static bool
 281 systemd_daemon_reload(int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
 282 {
 283     static unsigned int reload_count = 0;
 284     DBusMessage *msg = systemd_new_method("Reload");
 285 
 286     reload_count++;
 287     CRM_ASSERT(msg != NULL);
 288     systemd_send(msg, systemd_daemon_reload_complete,
 289                  GUINT_TO_POINTER(reload_count), timeout);
 290     dbus_message_unref(msg);
 291 
 292     return TRUE;
 293 }
 294 
 295 /*!
 296  * \internal
 297  * \brief Set an action result based on a method error
 298  *
 299  * \param[in,out] op     Action to set result for
 300  * \param[in]     error  Method error
 301  */
 302 static void
 303 set_result_from_method_error(svc_action_t *op, const DBusError *error)
     /* [previous][next][first][last][top][bottom][index][help] */
 304 {
 305     services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 306                          "Unable to invoke systemd DBus method");
 307 
 308     if (strstr(error->name, "org.freedesktop.systemd1.InvalidName")
 309         || strstr(error->name, "org.freedesktop.systemd1.LoadFailed")
 310         || strstr(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
 311 
 312         if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
 313             crm_trace("Masking systemd stop failure (%s) for %s "
 314                       "because unknown service can be considered stopped",
 315                       error->name, pcmk__s(op->rsc, "unknown resource"));
 316             services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
 317             return;
 318         }
 319 
 320         services__format_result(op, PCMK_OCF_NOT_INSTALLED,
 321                                PCMK_EXEC_NOT_INSTALLED,
 322                                "systemd unit %s not found", op->agent);
 323     }
 324 
 325     crm_info("DBus request for %s of systemd unit %s%s%s failed: %s",
 326              op->action, op->agent,
 327              ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""),
 328              error->message);
 329 }
 330 
 331 /*!
 332  * \internal
 333  * \brief Extract unit path from LoadUnit reply, and execute action
 334  *
 335  * \param[in]     reply  LoadUnit reply
 336  * \param[in,out] op     Action to execute (or NULL to just return path)
 337  *
 338  * \return DBus object path for specified unit if successful (only valid for
 339  *         lifetime of \p reply), otherwise NULL
 340  */
 341 static const char *
 342 execute_after_loadunit(DBusMessage *reply, svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 343 {
 344     const char *path = NULL;
 345     DBusError error;
 346 
 347     /* path here is not used other than as a non-NULL flag to indicate that a
 348      * request was indeed sent
 349      */
 350     if (pcmk_dbus_find_error((void *) &path, reply, &error)) {
 351         if (op != NULL) {
 352             set_result_from_method_error(op, &error);
 353         }
 354         dbus_error_free(&error);
 355 
 356     } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
 357                                      __func__, __LINE__)) {
 358         if (op != NULL) {
 359             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 360                                  "systemd DBus method had unexpected reply");
 361             crm_info("Could not load systemd unit %s for %s: "
 362                      "DBus reply has unexpected type", op->agent, op->id);
 363         } else {
 364             crm_info("Could not load systemd unit: "
 365                      "DBus reply has unexpected type");
 366         }
 367 
 368     } else {
 369         dbus_message_get_args (reply, NULL,
 370                                DBUS_TYPE_OBJECT_PATH, &path,
 371                                DBUS_TYPE_INVALID);
 372     }
 373 
 374     if (op != NULL) {
 375         if (path != NULL) {
 376             invoke_unit_by_path(op, path);
 377 
 378         } else if (!(op->synchronous)) {
 379             services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 380                                     "No DBus object found for systemd unit %s",
 381                                     op->agent);
 382             services__finalize_async_op(op);
 383         }
 384     }
 385 
 386     return path;
 387 }
 388 
 389 /*!
 390  * \internal
 391  * \brief Execute a systemd action after its LoadUnit completes
 392  *
 393  * \param[in,out] pending    If not NULL, DBus call associated with LoadUnit
 394  * \param[in,out] user_data  Action to execute
 395  */
 396 static void
 397 loadunit_completed(DBusPendingCall *pending, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 398 {
 399     DBusMessage *reply = NULL;
 400     svc_action_t *op = user_data;
 401 
 402     crm_trace("LoadUnit result for %s arrived", op->id);
 403 
 404     // Grab the reply
 405     if (pending != NULL) {
 406         reply = dbus_pending_call_steal_reply(pending);
 407     }
 408 
 409     // The call is no longer pending
 410     CRM_LOG_ASSERT(pending == op->opaque->pending);
 411     services_set_op_pending(op, NULL);
 412 
 413     // Execute the desired action based on the reply
 414     execute_after_loadunit(reply, user_data);
 415     if (reply != NULL) {
 416         dbus_message_unref(reply);
 417     }
 418 }
 419 
 420 /*!
 421  * \internal
 422  * \brief Execute a systemd action, given the unit name
 423  *
 424  * \param[in]     arg_name  Unit name (possibly without ".service" extension)
 425  * \param[in,out] op        Action to execute (if NULL, just get object path)
 426  * \param[out]    path      If non-NULL and \p op is NULL or synchronous, where
 427  *                          to store DBus object path for specified unit
 428  *
 429  * \return Standard Pacemaker return code (for NULL \p op, pcmk_rc_ok means unit
 430  *         was found; for synchronous actions, pcmk_rc_ok means unit was
 431  *         executed, with the actual result stored in \p op; for asynchronous
 432  *         actions, pcmk_rc_ok means action was initiated)
 433  * \note It is the caller's responsibility to free the path.
 434  */
 435 static int
 436 invoke_unit_by_name(const char *arg_name, svc_action_t *op, char **path)
     /* [previous][next][first][last][top][bottom][index][help] */
 437 {
 438     DBusMessage *msg;
 439     DBusMessage *reply = NULL;
 440     DBusPendingCall *pending = NULL;
 441     char *name = NULL;
 442 
 443     if (!systemd_init()) {
 444         if (op != NULL) {
 445             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 446                                  "No DBus connection");
 447         }
 448         return ENOTCONN;
 449     }
 450 
 451     /* Create a LoadUnit DBus method (equivalent to GetUnit if already loaded),
 452      * which makes the unit usable via further DBus methods.
 453      *
 454      * <method name="LoadUnit">
 455      *  <arg name="name" type="s" direction="in"/>
 456      *  <arg name="unit" type="o" direction="out"/>
 457      * </method>
 458      */
 459     msg = systemd_new_method("LoadUnit");
 460     CRM_ASSERT(msg != NULL);
 461 
 462     // Add the (expanded) unit name as the argument
 463     name = systemd_service_name(arg_name,
 464                                 (op == NULL)
 465                                 || pcmk__str_eq(op->action,
 466                                                 PCMK_ACTION_META_DATA,
 467                                                 pcmk__str_none));
 468     CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name,
 469                                             DBUS_TYPE_INVALID));
 470     free(name);
 471 
 472     if ((op == NULL) || op->synchronous) {
 473         // For synchronous ops, wait for a reply and extract the result
 474         const char *unit = NULL;
 475         int rc = pcmk_rc_ok;
 476 
 477         reply = systemd_send_recv(msg, NULL,
 478                                   (op? op->timeout : DBUS_TIMEOUT_USE_DEFAULT));
 479         dbus_message_unref(msg);
 480 
 481         unit = execute_after_loadunit(reply, op);
 482         if (unit == NULL) {
 483             rc = ENOENT;
 484             if (path != NULL) {
 485                 *path = NULL;
 486             }
 487         } else if (path != NULL) {
 488             *path = strdup(unit);
 489             if (*path == NULL) {
 490                 rc = ENOMEM;
 491             }
 492         }
 493 
 494         if (reply != NULL) {
 495             dbus_message_unref(reply);
 496         }
 497         return rc;
 498     }
 499 
 500     // For asynchronous ops, initiate the LoadUnit call and return
 501     pending = systemd_send(msg, loadunit_completed, op, op->timeout);
 502     if (pending == NULL) {
 503         services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 504                              "Unable to send DBus message");
 505         dbus_message_unref(msg);
 506         return ECOMM;
 507     }
 508 
 509     // LoadUnit was successfully initiated
 510     services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
 511     services_set_op_pending(op, pending);
 512     dbus_message_unref(msg);
 513     return pcmk_rc_ok;
 514 }
 515 
 516 /*!
 517  * \internal
 518  * \brief Compare two strings alphabetically (case-insensitive)
 519  *
 520  * \param[in] a  First string to compare
 521  * \param[in] b  Second string to compare
 522  *
 523  * \return 0 if strings are equal, -1 if a < b, 1 if a > b
 524  *
 525  * \note Usable as a GCompareFunc with g_list_sort().
 526  *       NULL is considered less than non-NULL.
 527  */
 528 static gint
 529 sort_str(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
 530 {
 531     if (!a && !b) {
 532         return 0;
 533     } else if (!a) {
 534         return -1;
 535     } else if (!b) {
 536         return 1;
 537     }
 538     return strcasecmp(a, b);
 539 }
 540 
 541 GList *
 542 systemd_unit_listall(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 543 {
 544     int nfiles = 0;
 545     GList *units = NULL;
 546     DBusMessageIter args;
 547     DBusMessageIter unit;
 548     DBusMessageIter elem;
 549     DBusMessage *reply = NULL;
 550 
 551     if (systemd_init() == FALSE) {
 552         return NULL;
 553     }
 554 
 555 /*
 556         "  <method name=\"ListUnitFiles\">\n"                               \
 557         "   <arg name=\"files\" type=\"a(ss)\" direction=\"out\"/>\n" \
 558         "  </method>\n"                                                 \
 559 */
 560 
 561     reply = systemd_call_simple_method("ListUnitFiles");
 562     if (reply == NULL) {
 563         return NULL;
 564     }
 565     if (!dbus_message_iter_init(reply, &args)) {
 566         crm_err("Could not list systemd unit files: systemd reply has no arguments");
 567         dbus_message_unref(reply);
 568         return NULL;
 569     }
 570     if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY,
 571                               __func__, __LINE__)) {
 572         crm_err("Could not list systemd unit files: systemd reply has invalid arguments");
 573         dbus_message_unref(reply);
 574         return NULL;
 575     }
 576 
 577     dbus_message_iter_recurse(&args, &unit);
 578     for (; dbus_message_iter_get_arg_type(&unit) != DBUS_TYPE_INVALID;
 579         dbus_message_iter_next(&unit)) {
 580 
 581         DBusBasicValue value;
 582         const char *match = NULL;
 583         char *unit_name = NULL;
 584         char *basename = NULL;
 585 
 586         if(!pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_STRUCT, __func__, __LINE__)) {
 587             crm_warn("Skipping systemd reply argument with unexpected type");
 588             continue;
 589         }
 590 
 591         dbus_message_iter_recurse(&unit, &elem);
 592         if(!pcmk_dbus_type_check(reply, &elem, DBUS_TYPE_STRING, __func__, __LINE__)) {
 593             crm_warn("Skipping systemd reply argument with no string");
 594             continue;
 595         }
 596 
 597         dbus_message_iter_get_basic(&elem, &value);
 598         if (value.str == NULL) {
 599             crm_debug("ListUnitFiles reply did not provide a string");
 600             continue;
 601         }
 602         crm_trace("DBus ListUnitFiles listed: %s", value.str);
 603 
 604         match = systemd_unit_extension(value.str);
 605         if (match == NULL) {
 606             // This is not a unit file type we know how to manage
 607             crm_debug("ListUnitFiles entry '%s' is not supported as resource",
 608                       value.str);
 609             continue;
 610         }
 611 
 612         // ListUnitFiles returns full path names, we just want base name
 613         basename = strrchr(value.str, '/');
 614         if (basename) {
 615             basename = basename + 1;
 616         } else {
 617             basename = value.str;
 618         }
 619 
 620         if (!strcmp(match, ".service")) {
 621             // Service is the "default" unit type, so strip it
 622             unit_name = strndup(basename, match - basename);
 623         } else {
 624             unit_name = strdup(basename);
 625         }
 626 
 627         nfiles++;
 628         units = g_list_prepend(units, unit_name);
 629     }
 630 
 631     dbus_message_unref(reply);
 632 
 633     crm_trace("Found %d manageable systemd unit files", nfiles);
 634     units = g_list_sort(units, sort_str);
 635     return units;
 636 }
 637 
 638 gboolean
 639 systemd_unit_exists(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 640 {
 641     char *path = NULL;
 642     char *state = NULL;
 643 
 644     /* Note: Makes a blocking dbus calls
 645      * Used by resources_find_service_class() when resource class=service
 646      */
 647     if ((invoke_unit_by_name(name, NULL, &path) != pcmk_rc_ok)
 648         || (path == NULL)) {
 649         return FALSE;
 650     }
 651 
 652     /* A successful LoadUnit is not sufficient to determine the unit's
 653      * existence; it merely means the LoadUnit request received a reply.
 654      * We must make another blocking call to check the LoadState property.
 655      */
 656     state = systemd_get_property(path, "LoadState", NULL, NULL, NULL,
 657                                  DBUS_TIMEOUT_USE_DEFAULT);
 658     free(path);
 659     if (pcmk__str_any_of(state, "loaded", "masked", NULL)) {
 660         free(state);
 661         return TRUE;
 662     }
 663     free(state);
 664     return FALSE;
 665 }
 666 
 667 // @TODO Use XML string constants and maybe a real XML object
 668 #define METADATA_FORMAT                                                        \
 669     "<?xml " PCMK_XA_VERSION "=\"1.0\"?>\n"                                    \
 670     "<" PCMK_XE_RESOURCE_AGENT " "                                             \
 671         PCMK_XA_NAME "=\"%s\" "                                                \
 672         PCMK_XA_VERSION "=\"" PCMK_DEFAULT_AGENT_VERSION "\">\n"               \
 673     "  <" PCMK_XE_VERSION ">1.1</" PCMK_XE_VERSION ">\n"                       \
 674     "  <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">\n"       \
 675     "    %s\n"                                                                 \
 676     "  </" PCMK_XE_LONGDESC ">\n"                                              \
 677     "  <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">"        \
 678         "systemd unit file for %s"                                             \
 679       "</" PCMK_XE_SHORTDESC ">\n"                                             \
 680     "  <" PCMK_XE_PARAMETERS "/>\n"                                            \
 681     "  <" PCMK_XE_ACTIONS ">\n"                                                \
 682     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_START "\""       \
 683                            " " PCMK_META_TIMEOUT "=\"100s\" />\n"              \
 684     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STOP "\""        \
 685                            " " PCMK_META_TIMEOUT "=\"100s\" />\n"              \
 686     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STATUS "\""      \
 687                            " " PCMK_META_TIMEOUT "=\"100s\" />\n"              \
 688     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_MONITOR "\""     \
 689                            " " PCMK_META_TIMEOUT "=\"100s\""                   \
 690                            " " PCMK_META_INTERVAL "=\"60s\" />\n"              \
 691     "    <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_META_DATA "\""   \
 692                            " " PCMK_META_TIMEOUT "=\"5s\" />\n"                \
 693     "  </" PCMK_XE_ACTIONS ">\n"                                               \
 694     "  <" PCMK_XE_SPECIAL " " PCMK_XA_TAG "=\"systemd\"/>\n"                   \
 695     "</" PCMK_XE_RESOURCE_AGENT ">\n"
 696 
 697 static char *
 698 systemd_unit_metadata(const char *name, int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
 699 {
 700     char *meta = NULL;
 701     char *desc = NULL;
 702     char *path = NULL;
 703 
 704     if (invoke_unit_by_name(name, NULL, &path) == pcmk_rc_ok) {
 705         /* TODO: Worth a making blocking call for? Probably not. Possibly if cached. */
 706         desc = systemd_get_property(path, "Description", NULL, NULL, NULL,
 707                                     timeout);
 708     } else {
 709         desc = crm_strdup_printf("Systemd unit file for %s", name);
 710     }
 711 
 712     if (pcmk__xml_needs_escape(desc, pcmk__xml_escape_text)) {
 713         gchar *escaped = pcmk__xml_escape(desc, pcmk__xml_escape_text);
 714 
 715         meta = crm_strdup_printf(METADATA_FORMAT, name, escaped, name);
 716         g_free(escaped);
 717 
 718     } else {
 719         meta = crm_strdup_printf(METADATA_FORMAT, name, desc, name);
 720     }
 721 
 722     free(desc);
 723     free(path);
 724     return meta;
 725 }
 726 
 727 /*!
 728  * \internal
 729  * \brief Determine result of method from reply
 730  *
 731  * \param[in]     reply  Reply to start, stop, or restart request
 732  * \param[in,out] op     Action that was executed
 733  */
 734 static void
 735 process_unit_method_reply(DBusMessage *reply, svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
 736 {
 737     DBusError error;
 738 
 739     dbus_error_init(&error);
 740 
 741     /* The first use of error here is not used other than as a non-NULL flag to
 742      * indicate that a request was indeed sent
 743      */
 744     if (pcmk_dbus_find_error((void *) &error, reply, &error)) {
 745         set_result_from_method_error(op, &error);
 746         dbus_error_free(&error);
 747 
 748     } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
 749                                      __func__, __LINE__)) {
 750         crm_info("DBus request for %s of %s succeeded but "
 751                  "return type was unexpected",
 752                  op->action, pcmk__s(op->rsc, "unknown resource"));
 753         services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE,
 754                              "systemd DBus method had unexpected reply");
 755 
 756     } else {
 757         const char *path = NULL;
 758 
 759         dbus_message_get_args(reply, NULL,
 760                               DBUS_TYPE_OBJECT_PATH, &path,
 761                               DBUS_TYPE_INVALID);
 762         crm_debug("DBus request for %s of %s using %s succeeded",
 763                   op->action, pcmk__s(op->rsc, "unknown resource"), path);
 764         services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
 765     }
 766 }
 767 
 768 /*!
 769  * \internal
 770  * \brief Process the completion of an asynchronous unit start, stop, or restart
 771  *
 772  * \param[in,out] pending    If not NULL, DBus call associated with request
 773  * \param[in,out] user_data  Action that was executed
 774  */
 775 static void
 776 unit_method_complete(DBusPendingCall *pending, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 777 {
 778     DBusMessage *reply = NULL;
 779     svc_action_t *op = user_data;
 780 
 781     crm_trace("Result for %s arrived", op->id);
 782 
 783     // Grab the reply
 784     if (pending != NULL) {
 785         reply = dbus_pending_call_steal_reply(pending);
 786     }
 787 
 788     // The call is no longer pending
 789     CRM_LOG_ASSERT(pending == op->opaque->pending);
 790     services_set_op_pending(op, NULL);
 791 
 792     // Determine result and finalize action
 793     process_unit_method_reply(reply, op);
 794     services__finalize_async_op(op);
 795     if (reply != NULL) {
 796         dbus_message_unref(reply);
 797     }
 798 }
 799 
 800 #define SYSTEMD_OVERRIDE_ROOT "/run/systemd/system/"
 801 
 802 /* When the cluster manages a systemd resource, we create a unit file override
 803  * to order the service "before" pacemaker. The "before" relationship won't
 804  * actually be used, since systemd won't ever start the resource -- we're
 805  * interested in the reverse shutdown ordering it creates, to ensure that
 806  * systemd doesn't stop the resource at shutdown while pacemaker is still
 807  * running.
 808  *
 809  * @TODO Add start timeout
 810  */
 811 #define SYSTEMD_OVERRIDE_TEMPLATE                           \
 812     "[Unit]\n"                                              \
 813     "Description=Cluster Controlled %s\n"                   \
 814     "Before=pacemaker.service pacemaker_remote.service\n"   \
 815     "\n"                                                    \
 816     "[Service]\n"                                           \
 817     "Restart=no\n"
 818 
 819 // Temporarily use rwxr-xr-x umask when opening a file for writing
 820 static FILE *
 821 create_world_readable(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 822 {
 823     mode_t orig_umask = umask(S_IWGRP | S_IWOTH);
 824     FILE *fp = fopen(filename, "w");
 825 
 826     umask(orig_umask);
 827     return fp;
 828 }
 829 
 830 static void
 831 create_override_dir(const char *agent)
     /* [previous][next][first][last][top][bottom][index][help] */
 832 {
 833     char *override_dir = crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT
 834                                            "/%s.service.d", agent);
 835     int rc = pcmk__build_path(override_dir, 0755);
 836 
 837     if (rc != pcmk_rc_ok) {
 838         crm_warn("Could not create systemd override directory %s: %s",
 839                  override_dir, pcmk_rc_str(rc));
 840     }
 841     free(override_dir);
 842 }
 843 
 844 static char *
 845 get_override_filename(const char *agent)
     /* [previous][next][first][last][top][bottom][index][help] */
 846 {
 847     return crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT
 848                              "/%s.service.d/50-pacemaker.conf", agent);
 849 }
 850 
 851 static void
 852 systemd_create_override(const char *agent, int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
 853 {
 854     FILE *file_strm = NULL;
 855     char *override_file = get_override_filename(agent);
 856 
 857     create_override_dir(agent);
 858 
 859     /* Ensure the override file is world-readable. This is not strictly
 860      * necessary, but it avoids a systemd warning in the logs.
 861      */
 862     file_strm = create_world_readable(override_file);
 863     if (file_strm == NULL) {
 864         crm_err("Cannot open systemd override file %s for writing",
 865                 override_file);
 866     } else {
 867         char *override = crm_strdup_printf(SYSTEMD_OVERRIDE_TEMPLATE, agent);
 868 
 869         int rc = fprintf(file_strm, "%s\n", override);
 870 
 871         free(override);
 872         if (rc < 0) {
 873             crm_perror(LOG_WARNING, "Cannot write to systemd override file %s",
 874                        override_file);
 875         }
 876         fflush(file_strm);
 877         fclose(file_strm);
 878         systemd_daemon_reload(timeout);
 879     }
 880 
 881     free(override_file);
 882 }
 883 
 884 static void
 885 systemd_remove_override(const char *agent, int timeout)
     /* [previous][next][first][last][top][bottom][index][help] */
 886 {
 887     char *override_file = get_override_filename(agent);
 888     int rc = unlink(override_file);
 889 
 890     if (rc < 0) {
 891         // Stop may be called when already stopped, which is fine
 892         crm_perror(LOG_DEBUG, "Cannot remove systemd override file %s",
 893                    override_file);
 894     } else {
 895         systemd_daemon_reload(timeout);
 896     }
 897     free(override_file);
 898 }
 899 
 900 /*!
 901  * \internal
 902  * \brief Parse result of systemd status check
 903  *
 904  * Set a status action's exit status and execution status based on a DBus
 905  * property check result, and finalize the action if asynchronous.
 906  *
 907  * \param[in]     name      DBus interface name for property that was checked
 908  * \param[in]     state     Property value
 909  * \param[in,out] userdata  Status action that check was done for
 910  */
 911 static void
 912 parse_status_result(const char *name, const char *state, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
 913 {
 914     svc_action_t *op = userdata;
 915 
 916     crm_trace("Resource %s has %s='%s'",
 917               pcmk__s(op->rsc, "(unspecified)"), name,
 918               pcmk__s(state, "<null>"));
 919 
 920     if (pcmk__str_eq(state, "active", pcmk__str_none)) {
 921         services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
 922 
 923     } else if (pcmk__str_eq(state, "reloading", pcmk__str_none)) {
 924         services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
 925 
 926     } else if (pcmk__str_eq(state, "activating", pcmk__str_none)) {
 927         services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
 928 
 929     } else if (pcmk__str_eq(state, "deactivating", pcmk__str_none)) {
 930         services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
 931 
 932     } else {
 933         services__set_result(op, PCMK_OCF_NOT_RUNNING, PCMK_EXEC_DONE, state);
 934     }
 935 
 936     if (!(op->synchronous)) {
 937         services_set_op_pending(op, NULL);
 938         services__finalize_async_op(op);
 939     }
 940 }
 941 
 942 /*!
 943  * \internal
 944  * \brief Invoke a systemd unit, given its DBus object path
 945  *
 946  * \param[in,out] op    Action to execute
 947  * \param[in]     unit  DBus object path of systemd unit to invoke
 948  */
 949 static void
 950 invoke_unit_by_path(svc_action_t *op, const char *unit)
     /* [previous][next][first][last][top][bottom][index][help] */
 951 {
 952     const char *method = NULL;
 953     DBusMessage *msg = NULL;
 954     DBusMessage *reply = NULL;
 955 
 956     if (pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR, PCMK_ACTION_STATUS,
 957                          NULL)) {
 958         DBusPendingCall *pending = NULL;
 959         char *state;
 960 
 961         state = systemd_get_property(unit, "ActiveState",
 962                                      (op->synchronous? NULL : parse_status_result),
 963                                      op, (op->synchronous? NULL : &pending),
 964                                      op->timeout);
 965         if (op->synchronous) {
 966             parse_status_result("ActiveState", state, op);
 967             free(state);
 968 
 969         } else if (pending == NULL) { // Could not get ActiveState property
 970             services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
 971                                     "Could not get state for unit %s from DBus",
 972                                     op->agent);
 973             services__finalize_async_op(op);
 974 
 975         } else {
 976             services_set_op_pending(op, pending);
 977         }
 978         return;
 979 
 980     } else if (pcmk__str_eq(op->action, PCMK_ACTION_START, pcmk__str_none)) {
 981         method = "StartUnit";
 982         systemd_create_override(op->agent, op->timeout);
 983 
 984     } else if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_none)) {
 985         method = "StopUnit";
 986         systemd_remove_override(op->agent, op->timeout);
 987 
 988     } else if (pcmk__str_eq(op->action, "restart", pcmk__str_none)) {
 989         method = "RestartUnit";
 990 
 991     } else {
 992         services__format_result(op, PCMK_OCF_UNIMPLEMENT_FEATURE,
 993                                 PCMK_EXEC_ERROR,
 994                                 "Action %s not implemented "
 995                                 "for systemd resources",
 996                                 pcmk__s(op->action, "(unspecified)"));
 997         if (!(op->synchronous)) {
 998             services__finalize_async_op(op);
 999         }
1000         return;
1001     }
1002 
1003     crm_trace("Calling %s for unit path %s%s%s",
1004               method, unit,
1005               ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""));
1006 
1007     msg = systemd_new_method(method);
1008     CRM_ASSERT(msg != NULL);
1009 
1010     /* (ss) */
1011     {
1012         const char *replace_s = "replace";
1013         char *name = systemd_service_name(op->agent,
1014                                           pcmk__str_eq(op->action,
1015                                                        PCMK_ACTION_META_DATA,
1016                                                        pcmk__str_none));
1017 
1018         CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID));
1019         CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &replace_s, DBUS_TYPE_INVALID));
1020 
1021         free(name);
1022     }
1023 
1024     if (op->synchronous) {
1025         reply = systemd_send_recv(msg, NULL, op->timeout);
1026         dbus_message_unref(msg);
1027         process_unit_method_reply(reply, op);
1028         if (reply != NULL) {
1029             dbus_message_unref(reply);
1030         }
1031 
1032     } else {
1033         DBusPendingCall *pending = systemd_send(msg, unit_method_complete, op,
1034                                                 op->timeout);
1035 
1036         dbus_message_unref(msg);
1037         if (pending == NULL) {
1038             services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1039                                  "Unable to send DBus message");
1040             services__finalize_async_op(op);
1041 
1042         } else {
1043             services_set_op_pending(op, pending);
1044         }
1045     }
1046 }
1047 
1048 static gboolean
1049 systemd_timeout_callback(gpointer p)
     /* [previous][next][first][last][top][bottom][index][help] */
1050 {
1051     svc_action_t * op = p;
1052 
1053     op->opaque->timerid = 0;
1054     crm_info("%s action for systemd unit %s named '%s' timed out",
1055              op->action, op->agent, op->rsc);
1056     services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_TIMEOUT,
1057                             "%s action for systemd unit %s "
1058                             "did not complete in time", op->action, op->agent);
1059     services__finalize_async_op(op);
1060     return FALSE;
1061 }
1062 
1063 /*!
1064  * \internal
1065  * \brief Execute a systemd action
1066  *
1067  * \param[in,out] op  Action to execute
1068  *
1069  * \return Standard Pacemaker return code
1070  * \retval EBUSY          Recurring operation could not be initiated
1071  * \retval pcmk_rc_error  Synchronous action failed
1072  * \retval pcmk_rc_ok     Synchronous action succeeded, or asynchronous action
1073  *                        should not be freed (because it's pending or because
1074  *                        it failed to execute and was already freed)
1075  *
1076  * \note If the return value for an asynchronous action is not pcmk_rc_ok, the
1077  *       caller is responsible for freeing the action.
1078  */
1079 int
1080 services__execute_systemd(svc_action_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
1081 {
1082     CRM_ASSERT(op != NULL);
1083 
1084     if ((op->action == NULL) || (op->agent == NULL)) {
1085         services__set_result(op, PCMK_OCF_NOT_CONFIGURED, PCMK_EXEC_ERROR_FATAL,
1086                              "Bug in action caller");
1087         goto done;
1088     }
1089 
1090     if (!systemd_init()) {
1091         services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1092                              "No DBus connection");
1093         goto done;
1094     }
1095 
1096     crm_debug("Performing %ssynchronous %s op on systemd unit %s%s%s",
1097               (op->synchronous? "" : "a"), op->action, op->agent,
1098               ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""));
1099 
1100     if (pcmk__str_eq(op->action, PCMK_ACTION_META_DATA, pcmk__str_casei)) {
1101         op->stdout_data = systemd_unit_metadata(op->agent, op->timeout);
1102         services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
1103         goto done;
1104     }
1105 
1106     /* invoke_unit_by_name() should always override these values, which are here
1107      * just as a fail-safe in case there are any code paths that neglect to
1108      */
1109     services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
1110                          "Bug in service library");
1111 
1112     if (invoke_unit_by_name(op->agent, op, NULL) == pcmk_rc_ok) {
1113         op->opaque->timerid = g_timeout_add(op->timeout + 5000,
1114                                             systemd_timeout_callback, op);
1115         services_add_inflight_op(op);
1116         return pcmk_rc_ok;
1117     }
1118 
1119 done:
1120     if (op->synchronous) {
1121         return (op->rc == PCMK_OCF_OK)? pcmk_rc_ok : pcmk_rc_error;
1122     } else {
1123         return services__finalize_async_op(op);
1124     }
1125 }

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