ref: b35328f248cab12ae0760c237d1544842200510c
dir: /kern/devproc.c/
#include "u.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"
#include "error.h"
static char *luser;
extern int pflag;
enum
{
Qdir = 1,
Qtrace,
Qargs,
Qctl,
Qfd,
Qmem,
Qnote,
Qnoteid,
Qnotepg,
Qns,
Qppid,
Qproc,
Qregs,
Qsegment,
Qstatus,
Qtext,
Qwait,
Qprofile,
Qsyscall,
Qwatchpt,
};
enum
{
CMclose,
CMclosefiles,
CMfixedpri,
CMhang,
CMkill,
CMnohang,
CMnoswap,
CMpri,
CMprivate,
CMprofile,
CMstart,
CMstartstop,
CMstartsyscall,
CMstop,
CMwaitstop,
CMwired,
CMtrace,
CMinterrupt,
CMnointerrupt,
};
enum{
Nevents = 0x4000,
Emask = Nevents - 1,
};
#define STATSIZE (2*28+12+9*12)
#define REGSIZE (15*16) /* TODO */
#define QSHIFT 5
#define QID(q) ((((ulong)(q).path)&0x0000001F)>>0)
#define SLOTMAX 0x4000000
#define SLOT(q) (((((ulong)(q).path)>>QSHIFT)&(SLOTMAX-1))-1)
#define PID(q) ((q).vers)
#define NOTEID(q) ((q).vers)
static void procctlreq(Proc*, char*, int);
static long procctlmemio(Chan*, Proc*, uintptr, void*, long, int);
static Chan* proctext(Chan*, Proc*);
static int procstopped(void*);
/*
* 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, 0660,
"ctl", {Qctl}, 0, 0000,
"fd", {Qfd}, 0, 0444,
"mem", {Qmem}, 0, 0000,
"note", {Qnote}, 0, 0000,
"noteid", {Qnoteid}, 0, 0664,
"notepg", {Qnotepg}, 0, 0000,
"ns", {Qns}, 0, 0444,
"ppid", {Qppid}, 0, 0444,
"proc", {Qproc}, 0, 0400,
"regs", {Qregs}, REGSIZE, 0000,
"segment", {Qsegment}, 0, 0444,
"status", {Qstatus}, STATSIZE, 0444,
"text", {Qtext}, 0, 0000,
"wait", {Qwait}, 0, 0400,
"profile", {Qprofile}, 0, 0400,
"syscall", {Qsyscall}, 0, 0400,
"watchpt", {Qwatchpt}, 0, 0600,
};
static
Cmdtab proccmd[] = {
CMclose, "close", 2,
CMclosefiles, "closefiles", 1,
CMfixedpri, "fixedpri", 2,
CMhang, "hang", 1,
CMnohang, "nohang", 1,
CMnoswap, "noswap", 1,
CMkill, "kill", 1,
CMpri, "pri", 2,
CMprivate, "private", 1,
CMprofile, "profile", 1,
CMstart, "start", 1,
CMstartstop, "startstop", 1,
CMstartsyscall, "startsyscall", 1,
CMstop, "stop", 1,
CMwaitstop, "waitstop", 1,
CMwired, "wired", 2,
CMtrace, "trace", 0,
CMinterrupt, "interrupt", 1,
CMnointerrupt, "nointerrupt", 1,
};
/* Segment type from dat.h */
static char *sname[]={ "Text", "Data", "Bss", "Stack", "Shared", "Phys", "Fixed", "Sticky" };
static int
procgen(Chan *c, char *name, Dirtab *tab, int nd, int s, Dir *dp)
{
Qid qid;
Proc *p;
char *ename;
Segment *q;
ulong pid, path, perm, len;
if(s == DEVDOTDOT){
mkqid(&qid, Qdir, 0, QTDIR);
devdir(c, qid, "#p", 0, eve, 0555, dp);
return 1;
}
if(c->qid.path == Qdir){
if(s == 0){
strcpy(up->genbuf, "trace");
mkqid(&qid, Qtrace, -1, QTFILE);
devdir(c, qid, up->genbuf, 0, eve, 0400, dp);
return 1;
}
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 >= conf.nproc)
return -1;
p = proctab(s);
if(p == nil)
return 0;
pid = p->pid;
if(pid == 0)
return 0;
/*
* String comparison is done in devwalk so name must match its formatted pid
*/
snprint(up->genbuf, sizeof(up->genbuf), "%lud", pid);
if(name != nil && strcmp(name, up->genbuf) != 0)
return -1;
mkqid(&qid, (s+1)<<QSHIFT, pid, QTDIR);
devdir(c, qid, up->genbuf, 0, p->user, 0555, dp);
return 1;
}
if(c->qid.path == Qtrace){
strcpy(up->genbuf, "trace");
mkqid(&qid, Qtrace, -1, QTFILE);
devdir(c, qid, up->genbuf, 0, eve, 0400, dp);
return 1;
}
if(s >= nelem(procdir))
return -1;
if(tab)
panic("procgen");
tab = &procdir[s];
path = c->qid.path&~((1<<QSHIFT)-1); /* slot component */
/* p->procmode determines default mode for files in /proc */
p = proctab(SLOT(c->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;
case Qprofile:
q = p->seg[TSEG];
if(q != nil && q->profile != nil) {
len = (q->size-q->start)>>LRESPROF;
len *= sizeof(*q->profile);
}
break;
case Qwatchpt:
len = lenwatchpt(p);
break;
}
mkqid(&qid, path|tab->qid.path, c->qid.vers, QTFILE);
devdir(c, qid, tab->name, len, p->user, perm, dp);
return 1;
}
static void
procinit(void)
{
return;
}
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, nil, 0, procgen);
}
static Chan*
procopen(Chan *c, int omode0)
{
Proc *p;
Chan *tc;
int pid;
int omode;
if(c->qid.type & QTDIR)
return devopen(c, omode0, 0, 0, procgen);
if(QID(c->qid) == Qtrace){
if (omode0 != OREAD || !iseve())
error(Eperm);
lock(&tlock);
if (waserror()){
topens--;
unlock(&tlock);
nexterror();
}
if (topens++ > 0)
error("already open");
if (tevents == nil){
tevents = (Traceevent*)malloc(sizeof(Traceevent) * Nevents);
if(tevents == nil)
error(Enomem);
tproduced = tconsumed = 0;
}
proctrace = _proctrace;
unlock(&tlock);
poperror();
c->mode = openmode(omode0);
c->flag |= COPEN;
c->offset = 0;
return c;
}
p = proctab(SLOT(c->qid));
eqlock(&p->debug);
if(waserror()){
qunlock(&p->debug);
nexterror();
}
pid = PID(c->qid);
if(p == nil || p->pid != pid)
error(Eprocdied);
omode = openmode(omode0);
switch(QID(c->qid)){
case Qtext:
if(omode != OREAD)
error(Eperm);
nonone(p);
qunlock(&p->debug);
poperror();
tc = proctext(c, p);
tc->offset = 0;
cclose(c);
return tc;
case Qstatus:
case Qppid:
case Qproc:
case Qsegment:
case Qns:
case Qfd:
if(omode != OREAD)
error(Eperm);
break;
case Qctl:
case Qargs:
case Qwait:
case Qnoteid:
if(omode == OREAD)
break;
case Qnote:
if(p->kp)
error(Eperm);
break;
case Qnotepg:
if(p->kp || omode != OWRITE)
error(Eperm);
pid = p->noteid;
break;
case Qmem:
case Qregs:
case Qprofile:
case Qsyscall:
case Qwatchpt:
break;
default:
print("procopen %#lux\n", QID(c->qid));
error(Egreg);
}
nonone(p);
/* Affix pid to qid */
if(pid == 0)
error(Eprocdied);
c->qid.vers = pid;
tc = devopen(c, omode, 0, 0, procgen);
if(waserror()){
cclose(tc);
nexterror();
}
switch(QID(c->qid)){
case Qwatchpt:
if((omode0 & OTRUNC) != 0)
clearwatchpt(p);
break;
}
poperror();
qunlock(&p->debug);
poperror();
return tc;
}
static long
procread(Chan *c, void *va, long n, vlong off)
{
char statbuf[1024], *sps;
ulong offset;
int i, j, rsize;
uchar *rptr;
uintptr addr;
Segment *s;
Waitq *wq;
Proc *p;
offset = off;
if(c->qid.type & QTDIR)
return devdirread(c, va, n, 0, 0, procgen);
if(QID(c->qid) == Qtrace){
int navail, ne;
if(!eventsavailable(nil))
return 0;
rptr = (uchar*)va;
navail = tproduced - tconsumed;
if(navail > n / sizeof(Traceevent))
navail = n / sizeof(Traceevent);
while(navail > 0) {
ne = ((tconsumed & Emask) + navail > Nevents)?
Nevents - (tconsumed & Emask): navail;
memmove(rptr, &tevents[tconsumed & Emask],
ne * sizeof(Traceevent));
tconsumed += ne;
rptr += ne * sizeof(Traceevent);
navail -= ne;
}
return rptr - (uchar*)va;
}
p = proctab(SLOT(c->qid));
if(p->pid != PID(c->qid))
error(Eprocdied);
switch(QID(c->qid)){
case Qmem:
addr = off2addr(off);
if(addr < KZERO)
return procctlmemio(c, p, addr, va, n, 1);
if(!iseve() || poolisoverlap(secrmem, (uchar*)addr, n))
error(Eperm);
// validate kernel addresses
if(addr < (uintptr)end) {
if(addr+n > (uintptr)end)
n = (uintptr)end - addr;
memmove(va, (char*)addr, n);
return n;
}
for(i=0; i<nelem(conf.mem); i++){
Confmem *cm = &conf.mem[i];
// klimit-1 because klimit might be zero!
if(cm->kbase <= addr && addr <= cm->klimit-1){
if(addr+n >= cm->klimit-1)
n = cm->klimit - addr;
memmove(va, (char*)addr, n);
return n;
}
}
error(Ebadarg);
case Qctl:
return readnum(offset, va, n, p->pid, NUMSIZE);
case Qnoteid:
return readnum(offset, va, n, p->noteid, NUMSIZE);
case Qppid:
return readnum(offset, va, n, p->parentpid, NUMSIZE);
case Qprofile:
s = p->seg[TSEG];
if(s == nil || s->profile == nil)
error("profile is off");
i = (s->size-s->start)>>LRESPROF;
i *= sizeof(s->profile[0]);
if(i < 0 || offset >= i)
return 0;
if(offset+n > i)
n = i - offset;
memmove(va, ((char*)s->profile)+offset, n);
return n;
case Qproc:
rptr = (uchar*)p;
rsize = sizeof(Proc);
regread:
if(rptr == nil)
error(Enoreg);
if(offset >= rsize)
return 0;
if(offset+n > rsize)
n = rsize - offset;
memmove(va, rptr+offset, n);
return n;
case Qregs:
rptr = (uchar*)p->dbgreg;
rsize = REGSIZE;
goto regread;
case Qstatus:
sps = p->psstate;
if(sps == nil)
sps = statename[p->state];
/* NOTE: We don't have any p->time tracking
this is handled by the pthreads/windows
j = snprint(statbuf, sizeof(statbuf),
"%-27s %-27s %-11s "
"%11lud %11lud %11lud "
"%11lud %11lud %11lud "
"%11lud %11lud %11lud\n",
p->text, p->user, sps,
tk2ms(p->time[TUser]),
tk2ms(p->time[TSys]),
tk2ms(MACHP(0)->ticks - p->time[TReal]),
tk2ms(p->time[TCUser]),
tk2ms(p->time[TCSys]),
tk2ms(p->time[TCReal]),
(ulong)(procpagecount(p)*BY2PG/1024),
p->basepri, p->priority);
*/
statbufread:
if(offset >= j)
return 0;
if(offset+n > j)
n = j - offset;
memmove(va, statbuf+offset, n);
return n;
case Qsegment:
j = 0;
for(i = 0; i < NSEG; i++) {
s = p->seg[i];
if(s == nil)
continue;
j += snprint(statbuf+j, sizeof(statbuf)-j,
"%-6s %c%c %8p %8p %4ld\n",
sname[s->type&SG_TYPE],
s->type&SG_FAULT ? 'F' : (s->type&SG_RONLY ? 'R' : ' '),
s->profile ? 'P' : ' ',
s->start, s->size, s->ref);
}
goto statbufread;
case Qwait:
if(!canqlock(&p->qwaitr))
error(Einuse);
if(waserror()) {
qunlock(&p->qwaitr);
nexterror();
}
lock(&p->exl);
while(p->waitq == nil && p->pid == PID(c->qid)) {
if(up == p && p->nchild == 0) {
unlock(&p->exl);
error(Enochild);
}
unlock(&p->exl);
sleep(&p->waitr, prochaswaitq, c);
lock(&p->exl);
}
if(p->pid != PID(c->qid)){
unlock(&p->exl);
error(Eprocdied);
}
wq = p->waitq;
p->waitq = wq->next;
p->nwait--;
unlock(&p->exl);
qunlock(&p->qwaitr);
poperror();
j = snprint(statbuf, sizeof(statbuf), "%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);
offset = 0;
goto statbufread;
}
eqlock(&p->debug);
if(waserror()){
qunlock(&p->debug);
nexterror();
}
if(p->pid != PID(c->qid))
error(Eprocdied);
switch(QID(c->qid)){
case Qns:
case Qfd:
if(offset == 0 || offset != c->mrock)
c->nrock = c->mrock = 0;
do {
if(QID(c->qid) == Qns)
j = readns1(c, p, statbuf, sizeof(statbuf));
else
j = readfd1(c, p, statbuf, sizeof(statbuf));
if(j == 0)
break;
c->mrock += j;
} while(c->mrock <= offset);
i = c->mrock - offset;
qunlock(&p->debug);
poperror();
if(i <= 0 || i > j)
return 0;
if(i < n)
n = i;
offset = j - i;
goto statbufread;
case Qargs:
j = procargs(p, statbuf, sizeof(statbuf));
qunlock(&p->debug);
poperror();
goto statbufread;
case Qwatchpt:
j = readwatchpt(p, statbuf, sizeof(statbuf));
qunlock(&p->debug);
poperror();
goto statbufread;
case Qsyscall:
if(p->syscalltrace != nil)
n = readstr(offset, va, n, p->syscalltrace);
else
n = 0;
break;
case Qnote:
if(n < 1) /* must accept at least the '\0' */
error(Etoosmall);
if(p->nnote == 0)
n = 0;
else {
assert(p->note[0] != nil);
i = strlen(p->note[0]->msg) + 1;
if(i < n)
n = i;
memmove(va, p->note[0]->msg, n-1);
((char*)va)[n-1] = '\0';
freenote(p->note[0]);
if(--p->nnote == 0)
p->notepending = 0;
else
memmove(&p->note[0], &p->note[1], p->nnote*sizeof(Note*));
p->note[p->nnote] = nil;
}
break;
default:
print("unknown qid in procwread\n");
error(Egreg);
}
qunlock(&p->debug);
poperror();
return n;
}
static long
procwrite(Chan *c, void *va, long n, vlong off)
{
char buf[ERRMAX];
ulong offset;
uchar *rptr;
Proc *p;
offset = off;
if(c->qid.type & QTDIR)
error(Eisdir);
/* use the remembered noteid in the channel qid */
if(QID(c->qid) == Qnotepg) {
if(n >= sizeof(buf))
error(Etoobig);
memmove(buf, va, n);
buf[n] = 0;
postnotepg(NOTEID(c->qid), buf, NUser);
return n;
}
p = proctab(SLOT(c->qid));
if(p->pid != PID(c->qid))
error(Eprocdied);
switch(QID(c->qid)){
case Qargs:
if(offset != 0 || n >= sizeof(buf))
error(Etoobig);
memmove(buf, va, n);
buf[n] = 0;
kstrdup(&p->args, buf);
p->nargs = 0;
p->setargs = 1;
break;
case Qmem:
if(p->state != Stopped)
error(Ebadctl);
n = procctlmemio(c, p, off2addr(off), va, n, 0);
break;
case Qregs:
if(offset >= REGSIZE)
n = 0;
else if(offset+n > REGSIZE)
n = REGSIZE - offset;
if(p->dbgreg == nil)
error(Enoreg);
setregisters(p->dbgreg, (char*)(p->dbgreg)+offset, va, n);
break;
case Qctl:
procctlreq(p, va, n);
break;
case Qnote:
if(n >= sizeof(buf))
error(Etoobig);
memmove(buf, va, n);
buf[n] = 0;
if(!postnote(p, 0, buf, NUser))
error("note not posted");
break;
case Qnoteid:
if(offset != 0 || n >= sizeof(buf))
error(Etoobig);
memmove(buf, va, n);
buf[n] = 0;
changenoteid(p, atoi(buf));
break;
default:
print("unknown qid in procwrite\n");
error(Egreg);
}
poperror();
return n;
}
static void
procclose(Chan *c)
{
Segio *sio;
if((c->flag & COPEN) == 0)
return;
switch(QID(c->qid)){
case Qtrace:
lock(&tlock);
if(topens > 0)
topens--;
if(topens == 0)
proctrace = nil;
unlock(&tlock);
return;
case Qmem:
sio = c->aux;
if(sio != nil){
c->aux = nil;
segio(sio, nil, nil, 0, 0, 0);
free(sio);
}
return;
}
}
static int
procwstat(Chan *c, uchar *db, int n)
{
Dir *d;
Proc *p;
if(c->qid.type&QTDIR)
error(Eperm);
switch(QID(c->qid)){
case Qnotepg:
case Qtrace:
return devwstat(c, db, n);
}
d = smalloc(sizeof(Dir)+n);
if(waserror()){
free(d);
nexterror();
}
n = convM2D(db, n, &d[0], (char*)&d[1]);
if(n == 0)
error(Eshortstat);
p = proctab(SLOT(c->qid));
eqlock(&p->debug);
if(waserror()){
qunlock(&p->debug);
nexterror();
}
if(p->pid != PID(c->qid))
error(Eprocdied);
nonone(p);
if(strcmp(up->user, p->user) != 0 && !iseve())
error(Eperm);
if(!emptystr(d->uid) && strcmp(d->uid, p->user) != 0){
if(strcmp(d->uid, "none") != 0 && !iseve())
error(Eperm);
kstrdup(&p->user, d->uid);
}
/* p->procmode determines default mode for files in /proc */
if(d->mode != ~0UL)
p->procmode = d->mode&0777;
qunlock(&p->debug);
poperror();
poperror();
free(d);
return n;
}
static int
procstat(Chan *c, uchar *db, int n)
{
return devstat(c, db, n, nil, 0, procgen);
}
static void
procctlreq(Proc *p, char *va, int n)
{
Segment *s;
uintptr npc;
int pri;
Cmdbuf *cb;
Cmdtab *ct;
vlong time;
char *e;
void (*pt)(Proc*, int, vlong);
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 CMhang:
p->hang = 1;
break;
case CMkill:
killproc(p, Proc_exitme);
break;
case CMnohang:
p->hang = 0;
break;
case CMnoswap:
p->noswap = 1;
break;
case CMpri:
pri = atoi(cb->f[1]);
if(pri > PriNormal && !iseve())
error(Eperm);
procpriority(p, pri, 0);
break;
case CMfixedpri:
pri = atoi(cb->f[1]);
if(pri > PriNormal && !iseve())
error(Eperm);
procpriority(p, pri, 1);
break;
case CMprivate:
p->privatemem = 1;
/*
* pages will now get marked private
* when faulted in for writing
* so force a tlb flush.
*/
p->newtlb = 1;
if(p == up)
flushmmu();
break;
case CMprofile:
s = p->seg[TSEG];
if(s == nil || (s->type&SG_TYPE) != SG_TEXT) /* won't expand */
error(Egreg);
eqlock(s);
npc = (s->size-s->start)>>LRESPROF;
if(s->profile == nil){
s->profile = malloc(npc*sizeof(*s->profile));
if(s->profile == nil){
qunlock(s);
error(Enomem);
}
} else {
memset(s->profile, 0, npc*sizeof(*s->profile));
}
qunlock(s);
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 CMstartsyscall:
if(p->state != Stopped)
error(Ebadctl);
p->procctl = Proc_tracesyscall;
ready(p);
procstopwait(p, Proc_tracesyscall);
break;
case CMstop:
procstopwait(p, Proc_stopme);
break;
case CMwaitstop:
procstopwait(p, 0);
break;
case CMwired:
procwired(p, atoi(cb->f[1]));
break;
case CMtrace:
switch(cb->nf){
case 1:
p->trace ^= 1;
break;
case 2:
p->trace = (atoi(cb->f[1]) != 0);
break;
default:
error("args");
}
break;
case CMinterrupt:
procinterrupt(p);
break;
case CMnointerrupt:
if(p->nnote == 0)
p->notepending = 0;
else
error("notes pending");
break;
}
poperror();
free(cb);
}
static int
procstopped(void *a)
{
return ((Proc*)a)->state == Stopped;
}
static long
procctlmemio(Chan *c, Proc *p, uintptr offset, void *a, long n, int read)
{
Segment *s;
int i;
eqlock(&p->seglock);
if(waserror()) {
qunlock(&p->seglock);
nexterror();
}
if(p->state <= New || p->pid != PID(c->qid))
error(Eprocdied);
s = seg(p, offset, 1);
if(s == nil)
error(Ebadarg);
if(waserror()){
qunlock(s);
nexterror();
}
for(i = 0; i < NSEG; i++) {
if(p->seg[i] == s)
break;
}
if(i == NSEG)
error(Egreg); /* segment gone */
if(!read && (s->type&SG_TYPE) == SG_TEXT) {
p->seg[i] = txt2data(s);
qunlock(s);
putseg(s);
s = p->seg[i];
} else {
qunlock(s);
}
poperror();
incref(s); /* for us while we copy */
qunlock(&p->seglock);
poperror();
if(waserror()) {
putseg(s);
nexterror();
}
offset -= s->base;
putseg(s);
poperror();
if(!read)
p->newtlb = 1;
return n;
}
Dev procdevtab = {
'P',
"proc",
devreset,
procinit,
devshutdown,
procattach,
procwalk,
procstat,
procopen,
devcreate,
procclose,
procread,
devbread,
devbwrite,
devremove,
procwstat,
};