Merge pull request #637 from LogosBible/enetdown
[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.Collections.Generic;
13 using System.IO;
14 using System.Reflection;
15 using Mono.Unix;
16 using Mono.Unix.Native;
17 using System.ServiceProcess;
18 using System.Threading;
19 using System.Runtime.InteropServices;
20
21 class MonoServiceRunner : MarshalByRefObject
22 {
23         string assembly, name, logname;
24         string[] args;
25         
26         static void info (string prefix, string format, params object [] args)
27         {
28                 Syscall.syslog (SyslogLevel.LOG_INFO, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
29         }
30         
31         static void error (string prefix, string format, params object [] args)
32         {
33                 Syscall.syslog (SyslogLevel.LOG_ERR, String.Format ("{0}: {1}", prefix, String.Format (format, args)));
34         }
35         
36         static void Usage ()
37         {
38                 Console.Error.WriteLine (
39                                          "Usage is:\n" +
40                                          "mono-service [-d:DIRECTORY] [-l:LOCKFILE] [-n:NAME] [-m:LOGNAME] service.exe\n");
41                 Environment.Exit (1);
42         }
43
44         static void call (object o, string method, object [] arg)
45         {
46                 MethodInfo m = o.GetType ().GetMethod (method, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
47                 if (arg != null)
48                         m.Invoke (o, new object [1] { arg });
49                 else
50                         m.Invoke (o, null);
51         }
52         
53         static int Main (string [] args)
54         {
55                 string assembly = null;
56                 string directory = null;
57                 string lockfile = null;
58                 string name = null;
59                 string logname = null;
60                 var assebmlyArgs = new List<string>();
61
62                 foreach (string s in args){
63                         if (s.Length > 3 && s [0] == '-' && s [2] == ':'){
64                                 string arg = s.Substring (3);
65
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;
72                                 }
73                         } else {
74                                 if (assembly != null)
75                                 {
76                                         assebmlyArgs.Add(s);
77                                 }
78                                 else
79                                 {
80                                         assembly = s;
81                                 }
82                         }
83                 }
84
85                 if (logname == null)
86                         logname = assembly;
87
88                 if (assembly == null){
89                         error (logname, "Assembly name is missing");
90                         Usage ();
91                 }
92                 
93                 if (directory != null){
94                         if (Syscall.chdir (directory) != 0){
95                                 error (logname, "Could not change to directory {0}", directory);
96                                 return 1;
97                         }
98                 }
99                 
100                 // Use lockfile to allow only one instance
101                 if (lockfile == null)
102                         lockfile = String.Format ("/tmp/{0}.lock", Path.GetFileName (assembly));
103
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);
106
107                 if (lfp<0)  {
108                         // Provide some useful info
109                         if (File.Exists (lockfile))
110                                 error (logname, String.Format ("Lock file already exists: {0}", lockfile));
111                         else 
112                                 error (logname, String.Format ("Cannot open/create lock file exclusively: {0}", lockfile));
113                         return 1;
114                 }
115         
116                 if (Syscall.lockf(lfp, LockfCommand.F_TLOCK,0)<0)  {
117                         info (logname, "Daemon is already running.");
118                         return 0;
119                 }
120                 
121                 try {
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);
127         
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;
133                         
134                         AppDomain newDomain = AppDomain.CreateDomain (logname, AppDomain.CurrentDomain.Evidence, setup);
135                         MonoServiceRunner rnr = newDomain.CreateInstanceAndUnwrap(
136                                 typeof (MonoServiceRunner).Assembly.FullName,
137                                 typeof (MonoServiceRunner).FullName,
138                                 true,
139                                 BindingFlags.Default,
140                                 null,
141                                 new object [] {assembly, name, logname, assebmlyArgs.ToArray()},
142                                 null, null, null) as MonoServiceRunner;
143                                 
144                         if (rnr == null) {
145                                 error (logname, "Internal Mono Error: Could not create MonoServiceRunner.");
146                                 return 1;
147                         }
148         
149                         return rnr.StartService ();
150                 } finally {
151                         // Remove lock file when done
152                         if (File.Exists(lockfile))
153                                 File.Delete (lockfile);
154                 }
155         }
156         
157         public MonoServiceRunner (string assembly, string name, string logname, string[] args)
158         {
159                 this.assembly = assembly;
160                 this.name = name;
161                 this.logname = logname;
162                 this.args = args;
163         }
164         
165         public int StartService ()
166         {
167                 try     {
168                         // Load service assembly
169                         Assembly a = null;
170                         
171                         try {
172                                 a = Assembly.LoadFrom (assembly);
173                         } catch (FileNotFoundException) {
174                                 error (logname, "Could not find assembly {0}", assembly);
175                                 return 1;
176                         } catch (BadImageFormatException){
177                                 error (logname, "File {0} is not a valid assembly", assembly);
178                                 return 1;
179                         } catch { }
180                         
181                         if (a == null){
182                                 error (logname, "Could not load assembly {0}", assembly);
183                                 return 1;
184                         }
185                         
186                         if (a.EntryPoint == null){
187                                 error (logname, "Entry point not defined in service");
188                                 return 1;
189                         }
190                         
191                         // Hook up RunService callback
192                         Type cbType = Type.GetType ("System.ServiceProcess.ServiceBase+RunServiceCallback, System.ServiceProcess");
193                         if (cbType == null){
194                                 error (logname, "Internal Mono Error: Could not find RunServiceCallback in ServiceBase");
195                                 return 1;                       
196                         }
197                         
198                         FieldInfo fi = typeof (ServiceBase).GetField ("RunService", BindingFlags.Static | BindingFlags.NonPublic);
199                         if (fi == null){
200                                 error (logname, "Internal Mono Error: Could not find RunService in ServiceBase");
201                                 return 1;
202                         }
203                         fi.SetValue (null, Delegate.CreateDelegate(cbType, this, "MainLoop"));
204                         
205                         // And run its Main. Our RunService handler is invoked from 
206                         // ServiceBase.Run.
207                         return AppDomain.CurrentDomain.ExecuteAssembly (assembly, AppDomain.CurrentDomain.Evidence, args);
208                         
209                 } catch ( Exception ex ) {
210                         for (Exception e = ex; e != null; e = e.InnerException) {
211                                 error (logname, e.Message);
212                         }
213                         
214                         return 1;
215                 }
216         }
217         
218         // The main service loop
219         private void MainLoop (ServiceBase [] services)
220         {
221                 try {
222                         ServiceBase service;
223         
224                         if (services == null || services.Length == 0){
225                                 error (logname, "No services were registered by this service");
226                                 return;
227                         }
228                         
229                         // Start up the service.
230                         service = null;
231                         
232                         if (name != null){
233                                 foreach (ServiceBase svc in services){
234                                         if (svc.ServiceName == name){
235                                                 service = svc;
236                                                 break;
237                                         }
238                                 }
239                         } else {
240                                 service = services [0];
241                         }
242
243                         if (service.ExitCode != 0) {
244                                 //likely due to a previous execution, so we need to reset it to default
245                                 service.ExitCode = 0;
246                         }
247                         call (service, "OnStart", args);
248                         info (logname, "Service {0} started", service.ServiceName);
249         
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);
254
255                         UnixSignal[] sigs = new UnixSignal[]{
256                                 intr,
257                                 term,
258                                 usr1,
259                                 usr2
260                         };
261
262                         for (bool running = true; running; ){
263                                 int idx = UnixSignal.WaitAny (sigs);
264                                 if (idx < 0 || idx >= sigs.Length)
265                                         continue;
266                                 if ((intr.IsSet || term.IsSet) && service.CanStop) {
267                                         intr.Reset ();
268                                         term.Reset ();
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);
273                                         running = false;
274                                 }
275                                 else if (usr1.IsSet && service.CanPauseAndContinue) {
276                                         usr1.Reset ();
277                                         info (logname, "Pausing service {0}", service.ServiceName);
278                                         call (service, "OnPause", null);
279                                 }
280                                 else if (usr2.IsSet && service.CanPauseAndContinue) {
281                                         usr2.Reset ();
282                                         info (logname, "Continuing service {0}", service.ServiceName);
283                                         call (service, "OnContinue", null);
284                                 }
285                         }
286                 } finally {
287                         // Clean up
288                         foreach (ServiceBase svc in services){
289                                 svc.Dispose ();
290                         }
291                 }
292         }
293 }