2 * monod.cs: Mono daemon for running services based on System.ServiceProcess
5 * Joerg Rosenkranz (joergr@voelcker.com)
6 * Miguel de Icaza (miguel@novell.com)
8 * (C) 2005 Voelcker Informatik AG
13 using System
.Reflection
;
15 using Mono
.Unix
.Native
;
16 using System
.ServiceProcess
;
17 using System
.Threading
;
18 using System
.Runtime
.InteropServices
;
20 class MonoServiceRunner
: MarshalByRefObject
22 string assembly
, name
, logname
;
24 static void info (string prefix
, string format
, params object [] args
)
26 Syscall
.syslog (SyslogLevel
.LOG_INFO
, String
.Format ("{0}: {1}", prefix
, String
.Format (format
, args
)));
29 static void error (string prefix
, string format
, params object [] args
)
31 Syscall
.syslog (SyslogLevel
.LOG_ERR
, String
.Format ("{0}: {1}", prefix
, String
.Format (format
, args
)));
36 Console
.Error
.WriteLine (
38 "mono-service [-d:DIRECTORY] [-l:LOCKFILE] [-n:NAME] [-m:LOGNAME] service.exe\n");
42 static void call (object o
, string method
, object [] arg
)
44 MethodInfo m
= o
.GetType ().GetMethod (method
, BindingFlags
.Instance
| BindingFlags
.NonPublic
| BindingFlags
.Public
);
46 m
.Invoke (o
, new object [1] { arg }
);
51 static int Main (string [] args
)
53 string assembly
= null;
54 string directory
= null;
55 string lockfile
= null;
57 string logname
= null;
59 foreach (string s
in args
){
60 if (s
.Length
> 3 && s
[0] == '-' && s
[2] == ':'){
61 string arg
= s
.Substring (3);
63 switch (Char
.ToLower (s
[1])){
64 case 'd': directory
= arg
; break;
65 case 'l': lockfile
= arg
; break;
66 case 'n': name
= arg
; break;
67 case 'm': logname
= arg
; break;
68 default: Usage (); break;
81 if (assembly
== null){
82 error (logname
, "Assembly name is missing");
86 if (directory
!= null){
87 if (Syscall
.chdir (directory
) != 0){
88 error (logname
, "Could not change to directory {0}", directory
);
93 // Use lockfile to allow only one instance
95 lockfile
= String
.Format ("/tmp/{0}.lock", Path
.GetFileName (assembly
));
97 int lfp
= Syscall
.open (lockfile
, OpenFlags
.O_RDWR
|OpenFlags
.O_CREAT
|OpenFlags
.O_EXCL
,
98 FilePermissions
.S_IRUSR
|FilePermissions
.S_IWUSR
|FilePermissions
.S_IRGRP
);
101 // Provide some useful info
102 if (File
.Exists (lockfile
))
103 error (logname
, String
.Format ("Lock file already exists: {0}", lockfile
));
105 error (logname
, String
.Format ("Cannot open/create lock file exclusively: {0}", lockfile
));
109 if (Syscall
.lockf(lfp
, LockfCommand
.F_TLOCK
,0)<0) {
110 info (logname
, "Daemon is already running.");
115 // Write pid to lock file
116 string pid
= Syscall
.getpid ().ToString () + Environment
.NewLine
;
117 IntPtr buf
= Marshal
.StringToCoTaskMemAnsi (pid
);
118 Syscall
.write (lfp
, buf
, (ulong)pid
.Length
);
119 Marshal
.FreeCoTaskMem (buf
);
121 // Create new AppDomain to run service
122 AppDomainSetup setup
= new AppDomainSetup ();
123 setup
.ApplicationBase
= Environment
.CurrentDirectory
;
124 setup
.ConfigurationFile
= Path
.Combine (Environment
.CurrentDirectory
, assembly
+ ".config");
125 setup
.ApplicationName
= logname
;
127 AppDomain newDomain
= AppDomain
.CreateDomain (logname
, AppDomain
.CurrentDomain
.Evidence
, setup
);
128 MonoServiceRunner rnr
= newDomain
.CreateInstanceAndUnwrap(
129 typeof (MonoServiceRunner
).Assembly
.FullName
,
130 typeof (MonoServiceRunner
).FullName
,
132 BindingFlags
.Default
,
134 new object [] {assembly, name, logname}
,
135 null, null, null) as MonoServiceRunner
;
138 error (logname
, "Internal Mono Error: Could not create MonoServiceRunner.");
142 return rnr
.StartService ();
144 // Remove lock file when done
145 if (File
.Exists(lockfile
))
146 File
.Delete (lockfile
);
150 public MonoServiceRunner (string assembly
, string name
, string logname
)
152 this.assembly
= assembly
;
154 this.logname
= logname
;
157 public int StartService ()
160 // Load service assembly
164 a
= Assembly
.LoadFrom (assembly
);
165 } catch (FileNotFoundException
) {
166 error (logname
, "Could not find assembly {0}", assembly
);
168 } catch (BadImageFormatException
){
169 error (logname
, "File {0} is not a valid assembly", assembly
);
174 error (logname
, "Could not load assembly {0}", assembly
);
178 if (a
.EntryPoint
== null){
179 error (logname
, "Entry point not defined in service");
183 // Hook up RunService callback
184 Type cbType
= Type
.GetType ("System.ServiceProcess.ServiceBase+RunServiceCallback, System.ServiceProcess");
186 error (logname
, "Internal Mono Error: Could not find RunServiceCallback in ServiceBase");
190 FieldInfo fi
= typeof (ServiceBase
).GetField ("RunService", BindingFlags
.Static
| BindingFlags
.NonPublic
);
192 error (logname
, "Internal Mono Error: Could not find RunService in ServiceBase");
195 fi
.SetValue (null, Delegate
.CreateDelegate(cbType
, this, "MainLoop"));
197 // And run its Main. Our RunService handler is invoked from
199 return AppDomain
.CurrentDomain
.ExecuteAssembly (assembly
, AppDomain
.CurrentDomain
.Evidence
);
201 } catch ( Exception ex
) {
202 for (Exception e
= ex
; e
!= null; e
= e
.InnerException
) {
203 error (logname
, e
.Message
);
210 // The main service loop
211 private void MainLoop (ServiceBase
[] services
)
216 if (services
== null || services
.Length
== 0){
217 error (logname
, "No services were registered by this service");
221 // Start up the service.
225 foreach (ServiceBase svc
in services
){
226 if (svc
.ServiceName
== name
){
232 service
= services
[0];
235 call (service
, "OnStart", new string [0]);
236 info (logname
, "Service {0} started", service
.ServiceName
);
238 UnixSignal intr
= new UnixSignal (Signum
.SIGINT
);
239 UnixSignal term
= new UnixSignal (Signum
.SIGTERM
);
240 UnixSignal usr1
= new UnixSignal (Signum
.SIGUSR1
);
241 UnixSignal usr2
= new UnixSignal (Signum
.SIGUSR2
);
243 UnixSignal
[] sigs
= new UnixSignal
[]{
250 for (bool running
= true; running
; ){
251 int idx
= UnixSignal
.WaitAny (sigs
);
252 if (idx
< 0 || idx
>= sigs
.Length
)
254 if ((intr
.IsSet
|| term
.IsSet
) && service
.CanStop
) {
257 info (logname
, "Stopping service {0}", service
.ServiceName
);
258 call (service
, "OnStop", null);
261 else if (usr1
.IsSet
&& service
.CanPauseAndContinue
) {
263 info (logname
, "Pausing service {0}", service
.ServiceName
);
264 call (service
, "OnPause", null);
266 else if (usr2
.IsSet
&& service
.CanPauseAndContinue
) {
268 info (logname
, "Continuing service {0}", service
.ServiceName
);
269 call (service
, "OnContinue", null);
274 foreach (ServiceBase svc
in services
){