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 *
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
212213static 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
642640systemd_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
887940static void
888941systemd_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