Browse Source

* Support for Daemon applications

git-svn-id: trunk@6278 -
michael 18 years ago
parent
commit
6345c3a59b
8 changed files with 2343 additions and 454 deletions
  1. 4 0
      .gitattributes
  2. 27 444
      fcl/Makefile
  3. 10 10
      fcl/Makefile.fpc
  4. 1340 0
      fcl/inc/daemonapp.pp
  5. 1 0
      fcl/tests/README
  6. 155 0
      fcl/tests/daemon.pp
  7. 189 0
      fcl/unix/daemonapp.inc
  8. 617 0
      fcl/win/daemonapp.inc

+ 4 - 0
.gitattributes

@@ -881,6 +881,7 @@ fcl/inc/cachecls.pp svneol=native#text/plain
 fcl/inc/cgiapp.pp svneol=native#text/plain
 fcl/inc/cgiapp.pp svneol=native#text/plain
 fcl/inc/contnrs.pp svneol=native#text/plain
 fcl/inc/contnrs.pp svneol=native#text/plain
 fcl/inc/custapp.pp svneol=native#text/plain
 fcl/inc/custapp.pp svneol=native#text/plain
+fcl/inc/daemonapp.pp svneol=native#text/plain
 fcl/inc/dbugintf.pp svneol=native#text/plain
 fcl/inc/dbugintf.pp svneol=native#text/plain
 fcl/inc/dbugmsg.pp svneol=native#text/plain
 fcl/inc/dbugmsg.pp svneol=native#text/plain
 fcl/inc/eventlog.pp svneol=native#text/plain
 fcl/inc/eventlog.pp svneol=native#text/plain
@@ -999,6 +1000,7 @@ fcl/tests/b64test.pp svneol=native#text/plain
 fcl/tests/b64test2.pp svneol=native#text/plain
 fcl/tests/b64test2.pp svneol=native#text/plain
 fcl/tests/cachetest.pp svneol=native#text/plain
 fcl/tests/cachetest.pp svneol=native#text/plain
 fcl/tests/cfgtest.pp svneol=native#text/plain
 fcl/tests/cfgtest.pp svneol=native#text/plain
+fcl/tests/daemon.pp svneol=native#text/plain
 fcl/tests/dbugsrv.pp svneol=native#text/plain
 fcl/tests/dbugsrv.pp svneol=native#text/plain
 fcl/tests/debugtest.pp svneol=native#text/plain
 fcl/tests/debugtest.pp svneol=native#text/plain
 fcl/tests/doecho.pp svneol=native#text/plain
 fcl/tests/doecho.pp svneol=native#text/plain
@@ -1071,6 +1073,7 @@ fcl/tests/txmlreg.pp svneol=native#text/plain
 fcl/tests/xmldump.pp svneol=native#text/plain
 fcl/tests/xmldump.pp svneol=native#text/plain
 fcl/unix/asyncio.inc svneol=native#text/plain
 fcl/unix/asyncio.inc svneol=native#text/plain
 fcl/unix/asyncioh.inc svneol=native#text/plain
 fcl/unix/asyncioh.inc svneol=native#text/plain
+fcl/unix/daemonapp.inc svneol=native#text/plain
 fcl/unix/eventlog.inc svneol=native#text/plain
 fcl/unix/eventlog.inc svneol=native#text/plain
 fcl/unix/pipes.inc svneol=native#text/plain
 fcl/unix/pipes.inc svneol=native#text/plain
 fcl/unix/process.inc svneol=native#text/plain
 fcl/unix/process.inc svneol=native#text/plain
@@ -1095,6 +1098,7 @@ fcl/web/websession.pp svneol=native#text/plain
 fcl/web/webutil.pp svneol=native#text/plain
 fcl/web/webutil.pp svneol=native#text/plain
 fcl/web/wtagsimpl.inc svneol=native#text/plain
 fcl/web/wtagsimpl.inc svneol=native#text/plain
 fcl/web/wtagsintf.inc svneol=native#text/plain
 fcl/web/wtagsintf.inc svneol=native#text/plain
+fcl/win/daemonapp.inc svneol=native#text/plain
 fcl/win/eventlog.inc svneol=native#text/plain
 fcl/win/eventlog.inc svneol=native#text/plain
 fcl/win/fclel.mc -text
 fcl/win/fclel.mc -text
 fcl/win/fclel.rc -text
 fcl/win/fclel.rc -text

File diff suppressed because it is too large
+ 27 - 444
fcl/Makefile


+ 10 - 10
fcl/Makefile.fpc

@@ -42,16 +42,16 @@ units=contnrs inifiles ezcgi pipes rtfpars idea base64 gettext \
       iostream zstream cachecls avl_tree htmldefs testutils fpcunit \
       iostream zstream cachecls avl_tree htmldefs testutils fpcunit \
       eventlog custapp cgiapp wformat whtml wtex rttiutils bufstream \
       eventlog custapp cgiapp wformat whtml wtex rttiutils bufstream \
       streamex blowfish zipper streamio inicol pooledmm libtar streamcoll \
       streamex blowfish zipper streamio inicol pooledmm libtar streamcoll \
-      xmlreg registry 
-units_freebsd=process ssockets resolve fpasync syncobjs simpleipc dbugmsg dbugintf
-units_darwin=process ssockets resolve fpasync syncobjs simpleipc dbugmsg dbugintf
-units_solaris=process ssockets resolve fpasync syncobjs simpleipc dbugmsg dbugintf
-units_netbsd=process ssockets resolve fpasync simpleipc dbugmsg dbugintf
-units_openbsd=process ssockets resolve fpasync simpleipc dbugmsg dbugintf
-units_linux=process resolve ssockets fpasync syncobjs simpleipc dbugmsg dbugintf
-units_win32=process fileinfo resolve ssockets syncobjs simpleipc dbugmsg dbugintf
-units_win64=process fileinfo resolve ssockets syncobjs simpleipc dbugmsg dbugintf
-units_wince=process fileinfo resolve ssockets syncobjs simpleipc dbugmsg dbugintf
+      xmlreg registry
+units_freebsd=process ssockets resolve fpasync syncobjs simpleipc dbugmsg dbugintf daemonapp
+units_darwin=process ssockets resolve fpasync syncobjs simpleipc dbugmsg dbugintf daemonapp
+units_solaris=process ssockets resolve fpasync syncobjs simpleipc dbugmsg dbugintf daemonapp
+units_netbsd=process ssockets resolve fpasync simpleipc dbugmsg dbugintf daemonapp
+units_openbsd=process ssockets resolve fpasync simpleipc dbugmsg dbugintf daemonapp
+units_linux=process resolve ssockets fpasync syncobjs simpleipc dbugmsg dbugintf daemonapp
+units_win32=process fileinfo resolve ssockets syncobjs simpleipc dbugmsg dbugintf daemonapp
+units_win64=process fileinfo resolve ssockets syncobjs simpleipc dbugmsg dbugintf daemonapp
+units_wince=process fileinfo resolve ssockets syncobjs simpleipc dbugmsg dbugintf daemonapp
 units_os2=resolve ssockets
 units_os2=resolve ssockets
 units_emx=resolve ssockets
 units_emx=resolve ssockets
 units_netware=resolve ssockets
 units_netware=resolve ssockets

+ 1340 - 0
fcl/inc/daemonapp.pp

