Enterprise Control/Configuration/Logging package ... preliminary check in.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/devmodules/dev-libs/ec@34775 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2012-02-19 11:59:22 +00:00
commit acbe80c889
38 changed files with 26394 additions and 0 deletions

34
Client.h Normal file
View file

@ -0,0 +1,34 @@
@interface ClientInfo : NSObject
{
id<CmdClient> theServer;
id obj;
NSString *name;
NSDate *lastUnanswered; /* Last unanswered ping. */
unsigned fwdSequence; /* Last ping sent TO client. */
unsigned revSequence; /* Last gnip sent BY client. */
NSMutableSet *files; /* Want update info for these. */
NSData *config; /* Config info for client. */
BOOL pingOk;
BOOL transient;
BOOL unregistered;
}
- (NSComparisonResult) compare: (ClientInfo*)other;
- (NSData*) config;
- (NSMutableSet*) files;
- (BOOL) gnip: (unsigned)seq;
- (id) initFor: (id)obj
name: (NSString*)n
with: (id<CmdClient>)svr;
- (NSDate*) lastUnanswered;
- (NSString*) name;
- (id) obj;
- (void) ping;
- (void) setConfig: (NSData*)c;
- (void) setName: (NSString*)n;
- (void) setObj: (id)o;
- (void) setTransient: (BOOL)flag;
- (void) setUnregistered: (BOOL)flag;
- (BOOL) transient;
@end

170
Client.m Normal file
View file

@ -0,0 +1,170 @@
#import <Foundation/Foundation.h>
#import "EcProcess.h"
#import "Client.h"
@implementation ClientInfo
- (NSComparisonResult) compare: (ClientInfo*)other
{
return [name compare: [other name]];
}
- (NSData*) config
{
return config;
}
- (void) dealloc
{
NSConnection *c = [obj connectionForProxy];
if (c != nil)
{
RETAIN(c);
[[NSNotificationCenter defaultCenter]
removeObserver: theServer
name: NSConnectionDidDieNotification
object: (id)c];
if (unregistered == NO && [c isValid] == YES)
{
NS_DURING
{
[obj cmdQuit: 0];
}
NS_HANDLER
{
NSLog(@"cmdQuit: to %@ - %@", name, localException);
}
NS_ENDHANDLER
}
RELEASE(c);
}
DESTROY(lastUnanswered);
DESTROY(config);
DESTROY(files);
DESTROY(name);
DESTROY(obj);
[super dealloc];
}
- (NSMutableSet*) files
{
return files;
}
- (BOOL) gnip: (unsigned)s
{
if (s != revSequence + 1 && revSequence != 0)
{
NSLog(@"Gnip from %@ seq: %u when expecting %u", name, s, revSequence);
if (s == 0)
{
fwdSequence = 0; // Reset
}
}
revSequence = s;
if (revSequence == fwdSequence)
{
DESTROY(lastUnanswered);
return YES; /* up to date */
}
else
{
return NO;
}
}
- (id) initFor: (id)o
name: (NSString*)n
with: (id<CmdClient>)s
{
self = [super init];
if (self != nil)
{
theServer = s;
files = [NSMutableSet new];
[self setObj: o];
[self setName: n];
pingOk = [o respondsToSelector: @selector(cmdPing:sequence:extra:)];
if (pingOk == NO)
NSLog(@"Warning - %@ is an old server ... it can't be pinged", n);
}
return self;
}
- (NSDate*) lastUnanswered
{
return lastUnanswered;
}
- (NSString*) name
{
return name;
}
- (id) obj
{
return obj;
}
- (void) ping
{
if (pingOk == NO)
{
return;
}
if (fwdSequence == revSequence)
{
lastUnanswered = [[NSDate date] retain];
NS_DURING
{
[obj cmdPing: theServer sequence: ++fwdSequence extra: nil];
}
NS_HANDLER
{
NSLog(@"Ping to %@ - %@", name, localException);
}
NS_ENDHANDLER
}
else
{
NSLog(@"Ping to %@ when one is already in progress.", name);
}
}
- (void) setConfig: (NSData*)c
{
ASSIGN(config, c);
}
- (void) setName: (NSString*)n
{
ASSIGN(name, n);
}
- (void) setObj: (id)o
{
ASSIGN(obj, o);
}
- (void) setTransient: (BOOL)flag
{
transient = flag;
}
- (void) setUnregistered: (BOOL)flag
{
unregistered = flag;
}
- (BOOL) transient
{
return transient;
}
@end

2436
Command.m Normal file

File diff suppressed because it is too large Load diff

900
Console.m Normal file
View file

