Linux Daemon Question

Just starting out? Need help? Post your questions and find answers here.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Linux Daemon Question

Post by infratec »

As promised, now with more 'action' :mrgreen:

Code: Select all

EnableExplicit


; 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



Global.i Exit, HUP, USR1, USR2




Procedure Logging(Text$)
  
  Protected.i File
  
  File = OpenFile(#PB_Any, "/var/log/" + GetFilePart(ProgramFilename()), #PB_File_Append|#PB_UTF8)
  If File
    WriteStringN(File, FormatDate("%yyyy.%mm.%dd %hh:%ii:%ss ", Date()) + Text$)
    CloseFile(File)
  EndIf
  
EndProcedure






ProcedureC SigHandler(Signal.l)
  
  Select Signal
    Case #SIGTERM
      Exit = #True
    Case #SIGHUP
      HUP = #True
    Case #SIGUSR1
      USR1 = #True
    Case #SIGUSR2
      USR2 = #True
  EndSelect
  
EndProcedure







;-main
Define.l PID
Define PIDFile$
Define sa.sigaction


PIDFile$ = "/var/run/" + GetFilePart(ProgramFilename()) + ".pid"
If FileSize(PIDFile$) > 0
  Logging(Str(#PB_Compiler_Line) + " - " + "Program is already running! (/var/run/...)")
  End
EndIf

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




Logging("Started")

If CreateFile(0, PIDFile$)
  WriteStringN(0, Str(getpid_()))
  CloseFile(0)
EndIf



OpenPreferences("/etc/" + GetFilePart(ProgramFilename()))

; read your preferences

ClosePreferences()


;- signal stuff  
sa\sa_handler = @SigHandler()
sa\sa_flags = #SA_RESTART

sigfillset_(@sa\sa_mask)


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


;-main_loop
Repeat
  
  Delay(10)
  
  If HUP
    Logging("HUP received")
    HUP = #False
  EndIf
  
  If USR1
    Logging("USR1 received")
    USR1 = #False
  EndIf
  
  If USR2
    Logging("USR2 received")
    USR2 = #False
  EndIf
  
Until Exit

If FileSize(PIDFile$) > 0
  DeleteFile(PIDFile$)
EndIf

Logging("Terminated")
Tested on x86 and x64.

Bernd
Last edited by infratec on Thu Oct 12, 2017 2:47 pm, edited 1 time in total.
swhite
Enthusiast
Enthusiast
Posts: 726
Joined: Thu May 21, 2009 6:56 pm

Re: Linux Daemon Question

Post by swhite »

Thanks for your willingness to help and provide code.

Simon
Simon White
dCipher Computing
vwidmer
Enthusiast
Enthusiast
Posts: 282
Joined: Mon Jan 20, 2014 6:32 pm

Re: Linux Daemon Question

Post by vwidmer »

I made a few changes I know I have some not clean coding in there but just trying to help improve on the code. Hope it helps.

Code: Select all

;http://www.purebasic.fr/english/viewtopic.php?f=13&t=68919

;EnableExplicit

; 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

XIncludeFile("inc.l.userinfo.pbi") ; https://www.purebasic.fr/english/viewtopic.php?f=15&t=49074&hilit=userid+userid

Global PIDFile$, oldPid$, daemonName$
Global PID.l
Global.i Exit, HUP, USR1, USR2,i

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

Procedure Logging(Text$)
  Protected.i File
  
  RunProgram("bash", "-c "+#DQUOTE$+"logger -t " + GetFilePart(ProgramFilename()) + " -- '" + Text$ + "'"+#DQUOTE$+"", "", #PB_Program_Open | #PB_Program_Hide)
  
  File = OpenFile(#PB_Any, "/var/log/" + GetFilePart(ProgramFilename()), #PB_File_Append|#PB_UTF8)
  If File
    WriteStringN(File, FormatDate("%yyyy.%mm.%dd %hh:%ii:%ss ", Date()) + Text$)
    CloseFile(File)
  EndIf
EndProcedure

ProcedureC SigHandler(Signal.l)
  Select Signal
    Case #SIGTERM
      Exit = #True
    Case #SIGHUP
      HUP = #True
    Case #SIGUSR1
      USR1 = #True
    Case #SIGUSR2
      USR2 = #True
  EndSelect
EndProcedure

Procedure.b checkPIDRunning(PID2Chk.s)
  Compiler = RunProgram("bash", "-c "+#DQUOTE$+"ps aux | grep " + PID2Chk + " | grep -v grep | grep " + GetFilePart(ProgramFilename()), "", #PB_Program_Open | #PB_Program_Read)
  Output$ = ""
  If Compiler
    While ProgramRunning(Compiler)
      If AvailableProgramOutput(Compiler)
        Output$ + ReadProgramString(Compiler) + Chr(13)
      EndIf
    Wend
    CloseProgram(Compiler) ; Close the connection to the program
  EndIf
  
  If Output$
    ProcedureReturn #True
  Else
    ProcedureReturn #False
  EndIf
EndProcedure

Procedure checkDaemon(verbose.b=#False)
 If oldPid$ = ""
    ProcedureReturn #False
  ElseIf kill_(Val(oldPid$),0) = 0
    If verbose
      PrintN(daemonName$ +" is running.")
    EndIf 
    ProcedureReturn #True
  ElseIf checkPIDRunning(oldPid$)
    ;If verbose
    PrintN(daemonName$ +" is running (maybe different user).")
    ;EndIf
    ProcedureReturn #True
  Else
    ProcedureReturn #False
  EndIf
EndProcedure

Procedure statusDaemon()
  If checkDaemon() = #False
    PrintN(daemonName$ +" isn't running.")
  EndIf
EndProcedure

Procedure stopDaemon() ; Stop the daemon.
  If checkDaemon() = #False
    PrintN("Error: " + daemonName$ +" isn't running.")
    End
  EndIf
  PrintN(" * Stopping " + daemonName$)
  Logging(daemonName$ +" stopped.")
  
  If oldPid$
    kill_(Val(oldPid$),9)
    If FileSize(PIDFile$) > 0
      DeleteFile(PIDFile$)
    EndIf
  EndIf
EndProcedure

Global sa.sigaction

Procedure startDaemon() ; Start the daemon.
  If checkDaemon() 
    PrintN("Error: " + daemonName$ +" is already running.")
    Logging(Str(#PB_Compiler_Line) + " - " + "Program is already running! (/var/run/...)")
    End
  EndIf
  
  PID = fork_()
  If PID = -1
    PrintN("Was not able to fork")
    End
  ElseIf PID <> 0
    End
  EndIf
  
  Logging("Starting up " + daemonName$ +".")
  
  If CreateFile(0, PIDFile$)
    WriteStringN(0, Str(getpid_()))
    CloseFile(0)
  EndIf
  
  PrintN(" * Starting " + daemonName$ +" With PID: " + Str(getpid_()))
  
  OpenPreferences("/etc/" + GetFilePart(ProgramFilename()))
  ; read your preferences
  ClosePreferences()
  
  ;- signal stuff 
  sa\sa_handler = @SigHandler()
  sa\sa_flags = #SA_RESTART
  
  sigfillset_(@sa\sa_mask)
  
  sigaction_(#SIGHUP, @sa, #Null)
  sigaction_(#SIGTERM, @sa, #Null)
  sigaction_(#SIGUSR1, @sa, #Null)
  sigaction_(#SIGUSR2, @sa, #Null)
  
  ;-main_loop
  Repeat
    
    Delay(10)
    
    If HUP
      Logging("HUP received")
      HUP = #False
    EndIf
    
    If USR1
      Logging("USR1 received")
      USR1 = #False
    EndIf
    
    If USR2
      Logging("USR2 received")
      USR2 = #False
    EndIf
    
  Until Exit
  
  stopDaemon()
  ;   If FileSize(PIDFile$) > 0
  ;     DeleteFile(PIDFile$)
  ;   EndIf
EndProcedure

Procedure restartDaemon() ; Restart the daemon.
  If checkDaemon() = #False
    PrintN("Error: " + daemonName$ +" isn't running.")
    End
  EndIf
  stopDaemon()
  Delay(100)
  startDaemon()
EndProcedure


;-main
PIDFile$ = "/var/run/user/"+Str(UserID())+"/" + GetFilePart(ProgramFilename()) + ".pid"
daemonName$ = GetFilePart(ProgramFilename())

If FileSize(PIDFile$) > 0
  If OpenFile(0, PIDFile$)   ; if the file could be read, we continue...
    oldPid$ = ReadString(0)  ; display line by line in the debug window
    CloseFile(0)             ; close the previously opened file
  EndIf
EndIf

;https://gist.githubusercontent.com/shawnrice/11076762/raw/48cbc4686e91ca058985d197ed57834aac57c969/skeleton-daemon.sh
;checkDaemon()

If CountProgramParameters() = 0
  PrintN("Error: usage " + GetFilePart(ProgramFilename()) + " { start | stop | restart | status }")
Else
  Select ProgramParameter(0)
    Case "start"
      startDaemon()
    Case "stop"
      stopDaemon()
    Case "status"
      statusDaemon()
    Case "restart"
      restartDaemon()
    Default
      PrintN("Error: usage " + GetFilePart(ProgramFilename()) + " { start | stop | restart | status }")
      End
  EndSelect
  
EndIf

Logging("Terminated")
WARNING: I dont know what I am doing! I just put stuff here and there and sometimes like magic it works. So please improve on my code and post your changes so I can learn more. TIA
User avatar
mk-soft
Always Here
Always Here
Posts: 5335
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Linux Daemon Question

Post by mk-soft »

Thanks to infratec ...

Clean up code. It work under Linux and macOS

myservice.pb

Code: Select all

;-TOP

; Base by Infratec
; Link: https://www.purebasic.fr/english/viewtopic.php?t=68919

; Update by mk-soft
; Date 04.12.2021

; Start service as root:
; sudo ./myservice

; Stop service as root:
; sudo kill `cat /var/run/myservice.pid`

; *************************************************************

EnableExplicit

;- signals constants
#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

; ----

Global SignalExit, SignalHUP, SignalUSR1, SignalUSR2
Global PID, PIDFile$, LOGFile$
Global sa.sigaction

; ----

Procedure Logging(Text$)
  
  Protected.i File
  
  File = OpenFile(#PB_Any, LOGFile$, #PB_File_Append | #PB_UTF8)
  If File
    WriteStringN(File, FormatDate("%yyyy.%mm.%dd %hh:%ii:%ss ", Date()) + Text$)
    CloseFile(File)
  EndIf
  
EndProcedure

; ----

ProcedureC SigHandler(Signal.l)
  
  Select Signal
    Case #SIGTERM
      SignalExit = #True
    Case #SIGHUP
      SignalHUP = #True
    Case #SIGUSR1
      SignalUSR1 = #True
    Case #SIGUSR2
      SignalUSR2 = #True
  EndSelect
  
EndProcedure

; ----

Procedure InitService()
  
  PIDFile$ = "/var/run/" + GetFilePart(ProgramFilename()) + ".pid"
  If FileSize(PIDFile$) > 0
    Logging(Str(#PB_Compiler_Line) + " - " + "Program is already running! (/var/run/...)")
    End
  EndIf
  
  LOGFile$ = "/var/log/" + GetFilePart(ProgramFilename()) + ".log"
  
  ; If fork() returns a negative value, the creation of a child process was unsuccessful.
  ; fork() returns a zero to the newly created child process.
  
  PID = fork_()
  If PID = -1
    PrintN("Was not able to fork")
    ProcedureReturn #False
  ElseIf PID <> 0
    ProcedureReturn #False
  EndIf
  
  If CreateFile(0, PIDFile$)
    WriteStringN(0, Str(getpid_()))
    CloseFile(0)
  Else
    ProcedureReturn #False
  EndIf
  
  Logging("Started")
  
  If OpenPreferences("/etc/" + GetFilePart(ProgramFilename()))
    
    ;- read your preferences
    
    ClosePreferences()
  EndIf
  
  ;- signal stuff  
  sa\sa_handler = @SigHandler()
  sa\sa_flags = #SA_RESTART
  
  sigfillset_(@sa\sa_mask)
  
  sigaction_(#SIGHUP, @sa, #Null)
  sigaction_(#SIGTERM, @sa, #Null)
  sigaction_(#SIGUSR1, @sa, #Null)
  sigaction_(#SIGUSR2, @sa, #Null)
  
  ProcedureReturn #True
  
EndProcedure

; ---

Procedure Main()
  
  ;- init your data
  
  ;- main loop
  Repeat
    
    If SignalHUP
      Logging("HUP received")
      SignalHUP = #False
    EndIf
    
    If SignalUSR1
      Logging("USR1 received")
      SignalUSR1 = #False
    EndIf
    
    If SignalUSR2
      Logging("USR2 received")
      SignalUSR2 = #False
    EndIf
    
    ; do any
    
    ; protected cpu power
    Delay(100)
    
  Until SignalExit
  
  If FileSize(PIDFile$) > 0
    DeleteFile(PIDFile$)
  EndIf
  
  ;- release your data
  
  Logging("Terminated")
  
EndProcedure

; Main

If InitService()
  Main()
EndIf
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Fred
Administrator
Administrator
Posts: 16619
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Linux Daemon Question

Post by Fred »

Nice !
Axolotl
Enthusiast
Enthusiast
Posts: 435
Joined: Wed Dec 31, 2008 3:36 pm

Re: Linux Daemon Question

Post by Axolotl »

mk-soft wrote: Sun Dec 04, 2022 3:34 pm ...

Code: Select all

;... 
Procedure InitService()
  
  PIDFile$ = "/var/run/" + GetFilePart(ProgramFilename()) + ".pid"
  If FileSize(PIDFile$) > 0
    Logging(Str(#PB_Compiler_Line) + " - " + "Program is already running! (/var/run/...)")  ; <-----  is LOGFile$ set ??
    End
  EndIf
  
  LOGFile$ = "/var/log/" + GetFilePart(ProgramFilename()) + ".log"  ; <-----  set LOGFile$ for Logging above ??
; ...   
Thanks for sharing ...
I am still new to linux, maybe a small suggestion for improvement.... (see comments above)
Mostly running PureBasic <latest stable version and current alpha/beta> (x64) on Windows 11 Home
User avatar
NicTheQuick
Addict
Addict
Posts: 1224
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: Linux Daemon Question

Post by NicTheQuick »

Nowadays many distributions use systemd as the service backend.

Here is just one of many articles about creating your own service with systemd: https://medium.com/@benmorel/creating-a ... 1b5c8b91d6
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.
User avatar
mk-soft
Always Here
Always Here
Posts: 5335
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Linux Daemon Question

Post by mk-soft »

I tried it once with systemd. With these small settings it worked. Needs all root rights.

Compile myservice.pb as console and copy to /usr/bin

Create service file:
- Folder: '/etc/systemd/system'
- File: 'myservice.service'

Testing:
- sudo systemctl start myservice
- sudo systemctl stop myservice

Add service on boot:
- sudo systemctl enable myservice

Remove service on boot:
- sudo systemctl disable myservice

myservice.service
# Purebasic myservice example for Linux
#
# Created by mk-soft, 2022

[Unit]
Description=Purebasic system configuration myservice

[Service]
Type=forking
User=root
ExecStart=/usr/bin/myservice

[Install]
WantedBy=multi-user.target
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Post Reply