xref: /inferno-os/emu/port/devcmd.c (revision b43c1ca5eb5fc65b93ae935a568432712797b049)
1 #include	"dat.h"
2 #include	"fns.h"
3 #include	"error.h"
4 
5 enum
6 {
7 	Qtopdir,	/* top level directory */
8 	Qcmd,
9 	Qclonus,
10 	Qconvdir,
11 	Qconvbase,
12 	Qdata = Qconvbase,
13 	Qstderr,
14 	Qctl,
15 	Qstatus,
16 	Qwait,
17 
18 	Debug=0	/* to help debug os.c */
19 };
20 #define TYPE(x) 	((ulong)(x).path & 0xf)
21 #define CONV(x) 	(((ulong)(x).path >> 4)&0xfff)
22 #define QID(c, y) 	(((c)<<4) | (y))
23 
24 typedef struct Conv	Conv;
25 struct Conv
26 {
27 	int	x;
28 	int	inuse;
29 	int	fd[3];	/* stdin, stdout, and stderr */
30 	int	count[3];	/* number of readers on stdin/stdout/stderr */
31 	int	perm;
32 	char*	owner;
33 	char*	state;
34 	Cmdbuf*	cmd;
35 	char*	dir;
36 	QLock	l;	/* protects state changes */
37 	Queue*	waitq;
38 	void*	child;
39 	char*	error;	/* on start up */
40 	int	nice;
41 	short	killonclose;
42 	short	killed;
43 	Rendez	startr;
44 };
45 
46 static struct
47 {
48 	QLock	l;
49 	int	nc;
50 	int	maxconv;
51 	Conv**	conv;
52 } cmd;
53 
54 static	Conv*	cmdclone(char*);
55 static	void	cmdproc(void*);
56 
57 static int
58 cmd3gen(Chan *c, int i, Dir *dp)
59 {
60 	Qid q;
61 	Conv *cv;
62 
63 	cv = cmd.conv[CONV(c->qid)];
64 	switch(i){
65 	default:
66 		return -1;
67 	case Qdata:
68 		mkqid(&q, QID(CONV(c->qid), Qdata), 0, QTFILE);
69 		devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
70 		return 1;
71 	case Qstderr:
72 		mkqid(&q, QID(CONV(c->qid), Qstderr), 0, QTFILE);
73 		devdir(c, q, "stderr", 0, cv->owner, 0444, dp);
74 		return 1;
75 	case Qctl:
76 		mkqid(&q, QID(CONV(c->qid), Qctl), 0, QTFILE);
77 		devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
78 		return 1;
79 	case Qstatus:
80 		mkqid(&q, QID(CONV(c->qid), Qstatus), 0, QTFILE);
81 		devdir(c, q, "status", 0, cv->owner, 0444, dp);
82 		return 1;
83 	case Qwait:
84 		mkqid(&q, QID(CONV(c->qid), Qwait), 0, QTFILE);
85 		devdir(c, q, "wait", 0, cv->owner, 0444, dp);
86 		return 1;
87 	}
88 }
89 
90 static int
91 cmdgen(Chan *c, char *name, Dirtab *d, int nd, int s, Dir *dp)
92 {
93 	Qid q;
94 	Conv *cv;
95 
96 	USED(name);
97 	USED(nd);
98 	USED(d);
99 
100 	if(s == DEVDOTDOT){
101 		switch(TYPE(c->qid)){
102 		case Qtopdir:
103 		case Qcmd:
104 			mkqid(&q, QID(0, Qtopdir), 0, QTDIR);
105 			devdir(c, q, "#C", 0, eve, DMDIR|0555, dp);
106 			break;
107 		case Qconvdir:
108 			mkqid(&q, QID(0, Qcmd), 0, QTDIR);
109 			devdir(c, q, "cmd", 0, eve, DMDIR|0555, dp);
110 			break;
111 		default:
112 			panic("cmdgen %llux", c->qid.path);
113 		}
114 		return 1;
115 	}
116 
117 	switch(TYPE(c->qid)) {
118 	case Qtopdir:
119 		if(s >= 1)
120 			return -1;
121 		mkqid(&q, QID(0, Qcmd), 0, QTDIR);
122 		devdir(c, q, "cmd", 0, "cmd", DMDIR|0555, dp);
123 		return 1;
124 	case Qcmd:
125 		if(s < cmd.nc) {
126 			cv = cmd.conv[s];
127 			mkqid(&q, QID(s, Qconvdir), 0, QTDIR);
128 			sprint(up->genbuf, "%d", s);
129 			devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
130 			return 1;
131 		}
132 		s -= cmd.nc;
133 		if(s == 0){
134 			mkqid(&q, QID(0, Qclonus), 0, QTFILE);
135 			devdir(c, q, "clone", 0, "cmd", 0666, dp);
136 			return 1;
137 		}
138 		return -1;
139 	case Qclonus:
140 		if(s == 0){
141 			mkqid(&q, QID(0, Qclonus), 0, QTFILE);
142 			devdir(c, q, "clone", 0, "cmd", 0666, dp);
143 			return 1;
144 		}
145 		return -1;
146 	case Qconvdir:
147 		return cmd3gen(c, Qconvbase+s, dp);
148 	case Qdata:
149 	case Qstderr:
150 	case Qctl:
151 	case Qstatus:
152 	case Qwait:
153 		return cmd3gen(c, TYPE(c->qid), dp);
154 	}
155 	return -1;
156 }
157 
158 static void
159 cmdinit(void)
160 {
161 	cmd.maxconv = 1000;
162 	cmd.conv = mallocz(sizeof(Conv*)*(cmd.maxconv+1), 1);
163 	/* cmd.conv is checked by cmdattach, below */
164 }
165 
166 static Chan *
167 cmdattach(char *spec)
168 {
169 	Chan *c;
170 
171 	if(cmd.conv == nil)
172 		error(Enomem);
173 	c = devattach('C', spec);
174 	mkqid(&c->qid, QID(0, Qtopdir), 0, QTDIR);
175 	return c;
176 }
177 
178 static Walkqid*
179 cmdwalk(Chan *c, Chan *nc, char **name, int nname)
180 {
181 	return devwalk(c, nc, name, nname, 0, 0, cmdgen);
182 }
183 
184 static int
185 cmdstat(Chan *c, uchar *db, int n)
186 {
187 	return devstat(c, db, n, 0, 0, cmdgen);
188 }
189 
190 static Chan *
191 cmdopen(Chan *c, int omode)
192 {
193 	int perm;
194 	Conv *cv;
195 	char *user;
196 
197 	perm = 0;
198 	omode = openmode(omode);
199 	switch(omode) {
200 	case OREAD:
201 		perm = 4;
202 		break;
203 	case OWRITE:
204 		perm = 2;
205 		break;
206 	case ORDWR:
207 		perm = 6;
208 		break;
209 	}
210 
211 	switch(TYPE(c->qid)) {
212 	default:
213 		break;
214 	case Qtopdir:
215 	case Qcmd:
216 	case Qconvdir:
217 	case Qstatus:
218 		if(omode != OREAD)
219 			error(Eperm);
220 		break;
221 	case Qclonus:
222 		qlock(&cmd.l);
223 		if(waserror()){
224 			qunlock(&cmd.l);
225 			nexterror();
226 		}
227 		cv = cmdclone(up->env->user);
228 		poperror();
229 		qunlock(&cmd.l);
230 		if(cv == 0)
231 			error(Enodev);
232 		mkqid(&c->qid, QID(cv->x, Qctl), 0, QTFILE);
233 		break;
234 	case Qdata:
235 	case Qstderr:
236 	case Qctl:
237 	case Qwait:
238 		qlock(&cmd.l);
239 		cv = cmd.conv[CONV(c->qid)];
240 		qlock(&cv->l);
241 		if(waserror()){
242 			qunlock(&cv->l);
243 			qunlock(&cmd.l);
244 			nexterror();
245 		}
246 		user = up->env->user;
247 		if((perm & (cv->perm>>6)) != perm) {
248 			if(strcmp(user, cv->owner) != 0 ||
249 		 	  (perm & cv->perm) != perm)
250 				error(Eperm);
251 		}
252 		switch(TYPE(c->qid)){
253 		case Qdata:
254 			if(omode == OWRITE || omode == ORDWR)
255 				cv->count[0]++;
256 			if(omode == OREAD || omode == ORDWR)
257 				cv->count[1]++;
258 			break;
259 		case Qstderr:
260 			if(omode != OREAD)
261 				error(Eperm);
262 			cv->count[2]++;
263 			break;
264 		case Qwait:
265 			if(cv->waitq == nil)
266 				cv->waitq = qopen(1024, Qmsg, nil, 0);
267 			break;
268 		}
269 		cv->inuse++;
270 		if(cv->inuse == 1) {
271 			cv->state = "Open";
272 			kstrdup(&cv->owner, user);
273 			cv->perm = 0660;
274 			cv->nice = 0;
275 		}
276 		poperror();
277 		qunlock(&cv->l);
278 		qunlock(&cmd.l);
279 		break;
280 	}
281 	c->mode = omode;
282 	c->flag |= COPEN;
283 	c->offset = 0;
284 	return c;
285 }
286 
287 static void
288 closeconv(Conv *c)
289 {
290 	kstrdup(&c->owner, "cmd");
291 	kstrdup(&c->dir, rootdir);
292 	c->perm = 0666;
293 	c->state = "Closed";
294 	c->killonclose = 0;
295 	c->killed = 0;
296 	c->nice = 0;
297 	free(c->cmd);
298 	c->cmd = nil;
299 	if(c->waitq != nil){
300 		qfree(c->waitq);
301 		c->waitq = nil;
302 	}
303 	free(c->error);
304 	c->error = nil;
305 }
306 
307 static void
308 cmdfdclose(Conv *c, int fd)
309 {
310 	if(--c->count[fd] == 0 && c->fd[fd] != -1){
311 		close(c->fd[fd]);
312 		c->fd[fd] = -1;
313 	}
314 }
315 
316 static void
317 cmdclose(Chan *c)
318 {
319 	Conv *cc;
320 	int r;
321 
322 	if((c->flag & COPEN) == 0)
323 		return;
324 
325 	switch(TYPE(c->qid)) {
326 	case Qctl:
327 	case Qdata:
328 	case Qstderr:
329 	case Qwait:
330 		cc = cmd.conv[CONV(c->qid)];
331 		qlock(&cc->l);
332 		if(TYPE(c->qid) == Qdata){
333 			if(c->mode == OWRITE || c->mode == ORDWR)
334 				cmdfdclose(cc, 0);
335 			if(c->mode == OREAD || c->mode == ORDWR)
336 				cmdfdclose(cc, 1);
337 		}else if(TYPE(c->qid) == Qstderr)
338 			cmdfdclose(cc, 2);
339 
340 		r = --cc->inuse;
341 		if(cc->child != nil){
342 			if(!cc->killed)
343 			if(r == 0 || (cc->killonclose && TYPE(c->qid) == Qctl)){
344 				oscmdkill(cc->child);
345 				cc->killed = 1;
346 			}
347 		}else if(r == 0)
348 			closeconv(cc);
349 
350 		qunlock(&cc->l);
351 		break;
352 	}
353 }
354 
355 static long
356 cmdread(Chan *ch, void *a, long n, vlong offset)
357 {
358 	Conv *c;
359 	char *p, *cmds;
360 	int fd;
361 
362 	USED(offset);
363 
364 	p = a;
365 	switch(TYPE(ch->qid)) {
366 	default:
367 		error(Eperm);
368 	case Qcmd:
369 	case Qtopdir:
370 	case Qconvdir:
371 		return devdirread(ch, a, n, 0, 0, cmdgen);
372 	case Qctl:
373 		sprint(up->genbuf, "%ld", CONV(ch->qid));
374 		return readstr(offset, p, n, up->genbuf);
375 	case Qstatus:
376 		c = cmd.conv[CONV(ch->qid)];
377 		cmds = "";
378 		if(c->cmd != nil)
379 			cmds = c->cmd->f[1];
380 		snprint(up->genbuf, sizeof(up->genbuf), "cmd/%d %d %s %q %q\n",
381 			c->x, c->inuse, c->state, c->dir, cmds);
382 		return readstr(offset, p, n, up->genbuf);
383 	case Qdata:
384 	case Qstderr:
385 		fd = 1;
386 		if(TYPE(ch->qid) == Qstderr)
387 			fd = 2;
388 		c = cmd.conv[CONV(ch->qid)];
389 		qlock(&c->l);
390 		if(c->fd[fd] == -1){
391 			qunlock(&c->l);
392 			return 0;
393 		}
394 		qunlock(&c->l);
395 		osenter();
396 		n = read(c->fd[fd], a, n);
397 		osleave();
398 		if(n < 0)
399 			oserror();
400 		return n;
401 	case Qwait:
402 		c = cmd.conv[CONV(ch->qid)];
403 		return qread(c->waitq, a, n);
404 	}
405 }
406 
407 static int
408 cmdstarted(void *a)
409 {
410 	Conv *c;
411 
412 	c = a;
413 	return c->child != nil || c->error != nil || strcmp(c->state, "Execute") != 0;
414 }
415 
416 enum
417 {
418 	CMdir,
419 	CMexec,
420 	CMkill,
421 	CMnice,
422 	CMkillonclose
423 };
424 
425 static
426 Cmdtab cmdtab[] = {
427 	CMdir,	"dir",	2,
428 	CMexec,	"exec",	0,
429 	CMkill,	"kill",	1,
430 	CMnice,	"nice",	0,
431 	CMkillonclose, "killonclose", 0,
432 };
433 
434 static long
435 cmdwrite(Chan *ch, void *a, long n, vlong offset)
436 {
437 	int i, r;
438 	Conv *c;
439 	Cmdbuf *cb;
440 	Cmdtab *ct;
441 
442 	USED(offset);
443 
444 	switch(TYPE(ch->qid)) {
445 	default:
446 		error(Eperm);
447 	case Qctl:
448 		c = cmd.conv[CONV(ch->qid)];
449 		cb = parsecmd(a, n);
450 		if(waserror()){
451 			free(cb);
452 			nexterror();
453 		}
454 		ct = lookupcmd(cb, cmdtab, nelem(cmdtab));
455 		switch(ct->index){
456 		case CMdir:
457 			kstrdup(&c->dir, cb->f[1]);
458 			break;
459 		case CMexec:
460 			poperror();	/* cb */
461 			qlock(&c->l);
462 			if(waserror()){
463 				qunlock(&c->l);
464 				free(cb);
465 				nexterror();
466 			}
467 			if(c->child != nil || c->cmd != nil)
468 				error(Einuse);
469 			for(i = 0; i < nelem(c->fd); i++)
470 				if(c->fd[i] != -1)
471 					error(Einuse);
472 			if(cb->nf < 1)
473 				error(Etoosmall);
474 			kproc("cmdproc", cmdproc, c, 0);	/* cmdproc held back until unlock below */
475 			free(c->cmd);
476 			c->cmd = cb;	/* don't free cb */
477 			c->state = "Execute";
478 			poperror();
479 			qunlock(&c->l);
480 			while(waserror())
481 				;
482 			Sleep(&c->startr, cmdstarted, c);
483 			poperror();
484 			if(c->error)
485 				error(c->error);
486 			return n;	/* avoid free(cb) below */
487 		case CMkill:
488 			qlock(&c->l);
489 			if(waserror()){
490 				qunlock(&c->l);
491 				nexterror();
492 			}
493 			if(c->child == nil)
494 				error("not started");
495 			if(oscmdkill(c->child) < 0)
496 				oserror();
497 			poperror();
498 			qunlock(&c->l);
499 			break;
500 		case CMnice:
501 			c->nice = cb->nf > 1? atoi(cb->f[1]): 1;
502 			break;
503 		case CMkillonclose:
504 			c->killonclose = 1;
505 			break;
506 		}
507 		poperror();
508 		free(cb);
509 		break;
510 	case Qdata:
511 		c = cmd.conv[CONV(ch->qid)];
512 		qlock(&c->l);
513 		if(c->fd[0] == -1){
514 			qunlock(&c->l);
515 			error(Ehungup);
516 		}
517 		qunlock(&c->l);
518 		osenter();
519 		r = write(c->fd[0], a, n);
520 		osleave();
521 		if(r == 0)
522 			error(Ehungup);
523 		if(r < 0) {
524 			/* XXX perhaps should kill writer "write on closed pipe" here, 2nd time around? */
525 			oserror();
526 		}
527 		return r;
528 	}
529 	return n;
530 }
531 
532 static int
533 cmdwstat(Chan *c, uchar *dp, int n)
534 {
535 	Dir *d;
536 	Conv *cv;
537 
538 	switch(TYPE(c->qid)){
539 	default:
540 		error(Eperm);
541 	case Qctl:
542 	case Qdata:
543 	case Qstderr:
544 		d = malloc(sizeof(*d)+n);
545 		if(d == nil)
546 			error(Enomem);
547 		if(waserror()){
548 			free(d);
549 			nexterror();
550 		}
551 		n = convM2D(dp, n, d, (char*)&d[1]);
552 		if(n == 0)
553 			error(Eshortstat);
554 		cv = cmd.conv[CONV(c->qid)];
555 		if(!iseve() && strcmp(up->env->user, cv->owner) != 0)
556 			error(Eperm);
557 		if(!emptystr(d->uid))
558 			kstrdup(&cv->owner, d->uid);
559 		if(d->mode != ~0UL)
560 			cv->perm = d->mode & 0777;
561 		poperror();
562 		free(d);
563 		break;
564 	}
565 	return n;
566 }
567 
568 static Conv*
569 cmdclone(char *user)
570 {
571 	Conv *c, **pp, **ep;
572 	int i;
573 
574 	c = nil;
575 	ep = &cmd.conv[cmd.maxconv];
576 	for(pp = cmd.conv; pp < ep; pp++) {
577 		c = *pp;
578 		if(c == nil) {
579 			c = malloc(sizeof(Conv));
580 			if(c == nil)
581 				error(Enomem);
582 			qlock(&c->l);
583 			c->inuse = 1;
584 			c->x = pp - cmd.conv;
585 			cmd.nc++;
586 			*pp = c;
587 			break;
588 		}
589 		if(canqlock(&c->l)){
590 			if(c->inuse == 0 && c->child == nil)
591 				break;
592 			qunlock(&c->l);
593 		}
594 	}
595 	if(pp >= ep)
596 		return nil;
597 
598 	c->inuse = 1;
599 	kstrdup(&c->owner, user);
600 	kstrdup(&c->dir, rootdir);
601 	c->perm = 0660;
602 	c->state = "Closed";
603 	for(i=0; i<nelem(c->fd); i++)
604 		c->fd[i] = -1;
605 
606 	qunlock(&c->l);
607 	return c;
608 }
609 
610 static void
611 cmdproc(void *a)
612 {
613 	Conv *c;
614 	int n;
615 	char status[ERRMAX];
616 	void *t;
617 
618 	c = a;
619 	qlock(&c->l);
620 	if(Debug)
621 		print("f[0]=%q f[1]=%q\n", c->cmd->f[0], c->cmd->f[1]);
622 	if(waserror()){
623 		if(Debug)
624 			print("failed: %q\n", up->env->errstr);
625 		kstrdup(&c->error, up->env->errstr);
626 		c->state = "Done";
627 		qunlock(&c->l);
628 		Wakeup(&c->startr);
629 		pexit("cmdproc", 0);
630 	}
631 	t = oscmd(c->cmd->f+1, c->nice, c->dir, c->fd);
632 	if(t == nil)
633 		oserror();
634 	c->child = t;	/* to allow oscmdkill */
635 	poperror();
636 	qunlock(&c->l);
637 	Wakeup(&c->startr);
638 	if(Debug)
639 		print("started\n");
640 	while(waserror())
641 		oscmdkill(t);
642 	osenter();
643 	n = oscmdwait(t, status, sizeof(status));
644 	osleave();
645 	if(n < 0){
646 		oserrstr(up->genbuf, sizeof(up->genbuf));
647 		n = snprint(status, sizeof(status), "0 0 0 0 %q", up->genbuf);
648 	}
649 	qlock(&c->l);
650 	c->child = nil;
651 	oscmdfree(t);
652 	if(Debug){
653 		status[n]=0;
654 		print("done %d %d %d: %q\n", c->fd[0], c->fd[1], c->fd[2], status);
655 	}
656 	if(c->inuse > 0){
657 		c->state = "Done";
658 		if(c->waitq != nil)
659 			qproduce(c->waitq, status, n);
660 	}else
661 		closeconv(c);
662 	qunlock(&c->l);
663 	pexit("", 0);
664 }
665 
666 Dev cmddevtab = {
667 	'C',
668 	"cmd",
669 
670 	cmdinit,
671 	cmdattach,
672 	cmdwalk,
673 	cmdstat,
674 	cmdopen,
675 	devcreate,
676 	cmdclose,
677 	cmdread,
678 	devbread,
679 	cmdwrite,
680 	devbwrite,
681 	devremove,
682 	cmdwstat
683 };
684