@ -0,0 +1,900 @@
#include <Foundation/Foundation.h>
#include "EcProcess.h"
#include "NSFileHandle+Printf.h"
#include <sys/signal.h>
#if WITH_READLINE
# include <stdlib.h>
# include <readline/readline.h>
# include <readline/history.h>
#endif
static BOOL commandIsRepeat (NSString *string)
{
switch ([string length])
{
case 1: return [string isEqualToString: @"r"];
case 2: return [string isEqualToString: @"re"];
case 3: return [string isEqualToString: @"rep"];
case 4: return [string isEqualToString: @"repe"];
case 5: return [string isEqualToString: @"repea"];
case 6: return [string isEqualToString: @"repeat"];
default: return NO;
}
}
@interface Console : EcProcess <RunLoopEvents, Console>
{
NSFileHandle *ichan;
NSFileHandle *ochan;
NSMutableData *data;
NSTimer *timer;
NSString *local;
NSString *host;
NSString *name;
NSString *user;
NSString *pass;
NSString *rnam;
id server;
int pos;
}
- (void) connectionBecameInvalid: (NSNotification*)notification;
#if !WITH_READLINE
- (void) didRead: (NSNotification*)notification;
#endif
- (void) didWrite: (NSNotification*)notification;
- (void) doCommand: (NSMutableArray*)words;
- (void) timedOut;
@end
@implementation Console
- (BOOL) cmdIsClient
{
return NO; // Not a client of the Command server
}
- (void) cmdQuit: (int)sig
{
[ochan puts: @"\nExiting\n"];
if (server)
{
id con = [(NSDistantObject*)server connectionForProxy];
[[NSNotificationCenter defaultCenter] removeObserver: self
name: NSConnectionDidDieNotification
object: con];
NS_DURING
{
[server unregister: self];
}
NS_HANDLER
{
NSLog(@"Exception unregistering from Control: %@", localException);
}
NS_ENDHANDLER
[con invalidate];
DESTROY(server);
}
if (ichan)
{
[[NSNotificationCenter defaultCenter] removeObserver: self
name: NSFileHandleReadCompletionNotification
object: (id)ichan];
if ([ichan fileDescriptor] >= 0)
{
[ichan closeFile];
}
[ichan release];
ichan = nil;
}
if (ochan)
{
if ([ochan fileDescriptor] >= 0)
{
[ochan closeFile];
}
[ochan release];
ochan = nil;
}
exit(0);
}
- (void) connectionBecameInvalid: (NSNotification*)notification
{
id conn = [notification object];
[[NSNotificationCenter defaultCenter] removeObserver: self
name: NSConnectionDidDieNotification
object: conn];
if ([conn isKindOfClass: [NSConnection class]])
{
if (server && [(NSDistantObject*)server connectionForProxy] == conn)
{
[server release];
server = nil;
NSLog(@"Lost connection to Control server.");
[self cmdQuit: 0];
}
}
}
- (void) dealloc
{
[rnam release];
[user release];
[pass release];
[host release];
[name release];
[local release];
[data release];
if (timer)
{
[timer invalidate];
timer = nil;
}
[self cmdQuit: 0];
[super dealloc];
}
- (void) receivedEvent: (void *)descriptorData
type: (RunLoopEventType)type
extra: (void*)extra
forMode: (NSString*)mode
{
#if WITH_READLINE
rl_callback_read_char();
#else
NSLog(@"ERROR: this should not get called w/o readline: %s",
__PRETTY_FUNCTION__);
#endif
}
- (void)processReadLine:(NSString *)_line
{
NSAutoreleasePool *arp;
NSMutableArray *words;
NSEnumerator *wordsEnum;
NSString *word;
if (_line == nil)
{
[self cmdQuit: 0];
}
if ([_line length] == 0)
{
return;
}
arp = [[NSAutoreleasePool alloc] init];
/* setup word array */
words = [NSMutableArray arrayWithCapacity:16];
wordsEnum = [[_line componentsSeparatedByString:@" "] objectEnumerator];
while ((word = [wordsEnum nextObject]) != nil)
[words addObject:[word stringByTrimmingSpaces]];
/* invoke server */
[self doCommand:words];
[arp release];
}
#if WITH_READLINE
- (char **)complete:(const char *)_text range:(NSRange)_range
{
return NULL; /* no completion yet */
}
#endif
#if !WITH_READLINE
- (void) didRead: (NSNotification*)notification
{
NSDictionary *userInfo = [notification userInfo];
NSData *d;
d = [[userInfo objectForKey: NSFileHandleNotificationDataItem] retain];
if (d == nil || [d length] == 0)
{
if (d != nil)
{
[d release];
}
[self cmdQuit: 0];
}
else
{
char *bytes;
int len;
int eol;
int done = 0;
[data appendData: d];
[d release];
bytes = (char*)[data mutableBytes];
len = [data length];
for (eol = 0; eol < len; eol++)
{
if (bytes[eol] == '\r' || bytes[eol] == '\n')
{
char *word = bytes;
char *end = word;
NSMutableArray *a;
a = [NSMutableArray arrayWithCapacity: 1];
bytes[eol++] = '\0';
while (eol < len && isspace(bytes[eol]))
{
eol++;
}
done = eol;
while (end && *end)
{
word = end;
while (*word && isspace(*word))
{
word++;
}
end = word;
if (*word == '"' || *word == '\'')
{
char term = *word;
char *ptr;
end = ++word;
ptr = end;
while (*end)
{
if (*end == term)
{
end++;
break;
}
if (*end == '\\')
{
end++;
switch (*end)
{
case '\\': *ptr++ = '\\'; break;
case 'b': *ptr++ = '\b'; break;
case 'f': *ptr++ = '\f'; break;
case 'n': *ptr++ = '\n'; break;
case 'r': *ptr++ = '\r'; break;
case 't': *ptr++ = '\t'; break;
case 'x':
{
int val = 0;
if (isxdigit(end[1]))
{
end++;
val <<= 4;
if (islower(*end))
{
val += *end - 'a' + 10;
}
else if (isupper(*end))
{
val += *end - 'A' + 10;
}
else
{
val += *end - '0';
}
}
if (isxdigit(end[1]))
{
end++;
val <<= 4;
if (islower(*end))
{
val += *end - 'a' + 10;
}
else if (isupper(*end))
{
val += *end - 'A' + 10;
}
else
{
val += *end - '0';
}
}
*ptr++ = val;
}
break;
case '0':
{
int val = 0;
if (isdigit(end[1]))
{
end++;
val <<= 3;
val += *end - '0';
}
if (isdigit(end[1]))
{
end++;
val <<= 3;
val += *end - '0';
}
if (isdigit(end[1]))
{
end++;
val <<= 3;
val += *end - '0';
}
*ptr++ = val;
}
break;
default: *ptr++ = *end; break;
}
if (*end)
{
end++;
}
}
else
{
*ptr++ = *end++;
}
}
*ptr = '\0';
}
else
{
while (*end && !isspace(*end))
{
if (isupper(*end))
{
*end = tolower(*end);
}
end++;
}
}
if (*end)
{
*end++ = '\0';
}
else
{
end = 0;
}
if (word && *word)
{
[a addObject: [NSString stringWithCString: word]];
}
}
[self doCommand: a];
if (ichan == nil)
{
return; /* Quit while doing command. */
}
}
}
if (done > 0)
{
memcpy(bytes, &bytes[done], len - done);
[data setLength: len - done];
}
[ichan readInBackgroundAndNotify]; /* Need more data. */
}
}
#endif
- (void) didWrite: (NSNotification*)notification
{
NSDictionary *userInfo = [notification userInfo];
NSString *e;
e = [userInfo objectForKey: GSFileHandleNotificationError];
if (e)
{
[self cmdQuit: 0];
}
}
- (void) doCommand: (NSMutableArray*)words
{
NSString *cmd;
static NSArray *pastWords = nil;
if (words == nil || [words count] == 0)
{
cmd = @"";
}
else
{
cmd = [words objectAtIndex: 0];
}
if ([cmd isEqual: @"quit"] && [words count] == 1)
{
[self cmdQuit: 0];
return;
}
#if !WITH_READLINE
if (user == nil)
{
if ([cmd length] > 0)
{
user = [cmd retain];
[ochan puts: @"Enter your password: "];
}
else
{
[ochan puts: @"Enter your username: "];
}
}
else if (pass == nil)
{
if ([cmd length] > 0)
{
pass = [cmd retain];
server = [NSConnection rootProxyForConnectionWithRegisteredName: name
host: host
usingNameServer: [NSSocketPortNameServer sharedInstance]];
if (server == nil)
{
if ([host isEqual: @"*"])
{
[ochan printf: @"Unable to connect to %@ on any host.\n",
name];
}
else
{
[ochan printf: @"Unable to connect to %@ on %@.\n",
name, host];
}
[self cmdQuit: 0];
return;
}
else
{
NSString *reject;
[rnam release];
rnam = [NSString stringWithFormat: @"%@:%@", user, local];
[rnam retain];
[server retain];
[server setProtocolForProxy: @protocol(Control)];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(connectionBecameInvalid:)
name: NSConnectionDidDieNotification
object: (id)[server connectionForProxy]];
[[server connectionForProxy] setDelegate: self];
reject = [server registerConsole: self
name: rnam
pass: pass];
if (reject == nil)
{
[words removeAllObjects];
[words addObject: @"list"];
[words addObject: @"consoles"];
[server command: [NSPropertyListSerialization
dataFromPropertyList: words
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0] from: rnam];
}
else
{
[pass release];
pass = nil;
[user release];
user = nil;
[ochan puts: [NSString stringWithFormat:
@"Connection attempt rejected - %@\n", reject]];
[ochan puts: @"Enter your username: "];
}
}
}
else
{
[ochan puts: @"Enter your password: "];
}
}
else
#endif /* WITH_READLINE */
if (commandIsRepeat(cmd))
{
if (pastWords == nil)
{
[ochan puts: @"No command to repeat.\n"];
}
else
{
[ochan printf: @"Repeating command `%@' -\n",
[pastWords componentsJoinedByString: @" "]];
[server command: [NSPropertyListSerialization
dataFromPropertyList: pastWords
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0] from: rnam];
}
}
else
{
ASSIGN(pastWords, words);
[server command: [NSPropertyListSerialization
dataFromPropertyList: words
format: NSPropertyListBinaryFormat_v1_0
errorDescription: 0] from: rnam];
}
}
#define add(C) { if (op - out >= len - 1) { int i = op - out; len += 128; [d setLength: len]; out = [d mutableBytes]; op = &out[i];} *op++ = C; }
- (oneway void) information: (NSString*)s
{
int ilen = [s cStringLength] + 1;
int len = (ilen + 4) * 2;
char buf[ilen];
const char *ip = buf;
NSMutableData *d = [NSMutableData dataWithCapacity: len];
char *out = [d mutableBytes];
char *op = out;
char c;
[s getCString: buf];
buf[ilen-1] = '\0';
[d setLength: len];
if (pos)
{
add('\n');
pos = 0;
}
while (*ip)
{
switch (*ip)
{
case '\r':
case '\n':
pos = 0;
add(*ip);
break;
case '\t':
if (pos >= 72)
{
pos = 0;
add('\n');
}
else
{
do
{
add(' ');
pos++;
}
while (pos % 8);
}
break;
default:
c = *ip;
if (c < ' ')
{
c = ' ';
}
if (c > 126)
{
c = ' ';
}
if (c == ' ')
{
int l = 0;
while (ip[l] && isspace(ip[l])) l++;
while (ip[l] && !isspace(ip[l])) l++;
l--;
if ((pos + l) >= 80 && l < 80)
{
add('\n');
pos = 0;
}
else
{
add(' ');
pos++;
}
}
else
{
add(c);
pos++;
}
break;
}
ip++;
}
[d setLength: op - out];
[ochan writeData: d];
}
#define NUMARGS 3
- (id) init
{
NSDictionary *appDefaults;
id objects[NUMARGS];
id keys[NUMARGS];
keys[0] = @"BSEffectiveUser";
objects[0] = @"";
keys[1] = @"BSDaemon";
objects[1] = @"NO"; /* Never run as daemon */
keys[2] = @"BSNoDaemon";
objects[2] = @"YES"; /* Never run as daemon */
appDefaults = [NSDictionary dictionaryWithObjects: objects
forKeys: keys
count: NUMARGS];
self = [super initWithDefaults: appDefaults];
if (self)
{
NSUserDefaults *defs = [self cmdDefaults];
local = [[[NSHost currentHost] name] retain];
name = [defs stringForKey: @"ControlName"];
if (name == nil)
{
name = @"Control";
}
host = [defs stringForKey: @"ControlHost"];
if (host == nil)
{
host = @"*";
}
if ([host length] == 0)
{
host = local;
}
[host retain];
rnam = [[NSString stringWithFormat: @"%@:%@", user, local] retain];
data = [[NSMutableData alloc] init];
ichan = [[NSFileHandle fileHandleWithStandardInput] retain];
ochan = [[NSFileHandle fileHandleWithStandardOutput] retain];
#if !WITH_READLINE
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(didRead:)
name: NSFileHandleReadCompletionNotification
object: (id)ichan];
[ochan puts: @"Enter your username: "];
[ichan readInBackgroundAndNotify];
#endif
}
return self;
}
- (void) timedOut
{
return;
}
/* readline handling */
#if WITH_READLINE
/* readline callbacks have no context, so we need this one ... */
static Console *rlConsole = nil;
static void readlineReadALine(char *_line)
{
NSString *s = _line ? [[NSString alloc] initWithCString:_line] : nil;
[rlConsole processReadLine:s];
if (_line != NULL && strlen(_line) > 0)
{
add_history(_line);
}
DESTROY(s);
}
static char **consoleCompleter(const char *text, int start, int end)
{
return [rlConsole complete:text range:NSMakeRange(start, end - start)];
}
- (void)activateReadline {
rlConsole = self;
/* setup readline */
rl_readline_name = "Console";
rl_attempted_completion_function = consoleCompleter;
rl_callback_handler_install("" /* prompt */, readlineReadALine);
atexit(rl_callback_handler_remove);
/* register in runloop */
[[NSRunLoop currentRunLoop] addEvent:(void*)(uintptr_t)0 type:ET_RDESC
watcher:self forMode:NSDefaultRunLoopMode];
}
- (void)deactivateReadline
{
[[NSRunLoop currentRunLoop] removeEvent:(void*)(uintptr_t)0 type:ET_RDESC
forMode:NSDefaultRunLoopMode all:YES];
rl_callback_handler_remove();
rlConsole = nil;
}
- (void) setupConnectionr
{
while (self->server == nil)
{
NSString *reject;
char lUser[128], *llUser;
/* read username */
printf("Login: ");
fflush(stdout);
llUser = fgets(lUser, sizeof(lUser), stdin);
if (llUser == NULL || strlen(llUser) == 0)
{
/* user pressed ctrl-D, just exit */
exit(0);
}
llUser[strlen(llUser) - 1] = '\0';
if (strlen(llUser) < 1)
{
/* user just pressed enter, retry */
continue;
}
/* read password (glibc documentation says not to use getpass?) */
const char *lPassword = getpass("Password: ");
if (lPassword == NULL) {
NSLog(@"Could not read password: %s", strerror(errno));
exit(1);
}
/* setup connection to server */
self->server =
[[NSConnection rootProxyForConnectionWithRegisteredName: name
host: host
usingNameServer:
[NSSocketPortNameServer sharedInstance]] retain];
if (self->server == nil)
{
if ([host isEqual: @"*"])
{
[ochan printf: @"Unable to connect to %@ on any host.\n",
name];
}
else
{
[ochan printf: @"Unable to connect to %@ on %@.\n",
name, host];
}
[self cmdQuit: 0];
return;
}
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(connectionBecameInvalid:)
name: NSConnectionDidDieNotification
object: (id)[self->server connectionForProxy]];
[server setProtocolForProxy: @protocol(Control)];
[[server connectionForProxy] setDelegate: self];
/* attempt login */
self->user = [[NSString alloc] initWithCString:lUser];
self->pass = [[NSString alloc] initWithCString:lPassword];
self->rnam =
[[NSString alloc] initWithFormat:@"%@:%@", self->user, self->local];
reject = [self->server registerConsole:self
name:self->rnam pass:self->pass];
/* connection failed */
if (reject != nil) {
[ochan puts:
[NSString stringWithFormat:
@"Connection attempt rejected - %@\n", reject]];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name: NSConnectionDidDieNotification
object: (id)[self->server connectionForProxy]];
DESTROY(self->pass);
DESTROY(self->user);
DESTROY(self->rnam);
DESTROY(self->server);
}
}
}
#endif /* WITH_READLINE */
@end
void
inner_main()
{
extern NSString *cmdVersion(NSString*);
Console *console;
NSAutoreleasePool *arp;
arp = [[NSAutoreleasePool alloc] init];
cmdVersion(@"$Date: 2012-02-13 08:11:49 +0000 (Mon, 13 Feb 2012) $ $Revision: 65934 $");
console = [Console new];
[arp release];
arp = [[NSAutoreleasePool alloc] init];
#if WITH_READLINE
[console setupConnection];
[console activateReadline];
#endif
/* Let standard signals simply kill the Console
*/
signal(SIGHUP, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
[[NSRunLoop currentRunLoop] run];
#if WITH_READLINE
[console deactivateReadline];
#endif
[console release];
[arp release];
exit(0);
}
int
main(int argc, char *argv[])
{
inner_main();
return 0;
}

2548
Control.m Normal file

File diff suppressed because it is too large Load diff

589
EcAlarm.h Normal file
View file

@ -0,0 +1,589 @@
#ifndef _ECALARM_H
#define _ECALARM_H
#import <Foundation/NSObject.h>
@class NSCoder;
@class NSDate;
@class NSString;
/**
* The EcAlarmEventType enumeration defines the different types of
* alarm we support.<br />
* The enumerated values MUST be matched by those in your SNMP MIB if
* you wish to have your software interact with SNMP tools.<br />
* NB. EcAlarmEventTypeUnknown must <em>NOT</em> be used in an alarm ...
* it is employed solely as a marker for the case where a lookup of
* type from probable cause can not determine a specific event type.
* <deflist>
* <term>EcAlarmEventTypeUnknown</term>
* <desc>Not used</desc>
* <term>EcAlarmEventTypeCommunications</term>
* <desc>A communications/networking/protocol issue</desc>
* <term>EcAlarmEventTypeEnvironmental</term>
* <desc>An external environmental issue (eg building on fire)</desc>
* <term>EcAlarmEventTypeEquipment</term>
* <desc>A hardware problem (eg disk failure)</desc>
* <term>EcAlarmEventTypeProcessingError</term>
* <desc>A software problem (a bug or misconfiguration)</desc>
* <term>EcAlarmEventTypeQualityOfService</term>
* <desc>A system not running as well as expected ... eg. overloaded</desc>
* </deflist>
*/
typedef enum {
EcAlarmEventTypeUnknown = 0,
EcAlarmEventTypeCommunications = 2,
EcAlarmEventTypeEnvironmental = 3,
EcAlarmEventTypeEquipment = 4,
EcAlarmEventTypeProcessingError = 10,
EcAlarmEventTypeQualityOfService = 11,
} EcAlarmEventType;
/** The EcAlarmProbableCause enumeration defines the probable causes of
* alarms produced by the system.<br />
* These are taken from the CCITT X.733 specification with the numeric
* values from the CCITT X.721 specification.<br />
* Enumeration values include a comment to specify which
* EcAlarmEventType they apply to.
* <deflist>
* <term>EcAlarmProbableCauseUnknown</term>
* <desc>Category: Any</desc>
* <term>EcAlarmAdapterError</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmApplicationSubsystemFailure</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmBandwidthReduced</term>
* <desc>Category: QoS</desc>
* <term>EcAlarmCallEstablishmentError</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmCommunicationsProtocolError</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmCommunicationsSubsystemFailure</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmConfigurationOrCustomizationError</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmCongestion</term>
* <desc>Category: QoS</desc>
* <term>EcAlarmCorruptData</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmCpuCyclesLimitExceeded</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmDataSetOrModemError</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmDegradedSignal</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmDTE_DCEInterfaceError</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmEnclosureDoorOpen</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmEquipmentMalfunction</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmExcessiveVibration</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmFileError</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmFireDetected</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmFloodDetected</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmFramingError</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmHeatingOrVentilationOrCoolingSystemProblem</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmHumidityUnacceptable</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmInputOutputDeviceError</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmInputDeviceError</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmLANError</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmLeakDetected</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmLocalNodeTransmissionError</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmLossOfFrame</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmLossOfSignal</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmMaterialSupplyExhausted</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmMultiplexerProblem</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmOutOfMemory</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmOuputDeviceError</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmPerformanceDegraded</term>
* <desc>Category: QoS</desc>
* <term>EcAlarmPowerProblem</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmPressureUnacceptable</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmProcessorProblem</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmPumpFailure</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmQueueSizeExceeded</term>
* <desc>Category: QoS</desc>
* <term>EcAlarmReceiveFailure</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmReceiverFailure</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmRemoteNodeTransmissionError</term>
* <desc>Category: Communications</desc>
* <term>EcAlarmResourceAtOrNearingCapacity</term>
* <desc>Category: QoS</desc>
* <term>EcAlarmResponseTimeExcessive</term>
* <desc>Category: QoS</desc>
* <term>EcAlarmRetransmissionRateExcessive</term>
* <desc>Category: QoS</desc>
* <term>EcAlarmSoftwareProgramAbnormallyTerminated</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmSoftwareProgramError</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmStorageCapacityProblem</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmTemperatureUnacceptable</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmThresholdCrossed</term>
* <desc>Category: QoS</desc>
* <term>EcAlarmTimingProblem</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmToxicLeakDetected</term>
* <desc>Category: Environmental</desc>
* <term>EcAlarmTransmitFailure</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmTransmitterFailure</term>
* <desc>Category: Equipment</desc>
* <term>EcAlarmUnderlyingResourceUnavailable</term>
* <desc>Category: Processing</desc>
* <term>EcAlarmVersionMismatch</term>
* <desc>Category: Processing</desc>
* </deflist>
*/
typedef enum {
EcAlarmProbableCauseUnknown = 0, // Any
EcAlarmAdapterError = 1, // Equipment
EcAlarmApplicationSubsystemFailure = 2, // Processing
EcAlarmBandwidthReduced = 3, // QoS
EcAlarmCallEstablishmentError = 4, // Communications
EcAlarmCommunicationsProtocolError = 5, // Communications
EcAlarmCommunicationsSubsystemFailure = 6, // Communications
EcAlarmConfigurationOrCustomizationError = 7, // Processing
EcAlarmCongestion = 8, // QoS
EcAlarmCorruptData = 9, // Processing
EcAlarmCpuCyclesLimitExceeded = 10, // Processing
EcAlarmDataSetOrModemError = 11, // Equipment
EcAlarmDegradedSignal = 12, // Communications
EcAlarmDTE_DCEInterfaceError = 13, // Communications
EcAlarmEnclosureDoorOpen = 14, // Environmental
EcAlarmEquipmentMalfunction = 15, // Equipment
EcAlarmExcessiveVibration = 16, // Environmental
EcAlarmFileError = 17, // Processing
EcAlarmFireDetected = 18, // Environmental
EcAlarmFloodDetected = 19, // Environmental
EcAlarmFramingError = 20, // Communications
EcAlarmHeatingOrVentilationOrCoolingSystemProblem = 21, // Environmental
EcAlarmHumidityUnacceptable = 22, // Environmental
EcAlarmInputOutputDeviceError = 23, // Equipment
EcAlarmInputDeviceError = 24, // Equipment
EcAlarmLANError = 25, // Communications
EcAlarmLeakDetected = 26, // Environmental
EcAlarmLocalNodeTransmissionError = 27, // Communications
EcAlarmLossOfFrame = 28, // Communications
EcAlarmLossOfSignal = 29, // Communications
EcAlarmMaterialSupplyExhausted = 30, // Environmental
EcAlarmMultiplexerProblem = 31, // Equipment
EcAlarmOutOfMemory = 32, // Processing
EcAlarmOuputDeviceError = 33, // Equipment
EcAlarmPerformanceDegraded = 34, // QoS
EcAlarmPowerProblem = 35, // Equipment
EcAlarmPressureUnacceptable = 36, // Environmental
EcAlarmProcessorProblem = 37, // Equipment
EcAlarmPumpFailure = 38, // Environmental
EcAlarmQueueSizeExceeded = 39, // QoS
EcAlarmReceiveFailure = 40, // Equipment
EcAlarmReceiverFailure = 41, // Equipment
EcAlarmRemoteNodeTransmissionError = 42, // Communications
EcAlarmResourceAtOrNearingCapacity = 43, // QoS
EcAlarmResponseTimeExcessive = 44, // QoS
EcAlarmRetransmissionRateExcessive = 45, // QoS
EcAlarmSoftwareProgramAbnormallyTerminated = 47, // Processing
EcAlarmSoftwareProgramError = 48, // Processing
EcAlarmStorageCapacityProblem = 49, // Processing
EcAlarmTemperatureUnacceptable = 50, // Environmental
EcAlarmThresholdCrossed = 51, // QoS
EcAlarmTimingProblem = 52, // Equipment
EcAlarmToxicLeakDetected = 53, // Environmental
EcAlarmTransmitFailure = 54, // Equipment
EcAlarmTransmitterFailure = 55, // Equipment
EcAlarmUnderlyingResourceUnavailable = 56, // Processing
EcAlarmVersionMismatch = 57, // Processing
} EcAlarmProbableCause;
/** The EcAlarmSeverity enumeration defines the 'perceived severities' of
* alarms produced by the system.<br />
* The enumerated values MUST be matched by those in your SNMP MIB if
* you wish to have your software interact with SNMP tools.<br />
* NB. The use of EcAlarmSeverityIndeterminate should be avoided.
* <deflist>
* <term>EcAlarmSeverityIndeterminate</term><desc>Do not use</desc>
* <term>EcAlarmSeverityCritical</term>
* <desc>Immediate intervention required to restore service</desc>
* <term>EcAlarmSeverityMajor</term>
* <desc>Severe but partial system failure or a problem which
* might recover without intervention</desc>
* <term>EcAlarmSeverityMinor</term>
* <desc>A failure, but one which is likely to recover or which is
* probably not urgent</desc>
* <term>EcAlarmSeverityWarning</term>
* <desc>An unusual event which may not indicate any problem, but
* which ought to be looked into</desc>
* <term>EcAlarmSeverityCleared</term>
* <desc>This indicates the resolution of an earlier issue</desc>
* </deflist>
*/
typedef enum {
EcAlarmSeverityIndeterminate = 0,
EcAlarmSeverityCritical = 1,
EcAlarmSeverityMajor = 2,
EcAlarmSeverityMinor = 3,
EcAlarmSeverityWarning = 4,
EcAlarmSeverityCleared = 5
} EcAlarmSeverity;
/** The EcAlarmTrend enumeration defines the severity trend of alarms
* produced by the system.<br />
* The enumerated values MUST be matched by those in your SNMP MIB if
* you wish to have your software interact with SNMP tools.<br />
* <deflist>
* <term>EcAlarmTrendNone</term>
* <desc>This is not a change in severity of an earlier alarm</desc>
* <term>EcAlarmTrendUp</term>
* <desc>This is a more severe version of an earlier alarm</desc>
* <term>EcAlarmTrendDown</term>
* <desc>This is a less severe version of an earlier alarm</desc>
* </deflist>
*/
typedef enum {
EcAlarmTrendNone = 0,
EcAlarmTrendUp = '+',
EcAlarmTrendDown = '-',
} EcAlarmTrend;
/** This function builds a managed object name from host, process,
* and component.<br />
* The host part may be nil ... for the current host.<br />
* The process part may be nil ... for the current process.<br />
* The component may well be nil if the alert applies to the process as
* a whole rather than to a particular component. This field is typically
* used to identify a particular network connection etc within a process.<br />
* This parses the process and separates out any instance ID (trailing
* hyphen and string of digits). It then builds the managed object from
* four parts (host, process, instance, component) separated by underscores.
* Any underscores in the arguments are replaced by hyphens.<br />
* NB. The total length must not exceed 127 ASCII characters.
*/
NSString *
EcMakeManagedObject(NSString *host, NSString *process, NSString *component);
/** <p>The EcAlarm class encapsulates an alarm to be sent out to a monitoring
* system. It's designed to work cleanly with industry standard SNMP
* alarm monitoring systems. For more information on how the SNMP
* operation works, see the [EcAlarmSinkSNMP] class documentation.
* </p>
* <p>Instances are created and sent to a central coordination point
* where checks are performed to see if there is an existing alarm for
* the same issue. If the incoming alarm does not change the severity
* of an existing alarm, it is ignored, otherwise it may be passed on to
* an external monitoring system. The central coordination system is
* responsible for ensuring that alarms for the same issue are updated
* to contain the first event date, notification ID and a trend indicator.
* </p>
*/
@interface EcAlarm : NSObject <NSCoding,NSCopying>
{
NSString *_managedObject;
NSDate *_eventDate;
NSDate *_firstEventDate;
EcAlarmEventType _eventType;
EcAlarmSeverity _perceivedSeverity;
EcAlarmProbableCause _probableCause;
NSString *_specificProblem;
NSString *_proposedRepairAction;
NSString *_additionalText;
EcAlarmTrend _trendIndicator;
int _notificationID;
void *_extra;
BOOL _frozen;
}
/** Creates and returns an autoreleased instance by calling the
* designated initialiser with all the supplied arguments.
*/
+ (EcAlarm*) alarmForManagedObject: (NSString*)managedObject
at: (NSDate*)eventDate
withEventType: (EcAlarmEventType)eventType
probableCause: (EcAlarmProbableCause)probableCause
specificProblem: (NSString*)specificProblem
perceivedSeverity: (EcAlarmSeverity)perceivedSeverity
proposedRepairAction: (NSString*)proposedRepairAction
additionalText: (NSString*)additionalText;
/** This method provides a mapping from the probable cause of an event to
* the event type.<br />
* The method is called during initialisation of an alarm instance (except
* where the probable cause is EcAlarmProbableCauseUnknown) to check that the
* supplied arguments are consistent. If a subclass extends the possible
* probable cause values, it must also override this method to handle those
* new values by returning a known event type.
*/
+ (EcAlarmEventType) eventTypeFromProbableCause: (EcAlarmProbableCause)value;
/** Provides a human readable string representation of an event type.<br />
* This method is called during initialisation of an alarm instance to check
* that the supplied event type is legal.<br />
* Returns nil if the value is unknown.<br />
*/
+ (NSString*) stringFromEventType: (EcAlarmEventType)value;
/** Provides a human readable string representation of a probable cause.<br />
* Returns nil if the value is unknown.
*/
+ (NSString*) stringFromProbableCause: (EcAlarmProbableCause)value;
/** Provides a human readable string representation of a severity.<br />
* Returns nil if the value is unknown.
*/
+ (NSString*) stringFromSeverity: (EcAlarmSeverity)value;
/** Provides a human readable string representation of a trend.<br />
* Returns nil if the value is unknown.
*/
+ (NSString*) stringFromTrend: (EcAlarmTrend)value;
/** This is the supplementary text (optional) which may be provided with
* and alarm an an aid to the human operator for the monitoring system.
*/
- (NSString*) additionalText;
/** Compares the other object with the receiver for sorting/ordering.<br />
* If both objects have a notificationID set then the result of the
* numeric comparison of those IDs is used.<br />
* Otherwise the result of the comparison orders the objects by
* managedObject, eventType, probableCause, and specificProblem.
*/
- (NSComparisonResult) compare: (EcAlarm*)other;
/** Returns an autoreleased copy of the receiver with the same notificationID
* but with a perceivedSeverity set to be EcAlarmSeverityCleared ... this may
* be used to clear the alarm represented by the receiver.
*/
- (EcAlarm*) clear;
/** EcAlarm objects may be copied. This method is provided to implement
* the NSCopying protocol.<br />
* A copy of an object does <em>not</em> copy any value provided by the
* -setExtra: method.
*/
- (id) copyWithZone: (NSZone*)aZone;
/** Deallocates the receiver.
*/
- (void) dealloc;
/** EcAlarm objects may be passed over the distributed objects system or
* archived. This method is provided to implement the NSCoding protocol.<br />
* An encoded copy of an object does <em>not</em> copy any value provided
* by the -setExtra: method.
*/
- (void) encodeWithCoder: (NSCoder*)aCoder;
/** Returns the timestamp of the event which generated the alarm.
*/
- (NSDate*) eventDate;
/** This method returns the type for event which generated the alarm.
*/
- (EcAlarmEventType) eventType;
/** Returns any extra information stored by the -setExtra: method.<br />
*/
- (void*) extra;
/** If this alarm is known to be represent an event updating the status of
* an existing alarm, this method returns the date of the initial event.
* otherwise it returns nil.
*/
- (NSDate*) firstEventDate;
/** Freeze the state of the alarm ... no more calls to setters are permitted.
*/
- (void) freeze;
/** Returns the hash of the receiver ... which is also the hash of its
* managedObject.
*/
- (NSUInteger) hash;
/** <init/>
* Initialises the receiver as an alarm for a particular event.<br />
* The eventDate argument may be nil if the alarm should use the current
* timestamp.<br />
* The managedObject, eventType, probableCause, and specificProblem
* arguments uniquely identify the issue for which an alarm is being
* produced.<br />
* The perceivedSeverity indicates the importance of the problem, with a
* value of EcAlarmSeverityCleared indicating that the problem is over.<br />
* A proposedRepairAction is mandatory (unless the severity is cleared)
* to provide the human operator with some sort of hint about how they
* should resolve the issue.
*/
- (id) initForManagedObject: (NSString*)managedObject
at: (NSDate*)eventDate
withEventType: (EcAlarmEventType)eventType
probableCause: (EcAlarmProbableCause)probableCause
specificProblem: (NSString*)specificProblem
perceivedSeverity: (EcAlarmSeverity)perceivedSeverity
proposedRepairAction: (NSString*)proposedRepairAction
additionalText: (NSString*)additionalText;
/** EcAlarm objects may be passed over the distributed objects system or
* archived. This method is provided to implement the NSCoding protocol.
*/
- (id) initWithCoder: (NSCoder*)aCoder;
/** Returns a flag indicating whether the receiver is equal to the other
* object. To be considered equal either:<br />
* The two objects must have equal managedObject values and equal (non-zero)
* notificationID values or<br />
* the two objects must have equal managedObject values,
* equal eventType values, equal probableCause values,
* and equal specificProblem values.<br />
* NB. you must not set two alarm instances to have the same notificationID
* values if they are not considered equal using the other criteria.
*/
- (BOOL) isEqual: (id)other;
/** Returns the managedObject value set when the receiver was initialised.
*/
- (NSString*) managedObject;
/** Returns the component of the managed object (if any).
*/
- (NSString*) moComponent;
/** Returns the host of the managed object.
*/
- (NSString*) moHost;
/** Returns the instance of the managed object (if any).
*/
- (NSString*) moInstance;
/** Returns the process name of the managed object.
*/
- (NSString*) moProcess;
/** Returns zero or the notificationID value most recently set by
* the -setNotificationID: method.
*/
- (int) notificationID;
/** Returns the perceivedSeverity set when the receiver was initialised.
*/
- (EcAlarmSeverity) perceivedSeverity;
/** Returns the proposedRepairAction set when the receiver was initialised.
*/
- (NSString*) proposedRepairAction;
/** Returns the probableCause set when the receiver was initialised.
*/
- (EcAlarmProbableCause) probableCause;
/** Sets extra data for the current instance.<br />
* Extra data is not copied, archived, or transferred over DO, it is available
* only in the exact instance of the class in which it was set.
*/
- (void) setExtra: (void*)extra;
/** Sets the first event date for the receiver.<br />
* You should not normally call this as it is reserved for use by code
* which has matched the receiver to an existing alarm.
*/
- (void) setFirstEventDate: (NSDate*)firstEventDate;
/** Sets the notification ID for the receiver.<br />
* You should not normally call this as it is reserved for use by code
* which has matched the receiver to an existing alarm.<br />
* In particular, two instances should not be set to have the same non-zero
* notificationID unless they are equal according to other criteria of
* equality (ie have the same managedObject, eventType, probableCause,
* and specificProblem values).
*/
- (void) setNotificationID: (int)notificationID;
/** Sets the trend indicator for the receiver.<br />
* You should not normally call this as it is reserved for use by code
* which has matched the receiver to an existing alarm.
*/
- (void) setTrendIndicator: (EcAlarmTrend)trendIndicator;
/** Returns the specificProblem set when the receiver was initialised.
*/
- (NSString*) specificProblem;
/** Returns the value most recently set by the -setTrendIndicator: method
* (or EcAlarmTrendNone if that method has not been called).<br />
* This tells you whether the receiver represents an increase in severity
* of an issue, or a decrease in severity (or no change).
*/
- (EcAlarmTrend) trendIndicator;
@end
@interface EcAlarm (Convenience)
/** Generates an alarm to clears an alarm previously generated.<br />
* The componentName may be nil for a process-wide alarm.<br />
* The probableCause must NOT be unknown ... it is used to infer
* the event type.<br />
* The specificProblem must be identical to the value supplied in the
* original alarm that this is intended to clear.
*/
+ (EcAlarm*) clear: (NSString*)componentName
cause: (EcAlarmProbableCause)probableCause
problem: (NSString*)specificProblem;
/** Generates a new alarm event wuth minimal parameters.<br />
* The componentName may be nil for a process-wide alarm.<br />
* The probablCause must NOT be unknown ... it is used to infer
* the event type.<br />
* The specificProblem is used to identify the event for which the
* alarm is raised.<br />
* The perceivedSeverity must be one of EcAlarmSeverityWarning,
* EcAlarmSeverityMinor, EcAlarmSeverityMajor or EcAlarmSeverityCritical.<br />
* The proposedRepairAction must contain information sufficient
* for any person receiving notification of the alarm to be able
* to deal with it. The action is a format string, optionally
* followed by any number of arguments to be incorporated into
* the repair action. NB. The resulting proposed repair action
* must be no more than 255 bytes in length when converted to
* UTF-8 data.
*/
+ (EcAlarm*) raise: (NSString*)componentName
cause: (EcAlarmProbableCause)probableCause
problem: (NSString*)specificProblem
severity: (EcAlarmSeverity)perceivedSeverity
action: (NSString*)proposedRepairAction, ...;
@end
#endif

879
EcAlarm.m Normal file
View file

@ -0,0 +1,879 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSCoder.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSException.h>
#import <Foundation/NSHost.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import "EcProcess.h"
#import "EcAlarm.h"
@class NSPortCoder;
NSString *
EcMakeManagedObject(NSString *host, NSString *process, NSString *component)
{
NSString *instance = @"";
NSRange r;
if (nil == host)
{
host = [[NSHost currentHost] name];
}
else
{
// No underscores permitted.
host = [host stringByReplacingString: @"_" withString: @"-"];
}
if (nil == process)
{
process = [EcProc cmdName];
}
/* Extract the instance number from the process name (the instance would
* be a string of digits after a hyphen) if it's there.
*/
r = [process rangeOfString: @"-"
options: NSCaseInsensitiveSearch|NSBackwardsSearch];
if (r.length > 0)
{
NSString *s = [process substringFromIndex: NSMaxRange(r)];
unsigned l = [s length];
if (l > 0)
{
while (l-- > 0)
{
unichar c = [s characterAtIndex: l];
if (c < '0' || c > '9')
{
break;
}
}
if (0 == l)
{
instance = s;
process = [process substringToIndex: r.location];
}
}
}
// No underscores permitted.
process = [process stringByReplacingString: @"_" withString: @"-"];
if (nil == component)
{
component = @"";
}
else
{
// No underscores permitted.
component = [component stringByReplacingString: @"_" withString: @"-"];
}
return [NSString stringWithFormat: @"%@_%@_%@_%@",
host, process, instance, component];
}
@implementation EcAlarm
+ (EcAlarm*) alarmForManagedObject: (NSString*)managedObject
at: (NSDate*)eventDate
withEventType: (EcAlarmEventType)eventType
probableCause: (EcAlarmProbableCause)probableCause
specificProblem: (NSString*)specificProblem
perceivedSeverity: (EcAlarmSeverity)perceivedSeverity
proposedRepairAction: (NSString*)proposedRepairAction
additionalText: (NSString*)additionalText
{
EcAlarm *a = [self alloc];
a = [a initForManagedObject: managedObject
at: eventDate
withEventType: eventType
probableCause: probableCause
specificProblem: specificProblem
perceivedSeverity: perceivedSeverity
proposedRepairAction: proposedRepairAction
additionalText: additionalText];
return [a autorelease];
}
+ (EcAlarmEventType) eventTypeFromProbableCause: (EcAlarmProbableCause)value
{
switch (value)
{
case EcAlarmProbableCauseUnknown: // Don't call with this value
return EcAlarmEventTypeUnknown;
case EcAlarmCallEstablishmentError:
case EcAlarmCommunicationsProtocolError:
case EcAlarmCommunicationsSubsystemFailure:
case EcAlarmDegradedSignal:
case EcAlarmDTE_DCEInterfaceError:
case EcAlarmFramingError:
case EcAlarmLANError:
case EcAlarmLocalNodeTransmissionError:
case EcAlarmLossOfFrame:
case EcAlarmLossOfSignal:
case EcAlarmRemoteNodeTransmissionError:
return EcAlarmEventTypeCommunications;
case EcAlarmEnclosureDoorOpen:
case EcAlarmExcessiveVibration:
case EcAlarmFireDetected:
case EcAlarmFloodDetected:
case EcAlarmHeatingOrVentilationOrCoolingSystemProblem:
case EcAlarmHumidityUnacceptable:
case EcAlarmLeakDetected:
case EcAlarmMaterialSupplyExhausted:
case EcAlarmPressureUnacceptable:
case EcAlarmPumpFailure:
case EcAlarmTemperatureUnacceptable:
case EcAlarmToxicLeakDetected:
return EcAlarmEventTypeEnvironmental;
case EcAlarmAdapterError:
case EcAlarmDataSetOrModemError:
case EcAlarmEquipmentMalfunction:
case EcAlarmInputDeviceError:
case EcAlarmInputOutputDeviceError:
case EcAlarmMultiplexerProblem:
case EcAlarmOuputDeviceError:
case EcAlarmPowerProblem:
case EcAlarmProcessorProblem:
case EcAlarmReceiveFailure:
case EcAlarmReceiverFailure:
case EcAlarmTimingProblem:
case EcAlarmTransmitFailure:
case EcAlarmTransmitterFailure:
return EcAlarmEventTypeEquipment;
case EcAlarmApplicationSubsystemFailure:
case EcAlarmConfigurationOrCustomizationError:
case EcAlarmCorruptData:
case EcAlarmCpuCyclesLimitExceeded:
case EcAlarmFileError:
case EcAlarmOutOfMemory:
case EcAlarmSoftwareProgramAbnormallyTerminated:
case EcAlarmSoftwareProgramError:
case EcAlarmStorageCapacityProblem:
case EcAlarmUnderlyingResourceUnavailable:
case EcAlarmVersionMismatch:
return EcAlarmEventTypeProcessingError;
case EcAlarmBandwidthReduced:
case EcAlarmCongestion:
case EcAlarmPerformanceDegraded:
case EcAlarmQueueSizeExceeded:
case EcAlarmResourceAtOrNearingCapacity:
case EcAlarmResponseTimeExcessive:
case EcAlarmRetransmissionRateExcessive:
case EcAlarmThresholdCrossed:
return EcAlarmEventTypeQualityOfService;
}
return EcAlarmEventTypeUnknown;
}
+ (NSString*) stringFromEventType: (EcAlarmEventType)value
{
switch (value)
{
case EcAlarmEventTypeUnknown: // Not legal
return nil;
case EcAlarmEventTypeCommunications:
return @"EcAlarmEventTypeCommunications";
case EcAlarmEventTypeEnvironmental:
return @"EcAlarmEventTypeEnvironmental";
case EcAlarmEventTypeEquipment:
return @"EcAlarmEventTypeEquipment";
case EcAlarmEventTypeProcessingError:
return @"EcAlarmEventTypeProcessingError";
case EcAlarmEventTypeQualityOfService:
return @"EcAlarmEventTypeQualityOfService";
}
return nil;
}
+ (NSString*) stringFromProbableCause: (EcAlarmProbableCause)value
{
switch (value)
{
case EcAlarmProbableCauseUnknown:
return @"EcAlarmProbableCauseUnknown";
case EcAlarmAdapterError:
return @"adapterError";
case EcAlarmApplicationSubsystemFailure:
return @"applicationSubsystemFailure";
case EcAlarmBandwidthReduced:
return @"bandwidthReduced";
case EcAlarmCallEstablishmentError:
return @"callEstablishmentError";
case EcAlarmCommunicationsProtocolError:
return @"communicationsProtocolError";
case EcAlarmCommunicationsSubsystemFailure:
return @"communicationsSubsystemFailure";
case EcAlarmConfigurationOrCustomizationError:
return @"configurationOrCustomizationError";
case EcAlarmCongestion:
return @"congestion";
case EcAlarmCorruptData:
return @"corruptData";
case EcAlarmCpuCyclesLimitExceeded:
return @"cpuCyclesLimitExceeded";
case EcAlarmDataSetOrModemError:
return @"dataSetOrModemError";
case EcAlarmDegradedSignal:
return @"degradedSignal";
case EcAlarmDTE_DCEInterfaceError:
return @"dTE-DCEInterfaceError";
case EcAlarmEnclosureDoorOpen:
return @"enclosureDoorOpen";
case EcAlarmEquipmentMalfunction:
return @"equipmentMalfunction";
case EcAlarmExcessiveVibration:
return @"excessiveVibration";
case EcAlarmFileError:
return @"fileError";
case EcAlarmFireDetected:
return @"fireDetected";
case EcAlarmFloodDetected:
return @"floodDetected";
case EcAlarmFramingError:
return @"framingError";
case EcAlarmHeatingOrVentilationOrCoolingSystemProblem:
return @"heatingOrVentilationOrCoolingSystemProblem";
case EcAlarmHumidityUnacceptable:
return @"humidityUnacceptable";
case EcAlarmInputOutputDeviceError:
return @"inputOutputDeviceError";
case EcAlarmInputDeviceError:
return @"inputDeviceError";
case EcAlarmLANError:
return @"lANError";
case EcAlarmLeakDetected:
return @"leakDetected";
case EcAlarmLocalNodeTransmissionError:
return @"localNodeTransmissionError";
case EcAlarmLossOfFrame:
return @"lossOfFrame";
case EcAlarmLossOfSignal:
return @"lossOfSignal";
case EcAlarmMaterialSupplyExhausted:
return @"materialSupplyExhausted";
case EcAlarmMultiplexerProblem:
return @"multiplexerProblem";
case EcAlarmOutOfMemory:
return @"outOfMemory";
case EcAlarmOuputDeviceError:
return @"ouputDeviceError";
case EcAlarmPerformanceDegraded:
return @"performanceDegraded";
case EcAlarmPowerProblem:
return @"powerProblem";
case EcAlarmPressureUnacceptable:
return @"pressureUnacceptable";
case EcAlarmProcessorProblem:
return @"processorProblem";
case EcAlarmPumpFailure:
return @"pumpFailure";
case EcAlarmQueueSizeExceeded:
return @"queueSizeExceeded";
case EcAlarmReceiveFailure:
return @"receiveFailure";
case EcAlarmReceiverFailure:
return @"receiverFailure";
case EcAlarmRemoteNodeTransmissionError:
return @"remoteNodeTransmissionError";
case EcAlarmResourceAtOrNearingCapacity:
return @"resourceAtOrNearingCapacity";
case EcAlarmResponseTimeExcessive:
return @"responseTimeExcessive";
case EcAlarmRetransmissionRateExcessive:
return @"retransmissionRateExcessive";
case EcAlarmSoftwareProgramAbnormallyTerminated:
return @"softwareProgramAbnormallyTerminated";
case EcAlarmSoftwareProgramError:
return @"softwareProgramError";
case EcAlarmStorageCapacityProblem:
return @"storageCapacityProblem";
case EcAlarmTemperatureUnacceptable:
return @"temperatureUnacceptable";
case EcAlarmThresholdCrossed:
return @"thresholdCrossed";
case EcAlarmTimingProblem:
return @"timingProblem";
case EcAlarmToxicLeakDetected:
return @"toxicLeakDetected";
case EcAlarmTransmitFailure:
return @"transmitFailure";
case EcAlarmTransmitterFailure:
return @"transmitterFailure";
case EcAlarmUnderlyingResourceUnavailable:
return @"underlyingResourceUnavailable";
case EcAlarmVersionMismatch:
return @"versionMismatch";
}
return nil;
}
+ (NSString*) stringFromSeverity: (EcAlarmSeverity)value
{
switch (value)
{
case EcAlarmSeverityIndeterminate: return @"EcAlarmSeverityIndeterminate";
case EcAlarmSeverityCritical: return @"EcAlarmSeverityCritical";
case EcAlarmSeverityMajor: return @"EcAlarmSeverityMajor";
case EcAlarmSeverityMinor: return @"EcAlarmSeverityMinor";
case EcAlarmSeverityWarning: return @"EcAlarmSeverityWarning";
case EcAlarmSeverityCleared: return @"EcAlarmSeverityCleared";
}
return nil;
}
+ (NSString*) stringFromTrend: (EcAlarmTrend)value
{
switch (value)
{
case EcAlarmTrendNone: return @"EcAlarmTrendNone";
case EcAlarmTrendUp: return @"EcAlarmTrendUp";
case EcAlarmTrendDown: return @"EcAlarmTrendDown";
}
return nil;
}
- (NSString*) additionalText
{
return _additionalText;
}
- (Class) classForCoder
{
return [EcAlarm class];
}
- (EcAlarm*) clear
{
EcAlarm *c = [self copy];
c->_perceivedSeverity = EcAlarmSeverityCleared;
c->_notificationID = _notificationID;
return [c autorelease];
}
- (NSComparisonResult) compare: (EcAlarm*)other
{
int oNotificationID;
NSString *sStr;
NSString *oStr;
if (NO == [other isKindOfClass: [EcAlarm class]])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] argument is not an EcAlarm",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
oNotificationID = [other notificationID];
if (_notificationID > 0 && oNotificationID > 0)
{
if (_notificationID < oNotificationID)
{
return NSOrderedAscending;
}
if (_notificationID > oNotificationID)
{
return NSOrderedDescending;
}
return NSOrderedSame;
}
sStr = [NSString stringWithFormat: @"%@ %d %d %@",
[self managedObject],
[self eventType],
[self probableCause],
[self specificProblem]];
oStr = [NSString stringWithFormat: @"%@ %d %d %@",
[other managedObject],
[other eventType],
[other probableCause],
[other specificProblem]];
return [sStr compare: oStr];
}
- (id) copyWithZone: (NSZone*)aZone
{
EcAlarm *c = [[self class] allocWithZone: aZone];
c = [c initForManagedObject: _managedObject
at: _eventDate
withEventType: _eventType
probableCause: _probableCause
specificProblem: _specificProblem
perceivedSeverity: _perceivedSeverity
proposedRepairAction: _proposedRepairAction
additionalText: _additionalText];
if (nil != c)
{
c->_firstEventDate = [_firstEventDate copyWithZone: aZone];
c->_notificationID = _notificationID;
c->_trendIndicator = _trendIndicator;
}
return c;
}
- (void) dealloc
{
DESTROY(_managedObject);
DESTROY(_eventDate);
DESTROY(_firstEventDate);
DESTROY(_specificProblem);
DESTROY(_proposedRepairAction);
DESTROY(_additionalText);
[super dealloc];
}
- (NSString*) description
{
Class c = [self class];
return [NSString stringWithFormat:
@"Alarm %-8d %@ %@ %@ %@ %@ at %@(%@) %@ %@ %@",
_notificationID,
_managedObject,
[c stringFromEventType: _eventType],
[c stringFromProbableCause: _probableCause],
[c stringFromSeverity: _perceivedSeverity],
[c stringFromTrend: _trendIndicator],
_eventDate,
_firstEventDate,
_specificProblem,
_proposedRepairAction,
_additionalText];
}
- (void) encodeWithCoder: (NSCoder*)aCoder
{
[aCoder encodeValuesOfObjCTypes: "iiiii@@@@@@",
&_eventType,
&_notificationID,
&_perceivedSeverity,
&_probableCause,
&_trendIndicator,
&_managedObject,
&_eventDate,
&_firstEventDate,
&_specificProblem,
&_proposedRepairAction,
&_additionalText];
}
- (NSDate*) eventDate
{
return _eventDate;
}
- (EcAlarmEventType) eventType
{
return _eventType;
}
- (void *) extra
{
return _extra;
}
- (NSDate*) firstEventDate
{
return _firstEventDate;
}
- (void) freeze
{
/* NB this value must NOT be archived ... because when we restore from
* archive we will want to store a pointer in _extra.
*/
_frozen = YES;
}
- (NSUInteger) hash
{
return [_managedObject hash];
}
- (id) init
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"Called -init on EcAlarm"];
return nil;
}
- (id) initForManagedObject: (NSString*)managedObject
at: (NSDate*)eventDate
withEventType: (EcAlarmEventType)eventType
probableCause: (EcAlarmProbableCause)probableCause
specificProblem: (NSString*)specificProblem
perceivedSeverity: (EcAlarmSeverity)perceivedSeverity
proposedRepairAction: (NSString*)proposedRepairAction
additionalText: (NSString*)additionalText
{
Class c = [self class];
if (nil == managedObject)
{
managedObject = EcMakeManagedObject(nil, nil, nil);
}
if (4 != [[managedObject componentsSeparatedByString: @"_"] count])
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"bad managed object (%@)", managedObject];
}
if (127 < strlen([managedObject UTF8String]))
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"managed object too long (over 127 bytes): (%@)",
managedObject];
}
if (0 == [specificProblem length])
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"empty specific problem"];
}
if (255 < strlen([specificProblem UTF8String]))
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"specific problem too long (over 255 bytes): (%@)",
specificProblem];
}
if (0 == [proposedRepairAction length])
{
if (EcAlarmSeverityCleared == perceivedSeverity)
{
/* We don't need a proposed repair action for a clear.
*/
proposedRepairAction = @"";
}
else
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"empty proposed repair action"];
}
}
if (255 < strlen([proposedRepairAction UTF8String]))
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"proposed repair action too long (over 255 bytes): (%@)",
proposedRepairAction];
}
if (nil == eventDate)
{
eventDate = [NSDate date];
}
if (nil == additionalText)
{
additionalText = @"";
}
if (255 < strlen([additionalText UTF8String]))
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"additional text too long (over 255 bytes): (%@)",
additionalText];
}
if (nil == [c stringFromEventType: eventType])
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"bad event type (%d)", eventType];
}
if (nil == [c stringFromSeverity: perceivedSeverity])
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"bad severity (%d)", perceivedSeverity];
}
if (nil == [c stringFromProbableCause: probableCause])
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"bad severity (%d)", probableCause];
}
/* Anything other than an unknown probable cause must correspond to a
* known event type, but an unknown probable cause may match any event.
*/
if (EcAlarmProbableCauseUnknown != probableCause)
{
_eventType = [c eventTypeFromProbableCause: probableCause];
if (_eventType != eventType)
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"missmatch of event type and probable cause"];
}
}
if (nil != (self = [super init]))
{
_notificationID = 0;
_eventType = eventType;
_perceivedSeverity = perceivedSeverity;
_probableCause = probableCause;
_trendIndicator = 0;
ASSIGNCOPY(_managedObject, managedObject);
ASSIGNCOPY(_eventDate, eventDate);
ASSIGNCOPY(_specificProblem, specificProblem);
ASSIGNCOPY(_proposedRepairAction, proposedRepairAction);
ASSIGNCOPY(_additionalText, additionalText);
}
return self;
}
- (id) initWithCoder: (NSCoder*)aCoder
{
[aCoder decodeValuesOfObjCTypes: "iiiii@@@@@@",
&_eventType,
&_notificationID,
&_perceivedSeverity,
&_probableCause,
&_trendIndicator,
&_managedObject,
&_eventDate,
&_firstEventDate,
&_specificProblem,
&_proposedRepairAction,
&_additionalText];
return self;
}
/* Return YES if the two instances are equal according to the correlation
* rules, NO otherwise.
*/
- (BOOL) isEqual: (id)other
{
if (other == self)
{
return YES;
}
if (NO == [other isKindOfClass: [EcAlarm class]])
{
return NO;
}
if (NO == [[other managedObject] isEqual: _managedObject])
{
return NO;
}
if (_notificationID > 0 && [other notificationID] == _notificationID)
{
/* We have a notificationID set ... if both have the same notification
* ID then they are the same.
*/
return YES;
}
/* The correlation rule normally is:
* Same managed object
* same event type
* same probable cause
* same specific problem
*/
if ([other eventType] == _eventType
&& [other probableCause] == _probableCause
&& [[other specificProblem] isEqual: _specificProblem])
{
return YES;
}
return NO;
}
- (NSString*) managedObject
{
return _managedObject;
}
- (NSString*) moComponent
{
NSArray *s = [_managedObject componentsSeparatedByString: @"_"];
return [s objectAtIndex: 3];
}
- (NSString*) moHost
{
NSArray *s = [_managedObject componentsSeparatedByString: @"_"];
return [s objectAtIndex: 0];
}
- (NSString*) moInstance
{
NSArray *s = [_managedObject componentsSeparatedByString: @"_"];
return [s objectAtIndex: 2];
}
- (NSString*) moProcess
{
NSArray *s = [_managedObject componentsSeparatedByString: @"_"];
return [s objectAtIndex: 1];
}
- (int) notificationID
{
return _notificationID;
}
- (EcAlarmSeverity) perceivedSeverity
{
return _perceivedSeverity;
}
- (NSString*) proposedRepairAction
{
return _proposedRepairAction;
}
- (EcAlarmProbableCause) probableCause
{
return _probableCause;
}
- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
{
return self;
}
- (void) setExtra: (void*)extra
{
if (YES == _frozen)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called for frozen instance",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
_extra = extra;
}
- (void) setFirstEventDate: (NSDate*)firstEventDate
{
if (nil != firstEventDate
&& NO == [firstEventDate isKindOfClass: [NSDate class]])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] bad argument '%@'",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
firstEventDate];
}
if (YES == _frozen)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called for frozen instance",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
ASSIGNCOPY(_firstEventDate, firstEventDate);
}
- (void) setNotificationID: (int)notificationID
{
if (YES == _frozen)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called for frozen instance",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
_notificationID = notificationID;
}
- (void) setTrendIndicator: (EcAlarmTrend)trendIndicator
{
if (nil == [[self class] stringFromTrend: trendIndicator])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] bad argument '%d'",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
trendIndicator];
}
if (YES == _frozen)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called for frozen instance",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
_trendIndicator = trendIndicator;
}
- (NSString*) specificProblem
{
return _specificProblem;
}
- (EcAlarmTrend) trendIndicator
{
return _trendIndicator;
}
@end
@implementation EcAlarm (Convenience)
+ (EcAlarm*) clear: (NSString*)componentName
cause: (EcAlarmProbableCause)probableCause
problem: (NSString*)specificProblem
{
NSString *managedObject;
EcAlarmEventType eventType;
managedObject = EcMakeManagedObject(nil, nil, componentName);
eventType = [self eventTypeFromProbableCause: probableCause];
return [self alarmForManagedObject: managedObject
at: nil
withEventType: eventType
probableCause: probableCause
specificProblem: specificProblem
perceivedSeverity: EcAlarmSeverityCleared
proposedRepairAction: nil
additionalText: nil];
}
+ (EcAlarm*) raise: (NSString*)componentName
cause: (EcAlarmProbableCause)probableCause
problem: (NSString*)specificProblem
severity: (EcAlarmSeverity)perceivedSeverity
action: (NSString*)proposedRepairAction,...
{
NSString *managedObject;
EcAlarmEventType eventType;
NSString *mesg;
va_list ap;
NSAssert(EcAlarmSeverityCleared != perceivedSeverity,
NSInvalidArgumentException);
managedObject = EcMakeManagedObject(nil, nil, componentName);
eventType = [self eventTypeFromProbableCause: probableCause];
va_start (ap, proposedRepairAction);
mesg = [NSString stringWithFormat: proposedRepairAction arguments: ap];
va_end (ap);
return [self alarmForManagedObject: managedObject
at: nil
withEventType: eventType
probableCause: probableCause
specificProblem: specificProblem
perceivedSeverity: perceivedSeverity
proposedRepairAction: mesg
additionalText: nil];
}
@end

