#include <9pm/u.h> #include <9pm/libc.h> #include <9pm/fcall.h> #include "dat.h" #include "fns.h" enum { Qdir, Qargs, Qctl, Qfd, Qnote, Qnoteid, Qnotepg, Qns, Qproc, Qstatus, Qwait, }; enum { CMclose, CMclosefiles, CMfixedpri, CMhang, CMkill, CMnohang, CMpri, CMprivate, CMprofile, CMstart, CMstartstop, CMstop, CMwaitstop, CMwired, }; #define STATSIZE (2*KNAMELEN+12+9*12) /* * Status, fd, and ns are left fully readable (0444) because of their use in debugging, * particularly on shared servers. * Arguably, ns and fd shouldn't be readable; if you'd prefer, change them to 0000 */ Dirtab procdir[] = { "args", {Qargs}, 0, 0440, "ctl", {Qctl}, 0, 0000, "fd", {Qfd}, 0, 0444, "note", {Qnote}, 0, 0000, "noteid", {Qnoteid}, 0, 0664, "notepg", {Qnotepg}, 0, 0000, "ns", {Qns}, 0, 0444, "proc", {Qproc}, 0, 0400, "status", {Qstatus}, STATSIZE, 0444, "wait", {Qwait}, 0, 0400, }; static Cmdtab proccmd[] = { CMclose, "close", 2, CMclosefiles, "closefiles", 1, CMfixedpri, "fixedpri", 2, CMhang, "hang", 1, CMnohang, "nohang", 1, CMkill, "kill", 1, CMpri, "pri", 2, CMprivate, "private", 1, CMprofile, "profile", 1, CMstart, "start", 1, CMstartstop, "startstop", 1, CMstop, "stop", 1, CMwaitstop, "waitstop", 1, CMwired, "wired", 2, }; /* * Qids are, in path: * 4 bits of file type (qids above) * 23 bits of process slot number + 1 * in vers, * 32 bits of pid, for consistency checking * If notepg, c->pgrpid.path is pgrp slot, .vers is noteid. */ #define QSHIFT 5 /* location in qid of proc slot # */ #define QID(q) ((((ulong)(q).path)&0x0000001F)>>0) #define SLOT(q) (((((ulong)(q).path)&0x07FFFFFE0)>>QSHIFT)-1) #define PID(q) ((q).vers) #define NOTEID(q) ((q).vers) void procctlreq(Proc*, char*, int); int procctlmemio(Proc*, ulong, int, void*, int); Chan* proctext(Chan*, Proc*); int procstopped(void*); void mntscan(Mntwalk*, Proc*); static int procgen(Chan *c, char *name, Dirtab *tab, int z, int s, Dir *dp) { Qid qid; Proc *p; char *ename; ulong pid, path, perm, len; USED(z); if(s == DEVDOTDOT){ mkqid(&qid, Qdir, 0, QTDIR); devdir(c, qid, "#p", 0, eve, 0555, dp); return 1; } if(c->qid.path == Qdir){ if(name != nil){ /* ignore s and use name to find pid */ pid = strtol(name, &ename, 10); if(pid==0 || ename[0]!='\0') return -1; s = procindex(pid); if(s < 0) return -1; }else if(s >= procalloc.nproc) return -1; p = proctab(s); pid = p->pid; if(pid == 0) return 0; sprint(up->genbuf, "%lud", pid); /* * String comparison is done in devwalk so name must match its formatted pid */ if(name != nil && strcmp(name, up->genbuf) != 0) return -1; mkqid(&qid, (s+1)<genbuf, 0, p->user, DMDIR|0555, dp); return 1; } if(s >= nelem(procdir)) return -1; if(tab) panic("procgen"); tab = &procdir[s]; path = c->qid.path&~(((1<qid)); perm = tab->perm; if(perm == 0) perm = p->procmode; else /* just copy read bits */ perm |= p->procmode & 0444; len = tab->length; switch(QID(c->qid)) { case Qwait: len = p->nwait; /* incorrect size, but >0 means there's something to read */ break; } mkqid(&qid, path|tab->qid.path, c->qid.vers, QTFILE); devdir(c, qid, tab->name, len, p->user, perm, dp); return 1; } static Chan* procattach(char *spec) { return devattach('p', spec); } static Walkqid* procwalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, 0, 0, procgen); } static int procstat(Chan *c, uchar *db, int n) { return devstat(c, db, n, 0, 0, procgen); } /* * none can't read or write state on other * processes. This is to contain access of * servers running as none should they be * subverted by, for example, a stack attack. */ static void nonone(Proc *p) { if(p == up) return; if(strcmp(up->user, "none") != 0) return; if(iseve()) return; error(Eperm); } static Chan* procopen(Chan *c, int omode) { Proc *p; Pgrp *pg; Chan *tc; int pid; if(c->qid.type & QTDIR) return devopen(c, omode, 0, 0, procgen); p = proctab(SLOT(c->qid)); qlock(&p->debug); if(waserror()){ qunlock(&p->debug); nexterror(); } pid = PID(c->qid); if(p->pid != pid) error(Eprocdied); omode = openmode(omode); switch(QID(c->qid)){ case Qproc: case Qfd: if(omode != OREAD) error(Eperm); break; case Qargs: case Qctl: case Qnote: case Qnoteid: case Qstatus: case Qwait: nonone(p); break; case Qns: if(omode != OREAD) error(Eperm); c->aux = malloc(sizeof(Mntwalk)); break; case Qnotepg: nonone(p); pg = p->pgrp; if(pg == nil) error(Eprocdied); if(omode!=OWRITE || pg->pgrpid == 1) error(Eperm); c->pgrpid.path = pg->pgrpid+1; c->pgrpid.vers = p->noteid; break; default: pprint("procopen %lux\n", c->qid); error(Egreg); } /* Affix pid to qid */ if(p->state != Dead) c->qid.vers = p->pid; /* make sure the process slot didn't get reallocated while we were playing */ coherence(); if(p->pid != pid) error(Eprocdied); tc = devopen(c, omode, 0, 0, procgen); qunlock(&p->debug); poperror(); return tc; } static int procwstat(Chan *c, uchar *db, int n) { Proc *p; Dir *d; if(c->qid.type&QTDIR) error(Eperm); p = proctab(SLOT(c->qid)); nonone(p); d = nil; if(waserror()){ free(d); qunlock(&p->debug); nexterror(); } qlock(&p->debug); if(p->pid != PID(c->qid)) error(Eprocdied); if(strcmp(up->user, p->user) != 0 && strcmp(up->user, eve) != 0) error(Eperm); d = smalloc(sizeof(Dir)+n); n = convM2D(db, n, &d[0], (char*)&d[1]); if(n == 0) error(Eshortstat); if(!emptystr(d->uid) && strcmp(d->uid, p->user) != 0){ if(strcmp(up->user, eve) != 0) error(Eperm); else kstrdup(&p->user, d->uid); } if(d->mode != ~0UL) p->procmode = d->mode&0777; poperror(); free(d); qunlock(&p->debug); return n; } static long procoffset(long offset, char *va, int *np) { if(offset > 0) { offset -= *np; if(offset < 0) { memmove(va, va+*np+offset, -offset); *np = -offset; } else *np = 0; } return offset; } static int procqidwidth(Chan *c) { char buf[32]; return sprint(buf, "%lud", c->qid.vers); } int procfdprint(Chan *c, int fd, int w, char *s, int ns) { int n; if(w == 0) w = procqidwidth(c); n = snprint(s, ns, "%3d %.2s %C %4ld (%.16llux %*lud %.2ux) %5ld %8lld %s\n", fd, &"r w rw"[(c->mode&3)<<1], devtab[c->type]->dc, c->dev, c->qid.path, w, c->qid.vers, c->qid.type, c->iounit, c->offset, c->name->s); return n; } static int procfds(Proc *p, char *va, int count, long offset) { Fgrp *f; Chan *c; int n, i, w, ww; qlock(&p->debug); f = p->fgrp; if(f == nil){ qunlock(&p->debug); return 0; } lock(&f->lk); if(waserror()){ unlock(&f->lk); qunlock(&p->debug); nexterror(); } n = readstr(0, va, count, p->dot->name->s); n += snprint(va+n, count-n, "\n"); offset = procoffset(offset, va, &n); /* compute width of qid.path */ w = 0; for(i = 0; i <= f->maxfd; i++) { c = f->fd[i]; if(c == nil) continue; ww = procqidwidth(c); if(ww > w) w = ww; } for(i = 0; i <= f->maxfd; i++) { c = f->fd[i]; if(c == nil) continue; n += procfdprint(c, i, w, va+n, count-n); offset = procoffset(offset, va, &n); } unlock(&f->lk); qunlock(&p->debug); poperror(); return n; } static void procclose(Chan * c) { if(QID(c->qid) == Qns && c->aux != 0) free(c->aux); } static void int2flag(int flag, char *s) { if(flag == 0){ *s = '\0'; return; } *s++ = '-'; if(flag & MAFTER) *s++ = 'a'; if(flag & MBEFORE) *s++ = 'b'; if(flag & MCREATE) *s++ = 'c'; if(flag & MCACHE) *s++ = 'C'; *s = '\0'; } static int procargs(Proc *p, char *buf, int nbuf) { int j, k, m; char *a; int n; a = p->args; n = p->nargs; for(j = 0; j < nbuf - 1; j += m){ if(n == 0) break; if(j != 0) buf[j++] = ' '; m = snprint(buf+j, nbuf-j, "%q", a); k = strlen(a) + 1; a += k; n -= k; } return j; } static long procread(Chan *c, void *va, long n, vlong off) { int m; long l; Proc *p; Waitq *wq; Mntwalk *mw; char *a = va, *sps; int i, j, pid; char *srv, statbuf[256], flag[10]; ulong offset = off; if(c->qid.type & QTDIR) return devdirread(c, a, n, 0, 0, procgen); p = proctab(SLOT(c->qid)); if(p->pid != PID(c->qid)) error(Eprocdied); switch(QID(c->qid)){ case Qargs: j = procargs(p, p->genbuf, sizeof p->genbuf); if(offset >= j) return 0; if(offset+n > j) n = j-offset; memmove(a, &p->genbuf[offset], n); return n; case Qnote: qlock(&p->debug); if(waserror()){ qunlock(&p->debug); nexterror(); } if(p->pid != PID(c->qid)) error(Eprocdied); if(n < 1) /* must accept at least the '\0' */ error(Etoosmall); if(p->nnote == 0) n = 0; else { m = strlen(p->note[0].msg) + 1; if(m > n) m = n; memmove(va, p->note[0].msg, m); ((char*)va)[m-1] = '\0'; p->nnote--; memmove(p->note, p->note+1, p->nnote*sizeof(Note)); n = m; } if(p->nnote == 0) p->notepending = 0; poperror(); qunlock(&p->debug); return n; case Qproc: if(offset >= sizeof(Proc)) return 0; if(offset+n > sizeof(Proc)) n = sizeof(Proc) - offset; memmove(a, ((char*)p)+offset, n); return n; case Qstatus: if(offset >= STATSIZE) return 0; if(offset+n > STATSIZE) n = STATSIZE - offset; sps = p->psstate; if(sps == 0) sps = statename[p->state]; memset(statbuf, ' ', sizeof statbuf); memmove(statbuf+0*KNAMELEN, p->text, strlen(p->text)); memmove(statbuf+1*KNAMELEN, p->user, strlen(p->user)); memmove(statbuf+2*KNAMELEN, sps, strlen(sps)); j = 2*KNAMELEN + 12; for(i = 0; i < 6; i++) { l = p->time[i]; if(i == TReal) l = machp0->ticks - l; l = TK2MS(l); readnum(0, statbuf+j+NUMSIZE*i, NUMSIZE, l, NUMSIZE); } readnum(0, statbuf+j+NUMSIZE*6, NUMSIZE, p->bss, NUMSIZE); readnum(0, statbuf+j+NUMSIZE*7, NUMSIZE, p->basepri, NUMSIZE); readnum(0, statbuf+j+NUMSIZE*8, NUMSIZE, p->priority, NUMSIZE); memmove(a, statbuf+offset, n); return n; case Qwait: if(!canqlock(&p->qwaitr)) error(Einuse); if(waserror()) { qunlock(&p->qwaitr); nexterror(); } lock(&p->exl); if(up == p && p->nchild == 0 && p->waitq == 0) { unlock(&p->exl); error(Enochild); } pid = p->pid; while(p->waitq == 0) { unlock(&p->exl); rendsleep(&p->waitr, haswaitq, p); if(p->pid != pid) error(Eprocdied); lock(&p->exl); } wq = p->waitq; p->waitq = wq->next; p->nwait--; unlock(&p->exl); qunlock(&p->qwaitr); poperror(); n = snprint(a, n, "%d %lud %lud %lud %q", wq->w.pid, wq->w.time[TUser], wq->w.time[TSys], wq->w.time[TReal], wq->w.msg); free(wq); return n; case Qns: qlock(&p->debug); if(waserror()){ qunlock(&p->debug); nexterror(); } if(p->pgrp == nil || p->pid != PID(c->qid)) error(Eprocdied); mw = c->aux; if(mw->cddone){ qunlock(&p->debug); poperror(); return 0; } mntscan(mw, p); if(mw->mh == 0){ mw->cddone = 1; i = snprint(a, n, "cd %s\n", p->dot->name->s); qunlock(&p->debug); poperror(); return i; } int2flag(mw->cm->mflag, flag); if(strcmp(mw->cm->to->name->s, "#M") == 0){ srv = srvname(mw->cm->to->mchan); i = snprint(a, n, "mount %s %s %s %s\n", flag, srv==nil? mw->cm->to->mchan->name->s : srv, mw->mh->from->name->s, mw->cm->spec? mw->cm->spec : ""); free(srv); }else i = snprint(a, n, "bind %s %s %s\n", flag, mw->cm->to->name->s, mw->mh->from->name->s); qunlock(&p->debug); poperror(); return i; case Qnoteid: return readnum(offset, va, n, p->noteid, NUMSIZE); case Qfd: return procfds(p, va, n, offset); } error(Egreg); return 0; /* not reached */ } void mntscan(Mntwalk *mw, Proc *p) { Pgrp *pg; Mount *t; Mhead *f; int nxt, i; ulong last, bestmid; pg = p->pgrp; rlock(&pg->ns); nxt = 0; bestmid = ~0; last = 0; if(mw->mh) last = mw->cm->mountid; for(i = 0; i < MNTHASH; i++) { for(f = pg->mnthash[i]; f; f = f->hash) { for(t = f->mount; t; t = t->next) { if(mw->mh == 0 || (t->mountid > last && t->mountid < bestmid)) { mw->cm = t; mw->mh = f; bestmid = mw->cm->mountid; nxt = 1; } } } } if(nxt == 0) mw->mh = 0; runlock(&pg->ns); } static long procwrite(Chan *c, void *va, long n, vlong off) { int id; Proc *p, **t, **et; char *a, buf[ERRMAX]; ulong offset = off; a = va; if(c->qid.type & QTDIR) error(Eisdir); p = proctab(SLOT(c->qid)); /* Use the remembered noteid in the channel rather * than the process pgrpid */ if(QID(c->qid) == Qnotepg) { pgrpnote(NOTEID(c->pgrpid), va, n, NUser); return n; } qlock(&p->debug); if(waserror()){ qunlock(&p->debug); nexterror(); } if(p->pid != PID(c->qid)) error(Eprocdied); switch(QID(c->qid)){ case Qctl: procctlreq(p, va, n); break; case Qnote: if(p->kp) error(Eperm); if(n >= ERRMAX-1) error(Etoobig); memmove(buf, va, n); buf[n] = 0; if(!postnote(p, 0, buf, NUser)) error("note not posted"); break; case Qnoteid: id = atoi(a); if(id == p->pid) { p->noteid = id; break; } t = procalloc.arena; for(et = t+procalloc.nproc; t < et; t++) { if(id == (*t)->noteid) { if(strcmp(p->user, (*t)->user) != 0) error(Eperm); p->noteid = id; break; } } if(p->noteid != id) error(Ebadarg); break; default: pprint("unknown qid in procwrite\n"); error(Egreg); } poperror(); qunlock(&p->debug); return n; } Dev devproc = { 'p', "proc", devreset, procattach, procwalk, procstat, procopen, devcreate, procclose, procread, devbread, procwrite, devbwrite, devremove, procwstat, }; void procstopwait(Proc *p, int ctl) { int pid; if(p->pdbg) error(Einuse); if(procstopped(p) || p->state == Broken) return; if(ctl != 0) p->procctl = ctl; p->pdbg = up; pid = p->pid; qunlock(&p->debug); up->psstate = "Stopwait"; if(waserror()) { p->pdbg = 0; qlock(&p->debug); nexterror(); } rendsleep(&up->sleep, procstopped, p); poperror(); qlock(&p->debug); if(p->pid != pid) error(Eprocdied); } static void procctlcloseone(Proc *p, Fgrp *f, int fd) { Chan *c; c = f->fd[fd]; if(c == nil) return; f->fd[fd] = nil; unlock(&f->lk); qunlock(&p->debug); cclose(c); qlock(&p->debug); lock(&f->lk); } void procctlclosefiles(Proc *p, int all, int fd) { int i; Fgrp *f; f = p->fgrp; if(f == nil) error(Eprocdied); lock(&f->lk); f->ref.ref++; if(all) for(i = 0; i < f->maxfd; i++) procctlcloseone(p, f, i); else procctlcloseone(p, f, fd); unlock(&f->lk); closefgrp(f); } void procctlreq(Proc *p, char *va, int n) { int i; Cmdbuf *cb; Cmdtab *ct; if(p->kp) /* no ctl requests to kprocs */ error(Eperm); cb = parsecmd(va, n); if(waserror()){ free(cb); nexterror(); } ct = lookupcmd(cb, proccmd, nelem(proccmd)); switch(ct->index){ case CMclose: procctlclosefiles(p, 0, atoi(cb->f[1])); break; case CMclosefiles: procctlclosefiles(p, 1, 0); break; case CMfixedpri: i = atoi(cb->f[1]); if(i < 0) i = 0; if(i >= Nrq) i = Nrq - 1; if(i > p->basepri && !iseve()) error(Eperm); p->basepri = i; p->fixedpri = 1; break; case CMhang: p->hang = 1; break; case CMkill: switch(p->state) { case Stopped: postnote(p, 0, "sys: killed", NExit); p->procctl = Proc_exitme; ready(p); break; default: postnote(p, 0, "sys: killed", NExit); p->procctl = Proc_exitme; } break; case CMnohang: p->hang = 0; break; case CMpri: i = atoi(cb->f[1]); if(i < 0) i = 0; if(i >= Nrq) i = Nrq - 1; if(i > p->basepri && !iseve()) error(Eperm); p->basepri = i; p->fixedpri = 0; break; case CMprivate: p->privatemem = 1; break; case CMstart: if(p->state != Stopped) error(Ebadctl); ready(p); break; case CMstartstop: if(p->state != Stopped) error(Ebadctl); p->procctl = Proc_traceme; ready(p); procstopwait(p, Proc_traceme); break; case CMstop: procstopwait(p, Proc_stopme); break; case CMwaitstop: procstopwait(p, 0); break; case CMwired: error(Ebadctl); /* procwired(p, atoi(cb->f[1])); */ break; } poperror(); free(cb); } int procstopped(void *a) { Proc *p = a; return p->state == Stopped; }