mirror of
https://github.com/gnustep/libs-ec.git
synced 2025-02-19 10:01:24 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/ec/trunk@40490 72102866-910b-0410-8b05-ffd578937521
3248 lines
82 KiB
Objective-C
3248 lines
82 KiB
Objective-C
|
|
/** Enterprise Control Configuration and Logging
|
|
|
|
Copyright (C) 2012 Free Software Foundation, Inc.
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
|
Date: Febrary 2010
|
|
Originally developed from 1996 to 2012 by Brainstorm, and donated to
|
|
the FSF.
|
|
|
|
This file is part of the GNUstep project.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 3 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free
|
|
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02111 USA.
|
|
|
|
*/
|
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
#import "EcProcess.h"
|
|
#import "EcAlarm.h"
|
|
#import "EcClientI.h"
|
|
#import "EcHost.h"
|
|
#import "NSFileHandle+Printf.h"
|
|
|
|
#import "config.h"
|
|
|
|
#define DLY 300.0
|
|
|
|
static const NSTimeInterval day = 24.0 * 60.0 * 60.0;
|
|
|
|
static int tStatus = 0;
|
|
|
|
static NSTimeInterval pingDelay = 240.0;
|
|
|
|
static int comp_len = 0;
|
|
|
|
static int comp(NSString *s0, NSString *s1)
|
|
{
|
|
if ([s0 length] > [s1 length])
|
|
{
|
|
comp_len = -1;
|
|
return -1;
|
|
}
|
|
if ([s1 compare: s0
|
|
options: NSCaseInsensitiveSearch|NSLiteralSearch
|
|
range: NSMakeRange(0, [s0 length])] == NSOrderedSame)
|
|
{
|
|
comp_len = [s0 length];
|
|
if (comp_len == (int)[s1 length])
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
comp_len = -1;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static NSString* cmdWord(NSArray* a, unsigned int pos)
|
|
{
|
|
if (a != nil && [a count] > pos)
|
|
{
|
|
return [a objectAtIndex: pos];
|
|
}
|
|
else
|
|
{
|
|
return @"";
|
|
}
|
|
}
|
|
|
|
/* Special configuration options are:
|
|
*
|
|
* CompressLogsAfter
|
|
* A positive integer number of days after which logs should be compressed
|
|
* defaults to 14.
|
|
*
|
|
* DeleteLogsAfter
|
|
* A positive integer number of days after which logs should be deleted.
|
|
* Constrained to be at least as large as CompressLogsAfter.
|
|
* Defaults to 1000, but logs may still be deleted as if this were set
|
|
* to CompressLogsAfter if NodesFree or SpaceFree is reached.
|
|
*
|
|
* Environment
|
|
* A dictionary setting the default environment for launched processes.
|
|
*
|
|
* Launch
|
|
* A dictionary describing the processes which the server is responsible
|
|
* for launching.
|
|
*
|
|
* NodesFree
|
|
* A string giving a percentage of the total nodes on the disk below
|
|
* which an alert should be raised. Defaults to 10.
|
|
* Minimum 2, Maximum 90.
|
|
*
|
|
* SpaceFree
|
|
* A string giving a percentage of the total space on the disk below
|
|
* which an alert should be raised. Defaults to 10.
|
|
* Minimum 2, Maximum 90.
|
|
*
|
|
*/
|
|
@interface EcCommand : EcProcess <Command>
|
|
{
|
|
NSString *host;
|
|
id<Control> control;
|
|
NSMutableArray *clients;
|
|
NSTimer *timer;
|
|
NSString *logname;
|
|
NSMutableDictionary *config;
|
|
NSDictionary *launchInfo;
|
|
NSArray *launchOrder;
|
|
NSDictionary *environment;
|
|
NSMutableDictionary *launches;
|
|
NSMutableDictionary *launching;
|
|
unsigned pingPosition;
|
|
NSTimer *terminating;
|
|
NSDate *lastUnanswered;
|
|
unsigned fwdSequence;
|
|
unsigned revSequence;
|
|
float nodesFree;
|
|
float spaceFree;
|
|
NSTimeInterval debUncompressed;
|
|
NSTimeInterval debUndeleted;
|
|
NSTimeInterval logUncompressed;
|
|
NSTimeInterval logUndeleted;
|
|
BOOL sweeping;
|
|
}
|
|
- (NSFileHandle*) openLog: (NSString*)lname;
|
|
- (void) cmdGnip: (id <CmdPing>)from
|
|
sequence: (unsigned)num
|
|
extra: (NSData*)data;
|
|
- (void) cmdPing: (id <CmdPing>)from
|
|
sequence: (unsigned)num
|
|
extra: (NSData*)data;
|
|
- (oneway void) cmdQuit: (NSInteger)sig;
|
|
- (void) command: (NSData*)dat
|
|
to: (NSString*)t
|
|
from: (NSString*)f;
|
|
- (NSData *) configurationFor: (NSString *)name;
|
|
- (BOOL) connection: (NSConnection*)ancestor
|
|
shouldMakeNewConnection: (NSConnection*)newConn;
|
|
- (id) connectionBecameInvalid: (NSNotification*)notification;
|
|
- (NSArray*) findAll: (NSArray*)a
|
|
byAbbreviation: (NSString*)s;
|
|
- (EcClientI*) findIn: (NSArray*)a
|
|
byAbbreviation: (NSString*)s;
|
|
- (EcClientI*) findIn: (NSArray*)a
|
|
byName: (NSString*)s;
|
|
- (EcClientI*) findIn: (NSArray*)a
|
|
byObject: (id)s;
|
|
- (void) information: (NSString*)inf
|
|
from: (NSString*)s
|
|
type: (EcLogType)t;
|
|
- (void) information: (NSString*)inf
|
|
from: (NSString*)s
|
|
to: (NSString*)d
|
|
type: (EcLogType)t;
|
|
- (void) killAll;
|
|
- (void) launch;
|
|
- (void) logMessage: (NSString*)msg
|
|
type: (EcLogType)t
|
|
for: (id<CmdClient>)o;
|
|
- (void) logMessage: (NSString*)msg
|
|
type: (EcLogType)t
|
|
name: (NSString*)c;
|
|
- (NSString*) makeSpace;
|
|
- (void) newConfig: (NSMutableDictionary*)newConfig;
|
|
- (void) pingControl;
|
|
- (void) quitAll;
|
|
- (void) requestConfigFor: (id<CmdConfig>)c;
|
|
- (NSData*) registerClient: (id<CmdClient>)c
|
|
name: (NSString*)n;
|
|
- (NSData*) registerClient: (id<CmdClient>)c
|
|
name: (NSString*)n
|
|
transient: (BOOL)t;
|
|
- (void) reply: (NSString*) msg to: (NSString*)n from: (NSString*)c;
|
|
- (NSArray*) restartAll;
|
|
- (void) terminate;
|
|
- (void) timedOut: (NSTimer*)t;
|
|
- (void) unregisterByObject: (id)obj;
|
|
- (void) unregisterByName: (NSString*)n;
|
|
- (void) update;
|
|
- (void) updateConfig: (NSData*)data;
|
|
@end
|
|
|
|
@implementation EcCommand
|
|
|
|
- (oneway void) alarm: (in bycopy EcAlarm*)alarm
|
|
{
|
|
NS_DURING
|
|
{
|
|
[control alarm: alarm];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Exception sending alarm to Control: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
|
|
- (oneway void) domanage: (in bycopy NSString*)managedObject
|
|
{
|
|
NS_DURING
|
|
{
|
|
[control domanage: managedObject];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Exception sending domanage: to Control: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
|
|
- (oneway void) unmanage: (in bycopy NSString*)managedObject
|
|
{
|
|
NS_DURING
|
|
{
|
|
[control unmanage: managedObject];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Exception sending unmanage: to Control: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
|
|
- (NSFileHandle*) openLog: (NSString*)lname
|
|
{
|
|
NSFileManager *mgr = [NSFileManager defaultManager];
|
|
NSFileHandle *lf;
|
|
|
|
if ([mgr isWritableFileAtPath: lname] == NO)
|
|
{
|
|
if ([mgr createFileAtPath: lname
|
|
contents: nil
|
|
attributes: nil] == NO)
|
|
{
|
|
NSLog(@"Log file '%@' is not writable and can't be created", lname);
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
lf = [NSFileHandle fileHandleForUpdatingAtPath: lname];
|
|
if (lf == nil)
|
|
{
|
|
NSLog(@"Unable to log to %@", lname);
|
|
return nil;
|
|
}
|
|
[lf seekToEndOfFile];
|
|
return lf;
|
|
}
|
|
|
|
- (void) newConfig: (NSMutableDictionary*)newConfig
|
|
{
|
|
NSString *diskCache;
|
|
NSData *data;
|
|
|
|
diskCache = [[self cmdDataDirectory]
|
|
stringByAppendingPathComponent: @"CommandConfig.cache"];
|
|
|
|
if (NO == [newConfig isKindOfClass: [NSMutableDictionary class]]
|
|
|| 0 == [newConfig count])
|
|
{
|
|
/* If we are called with a nil argument, we must obtain the config
|
|
* from local disk cache (if available).
|
|
*/
|
|
if (nil != (data = [NSData dataWithContentsOfFile: diskCache]))
|
|
{
|
|
newConfig = [NSPropertyListSerialization
|
|
propertyListWithData: data
|
|
options: NSPropertyListMutableContainers
|
|
format: 0
|
|
error: 0];
|
|
}
|
|
if (NO == [newConfig isKindOfClass: [NSMutableDictionary class]]
|
|
|| 0 == [newConfig count])
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
data = nil;
|
|
}
|
|
|
|
if (nil == config || [config isEqual: newConfig] == NO)
|
|
{
|
|
NSDictionary *d;
|
|
NSArray *a;
|
|
unsigned i;
|
|
|
|
ASSIGN(config, newConfig);
|
|
|
|
d = [config objectForKey: [self cmdName]];
|
|
DESTROY(launchInfo);
|
|
DESTROY(launchOrder);
|
|
DESTROY(environment);
|
|
if ([d isKindOfClass: [NSDictionary class]] == YES)
|
|
{
|
|
id o;
|
|
NSMutableDictionary *m;
|
|
NSString *k;
|
|
NSString *err = nil;
|
|
|
|
m = [[d mutableCopy] autorelease];
|
|
d = m;
|
|
NS_DURING
|
|
[self cmdUpdate: m];
|
|
NS_HANDLER
|
|
NSLog(@"Problem before updating config (in cmdUpdate:) %@",
|
|
localException);
|
|
err = @"the -cmdUpdate: method raised an exception";
|
|
NS_ENDHANDLER
|
|
if (nil == err)
|
|
{
|
|
NS_DURING
|
|
err = [self cmdUpdated];
|
|
NS_HANDLER
|
|
NSLog(@"Problem after updating config (in cmdUpdated) %@",
|
|
localException);
|
|
err = @"the -cmdUpdated method raised an exception";
|
|
NS_ENDHANDLER
|
|
}
|
|
if ([err length] > 0)
|
|
{
|
|
EcAlarm *a;
|
|
|
|
/* Truncate additional text to fit if necessary.
|
|
*/
|
|
err = [err stringByTrimmingSpaces];
|
|
if ([err length] > 255)
|
|
{
|
|
err = [err substringToIndex: 255];
|
|
while (255 < strlen([err UTF8String]))
|
|
{
|
|
err = [err substringToIndex: [err length] - 1];
|
|
}
|
|
}
|
|
a = [EcAlarm alarmForManagedObject: nil
|
|
at: nil
|
|
withEventType: EcAlarmEventTypeProcessingError
|
|
probableCause: EcAlarmConfigurationOrCustomizationError
|
|
specificProblem: @"configuration error"
|
|
perceivedSeverity: EcAlarmSeverityMajor
|
|
proposedRepairAction:
|
|
_(@"Correct config or software (check log for details).")
|
|
additionalText: err];
|
|
[self alarm: a];
|
|
}
|
|
else
|
|
{
|
|
EcAlarm *a;
|
|
|
|
a = [EcAlarm alarmForManagedObject: nil
|
|
at: nil
|
|
withEventType: EcAlarmEventTypeProcessingError
|
|
probableCause: EcAlarmConfigurationOrCustomizationError
|
|
specificProblem: @"configuration error"
|
|
perceivedSeverity: EcAlarmSeverityCleared
|
|
proposedRepairAction: nil
|
|
additionalText: nil];
|
|
[self alarm: a];
|
|
}
|
|
|
|
launchInfo = [d objectForKey: @"Launch"];
|
|
if ([launchInfo isKindOfClass: [NSDictionary class]] == NO)
|
|
{
|
|
NSLog(@"No 'Launch' information in latest config update");
|
|
launchInfo = nil;
|
|
}
|
|
else
|
|
{
|
|
NSEnumerator *e = [launchInfo keyEnumerator];
|
|
|
|
while ((k = [e nextObject]) != nil)
|
|
{
|
|
NSDictionary *d = [launchInfo objectForKey: k];
|
|
id o;
|
|
|
|
if ([d isKindOfClass: [NSDictionary class]] == NO)
|
|
{
|
|
NSLog(@"bad 'Launch' information for %@", k);
|
|
launchInfo = nil;
|
|
break;
|
|
}
|
|
o = [d objectForKey: @"Auto"];
|
|
if (o != nil && [o isKindOfClass: [NSString class]] == NO)
|
|
{
|
|
NSLog(@"bad 'Launch' Auto for %@", k);
|
|
launchInfo = nil;
|
|
break;
|
|
}
|
|
o = [d objectForKey: @"Disabled"];
|
|
if (o != nil && [o isKindOfClass: [NSString class]] == NO)
|
|
{
|
|
NSLog(@"bad 'Launch' Disabled for %@", k);
|
|
launchInfo = nil;
|
|
break;
|
|
}
|
|
o = [d objectForKey: @"Args"];
|
|
if (o != nil && [o isKindOfClass: [NSArray class]] == NO)
|
|
{
|
|
NSLog(@"bad 'Launch' Args for %@", k);
|
|
launchInfo = nil;
|
|
break;
|
|
}
|
|
o = [d objectForKey: @"Home"];
|
|
if (o != nil && [o isKindOfClass: [NSString class]] == NO)
|
|
{
|
|
NSLog(@"bad 'Launch' Home for %@", k);
|
|
launchInfo = nil;
|
|
break;
|
|
}
|
|
o = [d objectForKey: @"Prog"];
|
|
if (o == nil || [o isKindOfClass: [NSString class]] == NO)
|
|
{
|
|
NSLog(@"bad 'Launch' Prog for %@", k);
|
|
launchInfo = nil;
|
|
break;
|
|
}
|
|
o = [d objectForKey: @"AddE"];
|
|
if (o != nil && [o isKindOfClass: [NSDictionary class]] == NO)
|
|
{
|
|
NSLog(@"bad 'Launch' AddE for %@", k);
|
|
launchInfo = nil;
|
|
break;
|
|
}
|
|
o = [d objectForKey: @"SetE"];
|
|
if (o != nil && [o isKindOfClass: [NSDictionary class]] == NO)
|
|
{
|
|
NSLog(@"bad 'Launch' SetE for %@", k);
|
|
launchInfo = nil;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
RETAIN(launchInfo);
|
|
|
|
o = [d objectForKey: @"LaunchOrder"];
|
|
if (NO == [o isKindOfClass: [NSArray class]])
|
|
{
|
|
if (nil != o)
|
|
{
|
|
NSLog(@"bad 'LaunchOrder' config (not an array) ignored");
|
|
}
|
|
/* The default launch order is alphabetical by server name.
|
|
*/
|
|
o = [[launchInfo allKeys] sortedArrayUsingSelector:
|
|
@selector(compare:)];
|
|
launchOrder = RETAIN(o);
|
|
}
|
|
else
|
|
{
|
|
NSMutableArray *m;
|
|
NSEnumerator *e;
|
|
NSString *k;
|
|
NSUInteger c;
|
|
|
|
m = AUTORELEASE([o mutableCopy]);
|
|
c = [m count];
|
|
while (c-- > 0)
|
|
{
|
|
o = [m objectAtIndex: c];
|
|
if (NO == [o isKindOfClass: [NSString class]])
|
|
{
|
|
NSLog(@"bad 'LaunchOrder' item ('%@' at %u) ignored"
|
|
@" (not a server name)", o, (unsigned)c);
|
|
[m removeObjectAtIndex: c];
|
|
}
|
|
else if ([m indexOfObject: o] != c)
|
|
{
|
|
NSLog(@"bad 'LaunchOrder' item ('%@' at %u) ignored"
|
|
@" (repeat of earlier item)", o, (unsigned)c);
|
|
[m removeObjectAtIndex: c];
|
|
}
|
|
else if (nil == [launchInfo objectForKey: o])
|
|
{
|
|
NSLog(@"bad 'LaunchOrder' item ('%@' at %u) ignored"
|
|
@" (not in 'Launch' dictionary)", o, (unsigned)c);
|
|
[m removeObjectAtIndex: c];
|
|
}
|
|
}
|
|
/* Any missing servers are launched after others
|
|
* they are in lexicographic order.
|
|
*/
|
|
o = [[launchInfo allKeys] sortedArrayUsingSelector:
|
|
@selector(compare:)];
|
|
e = [o objectEnumerator];
|
|
while (nil != (k = [e nextObject]))
|
|
{
|
|
if (NO == [m containsObject: k])
|
|
{
|
|
[m addObject: k];
|
|
}
|
|
}
|
|
launchOrder = [m copy];
|
|
}
|
|
|
|
environment = [d objectForKey: @"Environment"];
|
|
if ([environment isKindOfClass: [NSDictionary class]] == NO)
|
|
{
|
|
NSLog(@"No 'Environment' information in latest config update");
|
|
environment = nil;
|
|
}
|
|
RETAIN(environment);
|
|
|
|
k = [d objectForKey: @"NodesFree"];
|
|
if (YES == [k isKindOfClass: [NSString class]])
|
|
{
|
|
nodesFree = [k floatValue];
|
|
nodesFree /= 100.0;
|
|
}
|
|
else
|
|
{
|
|
nodesFree = 0.0;
|
|
}
|
|
if (nodesFree < 0.02 || nodesFree > 0.9)
|
|
{
|
|
NSLog(@"bad or missing minimum disk 'NodesFree' ... using 10%%");
|
|
nodesFree = 0.1;
|
|
}
|
|
k = [d objectForKey: @"SpaceFree"];
|
|
if (YES == [k isKindOfClass: [NSString class]])
|
|
{
|
|
spaceFree = [k floatValue];
|
|
spaceFree /= 100.0;
|
|
}
|
|
else
|
|
{
|
|
spaceFree = 0.0;
|
|
}
|
|
if (spaceFree < 0.02 || spaceFree > 0.9)
|
|
{
|
|
NSLog(@"bad or missing minimum disk 'SpaceFree' ... using 10%%");
|
|
spaceFree = 0.1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSLog(@"No '%@' information in latest config update", [self cmdName]);
|
|
}
|
|
|
|
a = [NSArray arrayWithArray: clients];
|
|
i = [a count];
|
|
while (i-- > 0)
|
|
{
|
|
EcClientI *c = [a objectAtIndex: i];
|
|
|
|
if ([clients indexOfObjectIdenticalTo: c] != NSNotFound)
|
|
{
|
|
NS_DURING
|
|
{
|
|
NSData *d = [self configurationFor: [c name]];
|
|
|
|
if (nil != d)
|
|
{
|
|
[c setConfig: d];
|
|
[[c obj] updateConfig: d];
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Setting config for client: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
if (nil == data)
|
|
{
|
|
/* Need to update on-disk cache
|
|
*/
|
|
data = [NSPropertyListSerialization
|
|
dataFromPropertyList: newConfig
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0];
|
|
[data writeToFile: diskCache atomically: YES];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) pingControl
|
|
{
|
|
if (control == nil)
|
|
{
|
|
return;
|
|
}
|
|
if (fwdSequence == revSequence)
|
|
{
|
|
lastUnanswered = RETAIN([NSDate date]);
|
|
NS_DURING
|
|
{
|
|
[control cmdPing: self sequence: ++fwdSequence extra: nil];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Ping to control server - %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
else
|
|
{
|
|
NSLog(@"Ping to control server when one is already in progress.");
|
|
}
|
|
}
|
|
|
|
- (void) cmdGnip: (id <CmdPing>)from
|
|
sequence: (unsigned)num
|
|
extra: (NSData*)data
|
|
{
|
|
if (from == control)
|
|
{
|
|
if (num != revSequence + 1 && revSequence != 0)
|
|
{
|
|
NSLog(@"Gnip from control server seq: %u when expecting %u",
|
|
num, revSequence);
|
|
if (num == 0)
|
|
{
|
|
fwdSequence = 0; // Reset
|
|
}
|
|
}
|
|
revSequence = num;
|
|
if (revSequence == fwdSequence)
|
|
{
|
|
DESTROY(lastUnanswered);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EcClientI *r;
|
|
|
|
/* See if we have a fitting client - and update records.
|
|
*/
|
|
r = [self findIn: clients byObject: (id)from];
|
|
if (r != nil)
|
|
{
|
|
NSString *managedObject;
|
|
NSString *n;
|
|
EcAlarm *a;
|
|
|
|
[r gnip: num];
|
|
n = [r name];
|
|
|
|
/* After the first ping response from a client we assume
|
|
* that client has completed startup and is running OK.
|
|
* We can therefore clear any loss of client alarm, any
|
|
* alarm for being unable to register, and launch failure
|
|
* or fatal configuration alarms.
|
|
*/
|
|
managedObject = EcMakeManagedObject(host, n, nil);
|
|
a = [EcAlarm alarmForManagedObject: managedObject
|
|
at: nil
|
|
withEventType: EcAlarmEventTypeProcessingError
|
|
probableCause: EcAlarmSoftwareProgramAbnormallyTerminated
|
|
specificProblem: @"Process availability"
|
|
perceivedSeverity: EcAlarmSeverityCleared
|
|
proposedRepairAction: nil
|
|
additionalText: nil];
|
|
[self alarm: a];
|
|
a = [EcAlarm alarmForManagedObject: managedObject
|
|
at: nil
|
|
withEventType: EcAlarmEventTypeProcessingError
|
|
probableCause: EcAlarmSoftwareProgramAbnormallyTerminated
|
|
specificProblem: @"Unable to register"
|
|
perceivedSeverity: EcAlarmSeverityCleared
|
|
proposedRepairAction: nil
|
|
additionalText: nil];
|
|
[self alarm: a];
|
|
[self clearConfigurationFor: managedObject
|
|
specificProblem: @"Process launch"
|
|
additionalText: @"Process is now running"];
|
|
[self clearConfigurationFor: managedObject
|
|
specificProblem: @"Fatal configuration error"
|
|
additionalText: @"Process is now running"];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL) cmdIsClient
|
|
{
|
|
return NO; // Not a client of the Command server.
|
|
}
|
|
|
|
- (void) cmdPing: (id <CmdPing>)from
|
|
sequence: (unsigned)num
|
|
extra: (NSData*)data
|
|
{
|
|
/* Send back a response to let the other party know we are alive.
|
|
*/
|
|
[from cmdGnip: self sequence: num extra: nil];
|
|
}
|
|
|
|
- (oneway void) cmdQuit: (NSInteger)sig
|
|
{
|
|
if (sig == tStatus && control != nil)
|
|
{
|
|
NS_DURING
|
|
{
|
|
[control unregister: self];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Exception unregistering from Control: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
exit(sig);
|
|
}
|
|
|
|
- (void) command: (NSData*)dat
|
|
to: (NSString*)t
|
|
from: (NSString*)f
|
|
{
|
|
NSMutableArray *cmd = [NSPropertyListSerialization
|
|
propertyListWithData: dat
|
|
options: NSPropertyListMutableContainers
|
|
format: 0
|
|
error: 0];
|
|
|
|
if (cmd == nil || [cmd count] == 0)
|
|
{
|
|
[self information: cmdLogFormat(LT_ERROR, @"bad command array")
|
|
from: nil
|
|
to: f
|
|
type: LT_ERROR];
|
|
}
|
|
else if (t == nil)
|
|
{
|
|
NSString *m = @"";
|
|
NSString *wd = cmdWord(cmd, 0);
|
|
|
|
if ([wd length] == 0)
|
|
{
|
|
/* Quietly ignore. */
|
|
}
|
|
else if (comp(wd, @"archive") >= 0)
|
|
{
|
|
NSCalendarDate *when = [NSCalendarDate date];
|
|
NSString *sub;
|
|
int yy, mm, dd;
|
|
|
|
yy = [when yearOfCommonEra];
|
|
mm = [when monthOfYear];
|
|
dd = [when dayOfMonth];
|
|
|
|
sub = [NSString stringWithFormat: @"%04d-%02d-%02d", yy, mm, dd];
|
|
m = [NSString stringWithFormat: @"\n%@\n\n", [self cmdArchive: sub]];
|
|
}
|
|
else if (comp(wd, @"help") >= 0)
|
|
{
|
|
wd = cmdWord(cmd, 1);
|
|
if ([wd length] == 0)
|
|
{
|
|
m = @"Commands are -\n"
|
|
@"Help\tArchive\tControl\tLaunch\tList\tMemory\t"
|
|
@"Quit\tRestart\tTell\n\n"
|
|
@"Type 'help' followed by a command word for details.\n"
|
|
@"A command line consists of a sequence of words, "
|
|
@"the first of which is the command to be executed. "
|
|
@"A word can be a simple sequence of non-space characters, "
|
|
@"or it can be a 'quoted string'. "
|
|
@"Simple words are converted to lower case before "
|
|
@"matching them against commands and their parameters. "
|
|
@"Text in a 'quoted string' is NOT converted to lower case "
|
|
@"but a '\\' character is treated in a special manner -\n"
|
|
@" \\b is replaced by a backspace\n"
|
|
@" \\f is replaced by a formfeed\n"
|
|
@" \\n is replaced by a linefeed\n"
|
|
@" \\r is replaced by a carriage-return\n"
|
|
@" \\t is replaced by a tab\n"
|
|
@" \\0 followed by up to 3 octal digits is replaced"
|
|
@" by the octal value\n"
|
|
@" \\x followed by up to 2 hex digits is replaced"
|
|
@" by the hex value\n"
|
|
@" \\ followed by any other character is replaced by"
|
|
@" the second character.\n"
|
|
@" This permits use of quotes and backslashes inside"
|
|
@" a quoted string.\n";
|
|
}
|
|
else
|
|
{
|
|
if (comp(wd, @"Archive") >= 0)
|
|
{
|
|
m = @"Archive\nArchives the log file. The archived log "
|
|
@"file is stored in a subdirectory whose name is of "
|
|
@"the form YYYYMMDDhhmmss being the date and time at "
|
|
@"which the archive was created.\n";
|
|
}
|
|
else if (comp(wd, @"Control") >= 0)
|
|
{
|
|
m = @"Control ...\nPasses the command to the Control "
|
|
@"process. You may disconnect from this host by "
|
|
@"typing 'control host'\n";
|
|
}
|
|
else if (comp(wd, @"Launch") >= 0)
|
|
{
|
|
m = @"Launch <name>\nAdds the named program to the list "
|
|
@"of programs to be launched as soon as possible.\n"
|
|
@"Launch all\nAdds all unlaunched programs which do "
|
|
@"not have autolaunch disabled.\n";
|
|
}
|
|
else if (comp(wd, @"List") >= 0)
|
|
{
|
|
m = @"List\nLists all the connected clients.\n"
|
|
@"List launches\nLists the programs we can launch.\n";
|
|
}
|
|
else if (comp(wd, @"Memory") >= 0)
|
|
{
|
|
m = @"Memory\nDisplays recent memory allocation stats.\n"
|
|
@"Memory all\nDisplays all memory allocation stats.\n";
|
|
}
|
|
else if (comp(wd, @"Quit") >= 0)
|
|
{
|
|
m = @"Quit 'name'\n"
|
|
@"Shuts down the named client process(es).\n"
|
|
@"Quit all\n"
|
|
@"Shuts down all client processes.\n"
|
|
@"Quit self\n"
|
|
@"Shuts down the Command server for this host.\n";
|
|
}
|
|
else if (comp(wd, @"Restart") >= 0)
|
|
{
|
|
m = @"Restart 'name'\n"
|
|
@"Shuts down and starts the named client process(es).\n"
|
|
@"Restart all\n"
|
|
@"Shuts down and starts all client processes.\n"
|
|
@"Restart self\n"
|
|
@"Shuts down and starts Command server for this host.\n";
|
|
}
|
|
else if (comp(wd, @"Tell") >= 0)
|
|
{
|
|
m = @"Tell 'name' 'command'\n"
|
|
@"Sends the command to the named client(s).\n"
|
|
@"You may use 'tell all ...' to send to all clients.\n";
|
|
}
|
|
}
|
|
}
|
|
else if (comp(wd, @"launch") >= 0)
|
|
{
|
|
if ([cmd count] > 1)
|
|
{
|
|
if (launchInfo != nil)
|
|
{
|
|
NSEnumerator *enumerator;
|
|
NSString *key;
|
|
NSString *nam = [cmd objectAtIndex: 1];
|
|
BOOL found = NO;
|
|
|
|
enumerator = [launchOrder objectEnumerator];
|
|
if ([nam caseInsensitiveCompare: @"all"] == NSOrderedSame)
|
|
{
|
|
NSMutableArray *names = [NSMutableArray array];
|
|
|
|
while ((key = [enumerator nextObject]) != nil)
|
|
{
|
|
EcClientI *r;
|
|
NSDictionary *inf;
|
|
|
|
inf = [launchInfo objectForKey: key];
|
|
if ([[inf objectForKey: @"Auto"] boolValue]==NO)
|
|
{
|
|
continue;
|
|
}
|
|
r = [self findIn: clients byName: key];
|
|
if (nil != r)
|
|
{
|
|
continue;
|
|
}
|
|
found = YES;
|
|
[launches setObject: [NSDate distantPast]
|
|
forKey: key];
|
|
[names addObject: key];
|
|
}
|
|
if (YES == found)
|
|
{
|
|
[names sortUsingSelector: @selector(compare:)];
|
|
m = [NSString stringWithFormat:
|
|
@"Ok - I will launch %@ when I get a chance.\n",
|
|
names];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while ((key = [enumerator nextObject]) != nil)
|
|
{
|
|
if (comp(nam, key) >= 0)
|
|
{
|
|
EcClientI *r;
|
|
|
|
found = YES;
|
|
r = [self findIn: clients byName: key];
|
|
if (r == nil)
|
|
{
|
|
[launches setObject: [NSDate distantPast]
|
|
forKey: key];
|
|
m = @"Ok - I will launch that program "
|
|
@"when I get a chance.\n";
|
|
}
|
|
else
|
|
{
|
|
m = @"That program is already running\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (found == NO)
|
|
{
|
|
m = @"I don't know how to launch that program.\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m = @"There are no programs we can launch.\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m = @"I need the name of a program to launch.\n";
|
|
}
|
|
}
|
|
else if (comp(wd, @"list") >= 0)
|
|
{
|
|
wd = cmdWord(cmd, 1);
|
|
if ([wd length] == 0 || comp(wd, @"clients") >= 0)
|
|
{
|
|
if ([clients count] == 0)
|
|
{
|
|
m = @"No clients currently connected.\n";
|
|
}
|
|
else
|
|
{
|
|
unsigned i;
|
|
|
|
m = @"Current client processes -\n";
|
|
for (i = 0; i < [clients count]; i++)
|
|
{
|
|
EcClientI* c = [clients objectAtIndex: i];
|
|
|
|
m = [NSString stringWithFormat:
|
|
@"%@%2d. %-32.32s\n", m, i, [[c name] cString]];
|
|
}
|
|
}
|
|
}
|
|
else if (comp(wd, @"launches") >= 0)
|
|
{
|
|
if (launchInfo != nil)
|
|
{
|
|
NSEnumerator *enumerator;
|
|
NSString *key;
|
|
NSDate *date;
|
|
NSDate *now = [NSDate date];
|
|
|
|
m = @"Programs we can launch -\n";
|
|
enumerator = [[[launchInfo allKeys] sortedArrayUsingSelector:
|
|
@selector(compare:)] objectEnumerator];
|
|
while ((key = [enumerator nextObject]) != nil)
|
|
{
|
|
EcClientI *r;
|
|
NSDictionary *inf = [launchInfo objectForKey: key];
|
|
|
|
m = [m stringByAppendingFormat: @" %-32.32s ",
|
|
[key cString]];
|
|
r = [self findIn: clients byName: key];
|
|
if (r == nil)
|
|
{
|
|
if ([[inf objectForKey: @"Disabled"] boolValue]==YES)
|
|
{
|
|
m = [m stringByAppendingString:
|
|
@"disabled in config\n"];
|
|
}
|
|
else if ([[inf objectForKey: @"Auto"] boolValue]==NO)
|
|
{
|
|
date = [launches objectForKey: key];
|
|
if (nil == date
|
|
|| [NSDate distantFuture] == date)
|
|
{
|
|
m = [m stringByAppendingString:
|
|
@"may be launched manually\n"];
|
|
}
|
|
else if ([now timeIntervalSinceDate: date] > DLY)
|
|
{
|
|
m = [m stringByAppendingString:
|
|
@"ready to autolaunch now\n"];
|
|
}
|
|
else
|
|
{
|
|
m = [m stringByAppendingString:
|
|
@"autolaunch in a few minutes\n"];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
date = [launches objectForKey: key];
|
|
if (date == nil)
|
|
{
|
|
date = now;
|
|
[launches setObject: date forKey: key];
|
|
}
|
|
if ([NSDate distantFuture] == date)
|
|
{
|
|
m = [m stringByAppendingString:
|
|
@"manually suspended\n"];
|
|
}
|
|
else
|
|
{
|
|
if ([now timeIntervalSinceDate: date] > DLY)
|
|
{
|
|
m = [m stringByAppendingString:
|
|
@"ready to autolaunch now\n"];
|
|
}
|
|
else
|
|
{
|
|
m = [m stringByAppendingString:
|
|
@"autolaunch in a few minutes\n"];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m = [m stringByAppendingString: @"running\n"];
|
|
}
|
|
}
|
|
if ([launchInfo count] == 0)
|
|
{
|
|
m = [m stringByAppendingString: @"nothing\n"];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m = @"There are no programs we can launch.\n";
|
|
}
|
|
}
|
|
}
|
|
else if (comp(wd, @"memory") >= 0)
|
|
{
|
|
if (GSDebugAllocationActive(YES) == NO)
|
|
{
|
|
m = @"Memory statistics were not being gathered.\n"
|
|
@"Statistics Will start from NOW.\n";
|
|
}
|
|
else
|
|
{
|
|
const char* list;
|
|
|
|
wd = cmdWord(cmd, 1);
|
|
if ([wd length] > 0 && comp(wd, @"all") >= 0)
|
|
{
|
|
list = GSDebugAllocationList(NO);
|
|
}
|
|
else
|
|
{
|
|
list = GSDebugAllocationList(YES);
|
|
}
|
|
m = [NSString stringWithCString: list];
|
|
}
|
|
}
|
|
else if (comp(wd, @"quit") >= 0)
|
|
{
|
|
wd = cmdWord(cmd, 1);
|
|
if ([wd length] > 0)
|
|
{
|
|
if (comp(wd, @"self") == 0)
|
|
{
|
|
if (terminating == nil)
|
|
{
|
|
NS_DURING
|
|
{
|
|
[control unregister: self];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Exception unregistering from Control: %@",
|
|
localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
exit(0);
|
|
}
|
|
else
|
|
{
|
|
m = @"Already terminating!\n";
|
|
}
|
|
}
|
|
else if (comp(wd, @"all") == 0)
|
|
{
|
|
[self quitAll];
|
|
|
|
if ([clients count] == 0)
|
|
{
|
|
m = @"All clients have been shut down.\n";
|
|
}
|
|
else if ([clients count] == 1)
|
|
{
|
|
m = @"One client did not shut down.\n";
|
|
}
|
|
else
|
|
{
|
|
m = @"Some clients did not shut down.\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSArray *a = [self findAll: clients byAbbreviation: wd];
|
|
unsigned i;
|
|
BOOL found = NO;
|
|
|
|
for (i = 0; i < [a count]; i++)
|
|
{
|
|
EcClientI *c = [a objectAtIndex: i];
|
|
|
|
NS_DURING
|
|
{
|
|
[launches setObject: [NSDate distantFuture]
|
|
forKey: [c name]];
|
|
m = [m stringByAppendingFormat:
|
|
@"Sent 'quit' to '%@'\n", [c name]];
|
|
m = [m stringByAppendingString:
|
|
@" Please wait for this to be 'removed' before "
|
|
@"proceeding.\n"];
|
|
[c setTerminating: YES];
|
|
[[c obj] cmdQuit: 0];
|
|
found = YES;
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Caught exception: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
if (launchInfo != nil)
|
|
{
|
|
NSEnumerator *enumerator;
|
|
NSString *key;
|
|
|
|
enumerator = [launchOrder objectEnumerator];
|
|
while ((key = [enumerator nextObject]) != nil)
|
|
{
|
|
if (comp(wd, key) >= 0)
|
|
{
|
|
NSDate *when = [launches objectForKey: key];
|
|
|
|
found = YES;
|
|
[launches setObject: [NSDate distantFuture]
|
|
forKey: key];
|
|
if (when != [NSDate distantFuture])
|
|
{
|
|
m = [m stringByAppendingFormat:
|
|
@"Suspended %@\n", key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (NO == found)
|
|
{
|
|
m = [NSString stringWithFormat:
|
|
@"Nothing to shut down as '%@'\n", wd];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m = @"Quit what?.\n";
|
|
}
|
|
}
|
|
else if (comp(wd, @"restart") >= 0)
|
|
{
|
|
wd = cmdWord(cmd, 1);
|
|
if ([wd length] > 0)
|
|
{
|
|
if (comp(wd, @"self") == 0)
|
|
{
|
|
if (terminating == nil)
|
|
{
|
|
NS_DURING
|
|
{
|
|
[self information: @"Re-starting Command server\n"
|
|
from: t
|
|
to: f
|
|
type: LT_AUDIT];
|
|
[control unregister: self];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Exception unregistering from Control: %@",
|
|
localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
exit(1); // Watcher should restart us
|
|
}
|
|
else
|
|
{
|
|
m = @"Already terminating!\n";
|
|
}
|
|
}
|
|
else if (comp(wd, @"all") == 0)
|
|
{
|
|
NSArray *a;
|
|
|
|
a = [self restartAll];
|
|
if ([a count] == 0)
|
|
{
|
|
m = @"All clients have been shut down for restart.\n";
|
|
}
|
|
else if ([a count] == 1)
|
|
{
|
|
m = @"One client did not shut down for restart.\n";
|
|
}
|
|
else
|
|
{
|
|
m = @"Some clients did not shut down for restart.\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSArray *a = [self findAll: clients byAbbreviation: wd];
|
|
unsigned i;
|
|
BOOL found = NO;
|
|
NSDate *when;
|
|
|
|
when = [NSDate dateWithTimeIntervalSinceNow: 30.0 - DLY];
|
|
for (i = 0; i < [a count]; i++)
|
|
{
|
|
EcClientI *c = [a objectAtIndex: i];
|
|
|
|
NS_DURING
|
|
{
|
|
[launches setObject: when forKey: [c name]];
|
|
m = [m stringByAppendingFormat:
|
|
@" The process '%@' should restart shortly.\n",
|
|
[c name]];
|
|
[c setTerminating: YES];
|
|
[[c obj] cmdQuit: 0];
|
|
found = YES;
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Caught exception: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
if (NO == found)
|
|
{
|
|
m = [NSString stringWithFormat:
|
|
@"Nothing to restart as '%@'\n", wd];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m = @"Restart what?.\n";
|
|
}
|
|
}
|
|
else if (comp(wd, @"tell") >= 0)
|
|
{
|
|
wd = cmdWord(cmd, 1);
|
|
if ([wd length] > 0)
|
|
{
|
|
NSString *dest = AUTORELEASE(RETAIN(wd));
|
|
|
|
[cmd removeObjectAtIndex: 0];
|
|
[cmd removeObjectAtIndex: 0];
|
|
if (comp(dest, @"all") == 0)
|
|
{
|
|
unsigned i;
|
|
NSArray *a = [[NSArray alloc] initWithArray: clients];
|
|
|
|
for (i = 0; i < [a count]; i++)
|
|
{
|
|
EcClientI* c = [a objectAtIndex: i];
|
|
|
|
if ([clients indexOfObjectIdenticalTo: c]!=NSNotFound)
|
|
{
|
|
NS_DURING
|
|
{
|
|
NSData *dat = [NSPropertyListSerialization
|
|
dataFromPropertyList: cmd
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0];
|
|
[[c obj] cmdMesgData: dat from: f];
|
|
m = @"Sent message.\n";
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Caught exception: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSArray *a;
|
|
|
|
a = [self findAll: clients byAbbreviation: dest];
|
|
if ([a count] == 0)
|
|
{
|
|
m = [NSString stringWithFormat:
|
|
@"No such client as '%@'\n", dest];
|
|
}
|
|
else
|
|
{
|
|
unsigned i;
|
|
|
|
m = nil;
|
|
|
|
for (i = 0; i < [a count]; i++)
|
|
{
|
|
EcClientI *c = [a objectAtIndex: i];
|
|
|
|
NS_DURING
|
|
{
|
|
NSData *dat = [NSPropertyListSerialization
|
|
dataFromPropertyList: cmd
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0];
|
|
|
|
[[c obj] cmdMesgData: dat from: f];
|
|
if (m == nil)
|
|
{
|
|
m = [NSString stringWithFormat:
|
|
@"Sent message to %@", [c name]];
|
|
}
|
|
else
|
|
{
|
|
m = [m stringByAppendingFormat:
|
|
@", %@", [c name]];
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Caught exception: %@", localException);
|
|
if (m == nil)
|
|
{
|
|
m = @"Failed to send message!";
|
|
}
|
|
else
|
|
{
|
|
m = [m stringByAppendingFormat:
|
|
@", failed to send to %@", [c name]];
|
|
}
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
if (m != nil)
|
|
m = [m stringByAppendingString: @"\n"];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m = @"Tell where?.\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m = [NSString stringWithFormat: @"Unknown command - '%@'\n", wd];
|
|
}
|
|
|
|
[self information: m from: t to: f type: LT_AUDIT];
|
|
}
|
|
else
|
|
{
|
|
EcClientI *client = [self findIn: clients byName: t];
|
|
|
|
if (client)
|
|
{
|
|
NS_DURING
|
|
{
|
|
NSData *dat = [NSPropertyListSerialization
|
|
dataFromPropertyList: cmd
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0];
|
|
[[client obj] cmdMesgData: dat from: f];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Caught exception: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
else
|
|
{
|
|
NSString *m;
|
|
|
|
m = [NSString stringWithFormat:
|
|
@"command to unregistered client '%@'", t];
|
|
[self information: cmdLogFormat(LT_ERROR, m)
|
|
from: nil
|
|
to: f
|
|
type: LT_ERROR];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSData *) configurationFor: (NSString *)name
|
|
{
|
|
NSMutableDictionary *dict;
|
|
NSString *base;
|
|
NSRange r;
|
|
id o;
|
|
|
|
if (nil == config || 0 == [name length])
|
|
{
|
|
return nil; // Not available
|
|
}
|
|
|
|
r = [name rangeOfString: @"-"
|
|
options: NSBackwardsSearch | NSLiteralSearch];
|
|
if (r.length > 0)
|
|
{
|
|
base = [name substringToIndex: r.location];
|
|
}
|
|
else
|
|
{
|
|
base = nil;
|
|
}
|
|
|
|
dict = [NSMutableDictionary dictionaryWithCapacity: 2];
|
|
o = [config objectForKey: @"*"];
|
|
if (o != nil)
|
|
{
|
|
[dict setObject: o forKey: @"*"];
|
|
}
|
|
|
|
o = [config objectForKey: name]; // Lookup config
|
|
if (base != nil)
|
|
{
|
|
if (nil == o)
|
|
{
|
|
/* No instance specific config found for server,
|
|
* try using the base server name without instance ID.
|
|
*/
|
|
o = [config objectForKey: base];
|
|
}
|
|
else
|
|
{
|
|
id tmp;
|
|
|
|
/* We found instance specific configuration for the server,
|
|
* so we merge by taking values from generic server config
|
|
* (if any) and overwriting them with instance specific values.
|
|
*/
|
|
tmp = [config objectForKey: base];
|
|
if ([tmp isKindOfClass: [NSDictionary class]]
|
|
&& [o isKindOfClass: [NSDictionary class]])
|
|
{
|
|
tmp = [[tmp mutableCopy] autorelease];
|
|
[tmp addEntriesFromDictionary: o];
|
|
o = tmp;
|
|
}
|
|
}
|
|
}
|
|
if (o != nil)
|
|
{
|
|
[dict setObject: o forKey: name];
|
|
}
|
|
|
|
o = [config objectForKey: @"Operators"];
|
|
if (o != nil)
|
|
{
|
|
[dict setObject: o forKey: @"Operators"];
|
|
}
|
|
|
|
return [NSPropertyListSerialization
|
|
dataFromPropertyList: dict
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0];
|
|
}
|
|
|
|
- (BOOL) connection: (NSConnection*)ancestor
|
|
shouldMakeNewConnection: (NSConnection*)newConn
|
|
{
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(connectionBecameInvalid:)
|
|
name: NSConnectionDidDieNotification
|
|
object: (id)newConn];
|
|
[newConn setDelegate: self];
|
|
return YES;
|
|
}
|
|
|
|
- (id) connectionBecameInvalid: (NSNotification*)notification
|
|
{
|
|
id conn = [notification object];
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
removeObserver: self
|
|
name: NSConnectionDidDieNotification
|
|
object: conn];
|
|
if ([conn isKindOfClass: [NSConnection class]])
|
|
{
|
|
NSMutableArray *c;
|
|
NSMutableString *l = [NSMutableString stringWithCapacity: 20];
|
|
NSMutableString *e = [NSMutableString stringWithCapacity: 20];
|
|
NSMutableString *m = [NSMutableString stringWithCapacity: 20];
|
|
BOOL lostClients = NO;
|
|
NSUInteger i;
|
|
|
|
if (control && [(NSDistantObject*)control connectionForProxy] == conn)
|
|
{
|
|
[[self cmdLogFile: logname]
|
|
puts: @"Lost connection to control server.\n"];
|
|
DESTROY(control);
|
|
}
|
|
|
|
/* Now remove the clients from the active list.
|
|
*/
|
|
c = AUTORELEASE([clients mutableCopy]);
|
|
i = [c count];
|
|
while (i-- > 0)
|
|
{
|
|
EcClientI *o = [c objectAtIndex: i];
|
|
|
|
if ([(id)[o obj] connectionForProxy] == conn)
|
|
{
|
|
NSString *name = [o name];
|
|
NSString *s;
|
|
|
|
lostClients = YES;
|
|
[o setUnregistered: YES];
|
|
[clients removeObjectIdenticalTo: o];
|
|
if (i <= pingPosition && pingPosition > 0)
|
|
{
|
|
pingPosition--;
|
|
}
|
|
if ([o transient] == NO)
|
|
{
|
|
EcAlarm *a;
|
|
|
|
s = EcMakeManagedObject(host, name, nil);
|
|
a = [EcAlarm alarmForManagedObject: s
|
|
at: nil
|
|
withEventType: EcAlarmEventTypeProcessingError
|
|
probableCause: EcAlarmSoftwareProgramAbnormallyTerminated
|
|
specificProblem: @"Process availability"
|
|
perceivedSeverity: EcAlarmSeverityCritical
|
|
proposedRepairAction: @"Check system status"
|
|
additionalText: @"removed (lost) server"];
|
|
[self alarm: a];
|
|
}
|
|
else
|
|
{
|
|
s = [NSString stringWithFormat: cmdLogFormat(LT_DEBUG,
|
|
@"removed (lost) server - '%@' on %@"), name, host];
|
|
[l appendString: s];
|
|
}
|
|
}
|
|
}
|
|
|
|
[c removeAllObjects];
|
|
|
|
if ([l length] > 0)
|
|
{
|
|
[[self cmdLogFile: logname] puts: l];
|
|
}
|
|
if ([m length] > 0)
|
|
{
|
|
[self information: m from: nil to: nil type: LT_ALERT];
|
|
}
|
|
if ([e length] > 0)
|
|
{
|
|
[self information: e from: nil to: nil type: LT_ERROR];
|
|
}
|
|
if (lostClients)
|
|
{
|
|
[self update];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[self error: "non-Connection sent invalidation"];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[self cmdLogEnd: logname];
|
|
if (timer != nil)
|
|
{
|
|
[timer invalidate];
|
|
}
|
|
RELEASE(launching);
|
|
RELEASE(launches);
|
|
DESTROY(control);
|
|
RELEASE(host);
|
|
RELEASE(clients);
|
|
RELEASE(launchInfo);
|
|
RELEASE(launchOrder);
|
|
RELEASE(environment);
|
|
RELEASE(lastUnanswered);
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSArray*) findAll: (NSArray*)a
|
|
byAbbreviation: (NSString*)s
|
|
{
|
|
NSMutableArray *r = [NSMutableArray arrayWithCapacity: 4];
|
|
int i;
|
|
|
|
/*
|
|
* Special case - a numeric value is used as an index into the array.
|
|
*/
|
|
if (isdigit(*[s cString]))
|
|
{
|
|
i = [s intValue];
|
|
if (i >= 0 && i < (int)[a count])
|
|
{
|
|
[r addObject: [a objectAtIndex: i]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EcClientI *o;
|
|
|
|
for (i = 0; i < (int)[a count]; i++)
|
|
{
|
|
o = (EcClientI*)[a objectAtIndex: i];
|
|
if (comp(s, [o name]) == 0 || comp_len == (int)[s length])
|
|
{
|
|
[r addObject: o];
|
|
}
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
- (EcClientI*) findIn: (NSArray*)a
|
|
byAbbreviation: (NSString*)s
|
|
{
|
|
EcClientI *o;
|
|
int i;
|
|
int best_pos = -1;
|
|
int best_len = 0;
|
|
|
|
/*
|
|
* Special case - a numeric value is used as an index into the array.
|
|
*/
|
|
if (isdigit(*[s cString]))
|
|
{
|
|
i = [s intValue];
|
|
if (i >= 0 && i < (int)[a count])
|
|
{
|
|
return (EcClientI*)[a objectAtIndex: i];
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < (int)[a count]; i++)
|
|
{
|
|
o = (EcClientI*)[a objectAtIndex: i];
|
|
if (comp(s, [o name]) == 0)
|
|
{
|
|
return o;
|
|
}
|
|
if (comp_len > best_len)
|
|
{
|
|
best_len = comp_len;
|
|
best_pos = i;
|
|
}
|
|
}
|
|
if (best_pos >= 0)
|
|
{
|
|
return (EcClientI*)[a objectAtIndex: best_pos];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (EcClientI*) findIn: (NSArray*)a
|
|
byName: (NSString*)s
|
|
{
|
|
EcClientI *o;
|
|
int i;
|
|
|
|
for (i = 0; i < (int)[a count]; i++)
|
|
{
|
|
o = (EcClientI*)[a objectAtIndex: i];
|
|
|
|
if (comp([o name], s) == 0)
|
|
{
|
|
return o;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (EcClientI*) findIn: (NSArray*)a
|
|
byObject: (id)s
|
|
{
|
|
EcClientI *o;
|
|
int i;
|
|
|
|
for (i = 0; i < (int)[a count]; i++)
|
|
{
|
|
o = (EcClientI*)[a objectAtIndex: i];
|
|
|
|
if ([o obj] == s)
|
|
{
|
|
return o;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void) flush
|
|
{
|
|
/*
|
|
* Flush logs to disk ... dummy method as we don't cache them at present.
|
|
*/
|
|
}
|
|
|
|
- (void) information: (NSString*)inf
|
|
from: (NSString*)s
|
|
type: (EcLogType)t
|
|
{
|
|
[self information: inf from: s to: nil type: t];
|
|
}
|
|
|
|
- (void) information: (NSString*)inf
|
|
from: (NSString*)s
|
|
to: (NSString*)d
|
|
type: (EcLogType)t
|
|
{
|
|
if (t != LT_DEBUG && inf != nil && [inf length] > 0)
|
|
{
|
|
if (control == nil)
|
|
{
|
|
[self timedOut: nil];
|
|
}
|
|
if (control == nil)
|
|
{
|
|
NSLog(@"Information (from:%@ to:%@ type:%d) with no Control -\n%@",
|
|
s, d, t, inf);
|
|
}
|
|
else
|
|
{
|
|
NS_DURING
|
|
{
|
|
[control information: inf type: t to: d from: s];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Sending %@ from %@ to %@ type %x exception: %@",
|
|
inf, s, d, t, localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
}
|
|
|
|
- (id) initWithDefaults: (NSDictionary*)defs
|
|
{
|
|
ecSetLogsSubdirectory(@"Logs");
|
|
if (nil != (self = [super initWithDefaults: defs]))
|
|
{
|
|
debUncompressed = 0.0;
|
|
debUndeleted = 0.0;
|
|
logUncompressed = 0.0;
|
|
logUndeleted = 0.0;
|
|
nodesFree = 0.1;
|
|
spaceFree = 0.1;
|
|
|
|
logname = [[self cmdName] stringByAppendingPathExtension: @"log"];
|
|
RETAIN(logname);
|
|
if ([self cmdLogFile: logname] == nil)
|
|
{
|
|
exit(0);
|
|
}
|
|
host = RETAIN([[NSHost currentHost] wellKnownName]);
|
|
clients = [[NSMutableArray alloc] initWithCapacity: 10];
|
|
launches = [[NSMutableDictionary alloc] initWithCapacity: 10];
|
|
launching = [[NSMutableDictionary alloc] initWithCapacity: 10];
|
|
|
|
timer = [NSTimer scheduledTimerWithTimeInterval: 5.0
|
|
target: self
|
|
selector: @selector(timedOut:)
|
|
userInfo: nil
|
|
repeats: YES];
|
|
[self timedOut: nil];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void) killAll
|
|
{
|
|
#ifndef __MINGW__
|
|
NSUInteger i = [clients count];
|
|
|
|
if (i > 0)
|
|
{
|
|
while (i-- > 0)
|
|
{
|
|
EcClientI *c;
|
|
|
|
c = [clients objectAtIndex: i];
|
|
if (nil != c)
|
|
{
|
|
int p = [c processIdentifier];
|
|
|
|
if (p > 0)
|
|
{
|
|
kill(p, SIGKILL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
- (BOOL) launch: (NSString*)name
|
|
{
|
|
EcClientI *r = [self findIn: clients byName: name];
|
|
|
|
if (nil == r)
|
|
{
|
|
NSDictionary *taskInfo = [launchInfo objectForKey: name];
|
|
NSString *m;
|
|
|
|
if (nil == taskInfo)
|
|
{
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_AUDIT,
|
|
@"unrecognized name to launch %@"), name];
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
return NO;
|
|
}
|
|
else
|
|
{
|
|
NSDictionary *env = environment;
|
|
NSArray *args = [taskInfo objectForKey: @"Args"];
|
|
NSString *home = [taskInfo objectForKey: @"Home"];
|
|
NSString *prog = [taskInfo objectForKey: @"Prog"];
|
|
NSDictionary *addE = [taskInfo objectForKey: @"AddE"];
|
|
NSDictionary *setE = [taskInfo objectForKey: @"SetE"];
|
|
NSString *failed = nil;
|
|
NSTask *task = nil;
|
|
NSString *m;
|
|
|
|
/* As a convenience, the 'Home' option sets the -HomeDirectory
|
|
* for the process.
|
|
*/
|
|
if ([home length] > 0)
|
|
{
|
|
NSMutableArray *a = [[args mutableCopy] autorelease];
|
|
|
|
if (nil == a)
|
|
{
|
|
a = [NSMutableArray arrayWithCapacity: 2];
|
|
}
|
|
[a addObject: @"-HomeDirectory"];
|
|
[a addObject: home];
|
|
args = a;
|
|
}
|
|
|
|
/* Record time of launch start and the fact that this is launching.
|
|
*/
|
|
[launches setObject: [NSDate date] forKey: name];
|
|
if (nil != [launching objectForKey: name])
|
|
{
|
|
NSString *managedObject;
|
|
EcAlarm *a;
|
|
|
|
/* We are re-attempting a launch of a program which never
|
|
* contacted us and registered with us ... raise an alarm.
|
|
*/
|
|
managedObject = EcMakeManagedObject(host, name, nil);
|
|
a = [EcAlarm alarmForManagedObject: managedObject
|
|
at: nil
|
|
withEventType: EcAlarmEventTypeProcessingError
|
|
probableCause: EcAlarmSoftwareProgramAbnormallyTerminated
|
|
specificProblem: @"Process availability"
|
|
perceivedSeverity: EcAlarmSeverityCritical
|
|
proposedRepairAction: @"Check system status"
|
|
additionalText: @"failed to register after launch"];
|
|
[self alarm: a];
|
|
}
|
|
task = [NSTask new];
|
|
[launching setObject: task forKey: name];
|
|
RELEASE(task);
|
|
|
|
if (prog != nil && [prog length] > 0)
|
|
{
|
|
NSFileHandle *hdl;
|
|
|
|
if (setE != nil)
|
|
{
|
|
env = setE;
|
|
}
|
|
else if (env == nil)
|
|
{
|
|
env = [[NSProcessInfo processInfo] environment];
|
|
}
|
|
if (addE != nil)
|
|
{
|
|
NSMutableDictionary *e = [env mutableCopy];
|
|
|
|
[e addEntriesFromDictionary: addE];
|
|
env = AUTORELEASE(e);
|
|
}
|
|
[task setEnvironment: env];
|
|
hdl = [NSFileHandle fileHandleWithNullDevice];
|
|
NS_DURING
|
|
{
|
|
[task setLaunchPath: prog];
|
|
|
|
if ([task validatedLaunchPath] == nil)
|
|
{
|
|
failed = @"failed to launch (not executable)";
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_AUDIT,
|
|
@"failed to launch (not executable) %@"), name];
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
prog = nil;
|
|
}
|
|
if (prog != nil)
|
|
{
|
|
NSString *s;
|
|
|
|
s = [taskInfo objectForKey: @"KeepStandardInput"];
|
|
if (NO == [s respondsToSelector: @selector(boolValue)]
|
|
|| NO == [s boolValue])
|
|
{
|
|
[task setStandardInput: hdl];
|
|
}
|
|
s = [taskInfo objectForKey: @"KeepStandardOutput"];
|
|
if (NO == [s respondsToSelector: @selector(boolValue)]
|
|
|| NO == [s boolValue])
|
|
{
|
|
[task setStandardOutput: hdl];
|
|
}
|
|
s = [taskInfo objectForKey: @"KeepStandardError"];
|
|
if (NO == [s respondsToSelector: @selector(boolValue)]
|
|
|| NO == [s boolValue])
|
|
{
|
|
[task setStandardError: hdl];
|
|
}
|
|
if (home != nil && [home length] > 0)
|
|
{
|
|
[task setCurrentDirectoryPath: home];
|
|
}
|
|
if (args != nil)
|
|
{
|
|
[task setArguments: args];
|
|
}
|
|
[task launch];
|
|
[[self cmdLogFile: logname]
|
|
printf: @"%@ launched %@\n", [NSDate date], prog];
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
failed = @"failed to launch";
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_AUDIT,
|
|
@"failed to launch (%@) %@"), localException, name];
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
else
|
|
{
|
|
failed = @"bad program name to launch";
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_AUDIT,
|
|
@"bad program name to launch %@"), name];
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
}
|
|
if (nil != failed)
|
|
{
|
|
NSString *managedObject;
|
|
|
|
managedObject = EcMakeManagedObject(host, name, nil);
|
|
[self alarmConfigurationFor: managedObject
|
|
specificProblem: @"Process launch"
|
|
additionalText: failed
|
|
critical: NO];
|
|
return NO;
|
|
}
|
|
}
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (void) launch
|
|
{
|
|
if (launchInfo != nil)
|
|
{
|
|
NSEnumerator *enumerator;
|
|
NSString *key;
|
|
NSDate *date;
|
|
NSString *firstKey = nil;
|
|
NSDate *firstDate = nil;
|
|
NSDate *now = [NSDate date];
|
|
|
|
enumerator = [launchOrder objectEnumerator];
|
|
while ((key = [enumerator nextObject]) != nil)
|
|
{
|
|
EcClientI *r = [self findIn: clients byName: key];
|
|
|
|
if (r == nil)
|
|
{
|
|
NSDictionary *taskInfo = [launchInfo objectForKey: key];
|
|
BOOL disabled;
|
|
BOOL autoLaunch;
|
|
|
|
autoLaunch = [[taskInfo objectForKey: @"Auto"] boolValue];
|
|
disabled = [[taskInfo objectForKey: @"Disabled"] boolValue];
|
|
|
|
if (disabled == NO)
|
|
{
|
|
date = [launches objectForKey: key];
|
|
if (nil == date)
|
|
{
|
|
if (autoLaunch == YES)
|
|
{
|
|
NSDate *start;
|
|
NSTimeInterval offset = -(DLY - 5.0);
|
|
|
|
/* If there is no launch date, we set launch
|
|
* dates so that we can try this in 5 seconds.
|
|
*/
|
|
start = [NSDate dateWithTimeIntervalSinceNow: offset];
|
|
[launches setObject: start forKey: key];
|
|
date = start;
|
|
}
|
|
}
|
|
if (date != nil)
|
|
{
|
|
if (firstDate == nil
|
|
|| [date earlierDate: firstDate] == date)
|
|
{
|
|
firstDate = date;
|
|
firstKey = key;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
key = firstKey;
|
|
date = firstDate;
|
|
|
|
if (date != nil && [now timeIntervalSinceDate: date] > DLY)
|
|
{
|
|
[self launch: key];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) logMessage: (NSString*)msg
|
|
type: (EcLogType)t
|
|
for: (id<CmdClient>)o
|
|
{
|
|
EcClientI* r = [self findIn: clients byObject: o];
|
|
NSString *c;
|
|
|
|
if (r == nil)
|
|
{
|
|
c = @"unregistered client";
|
|
}
|
|
else
|
|
{
|
|
c = [r name];
|
|
}
|
|
[self logMessage: msg
|
|
type: t
|
|
name: c];
|
|
}
|
|
|
|
- (void) logMessage: (NSString*)msg
|
|
type: (EcLogType)t
|
|
name: (NSString*)c
|
|
{
|
|
NSString *m;
|
|
|
|
switch (t)
|
|
{
|
|
case LT_DEBUG:
|
|
m = msg;
|
|
break;
|
|
|
|
case LT_WARNING:
|
|
m = msg;
|
|
break;
|
|
|
|
case LT_ERROR:
|
|
m = msg;
|
|
break;
|
|
|
|
case LT_AUDIT:
|
|
m = msg;
|
|
break;
|
|
|
|
case LT_ALERT:
|
|
m = msg;
|
|
break;
|
|
|
|
default:
|
|
m = [NSString stringWithFormat: @"%@: Message of unknown type - %@",
|
|
c, msg];
|
|
break;
|
|
}
|
|
|
|
[[self cmdLogFile: logname] puts: m];
|
|
[self information: m from: c to: nil type: t];
|
|
}
|
|
|
|
- (void) quitAll
|
|
{
|
|
/*
|
|
* Suspend any task that might potentially be started.
|
|
*/
|
|
if (launchInfo != nil)
|
|
{
|
|
NSEnumerator *enumerator;
|
|
NSString *key;
|
|
|
|
enumerator = [launchOrder objectEnumerator];
|
|
while ((key = [enumerator nextObject]) != nil)
|
|
{
|
|
[launches setObject: [NSDate distantFuture] forKey: key];
|
|
}
|
|
}
|
|
|
|
if ([clients count] > 0)
|
|
{
|
|
NSUInteger i;
|
|
NSUInteger j;
|
|
NSMutableArray *a;
|
|
|
|
/* Now we tell all connected clients to quit.
|
|
*/
|
|
i = [clients count];
|
|
a = [NSMutableArray arrayWithCapacity: i];
|
|
while (i-- > 0)
|
|
{
|
|
[a addObject: [[clients objectAtIndex: i] name]];
|
|
}
|
|
for (i = 0; i < [a count]; i++)
|
|
{
|
|
EcClientI *c;
|
|
NSString *n;
|
|
|
|
n = [a objectAtIndex: i];
|
|
c = [self findIn: clients byName: n];
|
|
if (nil != c)
|
|
{
|
|
NS_DURING
|
|
{
|
|
[launches setObject: [NSDate distantFuture] forKey: n];
|
|
[c setTerminating: YES];
|
|
[[c obj] cmdQuit: 0];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Caught exception: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
|
|
/* Give the clients a short time to quit, and re-send
|
|
* the instruction to any which haven't budged.
|
|
*/
|
|
for (j = 0; j < 15; j++)
|
|
{
|
|
NSDate *next = [NSDate dateWithTimeIntervalSinceNow: 2.0];
|
|
|
|
while ([clients count] > 0 && [next timeIntervalSinceNow] > 0.0)
|
|
{
|
|
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
|
beforeDate: next];
|
|
}
|
|
for (i = 0; i < [a count] && [clients count] > 0; i++)
|
|
{
|
|
EcClientI *c;
|
|
NSString *n;
|
|
|
|
n = [a objectAtIndex: i];
|
|
c = [self findIn: clients byName: n];
|
|
if (nil != c)
|
|
{
|
|
NS_DURING
|
|
{
|
|
[launches setObject: [NSDate distantFuture] forKey: n];
|
|
[c setTerminating: YES];
|
|
[[c obj] cmdQuit: 0];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Caught exception: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handle a request for re-config from a client.
|
|
*/
|
|
- (void) requestConfigFor: (id<CmdConfig>)c
|
|
{
|
|
EcClientI *info = [self findIn: clients byObject: (id<CmdClient>)c];
|
|
NSData *conf = [info config];
|
|
|
|
if (nil != conf)
|
|
{
|
|
NS_DURING
|
|
{
|
|
[[info obj] updateConfig: conf];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Sending config to client: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
|
|
- (NSData*) registerClient: (id<CmdClient>)c
|
|
name: (NSString*)n
|
|
{
|
|
return [self registerClient: c name: n transient: NO];
|
|
}
|
|
|
|
- (NSData*) registerClient: (id<CmdClient>)c
|
|
name: (NSString*)n
|
|
transient: (BOOL)t
|
|
{
|
|
NSMutableDictionary *dict;
|
|
EcClientI *obj;
|
|
EcClientI *old;
|
|
NSString *m;
|
|
|
|
[(NSDistantObject*)c setProtocolForProxy: @protocol(CmdClient)];
|
|
|
|
if (nil == config)
|
|
{
|
|
m = [NSString stringWithFormat:
|
|
@"%@ back-off new server with name '%@' on %@\n",
|
|
[NSDate date], n, host];
|
|
[[self cmdLogFile: logname] puts: m];
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
dict = [NSMutableDictionary dictionaryWithCapacity: 1];
|
|
[dict setObject: @"configuration data not yet available."
|
|
forKey: @"back-off"];
|
|
return [NSPropertyListSerialization
|
|
dataFromPropertyList: dict
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0];
|
|
}
|
|
|
|
/*
|
|
* Create a new reference for this client.
|
|
*/
|
|
obj = [[EcClientI alloc] initFor: c name: n with: self];
|
|
|
|
if ((old = [self findIn: clients byName: n]) == nil)
|
|
{
|
|
NSData *d;
|
|
|
|
[clients addObject: obj];
|
|
RELEASE(obj);
|
|
[clients sortUsingSelector: @selector(compare:)];
|
|
|
|
[obj setProcessIdentifier: [c processIdentifier]];
|
|
|
|
/* This client has launched ... remove it from the set of launching
|
|
* clients and the set of launchable clients.
|
|
*/
|
|
[launching removeObjectForKey: n];
|
|
[launches removeObjectForKey: n];
|
|
|
|
/* If this client is in the list of clients launched automatically
|
|
* add its launch timestamp so it will be restarted quickly if it
|
|
* gets shut down.
|
|
*/
|
|
if ([[[launchInfo objectForKey: n] objectForKey: @"Auto"] boolValue])
|
|
{
|
|
[launches setObject: [NSDate date] forKey: n];
|
|
}
|
|
|
|
m = [NSString stringWithFormat:
|
|
@"%@ registered new server with name '%@' on %@\n",
|
|
[NSDate date], n, host];
|
|
[[self cmdLogFile: logname] puts: m];
|
|
if (t == YES)
|
|
{
|
|
[obj setTransient: YES];
|
|
}
|
|
else
|
|
{
|
|
[obj setTransient: NO];
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
}
|
|
[self update];
|
|
d = [self configurationFor: n];
|
|
if (nil != d)
|
|
{
|
|
[obj setConfig: d];
|
|
}
|
|
return [obj config];
|
|
}
|
|
else
|
|
{
|
|
RELEASE(obj);
|
|
m = [NSString stringWithFormat:
|
|
@"%@ rejected new server with name '%@' on %@\n",
|
|
[NSDate date], n, host];
|
|
[[self cmdLogFile: logname] puts: m];
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
dict = [NSMutableDictionary dictionaryWithCapacity: 1];
|
|
[dict setObject: @"client with that name already registered."
|
|
forKey: @"rejected"];
|
|
return [NSPropertyListSerialization
|
|
dataFromPropertyList: dict
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0];
|
|
}
|
|
}
|
|
|
|
- (void) reply: (NSString*) msg to: (NSString*)n from: (NSString*)c
|
|
{
|
|
if (control == nil)
|
|
{
|
|
[self timedOut: nil];
|
|
}
|
|
if (control != nil)
|
|
{
|
|
NS_DURING
|
|
{
|
|
[control reply: msg to: n from: c];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"reply: %@ to: %@ from: %@ - %@", msg, n, c, localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
else
|
|
{
|
|
}
|
|
}
|
|
|
|
- (NSArray*) restartAll
|
|
{
|
|
NSMutableArray *a = nil;
|
|
|
|
/* Quit tasks, but don't suspend them.
|
|
*/
|
|
if ([clients count] > 0)
|
|
{
|
|
unsigned i;
|
|
unsigned j;
|
|
NSDate *when;
|
|
|
|
|
|
/* We tell all connected clients to quit ...
|
|
* clients are allowed 30 seconds to terminate
|
|
* (though we give them 35 to allow for delays).
|
|
*/
|
|
a = [[clients mutableCopy] autorelease];
|
|
i = [a count];
|
|
when = [NSDate dateWithTimeIntervalSinceNow: 35.0 - DLY];
|
|
while (i-- > 0)
|
|
{
|
|
EcClientI *c = [a objectAtIndex: i];
|
|
|
|
if ([clients indexOfObjectIdenticalTo: c] == NSNotFound)
|
|
{
|
|
[a removeObjectAtIndex: i];
|
|
}
|
|
else
|
|
{
|
|
NS_DURING
|
|
{
|
|
[launches setObject: when forKey: [c name]];
|
|
[c setTerminating: YES];
|
|
[[c obj] cmdQuit: 0];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Caught exception: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
|
|
/* Give the clients a short time to quit, and re-send
|
|
* the instruction to any which haven't budged.
|
|
*/
|
|
for (j = 0; j < 15; j++)
|
|
{
|
|
NSDate *next = [NSDate dateWithTimeIntervalSinceNow: 2.0];
|
|
|
|
while ([a count] > 0 && [next timeIntervalSinceNow] > 0.0)
|
|
{
|
|
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
|
beforeDate: next];
|
|
}
|
|
i = [a count];
|
|
when = [NSDate dateWithTimeIntervalSinceNow: 30.0 - DLY];
|
|
while (i-- > 0)
|
|
{
|
|
EcClientI *c = [a objectAtIndex: i];
|
|
|
|
if ([clients indexOfObjectIdenticalTo: c] == NSNotFound)
|
|
{
|
|
[a removeObjectAtIndex: i];
|
|
}
|
|
else
|
|
{
|
|
NS_DURING
|
|
{
|
|
[launches setObject: when forKey: [c name]];
|
|
[c setTerminating: YES];
|
|
[[c obj] cmdQuit: 0];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Caught exception: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
|
|
- (NSString*) makeSpace
|
|
{
|
|
NSInteger deleteAfter;
|
|
NSTimeInterval latestDeleteAt;
|
|
NSTimeInterval now;
|
|
NSTimeInterval ti;
|
|
NSFileManager *mgr;
|
|
NSCalendarDate *when;
|
|
NSString *logs;
|
|
NSString *file;
|
|
NSString *gone;
|
|
NSAutoreleasePool *arp;
|
|
|
|
gone = nil;
|
|
arp = [NSAutoreleasePool new];
|
|
when = [NSCalendarDate date];
|
|
now = [when timeIntervalSinceReferenceDate];
|
|
|
|
logs = [[self ecUserDirectory] stringByAppendingPathComponent: @"DebugLogs"];
|
|
|
|
/* When trying to make space, we can delete up to the point when we
|
|
* would start compressing but no further ... we don't want to delete
|
|
* all logs!
|
|
*/
|
|
deleteAfter = [[self cmdDefaults] integerForKey: @"CompressLogsAfter"];
|
|
if (deleteAfter < 1)
|
|
{
|
|
deleteAfter = 14;
|
|
}
|
|
|
|
mgr = [NSFileManager defaultManager];
|
|
|
|
if (0.0 == debUndeleted)
|
|
{
|
|
debUndeleted = now - 365.0 * day;
|
|
}
|
|
ti = debUndeleted;
|
|
latestDeleteAt = now - day * deleteAfter;
|
|
while (nil == gone && ti < latestDeleteAt)
|
|
{
|
|
when = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate: ti];
|
|
file = [[logs stringByAppendingPathComponent:
|
|
[when descriptionWithCalendarFormat: @"%Y-%m-%d"]]
|
|
stringByStandardizingPath];
|
|
if ([mgr fileExistsAtPath: file])
|
|
{
|
|
[mgr removeFileAtPath: file handler: nil];
|
|
gone = [when descriptionWithCalendarFormat: @"%Y-%m-%d"];
|
|
}
|
|
ti += day;
|
|
}
|
|
debUndeleted = ti;
|
|
RETAIN(gone);
|
|
DESTROY(arp);
|
|
return AUTORELEASE(gone);
|
|
}
|
|
|
|
- (void) _sweep: (BOOL)deb at: (NSCalendarDate*)when
|
|
{
|
|
NSInteger compressAfter;
|
|
NSInteger deleteAfter;
|
|
NSTimeInterval uncompressed;
|
|
NSTimeInterval undeleted;
|
|
NSTimeInterval latestCompressAt;
|
|
NSTimeInterval latestDeleteAt;
|
|
NSTimeInterval now;
|
|
NSTimeInterval ti;
|
|
NSFileManager *mgr;
|
|
NSString *dir;
|
|
NSString *file;
|
|
NSAutoreleasePool *arp;
|
|
|
|
arp = [NSAutoreleasePool new];
|
|
now = [when timeIntervalSinceReferenceDate];
|
|
|
|
/* get number of days after which to do log compression/deletion.
|
|
*/
|
|
compressAfter = [[self cmdDefaults] integerForKey: @"CompressLogsAfter"];
|
|
if (compressAfter < 1)
|
|
{
|
|
compressAfter = 14;
|
|
}
|
|
deleteAfter = [[self cmdDefaults] integerForKey: @"DeleteLogsAfter"];
|
|
if (deleteAfter < 1)
|
|
{
|
|
deleteAfter = 1000;
|
|
}
|
|
if (deleteAfter < compressAfter)
|
|
{
|
|
deleteAfter = compressAfter;
|
|
}
|
|
|
|
mgr = [[NSFileManager new] autorelease];
|
|
|
|
dir = [self ecUserDirectory];
|
|
if (YES == deb)
|
|
{
|
|
dir = [dir stringByAppendingPathComponent: @"DebugLogs"];
|
|
uncompressed = debUncompressed;
|
|
undeleted = debUndeleted;
|
|
}
|
|
else
|
|
{
|
|
dir = [dir stringByAppendingPathComponent: @"Logs"];
|
|
uncompressed = logUncompressed;
|
|
undeleted = logUndeleted;
|
|
}
|
|
if (0.0 == undeleted)
|
|
{
|
|
undeleted = now - 365.0 * day;
|
|
}
|
|
ti = undeleted;
|
|
latestDeleteAt = now - day * deleteAfter;
|
|
while (ti < latestDeleteAt)
|
|
{
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
|
|
when = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate: ti];
|
|
file = [[dir stringByAppendingPathComponent:
|
|
[when descriptionWithCalendarFormat: @"%Y-%m-%d"]]
|
|
stringByStandardizingPath];
|
|
if ([mgr fileExistsAtPath: file])
|
|
{
|
|
[mgr removeFileAtPath: file handler: nil];
|
|
}
|
|
ti += day;
|
|
[pool release];
|
|
}
|
|
if (YES == deb) debUndeleted = ti;
|
|
else logUndeleted = ti;
|
|
|
|
if (uncompressed < undeleted)
|
|
{
|
|
uncompressed = undeleted;
|
|
}
|
|
ti = uncompressed;
|
|
latestCompressAt = now - day * compressAfter;
|
|
while (ti < latestCompressAt)
|
|
{
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
NSDirectoryEnumerator *enumerator;
|
|
BOOL isDirectory;
|
|
NSString *base;
|
|
|
|
when = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate: ti];
|
|
base = [[dir stringByAppendingPathComponent:
|
|
[when descriptionWithCalendarFormat: @"%Y-%m-%d"]]
|
|
stringByStandardizingPath];
|
|
if ([mgr fileExistsAtPath: base isDirectory: &isDirectory] == NO
|
|
|| NO == isDirectory)
|
|
{
|
|
ti += day;
|
|
[pool release];
|
|
continue; // No log directory for this date.
|
|
}
|
|
|
|
enumerator = [mgr enumeratorAtPath: base];
|
|
while ((file = [enumerator nextObject]) != nil)
|
|
{
|
|
NSString *src;
|
|
NSString *dst;
|
|
NSFileHandle *sh;
|
|
NSFileHandle *dh;
|
|
NSDictionary *a;
|
|
NSData *d;
|
|
|
|
if (YES == [[file pathExtension] isEqualToString: @"gz"])
|
|
{
|
|
continue; // Already compressed
|
|
}
|
|
a = [enumerator fileAttributes];
|
|
if (NSFileTypeRegular != [a fileType])
|
|
{
|
|
continue; // Not a regular file ... can't compress
|
|
}
|
|
|
|
src = [base stringByAppendingPathComponent: file];
|
|
|
|
if ([a fileSize] == 0)
|
|
{
|
|
[mgr removeFileAtPath: src handler: nil];
|
|
continue; // Nothing to compress
|
|
}
|
|
|
|
dst = [src stringByAppendingPathExtension: @"gz"];
|
|
if ([mgr fileExistsAtPath: dst isDirectory: &isDirectory] == YES)
|
|
{
|
|
[mgr removeFileAtPath: dst handler: nil];
|
|
}
|
|
|
|
[mgr createFileAtPath: dst contents: nil attributes: nil];
|
|
dh = [NSFileHandle fileHandleForWritingAtPath: dst];
|
|
if (NO == [dh useCompression])
|
|
{
|
|
[dh closeFile];
|
|
[mgr removeFileAtPath: dst handler: nil];
|
|
[self cmdError: @"Unable to compress %@ to %@", src, dst];
|
|
continue;
|
|
}
|
|
|
|
sh = nil;
|
|
NS_DURING
|
|
{
|
|
NSAutoreleasePool *inner;
|
|
|
|
sh = [NSFileHandle fileHandleForReadingAtPath: src];
|
|
inner = [NSAutoreleasePool new];
|
|
while ([(d = [sh readDataOfLength: 1000000]) length] > 0)
|
|
{
|
|
[dh writeData: d];
|
|
[inner release];
|
|
inner = [NSAutoreleasePool new];
|
|
}
|
|
[inner release];
|
|
[sh closeFile];
|
|
[dh closeFile];
|
|
[mgr removeFileAtPath: src handler: nil];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
[mgr removeFileAtPath: dst handler: nil];
|
|
[sh closeFile];
|
|
[dh closeFile];
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
ti += day;
|
|
[pool release];
|
|
}
|
|
if (YES == deb) debUncompressed = ti;
|
|
else logUncompressed = ti;
|
|
|
|
DESTROY(arp);
|
|
}
|
|
|
|
/* Perform this one in another thread.
|
|
* The sweep operation may compress really large logfiles and could be
|
|
* very slow, so it's performed in a separate thread to avoid blocking
|
|
* normal operations.
|
|
*/
|
|
- (void) sweep: (NSCalendarDate*)when
|
|
{
|
|
if (nil == when)
|
|
{
|
|
when = [NSDate date];
|
|
}
|
|
[self _sweep: YES at: when];
|
|
[self _sweep: NO at: when];
|
|
sweeping = NO;
|
|
}
|
|
|
|
- (void) ecNewHour: (NSCalendarDate*)when
|
|
{
|
|
if (sweeping == YES)
|
|
{
|
|
NSLog(@"Argh - nested sweep attempt");
|
|
return;
|
|
}
|
|
sweeping = YES;
|
|
[NSThread detachNewThreadSelector: @selector(sweep:)
|
|
toTarget: self
|
|
withObject: when];
|
|
}
|
|
|
|
|
|
/*
|
|
* Tell all our clients to quit, and wait for them to do so.
|
|
* If called while already terminating ... force immediate shutdown.
|
|
*/
|
|
- (void) terminate: (NSTimer*)t
|
|
{
|
|
if (nil == terminating)
|
|
{
|
|
[self information: @"Handling shutdown."
|
|
from: nil
|
|
to: nil
|
|
type: LT_AUDIT];
|
|
}
|
|
|
|
if (nil == terminating)
|
|
{
|
|
terminating = [NSTimer scheduledTimerWithTimeInterval: 10.0
|
|
target: self selector: @selector(terminate:)
|
|
userInfo: [NSDate new]
|
|
repeats: YES];
|
|
}
|
|
|
|
[self quitAll];
|
|
|
|
if (t != nil)
|
|
{
|
|
NSDate *when = (NSDate*)[t userInfo];
|
|
|
|
if ([when timeIntervalSinceNow] < -30.0)
|
|
{
|
|
[[self cmdLogFile: logname]
|
|
puts: @"Final shutdown.\n"];
|
|
[terminating invalidate];
|
|
terminating = nil;
|
|
[self killAll];
|
|
[self cmdQuit: tStatus];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) terminate
|
|
{
|
|
[self terminate: nil];
|
|
}
|
|
|
|
- (void) timedOut: (NSTimer*)t
|
|
{
|
|
static BOOL inTimeout = NO;
|
|
NSDate *now = [t fireDate];
|
|
|
|
if (now == nil)
|
|
{
|
|
now = [NSDate date];
|
|
}
|
|
|
|
[[self cmdLogFile: logname] synchronizeFile];
|
|
if (inTimeout == NO)
|
|
{
|
|
static unsigned pingControlCount = 0;
|
|
NSFileManager *mgr;
|
|
NSDictionary *a;
|
|
NSString *s;
|
|
float f;
|
|
unsigned count;
|
|
BOOL lost = NO;
|
|
|
|
inTimeout = YES;
|
|
if (control == nil)
|
|
{
|
|
NSUserDefaults *defs;
|
|
NSString *ctlName;
|
|
NSString *ctlHost;
|
|
id c;
|
|
|
|
defs = [self cmdDefaults];
|
|
ctlName = [defs stringForKey: @"ControlName"];
|
|
if (ctlName == nil)
|
|
{
|
|
ctlName = @"Control";
|
|
}
|
|
if (nil != (ctlHost = [NSHost controlWellKnownName]))
|
|
{
|
|
/* Map to operating system host name.
|
|
*/
|
|
ctlHost = [[NSHost hostWithWellKnownName: ctlHost] name];
|
|
}
|
|
if (nil == ctlHost)
|
|
{
|
|
ctlHost = @"*";
|
|
}
|
|
|
|
NS_DURING
|
|
{
|
|
NSLog(@"Connecting to %@ on %@", ctlName, ctlHost);
|
|
control = (id<Control>)[NSConnection
|
|
rootProxyForConnectionWithRegisteredName: ctlName
|
|
host: ctlHost
|
|
usingNameServer: [NSSocketPortNameServer sharedInstance] ];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Connecting to control server: %@", localException);
|
|
control = nil;
|
|
}
|
|
NS_ENDHANDLER
|
|
c = control;
|
|
if (RETAIN(c) != nil)
|
|
{
|
|
/* Re-initialise control server ping */
|
|
DESTROY(lastUnanswered);
|
|
fwdSequence = 0;
|
|
revSequence = 0;
|
|
|
|
[(NSDistantObject*)c setProtocolForProxy: @protocol(Control)];
|
|
c = [(NSDistantObject*)c connectionForProxy];
|
|
[c setDelegate: self];
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(connectionBecameInvalid:)
|
|
name: NSConnectionDidDieNotification
|
|
object: c];
|
|
NS_DURING
|
|
{
|
|
NSData *dat;
|
|
|
|
dat = [control registerCommand: self
|
|
name: host];
|
|
if (nil == dat)
|
|
{
|
|
// Control server not yet ready.
|
|
DESTROY(control);
|
|
}
|
|
else
|
|
{
|
|
NSMutableDictionary *conf;
|
|
|
|
conf = [NSPropertyListSerialization
|
|
propertyListWithData: dat
|
|
options: NSPropertyListMutableContainers
|
|
format: 0
|
|
error: 0];
|
|
if ([conf objectForKey: @"rejected"] == nil)
|
|
{
|
|
[self updateConfig: dat];
|
|
}
|
|
else
|
|
{
|
|
[[self cmdLogFile: logname]
|
|
printf: @"registration attempt rejected - %@\n",
|
|
[conf objectForKey: @"rejected"]];
|
|
DESTROY(control);
|
|
}
|
|
}
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Registering with control server: %@", localException);
|
|
DESTROY(control);
|
|
}
|
|
NS_ENDHANDLER
|
|
if (control != nil)
|
|
{
|
|
[self update];
|
|
}
|
|
}
|
|
}
|
|
[self launch];
|
|
|
|
count = [clients count];
|
|
while (count-- > 0)
|
|
{
|
|
EcClientI *r = [clients objectAtIndex: count];
|
|
NSDate *d = [r lastUnanswered];
|
|
|
|
if (d != nil && [d timeIntervalSinceDate: now] < -pingDelay)
|
|
{
|
|
NSString *m;
|
|
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_AUDIT,
|
|
@"Client '%@' failed to respond for over %d seconds"),
|
|
[r name], (int)pingDelay];
|
|
[[[[r obj] connectionForProxy] sendPort] invalidate];
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
lost = YES;
|
|
}
|
|
}
|
|
if (control != nil && lastUnanswered != nil
|
|
&& [lastUnanswered timeIntervalSinceDate: now] < -pingDelay)
|
|
{
|
|
NSString *m;
|
|
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_AUDIT,
|
|
@"Control server failed to respond for over %d seconds"),
|
|
(int)pingDelay];
|
|
[[(NSDistantObject*)control connectionForProxy] invalidate];
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
lost = YES;
|
|
}
|
|
if (lost == YES)
|
|
{
|
|
[self update];
|
|
}
|
|
/*
|
|
* We ping each client in turn. If there are fewer than 4 clients,
|
|
* we skip timeouts so that clients get pinged no more frequently
|
|
* than one per 4 timeouts.
|
|
*/
|
|
count = [clients count];
|
|
pingPosition++;
|
|
if (pingPosition >= 4 && pingPosition >= count)
|
|
{
|
|
pingPosition = 0;
|
|
}
|
|
if (pingPosition < count)
|
|
{
|
|
[[clients objectAtIndex: pingPosition] ping];
|
|
}
|
|
// Ping the control server too - once every four times.
|
|
pingControlCount++;
|
|
if (pingControlCount >= 4)
|
|
{
|
|
pingControlCount = 0;
|
|
}
|
|
if (pingControlCount == 0)
|
|
{
|
|
[self pingControl];
|
|
}
|
|
|
|
/* See if the filesystem containing our logging directory has enough
|
|
* space.
|
|
*/
|
|
mgr = [NSFileManager defaultManager];
|
|
|
|
s = [[self ecUserDirectory] stringByAppendingPathComponent: @"DebugLogs"];
|
|
a = [mgr fileSystemAttributesAtPath: s];
|
|
f = [[a objectForKey: NSFileSystemFreeSize] floatValue]
|
|
/ [[a objectForKey: NSFileSystemSize] floatValue];
|
|
if (f <= spaceFree)
|
|
{
|
|
static NSDate *last = nil;
|
|
|
|
if (nil == last || [last timeIntervalSinceNow] < -DLY)
|
|
{
|
|
NSString *m;
|
|
|
|
m = [self makeSpace];
|
|
ASSIGN(last, [NSDate date]);
|
|
if ([m length] == 0)
|
|
{
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_ALERT,
|
|
@"Debug disk debug space at %02.1f percent"), f * 100.0];
|
|
}
|
|
else
|
|
{
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_ALERT,
|
|
@"Debug disk space at %02.1f percent"
|
|
@" - deleted debug logs from %@ to make space"),
|
|
f * 100.0, m];
|
|
}
|
|
[self information: m from: nil to: nil type: LT_ALERT];
|
|
}
|
|
}
|
|
f = [[a objectForKey: NSFileSystemFreeNodes] floatValue]
|
|
/ [[a objectForKey: NSFileSystemNodes] floatValue];
|
|
if (f <= nodesFree)
|
|
{
|
|
static NSDate *last = nil;
|
|
|
|
if (nil == last || [last timeIntervalSinceNow] < -DLY)
|
|
{
|
|
NSString *m;
|
|
|
|
m = [self makeSpace];
|
|
ASSIGN(last, [NSDate date]);
|
|
if ([m length] == 0)
|
|
{
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_ALERT,
|
|
@"Debug disk nodes at %02.1f percent"), f * 100.0];
|
|
}
|
|
else
|
|
{
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_ALERT,
|
|
@"Debug disk nodes at %02.1f percent"
|
|
@" - deleted debug logs from %@ to make space"),
|
|
f * 100.0, m];
|
|
}
|
|
[self information: m from: nil to: nil type: LT_ALERT];
|
|
}
|
|
}
|
|
|
|
s = [[self ecUserDirectory] stringByAppendingPathComponent: @"Logs"];
|
|
a = [mgr fileSystemAttributesAtPath: s];
|
|
f = [[a objectForKey: NSFileSystemFreeSize] floatValue]
|
|
/ [[a objectForKey: NSFileSystemSize] floatValue];
|
|
if (f <= spaceFree)
|
|
{
|
|
static NSDate *last = nil;
|
|
|
|
if (nil == last || [last timeIntervalSinceNow] < -DLY)
|
|
{
|
|
NSString *m;
|
|
|
|
ASSIGN(last, [NSDate date]);
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_ALERT,
|
|
@"Disk space at %02.1f percent"), f * 100.0];
|
|
[self information: m from: nil to: nil type: LT_ALERT];
|
|
}
|
|
}
|
|
f = [[a objectForKey: NSFileSystemFreeNodes] floatValue]
|
|
/ [[a objectForKey: NSFileSystemNodes] floatValue];
|
|
if (f <= nodesFree)
|
|
{
|
|
static NSDate *last = nil;
|
|
|
|
if (nil == last || [last timeIntervalSinceNow] < -DLY)
|
|
{
|
|
NSString *m;
|
|
|
|
ASSIGN(last, [NSDate date]);
|
|
m = [NSString stringWithFormat: cmdLogFormat(LT_ALERT,
|
|
@"Disk nodes at %02.1f percent"), f * 100.0];
|
|
[self information: m from: nil to: nil type: LT_ALERT];
|
|
}
|
|
}
|
|
}
|
|
inTimeout = NO;
|
|
}
|
|
|
|
- (void) unregisterByObject: (id)obj
|
|
{
|
|
EcClientI *o = [self findIn: clients byObject: obj];
|
|
|
|
if (o != nil)
|
|
{
|
|
NSString *m;
|
|
NSUInteger i;
|
|
BOOL transient = [o transient];
|
|
NSString *name = [[[o name] retain] autorelease];
|
|
|
|
m = [NSString stringWithFormat:
|
|
@"\n%@ removed (unregistered) server -\n '%@' on %@\n",
|
|
[NSDate date], name, host];
|
|
[[self cmdLogFile: logname] puts: m];
|
|
[o setUnregistered: YES];
|
|
i = [clients indexOfObjectIdenticalTo: o];
|
|
if (i != NSNotFound)
|
|
{
|
|
[clients removeObjectAtIndex: i];
|
|
if (i <= pingPosition && pingPosition > 0)
|
|
{
|
|
pingPosition--;
|
|
}
|
|
}
|
|
if (transient == NO)
|
|
{
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
}
|
|
[self update];
|
|
}
|
|
}
|
|
|
|
- (void) unregisterByName: (NSString*)n
|
|
{
|
|
EcClientI *o = [self findIn: clients byName: n];
|
|
|
|
if (o)
|
|
{
|
|
NSString *m;
|
|
NSUInteger i;
|
|
BOOL transient = [o transient];
|
|
NSString *name = [[[o name] retain] autorelease];
|
|
|
|
m = [NSString stringWithFormat:
|
|
@"\n%@ removed (unregistered) server -\n '%@' on %@\n",
|
|
[NSDate date], name, host];
|
|
[[self cmdLogFile: logname] puts: m];
|
|
[o setUnregistered: YES];
|
|
i = [clients indexOfObjectIdenticalTo: o];
|
|
if (i != NSNotFound)
|
|
{
|
|
[clients removeObjectAtIndex: i];
|
|
if (i <= pingPosition && pingPosition > 0)
|
|
{
|
|
pingPosition--;
|
|
}
|
|
}
|
|
if (transient == NO)
|
|
{
|
|
[self information: m from: nil to: nil type: LT_AUDIT];
|
|
}
|
|
[self update];
|
|
}
|
|
}
|
|
|
|
- (void) update
|
|
{
|
|
if (control == nil)
|
|
{
|
|
[self timedOut: nil];
|
|
}
|
|
if (control)
|
|
{
|
|
NS_DURING
|
|
{
|
|
NSMutableArray *a;
|
|
int i;
|
|
|
|
a = [NSMutableArray arrayWithCapacity: [clients count]];
|
|
for (i = 0; i < (int)[clients count]; i++)
|
|
{
|
|
EcClientI *c;
|
|
|
|
c = [clients objectAtIndex: i];
|
|
[a addObject: [c name]];
|
|
}
|
|
[control servers: [NSPropertyListSerialization
|
|
dataFromPropertyList: a
|
|
format: NSPropertyListBinaryFormat_v1_0
|
|
errorDescription: 0] on: self];
|
|
}
|
|
NS_HANDLER
|
|
{
|
|
NSLog(@"Exception sending servers to Control: %@", localException);
|
|
}
|
|
NS_ENDHANDLER
|
|
}
|
|
if (terminating != nil && [clients count] == 0)
|
|
{
|
|
[self information: @"Final shutdown."
|
|
from: nil
|
|
to: nil
|
|
type: LT_AUDIT];
|
|
[terminating invalidate];
|
|
terminating = nil;
|
|
[self cmdQuit: tStatus];
|
|
}
|
|
}
|
|
|
|
- (void) updateConfig: (NSData*)data
|
|
{
|
|
NSMutableDictionary *info;
|
|
NSMutableDictionary *dict;
|
|
NSMutableDictionary *newConfig;
|
|
NSDictionary *operators;
|
|
NSEnumerator *enumerator;
|
|
NSString *key;
|
|
|
|
/* Ignore invalid/empty configuration
|
|
*/
|
|
if (nil == data)
|
|
{
|
|
return;
|
|
}
|
|
info = [NSPropertyListSerialization
|
|
propertyListWithData: data
|
|
options: NSPropertyListMutableContainers
|
|
format: 0
|
|
error: 0];
|
|
if (NO == [info isKindOfClass: [NSMutableDictionary class]]
|
|
|| 0 == [info count])
|
|
{
|
|
return;
|
|
}
|
|
|
|
newConfig = [NSMutableDictionary dictionaryWithCapacity: 32];
|
|
/*
|
|
* Put all values for this host in the config dictionary.
|
|
*/
|
|
dict = [info objectForKey: host];
|
|
if (dict)
|
|
{
|
|
[newConfig addEntriesFromDictionary: dict];
|
|
}
|
|
/*
|
|
* Add any default values to the config dictionary where we don't have
|
|
* host specific values.
|
|
*/
|
|
dict = [info objectForKey: @"*"];
|
|
if (dict)
|
|
{
|
|
enumerator = [dict keyEnumerator];
|
|
while ((key = [enumerator nextObject]) != nil)
|
|
{
|
|
NSMutableDictionary *partial = [newConfig objectForKey: key];
|
|
NSMutableDictionary *general = [dict objectForKey: key];
|
|
NSString *app = key;
|
|
|
|
if (partial == nil)
|
|
{
|
|
/*
|
|
* No host-specific info for this application -
|
|
* Use the general stuff for the application.
|
|
*/
|
|
[newConfig setObject: general forKey: key];
|
|
}
|
|
else
|
|
{
|
|
NSEnumerator *another = [general keyEnumerator];
|
|
|
|
/*
|
|
* Merge in any values for this application which
|
|
* exist in the general stuff, but not in the host
|
|
* specific area.
|
|
*/
|
|
while ((key = [another nextObject]) != nil)
|
|
{
|
|
if ([partial objectForKey: key] == nil)
|
|
{
|
|
id obj = [general objectForKey: key];
|
|
|
|
[partial setObject: obj forKey: key];
|
|
}
|
|
else
|
|
{
|
|
[[self cmdLogFile: logname]
|
|
printf: @"General config for %@/%@ overridden by"
|
|
@" host-specific version\n", app, key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add the list of operators to the config.
|
|
*/
|
|
operators = [info objectForKey: @"Operators"];
|
|
if (operators != nil)
|
|
{
|
|
[newConfig setObject: operators forKey: @"Operators"];
|
|
}
|
|
|
|
/* Finally, replace old config with new if they differ.
|
|
*/
|
|
[self newConfig: newConfig];
|
|
}
|
|
|
|
@end
|
|
|