179
EcAlarmDestination.h Normal file
View file

@ -0,0 +1,179 @@
#import <Foundation/NSObject.h>
@class EcAlarm;
@class NSRecursiveLock;
@class NSMutableArray;
@class NSMutableSet;
@class NSString;
@class NSTimer;
/** The EcAlarmDestination protocol describes the interface which must be
* provided by an object which handles alarms.<br />
* <p>The sender expects to be able to 'fire and forget', sending the
* messages in this protocol without having to wait for a response or deal
* with any error conditions, so the destination must <em>not</em> block
* for a long time or raise an exception.
* </p>
*/
@protocol EcAlarmDestination
/** Passes an alarm to the destination.
*/
- (oneway void) alarm: (in bycopy EcAlarm*)event;
/** Inform the destination of the existence of a managed object.<br />
* This is an indicator of a 'cold start' of that object ... meaning that the
* object has just started up afresh, and all outstanding alarms for the object
* are to be cleared.
*/
- (oneway void) domanage: (in bycopy NSString*)managedObject;
/** Inform the destination of the removal of a managed object.<br />
* This is an indicator of a graceful shutdown of that object ... meaning that
* the object has been stopped intentionally and all outstanding alarms for the
* object are to be cleared.
*/
- (oneway void) unmanage: (in bycopy NSString*)managedObject;
@end
/**
* <p>The EcAlarmDestination class provides an object to act as an alarm
* destination which is capable of buffering, coalescing, and forwarding
* alarms to another destination (usually in a separate process).
* </p>
* <p>The buffering and coalescing mechanism is important to prevent floods
* of alarms being sent over network connections.
* </p>
* <p>An EcAlarmDestination instance can also be set to forward alarm
* information to a number of other instances as backups for the main
* destination.
* </p>
*/
@interface EcAlarmDestination : NSObject <EcAlarmDestination>
{
NSRecursiveLock *_alarmLock;
NSMutableArray *_alarmQueue;
NSMutableSet *_alarmsActive;
NSMutableSet *_managedObjects;
NSTimer *_timer;
BOOL _isRunning;
BOOL _shouldStop;
BOOL _coalesceOff;
BOOL _inTimeout;
NSString *_host;
NSString *_name;
id<EcAlarmDestination> _destination;
NSArray *_backups;
}
/** Passes an alarm to the destination by adding it to a queue of alarm
* events which will be processed in the receivers running thread.
*/
- (oneway void) alarm: (in bycopy EcAlarm*)event;
/** Returns an array containing all the currently active alarms.
*/
- (NSArray*) alarms;
/** Returns an array of backup destinations (if set).<br />
* See -setBackups: for more information.
*/
- (NSArray*) backups;
/** Inform the destination of the existence of a managed object.<br />
* This is an indicator of a 'cold start' of that object ... meaning that the
* object has just started up afresh, and all outstanding alarms for the object
* are to be cleared.<br />
* The managedObject information is added to a queue which is processed by
* the receiver's running thread in order to pass the information on to the
* destination.
*/
- (oneway void) domanage: (in bycopy NSString*)managedObject;
/* <init />
* Initialises the receiver and starts up a secondary thread to manage
* alarms for it.
*/
- (id) init;
/** Sets the name/host of the object in a remote process to which
* alarms should be forwarded. If this information is set then the
* forwarder will attempt to maintain a Distributed Objects connection
* to the remote object.<br />
* The host may be nil for a local connection (current machine and account),
* or an empty string for a network connection to the local machine, or a
* host name for a network connection to another machine, or an asterisk
* for a network connection to any available machine.
*/
- (id) initWithHost: (NSString*)host name: (NSString*)name;
/** Returns a flag indicating whether the receiver is actually operating.
*/
- (BOOL) isRunning;
/** This method is called from -init in a secondary thread to start handling
* of alarms by the receiver. Do not call it yourself.
*/
- (void) run;
/** Sets an array containing EcAlarmDestination objects as backups to receive
* copies of the alarm and domanage/unmanage information sent to this
* destination.<br />
* You may set nil or an empty array to turn off backups, and may use the
* -backups method to get the currently set values.<br />
* Do not set up loops causing a destination to be its own backup either
* directly or indirectly, as this will cause alarms to be forwarded endlessly.
*/
- (void) setBackups: (NSArray*)backups;
/** Sets coalescing behavior for the queue of alarms and managed object
* changes. The default behavior is for coalescing to be turned on
* (so new values replace those in the queue), but setting this to NO
* will cause all events to be passed on (apart from repeated alarms at
* the same perceivedSeverity level, which are never passed one).
*/
- (BOOL) setCoalesce: (BOOL)coalesce;
/** Sets the destination to which alarms should be forwarded.<br />
* If nil this turns off forwarding until it is re-set to a non-nil
* destination.<br />
* The destination object is retained by the receiver.<br />
* Returns the previously set destination.
*/
- (id<EcAlarmDestination>) setDestination: (id<EcAlarmDestination>)destination;
/** Requests that the receiver's running thread should shut down. This method
* waits for a short while for the thread to shut down, but the process of
* shutting down is not guaranteed to have completed by the time the method
* returns.
*/
- (void) shutdown;
/** Inform the destination of the removal of a managed object.<br />
* This is an indicator of a graceful shutdown of that object ... meaning that
* the object has been stopped intentionally and all outstanding alarms for the
* object are to be cleared.<br />
* The managedObject information is added to a queue which is processed by
* the receiver's running thread in order to pass the information on to the
* destination.
*/
- (oneway void) unmanage: (in bycopy NSString*)managedObject;
@end
/** Methods called internally to forward events to the remote target of
* the receiver. These are provided for subclasses to oveerride.
*/
@interface EcAlarmDestination (Forwarding)
/** Forward an alarm event. */
- (void) alarmFwd: (EcAlarm*)event;
/** Forward a domanage event. */
- (void) domanageFwd: (NSString*)managedObject;
/** Forward an unmanage event. */
- (void) unmanageFwd: (NSString*)managedObject;
@end

531
EcAlarmDestination.m Normal file
View file

