]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOWatchDogApplicationMainOSX.m
added some WebDrive WebDAV properties
[sope] / sope-appserver / NGObjWeb / WOWatchDogApplicationMainOSX.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #import <Foundation/Foundation.h>
23 #include <NGObjWeb/NGObjWeb.h>
24
25 #include <unistd.h>
26
27 /*
28   Added #include of crt_externs.h and #define of environ so that environ
29   symbol would be found for passing into execve()
30 */
31 #include <crt_externs.h>
32 #define environ (*_NSGetEnviron())
33
34 #include <sys/wait.h>
35 #include <sys/types.h>
36 #include <sys/unistd.h>
37 #include <sys/param.h>
38 #include <time.h>
39
40 static pid_t    child = -1;
41 static NSString *pidFile = nil;
42 static time_t   lastFailExit = 0;
43 static unsigned failExitCount = 0;
44 static BOOL     killedChild = NO;
45
46 static void killChild(void) {
47   if (child > 0) {
48     int status;
49     
50     fprintf(stderr, "watchdog[%i]: terminating child %i ..\n", getpid(),child);
51     
52     if (kill(child, SIGTERM) == 0) {
53       waitpid(child, &status, 0);
54       killedChild = YES;
55       
56       fprintf(stderr, "  terminated child %i",  child);
57       
58       if (WIFEXITED(status))
59         fprintf(stderr, " exit=%i", WEXITSTATUS(status));
60       if (WIFSIGNALED(status))
61         fprintf(stderr, " signal=%i", WTERMSIG(status));
62       
63       fprintf(stderr, ".\n");
64       fflush(stderr);
65       
66       child = -1;
67       return;
68     }
69     else if (kill(child, SIGKILL)) {
70       waitpid(child, &status, 0);
71       killedChild = YES;
72       
73       fprintf(stderr, "  killed child %i",  child);
74       
75       if (WIFEXITED(status))
76         fprintf(stderr, " exit=%i", WEXITSTATUS(status));
77       if (WIFSIGNALED(status))
78         fprintf(stderr, " signal=%i", WTERMSIG(status));
79       
80       fprintf(stderr, ".\n");
81       fflush(stderr);
82       
83       child = -1;
84       return;
85     }
86   }
87 }
88
89 static void _writePid(NSString *pidFile) {
90   if ([pidFile length] > 0) {
91     FILE *pf;
92       
93     if ((pf = fopen([pidFile cString], "w"))) {
94       fprintf(pf, "%i\n", getpid());
95       fflush(pf);
96       fclose(pf);
97     }
98   }
99 }
100 static void _delPid(void) {
101   if ([pidFile length] > 0) {
102     if (unlink([pidFile cString]) == 0)
103       pidFile = nil;
104   }
105 }
106
107 static void exitWatchdog(void) {
108   killChild();
109   _delPid();
110 }
111
112 static void wsignalHandler(int _signal) {
113   switch (_signal) {
114   case SIGINT:
115     /* Control-C */
116     fprintf(stderr, "[%i]: watchdog handling signal ctrl-c ..\n", getpid());
117     killChild();
118     exit(0);
119     /* shouldn't get here */
120     abort();
121
122   case SIGSEGV:
123     /* Coredump ! */
124     fprintf(stderr,
125             "[%i]: watchdog handling segmentation fault "
126             "(SERIOUS PROBLEM) ..\n",
127             getpid());
128     killChild();
129     exit(123);
130     /* shouldn't get here */
131     abort();
132
133   case SIGTERM:
134     /* TERM signal (kill 'pid') */
135     fprintf(stderr, "[%i]: watchdog handling SIGTERM ..\n", getpid());
136     killChild();
137     exit(0);
138     /* shouldn't get here */
139     abort();
140     
141   case SIGHUP:
142     /* HUP signal (restart children) */
143     fprintf(stderr, "[%i]: watchdog handling SIGHUP ..\n", getpid());
144     killChild();
145     killedChild = YES;
146     signal(_signal, wsignalHandler);
147     return;
148     
149   case SIGCHLD:
150     break;
151     
152   default:
153     fprintf(stderr, "[%i]: watchdog handling signal %i ..\n",
154             getpid(), _signal);
155     break;
156   }
157   fflush(stderr);
158   
159   switch (_signal) {
160     case SIGTERM:
161     case SIGINT:
162     case SIGKILL:
163     case SIGILL:
164       killChild();
165       exit(0);
166       break;
167       
168     case SIGHUP:
169       killChild();
170       break;
171       
172     case SIGCHLD: {
173       int   returnStatus;
174       pid_t result;
175       
176       //      NSLog(@"SIGNAL: SIGCHLD");
177       // fetch return state
178       
179       do {
180         result = waitpid(-1, &returnStatus, WNOHANG);
181         if (result > 0) {
182           fprintf(stderr, "[%i]: process %i exited with code %i",
183                   getpid(), (int)result, WEXITSTATUS(returnStatus));
184
185           if (WIFSIGNALED(returnStatus)) {
186             fprintf(stderr, " (terminated due to signal %i%s)",
187                     WTERMSIG(returnStatus),
188                     WCOREDUMP(returnStatus) ? ", coredump" : "");
189           }
190           if (WIFSTOPPED(returnStatus)) {
191             fprintf(stderr, " (stopped due to signal %i)",
192                     WSTOPSIG(returnStatus));
193           }
194           
195           fprintf(stderr, "\n");
196           fflush(stderr);
197         }
198       }
199       while (result > 0);
200       
201       break;
202     }
203     
204     default:
205       fprintf(stderr, "watchdog[%i]: caught signal %i\n", getpid(), _signal);
206       break;
207   }
208   signal(_signal, wsignalHandler);
209 }
210
211 static void signalHandler(int _signal) {
212   fprintf(stderr, "[%i]: handling signal %i ..\n",
213           getpid(), _signal);
214   fflush(stderr);
215   
216   switch (_signal) {
217     case SIGPIPE:
218       fprintf(stderr, "[%i]: caught signal SIGPIPE\n", getpid());
219       break;
220       
221     default:
222       fprintf(stderr, "[%i]: caught signal %i\n", getpid(), _signal);
223       break;
224   }
225   signal(_signal, signalHandler);
226 }
227
228 int WOWatchDogApplicationMain
229 (NSString *appName, int argc, const char *argv[])
230 {
231   NSAutoreleasePool *pool;
232   NSUserDefaults *ud;
233
234   pool = [[NSAutoreleasePool alloc] init];
235   
236   /*
237     We have to reset the user defaults because this could be a child process
238     where we would have modified the arguments so that it doesn't try and run
239     as a parent again.
240   */
241   [NSUserDefaults resetStandardUserDefaults];
242   ud = [NSUserDefaults standardUserDefaults];
243   
244   /* default is to use the watch dog! */
245   /* Note: the Defaults.plist is not yet loaded at this stage! */
246   if ([ud objectForKey:@"WOUseWatchDog"] != nil) {
247     if (![ud boolForKey:@"WOUseWatchDog"])
248       return WOApplicationMain(appName, argc, argv);
249   }
250   
251   /* ensure we can actually do execve() */
252   if (argv[0][0] != '/') {
253     /*
254       Note: we cannot use getcwd to make the path absolute, the
255             binary could have been found anywhere in $PATH
256     */
257     fprintf(stderr,
258             "program called with a non-absolute path, can't use execve(): "
259             "'%s'\n"
260             "  (start using `which %s`)\n",
261             argv[0], argv[0]);
262     exit(123); // TODO: better error code
263   }
264   
265   /* watch dog */
266   {
267     int      failCount = 0;
268     int      forkCount = 0;
269     BOOL     repeat    = YES;
270     BOOL     isVerbose = NO;  
271     
272     isVerbose = [[ud objectForKey:@"watchdog_verbose"] boolValue];
273     pidFile   = [[[ud objectForKey:@"watchdog_pidfile"] stringValue] copy];
274     
275     /* write current pid to pidfile */
276     _writePid(pidFile);
277     
278     /* register exit handler */
279     atexit(exitWatchdog);
280     
281     /* register signal handlers of watch dog */
282     signal(SIGPIPE, wsignalHandler);
283     signal(SIGCHLD, wsignalHandler);
284     signal(SIGINT,  wsignalHandler);
285     signal(SIGTERM, wsignalHandler);
286     signal(SIGKILL, wsignalHandler);
287     signal(SIGHUP,  wsignalHandler);
288     
289     /* loop */
290     
291     while (repeat) {
292       time_t clientStartTime;
293       
294       clientStartTime = time(NULL);
295       killedChild = NO;
296       
297       if ((child = fork()) == -1) {
298         fprintf(stderr, "[%i]: fork failed: %s\n", getpid(), strerror(errno));
299         failCount++;
300         
301         if (failCount > 5) {
302           fprintf(stderr, "  fork failed %i times, sleeping 60 seconds ..\n",
303                   failCount);
304           sleep(60);
305         }
306         else {
307           sleep(1);
308         }
309       }
310       else {
311         if (child == 0) {
312           /* child process */
313           /* We need to modify the arguements passed into execve() */
314           char **childArgv;
315           int index;
316           int childIndex;
317           extern char **environ;
318           
319           if (isVerbose)
320             fprintf(stderr, "starting child %i ..\n", getpid());
321
322           pidFile = [pidFile stringByAppendingPathExtension:@"child"];
323           _writePid(pidFile);
324           
325           atexit(_delPid);
326           
327           /*
328             We need to call execve() to transform this child process into a
329             new process.
330             Signals set to be caught wil be set to the default action by
331             execve().
332           */
333           
334           /*
335             Set the path (0) argument and set -WOUseWatchDog to NO so this
336             process won't execute a parent
337           */
338           childArgv = calloc(argc + 5, sizeof(char *));
339           childArgv[0] = strdup(argv[0]);
340           childArgv[1] = strdup("-WOUseWatchDog");
341           childArgv[2] = strdup("NO");
342           
343           /*
344             Copy any remaining arguments skipping a -WOUseWatchDog if it exists
345           */
346           for (index = 1, childIndex = 3; index < argc; index++) {
347             if (strncmp(argv[index], "-WOUseWatchDog", 14) == 0) {
348               /* Skip past the -WOUseWatchDog argument since we used our own */
349               index++; 
350               
351               if (index >= argc) 
352                 break; /* We have reached the end of the arguments */
353               else if (argv[index][0] != '-') {
354                 /* 
355                    If the argument after -WOUseWatchDog is not another flag
356                    (and it shouldn't be, it should be YES or NO), then skip
357                    the YES/NO and go to the next argument
358                 */
359                 continue; 
360               }
361             }
362             
363             childArgv[childIndex] = strdup(argv[index]);
364             childIndex++;
365           }
366           
367           /* Mark the end of the array of arguments */
368           childArgv[childIndex] = NULL; 
369           
370           /* transform the child into a new process */
371           {
372             const char *p;
373             
374             p = argv[0];
375             if (p[0] != '/') {
376               /*
377                 Note: we cannot use getcwd to make the path absolute, the
378                       binary could have been found anywhere in $PATH
379               */
380               fprintf(stderr,
381                       "program called with a non-absolute path: '%s'\n", p);
382               exit(123); // TODO: better error code
383             }
384             
385             execve(p, childArgv, environ);
386           
387             /* shouldn't even get here ! */
388             fprintf(stderr,
389                     "execve of '%s' failed in child %i failed: %s\n",
390                     p, getpid(), strerror(errno));
391           }
392           abort();
393         }
394         else {
395           /* parent (watch dog) */
396           int      status = 0;
397           pid_t    result = 0;
398           time_t   clientStopTime;
399           unsigned uptime;
400           
401           forkCount++;
402           
403           if (isVerbose) {
404             fprintf(stderr, "forked child process %i (#%i) ..\n",
405                     child, forkCount);
406           }
407           
408           failCount = 0;
409           status    = 0;
410           
411           if ((result = waitpid(child, &status, 0)) == -1) {
412             if (killedChild) {
413               killedChild = NO;
414               continue;
415             }
416             
417             fprintf(stderr,
418                     "### waiting for child %i (#%i) failed: %s\n",
419                     child, forkCount, strerror(errno));
420             continue;
421           }
422
423           clientStopTime = time(NULL);
424           uptime = clientStopTime - clientStartTime;
425           
426           if (WIFSIGNALED(status)) {
427             fprintf(stderr,
428                     "### child %i (#%i) was terminated by signal %i "
429                     "(uptime=%ds).\n",
430                     child, forkCount, WTERMSIG(status), uptime);
431             
432             lastFailExit  = time(NULL);
433             failExitCount++;
434           }
435           else if (WIFEXITED(status)) {
436             unsigned exitCode;
437             
438             if ((exitCode = WEXITSTATUS(status)) != 0) {
439               time_t now;
440               
441               now = time(NULL);
442
443               if (uptime < 3) {
444                 if (failExitCount > 0) {
445                   unsigned secsSinceLastFail;
446                 
447                   secsSinceLastFail = (now - lastFailExit);
448                 
449                   if (secsSinceLastFail > 120) {
450                     /* reset fail count */
451                     failExitCount = 0;
452                   }
453                   else if (failExitCount > 20) {
454                     printf("### child %i (#%i) already failed %i times "
455                            "in the last %i seconds, stopping watchdog !\n",
456                            child, forkCount, failExitCount, secsSinceLastFail);
457                     repeat = NO;
458                   }
459                 }
460               }              
461               failExitCount++;
462               lastFailExit  = now;
463               
464               fprintf(stderr,
465                       "### child %i (#%i) exited with status %i "
466                       "(#fails=%i, uptime=%ds).\n",
467                       child, forkCount, exitCode, failExitCount, uptime);
468             }
469             else {
470               fprintf(stderr,
471                       "### child %i (#%i) exited successfully (uptime=%ds).\n",
472                       child, forkCount, uptime);
473             }
474             
475             if (exitCode == 123) // ???
476               repeat = NO;
477           }
478           else {
479             fprintf(stderr,
480                     "### abnormal termination of child %i (#%i) status=%i"
481                     "(was not signaled nor exited).",
482                     child, forkCount, status);
483           }
484         }
485       }
486     }
487     return 0;
488   }
489 }
490
491 /* main function which initializes server defaults (usually in /etc) */
492
493 @interface NSUserDefaults(ServerDefaults)
494 + (id)hackInServerDefaults:(NSUserDefaults *)_ud
495   withAppDomainPath:(NSString *)_appDomainPath
496   globalDomainPath:(NSString *)_globalDomainPath;
497 @end
498
499 int WOWatchDogApplicationMainWithServerDefaults
500 (NSString *appName, int argc, const char *argv[],
501  NSString *globalDomainPath, NSString *appDomainPath)
502 {
503   NSAutoreleasePool *pool;
504   Class defClass;
505   
506   pool = [[NSAutoreleasePool alloc] init];
507   
508   if ((defClass = NSClassFromString(@"WOServerDefaults")) != nil) {
509     NSUserDefaults *ud, *sd;
510     
511     ud = [NSUserDefaults standardUserDefaults];
512     sd = [defClass hackInServerDefaults:ud
513                    withAppDomainPath:appDomainPath
514                    globalDomainPath:globalDomainPath];
515   }
516   
517   return WOWatchDogApplicationMain(appName, argc, argv);
518 }