-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
DaemonController.m
241 lines (189 loc) · 7.03 KB
/
DaemonController.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
#import "DaemonController.h"
#import <sys/sysctl.h>
/** returns the pid of the running playdar instance, or 0 if not found */
static pid_t playdar_pid()
{
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
struct kinfo_proc *info;
size_t N;
pid_t pid = 0;
if(sysctl(mib, 3, NULL, &N, NULL, 0) < 0)
return 0; //wrong but unlikely
if(!(info = NSZoneMalloc(NULL, N)))
return 0; //wrong but unlikely
if(sysctl(mib, 3, info, &N, NULL, 0) < 0)
goto end;
N = N / sizeof(struct kinfo_proc);
for(size_t i = 0; i < N; i++)
if(strcmp(info[i].kp_proc.p_comm, "playdar.smp") == 0)
{ pid = info[i].kp_proc.p_pid; break; }
end:
NSZoneFree(NULL, info);
return pid;
}
static void kqueue_termination_callback(CFFileDescriptorRef f, CFOptionFlags callBackTypes, void* self)
{
[(id)self performSelector:@selector(daemonTerminated:) withObject:nil];
}
static inline void kqueue_watch_pid(pid_t pid, id self)
{
int kq;
struct kevent changes;
CFFileDescriptorContext context = { 0, self, NULL, NULL, NULL };
CFRunLoopSourceRef rls;
// Create the kqueue and set it up to watch for SIGCHLD. Use the
// new-in-10.5 EV_RECEIPT flag to ensure that we get what we expect.
kq = kqueue();
EV_SET(&changes, pid, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, NULL);
(void) kevent(kq, &changes, 1, &changes, 1, NULL);
// Wrap the kqueue in a CFFileDescriptor (new in Mac OS X 10.5!). Then
// create a run-loop source from the CFFileDescriptor and add that to the
// runloop.
CFFileDescriptorRef ref;
ref = CFFileDescriptorCreate(NULL, kq, true, kqueue_termination_callback, &context);
rls = CFFileDescriptorCreateRunLoopSource(NULL, ref, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
CFFileDescriptorEnableCallBacks(ref, kCFFileDescriptorReadCallBack);
}
#define START_POLL poll_timer = [NSTimer scheduledTimerWithTimeInterval:0.33 target:self selector:@selector(poll:) userInfo:nil repeats:true];
@implementation DaemonController
-(id)initWithDelegate:(id)d andRootDir:(NSString*)r
{
delegate = [d retain];
root = [r retain];
if(pid = playdar_pid())
kqueue_watch_pid(pid, self); // watch the pid for termination
else
START_POLL;
return self;
}
-(bool)isRunning
{
return daemon_task && [daemon_task isRunning] || (pid = playdar_pid());
}
-(NSString*)playdarctl
{
return [root stringByAppendingPathComponent:@"bin/playdarctl"];
}
-(void)daemonTerminated:(NSNotification*)note
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:daemon_task];
daemon_task = nil;
pid = 0;
START_POLL;
[delegate performSelector:@selector(playdarStopped)];
}
-(void)stop
{
NSTask* task = [[NSTask alloc] init];
task.launchPath = [self playdarctl];
task.arguments = [NSArray arrayWithObjects:@"stop", nil];
[delegate performSelector:@selector(playdarIsStopping)];
[task launch];
[task waitUntilExit];
if (task.terminationStatus == 0)
// the kqueue event will tell us when the process exits
return;
// playdarctl failed to stop the daemon, so it's time to be more forceful
if(daemon_task)
[daemon_task terminate];
else if (pid = playdar_pid() == 0){
// actually we weren't even running in the first place
START_POLL
[delegate performSelector:@selector(playdarStopped)];
}else if (kill(pid, SIGTERM) == -1 && errno != ESRCH)
[delegate performSelector:@selector(playdarFailedToStop) withObject:@"Perhaps you don't have the right permissions?"];
}
-(void)initDaemonTask
{
[poll_timer invalidate];
poll_timer = nil;
[delegate performSelector:@selector(playdarIsStarting)];
daemon_task = [[NSTask alloc] init];
}
-(void)failedToStartDaemonTask
{
NSMutableString* msg = [@"The file at “" mutableCopy];
[msg appendString:daemon_task.launchPath];
[msg appendString:@"” could not be executed."];
[delegate performSelector:@selector(playdarFailedToStart:) withObject:msg];
daemon_task = nil;
[msg release];
}
-(void)start
{
@try {
[self initDaemonTask];
daemon_task.launchPath = [self playdarctl];
daemon_task.arguments = [NSArray arrayWithObject:@"start-exec"];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(daemonTerminated:)
name:NSTaskDidTerminateNotification
object:daemon_task];
[daemon_task launch];
pid = daemon_task.processIdentifier;
#define CHECK_READY_FOR_SCAN \
[self performSelector:@selector(checkReadyForScan) withObject:nil afterDelay:0.2];
CHECK_READY_FOR_SCAN
}
@catch (NSException* e) {
[self failedToStartDaemonTask];
}
}
-(void)startInTerminal
{
NSMutableString* script = [@"tell application \"Terminal\" to do script \"" mutableCopy];
[script appendString:root];
[script appendString:@"/bin/playdarctrl start-debug; exit\""];
@try {
[self initDaemonTask];
daemon_task.launchPath = @"/usr/bin/osascript";
daemon_task.arguments = [NSArray arrayWithObjects:@"-e", script, nil];
daemon_task.environment = [NSDictionary dictionaryWithObject:root forKey:@"PLAYDAR_ROOT"];
[daemon_task launch];
[daemon_task waitUntilExit];
daemon_task = nil;
CHECK_READY_FOR_SCAN
}
@catch (NSException* e) {
[self failedToStartDaemonTask];
}
[script release];
}
-(void)checkReadyForScan
{
int const n = [self numFiles];
if (n < 0)
CHECK_READY_FOR_SCAN
else {
if (!pid) // started via Terminal route perhaps
kqueue_watch_pid(pid = playdar_pid(), self);
[delegate performSelector:@selector(playdarStarted:) withObject:[NSNumber numberWithInt:n]];
}
}
-(void)poll:(NSTimer*)t
{
if (pid = playdar_pid() == 0) return;
[delegate performSelector:@selector(playdarIsStarting)];
[poll_timer invalidate];
poll_timer = nil;
kqueue_watch_pid(pid, self);
[self checkReadyForScan];
}
/////////////////////////////////////////////////////////////////////////// misc
-(int)numFiles
{
NSTask* task = [[NSTask alloc] init];
[task setLaunchPath:[self playdarctl]];
[task setArguments:[NSArray arrayWithObject:@"numfiles"]];
[task setStandardOutput:[NSPipe pipe]];
[task launch];
[task waitUntilExit];
// if not zero then we library module isn't ready yet
if (task.terminationStatus != 0)
return -1;
NSData* data = [[[task standardOutput] fileHandleForReading] readDataToEndOfFile];
return [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] intValue];
}
@end