@ -0,0 +1,531 @@
#import <Foundation/Foundation.h>
#import "EcProcess.h"
#import "EcAlarm.h"
#import "EcAlarmDestination.h"
@interface EcAlarmDestination (Private)
/* Loss of connection ... clear destination.
*/
- (void) _connectionBecameInvalid: (id)connection;
/* Regular timer to handle alarms.
*/
- (void) _timeout: (NSTimer*)t;
@end
@implementation EcAlarmDestination
- (oneway void) alarm: (in bycopy EcAlarm*)event
{
if (NO == [event isKindOfClass: [EcAlarm class]])
{
NSLog(@"[%@-%@] invalid argument (%@)",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
event);
}
else
{
[_alarmLock lock];
if (YES == _coalesceOff)
{
[_alarmQueue addObject: event];
}
else
{
[event retain];
[_alarmQueue removeObject: event];
[_alarmQueue addObject: event];
[event release];
}
[_alarmLock unlock];
}
}
- (NSArray*) alarms
{
NSArray *a;
[_alarmLock lock];
a = [_alarmsActive allObjects];
[_alarmLock unlock];
return a;
}
- (NSArray*) backups
{
NSArray *a;
[_alarmLock lock];
a = [_backups retain];
[_alarmLock unlock];
return [a autorelease];
}
- (void) dealloc
{
[self shutdown];
[_backups release];
[(id)_destination release];
_destination = nil;
[_alarmQueue release];
_alarmQueue = nil;
[_alarmsActive release];
_alarmsActive = nil;
[_managedObjects release];
_managedObjects = nil;
[_alarmLock release];
_alarmLock = nil;
[super dealloc];
}
- (id) init
{
if (nil != (self = [super init]))
{
NSDate *begin;
_alarmLock = [NSRecursiveLock new];
_alarmQueue = [NSMutableArray new];
_alarmsActive = [NSMutableSet new];
_managedObjects = [NSMutableSet new];
[NSThread detachNewThreadSelector: @selector(run)
toTarget: self
withObject: nil];
begin = [NSDate date];
while (NO == [self isRunning])
{
if ([begin timeIntervalSinceNow] < -5.0)
{
NSLog(@"alarm thread failed to start within 5 seconds");
[_alarmLock lock];
_shouldStop = YES; // If the thread starts ... shutdown
[_alarmLock unlock];
[self release];
return nil;
}
[NSThread sleepForTimeInterval: 0.1];
}
}
return self;
}
- (oneway void) domanage: (in bycopy NSString*)managedObject
{
if (NO == [managedObject isKindOfClass: [NSString class]]
|| 4 != [[managedObject componentsSeparatedByString: @"_"] count])
{
NSLog(@"[%@-%@] invalid argument (%@)",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
managedObject);
}
else
{
NSString *event;
event = [NSString stringWithFormat: @"domanage %@", managedObject];
[_alarmLock lock];
if (YES == _coalesceOff)
{
[_alarmQueue addObject: event];
}
else
{
[_alarmQueue removeObject: event];
[_alarmQueue addObject: event];
}
[_alarmLock unlock];
}
}
- (id) initWithHost: (NSString*)host name: (NSString*)name
{
/* We set the host namd name before calling -init, so that subclasses
* which override -init may make use of the values we have set.
*/
_host = [host copy];
_name = [name copy];
return [self init];
}
- (BOOL) isRunning
{
BOOL result;
[_alarmLock lock];
result = _isRunning;
[_alarmLock unlock];
return result;
}
- (void) run
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRunLoop *loop = [NSRunLoop currentRunLoop];
NSDate *future = [NSDate distantFuture];
_isRunning = YES;
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0
target: self
selector: @selector(_timeout:)
userInfo: nil
repeats: YES];
while (NO == _shouldStop)
{
[loop runMode: NSDefaultRunLoopMode beforeDate: future];
}
[pool release];
_isRunning = NO;
}
- (void) setBackups: (NSArray*)backups
{
NSUInteger i;
if (nil != backups && NO == [backups isKindOfClass: [NSArray class]])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] argument is not nil or an array",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
i = [backups count];
if (0 == i)
{
backups = nil;
}
else
{
while (i-- > 0)
{
if (NO == [[backups objectAtIndex: i]
isKindOfClass: [EcAlarmDestination class]])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] array contains bad destination",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
}
}
[_alarmLock lock];
ASSIGNCOPY(_backups, backups);
[_alarmLock unlock];
}
- (BOOL) setCoalesce: (BOOL)coalesce
{
BOOL old;
[_alarmLock lock];
old = (NO == _coalesceOff) ? YES : NO;
_coalesceOff = (NO == coalesce) ? YES : NO;
[_alarmLock unlock];
return old;
}
- (id<EcAlarmDestination>) setDestination: (id<EcAlarmDestination>)destination
{
id old;
if (nil != (id)destination && NO == [(id)destination
conformsToProtocol: @protocol(EcAlarmDestination)])
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] arg does not conform to EcAlarmDestination protocol",
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
}
[_alarmLock lock];
old = (id)_destination;
_destination = (id<EcAlarmDestination>)[(id)destination retain];
[_alarmLock unlock];
return (id<EcAlarmDestination>)[old autorelease];
}
- (void) shutdown
{
NSDate *begin;
[_alarmLock lock];
_shouldStop = YES;
[_host release];
_host = nil;
[_name release];
_name = nil;
[_alarmLock unlock];
begin = [NSDate date];
while (YES == [self isRunning])
{
if ([begin timeIntervalSinceNow] < -5.0)
{
NSLog(@"alarm thread failed to stop within 5 seconds");
return;
}
[NSThread sleepForTimeInterval: 0.1];
}
}
- (oneway void) unmanage: (in bycopy NSString*)managedObject
{
if (NO == [managedObject isKindOfClass: [NSString class]]
|| 4 != [[managedObject componentsSeparatedByString: @"_"] count])
{
NSLog(@"[%@-%@] invalid argument (%@)",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
managedObject);
}
else
{
NSString *event;
event = [NSString stringWithFormat: @"unmanage %@", managedObject];
[_alarmLock lock];
if (YES == _coalesceOff)
{
[_alarmQueue addObject: event];
}
else
{
[_alarmQueue removeObject: event];
[_alarmQueue addObject: event];
}
[_alarmLock unlock];
}
}
@end
@implementation EcAlarmDestination (Private)
- (void) _connectionBecameInvalid: (id)connection
{
[self setDestination: nil];
}
- (void) _timeout: (NSTimer*)t
{
[_alarmLock lock];
if (NO == _inTimeout && YES == _isRunning && NO == _shouldStop)
{
_inTimeout = YES;
NS_DURING
{
if ([_alarmQueue count] > 0)
{
if (nil == (id)_destination)
{
if (nil != _name)
{
id proxy;
if (nil == _host)
{
proxy = [NSConnection
rootProxyForConnectionWithRegisteredName: _name
host: _host
usingNameServer:
[NSMessagePortNameServer sharedInstance]];
}
else
{
proxy = [NSConnection
rootProxyForConnectionWithRegisteredName: _name
host: _host
usingNameServer:
[NSSocketPortNameServer sharedInstance]];
}
if (proxy != nil)
{
id connection = [proxy connectionForProxy];
[connection setDelegate: self];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_connectionBecameInvalid:)
name: NSConnectionDidDieNotification
object: connection];
[self setDestination: (id<EcAlarmDestination>)proxy];
}
}
}
// Do stuff here
while ([_alarmQueue count] > 0)
{
id o = [_alarmQueue objectAtIndex: 0];
if (YES == [o isKindOfClass: [EcAlarm class]])
{
EcAlarm *next = (EcAlarm*)o;
EcAlarm *prev = [_alarmsActive member: next];
NSString *m = [next managedObject];
if (nil == prev)
{
[next setFirstEventDate: [next eventDate]];
}
else
{
[next setFirstEventDate: [prev firstEventDate]];
}
if ([next perceivedSeverity] == EcAlarmSeverityCleared)
{
if (nil != prev)
{
/* send the clear for the entry and remove it
*/
[_alarmsActive removeObject: prev];
[self alarmFwd: next];
}
}
else
{
/* If the managed object is not registered,
* register before sending an alarm for it.
*/
if (nil == [_managedObjects member: m])
{
[_managedObjects addObject: m];
[self domanageFwd: m];
}
/* If the alarm is new or of changed severity,
* update the records and pass it on.
*/
if (nil == prev || [next perceivedSeverity]
!= [prev perceivedSeverity])
{
[_alarmsActive addObject: next];
[self alarmFwd: next];
}
}
}
else
{
NSString *s = [o description];
if (YES == [s hasPrefix: @"domanage "])
{
NSString *m = [s substringFromIndex: 9];
if (nil == [_managedObjects member: m])
{
[_managedObjects addObject: m];
[self domanageFwd: m];
}
}
else if (YES == [s hasPrefix: @"unmanage "])
{
NSString *m = [s substringFromIndex: 9];
if (nil != [_managedObjects member: m])
{
[_managedObjects removeObject: m];
[self unmanageFwd: m];
}
/* When we unmanage an object, we also
* implicitly unmanage objects which
* are components of that object.
*/
if (YES == [m hasSuffix: @"_"])
{
NSEnumerator *e;
NSString *s;
e = [[[_managedObjects copy] autorelease]
objectEnumerator];
while (nil != (s = [e nextObject]))
{
if (YES == [s hasPrefix: m])
{
[_managedObjects removeObject: s];
}
}
}
}
else
{
NSLog(@"ERROR ... unexpected command '%@'", s);
}
}
[_alarmQueue removeObjectAtIndex: 0];
}
}
_inTimeout = NO;
[_alarmLock unlock];
}
NS_HANDLER
{
_inTimeout = NO;
[_alarmLock unlock];
NSLog(@"%@ %@", NSStringFromClass([self class]), localException);
}
NS_ENDHANDLER
}
}
@end
@implementation EcAlarmDestination (Forwarding)
- (void) alarmFwd: (EcAlarm*)event
{
NS_DURING
[_destination alarm: event];
NS_DURING
[_backups makeObjectsPerformSelector: @selector(alarm:)
withObject: event];
NS_HANDLER
DESTROY(_backups);
NSLog(@"Problem sending alarm to backups ... %@", localException);
NS_ENDHANDLER
NS_HANDLER
[self setDestination: nil];
NSLog(@"Problem sending alarm to destination ... %@", localException);
NS_ENDHANDLER
}
- (void) domanageFwd: (NSString*)managedObject
{
NS_DURING
[_destination domanage: managedObject];
NS_DURING
[_backups makeObjectsPerformSelector: @selector(domanage:)
withObject: managedObject];
NS_HANDLER
DESTROY(_backups);
NSLog(@"Problem with domanage to backups ... %@", localException);
NS_ENDHANDLER
NS_HANDLER
[self setDestination: nil];
NSLog(@"Problem with domanage to destination ... %@", localException);
NS_ENDHANDLER
}
- (void) unmanageFwd: (NSString*)managedObject
{
NS_DURING
[_destination unmanage: managedObject];
NS_DURING
[_backups makeObjectsPerformSelector: @selector(unmanage:)
withObject: managedObject];
NS_HANDLER
DESTROY(_backups);
NSLog(@"Problem with unmanage to backups ... %@", localException);
NS_ENDHANDLER
NS_HANDLER
[self setDestination: nil];
NSLog(@"Problem with unmanage to destination ... %@", localException);
NS_ENDHANDLER
}
@end

174
EcAlarmSinkSNMP.h Normal file
View file

@ -0,0 +1,174 @@
#import <Foundation/NSObject.h>
#import "EcAlarmDestination.h"
/** <p>The EcAlarmSinkSNMP class implements an alarm destination which
* terminates a chain of destinations by delivering alarms to an SNMP
* monitoring system rather than forwarding them to another EcAlarmDestination
* object.
* </p>
* <p>The SNMP functionality of the EcAlarmSinkSNMP is essentially to maintain
* a table of active alarms and to send out traps indicating changes of
* state of that table as follows:
* </p>
* <p>Alarms in the table are each identified by a unique numeric identifier,
* the 'notificationID' and traps concerning the alarms carry that ID.<br />
* For each alarm trap sent out, there is a corresponding trap sent when
* the alarm is cleared and removed from the table. The clear trap carries
* the same notification identifier as that of the alarm being cleared, and
* the pair (alarm/clear) is known as a correlation.<br />
* A clear trap is never sent without a corresponding alarm trap having first
* been sent.<br />
* Each trap carries a sequence number so that the SNMP manager is able to
* detect lost traps.
* </p>
* <p>For purposes of establishing a correlation between any alarms coming
* in to the system, the managed object, event type, specific problem,
* and probable cause values must be the same. When this occurs, any
* matching alarms are given the same notification identifier as the first
* alarm.<br />
* When an alarm is added to the table, only one trap will to notify about it,
* and no further traps will be sent for any correlated alarms arriving
* unless those alarms have new perceived severity values.<br />
* If an alarm changes its severity, a trap will be sent to clear the
* alarm (removing it from the table) and the alarm with new severity will
* be added to the table and its trap sent.
* </p>
* <p>To allow an SNMP manager to resynchronize with the agent, the manager
* may examine the table of active alarms. This may be needed where there
* is an agent error, manager error, or loss of connectivity/traps (jumps
* in the trap sequence number).<br />
* The entries in the table contain all the same information as the alarm
* traps, apart from the trap sequence number.
* </p>
* <p>After a crash/shutdown and subsequent start-up of the agent, the
* active alarm table is updated with the current situation of the system
* before the manager is able to respond to the coldstart (see RFC 1157).
* When the manager receives the agent's coldstart it may being a
* resynchronization process gathering alarms related to the current
* situation of the system by examining the alarm table.<br />
* There is a resync flag variable which indicates whether a resynchronization
* process is being carried out or not (set to 1 if resync is in progress,
* 0 otherwise). Before sending a trap, the variable is checked to know
* whether the resynchronization process is active or not. The traps will
* only be sent to the manager if there is no resync in progress, otherwise
* they will be deferred until the resync has completed.<br />
* The manager may therefore set the resync flag to 1, read the contents of
* the active alarms table, and then set the resync flag to zero to resume
* active operation.<br />
* As a safety measure to protect against manager failure, the resync flag
* is automatically reset to zero if it is left set to 1 for more than five
* minutes.
* </p>
* <p>In addition to the traps and the alarms table, a table of managed
* objects is also maintained. The system guarantees that any managed object
* referred to in a trap is present in the table before the trap is sent.
* </p>
* <p>In order for the manager to know that the agent is running, a heartbeat
* trap is sent periodically. The heartbeat contains a notification
* identifier of zero (never used other than for a heartbeat).<br />
* A poll heartbeat interval variable is provided to allow the manager to
* control the time between heartbeats (in minutes) and may be set to a
* positive integer value, or queried.
* </p>
* <p>The class works by connecting to an SNMP agent using the Agent-X
* protocol, and registering as the 'owner' of various OIDs in an SNMP MIB
* so that SNMP requests made to the agent for those OIDs are forwarded to
* the EcAlarmSinkSNMP, and so that the EcAlarmSinkSNMP can send SNMP 'traps'
* via the agent.<br />
* To do this, the agent must be configured to accept incoming Agent-X
* connections from the host on which the EcAlarmSinkSNMP is running, and the
* alarm sink object must be initialized using the -initWithHost:name: method
* to specify the host and port on which the agent is listening.<br />
* If the EcAlarmSinkSNMP object is initialized without a specific host and name
* then it assumes that the agent is running on localhost and the standard
* Agent-X tcp/ip port (705).
* </p>
* <p>To configure a net-snmp agent to work with this software you need to:
* </p>
* Edit /etc/snmp/snmpd.conf to get it to send traps to snmptrapd ...
* <example>
* rwcommunity public
* trap2sink localhost public
* </example>
* and to accept agentx connections via tcp ...
* <example>
* agentxsocket tcp:localhost:705
* master agentx
* </example>
* Then restart with '/etc/rc.d/init.d/snmpd restart'
*
* <p>All alarming is done a based on three OIDs which may be defined
* within the user defaults system. The EcAlarmSinkSNMP is responsible for
* managing those OIDs (and anything below them in the OID hierarchy).
* </p>
* <deflist>
* <term>AlarmsOID</term>
* <desc>All SNMP alarm data is in a fixed structure relative to this
* OID in the MIB.<br />
* The table of current alarms is at the OID 1 relative to AlarmsOID,
* so you can interrogate the table using;
* <example>snmpwalk -v 1 -c public localhost {AlarmsOID}.1
* </example>
* The resync flag is at OID 2 relative to AlarmsOID and can be queried
* or set using:
* <example>snmpget -v 1 -c public localhost {AlarmsOID}.2.0
* </example>
* <example>snmpset -v 1 -c public localhost {AlarmsOID}.2.0 i 1
* </example>
* The current trap sequence number is at OID 3 relative to AlarmsOID
* and can be queried using:
* <example>snmpget -v 1 -c public localhost {AlarmsOID}.3.0
* </example>
* The heartbeat poll interval is at OID 4 relative to AlarmsOID
* and can be queried or set using:
* <example>snmpget -v 1 -c public localhost {AlarmsOID}.4.0
* </example>
* <example>snmpset -v 1 -c public localhost {AlarmsOID}.4.0 i 5
* </example>
* </desc>
* <term>ObjectsOID</term>
* <desc>All SNMP managed object values are in a table relative to this
* OID in the MIB.<br />
* SNMP tools are able to interrogate the table at this OID to see which
* managed objects are currently registered:
* <example>snmpwalk -v 1 -c public localhost {ObjectsOID}
* </example>
* </desc>
* <term>TrapOID</term>
* <desc>The definition of the trap which carries alarms to any monitoring
* system is at this OID.
* </desc>
* </deflist>
* <p>Each of these OID strings must be a representation of an OID in the
* integer dotted format (eg. 1.3.6.1.4.1.37374.3.0.1).
* </p>
*/
@interface EcAlarmSinkSNMP : EcAlarmDestination
/** <p>Returns the singleton alarm sink instance.<br />
* This instance is the link between the Objective-C world and the net-snmp
* world which runs in a separate thread. You should stop the SNMP system
* by calling -shutdown before process termination.
* </p>
* <p>If the agent is on another host and/or is using a non-standard port,
* you need to call -initWithHost:name: before and/or instead of calling
* this method.
* </p>
*/
+ (EcAlarmSinkSNMP*) alarmSinkSNMP;
/** <p>Overrides the default behavior to specify a host and name for the
* SNMP agent this instance is to connect to.<br />
* The host must be the machine the agent is running on and the name must
* be the tcp/ip port on which that agent is listening for Agent-X connections.
* </p>
* <p>If an instance has already been created/initialized, this method returns
* the existing instance and its arguments are ignored.
* </p>
*/
- (id) initWithHost: (NSString*)host name: (NSString*)name;
@end

1604
EcAlarmSinkSNMP.m Normal file

File diff suppressed because it is too large Load diff

32
EcAlerter.h Normal file
View file

@ -0,0 +1,32 @@
#import <Foundation/NSObject.h>
@class GSMimeSMTPClient;
@class NSArray;
@class NSMutableArray;
@class NSMutableDictionary;
@class NSString;
@class NSTimer;
@interface EcAlerter : NSObject
{
NSArray *rules;
NSMutableDictionary *email;
NSMutableDictionary *sms;
NSTimer *timer;
NSString *eFrom;
NSString *eHost;
NSString *ePort;
GSMimeSMTPClient *smtp;
}
- (BOOL) configure: (NSNotification*)n;
- (void) handleInfo: (NSString*)str;
- (void) flushEmail;
- (void) flushSms;
- (void) log: (NSMutableDictionary*)m to: (NSArray*)destinations;
- (void) mail: (NSMutableDictionary*)m to: (NSArray*)destinations;
- (void) sms: (NSMutableDictionary*)m to: (NSArray*)destinations;
- (void) timeout: (NSTimer*)t;
@end

923
EcAlerter.m Normal file
View file

