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

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