Skip to content

Commit 47fa0d8

Browse files
authored
Merge pull request #3832 from nrwahl2/nrwahl2-systemd
Fix systemd unit overrides
2 parents 162b479 + bf3ae33 commit 47fa0d8

File tree

7 files changed

+204
-102
lines changed

7 files changed

+204
-102
lines changed

lib/common/strings.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,7 @@ static bool
996996
str_any_of(const char *s, va_list args, uint32_t flags)
997997
{
998998
if (s == NULL) {
999-
return pcmk_is_set(flags, pcmk__str_null_matches);
999+
return false;
10001000
}
10011001

10021002
while (1) {

lib/services/systemd.c

Lines changed: 168 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the Pacemaker project contributors
2+
* Copyright 2012-2025 the Pacemaker project contributors
33
*
44
* The version control history for this file may have further details.
55
*
@@ -15,6 +15,7 @@
1515
#include <crm/common/mainloop.h>
1616

1717
#include <stdbool.h>
18+
#include <stdio.h> // fopen(), NULL, etc.
1819
#include <sys/stat.h>
1920
#include <gio/gio.h>
2021
#include <services_private.h>
@@ -210,7 +211,7 @@ systemd_unit_extension(const char *name)
210211
}
211212

212213
static char *
213-
systemd_service_name(const char *name, bool add_instance_name)
214+
systemd_unit_name(const char *name, bool add_instance_name)
214215
{
215216
const char *dot = NULL;
216217

@@ -231,16 +232,10 @@ systemd_service_name(const char *name, bool add_instance_name)
231232

232233
if (dot) {
233234
if (dot != name && *(dot-1) == '@') {
234-
char *s = NULL;
235-
236-
if (asprintf(&s, "%.*spacemaker%s", (int) (dot-name), name, dot) == -1) {
237-
/* If asprintf fails, just return name. */
238-
return strdup(name);
239-
}
240-
241-
return s;
235+
return crm_strdup_printf("%.*spacemaker%s",
236+
(int) (dot - name), name, dot);
242237
} else {
243-
return strdup(name);
238+
return pcmk__str_copy(name);
244239
}
245240

246241
} else if (add_instance_name && *(name+strlen(name)-1) == '@') {
@@ -443,6 +438,10 @@ invoke_unit_by_name(const char *arg_name, svc_action_t *op, char **path)
443438
DBusPendingCall *pending = NULL;
444439
char *name = NULL;
445440

441+
if (pcmk__str_empty(arg_name)) {
442+
return EINVAL;
443+
}
444+
446445
if (!systemd_init()) {
447446
if (op != NULL) {
448447
services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
@@ -463,11 +462,10 @@ invoke_unit_by_name(const char *arg_name, svc_action_t *op, char **path)
463462
pcmk__assert(msg != NULL);
464463

465464
// Add the (expanded) unit name as the argument
466-
name = systemd_service_name(arg_name,
467-
(op == NULL)
468-
|| pcmk__str_eq(op->action,
469-
PCMK_ACTION_META_DATA,
470-
pcmk__str_none));
465+
name = systemd_unit_name(arg_name,
466+
(op == NULL)
467+
|| pcmk__str_eq(op->action, PCMK_ACTION_META_DATA,
468+
pcmk__str_none));
471469
CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name,
472470
DBUS_TYPE_INVALID));
473471
free(name);
@@ -638,18 +636,19 @@ systemd_unit_listall(void)
638636
return units;
639637
}
640638

641-
gboolean
639+
bool
642640
systemd_unit_exists(const char *name)
643641
{
644642
char *path = NULL;
645643
char *state = NULL;
644+
int rc = false;
646645

647646
/* Note: Makes a blocking dbus calls
648647
* Used by resources_find_service_class() when resource class=service
649648
*/
650649
if ((invoke_unit_by_name(name, NULL, &path) != pcmk_rc_ok)
651650
|| (path == NULL)) {
652-
return FALSE;
651+
goto done;
653652
}
654653

655654
/* A successful LoadUnit is not sufficient to determine the unit's
@@ -658,13 +657,12 @@ systemd_unit_exists(const char *name)
658657
*/
659658
state = systemd_get_property(path, "LoadState", NULL, NULL, NULL,
660659
DBUS_TIMEOUT_USE_DEFAULT);
660+
rc = pcmk__str_any_of(state, "loaded", "masked", NULL);
661+
662+
done:
661663
free(path);
662-
if (pcmk__str_any_of(state, "loaded", "masked", NULL)) {
663-
free(state);
664-
return TRUE;
665-
}
666664
free(state);
667-
return FALSE;
665+
return rc;
668666
}
669667

670668
// @TODO Use XML string constants and maybe a real XML object
@@ -800,8 +798,6 @@ unit_method_complete(DBusPendingCall *pending, void *user_data)
800798
}
801799
}
802800