@ -0,0 +1,923 @@
#import <Foundation/Foundation.h>
#import <GNUstepBase/GSMime.h>
#import "EcProcess.h"
#import "EcAlerter.h"
#import "NSFileHandle+Printf.h"
#include <regex.h>
@interface Regex: NSObject
{
regex_t regex;
BOOL built;
}
- (id) initWithString: (NSString*)pattern;
- (NSString*) match: (NSString*)string;
@end
@implementation Regex
- (void) dealloc
{
if (built == YES)
{
built = NO;
regfree(&regex);
}
[super dealloc];
}
- (id) initWithString: (NSString*)pattern
{
if (regcomp(&regex, [pattern UTF8String], REG_EXTENDED) != 0)
{
NSLog(@"Failed to compile regex - '%@'", pattern);
DESTROY(self);
}
else
{
built = YES;
}
return self;
}
- (NSString*) match: (NSString*)string
{
regmatch_t matches[2];
const char *str = [string UTF8String];
if (regexec(&regex, str, 1, matches, 0) != 0)
{
return nil;
}
else
{
int l = matches[0].rm_eo - matches[0].rm_so;
char b[l+1];
memcpy(b, &str[matches[0].rm_so], l);
b[l] = '\0';
return [NSString stringWithUTF8String: b];
}
}
@end
static NSMutableString *
replaceFields(NSDictionary *fields)
{
NSMutableString *m;
m = [[[fields objectForKey: @"Replacement"] mutableCopy] autorelease];
if (nil != m)
{
NSEnumerator *e;
NSString *k;
e = [fields keyEnumerator];
while (nil != (k = [e nextObject]))
{
if (NO == [k isEqualToString: @"Replacement"])
{
NSString *v;
v = [[fields objectForKey: k] description];
k = [NSString stringWithFormat: @"{%@}", k];
[m replaceOccurrencesOfString: k
withString: v
options: NSLiteralSearch
range: NSMakeRange(0, [m length])];
}
}
}
return m;
}
/**
* <p>This class handles delivery and logging of error and alert messages
* to the people who should be monitoring the system. It is used by the
* Control server (to which all these messages are delivered) and
* implements a simple rule based mechanism for managing final
* delivery of the messages.
* </p>
* <p>The configured rules are compared against each message and any
* actions associated with a matching rule are performed.<br />
* The matching fields in each rule are -
* </p>
* <deflist>
* <term>Host</term>
* <desc>An extended regular expression to match the name of the host
* machine on which the message originated (possibly just the host name).
* If this is not specified, messages from any host may match.
* </desc>
* <term>Server</term>
* <desc>An extended regular expression to match the name of the server
* process from which the message originated (possibly just the server
* name).
* If this is not specified, messages from any server may match.
* </desc>
* <term>Type</term>
* <desc>The type of message ... <em>Error</em> or <em>Alert</em>.
* If this is not specified, messages of any type may match.
* </desc>
* <term>Pattern</term>
* <desc>An extended regular expression used to match the main text
* of the message. See the posix regcomp documentation for details
* of enhanced posix regular expressions. If this is not present,
* any message text will match.
* </desc>
* <term>Stop</term>
* <desc>A boolean (YES or NO) saying whether rule matching should
* stop if this rule is matched. If this is NO (the default) then
* after any action associated with this rule is performed, matching
* continues at the next rule.<br />
* <em>Don't use this option injudiciusly. Try to write your pattern
* matching rules so that most messages match a single rule to map
* them to a nice readable version, and also match a default rule to
* log full details to the technical team.</em>
* </desc>
* <term>Flush</term>
* <desc>A boolean (YES or NO) saying whether stored messages due to
* be sent out later should be sent out immediately after processing
* this rule. This is useful in the event that some time critical
* message must be sent, but should not normally be used.<br />
* As a special case, instead of the boolean value, this may take
* the value <em>Email</em> or <em>Sms</em> indicating that a flush
* should be performed, but only on the specified type of messages.<br />
* <strong>beware</strong> The batching mechanism exists to prevent
* a single problem triggering floods of messages. You should only
* override it using <em>Flush</em> where you are <strong>sure</strong>
* that messages triggering the flush will be infrequent.
* </desc>
* </deflist>
* <p>There are two additional fields <em>Extra1</em> and <em>Extra2</em>
* which are matched against the message. These patterns do not effect
* whether the action of the rule is executed or not, but the text matched
* is made available for substitution into replacement messages.
* </p>
* <p>When a match is found the full message is normally sent to all the
* destinations listed in the <em>Email</em> and <em>Sms</em> arrays in
* the rule, and logged to all the destinations in the <em>Log</em> array.<br />
* However, the <em>Replacement</em> field may be used to specify
* a message to be sent in the place of the one received. Within the
* <em>Replacement</em> string values enclosed in curly brackets will
* be substituted as follows -
* </p>
* <deflist>
* <term>Extra1</term>
* <desc>The text in the message matched by the Extra1 pattern (if any)</desc>
* <term>Extra2</term>
* <desc>The text in the message matched by the Extra2 pattern (if any)</desc>
* <term>Host</term>
* <desc>The host name of the original message</desc>
* <term>Server</term>
* <desc>The server name of the original message</desc>
* <term>Type</term>
* <desc>The type of the original message</desc>
* <term>Timestamp</term>
* <desc>The timestamp of the original message</desc>
* <term>Message</term>
* <desc>The text of the original message</desc>
* <term>Match</term>
* <desc>The text matched by the <em>Pattern</em> if any</desc>
* </deflist>
* <p>The <em>Log</em> array specifies a list of log destinations which are
* normally treated as filenames (stored in the standard log directory).
* However, a value beginning 'database:' * is logged to a
* database (the default database configured for SQLClient).<br />
* After the colon you may place a table name, but if you don't then
* the message will be logged to the 'Alert' table.<br />
* The values logged in separate fields are the Timestamp, Type, Server, Host,
* Extra1, Extra2, and full log text (as produced by the Replacement config)
* is written into the Message field of the table after having been truncated
* to 200 chars. Because of the truncation limit, it is recommended that
* if you are trying to include the original alert {Message} (rather
* than rewriting it) the Replacement does not include Timestamp,
* Type, Server, Host, Extra1, Extra2 which are already saved in
* separate fields, and would take up a lot of the 200 chars, which would
* be better used to log the actual message.
*
* </p>
* <p>The <em>Sms</em> array lists phone numbers to which Sms alerts are
* to be sent.
* </p>
* <p>The <em>Email</em> array lists email addresses to which email alerts are
* to be sent.<br />
* An optional 'Subject' field may be present in the rule ... this is used
* to specify that the is to be tagged with the given subject line. This
* <em>defeats</em> batching of messages in that only messages with the
* same subject may be batched in the same email.
* </p>
*/
@implementation EcAlerter : NSObject
/**
* Called to set up or modify the configuration of the alerter.<br />
* The dictionary c must contain (keyed on <code>Rules</code> an
* array of dictionaries, each of which provides a rule for
* delivering some form of alert.<br />
* Other values in the configuration are used for standard configuration
* of message delivery to the queueing system etc.
*/
- (BOOL) configure: (NSNotification*)n
{
NSUserDefaults *d;
NSDictionary *c;
NSMutableDictionary *m;
NSMutableArray *r;
unsigned int i;
d = [EcProc cmdDefaults];
c = [d dictionaryForKey: @"Alerter"];
ASSIGNCOPY(eHost, [c objectForKey: @"EmailHost"]);
ASSIGNCOPY(eFrom, [c objectForKey: @"EmailFrom"]);
ASSIGNCOPY(ePort, [c objectForKey: @"EmailPort"]);
/*
* Cache a copy of the Rules with modifications to store information
* so we don't need to regenerate it every time we check a message.
*/
r = [[[m objectForKey: @"Rules"] mutableCopy]autorelease];
[m removeObjectForKey: @"Rules"];
for (i = 0; i < [r count]; i++)
{
NSMutableDictionary *md;
NSString *str;
Regex *val;
md = [[r objectAtIndex: i] mutableCopy];
[r replaceObjectAtIndex: i withObject: md];
[md release];
str = [md objectForKey: @"Host"];
[md removeObjectForKey: @"HostRegex"];
if (str != nil)
{
val = [[Regex alloc] initWithString: str];
if (nil == val)
{
return NO;
}
[md setObject: val forKey: @"HostRegex"];
[val release];
}
str = [md objectForKey: @"Pattern"];
[md removeObjectForKey: @"PatternRegex"];
if (str != nil)
{
val = [[Regex alloc] initWithString: str];
if (val == nil)
{
return NO;
}
[md setObject: val forKey: @"PatternRegex"];
RELEASE(val);
}
str = [md objectForKey: @"Server"];
[md removeObjectForKey: @"ServerRegex"];
if (str != nil)
{
val = [[Regex alloc] initWithString: str];
if (val == nil)
{
return NO;
}
[md setObject: val forKey: @"ServerRegex"];
RELEASE(val);
}
str = [md objectForKey: @"Extra1"];
[md removeObjectForKey: @"Extra1Regex"];
if (str != nil)
{
val = [[Regex alloc] initWithString: str];
if (val == nil)
{
return NO;
}
[md setObject: val forKey: @"Extra1Regex"];
RELEASE(val);
}
str = [md objectForKey: @"Extra2"];
[md removeObjectForKey: @"Extra2Regex"];
if (str != nil)
{
val = [[Regex alloc] initWithString: str];
if (val == nil)
{
return NO;
}
[md setObject: val forKey: @"Extra2Regex"];
RELEASE(val);
}
}
ASSIGN(rules, r);
return YES;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
[timer invalidate];
[self flushSms];
[self flushEmail];
[smtp flush: [NSDate dateWithTimeIntervalSinceNow: 30.0]];
RELEASE(smtp);
RELEASE(email);
RELEASE(sms);
RELEASE(rules);
[super dealloc];
}
- (NSString*) description
{
return [NSString stringWithFormat: @"%@ -\nEmail:%@\nSms:%@",
[super description], email, sms];
}
- (void) flushEmailForAddress: (NSString*)address
subject: (NSString*)subject
text: (NSString*)text
{
GSMimeHeader *hdr;
GSMimeDocument *doc;
NS_DURING
{
if (smtp == nil)
{
smtp = [GSMimeSMTPClient new];
}
if (nil != eHost)
{
[smtp setHostname: eHost];
}
if (nil != ePort)
{
[smtp setPort: ePort];
}
if (nil == eFrom)
{
eFrom = [NSString stringWithFormat: @"alerter@%@",
[[NSHost currentHost] name]];
RETAIN(eFrom);
}
doc = AUTORELEASE([GSMimeDocument new]);
hdr = [[GSMimeHeader alloc] initWithName: @"subject"
value: subject
parameters: nil];
[doc setHeader: hdr];
RELEASE(hdr);
hdr = [[GSMimeHeader alloc] initWithName: @"to"
value: address
parameters: nil];
[doc setHeader: hdr];
RELEASE(hdr);
hdr = [[GSMimeHeader alloc] initWithName: @"from"
value: eFrom
parameters: nil];
[doc setHeader: hdr];
RELEASE(hdr);
[doc setContent: text type: @"text/plain" name: nil];
[smtp send: doc];
}
NS_HANDLER
{
NSLog(@"Problem flushing email for address: %@, subject: %@, %@",
address, subject, localException);
}
NS_ENDHANDLER
}
/**
* This method is called to flush any batched email messages.
*/
- (void) flushEmail
{
NSDictionary *destinations;
NS_DURING
{
if ((destinations = email) != nil)
{
NSEnumerator *addressEnumerator = [destinations keyEnumerator];
NSString *address;
DESTROY(email);
while ((address = [addressEnumerator nextObject]) != nil)
{
NSDictionary *items = [destinations objectForKey: address];
NSEnumerator *itemEnumerator = [items keyEnumerator];
NSString *subject;
while ((subject = [itemEnumerator nextObject]) != nil)
{
NSString *text = [items objectForKey: subject];
[self flushEmailForAddress: address
subject: subject
text: text];
}
}
}
}
NS_HANDLER
{
NSLog(@"Problem flushing email: %@", localException);
}
NS_ENDHANDLER
}
/**
* This method is called periodically to flush any batched sms messages.
*/
- (void) flushSms
{
NS_DURING
{
NSLog(@"Problem flushing sms: ...not currently supported");
}
NS_HANDLER
{
NSLog(@"Problem flushing sms: %@", localException);
}
NS_ENDHANDLER
}
/**
* <p>This method handles error/alert messages. It is able to handle
* multiple (newline separated messages.
* </p>
* <p>Each message must be a line of the format -<br />
* serverName(hostName): YYYY-MM-DD hh:mm:ss.mmm szzzz type - text
* </p>
* <p>Each message is matched against each rule in the <em>Rules</em>
* configuration in turn, and the first match found is used. The
* message is sent to the people listed in the <code>Email</code> and
* <code>Sms</code> entries in the rule (which may be either single
* names or arryas of names).
* </p>
*/
- (void) handleInfo: (NSString*)str
{
NSArray *a;
unsigned int i;
NSMutableDictionary *m;
NS_DURING
{
a = [str componentsSeparatedByString: @"\n"];
for (i = 0; i < [a count]; i++)
{
NSString *inf = [a objectAtIndex: i];
NSRange r;
NSString *timestamp;
NSString *serverName;
NSString *hostName;
NSString *type = @"Error";
unsigned pos;
unsigned j;
str = inf;
if ([str length] == 0)
{
continue; // Nothing to do
}
/* Record format is -
* serverName(hostName): timestamp Alert - message
* or
* serverName(hostName): timestamp Error - message
*/
r = [str rangeOfString: @":"];
if (r.length == 0)
{
continue; // Not an alert or error
}
serverName = [str substringToIndex: r.location];
str = [str substringFromIndex: NSMaxRange(r) + 1];
r = [serverName rangeOfString: @"("];
if (r.length == 0)
{
continue; // Not an alert or error
}
pos = NSMaxRange(r);
hostName = [serverName substringWithRange:
NSMakeRange(pos, [serverName length] - pos - 1)];
serverName = [serverName substringToIndex: r.location];
r = [str rangeOfString: @" Alert - "];
if (r.length == 0)
{
r = [str rangeOfString: @" Error - "];
if (r.length == 0)
{
continue; // Not an alert or error
}
type = @"Error";
}
else
{
type = @"Alert";
}
timestamp = [str substringToIndex: r.location];
str = [str substringFromIndex: NSMaxRange(r)];
for (j = 0; j < [rules count]; j++)
{
NSDictionary *d = [rules objectAtIndex: j];
NSString *match = nil;
Regex *e;
NSString *s;
id o;
s = [d objectForKey: @"Type"];
if (s != nil && [s isEqualToString: type] == NO)
{
continue; // Not a match.
}
e = [d objectForKey: @"ServerRegex"];
if (e != nil && [e match: serverName] == nil)
{
continue; // Not a match.
}
e = [d objectForKey: @"HostRegex"];
if (e != nil && [e match: hostName] == nil)
{
continue; // Not a match.
}
e = [d objectForKey: @"PatternRegex"];
if (e != nil && (match = [e match: str]) == nil)
{
continue; // Not a match.
}
m = [NSMutableDictionary new];
/*
* If the Extra1 or Extra2 patterns are matched,
* The matching strings are made available for
* substitution inot the replacement message.
*/
[m removeObjectForKey: @"Extra1"];
e = [d objectForKey: @"Extra1Regex"];
if (e != nil && (match = [e match: str]) != nil)
{
[m setObject: match forKey: @"Extra1"];
}
[m removeObjectForKey: @"Extra2"];
e = [d objectForKey: @"Extra2Regex"];
if (e != nil && (match = [e match: str]) != nil)
{
[m setObject: match forKey: @"Extra2"];
}
/* We set the Replacement later, because if it is not
* set, we want to set a different default for Sms/Email
* and database: for Sms/Email logs we want to include
* Server, Host, Timestamp, Type and Message (possibly
* trying to use as little spaces as possible for Sms,
* while trying to display comfortably for Email), while
* for database logs we only want to include the
* Message.
*/
[m setObject: serverName forKey: @"Server"];
[m setObject: hostName forKey: @"Host"];
[m setObject: type forKey: @"Type"];
[m setObject: timestamp forKey: @"Timestamp"];
[m setObject: str forKey: @"Message"];
if (match != nil)
{
[m setObject: match forKey: @"Match"];
}
// NSLog(@"Match produced %@", s);
NS_DURING
{
o = [d objectForKey: @"Log"];
if ([o isKindOfClass: [NSString class]] == YES)
{
if ([o hasPrefix: @"("])
{
o = [(NSString*)o propertyList];
}
else
{
o = [NSArray arrayWithObject: o];
}
}
if (o != nil)
{
NSString *s = [d objectForKey: @"Replacement"];
if (s == nil)
{
s = @"{Message}";
}
[m setObject: s forKey: @"Replacement"];
[self log: m to: o];
}
}
NS_HANDLER
{
NSLog(@"Exception handling database log for rule: %@",
localException);
}
NS_ENDHANDLER
NS_DURING
{
o = [d objectForKey: @"Email"];
if ([o isKindOfClass: [NSString class]] == YES)
{
if ([o hasPrefix: @"("])
{
o = [(NSString*)o propertyList];
}
else
{
o = [NSArray arrayWithObject: o];
}
}
if (o != nil)
{
NSString *s = [d objectForKey: @"Subject"];
if (s != nil)
{
[m setObject: s forKey: @"Subject"];
}
s = [d objectForKey: @"Replacement"];
if (s == nil)
{
/* Full details. */
s = @"{Server}({Host}): {Timestamp} {Type} - {Message}";
}
[m setObject: s forKey: @"Replacement"];
[self mail: m to: o];
}
}
NS_HANDLER
{
NSLog(@"Exception handling Email send for rule: %@",
localException);
}
NS_ENDHANDLER
NS_DURING
{
o = [d objectForKey: @"Sms"];
if ([o isKindOfClass: [NSString class]] == YES)
{
if ([o hasPrefix: @"("])
{
o = [(NSString*)o propertyList];
}
else
{
o = [NSArray arrayWithObject: o];
}
}
if (o != nil)
{
NSString *s = [d objectForKey: @"Replacement"];
if (s == nil)
{
/* Use few spaces so that more of the
* message fits into an Sms. */
s = @"{Server}({Host}):{Timestamp} {Type}-{Message}";
}
[m setObject: s forKey: @"Replacement"];
[self sms: m to: o];
}
}
NS_HANDLER
{
NSLog(@"Exception handling Sms send for rule: %@",
localException);
}
NS_ENDHANDLER
RELEASE(m);
s = [d objectForKey: @"Flush"];
if (s != nil)
{
if ([s caseInsensitiveCompare: @"Email"] == NSOrderedSame)
{
[self flushEmail];
}
else if ([s caseInsensitiveCompare: @"Sms"] == NSOrderedSame)
{
[self flushSms];
}
else if ([s boolValue] == YES)
{
[self flushSms];
[self flushEmail];
}
}
if ([[d objectForKey: @"Stop"] boolValue] == YES)
{
break; // Don't want to perform any more matches.
}
}
}
}
NS_HANDLER
{
NSLog(@"Problem in handleInfo:'%@' ... %@", str, localException);
}
NS_ENDHANDLER
}
- (id) init
{
if (nil != (self = [super init]))
{
timer = [NSTimer scheduledTimerWithTimeInterval: 240.0
target: self
selector: @selector(timeout:)
userInfo: nil
repeats: YES];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(configure:)
name: NSUserDefaultsDidChangeNotification
object: [NSUserDefaults standardUserDefaults]];
}
return self;
}
/**
* Called by -handleInfo: to log a message to an array of destinations.
*/
- (void) log: (NSMutableDictionary*)m to: (NSArray*)destinations
{
NSEnumerator *e = [destinations objectEnumerator];
NSString *d;
NSString *s;
/*
* Perform {field-name} substitutions ...
*/
s = replaceFields(m);
while ((d = [e nextObject]) != nil)
{
[[EcProc cmdLogFile: d] printf: @"%@\n", s];
}
}
/**
* Called by -handleInfo: to pass a message to an array of destinations.
* The message is actually appended to any cached messages for those
* destinations ... and the cache is periodically flushed.
*/
- (void) mail: (NSMutableDictionary*)m to: (NSArray*)destinations
{
NSEnumerator *e = [destinations objectEnumerator];
NSString *d;
NSString *s;
NSString *subject = [m objectForKey: @"Subject"];
if (subject == nil)
{
subject = @"system alert";
}
else
{
AUTORELEASE(RETAIN(subject));
[m removeObjectForKey: @"Subject"];
}
/*
* Perform {field-name} substitutions ...
*/
s = replaceFields(m);
if (email == nil)
{
email = [NSMutableDictionary new];
}
while ((d = [e nextObject]) != nil)
{
NSMutableDictionary *md = [email objectForKey: d];
NSString *msg;
if (md == nil)
{
md = [NSMutableDictionary new];
[email setObject: md forKey: d];
RELEASE(md);
}
msg = [md objectForKey: subject];
/*
* If adding the new text would take an existing message over the
* size limit, send the existing stuff first.
*/
if ([msg length] > 0 && [msg length] + [s length] + 2 > 20*1024)
{
AUTORELEASE(RETAIN(msg));
[md removeObjectForKey: subject];
[self flushEmailForAddress: d
subject: subject
text: msg];
msg = nil;
}
if (msg == nil)
{
msg = s;
}
else
{
msg = [msg stringByAppendingFormat: @"\n\n%@", s];
}
[md setObject: msg forKey: subject];
}
}
/**
* Called by -handleInfo: to pass a message to an array of destinations.
* The message replaces any cached messages for those
* destinations (and has a count of the lost messages noted) ... and
* the cache is periodically flushed.
*/
- (void) sms: (NSMutableDictionary*)m to: (NSArray*)destinations
{
NSEnumerator *e = [destinations objectEnumerator];
NSString *d;
NSString *s;
NSString *t;
/*
* Perform {field-name} substitutions, but to shorten the message
* remove any Timestamp value from the dictionary.
*/
t = RETAIN([m objectForKey: @"Timestamp"]);
[m removeObjectForKey: @"Timestamp"];
s = replaceFields(m);
if (t != nil)
{
[m setObject: t forKey: @"Timestamp"];
RELEASE(t);
}
if (sms == nil)
{
sms = [NSMutableDictionary new];
}
while ((d = [e nextObject]) != nil)
{
NSString *msg = [sms objectForKey: d];
if (msg == nil)
{
msg = s;
}
else
{
int missed = 0;
if ([msg hasPrefix: @"Missed("] == YES)
{
NSRange r = [msg rangeOfString: @")"];
r = NSMakeRange(7, r.location - 7);
msg = [msg substringWithRange: r];
missed = [msg intValue];
}
missed++;
msg = [NSString stringWithFormat: @"Missed(%d)\n%@", missed, s];
}
[sms setObject: msg forKey: d];
}
}
/**
* Responsible for the periodic calling of -flushEmail and -flushSms
*/
- (void) timeout: (NSTimer*)t
{
[self flushSms];
[self flushEmail];
}
@end

132
EcBroadcastProxy.h Normal file
View file

@ -0,0 +1,132 @@
/* -*-objc-*-
* Nicola Pero, Brainstorm, October 2000
* EcBroadcastProxy - an object able to broadcast a message to
* a list of remote objects.
*
*/
#ifndef _ECBROADCASTPROXY_H
#define _ECBROADCASTPROXY_H
#include <Foundation/NSObject.h>
@class NSArray;
@class NSMutableArray;
@class NSString;
enum EcBroadcastProxyError
{
BCP_NO_ERROR = 0,
BCP_COULD_NOT_CONNECT = 1,
BCP_CONNECTION_WENT_DOWN = 2,
BCP_MESSAGING_TIMEOUT = 3
};
/**
* In this class, remote servers are always listed in the same order.
* So, you can access them by their index; to get the host/name, you
* can get it by asking the receiverNames and receiverHosts and then
* looking up the one at the index you want.
*/
@interface EcBroadcastProxy : NSObject
{
/* The names of the receiver object */
NSArray *receiverNames;
/* The hosts the receiver objects are on */
NSArray *receiverHosts;
/* The [proxy to the] remote objects */
NSMutableArray *receiverObjects;
/* The delegate (if any) */
id delegate;
/* The statistical info about what we did */
/* Messages returning void */
int onewayFullySent;
int onewayPartiallySent;
int onewayFailed;
/* Messages returning id */
int idFullySent;
int idPartiallySent;
int idFailed;
}
- (id) initWithReceiverNames: (NSArray *)names
receiverHosts: (NSArray *)hosts;
/** Configuration array contains a list of dictionaries (one for each
receiver) - each dictionary has two keys: `Name' and `Host', with
the corresponding values set. */
- (id) initWithReceivers: (NSArray *)receivers;
/* Methods specific to EcBroadcastProxy (which should not be forwarded
to remote servers) are prefixed with `BCP' to avoid name clashes.
Anything not prefixed with BCP is forwarded. */
/** Create connections to the receivers if needed. It is called
internally when a message to broadcast comes in; but you may want
to call this method in advance to raise the connections so that
when a message to broadcast comes in, the connections are already
up and ready. */
- (void) BCPraiseConnections;
/** Get a string describing the status of the broadcast object */
- (NSString *) BCPstatus;
/** Set a delegate.<br />
* The delegate gets a -BCP:lostConnectionToServer:onHost: message
* upon connection lost, and -BCP:madeConnectionToServer:onHost:
* message upon connection made.
*/
- (void) BCPsetDelegate: (id)delegate;
- (id) BCPdelegate;
/* [Advanced stuff]
Access to a single one of the servers we are broadcasting to.
Eg, after sending a search to multiple servers, you might need to
get further information only from the servers which answered
affirmatively to your query. To do it, use BCPproxyForName:host:
to get the single remote server(s) you want to talk to, and talk to
it (each of them) directly. */
/* Get the list of servers */
- (NSArray *) BCPreceiverNames;
- (NSArray *) BCPreceiverHosts;
/* Get only the number of receivers */
- (int) BCPreceiverCount;
/* Raise connection to server at index */
- (void) BCPraiseConnection: (int)index;
/* The following one gives you back the proxy to talk to.
It automatically BCPraise[s]Connection to that server before sending
you back a proxy. It returns nil upon failure. */
- (id) BCPproxy: (int)index;
@end
@interface NSObject (BCPdelegate)
- (void) BCP: (EcBroadcastProxy *)proxy
lostConnectionToServer: (NSString *)name
host: (NSString *)host;
- (void) BCP: (EcBroadcastProxy *)proxy
madeConnectionToServer: (NSString *)name
host: (NSString *)host;
@end
#endif /* _BROADCASTPROXY_H */

676
EcBroadcastProxy.m Normal file
View file

@ -0,0 +1,676 @@
/*
* Nicola Pero, Brainstorm, October 2000
* EcBroadcastProxy - an object able to broadcast a message to
* a list of remote objects.
*
*/
#import <Foundation/Foundation.h>
#import "EcBroadcastProxy.h"
/*
EcBroadcastProxy
*/
static NSNotificationCenter *nc;
static NSNull *null;
@interface EcBroadcastProxy (Private)
/* Methods which make it work :) */
/* NB: We post a @"EcBroadcastProxyHadIPNotification" when this method
is called, and a @"EcBroadcastProxyHadOPNotification" when it completed
(if any output was sent). */
- (void) forwardInvocation: (NSInvocation*)anInvocation;
- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector;
- (void) BCPforwardOneWayInvocation: (NSInvocation*)anInvocation;
- (void) BCPforwardIdInvocation: (NSInvocation*)anInvocation;
- (void) BCPforwardPrivateInvocation: (NSInvocation*)anInvocation;
@end
@implementation EcBroadcastProxy
+ (void) initialize
{
if (self == [EcBroadcastProxy class])
{
nc = [NSNotificationCenter defaultCenter];
null = [NSNull null];
}
}
/* Designated initializer */
- (id) initWithReceiverNames: (NSArray *)names
receiverHosts: (NSArray *)hosts
{
int i, count;
NSLog (@"EcBroadcastProxy: initializing with %@ names and %@ host",
names, hosts);
if (nil != (self = [super init]))
{
if ([names count] != [hosts count])
{
[NSException raise: NSInvalidArgumentException format:
@"invalid initialization of EcBroadcastProxy: "
@"[names count] != [hosts count]"];
}
ASSIGN (receiverNames, names);
ASSIGN (receiverHosts, hosts);
receiverObjects = [NSMutableArray new];
count = [receiverNames count];
for (i = 0; i < count; i++)
{
[receiverObjects addObject: null];
}
}
/* All the statistical ivars are automatically initialized to zero */
return self;
}
- (id) initWithReceivers: (NSArray *)receivers
{
int i, count;
NSMutableArray *names;
NSMutableArray *hosts;
names = AUTORELEASE ([NSMutableArray new]);
hosts = AUTORELEASE ([NSMutableArray new]);
count = [receivers count];
for (i = 0; i < count; i++)
{
NSString *string;
NSDictionary *dict = (NSDictionary *)[receivers objectAtIndex: i];
if ([dict isKindOfClass: [NSDictionary class]] == NO)
{
[NSException raise: NSInvalidArgumentException format:
@"invalid initialization of EcBroadcastProxy: "
@"config dictionary is not a dictionary"];
}
string = [dict objectForKey: @"Name"];
[(NSMutableArray *)names addObject: string];
string = [dict objectForKey: @"Host"];
if (string == nil)
{
/* Would `localhost' be a better choice ? */
string = @"*";
}
[(NSMutableArray *)hosts addObject: string];
}
return [self initWithReceiverNames: names receiverHosts: hosts];
}
- (void) dealloc
{
RELEASE (receiverNames);
RELEASE (receiverHosts);
RELEASE (receiverObjects);
[nc removeObserver: self];
[super dealloc];
}
- (void) BCPraiseConnections
{
int i, count;
count = [receiverNames count];
for (i = 0; i < count; i++)
{
[self BCPraiseConnection: i];
}
}
- (void) BCPraiseConnection: (int)index
{
NSString *host = [receiverHosts objectAtIndex: index];
NSString *name = [receiverNames objectAtIndex: index];
id object = [receiverObjects objectAtIndex: index];
if (object == null)
{
NS_DURING
{
object = [NSConnection
rootProxyForConnectionWithRegisteredName: name
host: host
usingNameServer: [NSSocketPortNameServer sharedInstance]];
}
NS_HANDLER
{
NSLog(@"Caught exception trying to connect to '%@:%@' - %@\n",
host, name, localException);
object = nil;
}
NS_ENDHANDLER
if (object != nil)
{
id c = [object connectionForProxy];
[receiverObjects replaceObjectAtIndex: index withObject: object];
[nc addObserver: self
selector: @selector(BCPconnectionBecameInvalid:)
name: NSConnectionDidDieNotification
object: c];
if ([delegate respondsToSelector:
@selector(BCP:madeConnectionToServer:host:)])
{
[delegate BCP: self madeConnectionToServer: name
host: host];
}
}
}
}
- (void) BCPsetDelegate: (id)object
{
delegate = object;
}
- (id) BCPdelegate
{
return delegate;
}
- (NSArray *) BCPreceiverNames
{
return receiverNames;
}
- (NSArray *) BCPreceiverHosts
{
return receiverHosts;
}
- (int) BCPreceiverCount
{
return [receiverNames count];
}
- (id) BCPproxy: (int)index
{
id proxy;
[self BCPraiseConnection: index];
proxy = [receiverObjects objectAtIndex: index];
if (proxy == null)
{ return nil; }
else
{ return proxy; }
}
- (NSString *) BCPstatus
{
NSMutableString *output;
int i, count;
output = AUTORELEASE ([NSMutableString new]);
count = [receiverNames count];
[output appendFormat: @"EcBroadcastProxy with %d receivers:\n", count];
for (i = 0; i < count; i++)
{
NSString *host = [receiverHosts objectAtIndex: i];
NSString *name = [receiverNames objectAtIndex: i];
id object = [receiverObjects objectAtIndex: i];
[output appendFormat: @" (%d) Connection to `%@' on host `%@' is ",
i, name, host];
if (object == null)
{
[output appendString: @"DEAD\n"];
}
else
{
[output appendString: @"LIVE\n"];
}
}
[output appendString: @"\nVoid messages statistics:\n"];
[output appendFormat: @" * succesfully broadcasted: %d\n", onewayFullySent];
[output appendFormat: @" * partially broadcasted: %d\n",
onewayPartiallySent];
[output appendFormat: @" * failed to broadcast: %d\n", onewayFailed];
[output appendString: @"\nId messages statistics:\n"];
[output appendFormat: @" * succesfully broadcasted: %d\n", idFullySent];
[output appendFormat: @" * partially broadcasted: %d\n", idPartiallySent];
[output appendFormat: @" * failed to broadcast: %d\n", idFailed];
/* TODO: Should display info about the last message ? */
return output;
}
- (void) BCPforwardOneWayInvocation: (NSInvocation*)anInvocation
{
unsigned int i, count;
NSMutableArray *list;
unsigned int sent = 0;
/* Raise any pending connection */
[self BCPraiseConnections];
/* Prepare a mutable list of servers to message */
list = [NSMutableArray arrayWithArray: receiverObjects];
count = [list count];
for (i = 0; i < count; i++)
{
id receiver = [list objectAtIndex: i];
/* Check that we are connected to the server. */
if (receiver == null)
{ continue; }
/* Check in case server has died. */
if ([receiverObjects indexOfObjectIdenticalTo: receiver]
== NSNotFound)
{ continue; }
/* Send the method */
NS_DURING
{
[anInvocation invokeWithTarget: receiver];
sent++;
}
NS_HANDLER
{
/* Not sure what to do here */
NSLog (@"Caught exception trying to send event - %@\n",
localException);
}
NS_ENDHANDLER
}
/* Update statistical records */
if (sent == [receiverObjects count])
{
onewayFullySent++;
}
else if (sent == 0)
{
onewayFailed++;
}
else
{
onewayPartiallySent++;
}
if (sent != 0)
{
/* Post notification we had an output */
[nc postNotificationName: @"EcBroadcastProxyHadOPNotification"
object: self];
}
NSLog (@"Broadcasted oneway message to %d (out of %@PRIuPTR@) targets",
sent, [receiverObjects count]);
}
- (void) BCPforwardIdInvocation: (NSInvocation*)anInvocation
{
int i, count;
NSMutableArray *list;
NSMutableArray *returnArray;
unsigned int sent = 0;
/* Raise any pending connection */
[self BCPraiseConnections];
/* Prepare a mutable list of servers to message */
list = [NSMutableArray arrayWithArray: receiverObjects];
/* Prepare the return array - this will contain an entry for *each*
server. Failures in contacting a server result in the error info
being set for that server. */
returnArray = [NSMutableArray new];
count = [list count];
/* We need to make sure we keep the order */
for (i = 0; i < count; i++)
{
id receiver = [list objectAtIndex: i];
NSDictionary *dict;
id returnValue;
/* Check that we are connected to the server. */
if (receiver == null)
{
/* No server to talk to */
dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:
BCP_COULD_NOT_CONNECT],
@"error", nil];
[returnArray addObject: dict];
continue;
}
if ([receiverObjects indexOfObjectIdenticalTo: receiver]
== NSNotFound)
{
/* Server died in the meanwhile */
dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:
BCP_CONNECTION_WENT_DOWN],
@"error", nil];
[returnArray addObject: dict];
continue;
}
/* Send the method */
NS_DURING
{
[anInvocation invokeWithTarget: receiver];
[anInvocation getReturnValue: &returnValue];
/* OK - done it! */
dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt: BCP_NO_ERROR],
@"error",
returnValue, @"response", nil];
[returnArray addObject: dict];
sent++;
}
NS_HANDLER
{
/* Didn't work - messaging timeout */
NSLog (@"Caught exception trying to send event - %@\n",
localException);
dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:
BCP_MESSAGING_TIMEOUT],
@"error",
[localException description],
@"error description", nil];
[returnArray addObject: dict];
}
NS_ENDHANDLER
}
[anInvocation setReturnValue: &returnArray];
/* Update statistical records */
if (sent == [receiverObjects count])
{
idFullySent++;
}
else if (sent == 0)
{
idFailed++;
}
else
{
idPartiallySent++;
}
if (sent != 0)
{
/* Post notification we had an output */
[nc postNotificationName: @"EcBroadcastProxyHadOPNotification"
object: self];
}
NSLog (@"Broadcasted Id message to %d (out of %@PRIuPTR@) targets",
sent, [receiverObjects count]);
}
- (void) forwardInvocation: (NSInvocation*)anInvocation
{
NSMethodSignature *methodSig;
NSLog (@"ForwardInvocation: %@", anInvocation);
/* Post notification we had an input */
[nc postNotificationName: @"EcBroadcastProxyHadIPNotification" object: self];
/* Get information about the return type */
methodSig = [anInvocation methodSignature];
if ([methodSig isOneway] == YES)
{
[self BCPforwardOneWayInvocation: anInvocation];
}
else
{
const char *signature;
signature = [methodSig methodReturnType];
if (*signature == _C_VOID)
{
/* Issue a warning here ? */
[self BCPforwardOneWayInvocation: anInvocation];
}
else if (*signature == _C_ID)
{
[self BCPforwardIdInvocation: anInvocation];
}
else
{
/* We only accept (id) return types */
[NSException raise: NSInvalidArgumentException format:
@"EcBroadcastProxy can only respond to oneway methods "
@"or methods returning an object or void"];
}
}
}
- (void) BCPconnectionBecameInvalid: (NSNotification*)notification
{
id connection;
connection = [notification object];
[nc removeObserver: self
name: NSConnectionDidDieNotification
object: connection];
if ([connection isKindOfClass: [NSConnection class]])
{
unsigned int i;
/*
* Now - remove any proxy that uses this connection from the cache.
*/
for (i = 0; i < [receiverObjects count]; i++)
{
id obj = [receiverObjects objectAtIndex: i];
if (obj != null)
{
if ([obj connectionForProxy] == connection)
{
[receiverObjects replaceObjectAtIndex: i
withObject: null];
if ([delegate respondsToSelector:
@selector(BCP:lostConnectionToServer:host:)])
{
NSString *name = [receiverNames objectAtIndex: i];
NSString *host = [receiverHosts objectAtIndex: i];
[delegate BCP: self lostConnectionToServer: name
host: host];
}
}
}
}
NSLog (@"client (%p) gone away.", connection);
}
else
{
NSLog (@"non-Connection sent invalidation");
}
}
/* And now the stuff which makes it work */
- (void) BCPforwardPrivateInvocation: (NSInvocation*)anInvocation
{
/* TODO - Better stuff */
int i, count;
int nilValue = 0;
BOOL ok = NO;
/* Raise any pending connection */
[self BCPraiseConnections];
count = [receiverObjects count];
/* Look for the first not-null receiver */
for (i = 0; i < count; i++)
{
id receiver = [receiverObjects objectAtIndex: i];
if (receiver != null)
{
/* Send the method to him */
NS_DURING
{
[anInvocation invokeWithTarget: receiver];
ok =YES;
}
NS_HANDLER
{
NSLog (@"Caught exception trying to send event - %@\n",
localException);
}
NS_ENDHANDLER
if (ok == YES)
return;
/* No luck - try with the next one */
}
}
/* Eh - should perhaps raise an exception ? */
NSLog (@"Could not forward private invocation %@", anInvocation);
/* Try this trick */
[anInvocation setReturnValue: &nilValue];
}
- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector
{
/* TODO - Better stuff */
BOOL ok = NO;
NSMethodSignature *methodSig = nil;
int i, count;
/* Raise any pending connection */
[self BCPraiseConnections];
count = [receiverObjects count];
/* Look for the first not-null receiver */
for (i = 0; i < count; i++)
{
id receiver = [receiverObjects objectAtIndex: i];
if (receiver != null)
{
/* Ask to him for the method signature */
NS_DURING
{
methodSig = [receiver methodSignatureForSelector: aSelector];
ok = YES;
}
NS_HANDLER
{
NSLog (@"Caught exception trying to send event - %@\n",
localException);
}
NS_ENDHANDLER
if (ok == YES)
{
return methodSig;
}
/* No luck - try with the next one */
}
}
/* Eh - should perhaps raise an exception ? */
NSLog (@"Could not determine method signature of selector %@",
NSStringFromSelector(aSelector));
return nil;
}
- (BOOL) respondsToSelector:(SEL)aSelector
{
NSMethodSignature *ms;
NSInvocation *inv;
SEL selector = @selector (respondsToSelector:);
BOOL result;
/* Recognize standard methods */
if ([super respondsToSelector: aSelector] == YES)
return YES;
/* And remote ones - FIXME/TODO: do not respond YES to stuff which
is not returning (oneway void) or (id) */
ms = [self methodSignatureForSelector: selector];
inv = [NSInvocation invocationWithMethodSignature: ms];
[inv setSelector: selector];
[inv setTarget: self];
[inv setArgument: &aSelector atIndex: 2];
[self BCPforwardPrivateInvocation: inv];
[inv getReturnValue: &result];
return result;
}
- (BOOL) conformsToProtocol: (Protocol *)aProtocol
{
NSMethodSignature *ms;
NSInvocation *inv;
SEL selector = @selector (conformsToProtocol:);
BOOL result;
if ([super conformsToProtocol: aProtocol] == YES)
return YES;
ms = [self methodSignatureForSelector: selector];
inv = [NSInvocation invocationWithMethodSignature: ms];
[inv setSelector: selector];
[inv setTarget: self];
[inv setArgument: &aProtocol atIndex: 2];
[self BCPforwardPrivateInvocation: inv];
[inv getReturnValue: &result];
return result;
}
/* Attention - this will be forwarded as any normal method !
The return is not a NSString, but an NSArray of dictionaries with
the responses of the various servers ! */
- (NSString *) description
{
NSMethodSignature *ms;
NSInvocation *inv;
SEL selector = @selector (description);
NSString *result;
ms = [self methodSignatureForSelector: selector];
inv = [NSInvocation invocationWithMethodSignature: ms];
[inv setSelector: selector];
[inv setTarget: self];
[self BCPforwardIdInvocation: inv];
[inv getReturnValue: &result];
return result;
}
/* who cares but anyway */
- (BOOL) isProxy
{
return YES;
}
@end

