-
Notifications
You must be signed in to change notification settings - Fork 2
/
EcCommand.m
7360 lines (6762 loc) · 193 KB
/
EcCommand.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/** Enterprise Control Configuration and Logging
Copyright (C) 2012 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <[email protected]>
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 <GNUstepBase/GSConfig.h>
#if GS_USE_GNUTLS
#import <GNUstepBase/GSTLS.h>
#endif
#import "EcProcess.h"
#import "EcAlarm.h"
#import "EcClientI.h"
#import "EcHost.h"
#import "NSFileHandle+Printf.h"
#import "config.h"
#define DLY 300.0
#define FIB 0.1
static NSCalendarDate *
date(NSTimeInterval t)
{
NSCalendarDate *d;
d = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate: t];
[d setCalendarFormat: @"%Y-%m-%d %H:%M:%S.%F %z"];
return d;
}
static BOOL
restartStatus(int terminationStatus)
{
switch (terminationStatus)
{
case -1:
case -5:
return YES;
default:
return NO;
}
}
static const NSTimeInterval day = 24.0 * 60.0 * 60.0;
static int tStatus = 0;
static NSTimeInterval pingTime = 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 BOOL matchCmd(NSString *word, NSString *reference, NSArray *allow)
{
if (comp(word, reference) < 0)
{
return NO;
}
if (nil == allow || [allow containsObject: reference])
{
return YES;
}
return NO;
}
static NSString*
cmdWord(NSArray* a, unsigned int pos)
{
if (a != nil && [a count] > pos)
{
return [a objectAtIndex: pos];
}
else
{
return @"";
}
}
static BOOL debug = YES;
static NSTimeInterval quitTime = 120.0;
/* When this control process needs to shut down *all* clients,
* we set the date for the shutdown to end.
*/
static NSDate *terminateBy = nil;
static NSUInteger launchLimit = 0;
static BOOL launchEnabled = NO;
static NSMutableDictionary *launchInfo = nil;
static NSArray *launchOrder = nil;
static NSMutableArray *launchQueue = nil;
typedef enum {
ACLaunchFailed, // Must be first
ACProcessHung,
ACProcessLost // Must be last
} AlarmCode;
static void
ACStrings(AlarmCode ac, NSString **problem, NSString **repair)
{
*problem = nil;
*repair = nil;
switch (ac)
{
case ACLaunchFailed:
*problem = @"Launch failed";
*repair = @"Check logs and correct launch/startup failure";
break;
case ACProcessHung:
*problem = @"Process hung";
*repair = @"Check logs and deal with cause of unresponsiveness";
break;
case ACProcessLost:
*problem = @"Process lost";
*repair = @"Check logs and deal with cause of shutdown/crash";
break;
}
}
/* The desired state of a LaunchInfo object says whether the corresponding
* process has a particular state to which it should be returned.
* The initial state is determined by the configuration ffor the process,
* but that may be changed either by instruction (eg the Console commands)
* or by a configuration change.
*/
typedef enum {
None = 0, // The process is free to start/stop
Dead, // The process should be stopped if it is live
Live // The process should be started if it is dead
} Desired;
static NSString *
desiredName(Desired state)
{
switch (state)
{
case Live: return @"Live";
case Dead: return @"Dead";
default: return @"None";
}
}
/**
* starting means that the server has attempted to start the process
* (or is waiting for some precondition of launching) but the
* process has not yet established a connection to the server
* and registered itself.
* stopping means that the server has attempted to shut down the process
* (or the process has told the server it is shutting down), so
* the connection between the process and the server may not
* exist (and should not be used).
* desired defines whether the steady state of this process should
* be running/live or shut down. When the process reaches
* a steady state (ie is not starting or stopping) that does
* not match the desired state, the server shold initiate a
* change of state.
* identifier If the process is shut down, this should be zero.
* Otherwise it is the process ID used by the operating system
* and indicates that the process is starting, stopping, or in
* a steady live state (in which case it should also be
* connected as a client of the server).
*
* If while starting, the process dies, we should schedule relaunches at
* increasing intervals until a process survives and connects.
* If starting takes too long (because launch attempts fail, the processes
* die, or they stay alive but fail to connect to the server),
* we should raise an alarm.
* If stopping takes too long, we should forcibly terminate the process
* if we can, and raise an alarm if we fail to kill it.
*/
@interface LaunchInfo : NSObject
{
/** The name of this process
*/
NSString *name;
/** The configuration from Control.plist
*/
NSDictionary *conf;
/** The current process ID (or zero if there isn't one).
* This is set when we launch the process or when the process is launched
* externally and connects and registers itself with the Command server.
*/
int identifier;
/** The current task (if launched by us).
*/
NSTask *task;
/** The client instance representing a registered distributed objects
* connection from the process into the Command server.
*/
EcClientI *client;
/* Set at the point when a previously registered process is shutting
* down and drops the connection to the Command server. It indicates
* an unintentional shutdown of the process (a failure).
*/
NSTimeInterval clientLostDate;
/* Set at the point when a previously registered process is shutting
* down and cleanly unregisters with the Command server. It indicates
* an intentional shutdown of the process.
*/
NSTimeInterval clientQuitDate;
NSTimeInterval fib0; // fibonacci sequence for delays
NSTimeInterval fib1; // fibonacci sequence for delays
/** Records the desired state for this process (usually when a command
* is issued at the Console). This is needed for situations where the
* system can no respond immediately to an instruction. For instance
* the process is shutting down and a start command is given: we must
* continue to complete the clean shutdown, and then start up again.
*/
Desired desired; // If process *should* be live/dead
/** The timestamp at which the current startup operation began, or zero
* if the process is not currently starting.
*/
NSTimeInterval startingDate;
/** A timer to progress the startup process. When it fires the -starting:
* method is called to check on progress and raise an alarm if startup is
* taking too long.
*/
NSTimer *startingTimer; // Not retained
/* A flag set, during the startup process, if an alarm has been raised
* because startup is taking too long. This prevents re-raising of the
* alarm.
*/
BOOL startingAlarm;
/** A timestamp set if an alarm has been raised because the process is not
* responding to pings. This is cleared if/when the process re-registers.
*/
NSTimeInterval hungDate;
/** A timestamp set when a ping response is received.
*/
NSTimeInterval pingDate;
/** The timestamp at which the process began shutting down, or zero
* if the process is not currently stopping.
*/
NSTimeInterval stoppingDate;
/** A timer to progress the stopping process. When it fires the -stopping:
* method is called to check on progress and, if stopping has taken too long,
* attempt to forcibly terminate the process.
*/
NSTimer *stoppingTimer; // Not retained
/** When a process termination is detected, this variable records it.
*/
NSTimeInterval terminationDate;
/** If, during startup, the process terminates and has to be relaunched,
* we record the count of attempts here.
*/
unsigned terminationCount;
/** Where the Command server launched the process and is able to get the
* process termination status, these variables are used to record it.
*/
int terminationSignal; // Last exit signal
int terminationStatus; // Last exit status
BOOL terminationStatusKnown;
/** The timestamp at which the process registered with the Command server.
* or zero if it has not registered.
*/
NSTimeInterval registrationDate; // Time of process registration
/** The timestamp at which the process told the Command server it had
* completely awakend (was ready to handle requests) or zero if it has
* not woken.
*/
NSTimeInterval awakenedDate; // When the process was awakened
/** If there is a problem causing processes to fail repeatedly and autostart
* to retry, we impose a slightly longer delay between each successive
* relaunch. In that case the deferredDate tells us when the queued
* starting process can next be launched.
*/
NSTimeInterval deferredDate; // Deferred re-launch interval
/** If a starting process cannt be launched immediately, this records the
* timestamp at which it was added to the queue of processes awaiting launch.
*/
NSTimeInterval queuedDate; // When queued for launch
/** Once a process has been active for a while it is considered stable.
* A stable process will, if it terminates without shutting down cleanly,
* be elegible for immediate autolaunch.
*/
NSTimeInterval stableDate; // Has been running for a while
/* On process registration this is set to the timestamp at which the
* process may be considered stable. Normally this is a short while
* after the startup, but if the process was lost it will be set to
* a later date.
*/
NSTimeInterval nextStableDate;
/** The timestamp at which we last launched this process.
*/
NSTimeInterval launchDate; // When we launched process
/** The timestamp at which we will aborting a process (if it fails to shut
* down as quickly as we need it to).
*/
NSTimeInterval abortDate; // When we abort process
/** If we want the process to restart, this reason is why, and is passed
* to the process so that it can log why it was restarted.
*/
NSString *restartReason; // Reason for restart or nil
/** Records the reason we desire the process to be started.
*/
NSString *startedReason;
/** Records the reason we desire the process to be stopped.
*/
NSString *stoppedReason;
/** Records the names of other processes which must be active in order for
* this process to work. Any attempt to start this process will result in
* it remaining in a queue of starting processes until all the dependencies
* have been met.
*/
NSArray *dependencies;
/** Records the alarms currently raised for this process.
*/
NSMutableArray *alarms;
/* Flag used for a manualy stopped process to show that autolaunch is
* no longer to be done.
*/
BOOL manual;
/* Flag to detect recursive call to -starting:
*/
BOOL inStarting;
}
+ (NSString*) description;
+ (LaunchInfo*) existing: (NSString*)name;
+ (LaunchInfo*) find: (NSString*)abbreviation;
+ (LaunchInfo*) launchInfo: (NSString*)name;
+ (NSUInteger) launching;
+ (NSArray*) names;
+ (void) processQueue;
+ (void) remove: (NSString*)name;
/** Adds an alarm to the list raised (removes if a clear is passed in)
* creating the array if it does not exist (even if a clear is passed in).
* This behavior ensures that the -alarms method returns nil if neither
* raises nor clears have taken place, but an array otherwise.
*/
- (void) alarm: (EcAlarm*)alarm;
/** Returns the alarms raised for this process, an empty array if none are
* currently raised, and nil if this is not known (eg if the process raised
* and alarm and then failed to clear the alarm when restarting).
*/
- (NSArray*) alarms;
- (BOOL) autolaunch;
- (void) awakened;
- (BOOL) checkActive;
- (BOOL) checkProcess;
- (void) clearClient: (EcClientI*)c cleanly: (BOOL)unregisteredOrTransient;
- (void) clearHung;
- (EcClientI*) client;
- (NSDictionary*) configuration;
- (NSTimeInterval) delay;
- (Desired) desired;
- (BOOL) disabled;
- (NSTimeInterval) hungDate;
- (BOOL) isActive;
- (BOOL) isStarting;
- (BOOL) isStopping;
- (BOOL) launch;
- (BOOL) manual;
- (BOOL) mayBecomeStable;
- (BOOL) mayCoreDump;
- (NSString*) name;
- (int) processIdentifier;
- (void) progress;
- (NSString*) reasonToPreventLaunch;
- (void) resetDelay;
- (void) setClient: (EcClientI*)c;
- (void) setConfiguration: (NSDictionary*)c;
- (void) setDesired: (Desired)state;
- (void) setHung;
- (void) setManual: (BOOL)f;
- (void) setPing;
- (void) setProcessIdentifier: (int)p;
- (void) setStable: (BOOL)s;
- (void) setTerminationStatus: (int)s;
- (BOOL) stable;
/** Initiates the startup of a process. This will either add the receiver to
* the queue of processes to be started (if it can't be started immediately)
* or launch the process using the configuration set for it. The startup
* will then continue until the launched process registers itself with the
* Command server, at which point the -started method will be called.
*/
- (void) start;
/** Initiates the startup of a process for the supplied reason, but only if
* it makes sense. This method performs the checks to see if the startup
* should take place, then calls -start if it should.
*/
- (void) start: (NSString*)reason;
/** Called automatically when startup of a process completes, either as a
* result of an internal -start or as a result of an externally launched
* process connectin to and registering with the Command server.
*/
- (void) started;
/** internal timer mathos for handling the progression of a startup. If the
* startup takes too long, this method will raise an alarm.
*/
- (void) starting: (NSTimer*)t;
/** Returns a human readble description of the current process status.
*/
- (NSString*) status;
/** Initiates the shut down of a process. This will use the DO connection to
* a registered process to tell it to shut itself down.
* The shutdown will continue until the process no longer exists, but if it
* goes on longer than the time limit, the process will be killed.
*/
- (void) stop;
/** Initiates stopping of a process only if it makes sense. This method
* performs checks and, if the process can be stopped, calls the -stop
* method to do it. This sets the desired state of the process to Dead.
*/
- (void) stop: (NSString*)reason;
/** Called at the point when a stopping process finally ceases to exist.
*/
- (void) stopped;
- (void) stopping: (NSTimer*)t;
- (NSTask*) task;
- (NSArray*) unfulfilled;
@end
/* Special configuration options are:
*
* CompressDebugAfter
* A positive integer number of days after which debug should be compressed
* defaults to 7.
*
* CompressLogsAfter
* A positive integer number of days after which logs should be compressed
* defaults to 7.
*
* DeleteDebugAfter
* A positive integer number of days after which debug should be deleted.
* Constrained to be at least as large as CompressDebugAfter.
* Defaults to 90, but debug may still be deleted as if this were set
* to CompressDebugAfter if NodesFree or SpaceFree is reached.
*
* DeleteLogsAfter
* A positive integer number of days after which logs should be deleted.
* Constrained to be at least as large as CompressLogsAfter.
* Defaults to 180, but logs may still be deleted as if this were set
* to CompressLogsAfter if NodesFree or SpaceFree is reached.
*
* SetE
* A dictionary setting the default environment for launched processes.
*
* AddE
* A dictionary overriding parts of 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 *environment;
NSTimer *terminating;
NSDate *outstanding;
unsigned fwdSequence;
unsigned revSequence;
float nodesFree;
float spaceFree;
NSTimeInterval debUncompressed;
NSTimeInterval debUndeleted;
NSTimeInterval logUncompressed;
NSTimeInterval logUndeleted;
NSInteger debCompressAfter;
NSInteger debDeleteAfter;
NSInteger logCompressAfter;
NSInteger logDeleteAfter;
BOOL sweeping;
}
- (void) alarmCode: (AlarmCode)ac
procName: (NSString*)name
addText: (NSString*)additional;
- (void) auditState: (LaunchInfo*)l reason: (NSString*)additional;
- (void) clear: (EcAlarm*)alarm;
- (void) clearAll: (NSString*)name
addText: (NSString*)additional;
- (void) clearCode: (AlarmCode)ac
procName: (NSString*)name
addText: (NSString*)additional;
- (oneway void) cmdGnip: (id <CmdPing>)from
sequence: (unsigned)num
extra: (NSData*)data;
- (oneway 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;
- (NSDictionary*) environment;
- (NSMutableArray*) 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;
- (NSString*) host;
- (void) housekeeping: (NSTimer*)t;
- (void) _housekeeping: (NSTimer*)t;
- (void) hungRestart: (LaunchInfo*)l;
- (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;
- (NSFileHandle*) logFile;
- (void) logChange: (NSString*)change for: (NSString*)name;
- (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;
- (NSFileHandle*) openLog: (NSString*)lname;
- (void) pingControl;
- (NSString*) quit: (NSString*)name exact: (BOOL)isFullName;
- (void) requestConfigFor: (id<CmdConfig>)c;
- (NSData*) registerClient: (id)c
identifier: (int)p
name: (NSString*)n
transient: (BOOL)t;
- (void) removeClient: (EcClientI*)o cleanly: (BOOL)ok;
- (void) reply: (NSString*) msg to: (NSString*)n from: (NSString*)c;
- (void) terminate: (NSDate*)by;
- (void) _terminate: (NSTimer*)t;
- (NSMutableArray*) unconfiguredClients;
- (void) unregisterByObject: (byref id)obj status: (int)s;
- (void) update;
- (void) updateConfig: (NSData*)data;
- (void) woken: (id)obj;
@end
@implementation LaunchInfo
+ (NSString*) description
{
NSEnumerator *e = [launchInfo objectEnumerator];
LaunchInfo *l;
unsigned autolaunch = 0;
unsigned disabled = 0;
unsigned launchable = 0;
unsigned starting = 0;
unsigned stopping = 0;
unsigned suspended = 0;
unsigned alive = 0;
while (nil != (l = [e nextObject]))
{
if ([l isStarting])
{
starting++;
}
else if ([l isStopping])
{
stopping++;
}
else if ([l processIdentifier] > 0)
{
alive++;
}
else if ([l disabled])
{
disabled++;
}
else
{
if ([l autolaunch])
{
if (Dead == l->desired || [l manual])
{
suspended++;
}
else
{
autolaunch++;
}
}
else
{
launchable++;
}
}
}
return [NSString stringWithFormat: @"LaunchInfo alive:%u, starting:%u,"
@" stopping:%u disabled:%u, suspended:%u, launchable:%u (auto:%u)\n",
alive, starting, stopping, disabled, suspended, launchable, autolaunch];
}
+ (LaunchInfo*) existing: (NSString*)name
{
LaunchInfo *l = RETAIN([launchInfo objectForKey: name]);
return AUTORELEASE(l);
}
+ (LaunchInfo*) find: (NSString*)abbreviation
{
LaunchInfo *l = [launchInfo objectForKey: abbreviation];
if (nil == l)
{
NSEnumerator *e = [launchInfo keyEnumerator];
NSString *s;
NSInteger bestLength = 0;
NSString *bestName = nil;
while (nil != (s = [e nextObject]))
{
if (comp(abbreviation, s) == 0)
{
bestName = s;
break;
}
if (comp_len > bestLength)
{
bestLength = comp_len;
bestName = s;
}
}
if (bestName != nil)
{
l = [launchInfo objectForKey: bestName];
}
}
return l;
}
+ (LaunchInfo*) findTask: (NSTask*)t
{
LaunchInfo *l = nil;
NSEnumerator *e = [launchInfo objectEnumerator];
while (nil != (l = [e nextObject]))
{
if (l->task == t)
{
return l;
}
}
return nil;
}
+ (void) initialize
{
if (nil == launchInfo)
{
launchInfo = [NSMutableDictionary new];
launchQueue = [NSMutableArray new];
}
}
+ (LaunchInfo*) launchInfo: (NSString*)name
{
LaunchInfo *l = [launchInfo objectForKey: name];
if (nil == RETAIN(l))
{
l = [self new];
l->desired = None;
ASSIGNCOPY(l->name, name);
[launchInfo setObject: l forKey: l->name];
}
return AUTORELEASE(l);
}
/* Return the number of processes actually launching (ie with a task running
* but starting not finished.
*/
+ (NSUInteger) launching
{
NSUInteger found = 0;
ENTER_POOL
NSEnumerator *e = [launchInfo objectEnumerator];
LaunchInfo *l;
while (nil != (l = [e nextObject]))
{
if ([l isStarting] && l->task != nil)
{
found++;
}
}
LEAVE_POOL
return found;
}
+ (NSArray*) names
{
return [launchInfo allKeys];
}
/* Check each process in the queue to see if it may now be launched.
* Launch each process which may do so (removing it from the queue).
*/
+ (void) processQueue
{
ENTER_POOL
NSUInteger count;
/* We work with a copy of the queue in case the process of launching
* causes the queue contents to be changed.
*/
if ((count = [launchQueue count]) > 0)
{
NSArray *q = AUTORELEASE([launchQueue copy]);
NSUInteger index;
for (index = 0; index < count; index++)
{
LaunchInfo *l = [q objectAtIndex: index];
if ([launchQueue containsObject: l])
{
NSString *r = [l reasonToPreventLaunch];
if (nil == r)
{
[launchQueue removeObject: l];
l->queuedDate = 0.0;
[l starting: nil];
}
}
}
}
LEAVE_POOL
}
+ (void) remove: (NSString*)name
{
LaunchInfo *l = [launchInfo objectForKey: name];
if (l != nil)
{
/* Detach the removed object from its client, destroy the task,
* and cancel timers/notifications so that the removed object
* will not try to manage anything before it is deallocated.
*/
[[NSNotificationCenter defaultCenter] removeObserver: l];
l->client = nil;
[l taskCleanup: l->task];
[l->startingTimer invalidate];
l->startingTimer = nil;
[l->stoppingTimer invalidate];
l->stoppingTimer = nil;
[launchInfo removeObjectForKey: name];
}
}
- (NSTimeInterval) abortDateFromStoppingDate: (NSTimeInterval)stopping
{
NSTimeInterval when = stopping;
NSTimeInterval ti = 0.0;
NSString *s;
s = [[self configuration] objectForKey: @"QuitTime"];
if ([s respondsToSelector: @selector(intValue)])
{
ti = (NSTimeInterval)[s intValue];
}
if (ti <= 0.0)
{
ti = quitTime;
}
when += ti;
if (terminateBy)
{
ti = [terminateBy timeIntervalSinceReferenceDate];
if (ti < when)
{
when = ti;
}
}
return when;
}
- (void) alarm: (EcAlarm*)alarm
{
NSUInteger index;
if (nil == alarms)
{
alarms = [NSMutableArray new];
}
index = [alarms indexOfObject: alarm];
if (EcAlarmSeverityCleared == [alarm perceivedSeverity])
{
if (NSNotFound != index)
{
[alarms removeObjectAtIndex: index];
}
}
else
{
if (NSNotFound == index)
{
[alarms addObject: alarm];
}
else
{
[alarms replaceObjectAtIndex: index withObject: alarm];
}
}
}
- (NSArray*) alarms
{
return AUTORELEASE([alarms copy]);
}
- (BOOL) autolaunch
{
return [[conf objectForKey: @"Auto"] boolValue];
}
- (void) awakened
{
awakenedDate = [NSDate timeIntervalSinceReferenceDate];
}
/* Check to see if there is an active process connected (or which connects
* when we contact it and ask it to).
*/
- (BOOL) checkActive
{
if ([self hungDate] > 0.0 && identifier > 0)
{
/* A hung process may be considered active but there is no point
* trying to send a DO message to communicate with it and ask it
* to respond to us, because 'hung' means that we have a DO connection
* to the process but it's not talking to us over it.
*/
return YES;
}
if (nil == client)
{
ENTER_POOL
/* When the Command server starts up, or on other rare occasions, we may
* have processes which are running but unknown to the Command server.
* To handle that we try, as part of the launch process, to establish a
* Distributed Objects connection to a process before we try to launch
* the executable.
* If a DO connection is established, we ask the process to reconnect to
* the command server, and that incoming connection will cause the client
* ivar to be set (and the instance ivar to be set to the process ID), so
* we know we don't have to start a subtask.
*/
NS_DURING
{
NSConnection *c;
c = [NSConnection
connectionWithRegisteredName: name
host: @""
usingNameServer: [NSSocketPortNameServer sharedInstance]];
NS_DURING
{
id<CmdClient> proxy;
/* Do not hang waiting for the other end to respond.
*/
[c setRequestTimeout: 10.0];
[c setReplyTimeout: 10.0];
proxy = (id<CmdClient>)[c rootProxy];
/* Sending an ecReconnect message to the client process should
* result in our 'client' ivar being set.
*/
[proxy ecReconnect];
[c setRequestTimeout: 0.0];
[c setReplyTimeout: 0.0];
}
NS_HANDLER