Linux systemd service Question

Just starting out? Need help? Post your questions and find answers here.
swhite
Addict
Addict
Posts: 805
Joined: Thu May 21, 2009 6:56 pm

Linux systemd service Question

Post by swhite »

Hi

I am trying to create a systemd service but when I try and start the service it fails. The code works if I run it as a regular executable so I am assuming I am doing something wrong with the service style code. Below is the basic code I am using for a Linux daemon. Is there something I have missed? Am I suppose to report something in the various cases after I get the return value from fork_?

Code: Select all

; Signals
   #SIGHUP    = 1
   #SIGINT    = 2
   #SIGQUIT   = 3
   #SIGILL    = 4
   #SIGTRAP   = 5
   #SIGABRT   = 6
   #SIGIOT    = 6
   #SIGBUS    = 7
   #SIGFPE    = 8
   #SIGKILL   = 9
   #SIGUSR1   = 10
   #SIGSEGV   = 11
   #SIGUSR2   = 12
   #SIGPIPE   = 13
   #SIGALRM   = 14
   #SIGTERM   = 15
   #SIGSTKFLT = 16
   #SIGCHLD   = 17
   #SIGCONT   = 18
   #SIGSTOP   = 19
   #SIGTSTP   = 20
   #SIGTTIN   = 21
   #SIGTTOU   = 22
   #SIGURG    = 23
   #SIGXCPU   = 24
   #SIGXFSZ   = 25
   #SIGVTALRM = 26
   #SIGPROF   = 27
   #SIGWINCH  = 28
   #SIGIO     = 29
   #SIGPOLL   = 29
   #SIGPWR    = 30
   #SIGSYS    = 31
   #SIGUNUSED = 31
   
   #SIG_BLOCK   = 0 
   #SIG_UNBLOCK = 1
   #SIG_SETMASK = 2
   
   #SA_NOCLDSTOP = $00000001
   #SA_NOCLDWAIT = $00000002
   #SA_SIGINFO   = $00000004
   #SA_ONSTACK   = $08000000
   #SA_RESTART   = $10000000
   #SA_NODEFER   = $40000000
   #SA_RESETHAND = $80000000
