xref: /inferno-os/emu/port/devcmd.c (revision 4eb166cf184c1f102fb79e31b1465ea3e2021c39)
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