79
EcHost.h Normal file
View file

@ -0,0 +1,79 @@
#import <Foundation/NSHost.h>
@class NSDictionary;
@class NSString;
/**
* <p>This category provides additional methods to standardise host names so
* that software can consistently refer to a host by a single well known
* name.<br />
* This mechanism, as well as ensuring naming consistency, can be used to
* work with logical names for hosts when the actual naming of the hosts
* (ie in the domain name system) is not under your control.
* </p>
* <p>This operates by managing a map from the various names and addresses a
* host may be known by, to a single well known name. This host name may,
* but need not, be a public domain name.
* </p>
* <p>The well known name methods are thread-safe, and on initial use the
* NSUserDefaults system is queried to set up two well known names
* automatically:<br />
* The value of EcHostCurrentName specifies the well known name for the
* current host (the machine on which the software is running).<br />
* The value of EcHostControlName specifies the well known name for the
* control host (the machine on which control functions for your software
* are centralised). If this is specified without EcHostControlDomain,
* it is ignored and the well known name of the current host is used.<br />
* The value of EcHostControlDomain specifies the fully qualified domain
* name of the control host. If it is specified without EcHostControlName,
* then it is used as the well known name for the control host.<br />
* NB. the defaults system is accessed via EcUserDefaults, so if a
* defaults prefix other than Ec has been set, these keys will use that
* alternative prefix.
* </p>
*/
@interface NSHost (EcHost)
/** Returns the well known name of the 'control' host as obtained from the
* NSUserDefaults system.<br />
* If EcHostControlName and EcHostControlDomain are both defined,
* the well known name is the string specified by EcHostControlName.<br />
* If EcHostControlDomain is defined, the well known name is the string
* specified by it.<br />
* If neither is defined, but EcHostCurrentName is defined, then the well
* known name is the string specified by that default.<br />
* Otherwise, the well known name is set to an arbitrarily selected name
* of the current machine.
*/
+ (NSString*) controlWellKnownName;
/** Returns a host previously established as having the well known name,
* or nil if no such association exists.
*/
+ (NSHost*) hostWithWellKnownName: (NSString*)aName;
/** Establishes mappings from a variety of host names (the dictionary keys)
* to well known names (the dictionary values).<br />
* The keys and values may be (and often are) identical in the case where
* the well known name for a host is the same as one of its normal names
* (eg its fully qualified domain name).<br />
* It is possible to set a well known name for a host which does not yet
* exist ... in which case the mapping will be established and will take
* effect later, when the host is set up.
*/
+ (void) setWellKnownNames: (NSDictionary*)map;
/** Sets the well known name for the receiver.<br />
* This replaces any previous well known name for the receiver and, if the
* name is already in use, removes any associations of that well known name
* with other hosts.
*/
- (void) setWellKnownName: (NSString*)aName;
/** Returns the well known name for the receiver (or any name of the receiver
* if no well known name has been set for it).
*/
- (NSString*) wellKnownName;
@end

285
EcHost.m Normal file
View file

@ -0,0 +1,285 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSString.h>
#import <Foundation/NSThread.h>
#import "EcUserDefaults.h"
#import "EcHost.h"
static NSRecursiveLock *lock = nil;
static NSMutableDictionary *fromWellKnown = nil;
static NSMutableDictionary *toWellKnown = nil;
static NSString *controlName = nil;
@implementation NSHost (EcHost)
+ (void) _EcHostSetup
{
if (nil == lock)
{
if (YES == [NSThread isMainThread])
{
NSUserDefaults *defs;
NSString *name;
NSHost *host;
NSRecursiveLock *l;
/* Create the lock locked ... until it is unlocked all other
* threads are waiting for this method to complete, and we
* can do what we like.
*/
l = [NSRecursiveLock new];
[l lock];
lock = l;
fromWellKnown = [NSMutableDictionary alloc];
toWellKnown = [NSMutableDictionary alloc];
/* Perform initial setup of control host and current host
* from user defaults system.
*/
defs = [NSUserDefaults prefixedDefaults];
if (nil == defs)
{
defs = [NSUserDefaults userDefaultsWithPrefix: nil
strict: NO];
}
name = [defs stringForKey: @"HostControlName"];
if (nil != name && nil != [defs stringForKey: @"HostControlDomain"])
{
/* Use mapping from domain name to well known name.
*/
controlName = [name copy];
name = [defs stringForKey: @"HostControlDomain"];
}
else if (nil != (name = [defs stringForKey: @"HostControlDomain"]))
{
/* Use domain name as the known name.
*/
controlName = [name copy];
}
if (nil != name)
{
host = [self hostWithName: name];
if (nil == host)
{
/* No such host ... set up name mapping in case
* the host becomes available later.
*/
[toWellKnown setObject: controlName forKey: name];
[fromWellKnown setObject: name forKey: controlName];
}
else
{
[host setWellKnownName: controlName];
}
}
host = [self currentHost];
name = [defs stringForKey: @"HostCurrentName"];
if (nil == name)
{
/* If the current host is the control host, we may have the
* well known name set already, but if we don't this method
* will select an arbitrary name that we can use.
*/
name = [host wellKnownName];
}
[host setWellKnownName: name];
if (nil == controlName)
{
/* use current host as the control host.
*/
controlName = [name copy];
}
[lock unlock];
}
else
{
NSArray *modes;
modes = [NSArray arrayWithObject: NSDefaultRunLoopMode];
[self performSelectorOnMainThread: _cmd
withObject: nil
waitUntilDone: YES
modes: modes];
}
}
}
+ (NSString*) controlWellKnownName
{
if (nil == lock) [self _EcHostSetup];
return controlName;
}
+ (NSHost*) hostWithWellKnownName: (NSString*)aName
{
NSHost *found = nil;
if (nil == lock) [self _EcHostSetup];
if (YES == [aName isKindOfClass: [NSString class]])
{
[lock lock];
aName = [[[fromWellKnown objectForKey: aName] retain] autorelease];
[lock unlock];
if (nil != aName)
{
found = [self hostWithName: aName];
}
}
return found;
}
+ (void) setWellKnownNames: (NSDictionary*)map
{
if (nil == lock) [self _EcHostSetup];
if ([map isKindOfClass: [NSDictionary class]])
{
NSEnumerator *e;
NSString *k;
e = [map keyEnumerator];
while (nil != (k = [e nextObject]))
{
NSString *v = [map objectForKey: k];
if ([k isKindOfClass: [NSString class]]
&& [v isKindOfClass: [NSString class]])
{
NSHost *h = [self hostWithName: k];
if (nil == h)
{
NSEnumerator *e;
NSString *name;
/* No such host ... set up name mapping in case
* the host becomes available later.
*/
[lock lock];
/* Remove any existing names which map to this new
* well known name.
*/
e = [[toWellKnown allKeys] objectEnumerator];
while (nil != (name = [e nextObject]))
{
NSString *wellKnown;
wellKnown = [toWellKnown objectForKey: name];
if ([wellKnown isEqualToString: v])
{
[toWellKnown removeObjectForKey: name];
}
}
/* Set up the specified mappings to and from the new
* well known name and its normal host name.
*/
[toWellKnown setObject: v forKey: k];
[fromWellKnown setObject: k forKey: v];
[lock unlock];
}
else
{
/* We have found a host with the specified names ...
* so set the well known name for it.
*/
[h setWellKnownName: v];
}
}
}
}
}
- (void) setWellKnownName: (NSString*)aName
{
if (nil == lock) [[self class] _EcHostSetup];
if ([aName isKindOfClass: [NSString class]])
{
NSEnumerator *e;
NSString *name;
[lock lock];
/* Set a mapping to this host from the well known name.
*/
name = [self name];
if (nil == name)
{
name = [self address];
}
[fromWellKnown setObject: name forKey: aName];
/* Remove any old mappings to this well known name.
*/
e = [[toWellKnown allKeys] objectEnumerator];
while (nil != (name = [e nextObject]))
{
if ([[toWellKnown objectForKey: name] isEqualToString: aName])
{
[toWellKnown removeObjectForKey: name];
}
}
/* Add mappings to the well known name from any of our names.
*/
e = [[self names] objectEnumerator];
while (nil != (name = [e nextObject]))
{
[toWellKnown setObject: aName forKey: name];
}
/* Add mappings to the well known name from any of our addresses.
*/
e = [[self addresses] objectEnumerator];
while (nil != (name = [e nextObject]))
{
[toWellKnown setObject: aName forKey: name];
}
[lock unlock];
}
}
- (NSString*) wellKnownName
{
NSString *found;
if (nil == lock) [[self class] _EcHostSetup];
[lock lock];
found = [[[toWellKnown objectForKey: [self name]] retain] autorelease];
if (nil == found)
{
NSEnumerator *e;
NSString *name;
e = [[self names] objectEnumerator];
while (nil == found && nil != (name = [e nextObject]))
{
found = [[[toWellKnown objectForKey: name] retain] autorelease];
}
if (nil == found)
{
e = [[self addresses] objectEnumerator];
while (nil == found && nil != (name = [e nextObject]))
{
found = [[[toWellKnown objectForKey: name] retain] autorelease];
}
}
}
[lock unlock];
if (nil == found)
{
found = [self name];
if (nil == found)
{
found = [self address];
}
}
return found;
}
@end

37
EcLogger.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef _ECLOGGER_H
#define _ECLOGGER_H
@interface EcLogger : NSObject <CmdPing>
{
NSRecursiveLock *lock;
NSDate *last;
NSTimer *timer;
unsigned interval;
unsigned size;
NSMutableString *message;
EcLogType type;
NSString *key;
NSString *flushKey;
NSString *serverKey;
NSString *serverName;
BOOL inFlush;
BOOL externalFlush;
BOOL registered;
BOOL pendingFlush;
}
+ (EcLogger*) loggerForType: (EcLogType)t;
- (void) cmdGnip: (id <CmdPing>)from
sequence: (unsigned)num
extra: (NSData*)data;
- (void) cmdMadeConnectionToServer: (NSString*)name;
- (void) cmdPing: (id <CmdPing>)from
sequence: (unsigned)num
extra: (NSData*)data;
- (void) flush;
- (void) log: (NSString*)fmt arguments: (va_list)args;
- (void) update;
@end
#endif

514
EcLogger.m Normal file
View file

@ -0,0 +1,514 @@
#include <Foundation/Foundation.h>
#include <Foundation/NSDebug.h>
#include "EcProcess.h"
#include "EcLogger.h"
@implementation EcLogger
static NSLock *loggersLock;
static NSMutableArray *loggers;
static NSArray *modes;
+ (void) initialize
{
if (self == [EcLogger class])
{
id objects[1];
loggersLock = [NSLock new];
loggers = [[NSMutableArray alloc] initWithCapacity: 6];
objects[0] = NSDefaultRunLoopMode;
modes = [[NSArray alloc] initWithObjects: objects count: 1];
}
}
+ (EcLogger*) loggerForType: (EcLogType)t
{
unsigned count;
EcLogger *logger;
[loggersLock lock];
count = [loggers count];
while (count-- > 0)
{
logger = [loggers objectAtIndex: count];
if (logger->type == t)
{
[loggersLock unlock];
return logger;
}
}
logger = [[EcLogger alloc] init];
if (logger != nil)
{
logger->lock = [NSRecursiveLock new];
logger->type = t;
logger->key = [cmdLogKey(t) copy];
logger->flushKey
= [[NSString alloc] initWithFormat: @"BS%@Flush", logger->key];
logger->serverKey
= [[NSString alloc] initWithFormat: @"BS%@Server", logger->key];
logger->interval = 10;
logger->size = 8 * 1024;
logger->message = [[NSMutableString alloc] initWithCapacity: 2048];
[EcProc setCmdUpdate: logger withMethod: @selector(update)];
[logger update];
[loggers addObject: logger];
RELEASE(logger);
}
[loggersLock unlock];
return logger;
}
/* Should only be called on main thread, but doesn't matter.
*/
- (void) cmdGnip: (id <CmdPing>)from
sequence: (unsigned)num
extra: (NSData*)data
{
return;
}
/*
* When connecting to a logging server, we need to register so it
* knows who we are.
* Should only be called on main thread.
*/
- (void) cmdMadeConnectionToServer: (NSString*)name
{
id<CmdLogger> server;
server = (id<CmdLogger>)[EcProc server: name];
[server registerClient: self name: cmdLogName()];
}
/* Should only be called on main thread, but doesn't matter.
*/
- (void) cmdPing: (id <CmdPing>)from
sequence: (unsigned)num
extra: (NSData*)data
{
[from cmdGnip: self sequence: num extra: nil];
}
/* Should only be called on main thread.
*/
- (oneway void) cmdQuit: (int)status
{
[EcProc cmdQuit: status];
}
- (void) dealloc
{
[self flush];
[timer invalidate];
RELEASE(key);
RELEASE(flushKey);
RELEASE(serverKey);
RELEASE(serverName);
RELEASE(message);
RELEASE(lock);
[super dealloc];
}
- (NSString*) description
{
NSMutableString *s = [NSMutableString stringWithCapacity: 256];
[lock lock];
if (size == 0)
{
[s appendFormat: @"%@ output is immediate.\n", key];
}
else if (interval > 0)
{
[s appendFormat: @"%@ flushed every %u seconds", key, interval];
[s appendFormat: @" or with a %u byte buffer.\n", size];
if (timer != nil)
{
[s appendFormat: @"Next flush - %@\n", [timer fireDate]];
}
}
else
{
[s appendFormat: @"%@ flushed with a %u byte buffer.\n", key, size];
}
[lock unlock];
return s;
}
/**
* Internal flush operation ... writes data out from us, but
* doesn't try any further.
*/
- (void) _flush
{
unsigned len;
if (inFlush == YES)
{
return;
}
[lock lock];
if (inFlush == NO && (len = [message length]) > 0)
{
BOOL ok = YES;
NSData *buf;
NSString *str;
inFlush = YES;
str = [message copy];
[message setString: @""];
[lock unlock];
buf = [str dataUsingEncoding: [NSString defaultCStringEncoding]];
if (buf == nil)
{
buf = [str dataUsingEncoding: NSUTF8StringEncoding];
}
fwrite([buf bytes], 1, [buf length], stderr);
if (LT_DEBUG != type)
{
if (nil == serverName)
{
id<CmdLogger> server;
NS_DURING
{
server = (id<CmdLogger>)[EcProc cmdNewServer];
}
NS_HANDLER
{
server = nil;
NSLog(@"Exception contacting Command server: %@\n",
localException);
}
NS_ENDHANDLER
if (server != nil)
{
NS_DURING
{
[server logMessage: str
type: type
for: EcProc];
}
NS_HANDLER
{
NSLog(@"Exception sending info to logger: %@",
localException);
ok = NO;
}
NS_ENDHANDLER
}
else
{
ok = NO;
}
}
else
{
id<CmdLogger> server;
NS_DURING
{
server = (id<CmdLogger>)
[EcProc server: serverName];
}
NS_HANDLER
{
server = nil;
NSLog(@"Exception contacting logging server: %@\n",
localException);
}
NS_ENDHANDLER
if (server != nil)
{
NS_DURING
{
[server logMessage: str
type: type
for: self];
}
NS_HANDLER
{
NSLog(@"Exception sending info to %@: %@",
serverName, localException);
ok = NO;
}
NS_ENDHANDLER
}
else
{
ok = NO;
}
}
}
if (NO == ok)
{
if (serverName == nil)
{
NSLog(@"Unable to log to Command server - %@", str);
}
else
{
NSLog(@"Unable to log to %@ - %@", serverName, str);
}
/*
* Flush any messages that might have been generated by
* (or during) our failed attempt to contact server.
*/
[lock lock];
if ([message length] > 0)
{
NSString *tmp = [message copy];
[message setString: @""];
NSLog(@"%@", tmp);
[tmp release];
}
[lock unlock];
}
RELEASE(str);
inFlush = NO;
}
else
{
[lock unlock];
}
}
- (void) _externalFlush: (id)dummy
{
if (externalFlush == NO)
{
externalFlush = YES;
[self _flush];
if (LT_DEBUG != type)
{
id<CmdLogger> server;
if (serverName == nil)
{
NS_DURING
{
server = (id<CmdLogger>)[EcProc cmdNewServer];
}
NS_HANDLER
{
server = nil;
NSLog(@"Exception contacting Command server: %@\n",
localException);
}
NS_ENDHANDLER
}
else
{
NS_DURING
{
server = (id<CmdLogger>)
[EcProc server: serverName];
}
NS_HANDLER
{
server = nil;
NSLog(@"Exception contacting logging server: %@\n",
localException);
}
NS_ENDHANDLER
}
if (server != nil)
{
NS_DURING
{
[server flush]; // Force round trip.
}
NS_HANDLER
{
NSLog(@"Exception flushing info to %@: %@",
serverName, localException);
}
NS_ENDHANDLER
}
}
externalFlush = NO;
}
}
/**
* External flush operation ... writes out data and asks any server
* we write to to flush its data out too.
*/
- (void) flush
{
[self performSelectorOnMainThread: @selector(_externalFlush:)
withObject: nil
waitUntilDone: YES
modes: modes];
}
- (void) _scheduleFlush: (id)reset
{
/* A non-nil value of reset means that we should reset to do
* a flush real soon.
*/
[lock lock];
if (reset != nil)
{
if (timer != nil && [[timer fireDate] timeIntervalSinceNow] > 0.001)
{
[timer invalidate];
timer = nil;
}
if (timer == nil)
{
timer = [NSTimer scheduledTimerWithTimeInterval: 0.0001
target: self selector: @selector(_timeout:)
userInfo: nil repeats: NO];
}
}
else if ([message length] >= size)
{
/*
* Buffer too large - schedule immediate flush.
*/
if (timer != nil && [[timer fireDate] timeIntervalSinceNow] > 0.001)
{
[timer invalidate];
timer = nil;
}
if (timer == nil)
{
timer = [NSTimer scheduledTimerWithTimeInterval: 0.0001
target: self selector: @selector(_timeout:)
userInfo: nil repeats: NO];
}
}
else if (interval > 0 && timer == nil)
{
/*
* No timer running - so schedule one to output the debug info.
*/
timer = [NSTimer scheduledTimerWithTimeInterval: interval
target: self selector: @selector(_timeout:)
userInfo: nil repeats: NO];
}
[lock unlock];
}
- (void) log: (NSString*)fmt arguments: (va_list)args
{
CREATE_AUTORELEASE_POOL(arp);
NSString *format;
NSString *text;
format = cmdLogFormat(type, fmt);
text = [NSString stringWithFormat: format arguments: args];
[lock lock];
if (message == nil)
{
message = [[NSMutableString alloc] initWithCapacity: 1024];
}
[message appendString: text];
if ([message length] >= size || (interval > 0 && timer == nil))
{
[self performSelectorOnMainThread: @selector(_scheduleFlush:)
withObject: nil
waitUntilDone: NO
modes: modes];
}
[lock unlock];
RELEASE(arp);
}
/* Should only be called on main thread.
*/
- (void) _timeout: (NSTimer*)t
{
if (t == nil)
{
[timer invalidate];
}
timer = nil;
[self _flush];
}
/* Should only be called on main thread.
*/
- (void) update
{
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
NSString *str;
/*
* If there is a server specified for this debug logger, we want it
* registered so we can use it - otherwise we will default to using
* the Command server for logging.
*/
str = [defs stringForKey: serverKey];
if ([str isEqual: @""] == YES)
{
str = nil; // An empty string means no server is used.
}
if ([serverName isEqual: str] == NO)
{
if (serverName != nil)
{
[EcProc removeServerFromList: serverName];
}
ASSIGN(serverName, str);
if (serverName != nil)
{
[EcProc addServerToList: serverName for: self];
}
}
/*
* Is the program to flush at intervals or at
* a particular buffer size (or both)?
*/
str = [defs stringForKey: flushKey];
if (str == nil)
{
str = [defs stringForKey: @"BSDefaultFlush"]; // Default settings.
if (str == nil)
{
str = [defs stringForKey: @"BSDebugFlush"]; // Backward compat.
}
}
if (str != nil)
{
NSScanner *scanner = [NSScanner scannerWithString: str];
int i;
if ([scanner scanInt: &i] == YES)
{
if (i < 0)
interval = 0;
else
interval = i;
}
if (([scanner scanString: @":" intoString: 0] == YES)
&& ([scanner scanInt: &i] == YES))
{
if (i < 0)
size = 0;
else
size = i*1024;
}
/*
* Ensure new values take effect real soon.
*/
[self performSelectorOnMainThread: @selector(_scheduleFlush:)
withObject: self
waitUntilDone: NO
modes: modes];
}
}
@end

