ref: e56902a661c4c62e04596b3c2c58e43ed8328590
dir: /kern/devcmd.c/
#include "u.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"
#include "error.h"
enum
{
Qtopdir, /* top level directory */
Qcmd,
Qclonus,
Qconvdir,
Qconvbase,
Qdata = Qconvbase,
Qstderr,
Qctl,
Qstatus,
Qwait,
Debug=0 /* to help debug os.c */
};
#define TYPE(x) ((ulong)(x).path & 0xf)
#define CONV(x) (((ulong)(x).path >> 4)&0xfff)
#define QID(c, y) (((c)<<4) | (y))
typedef struct Conv Conv;
struct Conv
{
int x;
int inuse;
Chan* fd[3]; /* stdin, stdout, and stderr */
int count[3]; /* number of readers on stdin/stdout/stderr */
int perm;
char* owner;
char* state;
Cmdbuf* cmd;
char* dir;
QLock l; /* protects state changes */
Queue* waitq;
void* child;
char* error; /* on start up */
int nice;
short killonclose;
short killed;
Rendez startr;
};
static struct
{
QLock l;
int nc;
int maxconv;
Conv** conv;
} cmd;
static Conv* cmdclone(char*);
static void cmdproc(void*);
static int
cmd3gen(Chan *c, int i, Dir *dp)
{
Qid q;
Conv *cv;
cv = cmd.conv[CONV(c->qid)];
switch(i){
default:
return -1;
case Qdata:
mkqid(&q, QID(CONV(c->qid), Qdata), 0, QTFILE);
devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
return 1;
case Qstderr:
mkqid(&q, QID(CONV(c->qid), Qstderr), 0, QTFILE);
devdir(c, q, "stderr", 0, cv->owner, 0444, dp);
return 1;
case Qctl:
mkqid(&q, QID(CONV(c->qid), Qctl), 0, QTFILE);
devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
return 1;
case Qstatus:
mkqid(&q, QID(CONV(c->qid), Qstatus), 0, QTFILE);
devdir(c, q, "status", 0, cv->owner, 0444, dp);
return 1;
case Qwait:
mkqid(&q, QID(CONV(c->qid), Qwait), 0, QTFILE);
devdir(c, q, "wait", 0, cv->owner, 0444, dp);
return 1;
}
}
static int
cmdgen(Chan *c, char *name, Dirtab *d, int nd, int s, Dir *dp)
{
Qid q;
Conv *cv;
USED(name);
USED(nd);
USED(d);
if(s == DEVDOTDOT){
switch(TYPE(c->qid)){
case Qtopdir:
case Qcmd:
mkqid(&q, QID(0, Qtopdir), 0, QTDIR);
devdir(c, q, "#C", 0, eve, DMDIR|0555, dp);
break;
case Qconvdir:
mkqid(&q, QID(0, Qcmd), 0, QTDIR);
devdir(c, q, "cmd", 0, eve, DMDIR|0555, dp);
break;
default:
panic("cmdgen %llux", c->qid.path);
}
return 1;
}
switch(TYPE(c->qid)) {
case Qtopdir:
if(s >= 1)
return -1;
mkqid(&q, QID(0, Qcmd), 0, QTDIR);
devdir(c, q, "cmd", 0, "cmd", DMDIR|0555, dp);
return 1;
case Qcmd:
if(s < cmd.nc) {
cv = cmd.conv[s];
mkqid(&q, QID(s, Qconvdir), 0, QTDIR);
sprint(up->genbuf, "%d", s);
devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
return 1;
}
s -= cmd.nc;
if(s == 0){
mkqid(&q, QID(0, Qclonus), 0, QTFILE);
devdir(c, q, "clone", 0, "cmd", 0666, dp);
return 1;
}
return -1;
case Qclonus:
if(s == 0){
mkqid(&q, QID(0, Qclonus), 0, QTFILE);
devdir(c, q, "clone", 0, "cmd", 0666, dp);
return 1;
}
return -1;
case Qconvdir:
return cmd3gen(c, Qconvbase+s, dp);
case Qdata:
case Qstderr:
case Qctl:
case Qstatus:
case Qwait:
return cmd3gen(c, TYPE(c->qid), dp);
}
return -1;
}
static void
cmdinit(void)
{
cmd.maxconv = 1000;
cmd.conv = mallocz(sizeof(Conv*)*(cmd.maxconv+1), 1);
/* cmd.conv is checked by cmdattach, below */
}
static Chan *
cmdattach(char *spec)
{
Chan *c;
if(cmd.conv == nil)
error(Enomem);
c = devattach('C', spec);
mkqid(&c->qid, QID(0, Qtopdir), 0, QTDIR);
return c;
}
static Walkqid*
cmdwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, 0, 0, cmdgen);
}
static int
cmdstat(Chan *c, uchar *db, int n)
{
return devstat(c, db, n, 0, 0, cmdgen);
}
static Chan *
cmdopen(Chan *c, int omode)
{
int perm;
Conv *cv;
char *user;
perm = 0;
omode = openmode(omode);
switch(omode) {
case OREAD:
perm = 4;
break;
case OWRITE:
perm = 2;
break;
case ORDWR:
perm = 6;
break;
}
switch(TYPE(c->qid)) {
default:
break;
case Qtopdir:
case Qcmd:
case Qconvdir:
case Qstatus:
if(omode != OREAD)
error(Eperm);
break;
case Qclonus:
qlock(&cmd.l);
if(waserror()){
qunlock(&cmd.l);
nexterror();
}
cv = cmdclone(up->user);
poperror();
qunlock(&cmd.l);
if(cv == 0)
error(Enodev);
mkqid(&c->qid, QID(cv->x, Qctl), 0, QTFILE);
break;
case Qdata:
case Qstderr:
case Qctl:
case Qwait:
qlock(&cmd.l);
cv = cmd.conv[CONV(c->qid)];
qlock(&cv->l);
if(waserror()){
qunlock(&cv->l);
qunlock(&cmd.l);
nexterror();
}
user = up->user;
if((perm & (cv->perm>>6)) != perm) {
if(strcmp(user, cv->owner) != 0 ||
(perm & cv->perm) != perm)
error(Eperm);
}
switch(TYPE(c->qid)){
case Qdata:
if(omode == OWRITE || omode == ORDWR)
cv->count[0]++;
if(omode == OREAD || omode == ORDWR)
cv->count[1]++;
break;
case Qstderr:
if(omode != OREAD)
error(Eperm);
cv->count[2]++;
break;
case Qwait:
if(cv->waitq == nil)
cv->waitq = qopen(1024, Qmsg, nil, 0);
break;
}
cv->inuse++;
if(cv->inuse == 1) {
cv->state = "Open";
kstrdup(&cv->owner, user);
cv->perm = 0660;
cv->nice = 0;
}
poperror();
qunlock(&cv->l);
qunlock(&cmd.l);
break;
}
c->mode = omode;
c->flag |= COPEN;
c->offset = 0;
return c;
}
static void
closeconv(Conv *c)
{
kstrdup(&c->owner, "cmd");
kstrdup(&c->dir, ".");
c->perm = 0666;
c->state = "Closed";
c->killonclose = 0;
c->killed = 0;
c->nice = 0;
free(c->cmd);
c->cmd = nil;
if(c->waitq != nil){
qfree(c->waitq);
c->waitq = nil;
}
free(c->error);
c->error = nil;
}
static void
cmdfdclose(Conv *c, int fd)
{
if(--c->count[fd] == 0 && c->fd[fd] != nil){
cclose(c->fd[fd]);
c->fd[fd] = nil;
}
}
static void
cmdclose(Chan *c)
{
Conv *cc;
int r;
if((c->flag & COPEN) == 0)
return;
switch(TYPE(c->qid)) {
case Qctl:
case Qdata:
case Qstderr:
case Qwait:
cc = cmd.conv[CONV(c->qid)];
qlock(&cc->l);
if(TYPE(c->qid) == Qdata){
if(c->mode == OWRITE || c->mode == ORDWR)
cmdfdclose(cc, 0);
if(c->mode == OREAD || c->mode == ORDWR)
cmdfdclose(cc, 1);
}else if(TYPE(c->qid) == Qstderr)
cmdfdclose(cc, 2);
r = --cc->inuse;
if(cc->child != nil){
if(!cc->killed)
if(r == 0 || (cc->killonclose && TYPE(c->qid) == Qctl)){
oscmdkill(cc->child);
cc->killed = 1;
}
}else if(r == 0)
closeconv(cc);
qunlock(&cc->l);
break;
}
}
static long
cmdread(Chan *ch, void *a, long n, vlong offset)
{
Conv *c;
char *p, *cmds;
int fd;
USED(offset);
p = a;
switch(TYPE(ch->qid)) {
default:
error(Eperm);
case Qcmd:
case Qtopdir:
case Qconvdir:
return devdirread(ch, a, n, 0, 0, cmdgen);
case Qctl:
sprint(up->genbuf, "%ld", CONV(ch->qid));
return readstr(offset, p, n, up->genbuf);
case Qstatus:
c = cmd.conv[CONV(ch->qid)];
cmds = "";
if(c->cmd != nil)
cmds = c->cmd->f[1];
snprint(up->genbuf, sizeof(up->genbuf), "cmd/%d %d %s %q %q\n",
c->x, c->inuse, c->state, c->dir, cmds);
return readstr(offset, p, n, up->genbuf);
case Qdata:
case Qstderr:
fd = 1;
if(TYPE(ch->qid) == Qstderr)
fd = 2;
c = cmd.conv[CONV(ch->qid)];
qlock(&c->l);
ch = c->fd[fd];
if(ch == nil){
qunlock(&c->l);
return 0;
}
incref(&ch->ref);
qunlock(&c->l);
if(waserror()){
cclose(ch);
nexterror();
}
n = devtab[ch->type]->read(ch, a, n, 0);
if(n < 0)
oserror();
poperror();
cclose(ch);
return n;
case Qwait:
c = cmd.conv[CONV(ch->qid)];
return qread(c->waitq, a, n);
}
}
static int
cmdstarted(void *a)
{
Conv *c;
c = a;
return c->child != nil || c->error != nil || strcmp(c->state, "Execute") != 0;
}
enum
{
CMdir,
CMexec,
CMkill,
CMnice,
CMkillonclose
};
static
Cmdtab cmdtab[] = {
CMdir, "dir", 2,
CMexec, "exec", 0,
CMkill, "kill", 1,
CMnice, "nice", 0,
CMkillonclose, "killonclose", 0,
};
static long
cmdwrite(Chan *ch, void *a, long n, vlong offset)
{
int i, r;
Conv *c;
Cmdbuf *cb;
Cmdtab *ct;
USED(offset);
switch(TYPE(ch->qid)) {
default:
error(Eperm);
case Qctl:
c = cmd.conv[CONV(ch->qid)];
cb = parsecmd(a, n);
if(waserror()){
free(cb);
nexterror();
}
ct = lookupcmd(cb, cmdtab, nelem(cmdtab));
switch(ct->index){
case CMdir:
kstrdup(&c->dir, cb->f[1]);
break;
case CMexec:
poperror(); /* cb */
qlock(&c->l);
if(waserror()){
qunlock(&c->l);
free(cb);
nexterror();
}
if(c->child != nil || c->cmd != nil)
error(Einuse);
for(i = 0; i < nelem(c->fd); i++)
if(c->fd[i] != nil)
error(Einuse);
if(cb->nf < 1)
error(Etoosmall);
kproc("cmdproc", cmdproc, c); /* cmdproc held back until unlock below */
free(c->cmd);
c->cmd = cb; /* don't free cb */
c->state = "Execute";
poperror();
qunlock(&c->l);
while(waserror())
;
sleep(&c->startr, cmdstarted, c);
poperror();
if(c->error)
error(c->error);
return n; /* avoid free(cb) below */
case CMkill:
qlock(&c->l);
if(waserror()){
qunlock(&c->l);
nexterror();
}
if(c->child == nil)
error("not started");
if(oscmdkill(c->child) < 0)
oserror();
poperror();
qunlock(&c->l);
break;
case CMnice:
c->nice = cb->nf > 1? atoi(cb->f[1]): 1;
break;
case CMkillonclose:
c->killonclose = 1;
break;
}
poperror();
free(cb);
break;
case Qdata:
c = cmd.conv[CONV(ch->qid)];
qlock(&c->l);
ch = c->fd[0];
if(ch == nil){
qunlock(&c->l);
error(Ehungup);
}
incref(&ch->ref);
qunlock(&c->l);
if(waserror()){
cclose(ch);
nexterror();
}
r = devtab[ch->type]->write(ch, a, n, 0);
if(r == 0)
error(Ehungup);
if(r < 0) {
/* XXX perhaps should kill writer "write on closed pipe" here, 2nd time around? */
oserror();
}
poperror();
cclose(ch);
return r;
}
return n;
}
static int
cmdwstat(Chan *c, uchar *dp, int n)
{
Dir *d;
Conv *cv;
switch(TYPE(c->qid)){
default:
error(Eperm);
case Qctl:
case Qdata:
case Qstderr:
d = malloc(sizeof(*d)+n);
if(d == nil)
error(Enomem);
if(waserror()){
free(d);
nexterror();
}
n = convM2D(dp, n, d, (char*)&d[1]);
if(n == 0)
error(Eshortstat);
cv = cmd.conv[CONV(c->qid)];
if(!iseve() && strcmp(up->user, cv->owner) != 0)
error(Eperm);
if(!emptystr(d->uid))
kstrdup(&cv->owner, d->uid);
if(d->mode != ~0UL)
cv->perm = d->mode & 0777;
poperror();
free(d);
break;
}
return n;
}
static Conv*
cmdclone(char *user)
{
Conv *c, **pp, **ep;
int i;
c = nil;
ep = &cmd.conv[cmd.maxconv];
for(pp = cmd.conv; pp < ep; pp++) {
c = *pp;
if(c == nil) {
c = malloc(sizeof(Conv));
if(c == nil)
error(Enomem);
qlock(&c->l);
c->inuse = 1;
c->x = pp - cmd.conv;
cmd.nc++;
*pp = c;
break;
}
if(canqlock(&c->l)){
if(c->inuse == 0 && c->child == nil)
break;
qunlock(&c->l);
}
}
if(pp >= ep)
return nil;
c->inuse = 1;
kstrdup(&c->owner, user);
kstrdup(&c->dir, ".");
c->perm = 0660;
c->state = "Closed";
for(i=0; i<nelem(c->fd); i++)
c->fd[i] = nil;
qunlock(&c->l);
return c;
}
static void
cmdproc(void *a)
{
Conv *c;
int n;
char status[ERRMAX];
void *t;
c = a;
qlock(&c->l);
if(Debug)
print("f[0]=%q f[1]=%q\n", c->cmd->f[0], c->cmd->f[1]);
if(waserror()){
if(Debug)
print("failed: %q\n", up->errstr);
kstrdup(&c->error, up->errstr);
c->state = "Done";
wakeup(&c->startr);
qunlock(&c->l);
pexit("cmdproc", 0);
}
t = oscmd(c->cmd->f+1, c->nice, c->dir, c->fd);
if(t == nil)
oserror();
c->child = t; /* to allow oscmdkill */
poperror();
qunlock(&c->l);
wakeup(&c->startr);
if(Debug)
print("started\n");
while(waserror()){
iprint("XXX %s\n", up->errstr);
oscmdkill(t);
}
n = oscmdwait(t, status, sizeof(status));
if(n < 0){
oserrstr();
n = snprint(status, sizeof(status), "0 0 0 0 %q", up->errstr);
}
qlock(&c->l);
c->child = nil;
oscmdfree(t);
if(Debug){
status[n]=0;
print("done %s %s %s: %q\n", chanpath(c->fd[0]), chanpath(c->fd[1]), chanpath(c->fd[2]), status);
}
if(c->inuse > 0){
c->state = "Done";
if(c->waitq != nil)
qproduce(c->waitq, status, n);
}else
closeconv(c);
qunlock(&c->l);
pexit("", 0);
}
Dev cmddevtab = {
'C',
"cmd",
devreset,
cmdinit,
devshutdown,
cmdattach,
cmdwalk,
cmdstat,
cmdopen,
devcreate,
cmdclose,
cmdread,
devbread,
cmdwrite,
devbwrite,
devremove,
cmdwstat
};