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
12 using System.Collections.Generic;
14 using System.Reflection;
16 using Mono.Unix.Native;
17 using System.ServiceProcess;
18 using System.Threading;
19 using System.Runtime.InteropServices;
21 class MonoServiceRunner : MarshalByRefObject
23 string assembly, name, logname;
26 static void info (string prefix, string format, params object [] args)
28 Syscall.syslog (SyslogLevel.LOG_INFO, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
31 static void error (string prefix, string format, params object [] args)
33 Syscall.syslog (SyslogLevel.LOG_ERR, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
38 Console.Error.WriteLine (
40 "mono-service [-d:DIRECTORY] [-l:LOCKFILE] [-n:NAME] [-m:LOGNAME] service.exe\n");
44 static void call (object o, string method, object [] arg)
46 MethodInfo m = o.GetType ().GetMethod (method, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
48 m.Invoke (o, new object [1] { arg });
53 static int Main (string [] args)
55 string assembly = null;
56 string directory = null;
57 string lockfile = null;
59 string logname = null;
60 var assebmlyArgs = new List<string>();
62 foreach (string s in args){
63 if (s.Length > 3 && s [0] == '-' && s [2] == ':'){
64 string arg = s.Substring (3);
66 switch (Char.ToLower (s [1])){
67 case 'd': directory = arg; break;
68 case 'l': lockfile = arg; break;
69 case 'n': name = arg; break;
70 case 'm': logname = arg; break;
71 default: Usage (); break;
88 if (assembly == null){
89 error (logname, "Assembly name is missing");
93 if (directory != null){
94 if (Syscall.chdir (directory) != 0){
95 error (logname, "Could not change to directory {0}", directory);
100 // Use lockfile to allow only one instance
101 if (lockfile == null)
102 lockfile = String.Format ("/tmp/{0}.lock", Path.GetFileName (assembly));
104 int lfp = Syscall.open (lockfile, OpenFlags.O_RDWR|OpenFlags.O_CREAT|OpenFlags.O_EXCL,
105 FilePermissions.S_IRUSR|FilePermissions.S_IWUSR|FilePermissions.S_IRGRP);
108 // Provide some useful info
109 if (File.Exists (lockfile))
110 error (logname, String.Format ("Lock file already exists: {0}", lockfile));
112 error (logname, String.Format ("Cannot open/create lock file exclusively: {0}", lockfile));
116 if (Syscall.lockf(lfp, LockfCommand.F_TLOCK,0)<0) {
117 info (logname, "Daemon is already running.");
122 // Write pid to lock file
123 string pid = Syscall.getpid ().ToString () + Environment.NewLine;
124 IntPtr buf = Marshal.StringToCoTaskMemAnsi (pid);
125 Syscall.write (lfp, buf, (ulong)pid.Length);
126 Marshal.FreeCoTaskMem (buf);
128 // Create new AppDomain to run service
129 AppDomainSetup setup = new AppDomainSetup ();
130 setup.ApplicationBase = Environment.CurrentDirectory;
131 setup.ConfigurationFile = Path.Combine (Environment.CurrentDirectory, assembly + ".config");
132 setup.ApplicationName = logname;
134 AppDomain newDomain = AppDomain.CreateDomain (logname, AppDomain.CurrentDomain.Evidence, setup);
135 MonoServiceRunner rnr = newDomain.CreateInstanceAndUnwrap(
136 typeof (MonoServiceRunner).Assembly.FullName,
137 typeof (MonoServiceRunner).FullName,
139 BindingFlags.Default,
141 new object [] {assembly, name, logname, assebmlyArgs.ToArray()},
142 null, null, null) as MonoServiceRunner;
145 error (logname, "Internal Mono Error: Could not create MonoServiceRunner.");
149 return rnr.StartService ();
151 // Remove lock file when done
152 if (File.Exists(lockfile))
153 File.Delete (lockfile);
157 public MonoServiceRunner (string assembly, string name, string logname, string[] args)
159 this.assembly = assembly;
161 this.logname = logname;
165 public int StartService ()
168 // Load service assembly
172 a = Assembly.LoadFrom (assembly);
173 } catch (FileNotFoundException) {
174 error (logname, "Could not find assembly {0}", assembly);
176 } catch (BadImageFormatException){
177 error (logname, "File {0} is not a valid assembly", assembly);
182 error (logname, "Could not load assembly {0}", assembly);
186 if (a.EntryPoint == null){
187 error (logname, "Entry point not defined in service");
191 // Hook up RunService callback
192 Type cbType = Type.GetType ("System.ServiceProcess.ServiceBase+RunServiceCallback, System.ServiceProcess");
194 error (logname, "Internal Mono Error: Could not find RunServiceCallback in ServiceBase");
198 FieldInfo fi = typeof (ServiceBase).GetField ("RunService", BindingFlags.Static | BindingFlags.NonPublic);
200 error (logname, "Internal Mono Error: Could not find RunService in ServiceBase");
203 fi.SetValue (null, Delegate.CreateDelegate(cbType, this, "MainLoop"));
205 // And run its Main. Our RunService handler is invoked from
207 return AppDomain.CurrentDomain.ExecuteAssembly (assembly, AppDomain.CurrentDomain.Evidence, args);
209 } catch ( Exception ex ) {
210 for (Exception e = ex; e != null; e = e.InnerException) {
211 error (logname, e.Message);
218 // The main service loop
219 private void MainLoop (ServiceBase [] services)
224 if (services == null || services.Length == 0){
225 error (logname, "No services were registered by this service");
229 // Start up the service.
233 foreach (ServiceBase svc in services){
234 if (svc.ServiceName == name){
240 service = services [0];
243 if (service.ExitCode != 0) {
244 //likely due to a previous execution, so we need to reset it to default
245 service.ExitCode = 0;
247 call (service, "OnStart", args);
248 info (logname, "Service {0} started", service.ServiceName);
250 UnixSignal intr = new UnixSignal (Signum.SIGINT);
251 UnixSignal term = new UnixSignal (Signum.SIGTERM);
252 UnixSignal usr1 = new UnixSignal (Signum.SIGUSR1);
253 UnixSignal usr2 = new UnixSignal (Signum.SIGUSR2);
255 UnixSignal[] sigs = new UnixSignal[]{
262 for (bool running = true; running; ){
263 int idx = UnixSignal.WaitAny (sigs);
264 if (idx < 0 || idx >= sigs.Length)
266 if ((intr.IsSet || term.IsSet) && service.CanStop) {
269 info (logname, "Stopping service {0}", service.ServiceName);
270 call (service, "OnStop", null);
271 if (service.ExitCode != 0)
272 error (logname, "Service stopped with a non-zero ExitCode: {0}", service.ExitCode);
275 else if (usr1.IsSet && service.CanPauseAndContinue) {
277 info (logname, "Pausing service {0}", service.ServiceName);
278 call (service, "OnPause", null);
280 else if (usr2.IsSet && service.CanPauseAndContinue) {
282 info (logname, "Continuing service {0}", service.ServiceName);
283 call (service, "OnContinue", null);
288 foreach (ServiceBase svc in services){