libs-ec/EcProcess.m
rfm 1d3916581b fixup for adding a new debug mode
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/ec/trunk@40506 72102866-910b-0410-8b05-ffd578937521
2017-05-16 11:43:29 +00:00

5478 lines
137 KiB
Objective-C

/** Enterprise Control Configuration and Logging
Copyright (C) 2012 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
Date: Febrary 2010
Originally developed from 1996 to 2012 by Brainstorm, and donated to
the FSF.
This file is part of the GNUstep project.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111 USA.
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSConnection.h>
#import <Foundation/NSData.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSDebug.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSDistantObject.h>
#import <Foundation/NSException.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSObjCRuntime.h>
#import <Foundation/NSPort.h>
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSScanner.h>
#import <Foundation/NSSerialization.h>
#import <Foundation/NSString.h>
#import <Foundation/NSTimer.h>
#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSValue.h>
#import <GNUstepBase/GSObjCRuntime.h>
#import <GNUstepBase/NSObject+GNUstepBase.h>
#import "EcProcess.h"
#import "EcLogger.h"
#import "EcAlarm.h"
#import "EcAlarmDestination.h"
#import "EcHost.h"
#import "EcUserDefaults.h"
#import "EcBroadcastProxy.h"
#import "EcMemoryLogger.h"
#include "config.h"
#ifdef HAVE_SYS_SIGNAL_H
#include <sys/signal.h>
#endif
#ifdef HAVE_SYS_FILE_H
#include <sys/file.h>
#endif
#ifdef HAVE_SYS_FCNTL_H
#include <sys/fcntl.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#if defined(HAVE_GETTID)
# include <unistd.h>
# include <sys/syscall.h>
# include <sys/types.h>
#endif
#include <stdio.h>
#if !defined(EC_DEFAULTS_PREFIX)
#define EC_DEFAULTS_PREFIX nil
#endif
#if !defined(EC_DEFAULTS_STRICT)
#define EC_DEFAULTS_STRICT NO
#endif
#if !defined(EC_EFFECTIVE_USER)
#define EC_EFFECTIVE_USER nil
#endif
NSUInteger
ecNativeThreadID()
{
#if defined(__MINGW__)
return (NSUInteger)GetCurrentThreadId();
#elif defined(HAVE_GETTID)
return (NSUInteger)syscall(SYS_gettid);
#else
return NSNotFound;
#endif
}
@interface EcDefaultRegistration : NSObject
{
NSString *name; // The name/key of the default (without prefix)
NSString *type; // The type text for the default
NSString *help; // The help text for the default
SEL cmd; // method to update when default values change
id obj; // The latest value of the default
id val; // The fallback value of the default
}
+ (void) defaultsChanged: (NSUserDefaults*)defs;
+ (NSMutableString*) listHelp;
+ (NSDictionary*) merge: (NSDictionary*)d;
+ (void) registerDefault: (NSString*)name
withTypeText: (NSString*)type
andHelpText: (NSString*)help
action: (SEL)cmd
value: (id)value;
+ (void) showHelp;
@end
/* Lock for controlling access to per-process singleton instance.
*/
static NSRecursiveLock *ecLock = nil;
static BOOL cmdFlagDaemon = NO;
static BOOL cmdFlagTesting = NO;
static BOOL cmdIsQuitting = NO;
static BOOL cmdIsRunning = NO;
static BOOL cmdKeepStderr = NO;
static NSInteger cmdQuitStatus = 0;
static NSString *cmdBase = nil;
static NSString *cmdInst = nil;
static NSString *cmdName = nil;
static NSString *cmdUser = nil;
static NSUserDefaults *cmdDefs = nil;
static NSString *cmdDebugName = nil;
static NSMutableDictionary *cmdLogMap = nil;
static id<EcMemoryLogger> cmdMemoryLogger = nil;
static NSDate *started = nil; /* Time object was created. */
static NSDate *memStats = nil; /* Time stats were started. */
static NSTimeInterval lastIP = 0.0; /* Time of last input to object. */
static NSTimeInterval lastOP = 0.0; /* Time of last output by object. */
static Class cDateClass = 0;
static Class dateClass = 0;
static Class stringClass = 0;
static int cmdSignalled = 0;
static RETSIGTYPE
ihandler(int sig)
{
static BOOL beenHere = NO;
signal(sig, ihandler);
if (NO == beenHere)
{
beenHere = YES;
signal(SIGABRT, SIG_DFL);
abort();
}
exit(sig);
#if RETSIGTYPE != void
return 0;
#endif
}
static RETSIGTYPE
qhandler(int sig)
{
if (SIGHUP == sig)
{
static int hupCount = 0;
/* We allow multiple HUP signals since, while shutting down we may
* attempt to write out messages to our terminal, generating more
* signals, and we want to ignore those and shut down cleanly.
*/
if (hupCount++ < 1000)
{
cmdSignalled = 0; // Allow signal to be set.
}
}
/* We store the signal value in a global variable and return to normal
* processing ... that way later code can check on the state of the
* variable and take action outside the handler.
* We can't act immediately here inside the handler as the signal may
* have interrupted some vital library (eg malloc()) and left it in a
* state such that our code can't continue. For instance if we try to
* cleanup after a signal and call free(), the process may hang waiting
* for a lock that the interupted function still holds.
*/
if (0 == cmdSignalled)
{
cmdSignalled = sig; // Record signal for event loop.
}
else
{
static BOOL beenHere = NO;
/* We have been signalled more than once ... so let's try to
* crash rather than continuing.
*/
if (NO == beenHere)
{
beenHere = YES;
signal(SIGABRT, SIG_DFL);
abort();
}
exit(cmdSignalled); // Exit with *first* signal number
}
#if RETSIGTYPE != void
return 0;
#endif
}
NSString*
cmdVersion(NSString *ver)
{
static NSString *version = @"1997-2013";
if (ver != nil)
{
ASSIGNCOPY(version, ver);
}
return version;
}
static NSString *homeDir = nil;
NSString*
cmdHomeDir()
{
return homeDir;
}
void
cmdSetHome(NSString *home)
{
ASSIGNCOPY(homeDir, home);
}
static NSString *logsDir = nil;
NSString*
ecLogsSubdirectory()
{
return logsDir;
}
void
ecSetLogsSubdirectory(NSString *pathComponent)
{
ASSIGNCOPY(logsDir, pathComponent);
}
static NSString *userDir = nil;
static NSString*
cmdUserDir()
{
if (userDir == nil)
return NSHomeDirectoryForUser(cmdUser);
else
return userDir;
}
static NSString*
cmdSetUserDirectory(NSString *dir)
{
if (dir == nil)
{
dir = NSHomeDirectoryForUser(cmdUser);
}
else if ([dir isAbsolutePath] == NO)
{
dir = [NSHomeDirectoryForUser(cmdUser)
stringByAppendingPathComponent: dir];
}
ASSIGNCOPY(userDir, dir);
return userDir;
}
static NSString *dataDir = nil;
/*
* Return the current logging directory - if 'today' is not nil, treat it as
* the name of a subdirectory in which todays logs should be archived.
* Create the directory path if necessary.
*/
NSString*
cmdLogsDir(NSString *date)
{
NSFileManager *mgr = [NSFileManager defaultManager];
NSString *str = cmdUserDir();
NSString *component;
BOOL flag;
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
if ([mgr createDirectoryAtPath: str
withIntermediateDirectories: YES
attributes: nil
error: NULL] == NO)
{
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
NSLog(@"Unable to create directory - %@", str);
return nil;
}
}
else
{
flag = YES;
}
}
if (flag == NO)
{
NSLog(@"The path '%@' is not a directory", str);
return nil;
}
component = ecLogsSubdirectory();
if (nil == component)
{
component = @"DebugLogs";
}
str = [str stringByAppendingPathComponent: component];
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
if ([mgr createDirectoryAtPath: str
withIntermediateDirectories: YES
attributes: nil
error: NULL] == NO)
{
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
NSLog(@"Unable to create directory - %@", str);
return nil;
}
}
else
{
flag = YES;
}
}
if (flag == NO)
{
NSLog(@"The path '%@' is not a directory", str);
return nil;
}
if (date != nil)
{
str = [str stringByAppendingPathComponent: date];
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
if ([mgr createDirectoryAtPath: str
withIntermediateDirectories: YES
attributes: nil
error: NULL] == NO)
{
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
NSLog(@"Unable to create directory - %@", str);
return nil;
}
}
else
{
flag = YES;
}
}
if (flag == NO)
{
NSLog(@"The path '%@' is not a directory", str);
return nil;
}
}
if (homeDir != nil)
{
str = [str stringByAppendingPathComponent: homeDir];
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
if ([mgr createDirectoryAtPath: str
withIntermediateDirectories: YES
attributes: nil
error: NULL] == NO)
{
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
NSLog(@"Unable to create directory - %@", str);
return nil;
}
}
else
{
flag = YES;
}
}
if (flag == NO)
{
NSLog(@"The path '%@' is not a directory", str);
return nil;
}
}
return str;
}
NSString*
cmdLogKey(EcLogType t)
{
switch (t)
{
case LT_DEBUG: return @"Debug";
case LT_WARNING: return @"Warn";
case LT_ERROR: return @"Error";
case LT_AUDIT: return @"Audit";
case LT_ALERT: return @"Alert";
default: return @"UnknownLogType";
}
}
NSString*
cmdLogName()
{
static NSString *cmdLogName = nil;
if (nil == cmdLogName)
{
[ecLock lock];
if (nil == cmdLogName)
{
NSString *n = [EcProc cmdName];
if (nil == n)
{
n = [[NSProcessInfo processInfo] processName];
}
cmdLogName = [n copy];
}
[ecLock unlock];
}
return cmdLogName;
}
NSString*
cmdLogFormat(EcLogType t, NSString *fmt)
{
static NSString *h = nil;
NSCalendarDate *c = [[cDateClass alloc] init];
NSString *f = cmdLogKey(t);
NSString *n = cmdLogName();
NSString *d;
NSString *result;
if (h == nil)
{
h = [[[NSHost currentHost] wellKnownName] copy];
}
d = [c descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S.%F %z" locale: nil];
result = [stringClass stringWithFormat: @"%@(%@): %@ %@ - %@\n",
n, h, d, f, fmt];
RELEASE(c);
return result;
}
EcProcess *EcProc = nil;
static NSConnection *EcProcConnection = nil;
static EcAlarmDestination *alarmDestination = nil;
static EcLogger *alertLogger = nil;
static EcLogger *auditLogger = nil;
static EcLogger *debugLogger = nil;
static EcLogger *errorLogger = nil;
static EcLogger *warningLogger = nil;
static NSMutableSet *cmdActions = nil;
static id cmdServer = nil;
static id cmdPTimer = nil;
static NSDictionary *cmdConf = nil;
static NSDictionary *cmdOperators = nil;
static NSDate *cmdFirst = nil;
static NSDate *cmdLast = nil;
static BOOL cmdIsTransient = NO;
static NSMutableSet *cmdDebugModes = nil;
static NSMutableDictionary *cmdDebugKnown = nil;
static NSMutableString *replyBuffer = nil;
static SEL cmdTimSelector = 0;
static NSTimeInterval cmdTimInterval = 60.0;
static NSMutableArray *noNetConfig = nil;
static NSMutableDictionary *servers = nil;
static int coreSize = -2; // Not yet set
static NSString *hostName = nil;
static NSString *
ecHostName()
{
NSString *name;
[ecLock lock];
if (nil == hostName)
{
hostName = [[[NSHost currentHost] wellKnownName] retain];
}
name = [hostName retain];
[ecLock unlock];
return [name autorelease];
}
static uint64_t memMaximum = 0;
static uint64_t memAllowed = 0;
static uint64_t excAvge = 0; // current period average
static uint64_t memAvge = 0; // current period average
static uint64_t excStrt = 0; // excluded usage at first check
static uint64_t memStrt = 0; // total usage at first check
static uint64_t excLast = 0; // excluded usage at last check
static uint64_t memLast = 0; // total usage at last check
static uint64_t excPrev = 0; // excluded usage at previous warning
static uint64_t memPrev = 0; // total usage at previous warning
static uint64_t excPeak = 0; // excluded peak usage
static uint64_t memPeak = 0; // total peak usage
static uint64_t memWarn = 0; // next warning interval
static uint64_t memSlot = 0; // minute counter
static uint64_t excRoll[10]; // last N values
static uint64_t memRoll[10]; // last N values
#define MEMCOUNT (sizeof(memRoll)/sizeof(*memRoll))
static NSDate *memTime = nil; // Time of last alert
#ifndef __MINGW__
static int reservedPipe[2] = { 0, 0 };
static NSInteger descriptorsMaximum = 0;
#endif
static NSString*
findAction(NSString *cmd)
{
NSString *found = nil;
cmd = [cmd lowercaseString];
[ecLock lock];
if (nil == (found = [cmdActions member: cmd]))
{
NSEnumerator *enumerator;
NSString *name;
enumerator = [cmdActions objectEnumerator];
while (nil != (name = [enumerator nextObject]))
{
if (YES == [name hasPrefix: cmd])
{
if (nil == found)
{
found = name;
}
else
{
found = nil; // Ambiguous
break;
}
}
}
}
cmd = [found retain];
[ecLock unlock];
return [cmd autorelease];
}
static NSString*
ecCommandHost()
{
NSString *host;
host = [cmdDefs stringForKey: @"CommandHost"];
if (nil == host)
{
host = @""; /* Local host */
}
return host;
}
static NSString*
ecCommandName()
{
NSString *name;
name = [cmdDefs stringForKey: @"CommandName"];
if (nil == name)
{
name = @"Command";
}
return name;
}
NSString *cmdBasicDbg = @"basicMode";
NSString *cmdDefaultDbg = @"basicMode"; // Allow older code to link
NSString *cmdConnectDbg = @"connectMode";
NSString *cmdDetailDbg = @"detailMode";
static int comp_len = 0;
static int
comp(const char* s0, const char* s1)
{
comp_len = 0;
if (s0 == 0) {
s0 = "";
}
if (s1 == 0) {
s1 = "";
}
while (*s0) {
if (*s0 != *s1) {
char c0 = islower(*s0) ? toupper(*s0) : *s0;
char c1 = islower(*s1) ? toupper(*s1) : *s1;
if (c0 != c1) {
if (c0 != '\0') {
comp_len = -1; /* s0 is not a substring of s1. */
}
return(-1);
}
}
comp_len++;
s0++;
s1++;
}
if (*s0 != *s1) {
return(-1);
}
return(0);
}
static NSString*
findMode(NSDictionary* d, NSString* s)
{
NSArray *a = [d allKeys];
NSString *o;
unsigned int i;
const char *s0 = [s UTF8String];
const char *s1;
int best_pos = -1;
int best_len = 0;
for (i = 0; i < [a count]; i++)
{
o = (NSString*)[a objectAtIndex: i];
s1 = [o UTF8String];
if (comp(s0, s1) == 0)
{
return o;
}
if (comp_len > best_len)
{
best_len = comp_len;
best_pos = i;
}
}
if (best_pos >= 0)
{
return (NSString*)[a objectAtIndex: best_pos];
}
return nil;
}
/*
* Auxiliary object representing a remote server a subclass might need
* to connect to. This class is for EcProcess.m internal use.
*/
@interface RemoteServer : NSObject
{
/* This is the string which identifies this server */
NSString *defaultName;
/* These are the actual name and host for this server, as obtained
by configuration for the `defaultName' server */
NSString *name;
NSString *host;
/* The same for multiple servers */
NSArray *multiple;
/* The real object representing the remote server. */
id proxy;
/* An object responding to cmdMadeConnectionToServer: and/or
cmdLostConnectionToServer: */
id delegate;
}
/* Initialize the object - string is the default server name */
- (id) initWithDefaultName: (NSString *)string
delegate: (id)object;
- (NSString *) defaultName;
- (void) setName: (NSString *)string;
- (NSString *) name;
- (void) setHost: (NSString *)string;
- (void) setMultiple: (NSArray*)config;
- (NSArray*) multiple;
/*
* Return a proxy to the remote server; create one if needed by making
* a connection, using name and host.
* If the server is multiple, create a EcBroadcastProxy object, and returns
* that object.
*/
- (id) proxy;
/*
* Internal connection management methods
*/
- (id) connectionBecameInvalid: (NSNotification*)notification;
- (BOOL) connection: (NSConnection*)ancestor
shouldMakeNewConnection: (NSConnection*)newConn;
- (void) BCP: (EcBroadcastProxy *)proxy
lostConnectionToServer: (NSString *)name
host: (NSString *)host;
- (void) BCP: (EcBroadcastProxy *)proxy
madeConnectionToServer: (NSString *)name
host: (NSString *)host;
/*
* Returns YES if the connection is ALIVE, NO if the connection is DEAD
*/
- (BOOL) isConnected;
- (NSString *)description;
- (void) update;
@end
@implementation RemoteServer
- (id) initWithDefaultName: (NSString *)string
delegate: (id)object
{
self = [super init];
if (self != nil)
{
ASSIGNCOPY(defaultName, string);
ASSIGN(name, defaultName);
host = @"*";
multiple = nil;
proxy = nil;
delegate = object;
/*
* Grab configuration information.
*/
[self update];
}
return self;
}
- (void) dealloc
{
DESTROY(defaultName);
DESTROY(name);
DESTROY(host);
DESTROY(multiple);
DESTROY(proxy);
[[NSNotificationCenter defaultCenter] removeObserver: self];
[super dealloc];
}
- (NSString *) defaultName
{
return defaultName;
}
- (void) setName: (NSString *)string
{
if ([name isEqual: string] == NO)
{
ASSIGNCOPY(name, string);
DESTROY(proxy);
}
}
- (NSString *) name
{
return name;
}
- (void) setHost: (NSString *)string
{
if ([host isEqual: string] == NO)
{
ASSIGNCOPY(host, string);
DESTROY(proxy);
}
}
- (NSString *) host
{
return host;
}
- (void) setMultiple: (NSArray *)config
{
if ([multiple isEqual: config] == NO)
{
ASSIGNCOPY(multiple, config);
DESTROY(proxy);
}
}
- (NSArray*) multiple
{
return multiple;
}
- (id) proxy
{
if (nil == proxy)
{
if (nil == multiple)
{
[EcProc cmdDbg: cmdConnectDbg
msg: @"Looking for service %@ on host %@", name, host];
proxy = [NSConnection rootProxyForConnectionWithRegisteredName: name
host: host
usingNameServer: [NSSocketPortNameServer sharedInstance]];
if (proxy != nil)
{
id connection = [proxy connectionForProxy];
RETAIN (proxy);
[connection setDelegate: self];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(connectionBecameInvalid:)
name: NSConnectionDidDieNotification
object: connection];
if ([delegate respondsToSelector:
@selector(cmdMadeConnectionToServer:)] == YES)
{
[delegate cmdMadeConnectionToServer: defaultName];
}
[EcProc cmdDbg: cmdConnectDbg
msg: @"Connected to %@ server on host %@",
name, host];
}
else
{
[EcProc cmdDbg: cmdConnectDbg
msg: @"Failed to contact %@ server on host %@",
name, host];
}
}
else /* a multiple server */
{
proxy = [[EcBroadcastProxy alloc] initWithReceivers: multiple];
[proxy BCPsetDelegate: self];
}
}
return proxy;
}
- (id) connectionBecameInvalid: (NSNotification*)notification
{
id connection = [notification object];
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: NSConnectionDidDieNotification
object: connection];
if ([connection isKindOfClass: [NSConnection class]])
{
if (connection == [proxy connectionForProxy])
{
[EcProc cmdDbg: cmdConnectDbg
msg: @"lost connection - clearing %@.",
name];
if ([delegate respondsToSelector:
@selector(cmdLostConnectionToServer:)] == YES)
{
[delegate cmdLostConnectionToServer: defaultName];
}
RELEASE (proxy);
proxy = nil;
}
}
else
{
[self error: "non-Connection sent invalidation"];
}
return self;
}
/* Debugging purposes only */
- (BOOL) connection: (NSConnection*)ancestor
shouldMakeNewConnection: (NSConnection*)newConn
{
[EcProc cmdDbg: cmdConnectDbg
msg: @"New connection 0x%p created", newConn];
return YES;
}
- (BOOL) isConnected
{
if (proxy != nil)
{
return YES;
}
else
{
return NO;
}
}
- (NSString*) description
{
if (multiple == nil)
{
NSString *status;
if (proxy != nil)
{
status = @"LIVE";
}
else
{
status = @"DEAD";
}
return [NSString stringWithFormat:
@"Connection to server `%@' on host `%@' is %@",
name, host, status];
}
else /* multiple server */
{
if (proxy == nil)
{
return [NSString stringWithFormat:
@"Multiple connection to servers %@\n"
@" has not yet been initialized",
multiple];
}
else
{
return [proxy BCPstatus];
}
}
}
- (void) BCP: (EcBroadcastProxy*)proxy
lostConnectionToServer: (NSString*)name
host: (NSString*)host
{
if ([delegate respondsToSelector:
@selector(cmdLostConnectionToServer:)] == YES)
{
/* FIXME: How do we inform delegate of this ? Is it of any use ? */
// [delegate cmdLostConnectionToServer: defaultName];
}
}
- (void) BCP: (EcBroadcastProxy*)proxy
madeConnectionToServer: (NSString*)name
host: (NSString*)host
{
if ([delegate respondsToSelector:
@selector(cmdLostConnectionToServer:)] == YES)
{
/* FIXME: How do we inform delegate of this ? Is it of any use ? */
//[delegate cmdMadConnectionToServer: defaultName];
}
}
- (void) update
{
NSString *configKey;
id configValue;
configKey = [defaultName stringByAppendingString: @"Name"];
configValue = [cmdDefs stringForKey: configKey];
if (nil != configValue)
{
[self setName: configValue];
}
configKey = [defaultName stringByAppendingString: @"Host"];
configValue = [cmdDefs stringForKey: configKey];
if (nil != configValue)
{
[self setHost: configValue];
}
configKey = [defaultName stringByAppendingString: @"BroadCast"];
configValue = [cmdDefs arrayForKey: configKey];
if (nil != configValue)
{
[self setMultiple: configValue];
}
}
@end
@interface EcProcess (Defaults)
- (void) _defMemory: (id)val;
- (void) _defRelease: (id)val;
- (void) _defTesting: (id)val;
@end
@interface EcProcess (Private)
- (void) cmdMesgrelease: (NSArray*)msg;
- (void) cmdMesgtesting: (NSArray*)msg;
- (void) _memCheck;
- (NSString*) _moveLog: (NSString*)name to: (NSString*)sub;
- (void) _timedOut: (NSTimer*)timer;
- (void) _update: (NSMutableDictionary*)info;
@end
@implementation EcProcess
+ (void) atExit
{
if ([NSObject shouldCleanUp])
{
DESTROY(EcProc);
DESTROY(EcProcConnection);
DESTROY(alarmDestination);
DESTROY(alertLogger);
DESTROY(auditLogger);
DESTROY(cmdActions);
DESTROY(cmdConf);
DESTROY(cmdDebugKnown);
DESTROY(cmdDebugModes);
DESTROY(cmdDebugName);
DESTROY(cmdDefs);
DESTROY(cmdFirst);
DESTROY(cmdInst);
DESTROY(cmdLast);
DESTROY(cmdLogMap);
DESTROY(cmdName);
DESTROY(cmdOperators);
DESTROY(cmdPTimer);
DESTROY(cmdServer);
DESTROY(cmdUser);
DESTROY(dataDir);
DESTROY(debugLogger);
DESTROY(ecLock);
DESTROY(errorLogger);
DESTROY(homeDir);
DESTROY(hostName);
DESTROY(noNetConfig);
DESTROY(replyBuffer);
DESTROY(servers);
DESTROY(started);
DESTROY(userDir);
DESTROY(warningLogger);
DESTROY(cmdMemoryLogger);
}
}
- (Class)_memoryLoggerClassFromBundle: (NSString*)bundleName
{
NSString *path = nil;
Class c = Nil;
NSBundle *bundle = nil;
NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSAllDomainsMask,
YES);
NSEnumerator *e = [paths objectEnumerator];
while (nil != (path = [e nextObject]))
{
path = [path stringByAppendingPathComponent: @"Bundles"];
path = [path stringByAppendingPathComponent: bundleName];
path = [path stringByAppendingPathExtension: @"bundle"];
bundle = [NSBundle bundleWithPath: path];
if (bundle != nil)
{
break;
}
}
if (nil == bundle)
{
[self cmdWarn: @"Could not load bundle '%@'", bundleName];
}
else if (Nil == (c = [bundle principalClass]))
{
[self cmdWarn: @"Could not load principal class from %@ at %@.",
bundleName, path];
}
else if (NO == [c conformsToProtocol: @protocol(EcMemoryLogger)])
{
[self cmdWarn:
@"%@ does not implement the EcMemoryLogger protocol",
NSStringFromClass(c)];
c = Nil;
}
return c;
}
+ (NSMutableDictionary*) ecInitialDefaults
{
NSProcessInfo *pi;
id objects[2];
id keys[2];
NSString *prefix;
pi = [NSProcessInfo processInfo];
objects[0] = [pi processName];
objects[1] = @".";
prefix = EC_DEFAULTS_PREFIX;
if (nil == prefix)
{
prefix = @"";
}
keys[0] = [prefix stringByAppendingString: @"ProgramName"];
keys[1] = [prefix stringByAppendingString: @"HomeDirectory"];
return [NSMutableDictionary dictionaryWithObjects: objects
forKeys: keys
count: 2];
}
+ (void) ecRegisterDefault: (NSString*)name
withTypeText: (NSString*)type
andHelpText: (NSString*)help
action: (SEL)cmd
{
[EcDefaultRegistration registerDefault: name
withTypeText: type
andHelpText: help
action: cmd
value: nil];
}
+ (void) ecRegisterDefault: (NSString*)name
withTypeText: (NSString*)type
andHelpText: (NSString*)help
action: (SEL)cmd
value: (id)value
{
[EcDefaultRegistration registerDefault: name
withTypeText: type
andHelpText: help
action: cmd
value: value];
}
+ (void) ecSetup
{
if (nil != EcProc)
{
[NSException raise: NSGenericException
format: @"+ecSetup called when EcProcess is already set up"];
}
[[self alloc] init];
}
- (void) _commandRemove
{
id connection = [cmdServer connectionForProxy];
if (nil != connection)
{
[connection setDelegate: nil];
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: NSConnectionDidDieNotification
object: connection];
[connection invalidate];
}
DESTROY(cmdServer);
}
- (void) _connectionRegistered
{
[alarmDestination domanage: nil];
}
static NSString *noFiles = @"No log files to archive";
- (NSString*) cmdBase
{
return cmdBase;
}
- (id) cmdConfig: (NSString*)key
{
return [cmdDefs objectForKey: key];
}
- (NSString*) cmdDataDirectory
{
if (dataDir == nil)
{
NSFileManager *mgr = [NSFileManager defaultManager];
NSString *str = cmdUserDir();
BOOL flag;
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
if ([mgr createDirectoryAtPath: str
withIntermediateDirectories: YES
attributes: nil
error: NULL] == NO)
{
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
NSLog(@"Unable to create directory - %@", str);
return nil;
}
}
else
{
flag = YES;
}
}
if (flag == NO)
{
NSLog(@"The path '%@' is not a directory", str);
return nil;
}
str = [str stringByAppendingPathComponent: @"Data"];
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
if ([mgr createDirectoryAtPath: str
withIntermediateDirectories: YES
attributes: nil
error: NULL] == NO)
{
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
NSLog(@"Unable to create directory - %@", str);
return nil;
}
}
else
{
flag = YES;
}
}
if (flag == NO)
{
NSLog(@"The path '%@' is not a directory", str);
return nil;
}
if (homeDir != nil)
{
str = [str stringByAppendingPathComponent: homeDir];
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
if ([mgr createDirectoryAtPath: str
withIntermediateDirectories: YES
attributes: nil
error: NULL] == NO)
{
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
NSLog(@"Unable to create directory - %@", str);
return nil;
}
}
else
{
flag = YES;
}
}
if (flag == NO)
{
NSLog(@"The path '%@' is not a directory", str);
return nil;
}
}
ASSIGNCOPY(dataDir, str);
}
return dataDir;
}
- (NSUserDefaults*) cmdDefaults
{
return cmdDefs;
}
- (void) cmdDefaultsChanged: (NSNotification*)n
{
NSEnumerator *enumerator;
NSDictionary *dict;
NSString *mode;
NSString *str;
int i;
[EcDefaultRegistration defaultsChanged: cmdDefs];
enumerator = [cmdDebugKnown keyEnumerator];
while (nil != (mode = [enumerator nextObject]))
{
NSString *key = [@"Debug-" stringByAppendingString: mode];
if (YES == [cmdDefs boolForKey: key])
{
[cmdDebugModes addObject: mode];
}
else
{
[cmdDebugModes removeObject: mode];
}
}
dict = [cmdDefs dictionaryForKey: @"WellKnownHostNames"];
if (nil != dict)
{
[NSHost setWellKnownNames: dict];
[ecLock lock];
ASSIGN(hostName, [[NSHost currentHost] wellKnownName]);
[ecLock unlock];
}
if ((str = [cmdDefs stringForKey: @"CmdInterval"]) != nil)
{
[self setCmdInterval: [str floatValue]];
}
#ifndef __MINGW__
descriptorsMaximum = [cmdDefs integerForKey: @"DescriptorsMaximum"];
#endif
memAllowed = (uint64_t)[cmdDefs integerForKey: @"MemoryAllowed"];
#if SIZEOF_VOIDP == 4
if (memAllowed >= 4*1024)
{
[self cmdError: @"MemoryAllowed (%"PRIu64" too large for 32bit machine..."
@" using 0", memAllowed];
memAllowed = 0;
}
#endif
memMaximum = (uint64_t)[cmdDefs integerForKey: @"MemoryMaximum"];
#if SIZEOF_VOIDP == 4
if (memMaximum >= 4*1024)
{
[self cmdError: @"MemoryMaximum (%"PRIu64" too large for 32bit machine..."
@" using 0", memAllowed];
memMaximum = 0; // Disabled
}
#endif
str = [cmdDefs stringForKey: @"CoreSize"];
if (nil == str)
{
i = 2*1024; // 2 GB default
}
else
{
i = [str intValue];
if (i < 0)
{
i = -1; // unlimited
}
}
if (i != coreSize)
{
struct rlimit rlim;
rlim_t want;
coreSize = i;
if (coreSize < 0)
{
want = RLIM_INFINITY;
}
else
{
want = i * 1024 * 1024;
}
if (getrlimit(RLIMIT_CORE, &rlim) < 0)
{
NSLog(@"Unable to get core file size limit: %d", errno);
}
else
{
if (RLIM_INFINITY != rlim.rlim_max && rlim.rlim_max < want)
{
int maxMB = (int)(rlim.rlim_max/(1024*1024));
if (RLIM_INFINITY == want)
{
NSLog(@"Hard limit for core file size (%dMB)"
@" less than requested (unlimited); using %dMB.",
maxMB, maxMB);
}
else
{
NSLog(@"Hard limit for core file size (%dMB)"
@" less than requested (%dMB); using %dMB.",
maxMB, coreSize, maxMB);
}
want = rlim.rlim_max;
}
rlim.rlim_cur = want;
if (setrlimit(RLIMIT_CORE, &rlim) < 0)
{
if (coreSize > 0)
{
NSLog(@"Unable to set core file size limit to %uMB"
@", errno: %d", coreSize, errno);
}
else if (coreSize < 0)
{
NSLog(@"Unable to set core file size unlimited"
@", errno: %d", errno);
}
else
{
NSLog(@"Unable to set core dumps disabled"
@", errno: %d", errno);
}
}
}
}
if (servers != nil)
{
NSEnumerator *e;
RemoteServer *server;
e = [servers objectEnumerator];
while ((server = [e nextObject]))
{
[server update];
}
}
}
- (NSString*) cmdInstance
{
return cmdInst;
}
- (BOOL) cmdIsDaemon
{
return cmdFlagDaemon;
}
- (BOOL) cmdIsTesting
{
return cmdFlagTesting;
}
- (NSDate*) cmdLastIP
{
if (0.0 == lastIP)
{
return nil;
}
return [dateClass dateWithTimeIntervalSinceReferenceDate: lastIP];
}
- (NSDate*) cmdLastOP
{
if (0.0 == lastOP)
{
return nil;
}
return [dateClass dateWithTimeIntervalSinceReferenceDate: lastOP];
}
- (void) cmdLogEnd: (NSString*)name
{
NSFileHandle *hdl;
if ([name length] == 0)
{
NSLog(@"Attempt to end log with empty filename");
return;
}
name = [name lastPathComponent];
[self ecDoLock];
hdl = [cmdLogMap objectForKey: name];
if (hdl != nil)
{
NSString *path;
NSDictionary *attr;
NSFileManager *mgr;
/* Ensure that all data is written to file, then close it unless it's
* stderr (which we must keep open for logging at all times).
*/
fflush(stderr);
if ([hdl fileDescriptor] != 2)
{
NS_DURING
[hdl closeFile];
NS_HANDLER
NS_ENDHANDLER
}
/*
* If the file is empty, remove it, otherwise move to archive directory.
*/
path = [cmdLogsDir(nil) stringByAppendingPathComponent: name];
mgr = [NSFileManager defaultManager];
attr = [mgr fileAttributesAtPath: path traverseLink: NO];
if ([[attr objectForKey: NSFileSize] intValue] == 0)
{
[mgr removeFileAtPath: path handler: nil];
}
else
{
NSDate *when;
NSString *where;
when = [NSDate date];
where = [when descriptionWithCalendarFormat: @"%Y-%m-%d"
timeZone: nil locale: nil];
if (where != nil)
{
[self _moveLog: name to: where];
}
}
/*
* Unregister filename.
*/
[cmdLogMap removeObjectForKey: name];
}
[self ecUnLock];
}
- (NSFileHandle*) cmdLogFile: (NSString*)name
{
NSFileHandle *hdl;
NSString *status = nil;
if ([name length] == 0)
{
NSLog(@"Attempt to log with empty filename");
return nil;
}
name = [name lastPathComponent];
[self ecDoLock];
hdl = [cmdLogMap objectForKey: name];
if (hdl == nil)
{
NSFileManager *mgr = [NSFileManager defaultManager];
NSString *path;
path = [cmdLogsDir(nil) stringByAppendingPathComponent: name];
if ([mgr fileExistsAtPath: path] == YES)
{
NSDictionary *attr;
NSDate *when;
NSString *where;
attr = [mgr fileAttributesAtPath: path traverseLink: NO];
when = [attr objectForKey: NSFileModificationDate];
where = [when descriptionWithCalendarFormat: @"%Y-%m-%d"
timeZone: nil locale: nil];
if (where != nil)
{
status = [self _moveLog: name to: where];
}
}
/*
* Create the file if necessary, and open it for updating.
*/
if ([mgr isWritableFileAtPath: path] == NO
&& [mgr createFileAtPath: path contents: nil attributes: nil] == NO)
{
NSLog(@"File '%@' is not writable and can't be created", path);
}
else
{
hdl = [NSFileHandle fileHandleForUpdatingAtPath: path];
if (hdl == nil)
{
if (status != nil)
{
NSLog(@"%@", status);
}
NSLog(@"Unable to log to %@", path);
}
else
{
[hdl seekToEndOfFile];
}
}
if (hdl == nil)
{
[self ecUnLock];
return nil;
}
/*
* As a special case, if this is the default debug file
* we must set it up to write to stderr.
*/
if (NO == cmdKeepStderr && [name isEqual: cmdDebugName] == YES)
{
int desc;
desc = [hdl fileDescriptor];
if (desc != 2)
{
dup2(desc, 2);
NS_DURING
[hdl closeFile];
NS_HANDLER
NS_ENDHANDLER
hdl = [NSFileHandle fileHandleWithStandardError];
}
}
/*
* Store the file handle in the dictionary for later use.
*/
[cmdLogMap setObject: hdl forKey: name];
if (status != nil)
{
NSLog(@"%@", status);
}
}
[hdl retain];
[self ecUnLock];
return [hdl autorelease];
}
- (void) cmdLostConnectionToServer: (NSString*)name
{
return;
}
- (void) cmdMadeConnectionToServer: (NSString*)name
{
return;
}
- (NSString*) cmdName
{
return cmdName;
}
- (int) cmdSignalled
{
return cmdSignalled;
}
- (void) ecLoggersChanged: (NSNotification*)n
{
DESTROY(alertLogger);
DESTROY(auditLogger);
DESTROY(debugLogger);
DESTROY(errorLogger);
DESTROY(warningLogger);
}
- (NSDate*) ecStarted
{
return started;
}
- (oneway void) alarm: (in bycopy EcAlarm*)event
{
[alarmDestination alarm: event];
}
- (EcAlarm*) alarmConfigurationFor: (NSString*)managedObject
specificProblem: (NSString*)specificProblem
additionalText: (NSString*)additionalText
critical: (BOOL)isCritical
{
EcAlarmSeverity severity;
NSString *action;
EcAlarm *a;
if (YES == isCritical)
{
severity = EcAlarmSeverityCritical;
}
else
{
severity = EcAlarmSeverityMajor;
}
action = @"Check/correct configuration"; // FIXME ... localize
a = [EcAlarm alarmForManagedObject: managedObject
at: nil
withEventType: EcAlarmEventTypeProcessingError
probableCause: EcAlarmConfigurationOrCustomizationError
specificProblem: specificProblem
perceivedSeverity: severity
proposedRepairAction: action
additionalText: additionalText];
[self alarm: a];
return a;
}
- (NSArray*) alarms
{
return [alarmDestination alarms];
}
- (void) clearConfigurationFor: (NSString*)managedObject
specificProblem: (NSString*)specificProblem
additionalText: (NSString*)additionalText
{
EcAlarm *a;
a = [EcAlarm alarmForManagedObject: managedObject
at: nil
withEventType: EcAlarmEventTypeProcessingError
probableCause: EcAlarmConfigurationOrCustomizationError
specificProblem: specificProblem
perceivedSeverity: EcAlarmSeverityCleared
proposedRepairAction: nil
additionalText: additionalText];
[self alarm: a];
}
- (oneway void) domanage: (in bycopy NSString*)managedObject
{
[alarmDestination domanage: managedObject];
}
- (oneway void) unmanage: (in bycopy NSString*)managedObject
{
[alarmDestination unmanage: managedObject];
}
- (int) processIdentifier
{
static int pi = 0;
if (0 == pi)
{
pi = [[NSProcessInfo processInfo] processIdentifier];
}
return pi;
}
- (void) setCmdInterval: (NSTimeInterval)interval
{
if (interval > 300.0)
{
NSLog(@"Ignored attempt to set timer interval to %g ... using 60.0", interval);
interval = 60.0;
}
if (interval < 0.001)
{
NSLog(@"Ignored attempt to set timer interval to %g ... using 10.0", interval);
interval = 10.0;
}
if (interval != cmdTimInterval)
{
cmdTimInterval = interval;
[self triggerCmdTimeout];
}
}
- (NSString*) ecCopyright
{
return @"";
}
- (void) ecDoLock
{
[ecLock lock];
}
- (void) ecUnLock
{
[ecLock unlock];
}
+ (void) initialize
{
if (nil == ecLock)
{
ecLock = [NSRecursiveLock new];
dateClass = [NSDate class];
cDateClass = [NSCalendarDate class];
stringClass = [NSString class];
cmdLogMap = [[NSMutableDictionary alloc] initWithCapacity: 4];
cmdDebugModes = [[NSMutableSet alloc] initWithCapacity: 4];
cmdDebugKnown = [[NSMutableDictionary alloc] initWithCapacity: 4];
[cmdDebugKnown setObject: @"Mode for distributed object connections"
forKey: cmdConnectDbg];
[cmdDebugKnown setObject: @"Standard mode for basic debug information"
forKey: cmdBasicDbg];
[cmdDebugKnown setObject: @"Detailed but general purpose debugging"
forKey: cmdDetailDbg];
[cmdDebugModes addObject: cmdBasicDbg];
[self ecRegisterDefault: @"Memory"
withTypeText: @"YES/NO"
andHelpText: @"Enable memory allocation checks"
action: @selector(_defMemory:)
value: @"YES"];
[self ecRegisterDefault: @"Release"
withTypeText: @"YES/NO"
andHelpText: @"Turn on double release checks (debug)"
action: @selector(_defRelease:)
value: @"NO"];
[self ecRegisterDefault: @"Testing"
withTypeText: @"YES/NO"
andHelpText: @"Run in test mode (if supported)"
action: @selector(_defTesting:)
value: @"NO"];
/*
* Set the timeouts for the default connection so that
* they will be inherited by other connections.
* A two minute timeout is long enough for almost all
* circumstances.
*/
[[NSConnection defaultConnection] setRequestTimeout: 120.0];
[[NSConnection defaultConnection] setReplyTimeout: 120.0];
[self registerAtExit];
}
}
- (void) addServerToList: (NSString *)serverName
{
[self addServerToList: serverName for: nil];
}
- (void) addServerToList: (NSString *)serverName for: (id)anObject
{
RemoteServer *remote;
if ((serverName == nil)
|| ([serverName isKindOfClass: [NSString class]] == NO))
{
NSLog (@"Warning: invalid string passed to addServerToList:for:");
return;
}
if (anObject == nil)
{
anObject = self;
}
if (servers == nil)
{
servers = [[NSMutableDictionary alloc] initWithCapacity: 2];
}
remote = [[RemoteServer alloc] initWithDefaultName: serverName
delegate: anObject];
[servers setObject: remote forKey: serverName];
[remote release];
}
- (void) removeServerFromList: (NSString *)serverName
{
if ((serverName == nil)
|| ([serverName isKindOfClass: [NSString class]] == NO))
{
NSLog (@"Warning: invalid array passed to removeServerFromList:");
return;
}
[servers removeObjectForKey: serverName];
}
- (id) cmdConnectionBecameInvalid: (NSNotification*)notification
{
id connection;
connection = [notification object];
[connection setDelegate: nil];
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: NSConnectionDidDieNotification
object: connection];
if (cmdServer != nil && connection == [cmdServer connectionForProxy])
{
[alarmDestination setDestination: nil];
DESTROY(cmdServer);
NSLog(@"lost connection 0x%p to command server\n", connection);
/*
* Cause timeout to go off really soon so we will try to
* re-establish the link to the server.
*/
[self triggerCmdTimeout];
}
else
{
NSLog(@"unknown connection sent invalidation\n");
}
return self;
}
- (void) cmdAlert: (NSString*)fmt arguments: (va_list)args
{
if (nil == alertLogger)
{
alertLogger = [[EcLogger loggerForType: LT_ALERT] retain];
}
[alertLogger log: fmt arguments: args];
}
- (void) cmdAlert: (NSString*)fmt, ...
{
va_list ap;
va_start (ap, fmt);
[self cmdAlert: fmt arguments: ap];
va_end (ap);
}
- (NSString*) cmdArchive: (NSString*)subdir
{
NSString *status = @"";
if ([cmdLogMap count] == 0)
{
status = noFiles;
}
else
{
NSEnumerator *enumerator;
NSString *name;
[self ecDoLock];
enumerator = [[cmdLogMap allKeys] objectEnumerator];
[self ecUnLock];
if (subdir == nil)
{
NSCalendarDate *when = [NSCalendarDate date];
int y, m, d;
y = [when yearOfCommonEra];
m = [when monthOfYear];
d = [when dayOfMonth];
subdir = [stringClass stringWithFormat: @"%04d-%02d-%02d", y, m, d];
}
while ((name = [enumerator nextObject]) != nil)
{
NSString *s;
s = [self _moveLog: name to: subdir];
if ([status length] > 0)
status = [status stringByAppendingString: @"\n"];
status = [status stringByAppendingString: s];
[self cmdLogEnd: name];
if (cmdIsQuitting == NO)
{
[self cmdLogFile: name];
}
}
}
return status;
}
- (void) cmdAudit: (NSString*)fmt arguments: (va_list)args
{
if (nil == auditLogger)
{
auditLogger = [[EcLogger loggerForType: LT_AUDIT] retain];
}
[auditLogger log: fmt arguments: args];
}
- (void) cmdAudit: (NSString*)fmt, ...
{
va_list ap;
va_start (ap, fmt);
[self cmdAudit: fmt arguments: ap];
va_end (ap);
}
- (void) cmdDbg: (NSString*)type msg: (NSString*)fmt arguments: (va_list)args
{
if (nil != [cmdDebugModes member: type])
{
if (nil == debugLogger)
{
debugLogger = [[EcLogger loggerForType: LT_DEBUG] retain];
}
[debugLogger log: fmt arguments: args];
}
}
- (void) cmdDbg: (NSString*)type msg: (NSString*)fmt, ...
{
va_list ap;
va_start (ap, fmt);
[self cmdDbg: type msg: fmt arguments: ap];
va_end (ap);
}
- (void) cmdDebug: (NSString*)fmt arguments: (va_list)args
{
if (nil != [cmdDebugModes member: cmdBasicDbg])
{
if (nil == debugLogger)
{
debugLogger = [[EcLogger loggerForType: LT_DEBUG] retain];
}
[debugLogger log: fmt arguments: args];
}
}
- (void) cmdDebug: (NSString*)fmt, ...
{
va_list ap;
va_start (ap, fmt);
[self cmdDebug: fmt arguments: ap];
va_end (ap);
}
- (void) cmdError: (NSString*)fmt arguments: (va_list)args
{
if (nil == errorLogger)
{
errorLogger = [[EcLogger loggerForType: LT_ERROR] retain];
}
[errorLogger log: fmt arguments: args];
}
- (void) cmdError: (NSString*)fmt, ...
{
va_list ap;
va_start (ap, fmt);
[self cmdError: fmt arguments: ap];
va_end (ap);
}
- (void) cmdFlushLogs
{
[alertLogger flush];
[auditLogger flush];
[debugLogger flush];
[errorLogger flush];
[warningLogger flush];
}
- (NSTimeInterval) cmdInterval
{
return cmdTimInterval;
}
- (BOOL) cmdIsClient
{
return YES;
}
- (void) log: (NSString*)message type: (EcLogType)t
{
switch (t)
{
case LT_DEBUG:
[self cmdDebug: @"%@", message];
break;
case LT_WARNING:
[self cmdWarn: @"%@", message];
break;
case LT_ERROR:
[self cmdError: @"%@", message];
break;
case LT_ALERT:
[self cmdAlert: @"%@", message];
break;
case LT_AUDIT:
[self cmdAudit: @"%@", message];
break;
default:
[self cmdError: @"%@", message];
break;
}
}
- (NSMutableDictionary*) cmdOperator: (NSString*)name password: (NSString*)pass
{
NSMutableDictionary *d = (NSMutableDictionary*)cmdOperators;
if (d == nil || [d isKindOfClass: [NSDictionary class]] == NO)
{
return nil;
}
d = [d objectForKey: name];
if (d == nil || [d isKindOfClass: [NSDictionary class]] == NO)
{
return nil;
}
d = [d mutableCopy];
if (pass != nil && [[d objectForKey: @"Password"] isEqual: pass] == YES)
{
[d setObject: @"yes" forKey: @"Password"];
}
else
{
[d setObject: @"no" forKey: @"Password"];
}
return AUTORELEASE(d);
}
- (id) cmdNewServer
{
static BOOL connecting = NO;
if (NO == connecting)
{
/*
* Use the 'cmdLast' variable to ensure that we don't try to
* check memory usage or connect to the command server more
* than once every 10 sec.
*/
if (cmdLast == nil || [cmdLast timeIntervalSinceNow] < -10.0)
{
int mayRetry;
connecting = YES;
/* The first time we try to connect to the Command server
* (on startup) we should retry for several seconds in case
* the whole system is coming up and the Command server has
* not yet been started.
*/
mayRetry = (nil == cmdLast ? 10 : 0);
ASSIGN(cmdLast, [dateClass date]);
if (cmdFirst == nil)
{
ASSIGN(cmdFirst, cmdLast);
}
if (cmdServer == nil && YES == [self cmdIsClient])
{
NSString *name = nil;
NSString *host = nil;
id proxy;
NS_DURING
{
NSSocketPortNameServer *ns;
host = ecCommandHost();
name = ecCommandName();
ns = [NSSocketPortNameServer sharedInstance];
proxy = [NSConnection
rootProxyForConnectionWithRegisteredName: name
host: host
usingNameServer: ns];
while (nil == proxy && mayRetry-- > 0)
{
[NSThread sleepForTimeInterval: 1.0];
proxy = [NSConnection
rootProxyForConnectionWithRegisteredName: name
host: host
usingNameServer: ns];
}
}
NS_HANDLER
{
proxy = nil;
NSLog(@"Exception connecting to Command server %@ on %@): %@",
name, host, localException);
}
NS_ENDHANDLER
if (proxy != nil)
{
NSMutableDictionary *r = nil;
[proxy setProtocolForProxy: @protocol(Command)];
NS_DURING
{
NSData *d;
d = [proxy registerClient: self
name: cmdLogName()
transient: cmdIsTransient];
r = [NSPropertyListSerialization
propertyListWithData: d
options: NSPropertyListMutableContainers
format: 0
error: 0];
}
NS_HANDLER
{
r = [NSMutableDictionary dictionaryWithCapacity: 1];
[r setObject: [localException reason]
forKey: @"rejected"];
NSLog(@"Caught exception registering with Command: %@",
localException);
}
NS_ENDHANDLER
/* We could be rejected or told to back off,
* otherwise we continue as normal.
*/
if (r != nil && [r objectForKey: @"rejected"] != nil)
{
NSLog(@"Rejected by Command - %@",
[r objectForKey: @"rejected"]);
cmdIsQuitting = YES;
[self cmdQuit: 0]; /* Rejected by server. */
}
else if (nil == r || nil == [r objectForKey: @"back-off"])
{
NSConnection *connection;
cmdServer = [proxy retain];
connection = [cmdServer connectionForProxy];
[connection enableMultipleThreads];
if (nil == alarmDestination)
{
alarmDestination = [EcAlarmDestination new];
}
[alarmDestination setDestination: cmdServer];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(cmdConnectionBecameInvalid:)
name: NSConnectionDidDieNotification
object: connection];
[self _update: r];
/* If we just connected to the command server,
* and we have a registered connection, then we
* can tell it that any alarm for failure to
* register must be cleared.
*/
if (nil != cmdServer && [EcProcConnection isValid])
{
[self _connectionRegistered];
}
}
}
}
connecting = NO;
}
else if (cmdServer == nil && YES == [self cmdIsClient])
{
NSLog(@"Unable to connect to Command server ... not retry time yet");
}
}
return cmdServer;
}
- (void) cmdUnregister
{
if (nil != cmdServer)
{
NS_DURING
{
[cmdServer unregisterByObject: self];
}
NS_HANDLER
{
[self _commandRemove];
NSLog(@"Caught exception unregistering from Command: %@",
localException);
}
NS_ENDHANDLER
[self _commandRemove];
}
}
- (void) cmdWarn: (NSString*)fmt arguments: (va_list)args
{
if (nil == warningLogger)
{
warningLogger = [[EcLogger loggerForType: LT_WARNING] retain];
}
[warningLogger log: fmt arguments: args];
}
- (void) cmdWarn: (NSString*)fmt, ...
{
va_list ap;
va_start (ap, fmt);
[self cmdWarn: fmt arguments: ap];
va_end (ap);
}
- (void) ecNewDay: (NSCalendarDate*)when
{
static NSDictionary *defs = nil;
NSString *sub;
NSDictionary *d;
NSEnumerator *e;
NSString *k;
/* New day ... archive debug/log files into a subdirectory based on
* the current date. This is yesterday's debug, so we use yesterday.
*/
sub = [[when dateByAddingYears: 0 months: 0 days: -1 hours: 0 minutes: 0
seconds: 0] descriptionWithCalendarFormat: @"%Y-%m-%d"];
NSLog(@"%@", [self cmdArchive: sub]);
/* Check information left in the EcCommand domain.
*/
d = [cmdDefs volatileDomainForName: @"EcCommand"];
e = [[d allKeys] objectEnumerator];
while (nil != (k = [e nextObject]))
{
id v = [d objectForKey: k];
if ([v isEqual: [defs objectForKey: k]])
{
[self cmdError: @"The Console defaults override for '%@'"
@" has been left at '%@' for more than a day."
@" Please reset it ('tell %@ defaults delete %@') after updating"
@" Control.plist as required.", k, v, [self cmdName], k];
}
}
ASSIGNCOPY(defs, d);
}
- (void) ecNewHour: (NSCalendarDate*)when
{
return;
}
- (void) ecNewMinute: (NSCalendarDate*)when
{
#ifndef __MINGW__
if (NO == cmdIsQuitting)
{
NSString *shutdown = nil;
int p[2];
if (pipe(p) == 0)
{
if (0 == reservedPipe[1])
{
reservedPipe[0] = p[0];
reservedPipe[1] = p[1];
}
else
{
close(p[0]);
close(p[1]);
}
if (descriptorsMaximum > 0)
{
if (p[0] > descriptorsMaximum || p[1] > descriptorsMaximum)
{
shutdown = [NSString stringWithFormat:
@"Open file descriptor limit (%lu) exceeded",
(unsigned long) descriptorsMaximum];
}
}
}
else
{
shutdown = @"Process ran out of file descriptors";
}
if (nil != shutdown)
{
/* We hope that closing two reserved file descriptors will allow
* us to shut down gracefully and restart.
*/
if (reservedPipe[1] > 0)
{
close(reservedPipe[0]); reservedPipe[0] = 0;
close(reservedPipe[1]); reservedPipe[1] = 0;
}
[self cmdError: @"%@", shutdown];
cmdIsQuitting = YES;
[self cmdQuit: -1];
return;
}
}
#endif
/* We want to be sure we work with reasonably up to date information.
*/
[NSHost flushHostCache];
[self _memCheck];
}
- (void) ecHadIP: (NSDate*)when
{
if (nil == when)
{
lastIP = [dateClass timeIntervalSinceReferenceDate];
}
else
{
lastIP = [when timeIntervalSinceReferenceDate];
}
}
- (void) ecHadOP: (NSDate*)when
{
if (nil == when)
{
lastOP = [dateClass timeIntervalSinceReferenceDate];
}
else
{
lastOP = [when timeIntervalSinceReferenceDate];
}
}
- (NSUInteger) ecNotLeaked
{
return 0;
}
- (int) ecRun
{
NSAutoreleasePool *arp;
NSConnection *c;
NSRunLoop *loop;
NSDate *future;
arp = [NSAutoreleasePool new];
if (YES == cmdIsTransient)
{
[self cmdWarn: @"Attempted to run transient process."];
[self cmdFlushLogs];
[arp release];
return 1;
}
NSAssert(nil == EcProcConnection, NSGenericException);
c = [[NSConnection alloc] initWithReceivePort: (NSPort*)[NSSocketPort port]
sendPort: nil];
[c setRootObject: self];
if ([c registerName: [self cmdName]
withNameServer: [NSSocketPortNameServer sharedInstance]] == NO)
{
EcAlarm *a;
DESTROY(c);
NSLog(@"Unable to register with name server. Perhaps a copy of this process is already running (or is hung or blocked waiting for a database query etc), or perhaps an old version was killed and is still registered. Check the state of any running process and and check the process registration with gdomap.");
a = [EcAlarm alarmForManagedObject: nil
at: nil
withEventType: EcAlarmEventTypeProcessingError
probableCause: EcAlarmSoftwareProgramAbnormallyTerminated
specificProblem: @"Unable to register"
perceivedSeverity: EcAlarmSeverityMajor
proposedRepairAction:
_(@"Check for running copy of process and/or registration in gdomap.")
additionalText: _(@"Process probably already running (possibly hung/delayed) or problem in name registration with distributed objects system (gdomap)")];
[self alarm: a];
[alarmDestination shutdown];
cmdIsQuitting = YES;
[self cmdFlushLogs];
[arp release];
return 2;
}
else
{
EcAlarm *a;
a = [EcAlarm alarmForManagedObject: nil
at: nil
withEventType: EcAlarmEventTypeProcessingError
probableCause: EcAlarmSoftwareProgramAbnormallyTerminated
specificProblem: @"Unable to register"
perceivedSeverity: EcAlarmSeverityCleared
proposedRepairAction: nil
additionalText: nil];
[self alarm: a];
}
[c setDelegate: self];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(cmdConnectionBecameInvalid:)
name: NSConnectionDidDieNotification
object: c];
EcProcConnection = c;
[self _connectionRegistered];
[self cmdAudit: @"Started `%@'", [self cmdName]];
[self cmdFlushLogs];
cmdIsRunning = YES;
loop = [NSRunLoop currentRunLoop];
future = [NSDate distantFuture];
while (YES == [EcProcConnection isValid])
{
NS_DURING
{
NSDate *d = [loop limitDateForMode: NSDefaultRunLoopMode];
if (0 == cmdSignalled)
{
if (nil == d)
{
d = future;
}
[loop acceptInputForMode: NSDefaultRunLoopMode beforeDate: d];
}
if (0 != cmdSignalled)
{
int sig = cmdSignalled;
cmdSignalled = 0;
cmdIsQuitting = YES;
[self cmdQuit: sig];
}
}
NS_HANDLER
{
[self cmdAlert: @"Problem running server: %@", localException];
[NSThread sleepForTimeInterval: 1.0];
}
NS_ENDHANDLER;
[arp emptyPool];
}
[arp release];
/* finish server */
cmdIsQuitting = YES;
[self cmdQuit: 0];
cmdIsRunning = NO;
DESTROY(EcProcConnection);
return 0;
}
- (void) ecTestLog: (NSString*)fmt arguments: (va_list)args
{
if (YES == cmdFlagTesting)
{
NSLogv(fmt, args);
}
}
- (void) ecTestLog: (NSString*)fmt, ...
{
if (YES == cmdFlagTesting)
{
va_list ap;
va_start (ap, fmt);
[self ecTestLog: fmt arguments: ap];
va_end (ap);
}
}
- (NSString*) ecUserDirectory
{
return cmdUserDir();
}
- (void) setCmdDebug: (NSString*)mode withDescription: (NSString*)desc
{
[cmdDebugKnown setObject: desc forKey: mode];
if (YES == [cmdDefs boolForKey: [@"Debug-" stringByAppendingString: mode]])
{
[cmdDebugModes addObject: mode];
}
else
{
[cmdDebugModes removeObject: mode];
}
}
- (void) setCmdTimeout: (SEL)sel
{
cmdTimSelector = sel;
[self triggerCmdTimeout];
}
- (void) triggerCmdTimeout
{
if (NO == [NSThread isMainThread])
{
[self performSelectorOnMainThread: _cmd
withObject: nil
waitUntilDone: NO];
return;
}
if (cmdPTimer != nil)
{
/*
* If the timer is due to go off soon - don't reset it -
* continually resetting could lead to it never firing.
*/
if ([[cmdPTimer fireDate] timeIntervalSinceNow] <= 0.01)
{
return;
}
[cmdPTimer invalidate];
cmdPTimer = nil;
}
cmdPTimer = [NSTimer scheduledTimerWithTimeInterval: 0.001
target: self
selector: @selector(_timedOut:)
userInfo: nil
repeats: NO];
}
- (BOOL) cmdDebugMode: (NSString*)mode
{
if ([cmdDebugModes member: mode] == nil)
return NO;
return YES;
}
- (void) cmdDebugMode: (NSString*)mode active: (BOOL)flag
{
if ((mode = findMode(cmdDebugKnown, mode)) != nil)
{
if (flag == YES && [cmdDebugModes member: mode] == nil)
{
[cmdDebugModes addObject: mode];
}
if (flag == NO && [cmdDebugModes member: mode] != nil)
{
[cmdDebugModes removeObject: mode];
}
}
}
- (oneway void) cmdGnip: (id <CmdPing>)from
sequence: (unsigned)num
extra: (in bycopy NSData*)data
{
[self cmdDbg: cmdConnectDbg msg: @"cmdGnip: %lx sequence: %u extra: %lx",
(unsigned long)from, num, (unsigned long)data];
}
- (BOOL) cmdIsConnected
{
return cmdServer != nil;
}
- (BOOL) cmdMatch: (NSString*)val toKey: (NSString*)key
{
unsigned int len = [val length];
if (len == 0)
{
return NO;
}
if (len > [key length])
{
return NO;
}
if ([key compare: val
options: NSCaseInsensitiveSearch|NSLiteralSearch
range: NSMakeRange(0, len)] != NSOrderedSame)
{
return NO;
}
return YES;
}
- (void) cmdMesgCache
{
NSEnumerator *enumerator;
NSString *name;
/* The cmdActions set contains the names of all the commands this
* instance will accept from the Command server. These are methods
* taking an array of strings as an argument and returning a string
* as their result. All have names of the form cmdMesgXXX: where
* XXX is the (lowercase) command.
*/
[ecLock lock];
if (nil == cmdActions)
{
cmdActions = [NSMutableSet new];
}
[cmdActions removeAllObjects];
enumerator = [GSObjCMethodNames(self, YES) objectEnumerator];
while (nil != (name = [enumerator nextObject]))
{
NSRange r = [name rangeOfString: @":"];
if ([name hasPrefix: @"cmdMesg"] && 1 == r.length && r.location > 7)
{
name = [name substringWithRange: NSMakeRange(7, r.location - 7)];
if (YES == [name isEqual: [name lowercaseString]])
{
[cmdActions addObject: name];
}
}
}
[ecLock unlock];
}
- (NSString*) cmdMesg: (NSArray*)msg
{
NSMutableString *saved;
NSString *result;
NSString *cmd;
SEL sel;
if (msg == nil || [msg count] < 1)
{
return @"no command specified\n";
}
cmd = findAction([msg objectAtIndex: 0]);
if (nil == cmd)
{
return @"unrecognised command\n";
}
sel = NSSelectorFromString([NSString stringWithFormat: @"cmdMesg%@:", cmd]);
saved = replyBuffer;
replyBuffer = [NSMutableString stringWithCapacity: 50000];
NS_DURING
{
[self performSelector: sel withObject: msg];
}
NS_HANDLER
{
[self cmdPrintf: @"\n%@ during command\n", localException];
}
NS_ENDHANDLER
result = replyBuffer;
replyBuffer = saved;
return result;
}
/*
* Name - cmdMesgData: from:
* Purpose - Invoke other methods to handle commands.
*/
- (void) cmdMesgData: (NSData*)dat from: (NSString*)name
{
NSArray *msg;
NSString *val;
msg = [NSPropertyListSerialization
propertyListWithData: dat
options: NSPropertyListMutableContainers
format: 0
error: 0];
val = [self cmdMesg: msg];
if (cmdServer)
{
NS_DURING
{
[cmdServer reply: val to: name from: cmdLogName()];
}
NS_HANDLER
{
[self _commandRemove];
NSLog(@"Caught exception sending client reply to Command: %@ %@",
name, localException);
}
NS_ENDHANDLER
}
}
- (void) cmdMesgalarms: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf: @"reports current alarms"];
}
else
{
if ([[msg objectAtIndex: 0] isEqualToString: @"help"])
{
[self cmdPrintf: @"\nThe alarms command is used to report the"];
[self cmdPrintf: @" alarms currently active for this process.\n"];
[self cmdPrintf: @"NB. Each individual process identifies current"];
[self cmdPrintf: @" alarms by address within the process.\n"];
[self cmdPrintf: @"This differs from the Control server which"];
[self cmdPrintf: @" uses a unique notification ID intended\n"];
[self cmdPrintf: @"for working with external SNMP systems.\n"];
}
else
{
NSArray *a = [alarmDestination alarms];
if (0 == [a count])
{
[self cmdPrintf: @"No alarms currently active.\n"];
}
else
{
int i;
a = [a sortedArrayUsingSelector: @selector(compare:)];
[self cmdPrintf: @"Current alarms -\n"];
for (i = 0; i < [a count]; i++)
{
EcAlarm *alarm = [a objectAtIndex: i];
[self cmdPrintf: @"%@\n", [alarm description]];
}
}
}
}
}
- (void) cmdMesgarchive: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf: @"archives log files"];
}
else
{
if ([[msg objectAtIndex: 0] caseInsensitiveCompare: @"help"]
== NSOrderedSame)
{
[self cmdPrintf: @"\nThe archive command is used to archive the"];
[self cmdPrintf: @" debug file to a subdirectory.\n"];
[self cmdPrintf: @"You should not need it - as archiving should"];
[self cmdPrintf: @"be done automatically at midnight.\n"];
}
else
{
[self cmdPrintf: @"\n%@\n", [self cmdArchive: nil]];
}
}
}
- (void) cmdMesgclear: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf: @"clears current alarms"];
}
else
{
if ([[msg objectAtIndex: 0] isEqualToString: @"help"])
{
[self cmdPrintf: @"\nThe clear command is used to clear the"];
[self cmdPrintf: @" alarms currently active for this process.\n"];
[self cmdPrintf: @"You may use the word 'all' or a space separated"];
[self cmdPrintf: @" list of alarm addresses.\n"];
[self cmdPrintf: @"NB. Each individual process identifies current"];
[self cmdPrintf: @" alarms by address within the process.\n"];
[self cmdPrintf: @"This differs from the Control server which"];
[self cmdPrintf: @" uses a unique notification ID intended\n"];
[self cmdPrintf: @"for working with external SNMP systems.\n"];
}
else
{
NSArray *a = [alarmDestination alarms];
NSUInteger count = [msg count];
if (count < 2)
{
[self cmdPrintf: @"The 'clear' command requires an alarm"
@" notificationID or the word all\n"];
}
else
{
NSUInteger alarmCount = [a count];
EcAlarm *alarm;
NSUInteger index;
for (index = 1; index < count; index++)
{
NSUInteger addr;
NSString *arg = [msg objectAtIndex: index];
if ([arg caseInsensitiveCompare: _(@"all")]
== NSOrderedSame)
{
NSUInteger i;
for (i = 0; i < alarmCount; i++)
{
alarm = [a objectAtIndex: i];
[self cmdPrintf: @"Clearing %@\n", alarm];
alarm = [alarm clear];
[alarmDestination alarm: alarm];
}
}
else if (1 == sscanf([arg UTF8String], "%" PRIxPTR, &addr))
{
NSUInteger i;
alarm = nil;
for (i = 0; i < alarmCount; i++)
{
alarm = [a objectAtIndex: i];
if ((NSUInteger)alarm == addr)
{
break;
}
alarm = nil;
}
if (nil == alarm)
{
[self cmdPrintf:
@"No alarm found with the address '%@'\n",
arg];
}
else
{
[self cmdPrintf: @"Clearing %@\n", alarm];
alarm = [alarm clear];
[alarmDestination alarm: alarm];
}
}
else
{
[self cmdPrintf: @"Not a hexadecimal address: '%@'\n",
arg];
}
}
}
}
}
}
- (void) cmdMesgdebug: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf: @"turns on debug logging"];
}
else
{
if ([[msg objectAtIndex: 0] caseInsensitiveCompare: @"help"]
== NSOrderedSame)
{
[self cmdPrintf: @"\nWithout parameters, the debug command is "];
[self cmdPrintf: @"used to list the currently active "];
[self cmdPrintf: @"debug modes.\n"];
[self cmdPrintf: @"With the single parameter 'default', the debug "];
[self cmdPrintf: @"command is used to revert to default "];
[self cmdPrintf: @"debug settings.\n"];
[self cmdPrintf: @"With the single parameter 'all', the debug "];
[self cmdPrintf: @"command is used to activate all "];
[self cmdPrintf: @"debugging.\n"];
[self cmdPrintf: @"With any other parameter, the debug command "];
[self cmdPrintf: @"is used to activate one of the "];
[self cmdPrintf: @"debug modes listed below.\n\n"];
[self cmdPrintf: @"%@\n", cmdDebugKnown];
}
else if ([msg count] > 1)
{
NSString *mode = (NSString*)[msg objectAtIndex: 1];
NSString *key;
if ([mode caseInsensitiveCompare: @"default"] == NSOrderedSame)
{
NSEnumerator *enumerator = [cmdDebugKnown keyEnumerator];
while (nil != (mode = [enumerator nextObject]))
{
key = [@"Debug-" stringByAppendingString: mode];
[cmdDefs setCommand: nil forKey: key];
}
[self cmdPrintf: @"Now using debug settings from config.\n"];
}
else if ([mode caseInsensitiveCompare: @"all"] == NSOrderedSame)
{
NSEnumerator *enumerator = [cmdDebugKnown keyEnumerator];
while (nil != (mode = [enumerator nextObject]))
{
key = [@"Debug-" stringByAppendingString: mode];
[cmdDefs setCommand: @"YES" forKey: key];
}
[self cmdPrintf: @"All debugging is now active.\n"];
}
else
{
[self cmdPrintf: @"debug mode '"];
if ((mode = findMode(cmdDebugKnown, mode)) == nil)
{
[self cmdPrintf: @"%@' is not known.\n", mode];
}
else
{
[self cmdPrintf: @"%@", mode];
if ([cmdDebugModes member: mode] == nil)
{
[self cmdPrintf: @"' is now active."];
}
else
{
[self cmdPrintf: @"' is already active."];
}
key = [@"Debug-" stringByAppendingString: mode];
[cmdDefs setCommand: @"YES" forKey: key];
}
}
}
else
{
[self cmdPrintf: @"%@\n", [EcLogger loggerForType: LT_DEBUG]];
[self cmdPrintf: @"Current active debug modes -\n"];
if ([cmdDebugModes count] == 0)
{
[self cmdPrintf: @"\nNone.\n"];
}
else
{
[self cmdPrintf: @"%@\n", cmdDebugModes];
}
}
}
}
- (void) cmdMesgdefaults: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf:
@"temporarily overrides defaults/Control.plist settings"];
}
else
{
if ([[msg objectAtIndex: 0] caseInsensitiveCompare: @"help"]
== NSOrderedSame)
{
[self cmdPrintf: @"\nWithout parameters,\n the defaults command is"];
[self cmdPrintf: @" used to list the current defaults overrides.\n"];
[self cmdPrintf: @"With the 'delete' parameter followed by a name,"];
[self cmdPrintf: @"\n removes an override.\n"];
[self cmdPrintf: @"With the 'write' parameter followed by a name "];
[self cmdPrintf: @"and value,\n sets an override of the default.\n"];
[self cmdPrintf: @"With the 'read' parameter followed by a name,\n"];
[self cmdPrintf: @" shows the effective default after overrides.\n"];
[self cmdPrintf: @"With the 'revert' parameter,\n the command"];
[self cmdPrintf: @" is used to revert all overides.\n"];
[self cmdPrintf: @"With the 'list' parameter,\n this lists"];
[self cmdPrintf: @" registered (not all) defaults options.\n"];
}
else if ([msg count] > 1 && [[msg objectAtIndex: 1] isEqual: @"list"])
{
[self cmdPrintf: @"%@", [EcDefaultRegistration listHelp]];
}
else if ([msg count] > 1 && [[msg objectAtIndex: 1] isEqual: @"revert"])
{
[cmdDefs revertSettings];
[self cmdPrintf: @"All override settings are removed.\n"];
}
else if ([msg count] > 2)
{
NSString *mode = (NSString*)[msg objectAtIndex: 1];
NSString *key = (NSString*)[msg objectAtIndex: 2];
id old;
id val;
old = [cmdDefs objectForKey: key];
if ([mode caseInsensitiveCompare: @"delete"] == NSOrderedSame)
{
[cmdDefs setCommand: nil forKey: key];
val = [cmdDefs objectForKey: key];
}
else if ([mode caseInsensitiveCompare: @"write"] == NSOrderedSame
|| [mode caseInsensitiveCompare: @"set"] == NSOrderedSame)
{
if ([msg count] == 4)
{
val = [msg objectAtIndex: 3];
[cmdDefs setCommand: val forKey: key];
val = [cmdDefs objectForKey: key];
}
else if ([msg count] == 3)
{
[self cmdPrintf: @"Missing value for '%@ %@' (no effect).\n",
mode, key];
val = old;
}
else
{
[self cmdPrintf: @"Too many values for '%@ %@' (ignored).\n",
mode, key];
val = old;
}
}
else if ([mode caseInsensitiveCompare: @"read"] == NSOrderedSame
|| [mode caseInsensitiveCompare: @"get"] == NSOrderedSame)
{
val = [cmdDefs objectForKey: key];
}
else
{
/* To be tolerant of typing errors and maintain backward
* compatibility, anything else is treated as a 'read'
*/
[self cmdPrintf: @"Unrecognised command '%@' (assume 'read').\n",
key];
val = [cmdDefs objectForKey: key];
}
if (val == old || [val isEqual: old])
{
if (nil == val)
{
[self cmdPrintf:
@"The override setting for the default '%@' is"
@" unchanged (and not set).\n", key];
}
else
{
[self cmdPrintf:
@"The override setting for the default '%@' is"
@" unchanged (%@).\n", key, val];
}
}
else if (nil == val)
{
[self cmdPrintf: @"The override setting for the default '%@' is"
@" deleted (was %@).\n", key, old];
}
else if (nil == old)
{
[self cmdPrintf: @"The override setting for the default '%@' is"
@" set to: %@ (was not set).\n", key, val];
}
else
{
[self cmdPrintf: @"The override setting for the default '%@' is"
@" set to: %@ (was %@).\n", key, val, old];
}
}
else
{
NSDictionary *d = [cmdDefs volatileDomainForName: @"EcCommand"];
NSArray *a;
NSEnumerator *e;
NSString *k;
[self cmdPrintf: @"Console temporary overrides of defaults:\n"];
a = [[d allKeys] sortedArrayUsingSelector: @selector(compare:)];
e = [a objectEnumerator];
k = [e nextObject];
if (nil == k)
{
[self cmdPrintf: @" None.\n"];
}
else
{
while (nil != k)
{
id v = [d objectForKey: k];
[self cmdPrintf: @" %@ = %@\n", k, v];
k = [e nextObject];
}
}
}
}
}
- (void) cmdMesghelp: (NSArray*)msg
{
NSEnumerator *e;
NSString *cmd;
SEL sel;
[ecLock lock];
e = [cmdActions objectEnumerator];
[ecLock unlock];
if ([msg count] == 0)
{
[self cmdPrintf: @"provides helpful information :-)"];
return;
}
else if ([msg count] > 1)
{
NSString *found;
cmd = [msg objectAtIndex: 1];
found = findAction(cmd);
if ([cmd caseInsensitiveCompare: @"control"] == NSOrderedSame)
{
[self cmdPrintf: @"Detailed help on the 'control' command -\n"];
[self cmdPrintf: @"This command enables you to send an"];
[self cmdPrintf: @"instruction to the 'Control' server rather\n"];
[self cmdPrintf: @"than to the currently connected server.\n"];
[self cmdPrintf: @"Everything typed on the line after the word"];
[self cmdPrintf: @" 'control' is treated as a command to\n"];
[self cmdPrintf: @"the 'Control' server process.\n"];
[self cmdPrintf: @"\nTo disconnect from the server type -\n"];
[self cmdPrintf: @" control connect\n"];
[self cmdPrintf: @"\nTo disconnect from the host type -\n"];
[self cmdPrintf: @" control host\n"];
return;
}
else if (nil == found)
{
[self cmdPrintf: @"Unable to find the '%@' command -\n", cmd];
}
else if ([found caseInsensitiveCompare: @"help"] != NSOrderedSame)
{
NSMutableArray *m;
[self cmdPrintf: @"Detailed help on the '%@' command -\n", found];
sel = NSSelectorFromString(
[NSString stringWithFormat: @"cmdMesg%@:", found]);
/* To get the help on a command, we invoke that command
* by passing the command and arguments (ie, the msg array).
* The command implementation should check the argument 0 -
* if it is "help", it should print out help on itself.
* Save expanded (unabbreviated) commands so the methods
* getting the help request don't need to recheck the values.
*/
m = [[msg mutableCopy] autorelease];
[m replaceObjectAtIndex: 0 withObject: @"help"];
[m replaceObjectAtIndex: 1 withObject: found];
[self performSelector: sel withObject: m];
return;
}
}
[self cmdPrintf: @"\n"];
[self cmdPrintf: @"For help on a particular command, type 'help <cmd>'\n"];
[self cmdPrintf: @"\n"];
[self cmdPrintf: @"These are the commands available to you -\n"];
[self cmdPrintf: @"\n"];
while ((cmd = [e nextObject]) != nil)
{
unsigned l;
sel = NSSelectorFromString(
[NSString stringWithFormat: @"cmdMesg%@:", cmd]);
[self cmdPrintf: @"%@ - ", cmd];
l = [cmd length];
while (l++ < 9)
{
[self cmdPrintf: @" "];
}
[self performSelector: sel withObject: nil];
[self cmdPrintf: @"\n"];
}
}
- (void) cmdMesgnodebug: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf: @"turns off debug logging"];
}
else
{
if ([[msg objectAtIndex: 0] caseInsensitiveCompare: @"help"]
== NSOrderedSame)
{
[self cmdPrintf: @"\n"];
[self cmdPrintf: @"Without parameters, the nodebug command is "];
[self cmdPrintf: @"used to list the currently inactive\n"];
[self cmdPrintf: @"debug modes.\n"];
[self cmdPrintf: @"With the single parameter 'all', the nodebug "];
[self cmdPrintf: @"command is used to deactivate all\n"];
[self cmdPrintf: @"debugging.\n"];
[self cmdPrintf: @"With the single parameter 'default', the "];
[self cmdPrintf: @"nodebug command is used to revert to default "];
[self cmdPrintf: @"debug settings.\n"];
[self cmdPrintf: @"With any other parameter, the nodebug command is"];
[self cmdPrintf: @" used to deactivate one of the\n"];
[self cmdPrintf: @"debug modes listed below.\n"];
[self cmdPrintf: @"\n"];
[self cmdPrintf: @"%@\n", cmdDebugKnown];
}
else if ([msg count] > 1)
{
NSString *mode = (NSString*)[msg objectAtIndex: 1];
NSString *key;
if ([mode caseInsensitiveCompare: @"default"] == NSOrderedSame)
{
NSEnumerator *enumerator = [cmdDebugKnown keyEnumerator];
while (nil != (mode = [enumerator nextObject]))
{
key = [@"Debug-" stringByAppendingString: mode];
[cmdDefs setCommand: nil forKey: key];
}
[self cmdPrintf: @"Now using debug settings from config.\n"];
}
else if ([mode caseInsensitiveCompare: @"all"] == NSOrderedSame)
{
NSEnumerator *enumerator = [cmdDebugKnown keyEnumerator];
while (nil != (mode = [enumerator nextObject]))
{
key = [@"Debug-" stringByAppendingString: mode];
[cmdDefs setCommand: @"NO" forKey: key];
}
[self cmdPrintf: @"All debugging is now inactive.\n"];
}
else
{
[self cmdPrintf: @"debug mode '"];
if ((mode = findMode(cmdDebugKnown, mode)) == nil)
{
[self cmdPrintf: @"%@' is not known.\n", mode];
}
else
{
[self cmdPrintf: @"%@' is ", mode];
if ([cmdDebugModes member: mode] == nil)
{
[self cmdPrintf: @"already inactive.\n"];
}
else
{
[self cmdPrintf: @"now deactivated.\n"];
}
key = [@"Debug-" stringByAppendingString: mode];
[cmdDefs setCommand: @"NO" forKey: key];
}
}
}
else
{
NSArray *a = [cmdDebugKnown allKeys];
NSMutableSet *s = [NSMutableSet setWithArray: a];
/*
* Find items known but not active.
*/
[s minusSet: cmdDebugModes];
[self cmdPrintf: @"Current inactive debug modes -\n"];
if (a == 0)
{
[self cmdPrintf: @"none.\n"];
}
else
{
[self cmdPrintf: @"%@\n", s];
}
}
}
}
- (void) cmdMesgmemory: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf: @"controls recording of memory management statistics"];
}
else
{
[self cmdPrintf: @"\n%@ on %@ running since %@\n\n",
cmdLogName(), ecHostName(), [self ecStarted]];
if ([[msg objectAtIndex: 0] caseInsensitiveCompare: @"help"]
== NSOrderedSame || ([msg count] > 1
&& [[msg objectAtIndex: 1] caseInsensitiveCompare: @"help"]
== NSOrderedSame))
{
[self cmdPrintf: @"\n\
Without parameters,\n\
the memory command is used to list the changes in the numbers of objects\n\
allocated since the command was last issued.\n\
With the single parameter 'all',\n\
the memory command is used to list the cumulative totals of objects\n\
allocated since the gathering of memory usage statistics was turned on.\n\
With the single parameter 'current',\n\
the memory command is used to list the current totals of objects\n\
allocated (and not deallocated) since the gathering of memory usage\n\
statistics was turned on.\n\
With the single parameter 'yes',\n\
the memory command is used to turn on gathering of memory usage statistics.\n\
With the single parameter 'no',\n\
the memory command is used to turn off gathering of memory usage statistics.\n\
With the single parameter 'default',\n\
the gathering of memory usage statistics reverts to the default setting.\n\
With two parameters ('class' and a class name),\n\
new instances of the class are recorded.\n\
With two parameters ('list' and a class),\n\
recorded instances of the class are reported.\n\
With two parameters ('allowed' and a number),\n\
the threshold for warnings about process size is set (in MB).\n\
Set to 'default' to revert to the default.\n\
With two parameters ('increment' and a number),\n\
the size increment between warnings about process size is set (in KB\n\
from 10 to 1048576). Set to 'default' to revert to the default.\n\
With two parameters ('percentage' and a number),\n\
the percentage increment between warnings about process memory size is\n\
set (from 1 to 1000). Set to 'default' to revert to the default.\n\
With two parameters ('maximum' and a number),\n\
the maximum process size (in MB) is set. On reaching the limit, the\n\
process restarts unless the limit is zero (meaning no maximum).\n\
Set to 'default' to revert to the default."];
[self cmdPrintf: @"\n"];
}
else if ([msg count] == 2)
{
NSString *word = [msg objectAtIndex: 1];
if ([word caseInsensitiveCompare: @"current"] == NSOrderedSame)
{
if (NO == [cmdDefs boolForKey: @"Memory"])
{
[self cmdPrintf:
@"Memory statistics were not being gathered.\n"];
[self cmdPrintf: @"Memory statistics Will start from NOW.\n"];
}
else
{
const char* list;
list = (const char*)GSDebugAllocationList(NO);
[self cmdPrintf: @"Memory current stats at %@:\n%s",
[NSDate date], list];
}
[cmdDefs setCommand: @"YES" forKey: @"Memory"];
}
else if ([word caseInsensitiveCompare: @"default"] == NSOrderedSame)
{
[cmdDefs setCommand: nil forKey: @"Memory"];
[self cmdPrintf: @"Memory checking: %s\n",
[cmdDefs boolForKey: @"Memory"] ? "YES" : "NO"];
}
else if ([word caseInsensitiveCompare: @"all"] == NSOrderedSame)
{
if (NO == [cmdDefs boolForKey: @"Memory"])
{
[self cmdPrintf:
@"Memory statistics were not being gathered.\n"];
[self cmdPrintf: @"Memory statistics Will start from NOW.\n"];
}
else
{
const char* list;
list = (const char*)GSDebugAllocationList(NO);
[self cmdPrintf: @"Memory total allocation stats at %@:\n%s",
[NSDate date], list];
}
[cmdDefs setCommand: @"YES" forKey: @"Memory"];
}
else if ([word boolValue] == YES)
{
if (NO == [cmdDefs boolForKey: @"Memory"])
{
[self cmdPrintf:
@"Memory statistics were not being gathered.\n"];
[self cmdPrintf: @"Statistics Will start from NOW.\n"];
}
else
{
[self cmdPrintf:
@"Memory statistics are already being gathered.\n"];
}
[cmdDefs setCommand: @"YES" forKey: @"Memory"];
}
else
{
if (NO == [cmdDefs boolForKey: @"Memory"])
{
[self cmdPrintf:
@"Memory statistics were not being gathered.\n"];
}
[self cmdPrintf: @"Memory statistics are turned off NOW.\n"];
[cmdDefs setCommand: @"NO" forKey: @"Memory"];
}
}
else if ([msg count] == 3)
{
NSString *op = [msg objectAtIndex: 1];
NSString *arg = [msg objectAtIndex: 2];
NSInteger val = [arg integerValue];
if ([op caseInsensitiveCompare: @"allowed"] == NSOrderedSame)
{
if (val <= 0)
{
[cmdDefs setCommand: nil forKey: @"MemoryAllowed"];
if (0 == memAllowed)
{
/* The threshold was set back to zero ... to be
* calculated from a ten minute baseline.
*/
memSlot = 0;
}
[self cmdPrintf: @"MemoryAllowed using default value.\n"];
}
else
{
arg = [NSString stringWithFormat: @"%"PRIu64, (uint64_t)val];
[cmdDefs setCommand: arg forKey: @"MemoryAllowed"];
[self cmdPrintf: @"MemoryAllowed set to %@MB.\n", arg];
}
memWarn = memAllowed * 1024 * 1024;
DESTROY(memTime);
[self _memCheck];
}
else if ([op caseInsensitiveCompare: @"increment"] == NSOrderedSame)
{
if (val <= 100 || val > 1048576)
{
[cmdDefs setCommand: nil forKey: @"MemoryIncrement"];
[self cmdPrintf: @"MemoryIncrement using default value.\n"];
}
else
{
arg = [NSString stringWithFormat: @"%"PRIu64, (uint64_t)val];
[cmdDefs setCommand: arg forKey: @"MemoryIncrement"];
[self cmdPrintf: @"MemoryIncrement set to %@KB.\n", arg];
}
}
else if ([op caseInsensitiveCompare: @"percentage"] == NSOrderedSame)
{
if (val <= 0 || val > 1000)
{
[cmdDefs setCommand: nil forKey: @"MemoryPercentage"];
[self cmdPrintf: @"MemoryPercentage using default value.\n"];
}
else
{
arg = [NSString stringWithFormat: @"%"PRIu64, (uint64_t)val];
[cmdDefs setCommand: arg forKey: @"MemoryPercentage"];
[self cmdPrintf: @"MemoryPercentage set to %@.\n", arg];
}
}
else if ([op caseInsensitiveCompare: @"maximum"] == NSOrderedSame)
{
if (val <= 0)
{
if ([arg caseInsensitiveCompare: @"default"] == NSOrderedSame)
{
[cmdDefs setCommand: nil forKey: @"MemoryMaximum"];
[self cmdPrintf: @"MemoryMaximum using default value.\n"];
}
else
{
[cmdDefs setCommand: @"0" forKey: @"MemoryMaximum"];
[self cmdPrintf: @"MemoryMaximum restart turned off.\n"];
}
}
else
{
arg = [NSString stringWithFormat: @"%"PRIu64, (uint64_t)val];
[cmdDefs setCommand: arg forKey: @"MemoryMaximum"];
[self cmdPrintf: @"MemoryMaximum set to %@MB.\n", arg];
}
}
else
{
Class c = NSClassFromString(arg);
if (Nil == c)
{
[self cmdPrintf: @"Unable to find class '%@'.\n", arg];
}
else
{
if ([op caseInsensitiveCompare: @"class"] == NSOrderedSame)
{
GSDebugAllocationActiveRecordingObjects(c);
[self cmdPrintf: @"Recording instances of '%@'.\n", arg];
}
else if ([op caseInsensitiveCompare: @"list"] == NSOrderedSame)
{
NSArray *array;
NSUInteger count;
NSUInteger index;
array = GSDebugAllocationListRecordedObjects(c);
[self cmdPrintf: @"Current instances of '%@':\n", arg];
count = [array count];
for (index = 0; index < count; index++)
{
[self cmdPrintf: @"%6lu %@\n",
(unsigned long)index, [array objectAtIndex: index]];
}
}
else
{
[self cmdPrintf: @"Unknown memory command '%@'.\n", op];
}
}
}
}
else
{
if (NO == [cmdDefs boolForKey: @"Memory"])
{
[self cmdPrintf: @"Memory stats are not being gathered.\n"];
}
else
{
const char *list;
NSDate *now;
now = [NSDate date];
list = (const char*)GSDebugAllocationList(YES);
if (nil == memStats)
{
[self cmdPrintf: @"Memory change stats at %@:\n%s",
now, list];
}
else
{
[self cmdPrintf: @"Memory change stats at %@\n"
@" (since %@):\n%s", now, memStats, list];
}
ASSIGN(memStats, now);
}
}
}
}
- (void) cmdMesgstatus: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf: @"provides server status information"];
}
else
{
[self cmdPrintf: @"\n%@ on %@ running since %@\n",
cmdLogName(), ecHostName(), [self ecStarted]];
if (lastIP > 0.0)
{
[self cmdPrintf: @"Last IP at %@\n", [self cmdLastIP]];
}
if (lastOP > 0.0)
{
[self cmdPrintf: @"Last OP at %@\n", [self cmdLastOP]];
}
if (servers != nil)
{
NSEnumerator *e;
RemoteServer *server;
e = [servers objectEnumerator];
while ((server = (RemoteServer *)[e nextObject]) != 0)
{
[self cmdPrintf: @"%@\n", server];
}
}
[self cmdPrintf: @"Memory usage: %"PRIu64"KB (current),"
@" %"PRIu64"KB (peak)\n",
memLast/1024, memPeak/1024];
[self cmdPrintf: @" %"PRIu64"KB (average),"
@" %"PRIu64"KB (start)\n",
memAvge/1024, memStrt/1024];
[self cmdPrintf: @" %"PRIu64"KB (reserved)\n",
excLast/1024];
if (memSlot < MEMCOUNT)
{
[self cmdPrintf: @"Memory error reporting disabled (for %d min"
@" of baseline stats collection).\n", (int)(MEMCOUNT - memSlot)];
}
else
{
[self cmdPrintf:
@"Memory error reporting after average usage: %"PRIu64"KB\n",
memWarn/1024];
}
if (memMaximum > 0)
{
[self cmdPrintf:
@"Memory exceeded shutdown after peak usage: %"PRIu64"KB\n",
memMaximum * 1024];
}
}
}
- (oneway void) cmdPing: (id <CmdPing>)from
sequence: (unsigned)num
extra: (in bycopy NSData*)data
{
[self cmdDbg: cmdConnectDbg msg: @"cmdPing: %lx sequence: %u extra: %lx",
(unsigned long)from, num, (unsigned long)data];
[from cmdGnip: self sequence: num extra: nil];
}
- (void) cmdPrintf: (NSString*)fmt arguments: (va_list)args
{
NSString *tmp;
tmp = [[stringClass alloc] initWithFormat: fmt arguments: args];
[replyBuffer appendString: tmp];
[tmp release];
}
- (void) cmdPrintf: (NSString*)fmt, ...
{
va_list ap;
va_start(ap, fmt);
[self cmdPrintf: fmt arguments: ap];
va_end(ap);
}
- (oneway void) cmdQuit: (NSInteger)status
{
if (reservedPipe[1] > 0)
{
close(reservedPipe[0]); reservedPipe[0] = 0;
close(reservedPipe[1]); reservedPipe[1] = 0;
}
cmdIsQuitting = YES;
cmdQuitStatus = status;
if (cmdPTimer != nil)
{
[cmdPTimer invalidate];
cmdPTimer = nil;
}
if (0 == status)
{
/* Normal shutdown ... unmanage this process first.
*/
[alarmDestination unmanage: nil];
}
[alarmDestination shutdown];
[self cmdFlushLogs];
[self cmdUnregister];
[alarmDestination shutdown];
DESTROY(alarmDestination);
[EcProcConnection invalidate];
{
NSArray *keys;
unsigned index;
/*
* Close down all log files.
*/
keys = [cmdLogMap allKeys];
for (index = 0; index < [keys count]; index++)
{
[self cmdLogEnd: [keys objectAtIndex: index]];
}
}
exit(status);
}
- (void) cmdUpdate: (NSMutableDictionary*)info
{
BOOL defaultsChanged;
if (nil == info)
{
defaultsChanged = NO;
}
else
{
ASSIGNCOPY(cmdConf, info);
defaultsChanged = [cmdDefs setConfiguration: cmdConf];
}
/* If the defaults did not actually change,
* trigger an update anyway.
*/
if (NO == defaultsChanged)
{
[self cmdDefaultsChanged: nil];
}
}
- (NSString*) cmdUpdated
{
return nil;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
[ecLock lock];
if (self == EcProc)
{
EcProc = nil;
}
[ecLock unlock];
[super dealloc];
}
- (NSString*) description
{
return [stringClass stringWithFormat: @"%@ (%@) on %@",
[super description], cmdLogName(), ecHostName()];
}
- (id) init
{
CREATE_AUTORELEASE_POOL(pool);
self = [self initWithDefaults: [[self class] ecInitialDefaults]];
RELEASE(pool);
return self;
}
- (id) initWithDefaults: (NSDictionary*) defs
{
[ecLock lock];
if (nil != EcProc)
{
[self release];
[ecLock unlock];
[NSException raise: NSGenericException
format: @"EcProcess initialiser called more than once"];
}
if (nil == (self = [super init]))
{
[ecLock unlock];
return nil;
}
else
{
NSProcessInfo *pinfo;
NSArray *args;
NSFileManager *mgr;
NSEnumerator *enumerator;
NSString *str;
NSString *dbg;
NSString *prf;
BOOL flag;
NSInteger i;
EcProc = self;
started = RETAIN([dateClass date]);
pinfo = [NSProcessInfo processInfo];
args = [pinfo arguments];
mgr = [NSFileManager defaultManager];
prf = EC_DEFAULTS_PREFIX;
if (nil == prf)
{
prf = @"";
}
ASSIGN(cmdDefs, [NSUserDefaults
userDefaultsWithPrefix: prf
strict: EC_DEFAULTS_STRICT]);
defs = [EcDefaultRegistration merge: defs];
if (defs != nil)
{
[cmdDefs registerDefaults: defs];
}
if ([args containsObject: @"--help"] || [args containsObject: @"-H"])
{
GSPrintf(stderr, @"Standard command-line arguments ...\n\n");
if ([self isKindOfClass: NSClassFromString(@"EcControl")])
{
GSPrintf(stderr,
@"-%@Daemon NO Run process in the foreground.\n",
prf);
}
else if ([self isKindOfClass: NSClassFromString(@"EcConsole")])
{
GSPrintf(stderr,
@"-%@ControlHost [aHost] Host of the Control server to use.\n"
@"-%@ControlName [aName] Name of the Control server to use.\n"
@"-%@Daemon [YES/NO] Fork process to run in background?\n",
prf, prf, prf);
}
else if ([self isKindOfClass: NSClassFromString(@"EcCommand")])
{
GSPrintf(stderr,
@"-%@ControlHost [aHost] Host of the Control server to use.\n"
@"-%@ControlName [aName] Name of the Control server to use.\n"
@"-%@Daemon NO Run process in in the foreground.\n",
prf, prf, prf);
}
else
{
GSPrintf(stderr,
@"-%@CommandHost [aHost] Host of the Command server to use.\n"
@"-%@CommandName [aName] Name of the Command server to use.\n"
@"-%@Daemon [YES/NO] Fork process to run in background?\n",
@"-%@Transient [YES/NO] Expect this process be short-lived?\n",
prf, prf, prf, prf);
}
GSPrintf(stderr, @"\n");
GSPrintf(stderr,
@"-%@CoreSize [MB] Maximum core dump size\n"
@" 0 = no dumps, -1 = unlimited\n"
@"-%@DescriptorsMaximum [N]\n"
@" Set maximum file descriptors to use\n"
@"-%@Debug-name [YES/NO] Turn on/off the named type of debug\n"
@"-%@EffectiveUser [aName] User to run this process as\n"
@"-%@HomeDirectory [relDir] Relative home within user directory\n"
@"-%@UserDirectory [dir] Override home directory for user\n"
@"-%@Instance [aNumber] Instance number for multiple copies\n"
@"-%@MemoryAllowed [MB] Expected memory usage (before alerts)\n"
@"-%@MemoryIncrement [KB] Absolute increase in alert threshold\n"
@"-%@MemoryMaximum [MB] Maximum memory usage (before restart)\n"
@"-%@MemoryPercentage [N] Percent increase in alert threshold\n"
@"-%@ProgramName [aName] Name to use for this program\n"
@"\n--version to get version information and quit\n\n",
prf, prf, prf, prf, prf, prf, prf, prf, prf, prf, prf, prf);
[EcDefaultRegistration showHelp];
RELEASE(self);
[ecLock unlock];
return nil;
}
if ([args containsObject: @"--version"])
{
NSLog(@"%@ %@", [self ecCopyright], cmdVersion(nil));
RELEASE(self);
[ecLock unlock];
return nil;
}
cmdUser = EC_EFFECTIVE_USER;
if (nil == cmdUser)
{
cmdUser = [[cmdDefs stringForKey: @"EffectiveUser"] retain];
}
if (YES == [cmdUser isEqual: @"*"]
|| YES == [cmdUser isEqualToString: NSUserName()])
{
ASSIGN(cmdUser, NSUserName());
}
else if ([cmdUser length] == 0)
{
NSLog(@"This software is not configured to run as any user.\n"
@"You may use the EffectiveUser user default setting"
@" to specify the user (setting this to an asterisk ('*')"
@" allows the software to run as any user). Alternatively"
@" an EC_EFFECTIVE_USER can be defined when the ec library"
@" is built.");
exit(1);
}
else
{
const char *user = [cmdUser UTF8String];
struct passwd *pwd = getpwnam(user);
int uid;
if (pwd != 0)
{
uid = pwd->pw_uid;
}
else
{
NSLog(@"This software is configured to run as the user '%@',"
@" but there does not appear to be any such user.", cmdUser);
if ([cmdUser isEqual: EC_EFFECTIVE_USER])
{
NSLog(@"You may use the EffectiveUser user default setting"
@" to override the user (setting this to an asterisk ('*')"
@" allows the software to run as any user). Alternatively"
@" a different EC_EFFECTIVE_USER can be defined when the"
@" ec library is built.");
}
exit(1);
}
if (uid != (int)geteuid())
{
if (geteuid() == 0 || (int)getuid() == uid)
{
if (0 != setuid(uid))
{
[ecLock unlock];
NSLog(@"You must be '%@' to run this.", cmdUser);
exit(1);
}
}
else
{
[ecLock unlock];
NSLog(@"You must be '%@' to run this.", cmdUser);
exit(1);
}
}
GSSetUserName(cmdUser);
if (NO == [cmdUser isEqualToString: NSUserName()])
{
[ecLock unlock];
NSLog(@"You must be '%@' to run this.", cmdUser);
exit(1);
}
ASSIGN(cmdDefs, [NSUserDefaults
userDefaultsWithPrefix: prf
strict: EC_DEFAULTS_STRICT]);
if (defs != nil)
{
[cmdDefs registerDefaults: defs];
}
}
/* See if we should keep stderr separate, or merge it with
* our debug output (the default).
*/
cmdKeepStderr = [cmdDefs boolForKey: @"KeepStandardError"];
if (nil == noNetConfig)
{
noNetConfig = [[NSMutableArray alloc] initWithCapacity: 4];
[noNetConfig
addObject: [prf stringByAppendingString: @"Daemon"]];
[noNetConfig
addObject: [prf stringByAppendingString: @"EffectiveUser"]];
[noNetConfig
addObject: [prf stringByAppendingString: @"Instance"]];
[noNetConfig
addObject: [prf stringByAppendingString: @"Transient"]];
}
defs = [cmdDefs dictionaryRepresentation];
enumerator = [defs keyEnumerator];
dbg = [prf stringByAppendingString: @"Debug-"];
while ((str = [enumerator nextObject]) != nil)
{
if ([str hasPrefix: dbg])
{
id obj = [defs objectForKey: str];
NSString *key = [str substringFromIndex: [dbg length]];
if ([cmdDebugKnown objectForKey: key] == nil)
{
[cmdDebugKnown setObject: key forKey: key];
}
if ([obj isKindOfClass: stringClass])
{
if ([obj intValue] != 0
|| [obj isEqual: @"YES"] || [obj isEqual: @"yes"])
{
if ([cmdDebugModes member: key] == nil)
{
[cmdDebugModes addObject: key];
}
}
else
{
if ([cmdDebugModes member: key] != nil)
{
[cmdDebugModes removeObject: key];
}
}
}
}
}
/* See if we have a name specified for this process.
*/
ASSIGN(cmdName, [cmdDefs stringForKey: @"ProgramName"]);
/* If there's no ProgramName specified, but this is a Control server,
* try looking for the ControlName instead.
*/
if (nil == cmdName
&& Nil != NSClassFromString(@"EcControl")
&& YES == [self isKindOfClass: NSClassFromString(@"EcControl")])
{
ASSIGN(cmdName, [cmdDefs stringForKey: @"ControlName"]);
}
/* If there's no ProgramName specified, but this is a Command server,
* try looking for the CommandName instead.
*/
if (nil == cmdName
&& Nil != NSClassFromString(@"EcCommand")
&& YES == [self isKindOfClass: NSClassFromString(@"EcCommand")])
{
ASSIGN(cmdName, [cmdDefs stringForKey: @"CommandName"]);
}
/* Finally, if no name is given at all, use the standard process name.
*/
if (nil == cmdName)
{
ASSIGN(cmdName, [pinfo processName]);
}
/* This is the base name of the process (without instance)
*/
if (nil == cmdBase)
{
ASSIGN(cmdBase, cmdName);
}
/*
* Make sure our users home directory exists.
*/
str = [cmdDefs objectForKey: @"UserDirectory"];
str = cmdSetUserDirectory(str);
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
if ([mgr createDirectoryAtPath: str
withIntermediateDirectories: YES
attributes: nil
error: NULL] == NO)
{
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
[ecLock unlock];
NSLog(@"Unable to create directory - %@", str);
exit(1);
}
}
else
{
flag = YES;
}
}
if (flag == NO)
{
[ecLock unlock];
NSLog(@"The path '%@' is not a directory", str);
exit(1);
}
str = [cmdDefs objectForKey: @"HomeDirectory"];
if (str != nil)
{
if ([str length] == 0)
{
str = nil;
}
else if ([str isAbsolutePath] == YES)
{
NSLog(@"Absolute HomeDirectory ignored.");
str = nil;
}
cmdSetHome(str);
}
for (i = 0; i < 32; i++)
{
switch (i)
{
case SIGPROF:
case SIGABRT:
break;
case SIGPIPE:
case SIGTTOU:
case SIGTTIN:
case SIGCHLD:
/* SIGWINCH is generated when the terminal size
changes (for example when you resize the xterm).
Ignore it. */
#ifdef SIGWINCH
case SIGWINCH:
#endif
signal(i, SIG_IGN);
break;
case SIGHUP:
if ([cmdDefs boolForKey: @"Daemon"] == YES)
{
signal(i, SIG_IGN);
}
else
{
signal(i, qhandler);
}
break;
case SIGINT:
case SIGTERM:
signal(i, qhandler);
break;
case SIGSTOP:
case SIGCONT:
case SIGTSTP:
signal(i, SIG_DFL);
break;
default:
signal(i, ihandler);
break;
}
}
str = [[cmdDefs stringForKey: @"Instance"] stringByTrimmingSpaces];
if (nil != str)
{
if ([str length] > 0 && isdigit([str characterAtIndex: 0]))
{
str = [NSString stringWithFormat: @"%d", [str intValue]];
}
else
{
str = nil;
}
}
ASSIGN(cmdInst, str);
if (nil != cmdInst)
{
str = [[NSString alloc] initWithFormat: @"%@-%@", cmdName, cmdInst];
ASSIGN(cmdName, str);
[str release];
}
str = userDir;
if (cmdHomeDir() != nil)
{
str = [str stringByAppendingPathComponent: cmdHomeDir()];
}
str = [str stringByStandardizingPath];
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
if ([mgr createDirectoryAtPath: str
withIntermediateDirectories: YES
attributes: nil
error: NULL] == NO)
{
if ([mgr fileExistsAtPath: str isDirectory: &flag] == NO)
{
[ecLock unlock];
NSLog(@"Unable to create directory - %@", str);
exit(1);
}
}
else
{
flag = YES;
}
}
if (flag == NO)
{
[ecLock unlock];
NSLog(@"The path '%@' is not a directory", str);
exit(1);
}
if ([mgr changeCurrentDirectoryPath: str] == NO)
{
[ecLock unlock];
NSLog(@"Unable to move to directory - %@", str);
exit(1);
}
/*
* Make sure the data directory exists.
*/
if ([self cmdDataDirectory] == nil)
{
[ecLock unlock];
NSLog(@"Unable to create/access data directory");
exit(1);
}
/*
* Make sure the logs directory exists.
*/
if (cmdLogsDir(nil) == nil)
{
[ecLock unlock];
NSLog(@"Unable to create/access logs directory");
exit(1);
}
[[NSProcessInfo processInfo] setProcessName: cmdName];
/* Archive any existing debug log left over by a crash.
*/
str = [cmdName stringByAppendingPathExtension: @"debug"];
if (cmdDebugName == nil || [cmdDebugName isEqual: str] == NO)
{
NSFileHandle *hdl;
/*
* Force archiving of old logfile.
*/
[self cmdArchive: nil];
ASSIGNCOPY(cmdDebugName, str);
hdl = [self cmdLogFile: cmdDebugName];
if (hdl == nil)
{
[ecLock unlock];
exit(1);
}
}
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(cmdDefaultsChanged:)
name: NSUserDefaultsDidChangeNotification
object: [NSUserDefaults standardUserDefaults]];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(ecLoggersChanged:)
name: EcLoggersDidChangeNotification
object: nil];
[self cmdMesgCache];
[self cmdDefaultsChanged: nil];
cmdIsTransient = [cmdDefs boolForKey: @"Transient"];
if ([cmdDefs objectForKey: @"CmdInterval"] != nil)
{
[self setCmdInterval: [cmdDefs floatForKey: @"CmdInterval"]];
}
if (YES == [self cmdIsClient] && nil == [self cmdNewServer])
{
NSLog(@"Giving up - unable to contact '%@' server on '%@'",
ecCommandName(), ecCommandHost());
[self release];
self = nil;
}
}
[ecLock unlock];
/* Put self in background.
*/
if ([cmdDefs boolForKey: @"Daemon"] == YES)
{
int pid = fork();
if (pid == 0)
{
cmdFlagDaemon = YES;
setpgid(0, getpid());
}
else
{
if (pid < 0)
{
printf("Failed fork to run as daemon.\r\n");
}
else
{
printf("Process backgrounded (running as daemon)\r\n");
}
exit(0);
}
}
/* And make sure that regular timers run.
*/
[self triggerCmdTimeout];
return self;
}
/*
* Implement the CmdConfig protocol.
*/
- (void) replaceFile: (NSData*)data
name: (NSString*)name
isConfig: (BOOL)f
{
[NSException raise: NSGenericException
format: @"Illegal method call"];
}
- (oneway void) requestConfigFor: (id<CmdConfig>)c
{
[NSException raise: NSGenericException
format: @"Illegal method call"];
}
- (void) requestFile: (BOOL)flag
name: (NSString*)name
for: (id<CmdConfig>)c
{
[NSException raise: NSGenericException
format: @"Illegal method call"];
}
- (oneway void) updateConfig: (in bycopy NSData*)info
{
id plist = [NSPropertyListSerialization
propertyListWithData: info
options: NSPropertyListMutableContainers
format: 0
error: 0];
if (nil != plist)
{
[self _update: plist];
}
}
- (id) server: (NSString *)serverName
{
RemoteServer *server;
server = (RemoteServer *)[servers objectForKey: serverName];
if (server == nil)
{
NSLog (@"Trying to ask for not-existent server %@", serverName);
return nil;
}
return [server proxy];
}
- (id) server: (NSString *)serverName forNumber: (NSString*)num
{
RemoteServer *server;
NSArray *config;
server = (RemoteServer *)[servers objectForKey: serverName];
if (server == nil)
{
NSLog (@"Trying to ask for not-existent server %@", serverName);
return nil;
}
config = [server multiple];
if (config != nil && [config count] > 1)
{
int val = -1;
unsigned count = [config count];
/*
* Get trailing two digits of number ... in range 00 to 99
*/
if ([num length] >= 2)
{
val = [[num substringFromIndex: [num length] - 2] intValue];
}
if (val < 0)
{
val = 0;
}
/*
* Try to find a broadcast server with a numeric range matching
* the number we were given.
*/
while (count-- > 0)
{
NSDictionary *d = [config objectAtIndex: count];
if (val >= [[d objectForKey: @"Low"] intValue]
&& val <= [[d objectForKey: @"High"] intValue])
{
return [[server proxy] BCPproxy: count];
}
}
[self cmdError: @"Attempt to get %@ server for number %@ with bad config",
serverName, num];
return nil;
}
return [server proxy];
}
- (BOOL) isServerMultiple: (NSString *)serverName
{
RemoteServer *server;
server = (RemoteServer *)[servers objectForKey: serverName];
if (server == nil)
{
NSLog (@"Trying to ask for not-existent server %@", serverName);
return NO;
}
return ([server multiple] == nil) ? NO : YES;
}
@end
@implementation EcProcess (Private)
- (void) cmdMesgrelease: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf: @"controls double release memory error detection"];
return;
}
if ([[msg objectAtIndex: 0] caseInsensitiveCompare: @"help"] == NSOrderedSame)
{
[self cmdPrintf: @"controls double release memory error detection\n"];
[self cmdPrintf: @"to report if an object is released too many times.\n"];
[self cmdPrintf: @"This has a big impact on program performance.\n"];
[self cmdPrintf: @"'release yes' turns on checking\n"];
[self cmdPrintf: @"'release no' turns off checking\n"];
[self cmdPrintf: @"'release default' reverts to default setting\n"];
[self cmdPrintf: @"'release' reports current status\n"];
return;
}
if ([msg count] == 1)
{
[self cmdPrintf: @"Double release checking: %s\n",
[cmdDefs boolForKey: @"Release"] ? "YES" : "NO"];
}
if ([msg count] > 1)
{
if ([[msg objectAtIndex: 1] caseInsensitiveCompare: @"default"]
== NSOrderedSame)
{
[cmdDefs setCommand: nil forKey: @"Release"];
}
else
{
[cmdDefs setCommand: [msg objectAtIndex: 1] forKey: @"Release"];
}
[self cmdPrintf: @"Double release checking: %s\n",
[cmdDefs boolForKey: @"Release"] ? "YES" : "NO"];
}
}
- (void) cmdMesgtesting: (NSArray*)msg
{
if ([msg count] == 0)
{
[self cmdPrintf: @"controls whether server is running in testing mode"];
return;
}
if ([[msg objectAtIndex: 0] caseInsensitiveCompare: @"help"] == NSOrderedSame)
{
[self cmdPrintf: @"controls whether server is running in testing mode\n"];
[self cmdPrintf: @"Behavior in testing mode is server dependent.\n"];
[self cmdPrintf: @"'testing yes' turns on testing mode\n"];
[self cmdPrintf: @"'testing no' turns off testing mode\n"];
[self cmdPrintf: @"'testing default' reverts to default setting\n"];
[self cmdPrintf: @"'testing' reports current status\n"];
return;
}
if ([msg count] == 1)
{
[self cmdPrintf: @"Server running in testing mode: %s\n",
cmdFlagTesting ? "YES" : "NO"];
}
if ([msg count] > 1)
{
if ([[msg objectAtIndex: 1] caseInsensitiveCompare: @"default"]
== NSOrderedSame)
{
[cmdDefs setCommand: nil forKey: @"Testing"];
}
else
{
[cmdDefs setCommand: [msg objectAtIndex: 1] forKey: @"Testing"];
}
[self cmdPrintf: @"Server running in testing mode: %s\n",
cmdFlagTesting ? "YES" : "NO"];
}
}
- (void)_ensureMemLogger
{
NSString *bundle = [cmdDefs stringForKey: @"MemoryLoggerBundle"];
Class cls = Nil;
if (nil == bundle)
{
DESTROY(cmdMemoryLogger);
return;
}
// This is a reasonable fast path if we have already loaded the bundle
cls = NSClassFromString(bundle);
if ((Nil == cls)
|| (NO == [cls conformsToProtocol: @protocol(EcMemoryLogger)]))
{
cls = [self _memoryLoggerClassFromBundle: bundle];
}
if (Nil == cls)
{
// No usable logger class, destroy any we might have
DESTROY(cmdMemoryLogger);
return;
}
if (NO == [cmdMemoryLogger isKindOfClass: cls])
{
// If it's no longer the right class, destroy it
DESTROY(cmdMemoryLogger);
}
if (nil == cmdMemoryLogger)
{
NS_DURING
{
cmdMemoryLogger = [cls new];
}
NS_HANDLER
{
[self cmdWarn: @"Exception creating memory logger: %@",
localException];
}
NS_ENDHANDLER
}
}
- (void) _memCheck
{
BOOL memDebug = [cmdDefs boolForKey: @"Memory"];
FILE *fptr;
int i;
/* /proc/pid/statm reports the process memory size in 4KB pages
*/
fptr = fopen([[NSString stringWithFormat: @"/proc/%d/statm",
[[NSProcessInfo processInfo] processIdentifier]] UTF8String], "r");
memLast = 1;
if (NULL != fptr)
{
if (fscanf(fptr, "%"PRIu64, &memLast) != 1)
{
memLast = 1;
}
else
{
memLast *= (4 * 1024);
if (memLast <= 0) memLast = 1;
}
fclose(fptr);
}
excLast = (uint64_t)[self ecNotLeaked];
[self _ensureMemLogger];
if (nil != cmdMemoryLogger)
{
NS_DURING
{
[cmdMemoryLogger process: self
didUseMemory: memLast
notLeaked: excLast];
}
NS_HANDLER
{
[self cmdWarn:
@"Exception logging memory usage to bundle: %@",
localException];
}
NS_ENDHANDLER
}
/* Do initial population so we can work immediately.
*/
if (0 == memSlot)
{
for (i = 1; i < MEMCOUNT; i++)
{
excRoll[i] = excLast;
memRoll[i] = memLast;
}
memPrev = memStrt = memLast;
excPrev = excStrt = excLast;
}
excRoll[memSlot % MEMCOUNT] = excLast;
memRoll[memSlot % MEMCOUNT] = memLast;
memSlot++;
/* Find the average usage over the last set of samples.
* Round up to a block size.
*/
excAvge = 0;
memAvge = 0;
for (i = 0; i < MEMCOUNT; i++)
{
excAvge += excRoll[i];
memAvge += memRoll[i];
}
excAvge /= MEMCOUNT;
memAvge /= MEMCOUNT;
/* Convert to 1KB blocks.
*/
if (memAvge % 1024)
{
memAvge = ((memAvge / 1024) + 1) * 1024;
}
if (excAvge % 1024)
{
excAvge = ((excAvge / 1024) + 1) * 1024;
}
/* Update peak memory usage if necessary.
*/
if (memLast > memPeak)
{
memPeak = memLast;
}
if (excLast > excPeak)
{
excPeak = excLast;
}
/* If we have a defined maximum memory usage for the process,
* we should shut down with a non-zero status to get a restart.
*/
if (memMaximum > 0 && memPeak > (memMaximum * 1024 * 1024))
{
if (NO == cmdIsQuitting)
{
cmdIsQuitting = YES;
[self cmdQuit: -1];
}
return;
}
/* If the average memory usage is above the threshold (adjusted by any
* change in known unleaked memory), we alert and reset the threshold.
* During the first ten minutes though, we always adjust the threshold and
* we suppress any warnings. This gives us a more stable starting point.
*/
if (memAvge + excPrev - excAvge > memWarn || memSlot < MEMCOUNT)
{
NSInteger inc;
NSInteger pct;
uint64_t iMax = 0;
uint64_t pMax = 0;
/* We increase the threshold for the next alert by a percentage
* of the existing usage or by a fixed increment, whichever is
* the larger.
*/
pct = [cmdDefs integerForKey: @"MemoryPercentage"];
if (pct < 1 || pct > 100)
{
/* Set the next alerting threshold 5%
* the current peak usage,
* ensuring that only serious increases
* in usage will generate an alert.
*/
pct = 5;
}
pMax = (memPeak * (100 + pct)) / 100;
inc = [cmdDefs integerForKey: @"MemoryIncrement"];
if (inc < 100 || inc > 1048576)
{
/* Set the next alerting threshold from
* 50MB above the current peak usage,
* ensuring that only serious increases
* in usage will generate an alert.
*/
inc = 50 * 1024;
}
iMax = memPeak + (inc * 1024);
memWarn = (iMax > pMax) ? iMax : pMax;
if (memWarn % 1024)
{
memWarn = (memWarn/1024 + 1) * 1024;
}
if (memWarn < memAllowed * 1024 * 1024)
{
/* Never warn at less than the allowed memory.
*/
memWarn = memAllowed * 1024 * 1024;
}
/* If not in the initial period, we need to generate an alert
* because the average has risen above the allowed size.
*/
if (memSlot >= MEMCOUNT)
{
uint64_t ePrev;
uint64_t mPrev;
NSDate *when;
ePrev = excPrev;
mPrev = memPrev;
when = AUTORELEASE(memTime);
excPrev = excAvge;
memPrev = memAvge;
memTime = [NSDate new];
if (nil == when)
{
[self cmdError: @"Average memory usage %luKB (grown by %ldKB)"
@" with %luKB (grown by %ldKB) accounted for;"
@" possible leak of %ldKB",
(unsigned long)memAvge/1024,
(long)(memAvge - mPrev)/1024,
(unsigned long)excAvge/1024,
(long)(excAvge - ePrev)/1024,
(long)(memAvge - mPrev + ePrev - excAvge)/1024];
}
else
{
[self cmdError: @"Average memory usage %luKB (grown by %ldKB)"
@" with %luKB (grown by %ldKB) accounted for;"
@" possible leak of %ldKB since %@",
(unsigned long)memAvge/1024,
(long)(memAvge - mPrev)/1024,
(unsigned long)excAvge/1024,
(long)(excAvge - ePrev)/1024,
(long)(memAvge - mPrev + ePrev - excAvge)/1024,
when];
}
}
}
if (YES == memDebug)
{
[self cmdDbg: cmdDetailDbg
msg: @"Memory usage %"PRIu64"KB (reserved: %"PRIu64"KB)",
memLast/1024, excLast/1024];
}
}
- (NSString*) _moveLog: (NSString*)name to: (NSString*)sub
{
NSString *status;
NSString *where;
NS_DURING
{
where = cmdLogsDir(sub);
if (where != nil)
{
NSFileManager *mgr = [NSFileManager defaultManager];
NSString *from;
NSString *path;
NSString *base;
NSString *gzpath;
unsigned count = 0;
path = [where stringByAppendingPathComponent: name];
from = [cmdLogsDir(nil) stringByAppendingPathComponent: name];
/*
* Check for pre-existing file - if found, try another.
*/
base = path;
path = [base stringByAppendingPathExtension: @"0"];
gzpath = [path stringByAppendingPathExtension: @"gz"];
while ([mgr fileExistsAtPath: path] == YES
|| [mgr fileExistsAtPath: gzpath] == YES)
{
NSString *ext;
ext = [stringClass stringWithFormat: @"%u", ++count];
path = [base stringByAppendingPathExtension: ext];
gzpath = [path stringByAppendingPathExtension: @"gz"];
}
if ([mgr movePath: from
toPath: path
handler: nil] == NO)
{
status = [NSString stringWithFormat:
@"Unable to move %@ to %@", from, path];
}
else
{
status = [NSString stringWithFormat:
@"Moved %@ to %@", from, path];
}
}
else
{
status = [NSString stringWithFormat:
@"Unable to archive log %@ into %@", name, sub];
}
}
NS_HANDLER
{
status = [NSString stringWithFormat: @"Problem in %@ with %@ to %@ - %@",
NSStringFromSelector(_cmd), name, sub, localException];
}
NS_ENDHANDLER
return status;
}
- (void) _timedOut: (NSTimer*)timer
{
static BOOL inProgress = NO;
int sig = [self cmdSignalled];
cmdPTimer = nil;
if (sig > 0)
{
cmdIsQuitting = YES;
[self cmdQuit: sig];
}
if (YES == cmdIsQuitting)
{
NSLog(@"_timedOut: ignored because process is quitting");
}
else if (YES == inProgress)
{
NSLog(@"_timedOut: ignored because timeout already in progress");
}
else
{
BOOL delay = NO;
inProgress = YES;
/* We only perform timeouts if the process is actually
* running (don't want them during startup before the
* thing is fully initialised.
* So if not running, skip to scheduling next timeout.
*/
if (YES == cmdIsRunning)
{
NS_DURING
{
NSCalendarDate *now = [NSCalendarDate date];
static int lastDay = 0;
static int lastHour = 0;
static int lastMinute = 0;
static int lastTenSecond = 0;
BOOL newDay = NO;
BOOL newHour = NO;
BOOL newMinute = NO;
BOOL newTenSecond = NO;
int i;
i = [now dayOfWeek];
if (i != lastDay)
{
lastDay = i;
newDay = YES;
newHour = YES;
newMinute = YES;
newTenSecond = YES;
}
i = [now hourOfDay];
if (i != lastHour)
{
lastHour = i;
newHour = YES;
newMinute = YES;
newTenSecond = YES;
}
i = [now minuteOfHour];
if (i != lastMinute)
{
lastMinute = i;
newMinute = YES;
newTenSecond = YES;
}
i = [now secondOfMinute] / 10;
if (i != lastTenSecond)
{
lastTenSecond = i;
newTenSecond = YES;
}
if (YES == newTenSecond)
{
[self cmdNewServer];
}
if (YES == newMinute)
{
[self ecNewMinute: now];
}
if (YES == newHour)
{
[self ecNewHour: now];
}
if (YES == newDay)
{
[self ecNewDay: now];
}
if (cmdTimSelector != 0)
{
[self performSelector: cmdTimSelector];
}
}
NS_HANDLER
{
NSLog(@"Exception performing regular timeout: %@",
localException);
delay = YES; // Avoid runaway logging.
}
NS_ENDHANDLER
}
if (cmdPTimer == nil)
{
NSTimeInterval when = cmdTimInterval;
if (when < 0.001 || (when < 10.0 && YES == delay))
{
when = 10.0;
}
if (when > 300.0)
{
when = 60.0;
}
cmdPTimer =
[NSTimer scheduledTimerWithTimeInterval: when
target: self
selector: @selector(_timedOut:)
userInfo: nil
repeats: NO];
}
inProgress = NO;
}
}
- (void) _update: (NSMutableDictionary*)info
{
NSMutableDictionary *newConfig;
NSDictionary *dict;
NSEnumerator *enumerator;
NSString *key;
newConfig = [NSMutableDictionary dictionaryWithCapacity: 32];
/*
* Put all values for this application in the cmdConf dictionary.
*/
dict = [info objectForKey: cmdLogName()];
if (dict != nil)
{
enumerator = [dict keyEnumerator];
while ((key = [enumerator nextObject]) != nil)
{
id obj;
if ([noNetConfig containsObject: key])
{
[self cmdWarn: @"Bad key '%@' in net config.", key];
continue;
}
obj = [dict objectForKey: key];
[newConfig setObject: obj forKey: key];
}
}
/*
* Add any default values to the cmdConf
* dictionary where we don't have application
* specific values.
*/
dict = [info objectForKey: @"*"];
if (dict)
{
enumerator = [dict keyEnumerator];
while ((key = [enumerator nextObject]) != nil)
{
if ([newConfig objectForKey: key] == nil)
{
id obj;
if ([noNetConfig containsObject: key])
{
[self cmdWarn: @"Bad key '%@' in net config.", key];
continue;
}
obj = [dict objectForKey: key];
[newConfig setObject: obj forKey: key];
}
}
}
dict = [info objectForKey: @"Operators"];
if (dict != nil && dict != cmdOperators)
{
ASSIGNCOPY(cmdOperators, dict);
}
if (nil == cmdConf || [cmdConf isEqual: newConfig] == NO)
{
NSString *err = nil;
NS_DURING
[self cmdUpdate: newConfig];
NS_HANDLER
NSLog(@"Problem before updating config (in cmdUpdate:) %@",
localException);
err = @"the -cmdUpdate: method raised an exception";
NS_ENDHANDLER
if (nil == err)
{
NS_DURING
err = [self cmdUpdated];
NS_HANDLER
NSLog(@"Problem after updating config (in cmdUpdated) %@",
localException);
err = @"the -cmdUpdated method raised an exception";
NS_ENDHANDLER
}
if ([err length] > 0)
{
EcAlarm *a;
/* Truncate additional text to fit if necessary.
*/
err = [err stringByTrimmingSpaces];
if ([err length] > 255)
{
err = [err substringToIndex: 255];
while (255 < strlen([err UTF8String]))
{
err = [err substringToIndex: [err length] - 1];
}
}
a = [EcAlarm alarmForManagedObject: nil
at: nil
withEventType: EcAlarmEventTypeProcessingError
probableCause: EcAlarmConfigurationOrCustomizationError
specificProblem: @"Fatal configuration error"
perceivedSeverity: EcAlarmSeverityMajor
proposedRepairAction:
_(@"Correct config (check additional text and/or log for details).")
additionalText: err];
[self alarm: a];
[alarmDestination shutdown];
cmdIsQuitting = YES;
[self cmdQuit: 1];
}
else
{
[self clearConfigurationFor: nil
specificProblem: @"Fatal configuration error"
additionalText: @"Configuration updated"];
}
}
}
@end
@implementation EcProcess (Test)
- (bycopy NSString*) ecTestCommand: (in bycopy NSString*)command
{
NSEnumerator *enumerator;
NSMutableArray *words;
NSString *word;
command = [command stringByTrimmingSpaces];
words = [NSMutableArray arrayWithCapacity: 16];
enumerator = [[command componentsSeparatedByString: @" "] objectEnumerator];
while ((word = [enumerator nextObject]) != nil)
{
[words addObject: [word stringByTrimmingSpaces]];
}
if ([words count] == 0)
{
return nil;
}
return [self cmdMesg: words];
}
- (bycopy NSData*) ecTestConfigForKey: (in bycopy NSString*)key
{
id result = [cmdDefs objectForKey: key];
if (nil != result)
{
result = [NSPropertyListSerialization
dataFromPropertyList: result
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0];
}
return result;
}
- (void) ecTestSetConfig: (in bycopy NSData*)data
forKey: (in bycopy NSString*)key
{
id val;
if (nil == data)
{
val = data;
}
else
{
val = [NSPropertyListSerialization
propertyListWithData: data
options: NSPropertyListMutableContainers
format: 0
error: 0];
}
[cmdDefs setCommand: val forKey: key];
}
@end
@implementation EcProcess (Defaults)
- (void) _defMemory: (id)val
{
BOOL stats = [val boolValue];
GSDebugAllocationActive(stats);
if (YES == stats && nil == memStats)
{
ASSIGN(memStats, [NSDate date]);
}
if (NO == stats && nil != memStats)
{
DESTROY(memStats);
}
}
- (void) _defRelease: (id)val
{
[NSObject enableDoubleReleaseCheck: [val boolValue]];
}
- (void) _defTesting: (id)val
{
cmdFlagTesting = [val boolValue];
}
@end
@implementation EcDefaultRegistration
static NSMutableDictionary *regDefs = nil;
static BOOL merged = NO;
+ (void) defaultsChanged: (NSUserDefaults*)defs
{
NSEnumerator *e;
NSString *n;
[ecLock lock];
e = [[regDefs allKeys] objectEnumerator];
[ecLock unlock];
while (nil != (n = [e nextObject]))
{
EcDefaultRegistration *d;
id o = nil;
SEL c = NULL;
[ecLock lock];
d = [regDefs objectForKey: n];
if (nil != d)
{
o = [defs objectForKey: n];
if (o != d->obj && NO == [o isEqual: d->obj])
{
ASSIGNCOPY(d->obj, o);
o = d->obj;
c = d->cmd;
}
}
[ecLock unlock];
if (NULL != c && [EcProc respondsToSelector: c])
{
[EcProc performSelector: c withObject: o];
}
}
}
+ (void) initialize
{
regDefs = [NSMutableDictionary new];
}
+ (NSMutableString*) listHelp
{
NSMutableString *out = [NSMutableString stringWithCapacity: 1000];
NSArray *keys;
NSString *prf;
NSEnumerator *e;
NSString *k;
NSUInteger max = 0;
prf = EC_DEFAULTS_PREFIX;
if (nil == prf)
{
prf = @"";
}
[ecLock lock];
keys = [regDefs allKeys];
[ecLock unlock];
e = [keys objectEnumerator];
while (nil != (k = [e nextObject]))
{
EcDefaultRegistration *d;
[ecLock lock];
d = [regDefs objectForKey: k];
if (nil != d->type && nil != d->help)
{
NSUInteger length = [prf length] + 5;
length += [k length] + [d->type length];
if (length > max)
{
max = length;
}
}
[ecLock unlock];
}
keys = [keys sortedArrayUsingSelector: @selector(compare:)];
e = [keys objectEnumerator];
while (nil != (k = [e nextObject]))
{
EcDefaultRegistration *d;
[ecLock lock];
d = [regDefs objectForKey: k];
if (nil != d->type && nil != d->help)
{
/* If the help text is short enough, put it all on one line.
*/
if ([d->help length] + max < 80)
{
NSMutableString *m;
m = [NSMutableString stringWithFormat: @"-%@%@ [%@] ",
prf, k, d->type];
while ([m length] < max)
{
[m appendString: @" "];
}
[out appendFormat: @"%@%@\n", m, d->help];
}
else
{
[out appendFormat: @"-%@%@ [%@]\n %@\n",
prf, k, d->type, d->help];
}
}
[ecLock unlock];
}
return out;
}
+ (NSDictionary*) merge: (NSDictionary*)d
{
NSMutableDictionary *m = AUTORELEASE([d mutableCopy]);
NSEnumerator *e;
NSString *k;
if (nil == m)
{
m = [NSMutableDictionary dictionaryWithCapacity: [regDefs count]];
}
[ecLock lock];
merged = YES;
e = [regDefs keyEnumerator];
while (nil != (k = [e nextObject]))
{
EcDefaultRegistration *r = [regDefs objectForKey: k];
if (nil != r->val && nil == [d objectForKey: k])
{
[m setObject: r->val forKey: k];
}
}
[ecLock unlock];
return m;
}
+ (void) registerDefault: (NSString*)name
withTypeText: (NSString*)type
andHelpText: (NSString*)help
action: (SEL)cmd
value: (id)value
{
static NSCharacterSet *w = nil;
EcDefaultRegistration *d;
BOOL alreadyMerged;
if (nil == w)
{
w = RETAIN([NSCharacterSet whitespaceAndNewlineCharacterSet]);
}
if ([type length] > 0)
{
type = [type stringByTrimmingSpaces];
if ([type length] == 0)
{
type = nil;
}
else
{
NSUInteger length = [type length];
NSMutableString *m = nil;
while (length-- > 0)
{
unichar u = [type characterAtIndex: length];
if (u != ' ' && [w characterIsMember: u])
{
if (nil == m)
{
m = AUTORELEASE([type mutableCopy]);
type = m;
}
[m replaceCharactersInRange: NSMakeRange(length, 1)
withString: @" "];
}
}
}
}
if ([help length] > 0)
{
help = [help stringByTrimmingSpaces];
if ([help length] == 0)
{
help = nil;
}
}
[ecLock lock];
alreadyMerged = merged;
d = [regDefs objectForKey: name];
if (nil == d)
{
d = [EcDefaultRegistration new];
ASSIGNCOPY(d->name, name);
[regDefs setObject: d forKey: d->name];
RELEASE(d);
}
ASSIGNCOPY(d->type, type);
ASSIGNCOPY(d->help, help);
if (0 != cmd)
{
d->cmd = cmd;
}
ASSIGNCOPY(d->val, value);
[ecLock unlock];
if (YES == alreadyMerged && nil != d->val)
{
[NSException raise: NSInternalInconsistencyException
format: @"default '%@' value '%@' registered too late",
d->name, d->val];
}
}
+ (void) showHelp
{
GSPrintf(stderr, @"%@", [self listHelp]);
}
- (void) dealloc
{
RELEASE(name);
RELEASE(type);
RELEASE(help);
RELEASE(obj);
[super dealloc];
}
@end