CompilerIf Not Defined(sigset_t, #PB_Structure)
   Structure sigset_t Align #PB_Structure_AlignC 
      __bits.l[32]
   EndStructure
CompilerEndIf

CompilerIf Not Defined(sigaction, #PB_Structure)
   Structure sigaction Align #PB_Structure_AlignC 
      StructureUnion
         *sa_handler
         *sa_sigaction
      EndStructureUnion
      sa_mask.sigset_t
      sa_flags.l
      *sa_restorer
   EndStructure
CompilerEndIf
ProcedureC SigHandler(Signal.i)
   Select Signal
      Case #SIGHUP
         gnStop = #True
      Case #SIGABRT 
         gnStop = #True
      Case #SIGTERM
         gnStop = #True
      Case #SIGKILL
         gnStop = #True
      Case #SIGSTOP
         gnStop = #True
      Case #SIGUSR1
      Case #SIGUSR2
   EndSelect
   
 EndProcedure
Define sa.sigaction
Global gnStop

sa\sa_handler = @SigHandler()

sa\sa_flags = #SA_RESTART

sigfillset_(@sa\sa_mask)

sigaction_(#SIGSTOP, @sa, #Null)
sigaction_(#SIGABRT, @sa, #Null)
sigaction_(#SIGKILL, @sa, #Null)
sigaction_(#SIGTERM, @sa, #Null)
sigaction_(#SIGUSR1, @sa, #Null)
sigaction_(#SIGUSR2, @sa, #Null)
sigaction_(#SIGHUP, @sa, #Null)


Select fork_()
   Case -1
      End 
    Case 0
      ; Am I suppose to report something here?
   Default
      End
EndSelect
Procedure Main()
  While Not gnStop
  Wend
  
EndProcedure

Main()


  
Simon White
dCipher Computing
infratec
Always Here
Always Here
Posts: 7662
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Linux systemd service Question

Post by infratec »

If you look in my skeleton from 2017, you can see that the 'main' code is executed if it returns 0

Code: Select all

PID = fork_()
If PID = -1
  PrintN("Was not able to fork")
  End
ElseIf PID <> 0
  End
EndIf

; do the main stuff
User avatar
NicTheQuick
Addict
Addict
Posts: 1527
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: Linux systemd service Question

Post by NicTheQuick »

A systemd service can be whatever you want. You just have to write the correct configuration file and put it in `/etc/systemd/user/your-service.service`. Here's an example of the `smartd` service on my machine:

Code: Select all

[Unit]
Description=Self Monitoring and Reporting Technology (SMART) Daemon
Documentation=man:smartd(8) man:smartd.conf(5)

# Typically physical storage devices are managed by the host physical machine
# Override it if you are using PCI/USB passthrough
ConditionVirtualization=no

[Service]
Type=notify
EnvironmentFile=-/etc/default/smartmontools
ExecStart=/usr/sbin/smartd -n $smartd_opts
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target
Alias=smartd.service
For a simple service a complicated signal handling is not needed.

How do you start your service and what are the errors you've encountered?
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
swhite
Addict
Addict
Posts: 805
Joined: Thu May 21, 2009 6:56 pm

Re: Linux systemd service Question

Post by swhite »

Yes and my code does the same.
infratec wrote: Wed Jan 29, 2025 4:02 pm If you look in my skeleton from 2017, you can see that the 'main' code is executed if it returns 0

Code: Select all

PID = fork_()
If PID = -1
  PrintN("Was not able to fork")
  End
ElseIf PID <> 0
  End
EndIf

; do the main stuff
Simon White
dCipher Computing
swhite
Addict
Addict
Posts: 805
Joined: Thu May 21, 2009 6:56 pm

Re: Linux systemd service Question

Post by swhite »

Hi

This is my systemd service which I am still learning how to configure. I would like both the network and Postgres running before the service starts. I was not sure what to put in the ExecReload nor WantedBy.
[Unit]
Description=Fuelwize Host Authorization Service
After=network.target

[Service]
ExecStart=/home/FWHostSrvc/Fuelwize/FWHostAuth
ExecReload=/home/FWHostSrvc/Fuelwize/FWHostAuth
Type=simple
Restart=on-success

[Install]
WantedBy=default.target
RequiredBy=network.target
NicTheQuick wrote: Wed Jan 29, 2025 4:05 pm A systemd service can be whatever you want. You just have to write the correct configuration file and put it in `/etc/systemd/user/your-service.service`. Here's an example of the `smartd` service on my machine:

Code: Select all

[Unit]
Description=Self Monitoring and Reporting Technology (SMART) Daemon
Documentation=man:smartd(8) man:smartd.conf(5)

# Typically physical storage devices are managed by the host physical machine
# Override it if you are using PCI/USB passthrough
ConditionVirtualization=no

[Service]
Type=notify
EnvironmentFile=-/etc/default/smartmontools
ExecStart=/usr/sbin/smartd -n $smartd_opts
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target
Alias=smartd.service
For a simple service a complicated signal handling is not needed.

How do you start your service and what are the errors you've encountered?
Simon White
dCipher Computing
swhite
Addict
Addict
Posts: 805
Joined: Thu May 21, 2009 6:56 pm

Re: Linux systemd service Question

Post by swhite »

Hi

According to my log the service fails because the child process receives a SIGTERM. I am not sure why systemd is sending a SIGTERM given that it said the unit FWHostAuth.service finished successfully. I suspect I should use Type=notify but I do not know how to notify the servicemanager. The man pages say to use sd_notify() but I do not know how to use it in PureBasic.
Jan 29 14:02:22 IntensePC systemd[1]: Started Fuelwize Host Authorization Service.
░░ Subject: A start job for unit FWHostAuth.service has finished successfully
░░ Defined-By: systemd
░░ Support: https://lists.freedesktop.org/mailman/l ... temd-devel
░░
░░ A start job for unit FWHostAuth.service has finished successfully.
░░
░░ The job identifier is 9998.
Jan 29 14:02:22 IntensePC systemd[1]: FWHostAuth.service: Deactivated successfully.
░░ Subject: Unit succeeded
░░ Defined-By: systemd
░░ Support: https://lists.freedesktop.org/mailman/l ... temd-devel
░░
░░ The unit FWHostAuth.service has successfully entered the 'dead' state.
2025-01-29 14:02:22 /home/fwhostsrvc/Fuelwize/
2025-01-29 14:02:22 /home/fwhostsrvc/Fuelwize/
2025-01-29 14:02:22 **ERROR** Fork <> 0
2025-01-29 14:02:22 Fork 0
2025-01-29 14:02:22 Main(Start) without debugging
2025-01-29 14:02:22 Service Stopped by SigHandler(SIGTERM)
2025-01-29 14:02:22 Stopping Services
2025-01-29 14:02:22 Main(End)
Simon White
dCipher Computing
swhite
Addict
Addict
Posts: 805
Joined: Thu May 21, 2009 6:56 pm

Re: Linux systemd service Question

Post by swhite »

If ignore the SIGTERM being sent from systemd when the service starts everything works. However, there must a reason that I do not understand that is causing systemd to send the SIGTERM.
[Unit]
Description=Fuelwize Host Authorization Service

[Service]
ExecStart=/home/fwhostsrvc/Fuelwize/FWHostAuth
Type=notify
NotifyAccess=all

[Install]
WantedBy=multi-user.target
Simon White
dCipher Computing
benubi
Enthusiast
Enthusiast
Posts: 227
Joined: Tue Mar 29, 2005 4:01 pm

Re: Linux systemd service Question

Post by benubi »

I am the one probably with the least knowledge or experience in that subject, but I could also imagine a signal "routing" problem (because I don't know the architecture). Meaning possibly you open a network connection, and the thread that gets it's signal to close is the one that was created in the background (by the network/other library) without you knowing it? Perhaps there's a hierarchy where the notification gets routed from the leaf object/thread/process to the root. In that case the PID associated with SIGTERM should be compared with the fork? (sorry if I have no clue)

https://www.man7.org/linux/man-pages/ma ... ify.3.html
swhite
Addict
Addict
Posts: 805
Joined: Thu May 21, 2009 6:56 pm

Re: Linux systemd service Question

Post by swhite »

Hi

This service is a kind of angel service that ensures three other applications are running. These other applications start network servers and access the Postgres database. The actual code for this angel service is shown below so perhaps it will help give you more of a clue. It never seems to get to the point of launching the other applications before it fails with the SIGTERM. This service was originally designed to work in Windows which it did perfectly so I am now trying to get it to work in Linux.

Code: Select all

;
; FWHostAuth.pb - Fuelwize Host Authorization Service
;
;
; Constants
EnableExplicit
#dCLogFile              = "FWHostAuth.log"
#dCMaskOn               = 1
#dCNoNulls              = 0
#dCSMTPFrom             = "FWHostAuth@fuelwise.ca"
#dCServiceName          = "FWHostAuth"
#dCServiceAppName       = "FWHostAuth.exe"
#dCServiceDisplayName   = "Fuelwize Host Authorization"
#dCLogRetension         = 6 ; Months
#dCAppPath              = "/home/fwhostsrvc/Projects/"
;
; Use a structure when Windows API use an array of strings
;
Structure SrvParam
   Param1.s
   Param2.s
EndStructure
;
; This structure hold the time when the program should be run and the
; program handle used to identify the running program
;
Structure Sched
   time.s
   prg.i
   ran.s
 EndStructure
 
 ImportC "/usr/lib64/libsystemd.so"
;   sd_notify.i(environ.i,status.p-ascii)
   sd_notify.i(environ.i,*status)
 EndImport
 
Declare Main()
Declare ErrorHandler()
Declare SendeMail(tcMsg.s)

Global ghStatus.i,gnStop.i

XIncludeFile #dCAppPath+"PB_Lib/dCSettings.pb"
XIncludeFile #dCAppPath+"PB_Lib/dC_Wait.pb"
XIncludeFile #dCAppPath+"PB_Lib/dC_Logging.pb"
;XIncludeFile #dCAppPath+"PB_Lib/dC_SigHandler.pb"

CompilerIf Not Defined(sigset_t, #PB_Structure)
   Structure sigset_t Align #PB_Structure_AlignC 
      __bits.l[32]
   EndStructure
CompilerEndIf

CompilerIf Not Defined(sigaction, #PB_Structure)
   Structure sigaction Align #PB_Structure_AlignC 
      StructureUnion
         *sa_handler
         *sa_sigaction
      EndStructureUnion
      sa_mask.sigset_t
      sa_flags.l
      *sa_restorer
   EndStructure
CompilerEndIf
ProcedureC SigHandler(Signal.i)
   Select Signal
      Case #SIGHUP
         CompilerIf Defined(goParam,#PB_Variable)
            goParam\Stop = #True
         CompilerElseIf Defined(gnStop,#PB_Variable)
            gnStop = #True
         CompilerEndIf
         WriteToLog("Service Stopped by SigHandler(SIGHUP)")
      Case #SIGABRT 
         CompilerIf Defined(goParam,#PB_Variable)
            goParam\Stop = #True
         CompilerElseIf Defined(gnStop,#PB_Variable)
            gnStop = #True
         CompilerEndIf
         WriteToLog("Service Stopped by SigHandler(SIGABRT)")
      Case #SIGTERM
         CompilerIf Defined(goParam,#PB_Variable)
            goParam\Stop = #True
         CompilerElseIf Defined(gnStop,#PB_Variable)
            gnStop = #True
         CompilerEndIf
         WriteToLog("Service Stopped by SigHandler(SIGTERM)")
      Case #SIGKILL
         CompilerIf Defined(goParam,#PB_Variable)
            goParam\Stop = #True
         CompilerElseIf Defined(gnStop,#PB_Variable)
            gnStop = #True
         CompilerEndIf
         WriteToLog("Service Stopped by SigHandler(SIGKILL)")
      Case #SIGSTOP
         CompilerIf Defined(goParam,#PB_Variable)
            goParam\Stop = #True
         CompilerElseIf Defined(gnStop,#PB_Variable)
            gnStop = #True
         CompilerEndIf
         WriteToLog("Service Stopped by SigHandler(SIGSTOP)")
      Case #SIGUSR1
      Case #SIGUSR2
   EndSelect
   
EndProcedure
Define sa.sigaction

sa\sa_handler = @SigHandler()
sa\sa_flags = #SA_RESTART

sigfillset_(@sa\sa_mask)

sigaction_(#SIGSTOP, @sa, #Null)
sigaction_(#SIGABRT, @sa, #Null)
sigaction_(#SIGKILL, @sa, #Null)
sigaction_(#SIGTERM, @sa, #Null)
sigaction_(#SIGUSR1, @sa, #Null)
sigaction_(#SIGUSR2, @sa, #Null)
sigaction_(#SIGHUP, @sa, #Null)


If ProgramParameter(0) = "debug"
   SetCurrentDirectory(ProgramParameter(1))
   gcStartIn = GetCurrentDirectory()
   gnDebug = 1
Else
   SetCurrentDirectory(GetPathPart(ProgramFilename()))
   gcStartIn = GetCurrentDirectory()
EndIf

WriteToLog(gcStartin)

Procedure ErrorHandler()
   ErrorToLog("Service stopped due to "+ErrorMessage()+" on line "+Str(ErrorLine())+" in "+ErrorFile())
   SendeMail(ErrorMessage())
   gnStop = 1
 EndProcedure
 
 If Not ClearLog()
   End
ElseIf gnDebug
   OnErrorCall(@ErrorHandler())
   Main()
   
   Goto Finish
EndIf
WriteToLog(gcStartIn)

 Select fork_()
    Case -1
       ErrorToLog("Failed to fork")
       End 1
    Case 0
       WriteToLog("Fork 0")
    Default
       ErrorToLog("Fork <> 0")
       End 0
 EndSelect
OnErrorCall(@ErrorHandler())

Main()

Finish:

If IsFile(#dCLogFileNo)
   CloseFile(#dCLogFileNo)
EndIf 

End

;
; Send an eMail
;
Procedure SendeMail(tcMsg.s)
   If CreateMail(#dCeMailNo,#dCSMTPFrom,"Fuelwize Host Authorization Error - "+ComputerName())
      AddMailRecipient(#dCeMailNo,#dCSMTPTechSup,#PB_Mail_Cc)
      SetMailBody(#dCeMailNo,"The following error occurred:<br><br>"+tcMsg)
      If SendMail(#dCeMailNo,#dCSMTPHost,#dCSMTPPort,#DCSMTPSSL,#dCSMTPUsr,#dCSMTPPwd)=0
         ErrorToLog("eMail failed to send: "+tcMsg) 
      Else
         WriteToLog("eMail Sent: "+tcMsg)
      EndIf
      FreeMail(#dCeMailNo)
   Else
      ErrorToLog("CreateMail Failed.")
   EndIf
EndProcedure

Procedure ShutDownServices(Map tmSrv.sched())
   WriteToLog("Stopping Services")
   ForEach tmSrv()
      If IsProgram(tmSrv()\Prg) And ProgramRunning(tmSrv()\Prg)
         CreateFile(1,gcStartIn+#dCCmdFolder+MapKey(tmSrv())+".stop")
         CloseFile(1)
         WriteToLog(gcStartIn+#dCCmdFolder+MapKey(tmSrv())+".stop")
         CloseProgram(tmSrv()\Prg)
      EndIf
   Next
EndProcedure
;
; Main Service 
;
Procedure Main() 
   Define lnDateTime.i,lcTime.s,lcDay.s,lnMonth.i ,*Status 
   NewMap lmServices.sched() ; The order in which the services are started is important so we cannot use a Map()
 ;  *status=UTF8("READY=1")
 ;  sd_notify(0,*status)
   
   OnErrorCall(@ErrorHandler())
      
   If gnDebug
      WriteToLog("Main(Start) with debugging")
   Else
      WriteToLog("Main(Start) without debugging")
   EndIf
   ;
   ; Remove any commands left over in the command folder
   ;
   If ExamineDirectory(0,gcStartIn+#dCCmdFolder, "*.*")
      While NextDirectoryEntry(0) 
         If DirectoryEntryType(0) = #PB_DirectoryEntry_File
            DeleteFile(gcStartIn+#dCCmdFolder+DirectoryEntryName(0),#PB_FileSystem_Force)
         EndIf
      Wend
      FinishDirectory(0)
   EndIf
   ;
   ; Start service if preferences exist
   ;
   If Not OpenPreferences(gcStartin+"FWMain.ini") Or Not PreferenceGroup("FWHostAuth")
      ErrorToLog("FWHostAuth could not start because of '[FWHostAuth]' section missing in FWMain.ini")        
   Else      
      If ExaminePreferenceKeys()
         Repeat
            If UCase(Left(PreferenceKeyName(),6)) = "FWHOST"
;                lmServices(LCase(PreferenceKeyName()))\Time = PreferenceKeyValue()
;                lmServices(LCase(PreferenceKeyName()))\Prg = 0
               lmServices(PreferenceKeyName())\Time = PreferenceKeyValue()
               lmServices(PreferenceKeyName())\Prg = 0
            EndIf
            
         Until Not NextPreferenceKey()
      EndIf
      ClosePreferences() 
      
     If gnDebug 
        OpenConsole()
        PrintN("Press Escape to exit.")
        PrintN(gcStartIn+#dCLogFile)
     EndIf
     
     
     While (Not gnDebug And Not gnStop ) Or (gnDebug And Inkey() <> Chr(27))
     
         lnDateTime = Date()
         ;
         ; Check every 30 seconds that the required services are running 
         ;
         If Second(lnDateTime) = 0 Or Second(lnDateTime) = 30
           lcTime = FormatDate("%hh:%ii",Date())
            ForEach lmServices()
               lcDay = StringField(lmServices()\Time,DayOfWeek(Date())+1,",")
               If lmServices()\Prg And IsProgram(lmServices()\Prg) And ProgramRunning(lmServices()\Prg) And (lcDay = "" Or (lcTime >= Left(lcDay,5) And lcTime <= Right(lcDay,5)))
                  ; Nothing to do 
               Else
                  ;
                  ; Close connection to non-running program before re-starting it.
                  ;
                  If IsProgram(lmServices()\Prg)
                     If CreateFile(#dCTmpFileNo,gcStartIn+#dCCmdFolder+MapKey(lmServices())+".stop")
                        CloseFile(#dCTmpFileNo)
                     EndIf
                     CloseProgram(lmServices()\Prg)
                     lmServices()\Prg = 0
                  EndIf
                  If lcDay = "" Or (lcDay <> "00:00-00:00" And lcTime >= Left(lcDay,5) And lcTime <= Right(lcDay,5))
                     lmServices()\Prg = RunProgram(gcStartIn+MapKey(lmServices()),"",gcStartIn,#PB_Program_Open)
                     If Not lmServices()\Prg Or Not IsProgram(lmServices()\Prg) Or Not ProgramRunning(lmServices()\Prg)
                        WriteToLog(gcStartIn+MapKey(lmServices())+"("+Str(lmServices()\Prg)+") failed To start. "+lcDay)
                     Else
                        WriteToLog(gcStartIn+MapKey(lmServices())+"("+Str(lmServices()\Prg)+") started. "+lcDay)
                     EndIf
                  EndIf
               EndIf
            Next
         EndIf
         ;
         ; Remove Old Log Files
         ;
         If Month(Date()) <>  lnMonth
            If ExamineDirectory(0, gcStartIn+#dCLogFolder,"*.log")  
               While NextDirectoryEntry(0)
                  If DirectoryEntryDate(0,#PB_Date_Created) < AddDate(Date(),#PB_Date_Month,-#dCLogRetension)
                     If Not DeleteFile(gcStartIn+#dCLogFolder+DirectoryEntryName(0),#PB_FileSystem_Force)
                        ErrorToLog("Unable to delete "+gcStartIn+#dCLogFolder+DirectoryEntryName(0))
                     EndIf
                  EndIf
               Wend            
               FinishDirectory(0)
            Else
               ErrorToLog("Unable delete old log files")
            EndIf
            lnMonth = Month(Date())
         EndIf
         
         ClearLog()
         Delay(1000)      
      Wend
      
      ShutDownServices(lmServices())
      
      If gnDebug
         CloseConsole()
      EndIf
   EndIf
   WriteToLog("Main(End)")
   
EndProcedure

    
Simon White
dCipher Computing
swhite
Addict
Addict
Posts: 805
Joined: Thu May 21, 2009 6:56 pm

Re: Linux systemd service Question

Post by swhite »

Hi

It turns out that systemd is not receiving the sd_notify(0,"READY=1") and I cannot figure out why but if I change my Purebasic code to a regular non-forking executable and use the following .service file with Type=simple everything works correctly. If I could figure out why sd_notify() is not being received by systemd I would probably be able to use Type=notify
[Unit]
Description=Fuelwize Host Authorization Service

[Service]
ExecStart=/home/fwhostsrvc/Fuelwize/FWHostAuth
Type=simply
Restart=on-failure
User=root
Group=root
StandardOutput=journal
StandardError=journal
SyslogIdentifier=FWHostAuth

[Install]
WantedBy=multi-user.target
Simon White
dCipher Computing
Post Reply