@@ -0,0 +1,1340 @@
+unit daemonapp;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Custapp, Classes, SysUtils, eventlog, rtlconsts;
+  
+Type
+  TCustomDaemon = Class;
+  TDaemonController = Class;
+
+  TDaemonEvent = procedure(Sender: TCustomDaemon) of object;
+  TDaemonOKEvent = procedure(Sender: TCustomDaemon; var OK: Boolean) of object;
+  
+  TDaemonOption = (doAllowStop,doAllowPause,doInteractive);
+  TDaemonOptions = Set of TDaemonOption;
+
+  TDaemonRunMode = (drmUnknown,drmInstall,drmUninstall,drmRun);
+  
+  { TCustomDaemonDescription }
+  TDaemonDef = Class;
+  TCurrentStatus =
+    (csStopped, csStartPending, csStopPending, csRunning,
+     csContinuePending, csPausePending, csPaused);
+
+  TCustomDaemon = Class(TDataModule)
+  private
+    FController: TDaemonController;
+    FDaemonDef: TDaemonDef;
+    FThread : TThread;
+    FStatus: TCurrentStatus;
+    function GetLogger: TEventLog;
+    procedure SetStatus(const AValue: TCurrentStatus);
+  Protected
+    Function Start : Boolean; virtual;
+    Function Stop : Boolean; virtual;
+    Function Pause : Boolean; virtual;
+    Function Continue : Boolean; virtual;
+    Function Execute : Boolean; virtual;
+    Function ShutDown : Boolean; virtual;
+    Function Install : Boolean; virtual;
+    Function UnInstall: boolean; virtual;
+    Function HandleCustomCode(ACode : DWord) : Boolean; Virtual;
+  Public
+    Procedure LogMessage(Msg : String);
+    Procedure ReportStatus;
+    
+    // Filled in at runtime by controller
+    Property Definition : TDaemonDef Read FDaemonDef;
+    Property DaemonThread : TThread Read FThread;
+    Property Controller : TDaemonController Read FController;
+    Property Status : TCurrentStatus Read FStatus Write SetStatus;
+    Property Logger : TEventLog Read GetLogger;
+  end;
+
+  TCustomDaemonClass = Class of TCustomDaemon;
+
+  { TDaemon }
+  TCustomControlCodeEvent = Procedure(Sender : TCustomDaemon; ACode : DWord; Var Handled : Boolean) of object;
+
+  TDaemon = Class(TCustomDaemon)
+  private
+    FAfterInstall: TDaemonEvent;
+    FAfterUnInstall: TDaemonEvent;
+    FBeforeInstall: TDaemonEvent;
+    FBeforeUnInstall: TDaemonEvent;
+    FOnContinue: TDaemonOKEvent;
+    FOnCustomControl: TCustomControlCodeEvent;
+    FOnExecute: TDaemonEvent;
+    FOnPause: TDaemonOKEvent;
+    FOnShutDown: TDaemonEvent;
+    FOnStart: TDaemonOKEvent;
+    FOnStop: TDaemonOKEvent;
+  Protected
+    Function Start : Boolean; override;
+    Function Stop : Boolean; override;
+    Function Pause : Boolean; override;
+    Function Continue : Boolean; override;
+    Function Execute : Boolean; override;
+    Function ShutDown : Boolean; override;
+    Function Install : Boolean; override;
+    Function UnInstall: boolean; override;
+    Function HandleCustomCode(ACode : DWord) : Boolean; Override;
+  Public
+    Property Definition;
+    Property Status;
+  Published
+    Property OnStart : TDaemonOKEvent Read FOnStart Write FOnStart;
+    Property OnStop : TDaemonOKEvent Read FOnStop Write FOnStop;
+    Property OnPause : TDaemonOKEvent Read FOnPause Write FOnPause;
+    Property OnContinue : TDaemonOKEvent Read FOnContinue Write FOnContinue;
+    Property OnShutDown : TDaemonEvent Read FOnShutDown Write FOnShutDown;
+    Property OnExecute : TDaemonEvent Read FOnExecute Write FOnExecute;
+    Property BeforeInstall : TDaemonEvent Read FBeforeInstall Write FBeforeInstall;
+    Property AfterInstall : TDaemonEvent Read FAfterInstall Write FAfterInstall;
+    Property BeforeUnInstall : TDaemonEvent Read FBeforeUnInstall Write FBeforeUnInstall;
+    Property AfterUnInstall : TDaemonEvent Read FAfterUnInstall Write FAfterUnInstall;
+    Property OnControlCode : TCustomControlCodeEvent Read FOnCustomControl Write FOnCustomControl;
+  end;
+
+  { TDaemonController }
+
+  TDaemonController = Class(TComponent)
+  Private
+    FDaemon : TCustomDaemon;
+    FLastStatus: TCurrentStatus;
+    FSysData : TObject;
+    FParams : TStrings;
+    FCheckPoint : DWord;
+    procedure ThreadTerminated(Sender: TObject);
+  Public
+    Constructor Create(AOwner : TComponent); override;
+    Destructor Destroy; override;
+    Procedure StartService; virtual;
+    Procedure Main(Argc : DWord; Args : PPChar); Virtual;
+    Procedure Controller(ControlCode,EventType : DWord; EventData : Pointer); Virtual;
+    Function ReportStatus : Boolean; virtual;
+    Property Daemon : TCustomDaemon Read FDaemon;
+    Property Params : TStrings Read FParams;
+    Property LastStatus : TCurrentStatus Read FLastStatus;
+    Property CheckPoint : DWord;
+  end;
+  
+  TDaemonClass = Class of TDaemon;
+  
+  { Windows specific service registration types }
+  
+  TServiceType   = (stWin32, stDevice, stFileSystem);
+  TErrorSeverity = (esIgnore, esNormal, esSevere, esCritical);
+  TStartType     = (stBoot, stSystem, stAuto, stManual, stDisabled);
+  
+  { TDependency }
+
+  TDependency = class(TCollectionItem)
+  private
+    FName: String;
+    FIsGroup: Boolean;
+  protected
+    function GetDisplayName: string; override;
+  Public
+    Procedure Assign(Source : TPersistent); override;
+  published
+    property Name: String read FName write FName;
+    property IsGroup: Boolean read FIsGroup write FIsGroup;
+  end;
+  
+  { TDependencies }
+
+  TDependencies = class(TCollection)
+  private
+    FOwner: TPersistent;
+    function GetItem(Index: Integer): TDependency;
+    procedure SetItem(Index: Integer; Value: TDependency);
+  protected
+    function GetOwner: TPersistent; override;
+  public
+    constructor Create(AOwner: TPersistent);
+    property Items[Index: Integer]: TDependency read GetItem write SetItem; default;
+  end;
+
+
+  { TWinBindings }
+
+  TWinBindings = class(TPersistent)
+  private
+    FDependencies: TDependencies;
+    FErrCode: DWord;
+    FErrorSeverity: TErrorSeverity;
+    FLoadGroup: String;
+    FPassWord: String;
+    FServiceType: TServiceType;
+    FStartType: TStartType;
+    FTagID: DWord;
+    FUserName: String;
+    FWaitHint: Integer;
+    FWin32ErrorCode: DWord;
+    procedure SetDependencies(const AValue: TDependencies);
+  Public
+    Constructor Create;
+    Destructor Destroy; override;
+    Procedure Assign(Source : TPersistent); override;
+    property ErrCode: DWord read FErrCode write FErrCode;
+    property Win32ErrCode: DWord read FWin32ErrorCode write FWin32ErrorCode;
+  Published
+    Property Dependencies : TDependencies Read FDependencies Write SetDependencies;
+    Property GroupName : String Read FLoadGroup Write FLoadGroup;
+    Property Password : String Read FPassWord Write FPassword;
+    Property UserName : String Read FUserName Write FUserName;
+    Property StartType : TStartType Read FStartType Write FStartType;
+    Property WaitHint : Integer Read FWaitHint Write FWaitHint;
+    Property IDTag : DWord Read FTagID Write FTagID;
+    Property ServiceType : TServiceType Read FServiceType Write FServiceType;
+    Property ErrorSeverity : TErrorSeverity Read FErrorSeverity Write FErrorSeverity;
+  end;
+
+  { TDaemonDef }
+  
+  TDaemonDef = Class(TCollectionItem)
+  private
+    FDaemonClass: TCustomDaemonClass;
+    FDaemonClassName: String;
+    FDescription: String;
+    FDisplayName: String;
+    FEnabled: Boolean;
+    FInstance: TCustomDaemon;
+    FLogStatusReport: Boolean;
+    FName: String;
+    FOnCreateInstance: TNotifyEvent;
+    FOptions: TDaemonOptions;
+    FServiceName: String;
+    FWinBindings: TWinBindings;
+    procedure SetName(const AValue: String);
+    procedure SetWinBindings(const AValue: TWinBindings);
+  Protected
+    function GetDisplayName: string; override;
+  Public
+    Constructor Create(ACollection : TCollection); override;
+    Destructor Destroy; override;
+    Property DaemonClass : TCustomDaemonClass read FDaemonClass;
+    Property Instance : TCustomDaemon Read FInstance Write FInstance;
+  Published
+    Property DaemonClassName : String Read FDaemonClassName Write FDaemonClassName;
+    Property Name : String Read FName Write SetName;
+    Property DisplayName : String Read FDisplayName Write FDisplayName;
+    Property Options : TDaemonOptions Read FOptions Write FOptions;
+    Property Enabled : Boolean Read FEnabled Write FEnabled default true;
+    Property WinBindings : TWinBindings Read FWinBindings Write SetWinBindings;
+    Property OnCreateInstance : TNotifyEvent Read FOnCreateInstance Write FOnCreateInstance;
+    Property LogStatusReport : Boolean Read FLogStatusReport Write FLogStatusReport;
+  end;
+
+  { TDaemonDefs }
+
+  TDaemonDefs = Class(TCollection)
+    FOwner : TPersistent;
+  private
+    function GetDaemonDef(Index : Integer): TDaemonDef;
+    procedure SetDaemonDef(Index : Integer; const AValue: TDaemonDef);
+  Protected
+    Procedure BindClasses;
+    Function GetOwner : TPersistent; override;
+  Public
+    Constructor Create(AOwner : TPersistent; AClass : TCollectionItemClass);
+    Function IndexOfDaemonDef(Const DaemonName : String) : Integer;
+    Function FindDaemonDef(Const DaemonName : String) : TDaemonDef;
+    Function DaemonDefByName(Const DaemonName : String) : TDaemonDef;
+    Property Daemons[Index : Integer] : TDaemonDef Read GetDaemonDef Write SetDaemonDef; default;
+  end;
+  
+  { TCustomDaemonMapper }
+  TCustomDaemonMapper = Class(TComponent)
+  private
+    FDaemonDefs: TDaemonDefs;
+    FOnCreate: TNotifyEvent;
+    FOnDestroy: TNotifyEvent;
+    FOnInstall: TNotifyEvent;
+    FOnRun: TNotifyEvent;
+    FOnUnInStall: TNotifyEvent;
+    procedure SetDaemonDefs(const AValue: TDaemonDefs);
+  Protected
+    Procedure CreateDefs; virtual;
+    Procedure DoOnCreate; virtual;
+    Procedure DoOnDestroy; virtual;
+    Procedure DoOnInstall; virtual;
+    Procedure DoOnUnInstall; virtual;
+    Procedure DoOnRun; virtual;
+  Public
+    Constructor Create(AOwner : TComponent); override;
+    Destructor Destroy; override;
+  Published
+    Property DaemonDefs : TDaemonDefs Read FDaemonDefs Write SetDaemonDefs;
+    Property OnCreate : TNotifyEvent Read FOnCreate Write FOnCreate;
+    Property OnDestroy : TNotifyEvent Read FOnDestroy Write FOnDestroy;
+    Property OnRun : TNotifyEvent Read FOnRun Write FOnRun;
+    Property OnInstall : TNotifyEvent Read FOnInstall Write FOnInstall;
+    Property OnUnInstall : TNotifyEvent Read FOnUnInStall Write FOnUninStall;
+  end;
+  
+  { TDaemonMapper }
+
+  TDaemonMapper = Class(TCustomDaemonMapper)
+    Constructor Create(AOwner : TComponent); override;
+    Constructor CreateNew(AOwner : TComponent; Dummy : Integer = 0);
+  end;
+  
+  TCustomDaemonMapperClass = Class of TCustomDaemonMapper;
+  
+  { TDaemonThread }
+
+  TDaemonThread = Class(TThread)
+  Private
+    FDaemon : TCustomDaemon;
+  Protected
+    procedure StartServiceExecute; virtual;
+    procedure HandleControlCode(ACode : DWord); virtual;
+  Public
+    Constructor Create(ADaemon : TCustomDaemon);
+    Procedure Execute; override;
+    Procedure CheckControlMessage(WaitForMessage : Boolean);
+    Function StopDaemon : Boolean; virtual;
+    Function PauseDaemon : Boolean; virtual;
+    Function ContinueDaemon : Boolean; virtual;
+    Function ShutDownDaemon : Boolean; virtual;
+    Function InterrogateDaemon : Boolean; virtual;
+    Property Daemon : TCustomDaemon Read FDaemon;
+  end;
+
+  { TCustomDaemonApplication }
+  TGuiLoopEvent = Procedure Of Object;
+  
+  TCustomDaemonApplication = Class(TCustomApplication)
+  private
+    FGUIHandle: THandle;
+    FGUIMainLoop: TGuiLoopEvent;
+    FLogger: TEventLog;
+    FMapper : TCustomDaemonMapper;
+    FOnRun: TNotifyEvent;
+    FRunMode: TDaemonRunMode;
+    FSysData: TObject;
+    FControllerCount : Integer;
+    procedure BindDaemonDefs(AMapper: TCustomDaemonMapper);
+    function  InstallRun: Boolean;
+    procedure SysInstallDaemon(Daemon: TCustomDaemon);
+    procedure SysUnInstallDaemon(Daemon: TCustomDaemon);
+    function  UnInstallRun: Boolean;
+    function  RunDaemonsRun: Boolean;
+    Procedure Main(Argc : DWord; Args : PPchar);
+    Function  RunGUIloop(P : Pointer) : integer;
+  Protected
+    // OS (System) dependent calls
+    Procedure SysStartUnInstallDaemons;
+    Procedure SysEndUnInstallDaemons;
+    Procedure SysStartInstallDaemons;
+    Procedure SysEndInstallDaemons;
+    Procedure SysStartRunDaemons;
+    Procedure SysEndRunDaemons;
+
+    // Customizable behaviour
+    procedure CreateDaemonController(Var AController : TDaemonController); virtual;
+    Procedure CreateServiceMapper(Var AMapper : TCustomDaemonMapper); virtual;
+    Procedure RemoveController(AController : TDaemonController); virtual;
+    procedure SetupLogger;
+    procedure StopLogger;
+    Procedure DoRun; override;
+    Property  OnRun : TNotifyEvent Read FOnRun Write FOnRun;
+    Property SysData : TObject Read FSysData Write FSysData;
+  Public
+    Procedure ShowException(E : Exception); override;
+    Function CreateDaemon(DaemonDef : TDaemonDef) : TCustomDaemon;
+    Procedure StopDaemons(Force : Boolean);
+    procedure InstallDaemons;
+    procedure RunDaemons;
+    procedure UnInstallDaemons;
+    procedure CreateForm(InstanceClass: TComponentClass; var Reference); virtual;
+    Property Logger : TEventLog Read FLogger;
+    Property GUIMainLoop : TGuiLoopEvent Read FGUIMainLoop Write FGuiMainLoop;
+    Property GuiHandle : THandle Read FGUIHandle Write FGUIHandle;
+    Property RunMode : TDaemonRunMode Read FRunMode;
+  end;
+  
+  TDaemonApplication = Class(TCustomDaemonApplication)
+  end;
+
+  EDaemon = Class(Exception);
+
+Function Application : TCustomDaemonApplication;
+Procedure RegisterDaemonMapper(AMapperClass : TCustomDaemonMapperClass);
+Procedure RegisterDaemonClass(AClass : TCustomDaemonClass);
+Procedure DaemonError(Msg : String);
+Procedure DaemonError(Fmt : String; Args : Array of const);
+
+
+Resourcestring
+  SErrNoServiceMapper           = 'No daemon mapper class registered.';
+  SErrOnlyOneMapperAllowed      = 'Not changing daemon mapper class %s with %s: Only 1 mapper allowed.';
+  SErrNothingToDo               = 'Options do not allow determining what needs to be done.';
+  SErrDuplicateName             = 'Duplicate daemon name: %s';
+  SErrUnknownDaemonClass        = 'Unknown daemon class name: %s';
+  SErrDaemonStartFailed         = 'Failed to start daemon %s : %s';
+  SDaemonStatus                 = 'Daemon %s current status: %s';
+  SControlFailed                = 'Control code %s handling failed: %s';
+  SCustomCode                   = '[Custom code %d]';
+  SErrServiceManagerStartFailed = 'Failed to start service manager: %s';
+  SErrNoDaemonForStatus         = '%s: No daemon for status report';
+  SErrNoDaemonDefForStatus      = '%s: No daemon definition for status report';
+  SErrWindowClass               = 'Could not register window class';
+
+{ $define svcdebug}
+
+{$ifdef svcdebug}
+Procedure DebugLog(Msg : String);
+{$endif}
+
+Var
+  CurrentStatusNames : Array[TCurrentStatus] of string =
+    ('Stopped', 'Start Pending', 'Stop Pending', 'Running',
+     'Continue Pending', 'Pause Pending', 'Paused');
+  SStatus : Array[1..5] of string =
+    ('Stop','Pause','Continue','Interrogate','Shutdown');
+  DefaultDaemonOptions : TDaemonOptions =  [doAllowStop,doAllowPause];
+  
+implementation
+
+// This must come first, so a uses clause can be added.
+{$i daemonapp.inc}
+
+Var
+  AppInstance  : TCustomDaemonApplication;
+  MapperClass  : TCustomDaemonMapperClass;
+  DesignMapper : TCustomDaemonMapper;
+  DaemonClasses : TStringList;
+
+{$ifdef svcdebug}
+Var
+  FL : Text;
+  LCS : TRTLCriticalSection;
+  
+Procedure StartLog;
+
+begin
+{$ifdef win32}
+  Assign(FL,'c:\service.log');
+{$else}
+  Assign(FL,'/tmp/service.log');
+{$endif}
+  Rewrite(FL);
+  InitCriticalSection(LCS);
+  DebugLog('Start logging');
+end;
+
+Procedure DebugLog(Msg : String);
+begin
+  EnterCriticalSection(LCS);
+  try
+    Writeln(FL,Msg);
+    Flush(FL);
+  Finally
+    LeaveCriticalSection(LCS);
+  end;
+end;
+
+Procedure EndLog;
+
+begin
+  DebugLog('Done logging');
+  Close(FL);
+  DoneCriticalSection(LCS);
+end;
+{$endif svcdebug}
+
+Procedure RegisterDaemonClass(AClass : TCustomDaemonClass);
+
+Var
+  DN : String;
+  I  : Integer;
+  
+begin
+  If Not Assigned(DaemonClasses) then
+    begin
+    DaemonClasses:=TStringList.Create;
+    DaemonClasses.Sorted:=True;
+    end;
+  DN:=AClass.ClassName;
+  I:=DaemonClasses.IndexOf(DN);
+  If (I=-1) then
+    I:=DaemonClasses.Add(DN);
+  DaemonClasses.Objects[I]:=TObject(AClass);
+end;
+
+Procedure CreateDaemonApplication;
+
+begin
+  AppInstance:=TDaemonApplication.Create(Nil);
+end;
+
+Procedure DoneDaemonApplication;
+
+begin
+  FreeAndNil(AppInstance);
+  FreeAndNil(DaemonClasses);
+end;
+
+function Application: TCustomDaemonApplication;
+begin
+  If (AppInstance=Nil) then
+    CreateDaemonApplication;
+  Result:=AppInstance;
+end;
+
+Procedure RegisterDaemonMapper(AMapperClass : TCustomDaemonMapperClass);
+
+begin
+  If Assigned(MapperClass) then
+    DaemonError(SErrOnlyOneMapperAllowed,[MapperClass.ClassName,AMapperClass.ClassName]);
+  MapperClass:=AMapperClass;
+end;
+
+procedure DaemonError(Msg: String);
+begin
+  Raise EDaemon.Create(MSg);
+end;
+
+procedure DaemonError(Fmt: String; Args: array of const);
+begin
+  Raise EDaemon.CreateFmt(Fmt,Args);
+end;
+
+{ TDaemon }
+
+function TDaemon.Start: Boolean;
+begin
+  Result:=inherited Start;
+  If assigned(FOnStart) then
+    FOnStart(Self,Result);
+end;
+
+function TDaemon.Stop: Boolean;
+begin
+  Result:=inherited Start;
+  If assigned(FOnStop) then
+    FOnStop(Self,Result);
+end;
+
+function TDaemon.Pause: Boolean;
+begin
+  Result:=inherited Start;
+  If assigned(FOnPause) then
+    FOnPause(Self,Result);
+end;
+
+function TDaemon.Continue: Boolean;
+begin
+  Result:=inherited Continue;
+  If assigned(FOnContinue) then
+    FOnContinue(Self,Result);
+end;
+
+function TDaemon.Execute: Boolean;
+begin
+  Result:=Assigned(FOnExecute);
+  If Result Then
+    FOnExecute(Self);
+end;
+
+function TDaemon.ShutDown: Boolean;
+begin
+  Result:=Inherited ShutDown;
+  If Assigned(FOnShutDown) then
+    FOnShutDown(Self);
+end;
+
+function TDaemon.Install: Boolean;
+begin
+  If Assigned(FBeforeInstall) then
+    FBeforeInstall(Self);
+  Result:=inherited Install;
+  If Assigned(FAfterInstall) then
+    FAfterInstall(Self)
+end;
+
+function TDaemon.UnInstall: boolean;
+begin
+  If Assigned(FBeforeUnInstall) then
+    FBeforeUnInstall(Self);
+  Result:=inherited UnInstall;
+  If Assigned(FAfterUnInstall) then
+    FAfterUnInstall(Self)
+end;
+
+function TDaemon.HandleCustomCode(ACode: DWord): Boolean;
+begin
+  Result:=Assigned(FOnCustomControl);
+  If Result then
+    FOnCustomControl(Self,ACode,Result);
+end;
+
+{ TCustomDaemon }
+
+Function TCustomDaemon.Start : Boolean;
+
+begin
+  Result:=True;
+end;
+
+Function  TCustomDaemon.Stop : Boolean;
+begin
+  Result:=True;
+end;
+
+Function TCustomDaemon.Pause : Boolean;
+begin
+  Result:=True;
+end;
+
+Function TCustomDaemon.Continue : Boolean;
+begin
+  Result:=True;
+end;
+
+function TCustomDaemon.Execute: Boolean;
+begin
+  Result:=False;
+end;
+
+Function TCustomDaemon.ShutDown : Boolean;
+begin
+  Result:=True;
+end;
+
+Procedure TCustomDaemon.ReportStatus;
+begin
+  Controller.ReportStatus;
+end;
+
+
+
+procedure TCustomDaemon.LogMessage(Msg: String);
+begin
+  Application.Logger.Error(Msg);
+end;
+
+function TCustomDaemon.GetLogger: TEventLog;
+begin
+  Result:=Application.Logger;
+end;
+
+procedure TCustomDaemon.SetStatus(const AValue: TCurrentStatus);
+begin
+  FStatus:=AValue;
+  Controller.ReportStatus;
+end;
+
+Function TCustomDaemon.Install : Boolean;
+begin
+  Result:=True;
+end;
+
+
+Function TCustomDaemon.UnInstall : Boolean;
+begin
+  Result:=True;
+end;
+
+function TCustomDaemon.HandleCustomCode(ACode: DWord): Boolean;
+begin
+  Result:=False
+end;
+
+{ TCustomServiceApplication }
+
+
+procedure TCustomDaemonApplication.CreateServiceMapper(Var AMapper : TCustomDaemonMapper);
+
+begin
+  AMapper:=MapperClass.Create(Self);
+  BindDaemonDefs(Amapper);
+end;
+
+procedure TCustomDaemonApplication.BindDaemonDefs(AMapper : TCustomDaemonMapper);
+
+begin
+  AMApper.DaemonDefs.BindClasses;
+end;
+
+procedure TCustomDaemonApplication.CreateDaemonController(Var AController : TDaemonController);
+
+begin
+  ACOntroller:=TDaemonController.Create(Self);
+end;
+
+Function TCustomDaemonApplication.RunDaemonsRun : Boolean;
+
+begin
+  Result:=HasOption('r','run');
+  // No Borland compatibility needed, as the install will take care of the -r
+end;
+
+procedure TCustomDaemonApplication.Main(Argc: DWord; Args: PPchar);
+
+Var
+  SN : String;
+  DD : TDaemonDef;
+  
+begin
+  If (Args=Nil) then
+    Exit;
+  SN:=StrPas(Args^);
+  DD:=FMapper.DaemonDefs.FindDaemonDef(SN);
+  If (DD<>Nil) then
+    DD.Instance.Controller.Main(Argc,Args);
+end;
+
+
+Function TCustomDaemonApplication.InstallRun : Boolean;
+
+begin
+  Result:=HasOption('i','install');
+  // Borland compatibility.
+  If not Result then
+    Result:=FindCmdLineSwitch ('install',['/'],True);
+end;
+
+
+
+Function TCustomDaemonApplication.UnInstallRun : Boolean;
+
+begin
+  Result:=HasOption('u','uninstall');
+  // Borland compatibility.
+  If not Result then
+    Result:=FindCmdLineSwitch ('uninstall',['/'],True);
+end;
+
+
+
+Procedure TCustomDaemonApplication.InstallDaemons;
+
+Var
+  D : TCustomDaemon;
+  DD : TDaemonDef;
+  C : TDaemonController;
+  I : Integer;
+
+begin
+  FrunMode:=drmInstall;
+  SysStartInstallDaemons;
+  try
+    FMapper.DoOnInstall;
+    For I:=0 to FMapper.DaemonDefs.Count-1 do
+      begin
+      DD:=FMapper.DaemonDefs[i];
+      If DD.Enabled then
+        begin
+        D:=CreateDaemon(DD);
+        Try
+          // Need to call this because of the before/after events.
+          D.Install;
+        Finally
+          D.Free;
+        end;
+        end;
+      end;
+  Finally
+    SysEndInstallDaemons;
+  end;
+end;
+
+Procedure TCustomDaemonApplication.UnInstallDaemons;
+
+Var
+  D : TCustomDaemon;
+  DD : TDaemonDef;
+  I : Integer;
+
+begin
+  FrunMode:=drmUnInstall;
+  SysStartUnInstallDaemons;
+  Try
+    FMapper.DoOnUnInstall;
+    // Uninstall in reverse order. One never knows.
+    For I:=FMapper.DaemonDefs.Count-1 downto 0 do
+      begin
+      DD:=FMapper.DaemonDefs[i];
+      If DD.Enabled then
+        begin
+        D:=CreateDaemon(FMapper.DaemonDefs[i]);
+        Try
+          // Need to call this because of the before/after events.
+          D.UnInstall;
+        Finally
+          D.Free;
+        end;
+        end;
+      end;
+  Finally
+    SysEndUnInstallDaemons;
+  end;
+end;
+
+procedure TCustomDaemonApplication.CreateForm(InstanceClass: TComponentClass;
+  var Reference);
+  
+Var
+  Instance: TComponent;
+  
+begin
+  // Allocate the instance, without calling the constructor
+  Instance := TComponent(InstanceClass.NewInstance);
+  // set the Reference before the constructor is called, so that
+  // events and constructors can refer to it
+  TComponent(Reference) := Instance;
+  try
+    Instance.Create(Self);
+  except
+    TComponent(Reference) := nil;
+    Raise;
+  end;
+end;
+
+Procedure TCustomDaemonApplication.RunDaemons;
+
+Var
+  D : TCustomDaemon;
+  DD : TDaemonDef;
+  I : Integer;
+
+begin
+  FRunMode:=drmRun;
+  SysStartRunDaemons;
+  FMapper.DoOnRun;
+  For I:=0 to FMapper.DaemonDefs.Count-1 do
+    begin
+    DD:=FMapper.DaemonDefs[i];
+    If DD.Enabled then
+      D:=CreateDaemon(FMapper.DaemonDefs[i]);
+    end;
+  try
+    SysEndRunDaemons;
+  except
+    HandleException(Self);
+    Terminate;
+  end;
+end;
+
+procedure TCustomDaemonApplication.SetupLogger;
+
+begin
+  FLogger:=TEventlog.Create(Self);
+  FLogger.RegisterMessageFile('');
+end;
+
+procedure TCustomDaemonApplication.StopLogger;
+
+begin
+  Flogger.Active:=False;
+  FreeAndNil(Flogger);
+end;
+
+procedure TCustomDaemonApplication.DoRun;
+
+begin
+  SetupLogger;
+  Try
+    try
+      If Not Assigned(MapperClass) then
+        DaemonError(SErrNoServiceMapper);
+      CreateServiceMapper(FMapper);
+      If InstallRun then
+        InstallDaemons
+      else If UnInstallRun then
+        UnInstallDaemons
+      else if RunDaemonsRun then
+        RunDaemons
+      else if Assigned(OnRun) then
+       OnRun(Self)
+      else
+        DaemonError(SErrNothingToDo);
+      {$ifdef svcdebug}DebugLog('Terminating');{$endif svcdebug}
+      Terminate;
+      {$ifdef svcdebug}DebugLog('Terminated');{$endif svcdebug}
+    except
+      Terminate;
+      Raise
+    end;
+  Finally
+    StopLogger;
+  end;
+end;
+
+procedure TCustomDaemonApplication.ShowException(E: Exception);
+begin
+  If assigned(Flogger) then
+    FLogger.Error(E.Message)
+  else
+   inherited ShowException(E)
+end;
+
+
+function TCustomDaemonApplication.CreateDaemon(DaemonDef: TDaemonDef): TCustomDaemon;
+
+Var
+  C : TDaemonController;
+
+begin
+  Result:=DaemonDef.DaemonClass.CreateNew(Self,0);
+  CreateDaemonController(C);
+  C.FDaemon:=Result;
+  Result.FController:=C;
+  Result.FDaemonDef:=DaemonDef;
+  If (Daemondef.Instance=Nil) then
+    DaemonDef.Instance:=Result;
+end;
+
+procedure TCustomDaemonApplication.StopDaemons(Force: Boolean);
+
+Const
+  ControlCodes : Array[Boolean] of DWord
+               = (SERVICE_CONTROL_STOP,SERVICE_CONTROL_SHUTDOWN);
+
+Var
+  L : TFPList;
+  I : Integer;
+
+begin
+  L:=TFPList.Create;
+  try
+    For I:=0 to ComponentCount-1 do
+      If Components[i] is TDaemonController then
+        L.Add(Components[i]);
+    For I:=L.Count-1 downto 0 do
+      TDaemonController(L[i]).Controller(ControlCodes[Force],0,Nil);
+  finally
+    L.Free;
+  end;
+end;
+
+
+
+
+{ TDaemonDefs }
+
+function TDaemonDefs.GetDaemonDef(Index : Integer): TDaemonDef;
+begin
+  Result:=TDaemonDef(Items[index]);
+end;
+
+procedure TDaemonDefs.SetDaemonDef(Index : Integer; const AValue: TDaemonDef);
+begin
+  Items[Index]:=AValue;
+end;
+
+procedure TDaemonDefs.BindClasses;
+
+Var
+  D : TDaemonDef;
+  I,J : Integer;
+  
+begin
+  For I:=0 to Count-1 do
+    begin
+    D:=GetDaemonDef(I);
+    J:=DaemonClasses.IndexOf(D.DaemonClassName);
+    If (J=-1) then
+      DaemonError(SErrUnknownDaemonClass,[D.DaemonClassName])
+    else
+      D.FDaemonClass:=TCustomDaemonClass(DaemonClasses.Objects[J]);
+    end;
+
+end;
+
+function TDaemonDefs.GetOwner: TPersistent;
+begin
+  Result:=FOwner;
+end;
+
+constructor TDaemonDefs.Create(AOwner: TPersistent; AClass : TCollectionItemClass);
+begin
+  Inherited Create(AClass);
+  FOwner:=AOwner;
+  
+end;
+
+function TDaemonDefs.IndexOfDaemonDef(Const DaemonName: String): Integer;
+begin
+  Result:=Count-1;
+  While (Result>=0) and (CompareText(GetDaemonDef(Result).Name,DaemonName)<>0) do
+    Dec(Result);
+end;
+
+function TDaemonDefs.FindDaemonDef(Const DaemonName: String): TDaemonDef;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfDaemonDef(DaemonName);
+  If I<>-1 then
+    Result:=GetDaemonDef(I)
+  else
+    Result:=Nil;
+end;
+
+function TDaemonDefs.DaemonDefByName(Const DaemonName: String): TDaemonDef;
+begin
+  Result:=FindDaemonDef(DaemonName);
+end;
+
+{ TDaemonDef }
+
+procedure TDaemonDef.SetName(const AValue: String);
+begin
+  If (AValue<>FName) then
+    begin
+    If (AValue<>'') and (Collection<>Nil)
+       and (Collection is TDaemonDefs)
+       and ((Collection as TDaemonDefs).IndexOfDaemonDef(AValue)<>-1) then
+      DaemonError(SErrDuplicateName,[Avalue]);
+    FName:=AValue;
+    end;
+end;
+
+procedure TDaemonDef.SetWinBindings(const AValue: TWinBindings);
+begin
+  FWinBindings.Assign(AValue);
+end;
+
+function TDaemonDef.GetDisplayName: string;
+begin
+  Result:=Name;
+end;
+
+constructor TDaemonDef.Create(ACollection: TCollection);
+begin
+  inherited Create(ACollection);
+  FWinBindings:=TWinBindings.Create;
+  FEnabled:=True;
+  FOptions:=DefaultDaemonOptions;
+end;
+
+destructor TDaemonDef.Destroy;
+begin
+  FreeAndNil(FWinBindings);
+  inherited Destroy;
+end;
+
+{ TCustomDaemonMapper }
+
+procedure TCustomDaemonMapper.SetDaemonDefs(const AValue: TDaemonDefs);
+begin
+  if (FDaemonDefs=AValue) then
+    exit;
+  FDaemonDefs.Assign(AValue);
+end;
+
+procedure TCustomDaemonMapper.CreateDefs;
+begin
+  FDaemonDefs:=TDaemonDefs.Create(Self,TDaemonDef);
+end;
+
+procedure TCustomDaemonMapper.DoOnCreate;
+begin
+  If Assigned(FOnCreate) then
+    FOnCreate(Self);
+end;
+
+procedure TCustomDaemonMapper.DoOnDestroy;
+begin
+  If Assigned(FOnDestroy) then
+    FOnDestroy(Self);
+end;
+
+procedure TCustomDaemonMapper.DoOnInstall;
+begin
+  If Assigned(FOnInstall) then
+    FOnInstall(Self);
+end;
+
+procedure TCustomDaemonMapper.DoOnUnInstall;
+begin
+  If Assigned(FOnUnInstall) then
+    FOnUnInstall(Self);
+end;
+
+procedure TCustomDaemonMapper.DoOnRun;
+begin
+  If Assigned(FOnRun) then
+    FOnRun(Self);
+end;
+
+constructor TCustomDaemonMapper.Create(AOwner: TComponent);
+begin
+  CreateDefs; // First, otherwise streaming will fail.
+  inherited Create(AOwner);
+  DoOnCreate;
+end;
+
+destructor TCustomDaemonMapper.Destroy;
+begin
+  DoOnDestroy;
+  FreeAndNil(FDaemonDefs);
+  inherited Destroy;
+end;
+
+{ TDaemonThread }
+
+constructor TDaemonThread.Create(ADaemon: TCustomDaemon);
+begin
+  FDaemon:=ADAemon;
+  FDaemon.FThread:=Self;
+  FreeOnTerminate:=False;
+  Inherited Create(True);
+end;
+
+procedure TDaemonThread.Execute;
+
+begin
+  If FDaemon.Start then
+    begin
+    FDaemon.Status:=csRunning;
+    StartServiceExecute;
+    if not FDaemon.Execute then
+      begin
+      While Not Terminated do
+        CheckControlMessage(True);
+      CheckControlMessage(False);
+      end;
+    end;
+end;
+
+
+procedure TDaemonThread.HandleControlCode(ACode : DWord);
+
+Var
+  CS : TCurrentStatus;
+  CC,OK : Boolean;
+  S : String;
+
+begin
+  CS:=FDaemon.Status;
+  Try
+    OK:=True;
+    CC:=False;
+    Case ACode of
+      SERVICE_CONTROL_STOP        : OK:=StopDaemon;
+      SERVICE_CONTROL_PAUSE       : OK:=PauseDaemon;
+      SERVICE_CONTROL_CONTINUE    : OK:=ContinueDaemon;
+      SERVICE_CONTROL_SHUTDOWN    : OK:=ShutDownDaemon;
+      SERVICE_CONTROL_INTERROGATE : OK:=InterrogateDaemon;
+    else
+      CC:=True;
+      FDaemon.HandleCustomCode(ACode);
+    end;
+    If not OK then
+      FDaemon.Status:=CS;
+  Except
+    On E : Exception do
+      begin
+      // Shutdown MUST be done, in all other cases roll back status.
+      If (ACode<>SERVICE_CONTROL_SHUTDOWN) then
+        FDaemon.Status:=CS;
+      If (ACode in [1..5]) then
+        S:=SStatus[ACode]
+      else
+        S:=Format(SCustomCode,[ACode]);
+      Application.Logger.Error(SControlFailed,[S,E.Message]);
+      end;
+  end;
+end;
+
+function TDaemonThread.StopDaemon: Boolean;
+
+begin
+  FDaemon.Status:=csStopPending;
+  Result:=FDaemon.Stop;
+  If Result then
+    begin
+    FDaemon.Status:=csStopped;
+    Terminate;
+    end;
+end;
+
+function TDaemonThread.PauseDaemon: Boolean;
+begin
+  FDaemon.Status:=csPausePending;
+  Result:=FDaemon.Pause;
+  If Result then
+    begin
+    FDaemon.Status:=csPaused;
+    Suspend;
+    end;
+end;
+
+function TDaemonThread.ContinueDaemon: Boolean;
+begin
+  FDaemon.Status:=csContinuePending;
+  Result:=FDaemon.Continue;
+  If Result then
+    FDaemon.Status:=csRunning;
+end;
+
+function TDaemonThread.ShutDownDaemon: Boolean;
+begin
+  FDaemon.Status:=csStopPending;
+  Try
+    Result:=FDaemon.ShutDown;
+  except
+    FDaemon.Status:=csStopped;
+    Terminate;
+  end;
+end;
+
+Function TDaemonThread.InterrogateDaemon: Boolean;
+begin
+  FDaemon.ReportStatus;
+  Result:=True;
+end;
+
+{ ---------------------------------------------------------------------
+  TDaemonController - Global implementation
+  ---------------------------------------------------------------------}
+
+
+constructor TDaemonController.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FParams:=TStringList.Create;
+end;
+
+destructor TDaemonController.Destroy;
+begin
+  FreeAndNil(FSysData);
+  FreeAndNil(FParams);
+  inherited Destroy;
+end;
+
+
+
+{ TWinBindings }
+
+procedure TWinBindings.SetDependencies(const AValue: TDependencies);
+begin
+  if (FDependencies<>AValue) then
+    FDependencies.Assign(AValue);
+end;
+
+Constructor TWinBindings.Create;
+begin
+  FDependencies:=TDependencies.Create(Self);
+end;
+
+destructor TWinBindings.Destroy;
+begin
+  FreeAndNil(FDependencies);
+  inherited Destroy;
+end;
+
+procedure TWinBindings.Assign(Source: TPersistent);
+
+Var
+  WB : TWinBindings;
+
+begin
+  if Source is TWinBindings then
+    begin
+    WB:=Source as TWinBindings;
+    GroupName:=WB.GroupName;
+    Password:=WB.PassWord;
+    UserName:=WB.UserName;
+    StartType:=WB.StartType;
+    WaitHint:=WB.WaitHint;
+    IDTag:=WB.IDTag;
+    ServiceType:=WB.ServiceType;
+    ErrorSeverity:=WB.ErrorSeverity;
+    Dependencies.Assign(WB.Dependencies);
+    ErrCode:=WB.ErrCode;
+    Win32ErrCode:=WB.Win32ErrCode;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+{ TDependency }
+
+function TDependency.GetDisplayName: string;
+begin
+  Result:=Name;
+end;
+
+procedure TDependency.Assign(Source: TPersistent);
+
+Var
+  D : TDependency;
+
+begin
+  if Source is TDependency then
+    begin
+    D:=Source as TDependency;
+    Name:=D.Name;
+    IsGroup:=D.IsGroup;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+{ TDependencies }
+
+function TDependencies.GetItem(Index: Integer): TDependency;
+begin
+  Result:=TDependency(Inherited GetItem(Index));
+end;
+
+procedure TDependencies.SetItem(Index: Integer; Value: TDependency);
+begin
+  Inherited SetItem(Index,Value);
+end;
+
+function TDependencies.GetOwner: TPersistent;
+begin
+  Result:=FOwner;
+end;
+
+constructor TDependencies.Create(AOwner: TPersistent);
+begin
+  Inherited Create(TDependency);
+  FOwner:=AOwner;
+end;
+
+{ TDaemonMapper }
+
+constructor TDaemonMapper.Create(AOwner: TComponent);
+begin
+ CreateNew(AOwner,0);
+ if (ClassType<>TDaemonMapper) and not (csDesigning in ComponentState) then
+    begin
+    if not InitInheritedComponent(Self,TDaemonMapper) then
+      raise EStreamError.CreateFmt(SErrNoSTreaming, [ClassName]);
+    end;
+end;
+
+constructor TDaemonMapper.CreateNew(AOwner: TComponent; Dummy: Integer);
+begin
+  inherited Create(AOwner);
+end;
+
+Initialization
+{$ifdef svcdebug}
+  StartLog;
+{$endif}
+  SysInitDaemonApp;
+
+Finalization
+  SysDoneDaemonApp;
+  DoneDaemonApplication;
+{$ifdef svcdebug}
+  EndLog;
+{$endif}
+end.
+

