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

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