803-
#define SYSTEMD_OVERRIDE_ROOT "/run/systemd/system/"
804-
805801
/* When the cluster manages a systemd resource, we create a unit file override
806802
* to order the service "before" pacemaker. The "before" relationship won't
807803
* actually be used, since systemd won't ever start the resource -- we're
@@ -811,93 +807,167 @@ unit_method_complete(DBusPendingCall *pending, void *user_data)
811807
*
812808
* @TODO Add start timeout
813809
*/
814-
#define SYSTEMD_OVERRIDE_TEMPLATE \
810+
#define SYSTEMD_UNIT_OVERRIDE_TEMPLATE \
815811
"[Unit]\n" \
816812
"Description=Cluster Controlled %s\n" \
817-
"Before=pacemaker.service pacemaker_remote.service\n" \
813+
"Before=pacemaker.service pacemaker_remote.service\n"
814+
815+
#define SYSTEMD_SERVICE_OVERRIDE \
818816
"\n" \
819817
"[Service]\n" \
820818
"Restart=no\n"
821819

822-
// Temporarily use rwxr-xr-x umask when opening a file for writing
823-
static FILE *
824-
create_world_readable(const char *filename)
820+
/*!
821+
* \internal
822+
* \brief Get runtime drop-in directory path for a systemd unit
823+
*
824+
* \param[in] unit_name Systemd unit (with extension)
825+
*
826+
* \return Drop-in directory path
827+
*/
828+
static GString *
829+
get_override_dir(const char *unit_name)
825830
{
826-
mode_t orig_umask = umask(S_IWGRP | S_IWOTH);
827-
FILE *fp = fopen(filename, "w");
831+
GString *buf = g_string_sized_new(128);
828832

829-
umask(orig_umask);
830-
return fp;
833+
pcmk__g_strcat(buf, "/run/systemd/system/", unit_name, ".d", NULL);
834+
return buf;
831835
}
832836

833-
static void
834-
create_override_dir(const char *agent)
837+
/*!
838+
* \internal
839+
* \brief Append systemd override filename to a directory path
840+
*
841+
* \param[in,out] buf Buffer containing directory path to append to
842+
*/
843+
static inline void
844+
append_override_basename(GString *buf)
835845
{
836-
char *override_dir = crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT
837-
"/%s.service.d", agent);
838-
int rc = pcmk__build_path(override_dir, 0755);
846+
g_string_append(buf, "/50-pacemaker.conf");
847+
}
839848

