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 System.ServiceProcess;
16 using System.Threading;
17 using System.Runtime.InteropServices;
19 class MonoServiceRunner : MarshalByRefObject
21 string assembly, name, logname;
23 static void info (string prefix, string format, params object [] args)
25 Syscall.syslog (SyslogLevel.LOG_INFO, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
28 static void error (string prefix, string format, params object [] args)
30 Syscall.syslog (SyslogLevel.LOG_ERR, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
35 Console.Error.WriteLine (
37 "mono-service [-d:DIRECTORY] [-l:LOCKFILE] [-n:NAME] [-m:LOGNAME] service.exe\n");
41 delegate void sighandler_t (int arg);
43 AutoResetEvent signal_event;
46 extern static int signal (int signum, sighandler_t handler);
50 void my_handler (int sig)
56 static void call (object o, string method, object [] arg)
58 MethodInfo m = o.GetType ().GetMethod (method, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
60 m.Invoke (o, new object [1] { arg });
65 static int Main (string [] args)
67 string assembly = null;
68 string directory = null;
69 string lockfile = null;
71 string logname = null;
73 foreach (string s in args){
74 if (s.Length > 3 && s [0] == '-' && s [2] == ':'){
75 string arg = s.Substring (3);
77 switch (Char.ToLower (s [1])){
78 case 'd': directory = arg; break;
79 case 'l': lockfile = arg; break;
80 case 'n': name = arg; break;
81 case 'm': logname = arg; break;
82 default: Usage (); break;
95 if (assembly == null){
96 error (logname, "Assembly name is missing");
100 if (directory != null){
101 if (Syscall.chdir (directory) != 0){
102 error (logname, "Could not change to directory {0}", directory);
107 // Use lockfile to allow only one instance
108 if (lockfile == null)
109 lockfile = String.Format ("/tmp/{0}.lock", Path.GetFileName (assembly));
111 int lfp = Syscall.open (lockfile, OpenFlags.O_RDWR|OpenFlags.O_CREAT,
112 FilePermissions.S_IRUSR|FilePermissions.S_IWUSR|FilePermissions.S_IRGRP);
115 error (logname, "Cannot open lock file.");
119 if (Syscall.lockf(lfp, LockfCommand.F_TLOCK,0)<0) {
120 info (logname, "Daemon is already running.");
124 // Write pid to lock file
125 string pid = Syscall.getpid ().ToString () + Environment.NewLine;
126 IntPtr buf = Marshal.StringToCoTaskMemAnsi (pid);
127 Syscall.write (lfp, buf, (ulong)pid.Length);
128 Marshal.FreeCoTaskMem (buf);
130 // Create new AppDomain to run service
131 AppDomainSetup setup = new AppDomainSetup ();
132 setup.ApplicationBase = Environment.CurrentDirectory;
133 setup.ConfigurationFile = Path.Combine (Environment.CurrentDirectory, assembly + ".config");
134 setup.ApplicationName = logname;
136 AppDomain newDomain = AppDomain.CreateDomain (logname, AppDomain.CurrentDomain.Evidence, setup);
137 MonoServiceRunner rnr = newDomain.CreateInstanceAndUnwrap(
\r
138 typeof (MonoServiceRunner).Assembly.FullName,
\r
139 typeof (MonoServiceRunner).FullName,
\r
141 BindingFlags.Default,
\r
143 new object [] {assembly, name, logname},
\r
144 null, null, null) as MonoServiceRunner;
147 error (logname, "Internal Mono Error: Could not create MonoServiceRunner.");
151 return rnr.StartService ();
154 public MonoServiceRunner (string assembly, string name, string logname)
156 this.assembly = assembly;
158 this.logname = logname;
161 public int StartService ()
167 signal_event = new AutoResetEvent (false);
169 // Invoke all the code used in the signal handler, so the JIT does
170 // not kick-in inside the signal handler
172 signal_event.Reset ();
175 signal (UnixConvert.FromSignum (Signum.SIGTERM), new sighandler_t (my_handler));
176 signal (UnixConvert.FromSignum (Signum.SIGUSR1), new sighandler_t (my_handler));
177 signal (UnixConvert.FromSignum (Signum.SIGUSR2), new sighandler_t (my_handler));
179 // Load service assembly
183 a = Assembly.LoadFrom (assembly);
184 } catch (FileNotFoundException) {
185 error (logname, "Could not find assembly {0}", assembly);
187 } catch (BadImageFormatException){
188 error (logname, "File {0} is not a valid assembly", assembly);
193 error (logname, "Could not load assembly {0}", assembly);
197 // Hook up RunService callback
198 Type cbType = Type.GetType ("System.ServiceProcess.ServiceBase+RunServiceCallback, System.ServiceProcess");
200 error (logname, "Internal Mono Error: Could not find RunServiceCallback in ServiceBase");
204 FieldInfo fi = typeof (ServiceBase).GetField ("RunService", BindingFlags.Static | BindingFlags.NonPublic);
206 error (logname, "Internal Mono Error: Could not find RunService in ServiceBase");
209 fi.SetValue (null, Delegate.CreateDelegate(cbType, this, "MainLoop"));
211 // And run its Main. Our RunService handler is invoked from
213 MethodInfo entry = a.EntryPoint;
215 error (logname, "Entry point not defined in service");
219 string [] service_args = new string [0];
220 entry.Invoke (null, service_args);
224 } catch ( Exception ex ) {
225 for (Exception e = ex; e != null; e = e.InnerException)
226 error (logname, e.Message);
232 // The main service loop
233 private void MainLoop (ServiceBase [] services)
238 if (services == null || services.Length == 0){
239 error (logname, "No services were registered by this service");
243 // Start up the service.
247 foreach (ServiceBase svc in services){
248 if (svc.ServiceName == name){
254 service = services [0];
257 call (service, "OnStart", new string [0]);
258 info (logname, "Service {0} started", service.ServiceName);
260 for (bool running = true; running; ){
261 signal_event.WaitOne ();
264 if (UnixConvert.TryToSignum (signum, out v)){
269 if (service.CanStop) {
270 info (logname, "Stopping service {0}", service.ServiceName);
271 call (service, "OnStop", null);
276 if (service.CanPauseAndContinue) {
277 info (logname, "Pausing service {0}", service.ServiceName);
278 call (service, "OnPause", null);
282 if (service.CanPauseAndContinue) {
283 info (logname, "Continuing service {0}", service.ServiceName);
284 call (service, "OnContinue", null);
292 foreach (ServiceBase svc in services){