* Mono.Mozilla/DOM/Node.cs,
[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 Mono.Unix.Native;
16 using System.ServiceProcess;
17 using System.Threading;
18 using System.Runtime.InteropServices;
19
20 class MonoServiceRunner : MarshalByRefObject
21 {
22         string assembly, name, logname;
23         
24         static void info (string prefix, string format, params object [] args)
25         {
26                 Syscall.syslog (SyslogLevel.LOG_INFO, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
27         }
28         
29         static void error (string prefix, string format, params object [] args)
30         {
31                 Syscall.syslog (SyslogLevel.LOG_ERR, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
32         }
33         
34         static void Usage ()
35         {
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);
40         }
41
42         static void call (object o, string method, object [] arg)
43         {
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);
49         }
50         
51         static int Main (string [] args)
52         {
53                 string assembly = null;
54                 string directory = null;
55                 string lockfile = null;
56                 string name = null;
57                 string logname = null;
58
59                 foreach (string s in args){
60                         if (s.Length > 3 && s [0] == '-' && s [2] == ':'){
61                                 string arg = s.Substring (3);
62
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;
69                                 }
70                         } else {
71                                 if (assembly != null)
72                                         Usage ();
73                                 
74                                 assembly = s;
75                         }
76                 }
77
78                 if (logname == null)
79                         logname = assembly;
80
81                 if (assembly == null){
82                         error (logname, "Assembly name is missing");
83                         Usage ();
84                 }
85                 
86                 if (directory != null){
87                         if (Syscall.chdir (directory) != 0){
88                                 error (logname, "Could not change to directory {0}", directory);
89                                 return 1;
90                         }
91                 }
92                 
93                 // Use lockfile to allow only one instance
94                 if (lockfile == null)
95                         lockfile = String.Format ("/tmp/{0}.lock", Path.GetFileName (assembly));
96
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);
99
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;
107                 }
108         
109                 if (Syscall.lockf(lfp, LockfCommand.F_TLOCK,0)<0)  {
110                         info (logname, "Daemon is already running.");
111                         return 0;
112                 }
113                 
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);
120         
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;
126                         
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;
136                                 
137                         if (rnr == null) {
138                                 error (logname, "Internal Mono Error: Could not create MonoServiceRunner.");
139                                 return 1;
140                         }
141         
142                         return rnr.StartService ();
143                 } finally {
144                         // Remove lock file when done
145                         if (File.Exists(lockfile))
146                                 File.Delete (lockfile);
147                 }
148         }
149         
150         public MonoServiceRunner (string assembly, string name, string logname)
151         {
152                 this.assembly = assembly;
153                 this.name = name;
154                 this.logname = logname;
155         }
156         
157         public int StartService ()
158         {
159                 try     {
160                         // Load service assembly
161                         Assembly a = null;
162                         
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 { }
172                         
173                         if (a == null){
174                                 error (logname, "Could not load assembly {0}", assembly);
175                                 return 1;
176                         }
177                         
178                         if (a.EntryPoint == null){
179                                 error (logname, "Entry point not defined in service");
180                                 return 1;
181                         }
182                         
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;                       
188                         }
189                         
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;
194                         }
195                         fi.SetValue (null, Delegate.CreateDelegate(cbType, this, "MainLoop"));
196                         
197                         // And run its Main. Our RunService handler is invoked from 
198                         // ServiceBase.Run.
199                         return AppDomain.CurrentDomain.ExecuteAssembly (assembly, AppDomain.CurrentDomain.Evidence);
200                         
201                 } catch ( Exception ex ) {
202                         for (Exception e = ex; e != null; e = e.InnerException) {
203                                 error (logname, e.Message);
204                         }
205                         
206                         return 1;
207                 }
208         }
209         
210         // The main service loop
211         private void MainLoop (ServiceBase [] services)
212         {
213                 try {
214                         ServiceBase service;
215         
216                         if (services == null || services.Length == 0){
217                                 error (logname, "No services were registered by this service");
218                                 return;
219                         }
220                         
221                         // Start up the service.
222                         service = null;
223                         
224                         if (name != null){
225                                 foreach (ServiceBase svc in services){
226                                         if (svc.ServiceName == name){
227                                                 service = svc;
228                                                 break;
229                                         }
230                                 }
231                         } else {
232                                 service = services [0];
233                         }
234         
235                         call (service, "OnStart", new string [0]);
236                         info (logname, "Service {0} started", service.ServiceName);
237         
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);
242
243                         UnixSignal[] sigs = new UnixSignal[]{
244                                 intr,
245                                 term,
246                                 usr1,
247                                 usr2
248                         };
249
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;
260                                 }
261                                 else if (usr1.IsSet && service.CanPauseAndContinue) {
262                                         usr1.Reset ();
263                                         info (logname, "Pausing service {0}", service.ServiceName);
264                                         call (service, "OnPause", null);
265                                 }
266                                 else if (usr2.IsSet && service.CanPauseAndContinue) {
267                                         usr2.Reset ();
268                                         info (logname, "Continuing service {0}", service.ServiceName);
269                                         call (service, "OnContinue", null);
270                                 }
271                         }
272                 } finally {
273                         // Clean up
274                         foreach (ServiceBase svc in services){
275                                 svc.Dispose ();
276                         }
277                 }
278         }
279 }