+ 1 - 0
fcl/tests/README

@@ -71,3 +71,4 @@ testzip.pp   Test for TZipper class (MVC)
 poolmm1.pp   Test for pooledmm (free) (MG)
 poolmm1.pp   Test for pooledmm (free) (MG)
 poolmm2.pp   Test for pooledmm (nonfree) (VS)
 poolmm2.pp   Test for pooledmm (nonfree) (VS)
 testweb.pp   Test for fpcgi (MVC)
 testweb.pp   Test for fpcgi (MVC)
+daemon.pp    Test for daemonapp (MVC)

+ 155 - 0
fcl/tests/daemon.pp

@@ -0,0 +1,155 @@
+program daemon;
+
+{$mode objfpc}{$H+}
+{$define usecthreads}
+{$apptype gui}
+
+uses
+  {$IFDEF UNIX}{$IFDEF UseCThreads}
+  cthreads,
+  {$ENDIF}{$ENDIF}
+  sysutils,
+  Classes
+  { add your units here }, daemonapp;
+
+Type
+
+  { TTestDaemon }
+
+  { TTestThread }
+
+  TTestThread = Class(TThread)
+    Procedure Execute; override;
+  end;
+
+  TTestDaemon = Class(TCustomDaemon)
+  Private
+    FThread : TTestThread;
+    Procedure ThreadStopped (Sender : TObject);
+  public
+    Function Start : Boolean; override;
+    Function Stop : Boolean; override;
+    Function Pause : Boolean; override;
+    Function Continue : Boolean; override;
+    Function Execute : Boolean; override;
+    Function ShutDown : Boolean; override;
+    Function Install : Boolean; override;
+    Function UnInstall: boolean; override;
+  end;
+
+{ TTestThread }
+
+procedure TTestThread.Execute;
+
+Var
+  C : Integer;
+
+begin
+  C:=0;
+  Repeat
+    Sleep(1000);
+    inc(c);
+    Application.Logger.Info(Format('Tick : %d',[C]));
+  Until Terminated;
+end;
+
+Procedure AWriteln(MSg : String; B : Boolean);
+
+begin
+  Application.Logger.Info(Msg+BoolToStr(B));
+end;
+
+{ TTestDaemon }
+
+procedure TTestDaemon.ThreadStopped(Sender: TObject);
+begin
+  FreeAndNil(FThread);
+end;
+
+function TTestDaemon.Start: Boolean;
+begin
+  Result:=inherited Start;
+  AWriteln('Daemon Start',Result);
+  FThread:=TTestThread.Create(True);
+  FThread.OnTerminate:=@ThreadStopped;
+  FThread.FreeOnTerminate:=False;
+  FThread.Resume;
+end;
+
+function TTestDaemon.Stop: Boolean;
+begin
+  Result:=inherited Stop;
+  AWriteln('Daemon Stop: ',Result);
+  FThread.Terminate;
+end;
+
+function TTestDaemon.Pause: Boolean;
+begin
+  Result:=inherited Pause;
+  AWriteln('Daemon pause: ',Result);
+  FThread.Suspend;
+end;
+
+function TTestDaemon.Continue: Boolean;
+begin
+  Result:=inherited Continue;
+  AWriteln('Daemon continue: ',Result);
+  FThread.Resume;
+end;
+
+function TTestDaemon.Execute: Boolean;
+begin
+  Result:=inherited Execute;
+  AWriteln('Daemon execute: ',Result);
+end;
+
+function TTestDaemon.ShutDown: Boolean;
+begin
+  Result:=inherited ShutDown;
+  AWriteln('Daemon Shutdown: ',Result);
+  FThread.Terminate;
+end;
+
+function TTestDaemon.Install: Boolean;
+begin
+  Result:=inherited Install;
+  AWriteln('Daemon Install: ',Result);
+end;
+
+function TTestDaemon.UnInstall: boolean;
+begin
+  Result:=inherited UnInstall;
+  AWriteln('Daemon UnInstall: ',Result);
+end;
+
+Type
+
+  { TTestDaemonMapper }
+
+  TTestDaemonMapper = Class(TCustomDaemonMapper)
+    Constructor Create(AOwner : TComponent); override;
+  end;
+
+{ TTestDaemonMapper }
+
+constructor TTestDaemonMapper.Create(AOwner: TComponent);
+
+Var
+  D : TDaemonDef;
+
+begin
+  inherited Create(AOwner);
+  D:=DaemonDefs.Add as TDaemonDef;
+  D.DisplayName:='Test daemon';
+  D.Name:='TestDaemon';
+  D.DaemonClassName:='TTestDaemon';
+  D.WinBindings.ServiceType:=stWin32;
+end;
+
+begin
+  RegisterDaemonClass(TTestDaemon);
+  RegisterDaemonMapper(TTestDaemonMapper);
+  Application.Title:='Daemon test application';
+  Application.Run;
+end.
+