662
EcProcess.h Normal file
View file

@ -0,0 +1,662 @@
#ifndef INCLUDED_ECPROCESS_H
#define INCLUDED_ECPROCESS_H
/**
<chapter>
<heading>The EcProcess class</heading>
<p>
The EcProcess class provides basic configuration, control, logging,
and inter-process connection systems for servers processors.
</p>
<section>
<heading>Configuration options</heading>
<p>
This class understands two groups of configuration options ...
those that take effect at process startup, and must therefore
be supplied on the command-line or in the defaults system, and
those which can take effect at any time, in which case the
values supplied by the configuration system will take
precedence over command line and defaults database settings.
</p>
<subsect>
<heading>startup settings</heading>
<deflist>
<term>EcDebug-</term>
<desc>
Any key of the form EcDebug-xxx turns on the xxx debug level
on program startup.
</desc>
<term>EcMemory</term>
<desc>
This boolean value determines whether statistics on creation
and destruction of objects are maintained.<br />
This may be set on the command line or in Control.plist, but
may be overridden by using the 'release' command in the
Console program.
</desc>
<term>EcRelease</term>
<desc>
This boolean value determines whether checks for memory problems
caused by release an object too many times are done. Turning
this on has a big impact on program performance and is not
recommended except for debugging crashes and other memory
issues.<br />
This may be set on the command line or in Control.plist, but
may be overridden by using the 'release' command in the
Console program.
</desc>
<term>EcTesting</term>
<desc>
This boolean value determines whether the server is running in
test mode (which may enable extra logging or prevent the server
from comm,unicating with live systems etc ... the actual
behavior is server dependent).<br />
This may be set on the command line or in Control.plist, but
may be overridden by using the 'testing' command in the
Console program.
</desc>
<term>EcTransient</term>
<desc>
This boolean option is used to specify that the program
should not be restarted automatically by the Command
server if/when it disconnects from that server.
</desc>
</deflist>
</subsect>
</section>
</chapter>
*/
#import <Foundation/Foundation.h>
#import "EcAlarmDestination.h"
@class NSFileHandle;
typedef enum {
LT_DEBUG, /* Debug message - internal info. */
LT_WARNING, /* Warning - possibly a real problem. */
LT_ERROR, /* Error - needs dealing with. */
LT_AUDIT, /* Not a problem but needs logging. */
LT_ALERT, /* Severe - needs immediate attention. */
} EcLogType;
/**
* A 'ping' can be sent from or to any process to check that it is alive.
* A 'gnip' should be sent in response to each 'ping'
* Each 'ping' carries a sequence number which should be echoed in its 'gnip'.
* Optionally, the 'ping' can also transport a serialized property-list
* to provide additional data or instructions - the value of which is
* dependent on the program being pinged - normally this value is nil.
*/
@protocol CmdPing
- (oneway void) cmdGnip: (id <CmdPing>)from
sequence: (unsigned)num
extra: (in bycopy NSData*)data;
- (oneway void) cmdPing: (id <CmdPing>)from
sequence: (unsigned)num
extra: (in bycopy NSData*)data;
@end
/** The CmdConfig protocol is needed by objects that send and receive
* configuarion information.
*/
@protocol CmdConfig
- (oneway void) requestConfigFor: (id<CmdConfig>)c;
- (oneway void) updateConfig: (in bycopy NSData*)info;
@end
/** Messages that the Command server may send to clients.
*/
@protocol CmdClient <CmdPing,CmdConfig>
- (oneway void) cmdMesgData: (in bycopy NSData*)dat from: (NSString*)name;
- (oneway void) cmdQuit: (int)status;
@end
/** Messages a Command logging process can be expected to handle.
*/
@protocol CmdLogger <CmdClient>
- (void) flush;
- (oneway void) logMessage: (NSString*)msg
type: (EcLogType)t
name: (NSString*)c;
- (oneway void) logMessage: (NSString*)msg
type: (EcLogType)t
for: (id)o;
- (bycopy NSData*) registerClient: (id)c
name: (NSString*)n;
- (void) unregisterByObject: (id)obj;
- (void) unregisterByName: (NSString*)n;
@end
@protocol Console <NSObject>
- (oneway void) information: (NSString*)txt;
@end
/**
* Messages that clients may send to the server.
* NB. The 'registerClient...' message must be sent first.
*/
@protocol Command <CmdLogger,CmdConfig,EcAlarmDestination>
- (oneway void) alarm: (in bycopy EcAlarm*)alarm;
- (oneway void) command: (in bycopy NSData*)dat
to: (NSString*)t
from: (NSString*)f;
- (bycopy NSData*) registerClient: (id<CmdClient>)c
name: (NSString*)n
transient: (BOOL)t;
- (oneway void) reply: (NSString*)msg
to: (NSString*)n
from: (NSString*)c;
/** Shut down the Command server and all its clients */
- (oneway void) terminate;
/** An exceptional method and can be used without registering first
* (ie, can be used by anyone, not only clients of the Command server).
* It's meant to be used remotely by java servlets, and all sort of
* software running on the machine and which is *not* a full Command
* client (ie, a subclass of EcProcess) but which still wants to retrieve
* configuration from a central location (the Control/Command servers).
* NB: The configuration might change later on, so you must not cache
* the configuration after asking for it, but rather ask for it each
* time your software needs it.
*/
- (bycopy NSData *) configurationFor: (NSString *)name;
@end
/*
* Messages that clients may send to the server.
*/
@protocol Control <CmdPing,CmdConfig,EcAlarmDestination>
- (oneway void) alarm: (in bycopy EcAlarm*)alarm;
- (oneway void) command: (in bycopy NSData*)cmd
from: (NSString*)f;
- (oneway void) domanage: (NSString*)name;
- (oneway void) information: (NSString*)msg
type: (EcLogType)t
to: (NSString*)to
from: (NSString*)from;
- (bycopy NSData*) registerCommand: (id<Command>)c
name: (NSString*)n;
- (bycopy NSString*) registerConsole: (id<Console>)c
name: (NSString*)n
pass: (NSString*)p;
- (oneway void) reply: (NSString*)msg
to: (NSString*)n
from: (NSString*)c;
- (oneway void) servers: (in bycopy NSData*)a
on: (id<Command>)s;
- (oneway void) unmanage: (NSString*)name;
- (void) unregister: (id)o;
@end
/*
* Useful functions -
*/
extern void cmdSetHome(NSString *home);
extern NSString* cmdLogsDir(NSString *date);
extern NSString* cmdLogKey(EcLogType t);
extern NSString* cmdLogName();
extern NSString* cmdLogFormat(EcLogType t, NSString *fmt);
/* Return value of lat signal received, or zero.
*/
extern int cmdSignalled();
/* Set/get version/compilation date.
*/
extern NSString* cmdVersion(NSString *ver);
/**
* Command line arguments -
*
*
* <p>On startup, these are taken from the command line, or from the
* local user defaults database of the person running the program.<br />
* If the EcEffectiveUser specifies an alternative user, or the
* program is able to read the database for the 'ecuser' user, then
* the other values are read from the defaults database of that user.
* </p>
* <p>After startup, the command line arguments still take precedence,
* but values retrieved from the network configuration system will
* then override any read from the local user defaults database.
* </p>
* <p>Settings in the network configuration system will have no effect on
* the following defaults which are used BEFORE the network configuration
* can be read.
* </p>
* <deflist>
* <term>EcDaemon</term>
* <desc>To specify whether the program should run in the background
* (boolean, YES if the program is to run as a daemon, NO otherwise).<br />
* The value in the network configuration has no effect.
* </desc>
* <term>EcEffectiveUser</term>
* <desc>To tell the server to change to being this user on startup.
* defaults to 'ecuser', but the default can be overridden by specifying
* '-DEC_EFFECTIVE_USER+@"username"' in the local.make make file.<br />
* Set a value of '' or '*' to remain whoever runs the program
* rather than changing.
* </desc>
* <term>EcInstance</term>
* <desc>To set the program instance ID (an arbitrary string).<br />
* If this is specified, the program name has a hyphen and the
* id appended to it by the '-initWithDefaults:' method.
* </desc>
* </deflist>
* <p>The following settings will be revised after startup to include the
* values from the network configuration system.
* </p>
* <deflist>
* <term>EcAuditLocal</term>
* <desc>A boolean used to specify that audit information should
* be logged locally rather than sending it to be logged centrally.<br />
* Default value is NO.
* </desc>
* <term>EcAuditFlush</term>
* <desc>A flush interval in seconds (optionally followed by a colon
* and a buffer size in KiloBytes) to control flushing of audit logs.<br />
* Setting an interval of zero or less disables flushing by timer.<br />
* Setting a size of zero or less, disables buffering (so logs are
* flushed immediately).
* </desc>
* <term>EcDebug-XXX</term>
* <desc>A boolean used to ensure that the debug mode named 'XXX' is either
* turned on or turned off. The value of 'XXX' must match
* the name of a debug mode used by the program!
* </desc>
* <term>EcDebugLocal</term>
* <desc>A boolean used to specify that debug information should
* be logged locally rather than sending it to be logged centrally.<br />
* Default value is YES.
* </desc>
* <term>EcDebugFlush</term>
* <desc>A flush interval in seconds (optionally followed by a colon
* and a buffer size in KiloBytes) to control flushing of debug logs.<br />
* Setting an interval of zero or less disables flushing by timer.<br />
* Setting a size of zero or less, disables buffering (so logs are
* flushed immediately).
* </desc>
* <term>EcMemory</term>
* <desc>A boolean used to ensure that monitoring of memory allocation is
* turned on or turned off.
* </desc>
* <term>EcRelease</term>
* <desc>A boolean used to specify whether the program should perform
* sanity checks for retain/release combinations. Slows things down a lot!
* </desc>
* <term>EcTesting</term>
* <desc>A boolean used to specify whether the program is running
* in test mode or not.
* </desc>
* <term>EcWellKnownHostNames</term>
* <desc>A dictionary mapping host names/address values to well known
* names (the canonical values used by Command and Control).
* </desc>
* </deflist>
*/
@interface EcProcess : NSObject <CmdClient,EcAlarmDestination>
/** Return a short copyright notice ... subclasses should override.
*/
- (NSString*) prcCopyright;
/* Call these methods during initialisation of your instance
* to set up automatic management of connections to servers.
* You then access the servers by calling -(id)server: (NSString*)serverName.
*/
- (void) addServerToList: (NSString*)serverName;
- (void) addServerToList: (NSString*)serverName for: (id)anObject;
- (void) removeServerFromList: (NSString*)serverName;
/** Send a SEVERE error message to the server.
*/
- (void) cmdAlert: (NSString*)fmt, ...;
/** Archives debug log files into the specified subdirectory of the debug
* logging directory. If subdir is nil then a subdirectory name corresponding
* to the current date is generated and that subdirectory is created if
* necessary.
*/
- (NSString*) cmdArchive: (NSString*)subdir;
/** Send a log message to the server.
*/
- (void) cmdAudit: (NSString*)fmt, ...;
/** Handles loss of connection to the server.
*/
- (id) cmdConnectionBecameInvalid: (NSNotification*)notification;
/** Returns the path to the data storage directory used by this process
* to store files containing persistent information.
*/
- (NSString*) cmdDataDirectory;
/** Send a debug message - as long as the debug mode specified as 'type'
* is currently set.
*/
- (void) cmdDbg: (NSString*)type msg: (NSString*)fmt, ...;
/** Send a debug message with debug mode 'defaultMode'.
*/
- (void) cmdDebug: (NSString*)fmt, ...;
/** Called whenever the user defaults are updated due to a central
* configuration change (or another defaults system change).<br />
* If you override this to handle configuration changes, don't forget
* to call the superclass implementation.
*/
- (void) cmdDefaultsChanged: (NSNotification*)n;
/** Send an error message to the server.
*/
- (void) cmdError: (NSString*)fmt, ...;
/** Flush logging information
*/
- (void) cmdFlushLogs;
/** This message returns YES if the receiver is intended to be a client
* of a Command server, and NO if it is a standalone process which does
* not need to contact the Command server.<br />
* The default implementation returns YES, but subclasses may override
* this method to return NO if they do not wish to contact the Command
* server.
*/
- (BOOL) cmdIsClient;
/** Returns a fag indicating whether this process is currently connected
* it its Command server.
*/
- (BOOL) cmdIsConnected;
/** Returns YES is the process is running in test mode, NO otherwise.
*/
- (BOOL) cmdIsTesting;
/** Closes a file handle previously obtained using the -cmdLogFile: method.
* You should not close a logging handle directly, use this method.
*/
- (void) cmdLogEnd: (NSString*)name;
/** Obtain a file handle for logging purposes. The file will have the
* specified name and will be created (if necessary) in the processes
* logging directory.<br />
* If there is already a handle for the specified file, this method
* returns the existing handle rather than creating a new one.<br />
* Do not close this file handle other than by calling the -cmdLogEnd: method.
*/
- (NSFileHandle*) cmdLogFile: (NSString*)name;
/** Used by the Command server to send messages to your application.
*/
- (void) cmdMesgData: (NSData*)dat from: (NSString*)name;
/** This method is called whenever the Command server sends an instruction
* for your Command client process to act upon. Often the command has been
* entered by an operator and you need to respond with a text string.<br />
* To implement support for an operator command, you must write a method
* whose name is of the form -cmdMesgXXX: where XXX is the command (a
* lowercase string) that the operator will enter. This method must accept
* a single NSArray object as an argument and must return a readable string
* as a result.<br />
* The array argument will contain the words in the command line entered
* by the operator (with the command itsself as the first item in the
* array).<br />
* There are two special cases ... when the operator types 'help XXX' your
* method will be called with 'help'as the first element of the array and
* you should respond with some useful hep text, and when the operator
* simply wants a short description of what the command does, the array
* argument will be nil (and your method should respond with a short
* description of the cmmand).
*/
- (NSString*) cmdMesg: (NSArray*)msg;
/** Attempt to establish connection to Command server etc.
* Return a proxy to that server if it is available.
*/
- (id) cmdNewServer;
/** Return dictionary giving info about specified operator. If the
* password string matches the password of the operator (or the operator
* has no password) then the dictionary field @"Password" will be set to
* @"yes", otherwise it will be @"no".
* If the named operator does not exist, the method will return nil.
*/
- (NSMutableDictionary*)cmdOperator: (NSString*)name password: (NSString*)pass;
/** Used to tell your application to quit.
*/
- (void) cmdQuit: (int)status;
/** Used to tell your application about configuration changes.
*/
- (void) cmdUpdate: (NSMutableDictionary*)info;
- (void) log: (NSString*)message type: (EcLogType)t;
/** Send a warning message to the server.
*/
- (void) cmdWarn: (NSString*)fmt, ...;
/* Return interval between timeouts.
*/
- (NSTimeInterval) cmdInterval;
- (void) cmdUnregister;
/*
* All this to unregister from the server.
*/
/** Register a debug mode 'mode'
*/
- (void) setCmdDebug: (NSString*)mode withDescription: (NSString*)desc;
/* Sets the interval between timeouts.
*/
- (void) setCmdInterval: (NSTimeInterval)interval;
/** Specify a handler method to be invoked after each timeout to let you
* perform additional tasks.
*/
- (void) setCmdTimeout: (SEL)sel;
/** Specify an object 'obj' to be sent a message 'sel' when the
* network configuration information for this process has been changed.
* If 'sel' is a nul selector, then the specified object is removed
* from the list of objects to be notified. The EcProces class will
* retain the object given.
*/
- (void) setCmdUpdate: (id)obj withMethod: (SEL)sel;
/*
* Trigger a timeout to go off as soon as possible ... subsequent timeouts
* go off at the normal interval after that one.
*/
- (void) triggerCmdTimeout;
/** Obtains the configuration value for the specified key from the
* NSUserDefaults system (as modified by the Command server).<br />
* If you need more than one value, or if you want a typed values,
* you should call -cmdDefaults to get the defaults object, and then
* call methods of that object directly.
*/
- (id) cmdConfig: (NSString*)key;
/** Check to see if a debug mode is active.
*/
- (BOOL) cmdDebugMode: (NSString*)mode;
/** Set a particular (named) debug mode to be active or inactive.
*/
- (void) cmdDebugMode: (NSString*)mode active: (BOOL)flag;
/** Returns the NSUserDefaults instance containing the configuration
* information for this process.
*/
- (NSUserDefaults*) cmdDefaults;
/** Utility method to perform partial (case insensitive) matching of
* an abbreviated command word (val) to a keyword (key)
*/
- (BOOL) cmdMatch: (NSString*)val toKey: (NSString*)key;
/** Handle with care - this method invokes the cmdMesg... methods.
*/
- (NSString*) cmdMesg: (NSArray*)msg;
/** Retrurns the name by which this process is known to the Command server.
*/
- (NSString*) cmdName;
/** May be used withing cmdMesg... methods to return formatted text to
* the Console.
*/
- (void) cmdPrintf: (NSString*)fmt, ...;
/** Should be over-ridden to perform extra tidy up on shutdown of the
* process - should call [super cmdQuit:...] at the end of the method.
*/
- (void) cmdQuit: (int)status;
/** Used to tell your application about configuration changes (the
* default implementation merges the configuration change into the
* NSUserDefaults system and sends the defaults change notification).<br />
* If you want to deal with configuration changes actively - override
* this and call [super cmdUpdate:...] to install the changed
* configuration before anything else.
* NB. This method WILL be called before your application is
* initialised. Make sure it is safe.
*/
- (void) cmdUpdate: (NSMutableDictionary*)info;
/** [-initWithDefaults:] is the Designated initialiser<br />
* It adds the defaults specified to the defaults system.
* It sets the process name to be that specified in the
* 'EcProgramName' default with an '-id' affix if EcInstance is used
* to provide an instance id.
* Moves to the directory (relative to the current user's home directory)
* given in 'EcHomeDirectory'.
* If 'EcHomeDirectory' is not present in the defaults system (or is
* an empty string) then no directory change is done.
*/
- (id) initWithDefaults: (NSDictionary*)defs;
/*
* How commands sent to the client via cmdMesg: are invoked -
*
* 1. If there is an action registered using [-setCmdAction:name] whose
* name unambiguously matches the command given, then the specified
* selector is used.
*
* 2. If a method exists whose name is 'cmdMesgfoo:' where 'foo' is the
* command given, then that method is registered and invoked.
*
* The action methods should use the [-cmdPrintf:] method repeatedly to
* add text to be returned to the caller.
*
* By default the method 'cmdMesghelp:' is defined to handle the 'help'
* command. To extend the help facility - invoke [super cmdMesghelp:]
* at the start of your own implementation.
* NB. This method may call any other 'cmdMesg...:' method passing it
* an array with the first parameter set to be the string 'help'.
* In this case the method should use 'cmdLine:' to return it's help
* information.
*
* We also have 'cmdMesgdebug:' to activate debug logging and
* 'cmdMesgnodebug:' to deactivate it. The known debug modes may
* be extended by using the 'setCmdDebug:withDescription:' method.
*
* 'cmdMesgmemory:' is for reporting process memory allocation stats -
* it should be overridden to give detailed info.
*
* 'cmdMesgstatus:' is for reporting process status information and
* should be overridden to give detailed info.
*
* 'cmdMesgarchive:' forces an archive of the debug log.
*/
- (void) cmdMesgarchive: (NSArray*)msg;
- (void) cmdMesgdebug: (NSArray*)msg;
- (void) cmdMesghelp: (NSArray*)msg;
- (void) cmdMesgmemory: (NSArray*)msg;
- (void) cmdMesgnodebug: (NSArray*)msg;
- (void) cmdMesgstatus: (NSArray*)msg;
/*
* Returns a proxy object to a[n automatically managed] server
*/
- (id) server: (NSString *)serverName;
/*
* Like server:, but if the configuration contains a multiple servers,
* this tries to locate the specific server that is set up to deal with
* users where the last two digits of the phone number are as specified.
*/
- (id) server: (NSString *)serverName forNumber: (NSString*)num;
/*
* Standard servers return NO to the following. But if we are using
* a multiple/broadcast server, this returns YES.
*/
- (BOOL) isServerMultiple: (NSString *)serverName;
/** Records the timestamp of the latest significant input for this process.
* If when is nil the current timestmp is used.
*/
- (void) prcHadIP: (NSDate*)when;
/** Records the timestamp of the latest significant output for this process.
* If when is nil the current timestmp is used.
*/
- (void) prcHadOP: (NSDate*)when;
/** Called on the first timeout of a new day.<br />
* The argument 'when' is the timestamp of the timeout.
*/
- (void) prcNewDay: (NSCalendarDate*)when;
/** Called on the first timeout of a new hour.<br />
* The argument 'when' is the timestamp of the timeout.
*/
- (void) prcNewHour: (NSCalendarDate*)when;
/** Called on the first timeout of a new minute.<br />
* The argument 'when' is the timestamp of the timeout.
*/
- (void) prcNewMinute: (NSCalendarDate*)when;
/** Return heap memory known not to be leaked ... for use in internal
* monitoring of memory usage. You should override this ti add in any
* heap store you have used and know is not leaked.
*/
- (NSUInteger) prcNotLeaked;
/** Establishes the receiver as a DO server and runs the runloop.<br />
* Returns zero when the run loop completes.<br />
* Returns one (immediately) if the receiver is transent.<br />
* Returns two if unable to register as a DO server.
*/
- (int) prcRun;
@end
@interface NSObject (RemoteServerDelegate)
- (void) cmdMadeConnectionToServer: (NSString *)serverName;
- (void) cmdLostConnectionToServer: (NSString *)serverName;
@end
extern EcProcess *EcProc; /* Single instance ir nil */
extern NSString* cmdConnectDbg; /* Debug connection attempts. */
extern NSString* cmdDefaultDbg; /* Debug normal stuff. */
extern NSString* cmdDetailDbg; /* Debug stuff in more detail. */
#endif /* INCLUDED_ECPROCESS_H */

3594
EcProcess.m Normal file

File diff suppressed because it is too large Load diff

52
EcUserDefaults.h Normal file
View file

@ -0,0 +1,52 @@
#import <Foundation/NSUserDefaults.h>
@class NSString;
/**
* This category simply provides an easy way to work with user defaults
* where you want all keys to share a common prefix.
*/
@interface NSUserDefaults (EcUserDefaults)
/** Returns the latest prefixed version of the shared user defaults,
* or nil if none has been set up.
*/
+ (NSUserDefaults*) prefixedDefaults;
/** Returns a proxy to the shared user defaults instance, which will use
* aPrefix at the start of every key.<br />
* If aPrefix is nil, the string given by the EcUserDefaultsPrefix user
* default is used.<br />
* If the enforcePrefix flag is YES, the prefix is strictly enforced,
* otherwise the system will read defaults using the unprefixed key
* if no value is found for the prefixed key.
*/
+ (NSUserDefaults*) userDefaultsWithPrefix: (NSString*)aPrefix
strict: (BOOL)enforcePrefix;
/** Returns the prefix used by the receiver, or nil if no prefix is in use.
*/
- (NSString*) defaultsPrefix;
/** Convenience method to prepend the pefix to the supplied aKey value
* if it is not already present.
*/
- (NSString*) key: (NSString*)aKey;
/** Sets a value to take precedence over others (in the volatile domain
* reserved for commands issued to the current process by an operator).<br />
* Returns YES if the configuration was changed, NO otherwise.
*/
- (BOOL) setCommand: (id)val forKey: (NSString*)key;
/** Replaces the system central configuration information for this process
* with the contents of the dictionary. Values in this dictionary take
* precedence over other configured values except for those set using the
* -setCommand:forKey: method.<br />
* Returns YES if the configuration changed, NO otherwise.
*/
- (BOOL) setConfiguration: (NSDictionary*)config;
@end

347
EcUserDefaults.m Normal file
View file

