1/* 2 Copyright (C) 2000-2005 SKYRIX Software AG 3 Copyright (C) 2009 Inverse inc. 4 5 This file is part of SOPE. 6 7 SOPE is free software; you can redistribute it and/or modify it under 8 the terms of the GNU Lesser General Public License as published by the 9 Free Software Foundation; either version 2, or (at your option) any 10 later version. 11 12 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY 13 WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 15 License for more details. 16 17 You should have received a copy of the GNU Lesser General Public 18 License along with SOPE; see the file COPYING. If not, write to the 19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 20 02111-1307, USA. 21*/ 22 23#import <Foundation/NSAutoreleasePool.h> 24#import <Foundation/NSCalendarDate.h> 25#import <Foundation/NSData.h> 26#import <Foundation/NSException.h> 27#import <Foundation/NSFileHandle.h> 28#import <Foundation/NSProcessInfo.h> 29#import <Foundation/NSRunLoop.h> 30#import <Foundation/NSTimer.h> 31#import <Foundation/NSThread.h> 32#import <Foundation/NSUserDefaults.h> 33#import <Foundation/NSValue.h> 34 35#import <NGObjWeb/WOAdaptor.h> 36#import <NGObjWeb/WOApplication.h> 37#import <NGExtensions/NSObject+Logs.h> 38#import <NGStreams/NGActiveSocket.h> 39#import <NGStreams/NGCTextStream.h> 40#import <NGStreams/NGInternetSocketAddress.h> 41#import <NGStreams/NGNetUtilities.h> 42#import <NGStreams/NGPassiveSocket.h> 43 44/* for signal handlers */ 45static volatile int pendingSIGHUP = 0; 46static volatile BOOL shouldTerminate = NO; 47 48void handle_SIGINTTERM(int signum) 49{ 50 shouldTerminate = YES; 51} 52 53void handle_SIGHUP(int signum) 54{ 55 pendingSIGHUP++; 56} 57 58extern void handle_SIGPIPE(int signum); 59 60#if defined(__CYGWIN32__) || defined(__MINGW32__) 61 62int WOWatchDogApplicationMain 63(NSString *appName, int argc, const char *argv[]) 64{ 65 /* no watchdog support on Win* */ 66 return WOApplicationMain(appName, argc, argv); 67} 68 69#else 70 71#include <sys/wait.h> 72#include <sys/types.h> 73#include <unistd.h> 74#include <time.h> 75#include <string.h> 76 77#define OUT_OF_CHILD_SLEEPTIME 250 /* ~0.25ms */ 78/* We sleep longer than that so the log interval is not very accurate. 79 * Should be good enough to avoid spamming the logs */ 80#define OUT_OF_CHILD_LOG_INTERVAL (1000000 / OUT_OF_CHILD_SLEEPTIME) /* 1 sec */ 81 82static NSTimeInterval respawnDelay; /* seconds */ 83static const char *pidFile = NULL; 84NSInteger watchDogRequestTimeout; 85 86typedef enum { 87 WOChildStatusDown = 0, 88 WOChildStatusSpawning, 89 WOChildStatusReady, 90 WOChildStatusBusy, 91 WOChildStatusExcessive, 92 WOChildStatusTerminating, 93 WOChildStatusMax 94} WOChildStatus; 95 96@class WOWatchDog; 97 98@interface WOWatchDogChild : NSObject <RunLoopEvents> 99{ 100 pid_t pid; 101 int counter; 102 NGActiveSocket *controlSocket; 103 WOChildStatus status; 104 NSTimer *killTimer; 105 NSUInteger killTimerIteration; 106 WOWatchDog *watchDog; 107 NSCalendarDate *lastSpawn; 108 BOOL loggedNotRespawn; 109} 110 111- (void) setWatchDog: (WOWatchDog *) newWatchDog; 112 113- (void) setPid: (int) newPid; 114- (int) pid; 115 116- (void) revokeKillTimer; 117 118- (void) handleProcessStatus: (int) status; 119 120- (void) setControlSocket: (NGActiveSocket *) newSocket; 121- (NGActiveSocket *) controlSocket; 122 123- (void) setStatus: (WOChildStatus) newStatus; 124- (WOChildStatus) status; 125 126- (void) setLastSpawn: (NSCalendarDate *) newLastSpawn; 127- (NSCalendarDate *) lastSpawn; 128- (NSCalendarDate *) nextSpawn; 129- (void) logNotRespawn; 130 131- (BOOL) readMessage; 132 133- (void) notify; 134- (void) terminate; 135 136@end 137 138@interface WOWatchDog : NSObject <RunLoopEvents> 139{ 140 NSString *appName; 141 int argc; 142 const char **argv; 143 144 pid_t pid; 145 NSTimer *loopTimer; 146 BOOL terminate; 147 BOOL willTerminate; 148 149 NGPassiveSocket *listeningSocket; 150 151 int numberOfChildren; 152 NSMutableArray *children; 153 NSMutableArray *readyChildren; 154 NSMutableArray *downChildren; 155 156 long outOfChildSleepCount; 157} 158 159+ (id) sharedWatchDog; 160 161- (pid_t) pid; 162 163- (void) declareChildReady: (WOWatchDogChild *) readyChild; 164- (void) declareChildDown: (WOWatchDogChild *) readyChild; 165 166- (int) run: (NSString *) appName 167 argc: (int) argc 168 argv: (const char **) argv; 169 170@end 171 172@implementation WOWatchDogChild 173 174+ (void) initialize 175{ 176 watchDogRequestTimeout = [[NSUserDefaults standardUserDefaults] 177 integerForKey: @"WOWatchDogRequestTimeout"]; 178 if (watchDogRequestTimeout > 0) 179 [self logWithFormat: @"watchdog request timeout set to %d minutes", 180 watchDogRequestTimeout]; 181 else 182 [self warnWithFormat: @"watchdog request timeout not set"]; 183} 184 185+ (WOWatchDogChild *) watchDogChild 186{ 187 WOWatchDogChild *newChild; 188 189 newChild = [self new]; 190 [newChild autorelease]; 191 192 return newChild; 193} 194 195- (id) init 196{ 197 if ((self = [super init])) 198 { 199 pid = -1; 200 controlSocket = nil; 201 status = WOChildStatusDown; 202 killTimer = nil; 203 killTimerIteration = 0; 204 counter = 0; 205 lastSpawn = nil; 206 loggedNotRespawn = NO; 207 } 208 209 return self; 210} 211 212- (void) dealloc 213{ 214 // [self logWithFormat: @"-dealloc (pid: %d)", pid]; 215 [killTimer invalidate]; 216 [self setControlSocket: nil]; 217 [lastSpawn release]; 218 [super dealloc]; 219} 220 221- (NSString *) description 222{ 223 return [NSString stringWithFormat: @"WOWatchDogChild (pid: %d)", pid]; 224} 225 226- (void) setWatchDog: (WOWatchDog *) newWatchDog 227{ 228 watchDog = newWatchDog; 229} 230 231- (void) setPid: (int) newPid 232{ 233 pid = newPid; 234} 235 236- (int) pid 237{ 238 return pid; 239} 240 241- (void) revokeKillTimer 242{ 243 [killTimer invalidate]; 244 killTimer = nil; 245} 246 247- (void) handleProcessStatus: (int) processStatus 248{ 249 int code; 250 251 code = WEXITSTATUS (processStatus); 252 if (code == 0) 253 [self logWithFormat: @"child %d exited", pid]; 254 else 255 [self logWithFormat: @"child %d exited with code %i", pid, code]; 256 if (WIFSIGNALED (processStatus)) 257 [self logWithFormat: @" (terminated due to signal %i%@)", 258 WTERMSIG (processStatus), 259 WCOREDUMP (processStatus) ? @", coredump" : @""]; 260 if (WIFSTOPPED (processStatus)) 261 [self logWithFormat: @" (stopped due to signal %i)", 262 WSTOPSIG (processStatus)]; 263 [self setStatus: WOChildStatusDown]; 264 [self setControlSocket: nil]; 265 [self revokeKillTimer]; 266} 267 268- (void) setControlSocket: (NGActiveSocket *) newSocket 269{ 270 NSRunLoop *runLoop; 271 272 runLoop = [NSRunLoop currentRunLoop]; 273 if (controlSocket) 274 [runLoop removeEvent: (void *) ((long) [controlSocket fileDescriptor]) 275 type: ET_RDESC 276 forMode: NSDefaultRunLoopMode 277 all: YES]; 278 [controlSocket close]; 279 ASSIGN (controlSocket, newSocket); 280 if (controlSocket) 281 [runLoop addEvent: (void *) ((long) [controlSocket fileDescriptor]) 282 type: ET_RDESC 283 watcher: self 284 forMode: NSDefaultRunLoopMode]; 285} 286 287- (NGActiveSocket *) controlSocket 288{ 289 return controlSocket; 290} 291 292- (void) setStatus: (WOChildStatus) newStatus 293{ 294 status = newStatus; 295} 296 297- (WOChildStatus) status 298{ 299 return status; 300} 301 302- (void) setLastSpawn: (NSCalendarDate *) newLastSpawn 303{ 304 ASSIGN (lastSpawn, newLastSpawn); 305 loggedNotRespawn = NO; 306} 307 308- (NSCalendarDate *) lastSpawn 309{ 310 return lastSpawn; 311} 312 313- (NSCalendarDate *) nextSpawn 314{ 315 return [lastSpawn addYear: 0 month: 0 day: 0 316 hour: 0 minute: 0 317 second: respawnDelay]; 318} 319 320- (void) logNotRespawn 321{ 322 if (!loggedNotRespawn) 323 { 324 [self logWithFormat: 325 @"avoiding to respawn child before %@", [self nextSpawn]]; 326 loggedNotRespawn = YES; 327 } 328} 329 330- (void) _safetyBeltIteration: (NSTimer *) aKillTimer 331{ 332 if ([watchDog pid] == getpid ()) { 333 killTimerIteration++; 334 if (killTimerIteration < watchDogRequestTimeout) { 335 [self warnWithFormat: 336 @"pid %d has been hanging in the same request for %d minutes", 337 pid, killTimerIteration]; 338 } 339 else { 340 if (status != WOChildStatusDown) { 341 [self warnWithFormat: @"safety belt -- sending KILL signal to pid %d", 342 pid]; 343 kill (pid, SIGKILL); 344 [self revokeKillTimer]; 345 } 346 } 347 } 348 else { 349 [self errorWithFormat: 350 @"messy processes: safety belt iteration occurring on child: %d", 351 pid]; 352 [self revokeKillTimer]; 353 } 354} 355 356- (void) _kill 357{ 358 if (status != WOChildStatusDown) { 359 [self logWithFormat: @"sending terminate signal to pid %d", pid]; 360 status = WOChildStatusTerminating; 361 kill (pid, SIGTERM); 362 [self revokeKillTimer]; 363 } 364} 365 366- (BOOL) readMessage 367{ 368 WOChildMessage message; 369 BOOL rc; 370 NSException *e; 371 372 if ([controlSocket readBytes: &message 373 count: sizeof (WOChildMessage)] == NGStreamError) { 374 rc = NO; 375 [self errorWithFormat: @"FAILURE receiving status for child %d", pid]; 376 [self errorWithFormat: @" socket: %@", controlSocket]; 377 e = [controlSocket lastException]; 378 if (e) 379 [self errorWithFormat: @" exception: %@", e]; 380 [self _kill]; 381 } 382 else { 383 rc = YES; 384 [self revokeKillTimer]; 385 if (message == WOChildMessageAccept) { 386 status = WOChildStatusBusy; 387 if (watchDogRequestTimeout > 0) { 388 /* We schedule an X minutes grace period while the child is processing 389 the request. This enables long requests to complete while providing 390 a safety belt for children gone rogue. */ 391 killTimer 392 = [NSTimer scheduledTimerWithTimeInterval: 60 393 target: self 394 selector: @selector (_safetyBeltIteration:) 395 userInfo: nil 396 repeats: YES]; 397 killTimerIteration = 0; 398 } 399 } 400 else if (message == WOChildMessageReady) { 401 status = WOChildStatusReady; 402 [watchDog declareChildReady: self]; 403 } 404 } 405 406 return rc; 407} 408 409- (void) notify 410{ 411 WOChildMessage message; 412 413 counter++; 414 message = WOChildMessageAccept; 415 if ([controlSocket writeBytes: &message 416 count: sizeof (WOChildMessage)] == NGStreamError 417 || ![self readMessage]) { 418 [self errorWithFormat: @"FAILURE notifying child %d", pid]; 419 [self _kill]; 420 } 421} 422 423- (void) terminate 424{ 425 if (status == WOChildStatusDown) { 426 [self logWithFormat: @"child is already down"]; 427 } else { 428 [self _kill]; 429 } 430} 431 432- (void) receivedEvent: (void*)data 433 type: (RunLoopEventType)type 434 extra: (void*)extra 435 forMode: (NSString*)mode 436{ 437 if ([controlSocket isAlive]) 438 [self readMessage]; 439 else { 440 /* This happens when a socket has been closed by the child but the child 441 has not terminated yet. */ 442 [[NSRunLoop currentRunLoop] removeEvent: data 443 type: ET_RDESC 444 forMode: NSDefaultRunLoopMode 445 all: YES]; 446 [self setControlSocket: nil]; 447 } 448} 449 450@end 451 452@implementation WOWatchDog 453 454+ (id) sharedWatchDog 455{ 456 static WOWatchDog *sharedWatchDog = nil; 457 458 if (!sharedWatchDog) 459 sharedWatchDog = [self new]; 460 461 return sharedWatchDog; 462} 463 464- (id) init 465{ 466 if ((self = [super init])) 467 { 468 listeningSocket = nil; 469 terminate = NO; 470 willTerminate = NO; 471 shouldTerminate = NO; 472 pendingSIGHUP = 0; 473 474 outOfChildSleepCount = 0; 475 numberOfChildren = 0; 476 children = [[NSMutableArray alloc] initWithCapacity: 10]; 477 readyChildren = [[NSMutableArray alloc] initWithCapacity: 10]; 478 downChildren = [[NSMutableArray alloc] initWithCapacity: 10]; 479 } 480 481 return self; 482} 483 484- (void) _releaseListeningSocket 485{ 486 if (listeningSocket) { 487 [[NSRunLoop currentRunLoop] removeEvent: (void *) ((long) [listeningSocket fileDescriptor]) 488 type: ET_RDESC 489 forMode: NSDefaultRunLoopMode 490 all: YES]; 491 [listeningSocket close]; 492 [listeningSocket release]; 493 listeningSocket = nil; 494 } 495} 496 497- (void) dealloc 498{ 499 [self _releaseListeningSocket]; 500 [appName release]; 501 [children release]; 502 [super dealloc]; 503} 504 505- (pid_t) pid 506{ 507 return pid; 508} 509 510- (void) _runChildWithControlSocket: (NGActiveSocket *) controlSocket 511{ 512 WOApplication *app; 513 extern char **environ; 514 515 [NSProcessInfo initializeWithArguments: (char **) argv 516 count: argc 517 environment: environ]; 518 NGInitTextStdio(); 519 app = [NSClassFromString(appName) new]; 520 [app autorelease]; 521 [app setListeningSocket: listeningSocket]; 522 [app setControlSocket: controlSocket]; 523 [app run]; 524} 525 526- (void) receivedEvent: (void*)data 527 type: (RunLoopEventType)type 528 extra: (void*)extra 529 forMode: (NSString*)mode 530{ 531 int nextId; 532 WOWatchDogChild *child; 533 NSUInteger max; 534 535 max = [readyChildren count]; 536 if (max > 0) { 537 outOfChildSleepCount = 0; 538 nextId = max - 1; 539 child = [readyChildren objectAtIndex: nextId]; 540 [readyChildren removeObjectAtIndex: nextId]; 541 [child notify]; 542 } 543 else { 544 /* we're out of ready children, sleep a bit to avoid hogging the CPU */ 545 usleep(OUT_OF_CHILD_SLEEPTIME); 546 if (outOfChildSleepCount % OUT_OF_CHILD_LOG_INTERVAL == 0) { 547 [self errorWithFormat: @"No child available to handle incoming request!"]; 548 } 549 outOfChildSleepCount++; 550 } 551} 552 553- (void) _cleanupSignalAndEventHandlers 554{ 555 NSRunLoop *runLoop; 556 557 signal(SIGHUP, SIG_DFL); 558 signal(SIGINT, SIG_DFL); 559 signal(SIGTERM, SIG_DFL); 560 signal(SIGPIPE, SIG_DFL); 561 562 [loopTimer invalidate]; 563 loopTimer = nil; 564 runLoop = [NSRunLoop currentRunLoop]; 565 [runLoop removeEvent: (void *) ((long) [listeningSocket fileDescriptor]) 566 type: ET_RDESC 567 forMode: NSDefaultRunLoopMode 568 all: YES]; 569} 570 571- (BOOL) _spawnChild: (WOWatchDogChild *) child 572{ 573 NGActiveSocket *pair[2]; 574 BOOL isChild; 575 int childPid; 576 extern char **environ; 577 578 isChild = NO; 579 580 if ([NGActiveSocket socketPair: pair]) { 581 childPid = fork (); 582 if (childPid == 0) { 583 setsid (); 584 isChild = YES; 585 [self _cleanupSignalAndEventHandlers]; 586 587 [child retain]; 588 [pair[0] retain]; 589 590 [children makeObjectsPerformSelector: @selector (revokeKillTimer)]; 591 [children release]; 592 children = nil; 593 [readyChildren release]; 594 readyChildren = nil; 595 [downChildren release]; 596 downChildren = nil; 597 598 [[NSAutoreleasePool currentPool] emptyPool]; 599 600 [self _runChildWithControlSocket: pair[0]]; 601 602 [pair[0] autorelease]; 603 [child autorelease]; 604 } else if (childPid > 0) { 605 [self logWithFormat: @"child spawned with pid %d", childPid]; 606 [child setPid: childPid]; 607 [child setStatus: WOChildStatusSpawning]; 608 [pair[1] setReceiveTimeout: 5.0]; 609 [child setControlSocket: pair[1]]; 610 [child setLastSpawn: [NSCalendarDate date]]; 611 } else { 612 perror ("fork"); 613 } 614 } 615 616 return isChild; 617} 618 619- (void) _ensureNumberOfChildren 620{ 621 int currentNumber, delta, count, min, max; 622 WOWatchDogChild *child; 623 624 currentNumber = [children count]; 625 if (currentNumber < numberOfChildren) { 626 delta = numberOfChildren - currentNumber; 627 for (count = 0; count < delta; count++) { 628 child = [WOWatchDogChild watchDogChild]; 629 [child setWatchDog: self]; 630 [children addObject: child]; 631 [downChildren addObject: child]; 632 } 633 [self logWithFormat: @"preparing %d children", delta]; 634 } 635 else if (currentNumber > numberOfChildren) { 636 delta = currentNumber - numberOfChildren; 637 max = [downChildren count]; 638 if (max > delta) 639 min = max - delta; 640 else 641 min = 0; 642 for (count = max - 1; count >= min; count--) { 643 child = [downChildren objectAtIndex: count]; 644 [downChildren removeObjectAtIndex: count]; 645 [children removeObject: child]; 646 delta--; 647 [self logWithFormat: @"%d processes purged from pool", delta]; 648 } 649 650 max = [readyChildren count]; 651 if (max > delta) 652 max -= delta; 653 for (count = max - 1; count > -1; count--) { 654 child = [readyChildren objectAtIndex: count]; 655 [readyChildren removeObjectAtIndex: count]; 656 [child terminate]; 657 [child setStatus: WOChildStatusExcessive]; 658 delta--; 659 } 660 [self logWithFormat: @"%d processes left to terminate", delta]; 661 } 662} 663 664- (void) _noop 665{ 666} 667 668- (BOOL) _ensureChildren 669{ 670 int count, max; 671 WOWatchDogChild *child; 672 BOOL isChild, delayed; 673 NSCalendarDate *now, *nextSpawn; 674 675 isChild = NO; 676 677 if (!willTerminate) { 678 [self _ensureNumberOfChildren]; 679 max = [downChildren count]; 680 for (count = max - 1; !isChild && count > -1; count--) { 681 delayed = NO; 682 child = [downChildren objectAtIndex: count]; 683 684 if ([child status] == WOChildStatusExcessive) 685 [children removeObject: child]; 686 else { 687 now = [NSCalendarDate date]; 688 nextSpawn = [child nextSpawn]; 689 if ([nextSpawn earlierDate: now] == nextSpawn) 690 isChild = [self _spawnChild: child]; 691 else { 692 delayed = YES; 693 [child logNotRespawn]; 694 } 695 } 696 if (!(delayed || isChild)) 697 [downChildren removeObjectAtIndex: count]; 698 } 699 } 700 701 return isChild; 702} 703 704/* SOPE on GNUstep does not need to parse the argument line, since the 705 arguments will be put in the NSArgumentDomain. I don't know about 706 libFoundation but OSX is supposed to act the same way. */ 707- (NGInternetSocketAddress *) _listeningAddress 708{ 709 NGInternetSocketAddress *listeningAddress; 710 NSUserDefaults *ud; 711 id port, allow; 712 static BOOL warnedAboutAllow = NO; 713 714 listeningAddress = nil; 715 716 ud = [NSUserDefaults standardUserDefaults]; 717 port = [ud objectForKey:@"p"]; 718 if (!port) { 719 port = [ud objectForKey:@"WOPort"]; 720 if (!port) 721 port = @"auto"; 722 } 723 allow = [ud objectForKey:@"WOHttpAllowHost"]; 724 if ([allow count] > 0 && !warnedAboutAllow) { 725 [self warnWithFormat: @"'WOHttpAllowHost' is ignored in watchdog mode," 726 @" use a real firewall instead"]; 727 warnedAboutAllow = YES; 728 } 729 730 if ([port isKindOfClass: [NSString class]]) { 731 if ([port isEqualToString: @"auto"]) { 732 listeningAddress 733 = [[NGInternetSocketAddress alloc] initWithPort:0 onHost:@"127.0.0.1"]; 734 [listeningAddress autorelease]; 735 } else if ([port rangeOfString: @":"].location == NSNotFound) { 736 if (allow) 737 listeningAddress = 738 [NGInternetSocketAddress wildcardAddressWithPort:[port intValue]]; 739 else 740 port = [NSString stringWithFormat: @"127.0.0.1:%d", [port intValue]]; 741 } 742 } 743 else { 744 if (allow) 745 listeningAddress = 746 [NGInternetSocketAddress wildcardAddressWithPort:[port intValue]]; 747 else { 748 port = [NSString stringWithFormat: @"127.0.0.1:%@", port]; 749 } 750 } 751 752 if (!listeningAddress) 753 listeningAddress = (NGInternetSocketAddress *) NGSocketAddressFromString(port); 754 755 return listeningAddress; 756} 757 758- (BOOL) _prepareListeningSocket 759{ 760 NGInternetSocketAddress *addr; 761 NSString *address; 762 BOOL rc; 763 int backlog; 764 765 addr = [self _listeningAddress]; 766 NS_DURING { 767 [listeningSocket release]; 768 listeningSocket = [[NGPassiveSocket alloc] initWithDomain: [addr domain]]; 769 [listeningSocket bindToAddress: addr]; 770 backlog = [[NSUserDefaults standardUserDefaults] 771 integerForKey: @"WOListenQueueSize"]; 772 if (!backlog) 773 backlog = 5; 774 [listeningSocket listenWithBacklog: backlog]; 775 address = [addr address]; 776 if (!address) 777 address = @"*"; 778 [self logWithFormat: @"listening on %@:%d", address, [addr port]]; 779 [[NSRunLoop currentRunLoop] addEvent: (void *) ((long) [listeningSocket fileDescriptor]) 780 type: ET_RDESC 781 watcher: self 782 forMode: NSDefaultRunLoopMode]; 783 rc = YES; 784 } 785 NS_HANDLER { 786 rc = NO; 787 } 788 NS_ENDHANDLER; 789 790 return rc; 791} 792 793- (WOWatchDogChild *) _childWithPID: (pid_t) childPid 794{ 795 WOWatchDogChild *currentChild, *child; 796 int count; 797 798 child = nil; 799 for (count = 0; !child && count < numberOfChildren; count++) { 800 currentChild = [children objectAtIndex: count]; 801 if ([currentChild pid] == childPid) 802 child = currentChild; 803 } 804 805 return child; 806} 807 808- (void) _setupSignals 809{ 810 signal(SIGHUP, handle_SIGHUP); 811 signal(SIGINT, handle_SIGINTTERM); 812 signal(SIGTERM, handle_SIGINTTERM); 813 signal(SIGPIPE, handle_SIGPIPE); 814} 815 816- (void) declareChildReady: (WOWatchDogChild *) readyChild 817{ 818 if (![readyChildren containsObject: readyChild]) 819 [readyChildren addObject: readyChild]; 820} 821 822- (void) declareChildDown: (WOWatchDogChild *) downChild 823{ 824 if (![downChildren containsObject: downChild]) 825 [downChildren addObject: downChild]; 826} 827 828- (void) _ensureWorkersCount 829{ 830 int newNumberOfChildren; 831 NSUserDefaults *ud; 832 833 ud = [NSUserDefaults standardUserDefaults]; 834 [ud synchronize]; 835 newNumberOfChildren = [ud integerForKey: @"WOHttpAdaptorForkCount"]; 836 if (newNumberOfChildren) 837 [self logWithFormat: @"user default 'WOHttpAdaptorForkCount' has been" 838 " replaced with 'WOWorkersCount'"]; 839 else 840 newNumberOfChildren = [ud integerForKey: @"WOWorkersCount"]; 841 if (newNumberOfChildren < 1) 842 newNumberOfChildren = 1; 843 numberOfChildren = newNumberOfChildren; 844} 845 846- (void) _handlePostTerminationSignal 847{ 848 WOWatchDogChild *child; 849 int count; 850 851 [self logWithFormat: @"Terminating with SIGINT or SIGTERM"]; 852 [self _releaseListeningSocket]; 853 for (count = 0; count < numberOfChildren; count++) { 854 child = [children objectAtIndex: count]; 855 if ([child status] != WOChildStatusDown 856 && [child status] != WOChildStatusTerminating) 857 [child terminate]; 858 } 859 860 if ([downChildren count] == numberOfChildren) { 861 [self logWithFormat: @"all children exited. We now terminate."]; 862 terminate = YES; 863 } 864 else 865 willTerminate = YES; 866} 867 868- (void) _checkProcessesStatus 869{ 870 int status; 871 pid_t childPid; 872 WOWatchDogChild *child; 873 874 while ((childPid = waitpid (-1, &status, WNOHANG)) > 0) { 875 child = [self _childWithPID: childPid]; 876 [child handleProcessStatus: status]; 877 [self declareChildDown: child]; 878 if (willTerminate && [downChildren count] == numberOfChildren) { 879 [self logWithFormat: @"all children exited. We now terminate."]; 880 terminate = YES; 881 } 882 } 883} 884 885- (int) run: (NSString *) newAppName 886 argc: (int) newArgC argv: (const char **) newArgV 887{ 888 NSAutoreleasePool *pool; 889 NSRunLoop *runLoop; 890 NSDate *limitDate; 891 BOOL listening; 892 int retries; 893 894 willTerminate = NO; 895 896 ASSIGN (appName, newAppName); 897 898 argc = newArgC; 899 argv = newArgV; 900 901 listening = NO; 902 retries = 0; 903 while (!listening && retries < 5) { 904 listening = [self _prepareListeningSocket]; 905 retries++; 906 if (!listening) { 907 [self warnWithFormat: @"listening socket: attempt %d failed", retries]; 908 [NSThread sleepForTimeInterval: 1.0]; 909 } 910 } 911 if (listening) { 912 pid = getpid (); 913 [self logWithFormat: @"watchdog process pid: %d", pid]; 914 [self _setupSignals]; 915 [self _ensureWorkersCount]; 916 917 // NSLog (@"ready to process requests"); 918 runLoop = [NSRunLoop currentRunLoop]; 919 920 /* This timer ensures the looping of the runloop at reasonable intervals 921 for correct processing of signal handlers. */ 922 loopTimer = [NSTimer scheduledTimerWithTimeInterval: 0.5 923 target: self 924 selector: @selector (_noop) 925 userInfo: nil 926 repeats: YES]; 927 terminate = NO; 928 while (!terminate) { 929 pool = [NSAutoreleasePool new]; 930 931 while (pendingSIGHUP) { 932 [self logWithFormat: @"received SIGHUP"]; 933 [self _ensureWorkersCount]; 934 pendingSIGHUP--; 935 } 936 937 if (shouldTerminate && pidFile) 938 unlink(pidFile); 939 940 // [self logWithFormat: @"watchdog loop"]; 941 NS_DURING { 942 terminate = [self _ensureChildren]; 943 if (!terminate) { 944 limitDate = [runLoop limitDateForMode:NSDefaultRunLoopMode]; 945 [runLoop runMode: NSDefaultRunLoopMode beforeDate: limitDate]; 946 } 947 } 948 NS_HANDLER { 949 terminate = YES; 950 [self errorWithFormat: 951 @"an exception occured in runloop %@", localException]; 952 } 953 NS_ENDHANDLER; 954 955 if (!terminate) { 956 if (shouldTerminate) 957 [self _handlePostTerminationSignal]; 958 [self _checkProcessesStatus]; 959 } 960 961 [pool release]; 962 } 963 964 [self _cleanupSignalAndEventHandlers]; 965 } 966 else 967 [self errorWithFormat: @"unable to listen on specified port," 968 @" check that no other process is already using it"]; 969 970 return 0; 971} 972 973@end 974 975static BOOL _writePid(NSString *nsPidFile) { 976 NSString *pid; 977 BOOL rc; 978 979 pid = [NSString stringWithFormat: @"%d", getpid()]; 980 rc = [pid writeToFile: nsPidFile atomically: NO]; 981 982 return rc; 983} 984 985int WOWatchDogApplicationMain 986(NSString *appName, int argc, const char *argv[]) 987{ 988 NSAutoreleasePool *pool; 989 NSUserDefaults *ud; 990 NSString *logFile, *nsPidFile; 991 int rc; 992 pid_t childPid; 993 NSProcessInfo *processInfo; 994 Class WOAppClass; 995 996 pool = [[NSAutoreleasePool alloc] init]; 997 998#if LIB_FOUNDATION_LIBRARY || defined(GS_PASS_ARGUMENTS) 999 { 1000 extern char **environ; 1001 [NSProcessInfo initializeWithArguments:(void*)argv count:argc 1002 environment:(void*)environ]; 1003 } 1004#endif 1005 1006 /* This invocation forces the class initialization of WOCoreApplication, 1007 which causes the NSUserDefaults to be initialized as well with 1008 Defaults.plist. */ 1009 WOAppClass = [NSClassFromString (appName) class]; 1010 1011 ud = [NSUserDefaults standardUserDefaults]; 1012 processInfo = [NSProcessInfo processInfo]; 1013 1014 logFile = [ud objectForKey: @"WOLogFile"]; 1015 if (!logFile) 1016 logFile = [NSString stringWithFormat: @"/var/log/%@/%@.log", 1017 [processInfo processName], 1018 [processInfo processName]]; 1019 if (![logFile isEqualToString: @"-"]) { 1020 freopen([logFile cString], "a", stdout); 1021 freopen([logFile cString], "a", stderr); 1022 } 1023 if ([ud boolForKey: @"WONoDetach"]) 1024 childPid = 0; 1025 else 1026 childPid = fork(); 1027 1028 if (childPid) { 1029 rc = 0; 1030 } 1031 else { 1032 nsPidFile = [ud objectForKey: @"WOPidFile"]; 1033 if (!nsPidFile) 1034 nsPidFile = [NSString stringWithFormat: @"/var/run/%@/%@.pid", 1035 [processInfo processName], 1036 [processInfo processName]]; 1037 pidFile = [nsPidFile UTF8String]; 1038 if (_writePid(nsPidFile)) { 1039 respawnDelay = [ud integerForKey: @"WORespawnDelay"]; 1040 if (!respawnDelay) 1041 respawnDelay = 5; 1042 1043 if ([WOAppClass respondsToSelector: @selector (applicationWillStart)]) 1044 [WOAppClass applicationWillStart]; 1045 1046 /* default is to use the watch dog! */ 1047 if ([ud objectForKey:@"WOUseWatchDog"] != nil 1048 && ![ud boolForKey:@"WOUseWatchDog"]) 1049 rc = WOApplicationMain(appName, argc, argv); 1050 else 1051 rc = [[WOWatchDog sharedWatchDog] run: appName argc: argc argv: argv]; 1052 } 1053 else { 1054 [ud errorWithFormat: @"unable to open pid file: %s", pidFile]; 1055 rc = -1; 1056 } 1057 } 1058 1059 [pool release]; 1060 1061 return rc; 1062} 1063#endif 1064 1065/* main function which initializes server defaults (usually in /etc) */ 1066 1067@interface NSUserDefaults(ServerDefaults) 1068+ (id)hackInServerDefaults:(NSUserDefaults *)_ud 1069 withAppDomainPath:(NSString *)_appDomainPath 1070 globalDomainPath:(NSString *)_globalDomainPath; 1071@end 1072 1073int WOWatchDogApplicationMainWithServerDefaults 1074(NSString *appName, int argc, const char *argv[], 1075 NSString *globalDomainPath, NSString *appDomainPath) 1076{ 1077 NSAutoreleasePool *pool; 1078 Class defClass; 1079 1080 pool = [[NSAutoreleasePool alloc] init]; 1081#if LIB_FOUNDATION_LIBRARY || defined(GS_PASS_ARGUMENTS) 1082 { 1083 extern char **environ; 1084 [NSProcessInfo initializeWithArguments:(void*)argv count:argc 1085 environment:(void*)environ]; 1086 } 1087#endif 1088 1089 if ((defClass = NSClassFromString(@"WOServerDefaults")) != nil) { 1090 NSUserDefaults *ud; 1091 1092 ud = [NSUserDefaults standardUserDefaults]; 1093 [defClass hackInServerDefaults:ud 1094 withAppDomainPath:appDomainPath 1095 globalDomainPath:globalDomainPath]; 1096 1097#if 0 1098 if (((sd == nil) || (sd == ud)) && (appDomainPath != nil)) { 1099 NSLog(@"Note: not using server defaults: '%@' " 1100 @"(not supported on this Foundation)", appDomainPath); 1101 } 1102#endif 1103 } 1104 1105 [pool release]; 1106 1107 return WOWatchDogApplicationMain(appName, argc, argv); 1108} 1109