+ 189 - 0
fcl/unix/daemonapp.inc

@@ -0,0 +1,189 @@
+{ System dependent service stuff }
+
+uses baseunix;
+
+{ ---------------------------------------------------------------------
+  TCustomDaemonApplication
+  ---------------------------------------------------------------------}
+Const
+  SERVICE_CONTROL_STOP                  = $00000001;
+  SERVICE_CONTROL_PAUSE                 = $00000002;
+  SERVICE_CONTROL_CONTINUE              = $00000003;
+  SERVICE_CONTROL_INTERROGATE           = $00000004;
+  SERVICE_CONTROL_SHUTDOWN              = $00000005;
+  
+function TCustomDaemonApplication.RunGUIloop(P: Pointer): integer;
+begin
+
+end;
+
+
+procedure TCustomDaemonApplication.SysInstallDaemon(Daemon: TCustomDaemon);
+begin
+
+end;
+
+procedure TCustomDaemonApplication.SysUnInstallDaemon(Daemon: TCustomDaemon);
+begin
+
+end;
+
+procedure TCustomDaemonApplication.SysStartUnInstallDaemons;
+begin
+
+end;
+
+procedure TCustomDaemonApplication.SysEndUnInstallDaemons;
+begin
+
+end;
+
+
+procedure TCustomDaemonApplication.SysStartInstallDaemons;
+begin
+
+end;
+
+procedure TCustomDaemonApplication.SysEndInstallDaemons;
+begin
+
+end;
+
+procedure TCustomDaemonApplication.SysStartRunDaemons;
+begin
+
+end;
+
+procedure TCustomDaemonApplication.SysEndRunDaemons;
+
+Var
+  I : Integer;
+  DC : TDaemonController;
+  
+begin
+  For I:=ComponentCount-1 downto 0 do
+    If Components[i] is TDaemoncontroller then
+      begin
+      DC:=Components[i] as TDaemoncontroller;
+      DC.Main(0,Nil); // Returns after starting thread.
+      end;
+  if Assigned(GUIMainLoop) then
+    GuiMainLoop
+  else
+    // Simply wait till everything terminates.
+    While Not Terminated do
+      fpPause;
+end;
+
+procedure TCustomDaemonApplication.RemoveController(
+  AController: TDaemonController);
+  
+Var
+  I : Integer;
+  HC : Boolean;
+  
+begin
+  FreeAndNil(AController.FDaemon);
+  AController.Free;
+end;
+
+
+{ ---------------------------------------------------------------------
+  TDaemonThread
+  ---------------------------------------------------------------------}
+
+procedure TDaemonThread.StartServiceExecute;
+begin
+
+end;
+
+procedure TDaemonThread.CheckControlMessage(WaitForMessage : Boolean);
+begin
+end;
+
+
+{ ---------------------------------------------------------------------
+  TDaemonController
+  ---------------------------------------------------------------------}
+
+procedure TDaemonController.StartService;
+begin
+  Main(0,Nil);
+end;
+
+procedure TDaemonController.Main(Argc: DWord; Args: PPChar);
+
+Var
+  T : TThread;
+
+begin
+  FDaemon.Status:=csStartPending;
+  Try
+    T:=TDaemonThread.Create(FDaemon);
+    T.FreeOnTerminate:=True;
+    T.Resume;
+    T.WaitFor;
+    FDaemon.FThread:=Nil;
+  except
+    On E : Exception do
+      FDaemon.Logmessage(Format(SErrDaemonStartFailed,[FDaemon.Definition.Name,E.Message]));
+  end;
+end;
+
+procedure TDaemonController.Controller(ControlCode, EventType: DWord;
+  EventData: Pointer);
+
+begin
+  // Send control code to daemon thread.
+end;
+
+function TDaemonController.ReportStatus: Boolean;
+
+Var
+  S : String;
+
+begin
+  S:='';
+  If Assigned(FDaemon) then
+    With FDaemon do
+      S:=Format(SDaemonStatus,[Definition.DisplayName,
+                               CurrentStatusNames[Status]]);
+  Application.Logger.Info(S);
+end;
+
+Procedure TDaemonController.ThreadTerminated(Sender : TObject);
+
+begin
+end;
+
+
+{ ---------------------------------------------------------------------
+  Global initialization/Finalization
+  ---------------------------------------------------------------------}
+
+Procedure DoShutDown(Sig : Longint; Info : PSigInfo; Context : PSigContext); cdecl;
+
+begin
+  Application.StopDaemons(True);
+  Application.Terminate;
+end;
+
+Procedure SysInitDaemonApp;
+
+Var
+  old,new : SigactionRec;
+
+begin
+  New.sa_handler:=@DoShutDown;
+  fpSigaction(SIGQUIT,@New,@Old);
+  fpSigaction(SIGTERM,@New,@Old);
+  fpSigaction(SIGINT,@New,@Old);
+end;
+
+
+Procedure SysDoneDaemonApp;
+
+begin
+end;
+
+