849+
/*!
850+
* \internal
851+
* \brief Create a runtime override file for a systemd unit
852+
*
853+
* The systemd daemon is then reloaded. This file does not survive a reboot.
854+
*
855+
* \param[in] agent Systemd resource agent
856+
* \param[in] timeout Timeout for systemd daemon reload
857+
*
858+
* \return Standard Pacemaker return code
859+
*
860+
* \note Any configuration in \c /etc takes precedence over our drop-in.
861+
* \todo Document this in Pacemaker Explained or Administration?
862+
*/
863+
static int
864+
systemd_create_override(const char *agent, int timeout)
865+
{
866+
char *unit_name = NULL;
867+
GString *filename = NULL;
868+
GString *override = NULL;
869+
FILE *fp = NULL;
870+
int fd = 0;
871+
int rc = pcmk_rc_ok;
872+
873+
unit_name = systemd_unit_name(agent, false);
874+
CRM_CHECK(!pcmk__str_empty(unit_name),
875+
rc = EINVAL; goto done);
876+
877+
filename = get_override_dir(unit_name);
878+
rc = pcmk__build_path(filename->str, 0755);
840879
if (rc != pcmk_rc_ok) {
841-
crm_warn("Could not create systemd override directory %s: %s",
842-
override_dir, pcmk_rc_str(rc));
880+
crm_err("Could not create systemd override directory %s: %s",
881+
filename->str, pcmk_rc_str(rc));
882+
goto done;
843883
}
844-
free(override_dir);
845-
}
846884

847-
static char *
848-
get_override_filename(const char *agent)
849-
{
850-
return crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT
851-
"/%s.service.d/50-pacemaker.conf", agent);
852-
}
885+
append_override_basename(filename);
886+
fp = fopen(filename->str, "w");
887+
if (fp == NULL) {
888+
rc = errno;
889+
crm_err("Cannot open systemd override file %s for writing: %s",
890+
filename->str, pcmk_rc_str(rc));
891+
goto done;
892+
}
853893

