#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 != (ulong)~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; ifd); 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 };