2010-04-07 Jb Evain <jbevain@novell.com>
[mcs.git] / tools / mono-service / mono-service.cs
bloba59e4d730c9c95ff517fdafd4695203d4b68fb3c
1 /*
2 * monod.cs: Mono daemon for running services based on System.ServiceProcess
4 * Author:
5 * Joerg Rosenkranz (joergr@voelcker.com)
6 * Miguel de Icaza (miguel@novell.com)
8 * (C) 2005 Voelcker Informatik AG
9 * (C) 2005 Novell Inc
11 using System;
12 using System.IO;
13 using System.Reflection;
14 using Mono.Unix;
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)));
34 static void Usage ()
36 Console.Error.WriteLine (
37 "Usage is:\n" +
38 "mono-service [-d:DIRECTORY] [-l:LOCKFILE] [-n:NAME] [-m:LOGNAME] service.exe\n");
39 Environment.Exit (1);
42 static void call (object o, string method, object [] arg)
44 MethodInfo m = o.GetType ().GetMethod (method, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
45 if (arg != null)
46 m.Invoke (o, new object [1] { arg });
47 else
48 m.Invoke (o, null);
51 static int Main (string [] args)
53 string assembly = null;
54 string directory = null;
55 string lockfile = null;
56 string name = 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;
70 } else {
71 if (assembly != null)
72 Usage ();
74 assembly = s;
78 if (logname == null)
79 logname = assembly;
81 if (assembly == null){
82 error (logname, "Assembly name is missing");
83 Usage ();
86 if (directory != null){
87 if (Syscall.chdir (directory) != 0){
88 error (logname, "Could not change to directory {0}", directory);
89 return 1;
93 // Use lockfile to allow only one instance
94 if (lockfile == null)
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);
100 if (lfp<0) {
101 // Provide some useful info
102 if (File.Exists (lockfile))
103 error (logname, String.Format ("Lock file already exists: {0}", lockfile));
104 else
105 error (logname, String.Format ("Cannot open/create lock file exclusively: {0}", lockfile));
106 return 1;
109 if (Syscall.lockf(lfp, LockfCommand.F_TLOCK,0)<0) {
110 info (logname, "Daemon is already running.");
111 return 0;
114 try {
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,
131 true,
132 BindingFlags.Default,
133 null,
134 new object [] {assembly, name, logname},
135 null, null, null) as MonoServiceRunner;
137 if (rnr == null) {
138 error (logname, "Internal Mono Error: Could not create MonoServiceRunner.");
139 return 1;
142 return rnr.StartService ();
143 } finally {
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;
153 this.name = name;
154 this.logname = logname;
157 public int StartService ()
159 try {
160 // Load service assembly
161 Assembly a = null;
163 try {
164 a = Assembly.LoadFrom (assembly);
165 } catch (FileNotFoundException) {
166 error (logname, "Could not find assembly {0}", assembly);
167 return 1;
168 } catch (BadImageFormatException){
169 error (logname, "File {0} is not a valid assembly", assembly);
170 return 1;
171 } catch { }
173 if (a == null){
174 error (logname, "Could not load assembly {0}", assembly);
175 return 1;
178 if (a.EntryPoint == null){
179 error (logname, "Entry point not defined in service");
180 return 1;
183 // Hook up RunService callback
184 Type cbType = Type.GetType ("System.ServiceProcess.ServiceBase+RunServiceCallback, System.ServiceProcess");
185 if (cbType == null){
186 error (logname, "Internal Mono Error: Could not find RunServiceCallback in ServiceBase");
187 return 1;
190 FieldInfo fi = typeof (ServiceBase).GetField ("RunService", BindingFlags.Static | BindingFlags.NonPublic);
191 if (fi == null){
192 error (logname, "Internal Mono Error: Could not find RunService in ServiceBase");
193 return 1;
195 fi.SetValue (null, Delegate.CreateDelegate(cbType, this, "MainLoop"));
197 // And run its Main. Our RunService handler is invoked from
198 // ServiceBase.Run.
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);
206 return 1;
210 // The main service loop
211 private void MainLoop (ServiceBase [] services)
213 try {
214 ServiceBase service;
216 if (services == null || services.Length == 0){
217 error (logname, "No services were registered by this service");
218 return;
221 // Start up the service.
222 service = null;
224 if (name != null){
225 foreach (ServiceBase svc in services){
226 if (svc.ServiceName == name){
227 service = svc;
228 break;
231 } else {
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[]{
244 intr,
245 term,
246 usr1,
247 usr2
250 for (bool running = true; running; ){
251 int idx = UnixSignal.WaitAny (sigs);
252 if (idx < 0 || idx >= sigs.Length)
253 continue;
254 if ((intr.IsSet || term.IsSet) && service.CanStop) {
255 intr.Reset ();
256 term.Reset ();
257 info (logname, "Stopping service {0}", service.ServiceName);
258 call (service, "OnStop", null);
259 running = false;
261 else if (usr1.IsSet && service.CanPauseAndContinue) {
262 usr1.Reset ();
263 info (logname, "Pausing service {0}", service.ServiceName);
264 call (service, "OnPause", null);
266 else if (usr2.IsSet && service.CanPauseAndContinue) {
267 usr2.Reset ();
268 info (logname, "Continuing service {0}", service.ServiceName);
269 call (service, "OnContinue", null);
272 } finally {
273 // Clean up
274 foreach (ServiceBase svc in services){
275 svc.Dispose ();