854-
static void
855-
systemd_create_override(const char *agent, int timeout)
856-
{
857-
FILE *file_strm = NULL;
858-
char *override_file = get_override_filename(agent);
894+
// Ensure the override file is world-readable (avoid systemd warning in log)
895+
fd = fileno(fp);
896+
if ((fd < 0) || (fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0)) {
897+
rc = errno;
898+
crm_err("Failed to set permissions on systemd override file %s: %s",
899+
filename->str, pcmk_rc_str(rc));
900+
goto done;
901+
}
859902

860-
create_override_dir(agent);
903+
override = g_string_sized_new(2 * sizeof(SYSTEMD_UNIT_OVERRIDE_TEMPLATE));
904+
g_string_printf(override, SYSTEMD_UNIT_OVERRIDE_TEMPLATE, unit_name);
905+
if (pcmk__ends_with_ext(unit_name, ".service")) {
906+
g_string_append(override, SYSTEMD_SERVICE_OVERRIDE);
907+
}
861908

862-
/* Ensure the override file is world-readable. This is not strictly
863-
* necessary, but it avoids a systemd warning in the logs.
864-
*/
865-
file_strm = create_world_readable(override_file);
866-
if (file_strm == NULL) {
867-
crm_err("Cannot open systemd override file %s for writing",
868-
override_file);
869-
} else {
870-
char *override = crm_strdup_printf(SYSTEMD_OVERRIDE_TEMPLATE, agent);
909+
if (fputs(override->str, fp) == EOF) {
910+
rc = EIO;
911+
crm_err("Cannot write to systemd override file %s", filename->str);
912+
}
871913

872-
int rc = fprintf(file_strm, "%s\n", override);
914+
done:
915+
if (fp != NULL) {
916+
fclose(fp);
917+
}
873918

874-
free(override);
875-
if (rc < 0) {
876-
crm_perror(LOG_WARNING, "Cannot write to systemd override file %s",
877-
override_file);
878-
}
879-
fflush(file_strm);
880-
fclose(file_strm);
919+
if (rc == pcmk_rc_ok) {
920+
// @TODO Make sure the reload succeeds
881921
systemd_daemon_reload(timeout);
922+
923+
} else if (fp != NULL) {
924+
// File was created, so remove it
925+
unlink(filename->str);
882926
}
883927

884-
free(override_file);
928+
free(unit_name);
929+
930+
// coverity[check_after_deref] False positive
931+
if (filename != NULL) {
932+
g_string_free(filename, TRUE);
933+
}
934+
if (override != NULL) {
935+
g_string_free(override, TRUE);
936+
}
937+
return rc;
885938
}
886939

887940
static void
888941
systemd_remove_override(const char *agent, int timeout)
889942
{
890-
char *override_file = get_override_filename(agent);
891-
int rc = unlink(override_file);
943+
char *unit_name = systemd_unit_name(agent, false);
944+
GString *filename = NULL;
945+
946+
CRM_CHECK(!pcmk__str_empty(unit_name), goto done);
947+
948+
filename = get_override_dir(unit_name);
949+
append_override_basename(filename);
950+
951+
if (unlink(filename->str) < 0) {
952+
int rc = errno;
953+
954+
if (rc != ENOENT) {
955+
// Stop may be called when already stopped, which is fine
956+
crm_warn("Cannot remove systemd override file %s: %s",
957+
filename->str, pcmk_rc_str(rc));
958+
}
892959

893-
if (rc < 0) {
894-
// Stop may be called when already stopped, which is fine
895-
crm_perror(LOG_DEBUG, "Cannot remove systemd override file %s",
896-
override_file);
897960
} else {
898961
systemd_daemon_reload(timeout);
899962
}
900-
free(override_file);
963+
964+
done:
965+
free(unit_name);
966+
967+
// coverity[check_after_deref] False positive
968+
if (filename != NULL) {
969+
g_string_free(filename, TRUE);
970+
}
901971
}
902972

903973
/*!
@@ -981,8 +1051,20 @@ invoke_unit_by_path(svc_action_t *op, const char *unit)
9811051
return;
9821052

9831053
} else if (pcmk__str_eq(op->action, PCMK_ACTION_START, pcmk__str_none)) {
1054+
int rc = pcmk_rc_ok;
1055+
9841056
method = "StartUnit";
985-
systemd_create_override(op->agent, op->timeout);
1057+
rc = systemd_create_override(op->agent, op->timeout);
1058+
if (rc != pcmk_rc_ok) {
1059+
services__format_result(op, pcmk_rc2ocf(rc), PCMK_EXEC_ERROR,
1060+
"Failed to create systemd override file "
1061+
"for %s",
1062+
pcmk__s(op->agent, "(unspecified)"));
1063+
if (!(op->synchronous)) {
1064+
services__finalize_async_op(op);
1065+
}
1066+
return;
1067+
}
9861068

9871069
} else if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_none)) {
9881070
method = "StopUnit";
@@ -1013,10 +1095,10 @@ invoke_unit_by_path(svc_action_t *op, const char *unit)
10131095
/* (ss) */
10141096
{
10151097
const char *replace_s = "replace";
1016-
char *name = systemd_service_name(op->agent,
1017-
pcmk__str_eq(op->action,
1018-
PCMK_ACTION_META_DATA,
1019-
pcmk__str_none));
1098+
char *name = systemd_unit_name(op->agent,
1099+
pcmk__str_eq(op->action,
1100+
PCMK_ACTION_META_DATA,
1101+
pcmk__str_none));
10201102

10211103
CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID));
10221104
CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &replace_s, DBUS_TYPE_INVALID));
@@ -1084,7 +1166,7 @@ services__execute_systemd(svc_action_t *op)
10841166
{
10851167
pcmk__assert(op != NULL);
10861168

1087-
if ((op->action == NULL) || (op->agent == NULL)) {
1169+
if (pcmk__str_empty(op->action) || pcmk__str_empty(op->agent)) {
10881170
services__set_result(op, PCMK_OCF_NOT_CONFIGURED, PCMK_EXEC_ERROR_FATAL,
10891171
"Bug in action caller");
10901172
goto done;

0 commit comments

Comments
 (0)