1 #ifndef ForNT4
2 /* only for XP, 2000 and above - JobObject only available on these*/
3 #undef _WIN32_WINNT
4 #define _WIN32_WINNT 0x0500
5 #endif
6
7 #include "lib9.h"
8 #include <windows.h>
9 #include <winsvc.h>
10 #include <stdarg.h>
11
12 #ifdef JOB_OBJECT_TERMINATE
13 #define Jobs
14 #endif
15
16 #ifdef ForNT4
17 #undef Jobs
18 #endif
19
20 /*
21 * ntsrv add Name Inferno-root Cmds
22 * ntsrv del Name
23 * ntsrv run Name Inferno-root Cmds
24 *
25 * 'add' registers service: Name with args "run Name Inferno-root Cmds"
26 * 'del' unregisters service Name
27 * 'run' - only given by NT service manager when starting the service (see 'add')
28 *
29 * Cmds are cat'd (with space separator) and requoted for CreateProcess()
30 *
31 * There must be an ntsrv.exe in Inferno-root/Nt/386/bin
32 */
33
34
35 SERVICE_STATUS status = {
36 SERVICE_WIN32_OWN_PROCESS, /* dwServiceType */
37 0, /* dwCurrentState */
38 SERVICE_ACCEPT_STOP /* dwControlsAccepted */
39 };
40
41 typedef struct Emu Emu;
42 struct Emu {
43 HANDLE proc; /* NULL if error */
44 HANDLE job; /* job for all children */
45 HANDLE stdin; /* stdio pipes */
46 HANDLE stdout;
47 DWORD notepg; /* process group ID (in case we lack Jobs) */
48 };
49
50 typedef struct Copyargs Copyargs;
51 struct Copyargs {
52 HANDLE in;
53 HANDLE out;
54 };
55
56 #ifdef Jobs
57 static char *myname = "ntsrv.exe";
58 #else
59 static char *myname = "ntsrv4.exe";
60 #endif
61 #define LOGFILE "grid\\slave\\svclog"
62 static char *name;
63 static char *root;
64 static char *cmds;
65 static SERVICE_STATUS_HANDLE statush;
66 static HANDLE emujob; /* win32 job object for emu session */
67 static DWORD emugroup; /* process group ID for emu session */
68 static HANDLE emuin; /* stdin pipe of emu */
69 static HANDLE logfile;
70
71 HANDLE openlog(char*);
72 void logmsg(char*, ...);
73 void WINAPI infmain(ulong, LPTSTR[]);
74 void WINAPI infctl(ulong);
75 Emu runemu(char*);
76 HANDLE exporthandle(HANDLE, int);
77 DWORD WINAPI copy(LPVOID);
78 int shuttingdown = 0;
79 int nice = 0;
80
81 static void
usage()82 usage()
83 {
84 fprint(2, "usage: ntsrv [-n] add name root cmds | del name\n");
85 }
86
87 /* (from rcsh)
88 * windows quoting rules - I think
89 * Words are seperated by space or tab
90 * Words containing a space or tab can be quoted using "
91 * 2N backslashes + " ==> N backslashes and end quote
92 * 2N+1 backslashes + " ==> N backslashes + literal "
93 * N backslashes not followed by " ==> N backslashes
94 */
95 static char *
dblquote(char * cmd,char * s)96 dblquote(char *cmd, char *s)
97 {
98 int nb;
99 char *p;
100
101 for(p=s; *p; p++)
102 if(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == '"')
103 break;
104
105 if(*p == 0){ /* easy case */
106 strcpy(cmd, s);
107 return cmd+(p-s);
108 }
109
110 *cmd++ = '"';
111 for(;;) {
112 for(nb=0; *s=='\\'; nb++)
113 *cmd++ = *s++;
114
115 if(*s == 0) { /* trailing backslashes -> 2N */
116 while(nb-- > 0)
117 *cmd++ = '\\';
118 break;
119 }
120
121 if(*s == '"') { /* literal quote -> 2N+1 backslashes */
122 while(nb-- > 0)
123 *cmd++ = '\\';
124 *cmd++ = '\\'; /* escape the quote */
125 }
126 *cmd++ = *s++;
127 }
128
129 *cmd++ = '"';
130 *cmd = 0;
131
132 return cmd;
133 }
134
135 static char *
proccmd(char ** argv)136 proccmd(char **argv)
137 {
138 int i, n;
139 char *cmd, *p;
140
141 /* conservatively calculate length of command;
142 * backslash expansion can cause growth in dblquote().
143 */
144 for(i=0,n=0; argv[i]; i++) {
145 n += 2*strlen(argv[i]);
146 }
147 n++;
148
149 cmd = malloc(n);
150 for(i=0,p=cmd; argv[i]; i++) {
151 p = dblquote(p, argv[i]);
152 *p++ = ' ';
153 }
154 if(p != cmd)
155 p--;
156 *p = 0;
157
158 return cmd;
159 }
160
161 int
installnewemu()162 installnewemu()
163 {
164 LPCTSTR currpath, newpath;
165 currpath = smprint("%s\\Nt\\386\\bin\\emu.exe", root);
166 newpath = smprint("%s\\Nt\\386\\bin\\newemu.exe", root);
167 if(GetFileAttributes(newpath) == 0xffffffff) // INVALID_FILE_ATTRIBUTES is not defined
168 return 0;
169 DeleteFile(currpath); // ignore error message - it might not be there.
170 if(MoveFile(newpath, currpath) == 0){
171 logmsg("cannot rename %s to %s: %r", newpath, currpath);
172 return -1;
173 }
174 return 0;
175 }
176
177 void WINAPI
infmain(ulong argc,char * argv[])178 infmain(ulong argc, char *argv[])
179 {
180 HANDLE cpt;
181 Emu emu;
182 Copyargs cp;
183 DWORD tid;
184 char *cmd;
185
186 argc--;
187 argv++;
188 cmd = smprint("%s\\%s", root, LOGFILE);
189 logfile = openlog(cmd);
190 free(cmd);
191 statush = RegisterServiceCtrlHandler(name, infctl);
192 if (statush == 0)
193 return;
194
195 status.dwCurrentState = SERVICE_START_PENDING;
196 SetServiceStatus(statush, &status);
197
198 while(installnewemu() != -1){
199 /* start the service */
200 cmd = smprint("%s\\Nt\\386\\bin\\emu.exe -r%s %s", root, root, cmds);
201 logmsg("starting %s", cmd);
202 emu = runemu(cmd);
203 free(cmd);
204 if (emu.proc == NULL) {
205 logmsg("runemu failed: %r");
206 status.dwCurrentState = SERVICE_STOPPED;
207 SetServiceStatus(statush, &status);
208 return;
209 }
210
211 cp.in = emu.stdout;
212 cp.out = logfile;
213 cpt = CreateThread(NULL, 0, copy, (void*)&cp, 0, &tid);
214 if (cpt == NULL) {
215 logmsg("failed to create copy thread: %r");
216 CloseHandle(emu.stdout);
217 }
218
219 logmsg("infmain blocking on emu proc");
220 emujob = emu.job;
221 emugroup = emu.notepg;
222 status.dwCurrentState = SERVICE_RUNNING;
223 SetServiceStatus(statush, &status);
224 WaitForSingleObject(emu.proc, INFINITE);
225 logmsg("infmain emu proc terminated");
226 emujob = NULL;
227 emugroup = 0;
228 #ifdef Jobs
229 logmsg("terminating job");
230 TerminateJobObject(emu.job, 0);
231 #else
232 logmsg("notepg (%d)", emu.notepg);
233 if(emu.notepg)
234 GenerateConsoleCtrlEvent(CTRL_C_EVENT, emu.notepg);
235 #endif
236 if (cpt) {
237 /* copy() sees eof on emu.stdout and exits */
238 WaitForSingleObject(cpt, INFINITE);
239 CloseHandle(cpt);
240 CloseHandle(emu.stdout);
241 }
242 CloseHandle(emu.proc);
243 if(emu.job != NULL)
244 CloseHandle(emu.job);
245 CloseHandle(emu.stdin);
246 // XXX should check to see that we're not starting up again too quickly, as
247 // it's quite possible to get into an infinite loop here.
248 // but what are good criteria? 5 times? 100 times?
249 // 10 times within a minute?
250 // for the moment, just sleep for a while before restarting...
251 if(shuttingdown)
252 break;
253 SleepEx(10000, FALSE);
254 }
255 logmsg("infmain done");
256 if (logfile)
257 CloseHandle(logfile);
258 status.dwCurrentState = SERVICE_STOPPED;
259 SetServiceStatus(statush, &status);
260 return;
261 }
262
263 void WINAPI
infctl(ulong op)264 infctl(ulong op)
265 {
266 if (op != SERVICE_CONTROL_STOP)
267 return;
268
269 /* stop the service (status set by infmain()
270 *
271 * NOTE: there is a race for emujob - may have been closed
272 * after test, but before TerminateJobObject()
273 * MSDN is unclear as to whether TerminatJobObject() handles
274 * NULL job ptr - should probably use a mutex
275 */
276 shuttingdown = 1;
277 #ifdef Jobs
278 logmsg("svc stop: stopping job");
279 if (emujob)
280 TerminateJobObject(emujob, 0);
281 #else
282 logmsg("svc stop: interrupting emu");
283 if (emugroup)
284 GenerateConsoleCtrlEvent(CTRL_C_EVENT, emugroup);
285 #endif
286 }
287
288 void
printerror(char * s)289 printerror(char *s)
290 {
291 char *msg;
292 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
293 FORMAT_MESSAGE_FROM_SYSTEM|
294 FORMAT_MESSAGE_IGNORE_INSERTS,
295 NULL,
296 GetLastError(),
297 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
298 (LPTSTR)&msg,
299 0,
300 NULL);
301 fprint(2, "%s: %s\n", s, msg);
302 LocalFree(msg);
303 }
304
305 int
add(char * name,char * root,char * cmds)306 add(char *name, char *root, char *cmds)
307 {
308 char *path;
309 int r;
310 SC_HANDLE scm, scs;
311 char *nopt;
312
313 nopt = nice ? " -n" : "";
314 path = smprint("%s\\Nt\\386\\bin\\%s%s run %s %s %s", root, myname, nopt, name, root, cmds);
315 r = 0;
316 scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
317 if (scm == NULL) {
318 printerror("cannot open service control manager");
319 return -1;
320 }
321 scs = CreateService(scm,
322 name,
323 name,
324 SERVICE_START|SERVICE_STOP,
325 SERVICE_WIN32_OWN_PROCESS,
326 SERVICE_AUTO_START,
327 SERVICE_ERROR_IGNORE,
328 path,
329 NULL,
330 NULL,
331 NULL,
332 NULL,
333 NULL
334 );
335 if (scs == NULL) {
336 printerror("cannot create service");
337 r = -1;
338 } else {
339 CloseServiceHandle(scs);
340 }
341 CloseServiceHandle(scm);
342 return r;
343 }
344
345 int
del(char * name)346 del(char *name)
347 {
348 SC_HANDLE scm, scs;
349
350 scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
351 if (scm == NULL) {
352 printerror("cannot open service control manager");
353 return -1;
354 }
355
356 scs = OpenService(scm, name, DELETE);
357 if (scs == NULL) {
358 printerror("cannot open service");
359 CloseServiceHandle(scm);
360 return -1;
361 }
362 if (!DeleteService(scs)) {
363 printerror("cannot delete Iservice");
364 CloseServiceHandle(scs);
365 CloseServiceHandle(scm);
366 return -1;
367 }
368 CloseServiceHandle(scs);
369 CloseServiceHandle(scm);
370 return 0;
371 }
372
373 HANDLE
openlog(char * p)374 openlog(char *p)
375 {
376 HANDLE h;
377 h = CreateFile(p, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
378 if (h == INVALID_HANDLE_VALUE)
379 return NULL;
380 SetFilePointer(h, 0, NULL, FILE_END);
381 return h;
382 }
383
384 void
logmsg(char * fmt,...)385 logmsg(char *fmt, ...)
386 {
387 int n;
388 char *p;
389 va_list args;
390 if(logfile == 0)
391 return;
392 va_start(args, fmt);
393 p = vsmprint(fmt, args);
394 va_end(args);
395 n = strlen(p);
396 if (n)
397 WriteFile(logfile, p, n, &n, NULL);
398 WriteFile(logfile, "\n", 1, &n, NULL);
399 }
400
401 Emu
runemu(char * cmd)402 runemu(char *cmd)
403 {
404 Emu r = {NULL, NULL, NULL};
405 STARTUPINFO si;
406 PROCESS_INFORMATION pinfo;
407 HANDLE job, emu, emut, stdin, stdout, stderr, emui, emuo;
408 SECURITY_ATTRIBUTES sec;
409 DWORD flags;
410
411 job = emu = emut = stdin = stdout = stderr = emui = emuo = NULL;
412 #ifdef Jobs
413 job = CreateJobObject(NULL, NULL);
414 if (job == NULL) {
415 logmsg("cannot create job object: %r");
416 goto error;
417 }
418 #endif
419
420 /* set up pipes */
421 sec.nLength = sizeof(sec);
422 sec.lpSecurityDescriptor = 0;
423 sec.bInheritHandle = 0;
424 if (!CreatePipe(&stdin, &emui, &sec, 0)) {
425 logmsg("cannot create stdin pipe: %r");
426 goto error;
427 }
428 if (!CreatePipe(&emuo, &stdout, &sec, 0)) {
429 logmsg("cannot create stdout pipe: %r");
430 goto error;
431 }
432 stdin = exporthandle(stdin, 1);
433 stdout = exporthandle(stdout, 1);
434 stderr = exporthandle(stdout, 0);
435
436 /* create emu process (suspended) */
437 memset(&si, 0, sizeof(si));
438 si.cb = sizeof(si);
439 si.dwFlags = STARTF_USESTDHANDLES;
440 si.hStdInput = stdin;
441 si.hStdOutput = stdout;
442 si.hStdError = stderr;
443
444 flags = CREATE_NEW_PROCESS_GROUP|CREATE_DEFAULT_ERROR_MODE|CREATE_SUSPENDED;
445 if(nice)
446 flags |= IDLE_PRIORITY_CLASS;
447 if(!CreateProcess(0, cmd, 0, 0, 1, flags, 0, 0, &si, &pinfo)) {
448 logmsg("cannot create process: %r");
449 goto error;
450 }
451 emu = pinfo.hProcess;
452 emut = pinfo.hThread;
453 CloseHandle(stdin);
454 stdin = NULL;
455 CloseHandle(stdout);
456 stdout = NULL;
457 CloseHandle(stderr);
458 stderr = NULL;
459
460 #ifdef Jobs
461 if(!AssignProcessToJobObject(job, emu)) {
462 logmsg("failed to assign emu to job: %r");
463 goto error;
464 }
465 #endif
466 ResumeThread(emut);
467 CloseHandle(emut);
468
469 r.proc = emu;
470 r.notepg = pinfo.dwProcessId;
471 r.job = job; /* will be NULL if not implemented (NT4) */
472 r.stdin = emui;
473 r.stdout = emuo;
474 return r;
475
476 error:
477 if (stdin)
478 CloseHandle(stdin);
479 if (stdout)
480 CloseHandle(stdout);
481 if (stderr)
482 CloseHandle(stderr);
483 if (emui)
484 CloseHandle(emuin);
485 if (emuo)
486 CloseHandle(emuo);
487 if (emut)
488 CloseHandle(emut);
489 if (emu) {
490 TerminateProcess(emu, 0);
491 CloseHandle(emu);
492 }
493 if (job)
494 CloseHandle(job);
495 return r;
496 }
497
498 HANDLE
exporthandle(HANDLE h,int close)499 exporthandle(HANDLE h, int close)
500 {
501 HANDLE cp, dh;
502 DWORD flags = DUPLICATE_SAME_ACCESS;
503 if (close)
504 flags |= DUPLICATE_CLOSE_SOURCE;
505 cp = GetCurrentProcess();
506 if (!DuplicateHandle(cp, h, cp, &dh, DUPLICATE_SAME_ACCESS, 1, flags))
507 return nil;
508 return dh;
509 }
510
511 DWORD WINAPI
copy(void * arg)512 copy(void *arg)
513 {
514 Copyargs *cp = (Copyargs*)arg;
515 char buf[1024];
516 DWORD n;
517
518 while (ReadFile(cp->in, buf, sizeof(buf), &n, NULL)) {
519 if (n && cp->out)
520 WriteFile(cp->out, buf, n, &n, NULL);
521 }
522 return 0;
523 }
524
525 void
main(int argc,char * argv[])526 main(int argc, char *argv[])
527 {
528 char *verb;
529 SERVICE_TABLE_ENTRY services[2];
530
531 memset(services, 0, sizeof(services));
532
533 ARGBEGIN{
534 case 'n':
535 nice = 1;
536 break;
537 default:
538 usage();
539 }ARGEND
540
541 if (argc < 2) {
542 usage();
543 return;
544 }
545
546 verb = argv[0];
547 name = argv[1];
548 if (argc > 2)
549 root = argv[2];
550 if (argc > 3)
551 cmds = proccmd(argv+3);
552
553 if (strcmp(verb, "del") == 0)
554 exit(del(name));
555 if (strcmp(verb, "add") == 0) {
556 if (root == NULL || cmds == NULL) {
557 usage();
558 return;
559 }
560 exit(add(name, root, cmds));
561 }
562 if (strcmp(verb, "run") == 0) {
563 if (root == NULL || cmds == NULL || *cmds == '\0') {
564 usage();
565 return;
566 }
567 services[0].lpServiceName = name;
568 services[0].lpServiceProc = infmain;
569 StartServiceCtrlDispatcher(services);
570 exit(0);
571 }
572 usage();
573 }
574