| /* |
| * This file is part of the UCB release of Plan 9. It is subject to the license |
| * terms in the LICENSE file found in the top-level directory of this |
| * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No |
| * part of the UCB release of Plan 9, including this file, may be copied, |
| * modified, propagated, or distributed except according to the terms contained |
| * in the LICENSE file. |
| */ |
| |
| //#define DEBUG |
| /* proc on plan 9 has lots of capabilities, some of which we might |
| * want for akaros: |
| * debug control |
| * event tracing |
| * process control (no need for signal system call, etc.) |
| * textual status |
| * rather than excise code that won't work, I'm bracketing it with |
| * #if 0 until we know we don't want it |
| */ |
| #include <vfs.h> |
| #include <kfs.h> |
| #include <slab.h> |
| #include <kmalloc.h> |
| #include <kref.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <assert.h> |
| #include <error.h> |
| #include <cpio.h> |
| #include <pmap.h> |
| #include <smp.h> |
| #include <arch/vmm/vmm.h> |
| |
| enum { |
| Qdir, |
| Qtrace, |
| Qtracepids, |
| Qns, |
| Qargs, |
| Qctl, |
| Qfd, |
| Qfpregs, |
| Qkregs, |
| Qmem, |
| Qnote, |
| Qnoteid, |
| Qnotepg, |
| Qproc, |
| Qregs, |
| Qsegment, |
| Qstatus, |
| Qtext, |
| Qwait, |
| Qprofile, |
| Qsyscall, |
| Qcore, |
| }; |
| |
| enum { |
| CMclose, |
| CMclosefiles, |
| CMfixedpri, |
| CMhang, |
| CMkill, |
| CMnohang, |
| CMnoswap, |
| CMpri, |
| CMprivate, |
| CMprofile, |
| CMstart, |
| CMstartstop, |
| CMstartsyscall, |
| CMstop, |
| CMwaitstop, |
| CMwired, |
| CMtrace, |
| CMcore, |
| CMvminit, |
| CMvmstart, |
| CMvmkill, |
| }; |
| |
| enum { |
| Nevents = 0x4000, |
| Emask = Nevents - 1, |
| Ntracedpids = 1024, |
| STATSIZE = 8 + 1 + 10 + 1 + 6 + 2, |
| }; |
| |
| /* |
| * 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 |
| */ |
| struct dirtab procdir[] = { |
| {"args", {Qargs}, 0, 0660}, |
| {"ctl", {Qctl}, 0, 0660}, |
| {"fd", {Qfd}, 0, 0444}, |
| {"fpregs", {Qfpregs}, 0, 0000}, |
| // {"kregs", {Qkregs}, sizeof(Ureg), 0600}, |
| {"mem", {Qmem}, 0, 0000}, |
| {"note", {Qnote}, 0, 0000}, |
| {"noteid", {Qnoteid}, 0, 0664}, |
| {"notepg", {Qnotepg}, 0, 0000}, |
| {"ns", {Qns}, 0, 0444}, |
| {"proc", {Qproc}, 0, 0400}, |
| // {"regs", {Qregs}, sizeof(Ureg), 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}, |
| {"core", {Qcore}, 0, 0444}, |
| }; |
| |
| static |
| struct 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}, |
| {CMcore, "core", 2}, |
| {CMcore, "core", 2}, |
| {CMcore, "core", 2}, |
| {CMvminit, "vminit", 0}, |
| {CMvmstart, "vmstart", 0}, |
| {CMvmkill, "vmkill", 0}, |
| }; |
| |
| /* |
| * struct qids are, in path: |
| * 5 bits of file type (qids above) (old comment said 4 here) |
| * 23 bits of process slot number + 1 (pid + 1 is stored) |
| * 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 SLOTBITS 23 /* number of bits in the slot */ |
| #define QIDMASK ((1<<QSHIFT)-1) |
| #define SLOTMASK (((1<<SLOTBITS)-1) << QSHIFT) |
| |
| #define QID(q) ((((uint32_t)(q).path)&QIDMASK)>>0) |
| #define SLOT(q) (((((uint32_t)(q).path)&SLOTMASK)>>QSHIFT)-1) |
| #define PID(q) ((q).vers) |
| #define NOTEID(q) ((q).vers) |
| |
| static void procctlreq(struct proc *, char *, int); |
| static int procctlmemio(struct proc *, uintptr_t, int, void *, int); |
| //static struct chan* proctext(struct chan*, struct proc*); |
| //static Segment* txt2data(struct proc*, Segment*); |
| //static int procstopped(void*); |
| static void mntscan(struct mntwalk *, struct proc *); |
| |
| //static Traceevent *tevents; |
| static char *tpids, *tpidsc, *tpidse; |
| static spinlock_t tlock; |
| static int topens; |
| static int tproduced, tconsumed; |
| //static void notrace(struct proc*, int, int64_t); |
| |
| //void (*proctrace)(struct proc*, int, int64_t) = notrace; |
| |
| #if 0 |
| static void profclock(Ureg * ur, Timer *) |
| { |
| Tos *tos; |
| |
| if (up == NULL || current->state != Running) |
| return; |
| |
| /* user profiling clock */ |
| if (userureg(ur)) { |
| tos = (Tos *) (USTKTOP - sizeof(Tos)); |
| tos->clock += TK2MS(1); |
| segclock(userpc(ur)); |
| } |
| } |
| #endif |
| static int |
| procgen(struct chan *c, char *name, struct dirtab *tab, int unused, int s, |
| struct dir *dp) |
| { |
| struct qid qid; |
| struct proc *p; |
| char *ename; |
| |
| int pid; |
| uint32_t 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) { |
| strncpy(get_cur_genbuf(), "trace", GENBUF_SZ); |
| mkqid(&qid, Qtrace, -1, QTFILE); |
| devdir(c, qid, get_cur_genbuf(), 0, eve, 0444, dp); |
| return 1; |
| } |
| if (s == 1) { |
| strncpy(get_cur_genbuf(), "tracepids", GENBUF_SZ); |
| mkqid(&qid, Qtracepids, -1, QTFILE); |
| devdir(c, qid, get_cur_genbuf(), 0, eve, 0444, dp); |
| return 1; |
| } |
| s -= 2; |
| if (name != NULL) { |
| /* ignore s and use name to find pid */ |
| pid = strtol(name, &ename, 10); |
| if (pid <= 0 || ename[0] != '\0') |
| return -1; |
| p = pid2proc(pid); |
| if (!p) |
| return -1; |
| /* Need to update s, so that it's the correct 'index' for our proc |
| * (aka, the pid). We use s later when making the qid. */ |
| s = pid; |
| } else { |
| /* This is a shitty iterator, and the list isn't guaranteed to give |
| * you the same ordering twice in a row. (procs come and go). */ |
| p = pid_nth(s); |
| if (!p) |
| return -1; |
| pid = p->pid; |
| } |
| |
| snprintf(get_cur_genbuf(), GENBUF_SZ, "%u", pid); |
| /* |
| * String comparison is done in devwalk so |
| * name must match its formatted pid. |
| */ |
| if (name != NULL && strcmp(name, get_cur_genbuf()) != 0) { |
| printk("pid-name mismatch, name: %s, pid %d\n", name, pid); |
| kref_put(&p->p_kref); |
| return -1; |
| } |
| mkqid(&qid, (s + 1) << QSHIFT, pid, QTDIR); |
| devdir(c, qid, get_cur_genbuf(), 0, p->user, DMDIR | 0555, dp); |
| kref_put(&p->p_kref); |
| return 1; |
| } |
| if (c->qid.path == Qtrace) { |
| strncpy(get_cur_genbuf(), "trace", GENBUF_SZ); |
| mkqid(&qid, Qtrace, -1, QTFILE); |
| devdir(c, qid, get_cur_genbuf(), 0, eve, 0444, dp); |
| return 1; |
| } |
| if (c->qid.path == Qtracepids) { |
| strncpy(get_cur_genbuf(), "tracepids", GENBUF_SZ); |
| mkqid(&qid, Qtracepids, -1, QTFILE); |
| devdir(c, qid, get_cur_genbuf(), 0, eve, 0444, dp); |
| return 1; |
| } |
| if (s >= ARRAY_SIZE(procdir)) |
| return -1; |
| if (tab) |
| panic("procgen"); |
| |
| tab = &procdir[s]; |
| /* path is everything other than the QID part. Not sure from the orig code |
| * if they wanted just the pid part (SLOTMASK) or everything above QID */ |
| path = c->qid.path & ~QIDMASK; /* slot component */ |
| if ((p = pid2proc(SLOT(c->qid))) == NULL) |
| return -1; |
| perm = 0444 | tab->perm; |
| #if 0 |
| if (perm == 0) |
| perm = p->procmode; |
| else /* just copy read bits */ |
| perm |= p->procmode & 0444; |
| #endif |
| |
| len = tab->length; |
| #if 0 |
| 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 && q->profile) { |
| len = (q->top - q->base) >> LRESPROF; |
| len *= sizeof(*q->profile); |
| } |
| break; |
| } |
| #endif |
| |
| mkqid(&qid, path | tab->qid.path, c->qid.vers, QTFILE); |
| devdir(c, qid, tab->name, len, p->user, perm, dp); |
| kref_put(&p->p_kref); |
| return 1; |
| } |
| |
| #if 0 |
| static void notrace(struct proc *, Tevent, int64_t) |
| { |
| } |
| |
| static spinlock_t tlck = SPINLOCK_INITIALIZER_IRQSAVE; |
| |
| static void _proctrace(struct proc *p, Tevent etype, int64_t ts) |
| { |
| Traceevent *te; |
| int tp; |
| |
| ilock(&tlck); |
| if (p->trace == 0 || topens == 0 || tproduced - tconsumed >= Nevents) { |
| iunlock(&tlck); |
| return; |
| } |
| tp = tproduced++; |
| iunlock(&tlck); |
| |
| te = &tevents[tp & Emask]; |
| te->pid = p->pid; |
| te->etype = etype; |
| if (ts == 0) |
| te->time = todget(NULL); |
| else |
| te->time = ts; |
| te->core = m->machno; |
| } |
| |
| void proctracepid(struct proc *p) |
| { |
| if (p->trace == 1 && proctrace != notrace) { |
| p->trace = 2; |
| ilock(&tlck); |
| tpidsc = seprint(tpidsc, tpidse, "%d %s\n", p->pid, p->text); |
| iunlock(&tlck); |
| } |
| } |
| |
| #endif |
| static void procinit(void) |
| { |
| #if 0 |
| if (conf.nproc >= (SLOTMASK >> QSHIFT) - 1) |
| printd("warning: too many procs for devproc\n"); |
| addclock0link((void (*)(void))profclock, 113); /* Relative prime to HZ */ |
| #endif |
| } |
| |
| static struct chan *procattach(char *spec) |
| { |
| return devattach('p', spec); |
| } |
| |
| static struct walkqid *procwalk(struct chan *c, struct chan *nc, char **name, |
| int nname) |
| { |
| return devwalk(c, nc, name, nname, 0, 0, procgen); |
| } |
| |
| static int procstat(struct chan *c, uint8_t * 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(struct proc *p) |
| { |
| return; |
| #if 0 |
| if (p == up) |
| return; |
| if (strcmp(current->user, "none") != 0) |
| return; |
| if (iseve()) |
| return; |
| error(Eperm); |
| #endif |
| } |
| |
| static struct chan *procopen(struct chan *c, int omode) |
| { |
| ERRSTACK(2); |
| struct proc *p; |
| struct pgrp *pg; |
| struct chan *tc; |
| int pid; |
| |
| if (c->qid.type & QTDIR) |
| return devopen(c, omode, 0, 0, procgen); |
| |
| if (QID(c->qid) == Qtrace) { |
| error("proc: Qtrace: not yet"); |
| #if 0 |
| if (omode != OREAD) |
| error(Eperm); |
| lock(&tlock); |
| if (waserror()) { |
| unlock(&tlock); |
| nexterror(); |
| } |
| if (topens > 0) |
| error("already open"); |
| topens++; |
| if (tevents == NULL) { |
| tevents = (Traceevent *) kzmalloc(sizeof(Traceevent) * Nevents, |
| KMALLOC_WAIT); |
| if (tevents == NULL) |
| error(Enomem); |
| tpids = kzmalloc(Ntracedpids * 20, KMALLOC_WAIT); |
| if (tpids == NULL) { |
| kfree(tpids); |
| tpids = NULL; |
| error(Enomem); |
| } |
| tpidsc = tpids; |
| tpidse = tpids + Ntracedpids * 20; |
| *tpidsc = 0; |
| tproduced = tconsumed = 0; |
| } |
| proctrace = _proctrace; |
| poperror(); |
| unlock(&tlock); |
| |
| c->mode = openmode(omode); |
| c->flag |= COPEN; |
| c->offset = 0; |
| return c; |
| #endif |
| } |
| if (QID(c->qid) == Qtracepids) { |
| error("Proc: Qtracepids: not yet"); |
| #if 0 |
| if (omode != OREAD) |
| error(Eperm); |
| c->mode = openmode(omode); |
| c->flag |= COPEN; |
| c->offset = 0; |
| return c; |
| #endif |
| } |
| if ((p = pid2proc(SLOT(c->qid))) == NULL) |
| error(Eprocdied); |
| //qlock(&p->debug); |
| if (waserror()) { |
| //qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| nexterror(); |
| } |
| pid = PID(c->qid); |
| if (p->pid != pid) |
| error(Eprocdied); |
| |
| omode = openmode(omode); |
| |
| switch (QID(c->qid)) { |
| case Qtext: |
| error("notyet"); |
| /* |
| if (omode != OREAD) |
| error(Eperm); |
| tc = proctext(c, p); |
| tc->offset = 0; |
| poperror(); |
| qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| cclose(c); |
| return tc; |
| */ |
| case Qproc: |
| case Qsegment: |
| case Qprofile: |
| case Qfd: |
| if (omode != OREAD) |
| error(Eperm); |
| break; |
| |
| case Qnote: |
| // if (p->privatemem) |
| error(Eperm); |
| break; |
| |
| case Qmem: |
| // if (p->privatemem) |
| error(Eperm); |
| //nonone(p); |
| break; |
| |
| case Qargs: |
| case Qnoteid: |
| case Qwait: |
| case Qregs: |
| case Qfpregs: |
| case Qkregs: |
| case Qsyscall: |
| case Qcore: |
| nonone(p); |
| break; |
| |
| case Qns: |
| if (omode != OREAD) |
| error(Eperm); |
| c->aux = kzmalloc(sizeof(struct mntwalk), KMALLOC_WAIT); |
| break; |
| case Qstatus: |
| case Qctl: |
| break; |
| case Qnotepg: |
| error("not yet"); |
| #if 0 |
| nonone(p); |
| pg = p->pgrp; |
| if (pg == NULL) |
| error(Eprocdied); |
| if (omode != OWRITE || pg->pgrpid == 1) |
| error(Eperm); |
| c->pgrpid.path = pg->pgrpid + 1; |
| c->pgrpid.vers = p->noteid; |
| #endif |
| break; |
| |
| default: |
| poperror(); |
| //qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| printk("procopen %#llux\n", c->qid.path); |
| 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(); |
| /* TODO: think about what we really want here. In akaros, we wouldn't have |
| * our pid changed like that. */ |
| if (p->pid != pid) |
| error(Eprocdied); |
| |
| tc = devopen(c, omode, 0, 0, procgen); |
| poperror(); |
| //qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| return tc; |
| } |
| |
| static int procwstat(struct chan *c, uint8_t * db, int n) |
| { |
| ERRSTACK(2); |
| error("procwwstat: not yet"); |
| #if 0 |
| struct proc *p; |
| struct dir *d; |
| |
| if (c->qid.type & QTDIR) |
| error(Eperm); |
| |
| if (QID(c->qid) == Qtrace) |
| return devwstat(c, db, n); |
| |
| if ((p = pid2proc(SLOT(c->qid))) == NULL) |
| error(Eprocdied); |
| nonone(p); |
| d = NULL; |
| qlock(&p->debug); |
| if (waserror()) { |
| qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| kfree(d); |
| nexterror(); |
| } |
| |
| if (p->pid != PID(c->qid)) |
| error(Eprocdied); |
| |
| if (strcmp(current->user, p->user) != 0 && strcmp(current->user, eve) != 0) |
| error(Eperm); |
| |
| d = kzmalloc(sizeof(struct dir) + n, KMALLOC_WAIT); |
| 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(current->user, eve) != 0) |
| error(Eperm); |
| else |
| kstrdup(&p->user, d->uid); |
| } |
| if (d->mode != ~0UL) |
| p->procmode = d->mode & 0777; |
| |
| poperror(); |
| qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| kfree(d); |
| |
| return n; |
| #endif |
| } |
| |
| #if 0 |
| 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(struct chan *c) |
| { |
| char buf[32]; |
| |
| return sprint(buf, "%lu", c->qid.vers); |
| } |
| |
| int procfdprint(struct 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 %4ud (%.16llux %*lud %.2ux) %5ld %8lld %s\n", fd, |
| &"r w rw"[(c->mode & 3) << 1], c->dev->dc, c->devno, |
| c->qid.path, w, c->qid.vers, c->qid.type, c->iounit, c->offset, |
| c->name->s); |
| return n; |
| } |
| |
| static int procfds(struct proc *p, char *va, int count, long offset) |
| { |
| ERRSTACK(2); |
| struct fgrp *f; |
| struct chan *c; |
| char buf[256]; |
| int n, i, w, ww; |
| char *a; |
| |
| /* print to buf to avoid holding fgrp lock while writing to user space */ |
| if (count > sizeof buf) |
| count = sizeof buf; |
| a = buf; |
| |
| qlock(&p->debug); |
| f = p->fgrp; |
| if (f == NULL) { |
| qunlock(&p->debug); |
| return 0; |
| } |
| lock(f); |
| if (waserror()) { |
| unlock(f); |
| qunlock(&p->debug); |
| nexterror(); |
| } |
| |
| n = readstr(0, a, count, p->dot->name->s); |
| n += snprint(a + n, count - n, "\n"); |
| offset = procoffset(offset, a, &n); |
| /* compute width of qid.path */ |
| w = 0; |
| for (i = 0; i <= f->maxfd; i++) { |
| c = f->fd[i]; |
| if (c == NULL) |
| continue; |
| ww = procqidwidth(c); |
| if (ww > w) |
| w = ww; |
| } |
| for (i = 0; i <= f->maxfd; i++) { |
| c = f->fd[i]; |
| if (c == NULL) |
| continue; |
| n += procfdprint(c, i, w, a + n, count - n); |
| offset = procoffset(offset, a, &n); |
| } |
| poperror(); |
| unlock(f); |
| qunlock(&p->debug); |
| |
| /* copy result to user space, now that locks are released */ |
| memmove(va, buf, n); |
| |
| return n; |
| } |
| #endif |
| static void procclose(struct chan *c) |
| { |
| if (QID(c->qid) == Qtrace) { |
| spin_lock(&tlock); |
| if (topens > 0) |
| topens--; |
| /* ?? |
| if(topens == 0) |
| proctrace = notrace; |
| */ |
| spin_unlock(&tlock); |
| } |
| if (QID(c->qid) == Qns && c->aux != 0) |
| kfree(c->aux); |
| } |
| |
| 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'; |
| } |
| |
| #if 0 |
| static char *argcpy(char *s, char *p) |
| { |
| char *t, *tp, *te; |
| int n; |
| |
| n = p - s; |
| if (n > 128) |
| n = 128; |
| if (n <= 0) { |
| t = kzmalloc(1, KMALLOC_WAIT); |
| *t = 0; |
| return t; |
| } |
| t = kzmalloc(n, KMALLOC_WAIT); |
| tp = t; |
| te = t + n; |
| |
| while (tp + 1 < te) { |
| for (p--; p > s && p[-1] != 0; p--) ; |
| tp = seprint(tp, te, "%q ", p); |
| if (p == s) |
| break; |
| } |
| if (*tp == ' ') |
| *tp = 0; |
| return t; |
| } |
| |
| static int procargs(struct proc *p, char *buf, int nbuf) |
| { |
| char *s; |
| |
| if (p->setargs == 0) { |
| s = argcpy(p->args, p->args + p->nargs); |
| kfree(p->args); |
| p->nargs = strlen(s); |
| p->args = s; |
| p->setargs = 1; |
| } |
| return snprint(buf, nbuf, "%s", p->args); |
| } |
| |
| static int eventsavailable(void *) |
| { |
| return tproduced > tconsumed; |
| } |
| #endif |
| static long procread(struct chan *c, void *va, long n, int64_t off) |
| { |
| ERRSTACK(5); |
| struct proc *p; |
| long l, r; |
| int i, j, navail, pid, rsize; |
| char flag[10], *sps, *srv, statbuf[512]; |
| uintptr_t offset, u; |
| int tesz; |
| uint8_t *rptr; |
| struct mntwalk *mw; |
| |
| if (c->qid.type & QTDIR) { |
| int nn; |
| printd("procread: dir\n"); |
| nn = devdirread(c, va, n, 0, 0, procgen); |
| printd("procread: %d\n", nn); |
| return nn; |
| } |
| |
| offset = off; |
| #if 0 |
| if (QID(c->qid) == Qtrace) { |
| if (!eventsavailable(NULL)) |
| return 0; |
| |
| rptr = va; |
| tesz = BIT32SZ + BIT32SZ + BIT64SZ + BIT32SZ; |
| navail = tproduced - tconsumed; |
| if (navail > n / tesz) |
| navail = n / tesz; |
| while (navail > 0) { |
| PBIT32(rptr, tevents[tconsumed & Emask].pid); |
| rptr += BIT32SZ; |
| PBIT32(rptr, tevents[tconsumed & Emask].etype); |
| rptr += BIT32SZ; |
| PBIT64(rptr, tevents[tconsumed & Emask].time); |
| rptr += BIT64SZ; |
| PBIT32(rptr, tevents[tconsumed & Emask].core); |
| rptr += BIT32SZ; |
| tconsumed++; |
| navail--; |
| } |
| return rptr - (uint8_t *) va; |
| } |
| |
| if (QID(c->qid) == Qtracepids) |
| if (tpids == NULL) |
| return 0; |
| else |
| return readstr(off, va, n, tpids); |
| #endif |
| if ((p = pid2proc(SLOT(c->qid))) == NULL) |
| error(Eprocdied); |
| if (p->pid != PID(c->qid)) { |
| kref_put(&p->p_kref); |
| error(Eprocdied); |
| } |
| switch (QID(c->qid)) { |
| default: |
| kref_put(&p->p_kref); |
| break; |
| #if 0 |
| #warning check refcnting in here |
| case Qargs: |
| qlock(&p->debug); |
| j = procargs(p, current->genbuf, sizeof current->genbuf); |
| qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| if (offset >= j) |
| return 0; |
| if (offset + n > j) |
| n = j - offset; |
| memmove(va, ¤t->genbuf[offset], n); |
| return n; |
| |
| case Qsyscall: |
| if (p->syscalltrace == NULL) |
| return 0; |
| return readstr(offset, va, n, p->syscalltrace); |
| |
| case Qcore: |
| i = 0; |
| ac = p->ac; |
| wired = p->wired; |
| if (ac != NULL) |
| i = ac->machno; |
| else if (wired != NULL) |
| i = wired->machno; |
| snprint(statbuf, sizeof statbuf, "%d\n", i); |
| return readstr(offset, va, n, statbuf); |
| |
| case Qmem: |
| if (offset < KZERO |
| || (offset >= USTKTOP - USTKSIZE && offset < USTKTOP)) { |
| r = procctlmemio(p, offset, n, va, 1); |
| kref_put(&p->p_kref); |
| return r; |
| } |
| |
| if (!iseve()) { |
| kref_put(&p->p_kref); |
| error(Eperm); |
| } |
| |
| /* validate kernel addresses */ |
| if (offset < PTR2UINT(end)) { |
| if (offset + n > PTR2UINT(end)) |
| n = PTR2UINT(end) - offset; |
| memmove(va, UINT2PTR(offset), n); |
| kref_put(&p->p_kref); |
| return n; |
| } |
| for (i = 0; i < nelem(conf.mem); i++) { |
| cm = &conf.mem[i]; |
| /* klimit-1 because klimit might be zero! */ |
| if (cm->kbase <= offset && offset <= cm->klimit - 1) { |
| if (offset + n >= cm->klimit - 1) |
| n = cm->klimit - offset; |
| memmove(va, UINT2PTR(offset), n); |
| kref_put(&p->p_kref); |
| return n; |
| } |
| } |
| kref_put(&p->p_kref); |
| error(Ebadarg); |
| |
| case Qprofile: |
| s = p->seg[TSEG]; |
| if (s == 0 || s->profile == 0) |
| error("profile is off"); |
| i = (s->top - s->base) >> LRESPROF; |
| i *= sizeof(*s->profile); |
| if (offset >= i) { |
| kref_put(&p->p_kref); |
| return 0; |
| } |
| if (offset + n > i) |
| n = i - offset; |
| memmove(va, ((char *)s->profile) + offset, n); |
| kref_put(&p->p_kref); |
| return n; |
| |
| case Qnote: |
| qlock(&p->debug); |
| if (waserror()) { |
| qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| 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 { |
| i = strlen(p->note[0].msg) + 1; |
| if (i > n) |
| i = n; |
| rptr = va; |
| memmove(rptr, p->note[0].msg, i); |
| rptr[i - 1] = '\0'; |
| p->nnote--; |
| memmove(p->note, p->note + 1, p->nnote * sizeof(Note)); |
| n = i; |
| } |
| if (p->nnote == 0) |
| p->notepending = 0; |
| poperror(); |
| qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| return n; |
| |
| case Qproc: |
| if (offset >= sizeof(struct proc)) { |
| kref_put(&p->p_kref); |
| return 0; |
| } |
| if (offset + n > sizeof(struct proc)) |
| n = sizeof(struct proc) - offset; |
| memmove(va, ((char *)p) + offset, n); |
| kref_put(&p->p_kref); |
| return n; |
| |
| case Qregs: |
| rptr = (uint8_t *) p->dbgreg; |
| rsize = sizeof(Ureg); |
| regread: |
| if (rptr == 0) { |
| kref_put(&p->p_kref); |
| error(Enoreg); |
| } |
| if (offset >= rsize) { |
| kref_put(&p->p_kref); |
| return 0; |
| } |
| if (offset + n > rsize) |
| n = rsize - offset; |
| memmove(va, rptr + offset, n); |
| kref_put(&p->p_kref); |
| return n; |
| |
| case Qkregs: |
| memset(&kur, 0, sizeof(Ureg)); |
| setkernur(&kur, p); |
| rptr = (uint8_t *) & kur; |
| rsize = sizeof(Ureg); |
| goto regread; |
| |
| case Qfpregs: |
| r = fpudevprocio(p, va, n, offset, 0); |
| kref_put(&p->p_kref); |
| return r; |
| |
| case Qstatus: |
| if (offset >= STATSIZE) { |
| kref_put(&p->p_kref); |
| return 0; |
| } |
| if (offset + n > STATSIZE) |
| n = STATSIZE - offset; |
| |
| sps = p->psstate; |
| if (sps == 0) |
| sps = statename[p->state]; |
| memset(statbuf, ' ', sizeof statbuf); |
| j = 2 * KNAMELEN + 12; |
| snprint(statbuf, j + 1, "%-*.*s%-*.*s%-12.11s", |
| KNAMELEN, KNAMELEN - 1, p->text, |
| KNAMELEN, KNAMELEN - 1, p->user, sps); |
| |
| for (i = 0; i < 6; i++) { |
| l = p->time[i]; |
| if (i == TReal) |
| l = sys->ticks - l; |
| l = TK2MS(l); |
| readnum(0, statbuf + j + NUMSIZE * i, NUMSIZE, l, NUMSIZE); |
| } |
| /* ignore stack, which is mostly non-existent */ |
| u = 0; |
| for (i = 1; i < NSEG; i++) { |
| s = p->seg[i]; |
| if (s) |
| u += s->top - s->base; |
| } |
| readnum(0, statbuf + j + NUMSIZE * 6, NUMSIZE, u >> 10u, NUMSIZE); /* wrong size */ |
| readnum(0, statbuf + j + NUMSIZE * 7, NUMSIZE, p->basepri, NUMSIZE); |
| readnum(0, statbuf + j + NUMSIZE * 8, NUMSIZE, p->priority, |
| NUMSIZE); |
| |
| /* |
| * NIX: added # of traps, syscalls, and iccs |
| */ |
| readnum(0, statbuf + j + NUMSIZE * 9, NUMSIZE, p->ntrap, NUMSIZE); |
| readnum(0, statbuf + j + NUMSIZE * 10, NUMSIZE, p->nintr, NUMSIZE); |
| readnum(0, statbuf + j + NUMSIZE * 11, NUMSIZE, p->nsyscall, |
| NUMSIZE); |
| readnum(0, statbuf + j + NUMSIZE * 12, NUMSIZE, p->nicc, NUMSIZE); |
| readnum(0, statbuf + j + NUMSIZE * 13, NUMSIZE, p->nactrap, |
| NUMSIZE); |
| readnum(0, statbuf + j + NUMSIZE * 14, NUMSIZE, p->nacsyscall, |
| NUMSIZE); |
| memmove(va, statbuf + offset, n); |
| kref_put(&p->p_kref); |
| return n; |
| |
| case Qsegment: |
| j = 0; |
| for (i = 0; i < NSEG; i++) { |
| sg = p->seg[i]; |
| if (sg == 0) |
| continue; |
| j += sprint(statbuf + j, "%-6s %c%c %p %p %4d\n", |
| sname[sg->type & SG_TYPE], |
| sg->type & SG_RONLY ? 'R' : ' ', |
| sg->profile ? 'P' : ' ', |
| sg->base, sg->top, sg->ref); |
| } |
| kref_put(&p->p_kref); |
| if (offset >= j) |
| return 0; |
| if (offset + n > j) |
| n = j - offset; |
| if (n == 0 && offset == 0) |
| exhausted("segments"); |
| memmove(va, &statbuf[offset], n); |
| return n; |
| |
| case Qwait: |
| if (!canqlock(&p->qwaitr)) { |
| kref_put(&p->p_kref); |
| error(Einuse); |
| } |
| |
| if (waserror()) { |
| qunlock(&p->qwaitr); |
| kref_put(&p->p_kref); |
| 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); |
| rendez_sleep(&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); |
| |
| poperror(); |
| qunlock(&p->qwaitr); |
| kref_put(&p->p_kref); |
| n = snprint(va, n, "%d %lu %lud %lud %q", |
| wq->w.pid, |
| wq->w.time[TUser], wq->w.time[TSys], wq->w.time[TReal], |
| wq->w.msg); |
| kfree(wq); |
| return n; |
| #endif |
| case Qstatus:{ |
| /* the extra 2 is paranoia */ |
| char buf[8 + 1 + PROC_PROGNAME_SZ + 1 + 10 + 1 + 6 + 2]; |
| snprintf(buf, sizeof(buf), |
| "%8d %-*s %-10s %6d", p->pid, PROC_PROGNAME_SZ, |
| p->progname, procstate2str(p->state), |
| p->ppid); |
| kref_put(&p->p_kref); |
| return readstr(off, va, n, buf); |
| } |
| |
| case Qns: |
| //qlock(&p->debug); |
| if (waserror()) { |
| //qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| nexterror(); |
| } |
| if (p->pgrp == NULL || p->pid != PID(c->qid)) |
| error(Eprocdied); |
| mw = c->aux; |
| if (mw->cddone) { |
| poperror(); |
| //qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| return 0; |
| } |
| mntscan(mw, p); |
| if (mw->mh == 0) { |
| mw->cddone = 1; |
| i = snprintf(va, n, "cd %s\n", p->dot->name->s); |
| poperror(); |
| //qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| return i; |
| } |
| int2flag(mw->cm->mflag, flag); |
| if (strcmp(mw->cm->to->name->s, "#M") == 0) { |
| srv = srvname(mw->cm->to->mchan); |
| i = snprintf(va, n, "mount %s %s %s %s\n", flag, |
| srv == NULL ? mw->cm->to->mchan->name->s : srv, |
| mw->mh->from->name->s, |
| mw->cm->spec ? mw->cm->spec : ""); |
| kfree(srv); |
| } else |
| i = snprintf(va, n, "bind %s %s %s\n", flag, |
| mw->cm->to->name->s, mw->mh->from->name->s); |
| poperror(); |
| //qunlock(&p->debug); |
| kref_put(&p->p_kref); |
| return i; |
| #if 0 |
| case Qnoteid: |
| r = readnum(offset, va, n, p->noteid, NUMSIZE); |
| kref_put(&p->p_kref); |
| return r; |
| case Qfd: |
| r = procfds(p, va, n, offset); |
| kref_put(&p->p_kref); |
| return r; |
| #endif |
| } |
| |
| error(Egreg); |
| return 0; /* not reached */ |
| } |
| |
| static void mntscan(struct mntwalk *mw, struct proc *p) |
| { |
| struct pgrp *pg; |
| struct mount *t; |
| struct mhead *f; |
| int best, i, last, nxt; |
| |
| pg = p->pgrp; |
| rlock(&pg->ns); |
| |
| nxt = 0; |
| best = (int)(~0U >> 1); /* largest 2's complement int */ |
| |
| 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 < best)) { |
| mw->cm = t; |
| mw->mh = f; |
| best = mw->cm->mountid; |
| nxt = 1; |
| } |
| } |
| } |
| } |
| if (nxt == 0) |
| mw->mh = 0; |
| |
| runlock(&pg->ns); |
| } |
| |
| static long procwrite(struct chan *c, void *va, long n, int64_t off) |
| { |
| ERRSTACK(2); |
| |
| struct proc *p, *t; |
| int i, id, l; |
| char *args; |
| uintptr_t offset; |
| |
| if (c->qid.type & QTDIR) |
| error(Eisdir); |
| |
| if ((p = pid2proc(SLOT(c->qid))) == NULL) |
| error(Eprocdied); |
| |
| if (waserror()) { |
| kref_put(&p->p_kref); |
| nexterror(); |
| } |
| if (p->pid != PID(c->qid)) |
| error(Eprocdied); |
| |
| offset = off; |
| |
| switch (QID(c->qid)) { |
| #if 0 |
| case Qargs: |
| if (n == 0) |
| error(Eshort); |
| if (n >= sizeof buf - strlen(p->text) - 1) |
| error(Etoobig); |
| l = snprintf(buf, sizeof buf, "%s [%s]", p->text, (char *)va); |
| args = kzmalloc(l + 1, KMALLOC_WAIT); |
| if (args == NULL) |
| error(Enomem); |
| memmove(args, buf, l); |
| args[l] = 0; |
| kfree(p->args); |
| p->nargs = l; |
| p->args = args; |
| p->setargs = 1; |
| break; |
| |
| case Qmem: |
| if (p->state != Stopped) |
| error(Ebadctl); |
| |
| n = procctlmemio(p, offset, n, va, 0); |
| break; |
| |
| case Qregs: |
| if (offset >= sizeof(Ureg)) |
| n = 0; |
| else if (offset + n > sizeof(Ureg)) |
| n = sizeof(Ureg) - offset; |
| if (p->dbgreg == 0) |
| error(Enoreg); |
| setregisters(p->dbgreg, (char *)(p->dbgreg) + offset, va, n); |
| break; |
| |
| case Qfpregs: |
| n = fpudevprocio(p, va, n, offset, 1); |
| break; |
| #endif |
| case Qctl: |
| procctlreq(p, va, n); |
| break; |
| |
| default: |
| poperror(); |
| kref_put(&p->p_kref); |
| error("unknown qid %#llux in procwrite\n", c->qid.path); |
| } |
| poperror(); |
| kref_put(&p->p_kref); |
| return n; |
| |
| } |
| |
| struct dev procdevtab __devtab = { |
| 'p', |
| "proc", |
| |
| devreset, |
| procinit, |
| devshutdown, |
| procattach, |
| procwalk, |
| procstat, |
| procopen, |
| devcreate, |
| procclose, |
| procread, |
| devbread, |
| procwrite, |
| devbwrite, |
| devremove, |
| procwstat, |
| devpower, |
| devchaninfo, |
| }; |
| |
| #if 0 |
| static struct chan *proctext(struct chan *c, struct proc *p) |
| { |
| ERRSTACK(2); |
| struct chan *tc; |
| Image *i; |
| Segment *s; |
| |
| s = p->seg[TSEG]; |
| if (s == 0) |
| error(Enonexist); |
| if (p->state == Dead) |
| error(Eprocdied); |
| |
| lock(s); |
| i = s->image; |
| if (i == 0) { |
| unlock(s); |
| error(Eprocdied); |
| } |
| unlock(s); |
| |
| lock(i); |
| if (waserror()) { |
| unlock(i); |
| nexterror(); |
| } |
| |
| tc = i->c; |
| if (tc == 0) |
| error(Eprocdied); |
| |
| /* TODO: what do you want here? you can't get a kref and have the new val |
| * be 1. Here is the old code: if (kref_get(&tc->ref, 1) == 1 || ... ) */ |
| if (kref_refcnt(&tc->ref, 1) == 1 || (tc->flag & COPEN) == 0 |
| || tc->mode != OREAD) { |
| cclose(tc); |
| error(Eprocdied); |
| } |
| |
| if (p->pid != PID(c->qid)) { |
| cclose(tc); |
| error(Eprocdied); |
| } |
| |
| poperror(); |
| unlock(i); |
| |
| return tc; |
| } |
| |
| /* TODO: this will fail at compile time, since we don't have a proc-wide rendez, |
| * among other things, and we'll need to rewrite this for akaros */ |
| void procstopwait(struct proc *p, int ctl) |
| { |
| ERRSTACK(2); |
| 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); |
| current->psstate = "Stopwait"; |
| if (waserror()) { |
| p->pdbg = 0; |
| qlock(&p->debug); |
| nexterror(); |
| } |
| rendez_sleep(¤t->sleep, procstopped, p); |
| poperror(); |
| qlock(&p->debug); |
| if (p->pid != pid) |
| error(Eprocdied); |
| } |
| |
| #endif |
| static void procctlcloseone(struct proc *p, int fd) |
| { |
| // TODO: resolve this and sys_close |
| struct file *file = get_file_from_fd(&p->open_files, fd); |
| int retval = 0; |
| printd("%s %d\n", __func__, fd); |
| /* VFS */ |
| if (file) { |
| put_file_from_fd(&p->open_files, fd); |
| kref_put(&file->f_kref); /* Drop the ref from get_file */ |
| return; |
| } |
| /* 9ns, should also handle errors (bad FD, etc) */ |
| retval = sysclose(fd); |
| return; |
| |
| //sys_close(p, fd); |
| } |
| |
| void procctlclosefiles(struct proc *p, int all, int fd) |
| { |
| int i; |
| |
| if (all) |
| for (i = 0; i < NR_FILE_DESC_MAX; i++) |
| procctlcloseone(p, i); |
| else |
| procctlcloseone(p, fd); |
| } |
| |
| static void procctlreq(struct proc *p, char *va, int n) |
| { |
| ERRSTACK(1); |
| int8_t irq_state = 0; |
| int npc, pri, core; |
| struct cmdbuf *cb; |
| struct cmdtab *ct; |
| int64_t time; |
| char *e; |
| |
| cb = parsecmd(va, n); |
| if (waserror()) { |
| kfree(cb); |
| nexterror(); |
| } |
| |
| ct = lookupcmd(cb, proccmd, ARRAY_SIZE(proccmd)); |
| |
| switch (ct->index) { |
| case CMvmstart: |
| case CMvmkill: |
| default: |
| error("nope\n"); |
| break; |
| case CMtrace: |
| systrace_trace_pid(p); |
| break; |
| case CMclose: |
| procctlclosefiles(p, 0, atoi(cb->f[1])); |
| break; |
| case CMclosefiles: |
| procctlclosefiles(p, 1, 0); |
| break; |
| #if 0 |
| we may want this.Let us pause a proc.case CMhang:p->hang = 1; |
| break; |
| #endif |
| case CMkill: |
| p = pid2proc(strtol(cb->f[1], 0, 0)); |
| if (!p) |
| error("No such proc\n"); |
| |
| enable_irqsave(&irq_state); |
| proc_destroy(p); |
| disable_irqsave(&irq_state); |
| proc_decref(p); |
| /* this is a little ghetto. it's not fully free yet, but we are also |
| * slowing it down by messing with it, esp with the busy waiting on a |
| * hyperthreaded core. */ |
| spin_on(p->env_cr3); |
| break; |
| case CMvminit: |
| break; |
| } |
| poperror(); |
| kfree(cb); |
| } |
| |
| #if 0 |
| static int procstopped(void *a) |
| { |
| struct proc *p = a; |
| return p->state == Stopped; |
| } |
| |
| static int |
| procctlmemio(struct proc *p, uintptr_t offset, int n, void *va, int read) |
| { |
| KMap *k; |
| Pte *pte; |
| Page *pg; |
| Segment *s; |
| uintptr_t soff, l; /* hmmmm */ |
| uint8_t *b; |
| uintmem pgsz; |
| |
| for (;;) { |
| s = seg(p, offset, 1); |
| if (s == 0) |
| error(Ebadarg); |
| |
| if (offset + n >= s->top) |
| n = s->top - offset; |
| |
| if (!read && (s->type & SG_TYPE) == SG_TEXT) |
| s = txt2data(p, s); |
| |
| s->steal++; |
| soff = offset - s->base; |
| if (waserror()) { |
| s->steal--; |
| nexterror(); |
| } |
| if (fixfault(s, offset, read, 0, s->color) == 0) |
| break; |
| poperror(); |
| s->steal--; |
| } |
| poperror(); |
| pte = s->map[soff / PTEMAPMEM]; |
| if (pte == 0) |
| panic("procctlmemio"); |
| pgsz = m->pgsz[s->pgszi]; |
| pg = pte->pages[(soff & (PTEMAPMEM - 1)) / pgsz]; |
| if (pagedout(pg)) |
| panic("procctlmemio1"); |
| |
| l = pgsz - (offset & (pgsz - 1)); |
| if (n > l) |
| n = l; |
| |
| k = kmap(pg); |
| if (waserror()) { |
| s->steal--; |
| kunmap(k); |
| nexterror(); |
| } |
| b = (uint8_t *) VA(k); |
| b += offset & (pgsz - 1); |
| if (read == 1) |
| memmove(va, b, n); /* This can fault */ |
| else |
| memmove(b, va, n); |
| poperror(); |
| kunmap(k); |
| |
| /* Ensure the process sees text page changes */ |
| if (s->flushme) |
| memset(pg->cachectl, PG_TXTFLUSH, sizeof(pg->cachectl)); |
| |
| s->steal--; |
| |
| if (read == 0) |
| p->newtlb = 1; |
| |
| return n; |
| } |
| |
| static Segment *txt2data(struct proc *p, Segment * s) |
| { |
| int i; |
| Segment *ps; |
| |
| ps = newseg(SG_DATA, s->base, s->size); |
| ps->image = s->image; |
| kref_get(&ps->image->ref, 1); |
| ps->fstart = s->fstart; |
| ps->flen = s->flen; |
| ps->flushme = 1; |
| |
| qlock(&p->seglock); |
| for (i = 0; i < NSEG; i++) |
| if (p->seg[i] == s) |
| break; |
| if (i == NSEG) |
| panic("segment gone"); |
| |
| qunlock(&s->lk); |
| putseg(s); |
| qlock(&ps->lk); |
| p->seg[i] = ps; |
| qunlock(&p->seglock); |
| |
| return ps; |
| } |
| |
| Segment *data2txt(Segment * s) |
| { |
| Segment *ps; |
| |
| ps = newseg(SG_TEXT, s->base, s->size); |
| ps->image = s->image; |
| kref_get(&ps->image->ref, 1); |
| ps->fstart = s->fstart; |
| ps->flen = s->flen; |
| ps->flushme = 1; |
| |
| return ps; |
| } |
| #endif |