+ 617 - 0
fcl/win/daemonapp.inc

@@ -0,0 +1,617 @@
+{ Win32 implementation of service application }
+
+uses windows,jwawinsvc;
+
+Const
+  CM_SERVICE_CONTROL_CODE = WM_USER+1;
+  
+Resourcestring
+  SErrRegisterHandler  = 'Could not register control handler, error code %d';
+  SErrNoControlContext = 'Not handling Control message without control context: (%d %d %d).';
+  SControlCodeReceived = 'Control message received: (%d %d %d).';
+
+function StartServiceCtrlDispatcher(lpServiceStartTable: LPSERVICE_TABLE_ENTRY): BOOL; stdcall; external 'advapi32.dll' name 'StartServiceCtrlDispatcherA';
+function RegisterServiceCtrlHandlerEx(lpServiceName: LPCSTR;lpHandlerProc: LPHANDLER_FUNCTION_EX; lpContext: LPVOID): SERVICE_STATUS_HANDLE; stdcall;external 'advapi32.dll' name 'RegisterServiceCtrlHandlerExA';
+function SetServiceStatus(hServiceStatus: SERVICE_STATUS_HANDLE; const lpServiceStatus: SERVICE_STATUS): BOOL; stdcall; external 'advapi32.dll' name 'SetServiceStatus';
+
+{ ---------------------------------------------------------------------
+  Win32 entry points
+  ---------------------------------------------------------------------}
+
+Function ServiceControlHandlerEntry(dwControl,dwEventType: DWord; lpEventData,lpContext : Pointer) : DWord; StdCall;
+
+begin
+  If (Nil<>lpContext) then
+    TDaemonController(lpContext).Controller(dwControl,dwEventType,lpEventData)
+  else
+    If Assigned(Application.Logger) then
+      Application.Logger.Error(SerrNoControlContext,[dwControl,dwEventType,ptrint(lpEventData)]);
+end;
+
+Procedure ServiceMainEntry(Argc : DWord; Args : Pchar); stdcall;
+
+begin
+  If Assigned(Application) then
+    Application.Main(Argc,PPChar(Args));
+{$ifdef svcdebug}Debuglog('Main end');{$endif}
+end;
+
+{ ---------------------------------------------------------------------
+  TDaemonStartThread
+  ---------------------------------------------------------------------}
+Type
+  TDaemonStartThread = Class(TThread)
+    FEntryTable : PServiceTableEntry;
+    FLoopHandle : THandle;
+  Public
+    Constructor Create(T : PServiceTableEntry; LoopHandle : THandle);
+    Procedure DoTerminate; override;
+    Procedure Execute; override;
+    Property ReturnValue;
+  end;
+
+{ TDaemonStartThread }
+
+constructor TDaemonStartThread.create(T: PServiceTableEntry; LoopHandle : THandle);
+begin
+  FEntryTable:=T;
+  FLoopHandle:=LoopHandle;
+  FreeOnTerminate:=False;
+  ReturnValue:=0;
+  Inherited Create(False,DefaultStackSize*2);
+end;
+
+procedure TDaemonStartThread.DoTerminate;
+begin
+  Inherited DoTerminate;
+  If (FLoopHandle<>0) then
+    PostMessage(FLoopHandle,WM_QUIT,0,0);
+end;
+
+procedure TDaemonStartThread.Execute;
+begin
+{$ifdef svcdebug}DebugLog('Calling service dispatcher');{$endif svcdebug}
+  if StartServiceCtrlDispatcher(FEntryTable) then
+    ReturnValue:=0
+  else
+    ReturnValue:=GetLastError;
+{$ifdef svcdebug}DebugLog('Called service dispatcher');{$endif svcdebug}
+end;
+
+{ ---------------------------------------------------------------------
+  TSCMData : private data of controller.
+  ---------------------------------------------------------------------}
+
+Type
+  TSCMData = Class(TObject)
+    FHandle : SERVICE_STATUS_HANDLE;
+    Constructor Create(AHandle : SERVICE_STATUS_HANDLE);
+  end;
+
+Constructor TSCMData.Create(AHandle : SERVICE_STATUS_HANDLE);
+begin
+  FHandle:=AHandle;
+end;
+
+{ ---------------------------------------------------------------------
+  TSMData : private data of Application.
+  ---------------------------------------------------------------------}
+
+Type
+
+  { TSMData }
+
+  TSMData = Class(TObject)
+  Private
+    FHandle : SC_HANDLE;
+  Public
+    Constructor Create(AHandle : SC_HANDLE);
+    Destructor Destroy; override;
+  end;
+
+Constructor TSMData.Create(AHandle : SC_HANDLE);
+begin
+  FHandle:=AHandle;
+end;
+
+destructor TSMData.Destroy;
+begin
+  inherited;
+end;
+
+{ ---------------------------------------------------------------------
+  TCustomDaemonApplication
+  ---------------------------------------------------------------------}
+  
+const
+  WinServiceTypes : array[TServiceType] of Integer
+                  = (SERVICE_WIN32_OWN_PROCESS, SERVICE_KERNEL_DRIVER,
+                     SERVICE_FILE_SYSTEM_DRIVER);
+
+  WinStartTypes : array[TStartType] of Integer
+                = (SERVICE_BOOT_START, SERVICE_SYSTEM_START,
+                   SERVICE_AUTO_START, SERVICE_DEMAND_START,
+                   SERVICE_DISABLED);
+  WinErrorSeverities : array[TErrorSeverity] of Integer
+                     = (SERVICE_ERROR_IGNORE, SERVICE_ERROR_NORMAL,
+                        SERVICE_ERROR_SEVERE, SERVICE_ERROR_CRITICAL);
+
+// Careful, result of this function must be freed !!
+
+Function GetDependencies(D : TDependencies) : PChar;
+  
+var
+  I,L : Integer;
+  P   : PChar;
+
+begin
+  Result:=Nil;
+  L:=0;
+  for i:=0 to D.Count-1 do
+    Inc(L, Length(D[i].Name)+1+Ord(D[i].IsGroup));
+  if (L<>0) then
+    begin
+    Inc(L); // For final null-terminator;
+    GetMem(Result,L);
+    P:=Result;
+    for i:=0 to D.Count - 1 do
+      begin
+      if D[i].IsGroup then
+        begin
+        P^:=Char(SC_GROUP_IDENTIFIER);
+        Inc(P);
+        end;
+      P:=StrECopy(P,PChar(D[i].Name));
+      Inc(P);
+      end;
+    P^:=#0;
+    end;
+end;
+
+Procedure TCustomDaemonApplication.SysInstallDaemon(Daemon : TCustomDaemon);
+
+Var
+  SM,SV: SC_HANDLE;
+  N,DN,E,LG,UN,UP : String;
+  DD : TDaemonDef;
+  ST,STT,ES: Integer;
+  IDTag : DWord;
+  PIDTag : LPDWord;
+  PDeps,PN,PP : PChar;
+  
+begin
+  SM:=TSMData(FSysData).FHandle;
+  DD:=Daemon.Definition;
+  E:=Paramstr(0);
+  If (Pos(' ',E)<>0) then
+    E:='"'+E+'"';
+  E:=E+' --run'; // Add --run argument;
+  N:=DD.Name;
+  DN:=DD.DisplayName;
+  With DD.WinBindings do
+    begin
+    LG:=GroupName;
+    UN:=UserName;
+    If (UN='') then
+      PN:=Nil
+    else
+      PN:=PChar(UN);
+    UP:=Password;
+    if (UP='') then
+      PP:=Nil
+    else
+      PP:=PChar(UP);
+    // ServiceType ST
+      ST:=WinServiceTypes[ServiceType];
+    if (doInteractive in DD.Options) and (ServiceType=stWin32)  then
+      ST:=ST or SERVICE_INTERACTIVE_PROCESS;
+    // Actually, we should check count of enabled definitons only :/
+    if (ServiceType=stWin32) and (FMapper.DaemonDefs.Count>1) then
+      ST:=(ST xor SERVICE_WIN32_OWN_PROCESS) or SERVICE_WIN32_SHARE_PROCESS;
+    // StartType STT
+    STT:=WinStartTypes[StartType];
+    if (StartType in [stBoot, stSystem]) and (ServiceType<>stDevice) then
+      STT:=SERVICE_AUTO_START;
+    IDTag:=IDTag;
+    If (IDTag<>0) then
+      PIDTag:=@IDTag
+    else
+      PIDTag:=Nil;
+    ES:=WinErrorSeverities[ErrorSeverity];
+    PDeps:=GetDependencies(Dependencies);
+    end;
+  Try
+    flush(output);
+    SV:=CreateService(SM, PChar(N), PChar(DN), SERVICE_ALL_ACCESS, ST, STT, ES,
+                      PChar(E), PChar(LG), PIDTag, PDeps, PN, PP);
+    If (SV=0) then
+      RaiseLastOSError;
+    Try
+      If (PIDTag<>Nil) then
+        DD.WinBindings.IDTag:=IDTag;
+    finally
+      CloseServiceHandle(SV);
+    end
+  Finally
+    If (PDeps<>Nil) then
+      FreeMem(PDeps);
+  end;
+end;
+
+procedure TCustomDaemonApplication.SysUnInstallDaemon(Daemon: TCustomDaemon);
+
+var
+  SM,SV : SC_HANDLE;
+  DN : String;
+begin
+  SM:=TSMData(FSysData).FHandle;
+  DN:=Daemon.Definition.Name;
+  SV:=OpenService(SM,PChar(DN),SERVICE_ALL_ACCESS);
+  if (SV=0) then
+    RaiseLastOSError;
+  try
+    if Not DeleteService(SV) then
+      RaiseLastOSError;
+  finally
+    CloseServiceHandle(SV);
+  end;
+end;
+
+procedure TCustomDaemonApplication.SysStartUnInstallDaemons;
+Var
+  SM : SC_HANDLE;
+
+begin
+  SM:=OpenSCManager(Nil,Nil,SC_MANAGER_ALL_ACCESS);
+  if (SM=0) then
+    RaiseLastOSError;
+  FSysData:=TSMData.Create(SM);
+end;
+
+procedure TCustomDaemonApplication.SysEndUnInstallDaemons;
+begin
+  CloseServiceHandle(TSMData(FSysData).FHandle);
+  FreeandNil(FSysData);
+end;
+
+
+procedure TCustomDaemonApplication.SysStartInstallDaemons;
+
+Var
+  SM : SC_HANDLE;
+  
+begin
+  SM:=OpenSCManager(Nil,Nil,SC_MANAGER_ALL_ACCESS);
+  if (SM=0) then
+    RaiseLastOSError;
+  FSysData:=TSMData.Create(SM);
+end;
+
+procedure TCustomDaemonApplication.SysEndInstallDaemons;
+begin
+  CloseServiceHandle(TSMData(FSysData).FHandle);
+  FreeandNil(FSysData);
+end;
+
+procedure TCustomDaemonApplication.SysStartRunDaemons;
+begin
+  // Do nothing.
+end;
+
+Function TCustomDaemonApplication.RunGUIloop(P : Pointer) : integer;
+
+Const
+  HandleOpts=WS_POPUP or WS_CAPTION or WS_CLIPSIBLINGS or WS_SYSMENU or WS_MINIMIZEBOX;
+
+Var
+  T : TDaemonStartThread;
+  Msg : TMsg;
+  TClass: TWndClass;
+  AWClass: TWndClass = (
+    style: 0;
+    lpfnWndProc: @DefWindowProc;
+    cbClsExtra: 0;
+    cbWndExtra: 0;
+    hInstance: 0;
+    hIcon: 0;
+    hCursor: 0;
+    hbrBackground: 0;
+    lpszMenuName: nil;
+    lpszClassName: 'TDaemonApplication');
+
+begin
+  If (GUIHandle=0) then
+    begin
+    if not GetClassInfo(HInstance,AWClass.lpszClassName,TClass) then
+      begin
+      AWClass.hInstance := HInstance;
+      if Windows.RegisterClass(AWClass) = 0 then
+        DaemonError(SErrWindowClass);
+      end;
+    GUIHandle := CreateWindow(AWClass.lpszClassName, Pchar(Title),
+      HandleOpts, 1,1, 0, 0, 0, 0, HInstance, nil);
+    end;
+  T:=TDaemonStartThread.Create(P,GUIHandle);
+  Try
+    If Assigned(GuiMainLoop) then
+       GUIMainLoop
+    else
+      begin
+      // Run a message loop.
+      Msg.Message:=0;
+      Repeat
+        if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
+          begin
+          if (Msg.Message<>WM_QUIT) and (Msg.Message<>WM_ENDSESSION) then
+            begin
+            TranslateMessage(Msg);
+            DispatchMessage(Msg);
+            end
+          else
+            Terminate;
+          end;
+      Until Terminated;
+      end;
+  finally
+    Result:=T.ReturnValue;
+    T.Free;
+  end;
+end;
+
+procedure TCustomDaemonApplication.SysEndRunDaemons;
+
+Var
+  P : PServiceTableEntry;
+  I,C : Integer;
+  RV : Integer;
+  
+begin
+  GetMem(P,SizeOf(TServiceTableEntry)*FMapper.DaemonDefs.Count+1);
+  Try
+    C:=FMapper.DaemonDefs.Count;
+    For I:=0 to C-1 do
+      If Assigned(FMapper.DaemonDefs[i].Instance) then
+        begin
+        P[i].lpServiceName:=Pchar(FMapper.DaemonDefs[i].Name);
+        P[i].lpServiceProc:=@ServiceMainEntry;
+        end;
+    // Set last entry to Nil.
+    P[C].lpServiceName:=Nil;
+    P[C].lpServiceProc:=Nil;
+    If IsConsole then
+      begin
+       {$ifdef svcdebug}DebugLog('Starting ctrl dispatcher');{$endif svcdebug}
+      Try
+        if StartServiceCtrlDispatcher(P) then
+          begin
+          {$ifdef svcdebug}DebugLog('Return of dispatcher OK');{$endif svcdebug}
+          RV:=0;
+          end
+        else
+          begin
+          RV:=GetLastError;
+          {$ifdef svcdebug}DebugLog('Return of dispatcher NOK');{$endif svcdebug}
+          end;
+      except
+        On E : Exception do
+          begin
+          {$ifdef svcdebug}Debuglog('Caught exception : '+E.MEssage);{$endif svcdebug}
+          Raise;
+          end;
+      end;
+      end
+    else
+      begin
+      RV:=RunGuiLoop(P);
+      end;
+    {$ifdef svcdebug}DebugLog('SysRun Terminating');{$endif svcdebug}
+    Terminate;
+    If (RV<>0) then
+      Logger.Error(SErrServiceManagerStartFailed,[SysErrorMessage(RV)]);
+  Finally
+    FreeMem(P);
+  end;
+end;
+
+procedure TCustomDaemonApplication.RemoveController(
+  AController: TDaemonController);
+  
+begin
+  FreeAndNil(AController.FDaemon);
+  AController.Free;
+end;
+
+{ ---------------------------------------------------------------------
+  TDaemonThread
+  ---------------------------------------------------------------------}
+
+procedure TDaemonThread.StartServiceExecute;
+
+Var
+  Msg : TMsg;
+  
+begin
+  PeekMessage(Msg,0,WM_USER,WM_USER,PM_NOREMOVE);
+end;
+
+
+procedure TDaemonThread.CheckControlMessage(WaitForMessage : Boolean);
+
+Var
+  Msg : TMsg;
+  StopLoop : Boolean;
+
+begin
+  StopLoop:=False;
+  Repeat
+    StopLoop:=Terminated and WaitForMessage;
+    If Not StopLoop then
+      begin
+      If WaitForMessage Then
+        StopLoop:=Not GetMessage(Msg,0,0,0)
+      else
+        StopLoop:=Not PeekMessage(Msg,0,0,0,PM_REMOVE);
+      If Not StopLoop then
+        begin
+        If (Msg.hwnd<>0) or (Msg.Message<>CM_SERVICE_CONTROL_CODE) then
+          DispatchMessage(Msg)
+        else
+          HandleControlCode(Msg.wParam);
+        end;
+      end;
+  Until StopLoop;
+end;
+
+{ ---------------------------------------------------------------------
+  TDaemonController
+  ---------------------------------------------------------------------}
+
+procedure TDaemonController.StartService;
+begin
+  Main(0,Nil);
+end;
+
+
+procedure TDaemonController.Main(Argc: DWord; Args: PPChar);
+
+Var
+  T : TThread;
+  H : SERVICE_STATUS_HANDLE;
+  I : Integer;
+  
+begin
+  For I:=0 to Argc-1 do
+    FParams.Add(StrPas(Args[I]));
+  H:=RegisterServiceCtrlHandlerEx(Args[0],@ServiceControlHandlerEntry,Self);
+  if (H=0) then
+    Application.Logger.Error(SErrRegisterHandler,[getlasterror]);
+  FSysData:=TSCMData.Create(H);
+  FDaemon.Status:=csStartPending;
+  Try
+    T:=TDaemonThread.Create(FDaemon);
+    T.Resume;
+    T.WaitFor;
+    FreeAndNil(T);
+    FDaemon.FThread:=Nil;
+  except
+    On E : Exception do
+      FDaemon.Logmessage(Format(SErrDaemonStartFailed,[FDaemon.Definition.Name,E.Message]));
+  end;
+end;
+
+procedure TDaemonController.Controller(ControlCode, EventType: DWord;
+  EventData: Pointer);
+
+Var
+  TID : THandle;
+
+begin
+  if Assigned(FDaemon.FThread) then
+    begin
+    TID:=FDaemon.FThread.ThreadID;
+    If FDaemon.FThread.Suspended then
+      FDaemon.FThread.Resume;
+    PostThreadMessage(TID,CM_SERVICE_CONTROL_CODE,ControlCode,EventType);
+    end;
+end;
+
+
+function TDaemonController.ReportStatus: Boolean;
+
+  Function GetAcceptedCodes : Integer;
+
+  begin
+    Result := SERVICE_ACCEPT_SHUTDOWN;
+    if doAllowStop in FDAemon.Definition.Options then
+      Result := Result or SERVICE_ACCEPT_STOP;
+    if doAllowPause in FDAemon.Definition.Options then
+      Result := Result or SERVICE_ACCEPT_PAUSE_CONTINUE;
+  end;
+
+Var
+  S : String;
+  DD : TDaemonDef;
+  SS : TServiceStatus;
+  WB : TWinBindings;
+
+Const
+  WinServiceStatus : array[TCurrentStatus] of Integer
+                   = (SERVICE_STOPPED, SERVICE_START_PENDING,
+                      SERVICE_STOP_PENDING, SERVICE_RUNNING,
+                      SERVICE_CONTINUE_PENDING, SERVICE_PAUSE_PENDING,
+                      SERVICE_PAUSED);
+  PendingStatus : set of TCurrentStatus
+                = [csStartPending, csStopPending,
+                   csContinuePending,csPausePending];
+
+begin
+  If not Assigned(FDaemon) then
+    begin
+    Application.Logger.Error(SErrNoDaemonForStatus,[Name]);
+    Exit;
+    end;
+  DD:=FDaemon.Definition;
+  If not Assigned(DD) then
+    begin
+    Application.Logger.Error(SErrNoDaemonDefForStatus,[Name]);
+    Exit;
+    end;
+  DD.LogStatusReport:=True;
+{$ifndef svcdebug}
+  If DD.LogStatusReport then
+{$endif svcdebug}
+    With FDaemon do
+      begin
+      S:=Format(SDaemonStatus,[Definition.DisplayName, CurrentStatusNames[Status]]);
+      Application.Logger.Info(S);
+      {$ifdef svcdebug}DebugLog(S);{$endif svcdebug}
+      end;
+  FillChar(SS,SizeOf(SS),0);
+  WB:=DD.WinBindings;
+  with SS do
+    begin
+    dwWaitHint := WB.WaitHint;
+    dwServiceType :=WinServiceTypes[WB.ServiceType];
+    if (FDaemon.Status=csStartPending) then
+      dwControlsAccepted := 0
+    else
+      dwControlsAccepted := GetAcceptedCodes;
+    if (FDaemon.Status in PendingStatus) and (FDaemon.Status = LastStatus) then
+      Inc(FCheckPoint)
+    else
+      FCheckPoint := 0;
+    dwCheckPoint:=FCheckPoint;
+    FLastStatus := FDaemon.Status;
+    dwCurrentState := WinServiceStatus[FDaemon.Status];
+    dwServiceSpecificExitCode:=WB.ErrCode;
+    if (WB.ErrCode<>0) then
+      dwWin32ExitCode:=ERROR_SERVICE_SPECIFIC_ERROR
+    else
+      dwWin32ExitCode := WB.Win32ErrCode;
+    if not SetServiceStatus(TSCMData(FSysData).FHandle, SS) then
+      Application.Logger.Error(SysErrorMessage(GetLastError));
+  end;
+end;
+
+Procedure TDaemonController.ThreadTerminated(Sender : TObject);
+
+begin
+end;
+
+
+{ ---------------------------------------------------------------------
+  Global initialization/Finalization
+  ---------------------------------------------------------------------}
+
+Procedure SysInitDaemonApp;
+
+begin
+end;
+
+
+Procedure SysDoneDaemonApp;
+
+begin
+end;
+
+

Some files were not shown because too many files changed in this diff