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