xref: /inferno-os/utils/ntsrv/ntsrv.c (revision 7ef44d652ae9e5e1f5b3465d73684e4a54de73c0)
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
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 *
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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