2005-06-06 Marek Safar <marek.safar@seznam.cz>
[mono.git] / mcs / tools / mono-service / mono-service.cs
1 /*
2  * monod.cs: Mono daemon for running services based on System.ServiceProcess
3  *
4  * Author:
5  *   Joerg Rosenkranz (joergr@voelcker.com)
6  *   Miguel de Icaza (miguel@novell.com)
7  *
8  * (C) 2005 Voelcker Informatik AG
9  * (C) 2005 Novell Inc
10  */
11 using System;
12 using System.IO;
13 using System.Reflection;
14 using Mono.Unix;
15 using System.ServiceProcess;
16 using System.Threading;
17 using System.Runtime.InteropServices;
18
19 class MonoServiceRunner : MarshalByRefObject
20 {
21         string assembly, name, logname;
22         
23         static void info (string prefix, string format, params object [] args)
24         {
25                 Syscall.syslog (SyslogLevel.LOG_INFO, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
26         }
27         
28         static void error (string prefix, string format, params object [] args)
29         {
30                 Syscall.syslog (SyslogLevel.LOG_ERR, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
31         }
32         
33         static void Usage ()
34         {
35                 Console.Error.WriteLine (
36                                          "Usage is:\n" +
37                                          "mono-service [-d:DIRECTORY] [-l:LOCKFILE] [-n:NAME] [-m:LOGNAME] service.exe\n");
38                 Environment.Exit (1);
39         }
40
41         delegate void sighandler_t (int arg);
42         
43         AutoResetEvent signal_event;
44
45         [DllImport ("libc")]
46         extern static int signal (int signum, sighandler_t handler);
47
48         int signum;
49         
50         void my_handler (int sig)
51         {
52                 signum = sig;
53                 signal_event.Set ();
54         }
55
56         static void call (object o, string method, object [] arg)
57         {
58                 MethodInfo m = o.GetType ().GetMethod (method, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
59                 if (arg != null)
60                         m.Invoke (o, new object [1] { arg });
61                 else
62                         m.Invoke (o, null);
63         }
64         
65         static int Main (string [] args)
66         {
67                 string assembly = null;
68                 string directory = null;
69                 string lockfile = null;
70                 string name = null;
71                 string logname = null;
72
73                 foreach (string s in args){
74                         if (s.Length > 3 && s [0] == '-' && s [2] == ':'){
75                                 string arg = s.Substring (3);
76
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;
83                                 }
84                         } else {
85                                 if (assembly != null)
86                                         Usage ();
87                                 
88                                 assembly = s;
89                         }
90                 }
91
92                 if (logname == null)
93                         logname = assembly;
94
95                 if (assembly == null){
96                         error (logname, "Assembly name is missing");
97                         Usage ();
98                 }
99                 
100                 if (directory != null){
101                         if (Syscall.chdir (directory) != 0){
102                                 error (logname, "Could not change to directory {0}", directory);
103                                 return 1;
104                         }
105                 }
106                 
107                 // Use lockfile to allow only one instance
108                 if (lockfile == null)
109                         lockfile = String.Format ("/tmp/{0}.lock", Path.GetFileName (assembly));
110
111                 int lfp = Syscall.open (lockfile, OpenFlags.O_RDWR|OpenFlags.O_CREAT, 
112                         FilePermissions.S_IRUSR|FilePermissions.S_IWUSR|FilePermissions.S_IRGRP);
113         
114                 if (lfp<0)  {
115                         error (logname, "Cannot open lock file.");
116                         return 1;
117                 }
118         
119                 if (Syscall.lockf(lfp, LockfCommand.F_TLOCK,0)<0)  {
120                         info (logname, "Daemon is already running.");
121                         return 0;
122                 }
123                 
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);
129
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;
135                 
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
140             true,\r
141             BindingFlags.Default,\r
142             null,\r
143             new object [] {assembly, name, logname},\r
144             null, null, null) as MonoServiceRunner;
145                         
146                 if (rnr == null) {
147                         error (logname, "Internal Mono Error: Could not create MonoServiceRunner.");
148                         return 1;
149                 }\r
150
151                 return rnr.StartService ();
152         }
153         
154         public MonoServiceRunner (string assembly, string name, string logname)
155         {
156                 this.assembly = assembly;
157                 this.name = name;
158                 this.logname = logname;
159         }
160         
161         public int StartService ()
162         {
163                 try     {
164                         //
165                         // Setup signals
166                         //
167                         signal_event = new AutoResetEvent (false);
168         
169                         // Invoke all the code used in the signal handler, so the JIT does
170                         // not kick-in inside the signal handler
171                         signal_event.Set ();
172                         signal_event.Reset ();
173         
174                         // Hook up 
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));
178         
179                         // Load service assembly
180                         Assembly a = null;
181                         
182                         try {
183                                 a = Assembly.LoadFrom (assembly);
184                         } catch (FileNotFoundException) {
185                                 error (logname, "Could not find assembly {0}", assembly);
186                                 return 1;
187                         } catch (BadImageFormatException){
188                                 error (logname, "File {0} is not a valid assembly", assembly);
189                                 return 1;
190                         } catch { }
191                         
192                         if (a == null){
193                                 error (logname, "Could not load assembly {0}", assembly);
194                                 return 1;
195                         }
196         
197                         // Hook up RunService callback
198                         Type cbType = Type.GetType ("System.ServiceProcess.ServiceBase+RunServiceCallback, System.ServiceProcess");
199                         if (cbType == null){
200                                 error (logname, "Internal Mono Error: Could not find RunServiceCallback in ServiceBase");
201                                 return 1;                       
202                         }
203                         
204                         FieldInfo fi = typeof (ServiceBase).GetField ("RunService", BindingFlags.Static | BindingFlags.NonPublic);
205                         if (fi == null){
206                                 error (logname, "Internal Mono Error: Could not find RunService in ServiceBase");
207                                 return 1;
208                         }
209                         fi.SetValue (null, Delegate.CreateDelegate(cbType, this, "MainLoop"));
210                         
211                         // And run its Main. Our RunService handler is invoked from 
212                         // ServiceBase.Run.
213                         MethodInfo entry = a.EntryPoint;
214                         if (entry == null){
215                                 error (logname, "Entry point not defined in service");
216                                 return 1;
217                         }
218         
219                         string [] service_args = new string [0];
220                         entry.Invoke (null, service_args);
221                         
222                         return 0;
223                         
224                 } catch ( Exception ex ) {
225                         for (Exception e = ex; e != null; e = e.InnerException)
226                                 error (logname, e.Message);
227                         
228                         return 1;
229                 }
230         }
231         
232         // The main service loop
233         private void MainLoop (ServiceBase [] services)
234         {
235                 try {
236                         ServiceBase service;
237         
238                         if (services == null || services.Length == 0){
239                                 error (logname, "No services were registered by this service");
240                                 return;
241                         }
242                         
243                         // Start up the service.
244                         service = null;
245                         
246                         if (name != null){
247                                 foreach (ServiceBase svc in services){
248                                         if (svc.ServiceName == name){
249                                                 service = svc;
250                                                 break;
251                                         }
252                                 }
253                         } else {
254                                 service = services [0];
255                         }
256         
257                         call (service, "OnStart", new string [0]);
258                         info (logname, "Service {0} started", service.ServiceName);
259         
260                         for (bool running = true; running; ){
261                                 signal_event.WaitOne ();
262                                 Signum v;
263                                 
264                                 if (UnixConvert.TryToSignum (signum, out v)){
265                                         signum = 0;
266                                         
267                                         switch (v){
268                                         case Signum.SIGTERM:
269                                                 if (service.CanStop) {
270                                                         info (logname, "Stopping service {0}", service.ServiceName);
271                                                         call (service, "OnStop", null);
272                                                         running = false;
273                                                 }
274                                                 break;
275                                         case Signum.SIGUSR1:
276                                                 if (service.CanPauseAndContinue) {
277                                                         info (logname, "Pausing service {0}", service.ServiceName);
278                                                         call (service, "OnPause", null);
279                                                 }
280                                                 break;
281                                         case Signum.SIGUSR2:
282                                                 if (service.CanPauseAndContinue) {
283                                                         info (logname, "Continuing service {0}", service.ServiceName);
284                                                         call (service, "OnContinue", null);
285                                                 }
286                                                 break;
287                                         }
288                                 }
289                         }
290                 } finally {
291                         // Clean up
292                         foreach (ServiceBase svc in services){
293                                 svc.Dispose ();
294                         }
295                 }
296         }
297 }