@ -0,0 +1,347 @@
#import <Foundation/NSInvocation.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSMethodSignature.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSProxy.h>
#import <Foundation/NSString.h>
#import "EcUserDefaults.h"
static NSUserDefaults *latest = nil;
static NSLock *lock = nil;
@interface EcUserDefaults : NSProxy
{
NSUserDefaults *defs;
NSString *prefix;
BOOL enforce;
}
- (id) initWithPrefix: (NSString*)p strict: (BOOL)s;
- (NSString*) _getKey: (NSString*)baseKey;
@end
@implementation EcUserDefaults
+ (void) initialize
{
if (nil == lock)
{
lock = [NSLock new];
}
}
- (NSArray*) arrayForKey: (NSString*)aKey
{
return [defs arrayForKey: [self _getKey: aKey]];
}
- (BOOL) boolForKey: (NSString*)aKey
{
return [defs boolForKey: [self _getKey: aKey]];
}
- (NSData*) dataForKey: (NSString*)aKey
{
return [defs dataForKey: [self _getKey: aKey]];
}
- (void) dealloc
{
[lock lock];
if (latest == (NSUserDefaults*)self)
{
latest = nil;
}
[lock unlock];
[prefix release];
[defs release];
[super dealloc];
}
- (NSString*) defaultsPrefix
{
return prefix;
}
- (NSDictionary*) dictionaryForKey: (NSString*)aKey
{
return [defs dictionaryForKey: [self _getKey: aKey]];
}
#if 0
- (double) doubleForKey: (NSString*)aKey
{
return [defs doubleForKey: [self _getKey: aKey]];
}
#endif
- (float) floatForKey: (NSString*)aKey
{
return [defs floatForKey: [self _getKey: aKey]];
}
- (void) forwardInvocation: (NSInvocation*)anInvocation
{
[anInvocation invokeWithTarget: defs];
}
- (NSString*) _getKey: (NSString*)aKey
{
/* Make sure we have the prefix.
*/
if (nil != prefix)
{
if (NO == [aKey hasPrefix: prefix])
{
aKey = [prefix stringByAppendingString: aKey];
}
if (NO == enforce && nil == [defs objectForKey: aKey])
{
/* Nothing found for key ... try without the prefix.
*/
aKey = [aKey substringFromIndex: [prefix length]];
}
}
return aKey;
}
- (id) init
{
[self release];
return nil;
}
- (id) initWithPrefix: (NSString*)p strict: (BOOL)s
{
NSMutableArray *list;
[lock lock];
enforce = s;
defs = [[NSUserDefaults standardUserDefaults] retain];
if (0 == [p length])
{
p = [defs stringForKey: @"EcUserDefaultsPrefix"];
if (0 == [p length])
{
p = nil;
}
}
prefix = [p copy];
/* Make sure the defaults database has our special domains at the start
* of the search list and in the correct order.
*/
list = [[defs searchList] mutableCopy];
[list removeObject: @"EcCommand"];
[list removeObject: @"EcConfiguration"];
[list insertObject: @"EcCommand" atIndex: 0];
[list insertObject: @"EcConfiguration" atIndex: 1];
[defs setSearchList: list];
[list release];
latest = (NSUserDefaults*)self;
[lock unlock];
return self;
}
- (NSInteger) integerForKey: (NSString*)aKey
{
return [defs integerForKey: [self _getKey: aKey]];
}
- (NSString*) key: (NSString*)aKey
{
/* Make sure we have the prefix.
*/
if (nil != prefix && NO == [aKey hasPrefix: prefix])
{
aKey = [prefix stringByAppendingString: aKey];
}
return aKey;
}
- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector
{
if (class_respondsToSelector(object_getClass(self), aSelector))
{
return [super methodSignatureForSelector: aSelector];
}
return [defs methodSignatureForSelector: aSelector];
}
- (id) objectForKey: (NSString*)aKey
{
return [defs objectForKey: [self _getKey: aKey]];
}
- (void) removeObjectForKey: (NSString*)aKey
{
return [defs removeObjectForKey: [self _getKey: aKey]];
}
- (void) setBool: (BOOL)value forKey: (NSString*)aKey
{
[defs setBool: value forKey: [self key: aKey]];
}
- (BOOL) setCommand: (id)val forKey: (NSString*)key
{
return [defs setCommand: val forKey: [self key: key]];
}
#if 0
- (void) setDouble: (double)value forKey: (NSString*)aKey
{
[defs setDouble: value forKey: [self key: aKey]];
}
#endif
- (void) setFloat: (float)value forKey: (NSString*)aKey
{
[defs setFloat: value forKey: [self key: aKey]];
}
- (void) setInteger: (NSInteger)value forKey: (NSString*)aKey
{
[defs setInteger: value forKey: [self key: aKey]];
}
- (void) setObject: (id)anObject forKey: (NSString*)aKey
{
[defs setObject: anObject forKey: [self key: aKey]];
}
- (NSArray*) stringArrayForKey: (NSString*)aKey
{
return [defs stringArrayForKey: [self _getKey: aKey]];
}
- (NSString*) stringForKey: (NSString*)aKey
{
return [defs stringForKey: [self _getKey: aKey]];
}
- (NSUserDefaults*) target
{
return defs;
}
@end
@implementation NSUserDefaults (EcUserDefaults)
+ (NSUserDefaults*) prefixedDefaults
{
NSUserDefaults *defs = nil;
if (Nil != [EcUserDefaults class])
{
[lock lock];
defs = [latest retain];
[lock unlock];
}
return [defs autorelease];
}
+ (NSUserDefaults*) userDefaultsWithPrefix: (NSString*)aPrefix
strict: (BOOL)enforcePrefix
{
return (NSUserDefaults*)[[[EcUserDefaults alloc] initWithPrefix:
aPrefix strict: enforcePrefix] autorelease];
}
- (NSString*) defaultsPrefix
{
return nil; // No prefix in use ... this is not a proxy
}
- (NSString*) key: (NSString*)aKey
{
NSString *prefix = [self defaultsPrefix];
/* Make sure we have the prefix.
*/
if (nil != prefix && NO == [aKey hasPrefix: prefix])
{
aKey = [prefix stringByAppendingString: aKey];
}
return aKey;
}
- (BOOL) setCommand: (id)val forKey: (NSString*)key
{
NSDictionary *old = [self volatileDomainForName: @"EcCommand"];
NSDictionary *new = nil;
NSString *pre = [self defaultsPrefix];
/* Make sure prefix is used if we have one set.
*/
if (nil != pre)
{
if (NO == [key hasPrefix: pre])
{
key = [pre stringByAppendingString: key];
}
}
if (nil == val)
{
if (nil != [old objectForKey: key])
{
new = [old mutableCopy];
[new removeObjectForKey: key];
}
}
else
{
if (NO == [val isEqual: [old objectForKey: key]])
{
new = [old mutableCopy];
if (nil == new)
{
new = [NSMutableDictionary new];
}
[new setObject: val forKey: key];
}
}
if (nil != new)
{
if (nil != old)
{
[self removeVolatileDomainForName: @"EcCommand"];
}
[self setVolatileDomain: new forName: @"EcCommand"];
[new release];
[[NSNotificationCenter defaultCenter] postNotificationName:
NSUserDefaultsDidChangeNotification object: self];
return YES;
}
return NO;
}
- (BOOL) setConfiguration: (NSDictionary*)config
{
NSDictionary *old = [self volatileDomainForName: @"EcConfiguration"];
BOOL changed = NO;
if (NO == [old isEqual: config])
{
[self removeVolatileDomainForName: @"EcConfiguration"];
changed = YES;
}
if (nil != config)
{
[self setVolatileDomain: config forName: @"EcConfiguration"];
changed = YES;
}
if (YES == changed)
{
[[NSNotificationCenter defaultCenter] postNotificationName:
NSUserDefaultsDidChangeNotification
object: self];
}
return changed;
}
@end

99
GNUmakefile Normal file
View file

@ -0,0 +1,99 @@
ifeq ($(GNUSTEP_MAKEFILES),)
GNUSTEP_MAKEFILES := $(shell gnustep-config --variable=GNUSTEP_MAKEFILES 2>/dev/null)
ifeq ($(GNUSTEP_MAKEFILES),)
$(warning )
$(warning Unable to obtain GNUSTEP_MAKEFILES setting from gnustep-config!)
$(warning Perhaps gnustep-make is not properly installed,)
$(warning so gnustep-config is not in your PATH.)
$(warning )
$(warning Your PATH is currently $(PATH))
$(warning )
endif
endif
ifeq ($(GNUSTEP_MAKEFILES),)
$(error You need to set GNUSTEP_MAKEFILES before compiling!)
endif
include $(GNUSTEP_MAKEFILES)/common.make
-include config.make
-include local.make
PACKAGE_NAME=EnterpriseControlConfigurationLogging
PACKAGE_VERSION=0.1.0
Ec_INTERFACE_VERSION=0.1
NEEDS_GUI=NO
# The library to be compiled
LIBRARY_NAME=ECCL
# The Objective-C source files to be compiled
ECCL_OBJC_FILES = \
EcAlarm.m \
EcAlarmDestination.m \
EcAlarmSinkSNMP.m \
EcAlerter.m \
EcBroadcastProxy.m \
EcHost.m \
EcLogger.m \
EcProcess.m \
EcUserDefaults.m \
ECCL_HEADER_FILES = \
EcAlarm.h \
EcAlarmDestination.h \
EcAlarmSinkSNMP.h \
EcAlerter.h \
EcBroadcastProxy.h \
EcHost.h \
EcLogger.h \
EcProcess.h \
EcUserDefaults.h \
TOOL_NAME = \
Command \
Console \
Control \
Command_OBJC_FILES = Command.m Client.m NSFileHandle+Printf.m
Command_TOOL_LIBS += -lECCL
Command_LIB_DIRS += -L./$(GNUSTEP_OBJ_DIR)
Console_OBJC_FILES = Console.m NSFileHandle+Printf.m
Console_TOOL_LIBS += -lECCL
Console_LIB_DIRS += -L./$(GNUSTEP_OBJ_DIR)
Control_OBJC_FILES = Control.m Client.m NSFileHandle+Printf.m
Control_TOOL_LIBS += -lECCL
Control_LIB_DIRS += -L./$(GNUSTEP_OBJ_DIR)
DOCUMENT_NAME = ECCL
ECCL_AGSDOC_FILES = \
EcAlarm.h \
EcAlarmDestination.h \
EcAlarmSinkSNMP.h \
EcAlerter.h \
EcHost.h \
EcLogger.h \
EcProcess.h \
EcUserDefaults.h \
ECCL_DOC_INSTALL_DIR = Libraries
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/library.make
include $(GNUSTEP_MAKEFILES)/tool.make
include $(GNUSTEP_MAKEFILES)/documentation.make
-include GNUmakefile.postamble

48
GNUmakefile.postamble Normal file
View file

@ -0,0 +1,48 @@
#
# Makefile.postamble
#
# Project specific makefile rules
#
# Uncomment the targets you want.
# The double colons (::) are important, do not make them single colons
# otherwise the normal makefile rules will not be performed.
#
# Things to do before compiling
# before-all::
# Things to do after compiling
# after-all::
# Things to do before installing
# before-install::
# Things to do after installing
# after-install::
# Things to do before uninstalling
# before-uninstall::
# Things to do after uninstalling
# after-uninstall::
# Things to do before cleaning
# before-clean::
# Things to do after cleaning
# after-clean::
# Things to do before distcleaning
# before-distclean::
# Things to do after distcleaning
# after-distclean::
# Things to do before checking
# before-check::
# Things to do after checking
# after-check::
EcAlarmSink.m_FILE_FILTER_OUT_FLAGS = -O%

47
GNUmakefile.preamble Normal file
View file

@ -0,0 +1,47 @@
#
# Makefile.preamble
#
# Project specific makefile variables, and additional
#
# Do not put any Makefile rules in this file, instead they should
# be put into Makefile.postamble.
#
#
# Flags dealing with compiling and linking
#
# Additional flags to pass to the preprocessor
#ADDITIONAL_CPPFLAGS +=
# Additional flags to pass to the Objective-C compiler
#ADDITIONAL_OBJCFLAGS +=
# Additional flags to pass to the C compiler
#ADDITIONAL_CFLAGS +=
# Additional include directories the compiler should search
#ADDITIONAL_INCLUDE_DIRS +=
# Additional LDFLAGS to pass to the linker
# ADDITIONAL_LDFLAGS +=
# Additional library directories the linker should search
ADDITIONAL_LIB_DIRS +=
# Additional libraries when linking tools
#ADDITIONAL_TOOL_LIBS +=
# Additional libraries when linking applications
#ADDITIONAL_GUI_LIBS +=
#
# Flags dealing with installing and uninstalling
#
# Additional directories to be created during installation
#ADDITIONAL_INSTALL_DIRS +=
EcAlarmSink_OBJCFLAGS += $(shell net-snmp-config --cflags)
LIBRARIES_DEPEND_UPON += $(shell net-snmp-config --agent-libs)

13
NSFileHandle+Printf.h Normal file
View file

@ -0,0 +1,13 @@
#include <Foundation/NSFileHandle.h>
/** File handle printing utilities.
*/
@interface NSFileHandle (Printf)
- (void) printWithFormat: (NSString*)format arguments: (va_list)args;
- (void) printf: (NSString*)format,...;
- (void) puts: (NSString*)text;
@end

48
NSFileHandle+Printf.m Normal file
View file

@ -0,0 +1,48 @@
#import <Foundation/NSData.h>
#import <Foundation/NSException.h>
#import <Foundation/NSFileHandle.h>
#import <Foundation/NSString.h>
#include "NSFileHandle+Printf.h"
@implementation NSFileHandle (Printf)
- (void) printWithFormat: (NSString*)format arguments: (va_list)args
{
NSString *text;
text = [NSString stringWithFormat: format arguments: args];
[self puts: text];
}
- (void) printf: (NSString*)format,...
{
va_list ap;
va_start(ap, format);
[self printWithFormat: format arguments: ap];
va_end(ap);
}
- (void) puts: (NSString*)text
{
NS_DURING
{
NSData *data;
data = [text dataUsingEncoding: [NSString defaultCStringEncoding]];
if (data == nil)
{
data = [text dataUsingEncoding: NSUTF8StringEncoding];
}
[self writeData: data];
}
NS_HANDLER
{
NSLog(@"Exception writing to log file: %@", localException);
[localException raise];
}
NS_ENDHANDLER
}
@end

50
Terminate.m Normal file
View file

@ -0,0 +1,50 @@
#include <Foundation/Foundation.h>
#include "EcProcess.h"
int
main()
{
CREATE_AUTORELEASE_POOL(arp);
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
NSString *host;
NSString *name;
id proxy;
host = [defs stringForKey: @"BSCommandHost"];
if (host == nil)
{
host = [defs stringForKey: @"CommandHost"];
if (host == nil)
{
host = @"*";
}
}
if ([host length] == 0)
{
host = [[NSHost currentHost] name];
}
/*
* Shut down the local command server.
*/
name = [defs stringForKey: @"BSCommandName"];
if (name == nil)
{
name = [defs stringForKey: @"CommandName"];
if (name == nil)
{
name = CMD_SERVER_NAME;
}
}
proxy = [NSConnection rootProxyForConnectionWithRegisteredName: name
host: host
usingNameServer: [NSSocketPortNameServer sharedInstance]];
[(id<Command>)proxy terminate];
RELEASE(arp);
return 0;
}

1326
config.guess vendored Executable file

File diff suppressed because it is too large Load diff

105
config.h Normal file
View file

@ -0,0 +1,105 @@
/* config.h. Generated from config.h.in by configure. */
/* config.h.in. Generated from configure.in by autoheader. */
/* Define to the type of elements in the array set by `getgroups'. Usually
this is either `int' or `gid_t'. */
#define GETGROUPS_T gid_t
/* Define to 1 if you have the <arpa/inet.h> header file. */
#define HAVE_ARPA_INET_H 1
/* Define to 1 if you have the <arpa/telnet.h> header file. */
#define HAVE_ARPA_TELNET_H 1
/* Define to 1 if you have the <fcntl.h> header file. */
#define HAVE_FCNTL_H 1
/* Define to 1 if you have the `getpid' function. */
#define HAVE_GETPID 1
/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1
/* Define to 1 if you have the <memory.h> header file. */
#define HAVE_MEMORY_H 1
/* Define to 1 if you have the <netdb.h> header file. */
#define HAVE_NETDB_H 1
/* Define to 1 if you have the <netinet/in.h> header file. */
#define HAVE_NETINET_IN_H 1
/* Define to 1 if you have the <pwd.h> header file. */
#define HAVE_PWD_H 1
/* Define to 1 if you have the `setpgid' function. */
#define HAVE_SETPGID 1
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if you have the <sys/fcntl.h> header file. */
#define HAVE_SYS_FCNTL_H 1
/* Define to 1 if you have the <sys/file.h> header file. */
#define HAVE_SYS_FILE_H 1
/* Define to 1 if you have the <sys/signal.h> header file. */
#define HAVE_SYS_SIGNAL_H 1
/* Define to 1 if you have the <sys/socket.h> header file. */
#define HAVE_SYS_SOCKET_H 1
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
#define HAVE_SYS_WAIT_H 1
/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT ""
/* Define to the full name of this package. */
#define PACKAGE_NAME ""
/* Define to the full name and version of this package. */
#define PACKAGE_STRING ""
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME ""
/* Define to the version of this package. */
#define PACKAGE_VERSION ""
/* Define as the return type of signal handlers (`int' or `void'). */
#define RETSIGTYPE void
/* Define to 1 if you have the ANSI C header files. */
#define STDC_HEADERS 1
/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
#define TIME_WITH_SYS_TIME 1
/* Define to `int' if <sys/types.h> doesn't define. */
/* #undef gid_t */
/* Define to `int' if <sys/types.h> does not define. */
/* #undef mode_t */
/* Define to `int' if <sys/types.h> doesn't define. */
/* #undef uid_t */

104
config.h.in Normal file
View file

@ -0,0 +1,104 @@
/* config.h.in. Generated from configure.in by autoheader. */
/* Define to the type of elements in the array set by `getgroups'. Usually
this is either `int' or `gid_t'. */
#undef GETGROUPS_T
/* Define to 1 if you have the <arpa/inet.h> header file. */
#undef HAVE_ARPA_INET_H
/* Define to 1 if you have the <arpa/telnet.h> header file. */
#undef HAVE_ARPA_TELNET_H
/* Define to 1 if you have the <fcntl.h> header file. */
#undef HAVE_FCNTL_H
/* Define to 1 if you have the `getpid' function. */
#undef HAVE_GETPID
/* Define to 1 if you have the <inttypes.h> header file. */
#undef HAVE_INTTYPES_H
/* Define to 1 if you have the <memory.h> header file. */
#undef HAVE_MEMORY_H
/* Define to 1 if you have the <netdb.h> header file. */
#undef HAVE_NETDB_H
/* Define to 1 if you have the <netinet/in.h> header file. */
#undef HAVE_NETINET_IN_H
/* Define to 1 if you have the <pwd.h> header file. */
#undef HAVE_PWD_H
/* Define to 1 if you have the `setpgid' function. */
#undef HAVE_SETPGID
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H
/* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H
/* Define to 1 if you have the <strings.h> header file. */
#undef HAVE_STRINGS_H
/* Define to 1 if you have the <string.h> header file. */
#undef HAVE_STRING_H
/* Define to 1 if you have the <sys/fcntl.h> header file. */
#undef HAVE_SYS_FCNTL_H
/* Define to 1 if you have the <sys/file.h> header file. */
#undef HAVE_SYS_FILE_H
/* Define to 1 if you have the <sys/signal.h> header file. */
#undef HAVE_SYS_SIGNAL_H
/* Define to 1 if you have the <sys/socket.h> header file. */
#undef HAVE_SYS_SOCKET_H
/* Define to 1 if you have the <sys/stat.h> header file. */
#undef HAVE_SYS_STAT_H
/* Define to 1 if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H
/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
#undef HAVE_SYS_WAIT_H
/* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H
/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT
/* Define to the full name of this package. */
#undef PACKAGE_NAME
/* Define to the full name and version of this package. */
#undef PACKAGE_STRING
/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME
/* Define to the version of this package. */
#undef PACKAGE_VERSION
/* Define as the return type of signal handlers (`int' or `void'). */
#undef RETSIGTYPE
/* Define to 1 if you have the ANSI C header files. */
#undef STDC_HEADERS
/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
#undef TIME_WITH_SYS_TIME
/* Define to `int' if <sys/types.h> doesn't define. */
#undef gid_t
/* Define to `int' if <sys/types.h> does not define. */
#undef mode_t
/* Define to `int' if <sys/types.h> doesn't define. */
#undef uid_t

0
config.make.in Normal file
View file

1451
config.sub vendored Executable file

File diff suppressed because it is too large Load diff

5445
configure vendored Executable file

File diff suppressed because it is too large Load diff

25
configure.in Normal file
View file

@ -0,0 +1,25 @@
dnl Process this file with autoconf to produce configure.
AC_INIT(EcProcess.h)
AC_CONFIG_HEADER(config.h)
AC_PROG_CC
AC_PROG_CPP
AC_CANONICAL_HOST
dnl start proper config checks
AC_TYPE_SIGNAL
AC_HEADER_STDC
AC_HEADER_TIME
AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS(arpa/inet.h arpa/telnet.h netinet/in.h netdb.h pwd.h string.h fcntl.h sys/fcntl.h sys/file.h sys/types.h sys/socket.h sys/signal.h stdlib.h unistd.h)
AC_TYPE_GETGROUPS
AC_TYPE_SIGNAL
AC_TYPE_MODE_T
AC_CHECK_FUNCS(getpid setpgid)
AC_OUTPUT(config.make)

250
install-sh Executable file
View file

@ -0,0 +1,250 @@
#! /bin/sh
#
# install - install a program, script, or datafile
# This comes from X11R5 (mit/util/scripts/install.sh).
#
# Copyright 1991 by the Massachusetts Institute of Technology
#
# Permission to use, copy, modify, distribute, and sell this software and its
# documentation for any purpose is hereby granted without fee, provided that
# the above copyright notice appear in all copies and that both that
# copyright notice and this permission notice appear in supporting
# documentation, and that the name of M.I.T. not be used in advertising or
# publicity pertaining to distribution of the software without specific,
# written prior permission. M.I.T. makes no representations about the
# suitability of this software for any purpose. It is provided "as is"
# without express or implied warranty.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# `make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch. It can only install one file at a time, a restriction
# shared with many OS's install programs.
# set DOITPROG to echo to test this script
# Don't use :- since 4.3BSD and earlier shells don't like it.
doit="${DOITPROG-}"
# put in absolute paths if you don't have them in your path; or use env. vars.
mvprog="${MVPROG-mv}"
cpprog="${CPPROG-cp}"
chmodprog="${CHMODPROG-chmod}"
chownprog="${CHOWNPROG-chown}"
chgrpprog="${CHGRPPROG-chgrp}"
stripprog="${STRIPPROG-strip}"
rmprog="${RMPROG-rm}"
mkdirprog="${MKDIRPROG-mkdir}"
transformbasename=""
transform_arg=""
instcmd="$mvprog"
chmodcmd="$chmodprog 0755"
chowncmd=""
chgrpcmd=""
stripcmd=""
rmcmd="$rmprog -f"
mvcmd="$mvprog"
src=""
dst=""
dir_arg=""
while [ x"$1" != x ]; do
case $1 in
-c) instcmd="$cpprog"
shift
continue;;
-d) dir_arg=true
shift
continue;;
-m) chmodcmd="$chmodprog $2"
shift
shift
continue;;
-o) chowncmd="$chownprog $2"
shift
shift
continue;;
-g) chgrpcmd="$chgrpprog $2"
shift
shift
continue;;
-s) stripcmd="$stripprog"
shift
continue;;
-t=*) transformarg=`echo $1 | sed 's/-t=//'`
shift
continue;;
-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
shift
continue;;
*) if [ x"$src" = x ]
then
src=$1
else
# this colon is to work around a 386BSD /bin/sh bug
:
dst=$1
fi
shift
continue;;
esac
done
if [ x"$src" = x ]
then
echo "install: no input file specified"
exit 1
else
true
fi
if [ x"$dir_arg" != x ]; then
dst=$src
src=""
if [ -d $dst ]; then
instcmd=:
else
instcmd=mkdir
fi
else
# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
# might cause directories to be created, which would be especially bad
# if $src (and thus $dsttmp) contains '*'.
if [ -f $src -o -d $src ]
then
true
else
echo "install: $src does not exist"
exit 1
fi
if [ x"$dst" = x ]
then
echo "install: no destination specified"
exit 1
else
true
fi
# If destination is a directory, append the input filename; if your system
# does not like double slashes in filenames, you may need to add some logic
if [ -d $dst ]
then
dst="$dst"/`basename $src`
else
true
fi
fi
## this sed command emulates the dirname command
dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
# Make sure that the destination directory exists.
# this part is taken from Noah Friedman's mkinstalldirs script
# Skip lots of stat calls in the usual case.
if [ ! -d "$dstdir" ]; then
defaultIFS='
'
IFS="${IFS-${defaultIFS}}"
oIFS="${IFS}"
# Some sh's can't handle IFS=/ for some reason.
IFS='%'
set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
IFS="${oIFS}"
pathcomp=''
while [ $# -ne 0 ] ; do
pathcomp="${pathcomp}${1}"
shift
if [ ! -d "${pathcomp}" ] ;
then
$mkdirprog "${pathcomp}"
else
true
fi
pathcomp="${pathcomp}/"
done
fi
if [ x"$dir_arg" != x ]
then
$doit $instcmd $dst &&
if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
else
# If we're going to rename the final executable, determine the name now.
if [ x"$transformarg" = x ]
then
dstfile=`basename $dst`
else
dstfile=`basename $dst $transformbasename |
sed $transformarg`$transformbasename
fi
# don't allow the sed command to completely eliminate the filename
if [ x"$dstfile" = x ]
then
dstfile=`basename $dst`
else
true
fi
# Make a temp file name in the proper directory.
dsttmp=$dstdir/#inst.$$#
# Move or copy the file name to the temp name
$doit $instcmd $src $dsttmp &&
trap "rm -f ${dsttmp}" 0 &&
# and set any options; do chmod last to preserve setuid bits
# If any of these fail, we abort the whole thing. If we want to
# ignore errors from any of these, just make sure not to ignore
# errors from the above "$doit $instcmd $src $dsttmp" command.
if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
# Now rename the file to the real destination.
$doit $rmcmd -f $dstdir/$dstfile &&
$doit $mvcmd $dsttmp $dstdir/$dstfile
fi &&
exit 0

6
local.make Normal file
View file

@ -0,0 +1,6 @@
ADDITIONAL_CPPFLAGS += \
'-DEC_DEFAULTS_PREFIX=@"BS"'\
'-DEC_DEFAULTS_STRICT=NO'\
'-DEC_EFFECTIVE_USER=@"brains99"'\