|  | /* | 
|  | * Copyright 2013 Google Inc. | 
|  | * Copyright (c) 1989-2003 by Lucent Technologies, Bell Laboratories. | 
|  | */ | 
|  | //#define DEBUG | 
|  | #include <setjmp.h> | 
|  | #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 <fcall.h> | 
|  | #include <ros/fs.h> | 
|  |  | 
|  | void | 
|  | validstat(uint8_t *s, unsigned long n) | 
|  | { | 
|  | unsigned long m; | 
|  | char buf[64]; | 
|  |  | 
|  | if(statcheck(s, n) < 0) | 
|  | error(Ebadstat); | 
|  | /* verify that name entry is acceptable */ | 
|  | s += STATFIXLEN - 4*BIT16SZ;	/* location of first string */ | 
|  | /* | 
|  | * s now points at count for first string. | 
|  | * if it's too long, let the server decide; this is | 
|  | * only for his protection anyway. otherwise | 
|  | * we'd have to allocate and waserror. | 
|  | */ | 
|  | m = GBIT16(s); | 
|  | s += BIT16SZ; | 
|  | if(m+1 > sizeof buf) | 
|  | return; | 
|  | memmove(buf, s, m); | 
|  | buf[m] = '\0'; | 
|  | /* name could be '/' */ | 
|  | if(strcmp(buf, "/") != 0) | 
|  | validname(buf, 0); | 
|  | } | 
|  |  | 
|  | /* read memory to a process. */ | 
|  | int readmem(unsigned long offset, char *buf, unsigned long n, | 
|  | void *mem, size_t len) | 
|  | { | 
|  | if (offset >= len) | 
|  | return 0; | 
|  | if (offset + n > len) | 
|  | n = len - offset; | 
|  | memmove(buf, mem + offset, n); | 
|  | return n; | 
|  | } | 
|  |  | 
|  | /* simple functions for common uses. Read a num/string to user mode, | 
|  | * accounting for offset.  Not a huge fan of the 'size' parameter (the old plan9 | 
|  | * users just picked NUMSIZE (12), though they seem to want to limit it).  */ | 
|  | int readnum(unsigned long off, char *buf, unsigned long n, unsigned long val, | 
|  | int size) | 
|  | { | 
|  | char tmp[64]; | 
|  | size = MIN(sizeof(tmp), size); | 
|  | /* we really need the %* format. */ | 
|  | size = snprintf(tmp, size, "%lu", val); | 
|  | /* size is now strlen, so the rest of this is just like readstr. */ | 
|  | /* always include the \0 */ | 
|  | return readmem(off, buf, n, tmp, size + 1); | 
|  | } | 
|  |  | 
|  | long readstr(long offset, char *buf, long n, char *str) | 
|  | { | 
|  | /* always include the \0 */ | 
|  | return readmem(offset, buf, n, str, strlen(str) + 1); | 
|  | } | 
|  |  | 
|  | void fdclose(int fd, int flag) | 
|  | { | 
|  | int i; | 
|  | struct chan *c; | 
|  | struct fgrp *f; | 
|  |  | 
|  | f = current->fgrp; | 
|  | spin_lock(&f->lock); | 
|  | if (f->closed) { | 
|  | spin_unlock(&f->lock); | 
|  | return; | 
|  | } | 
|  | c = f->fd[fd]; | 
|  | if (c == NULL) { | 
|  | /* can happen for users with shared fd tables */ | 
|  | spin_unlock(&f->lock); | 
|  | return; | 
|  | } | 
|  | if (flag) { | 
|  | if (c == NULL || !(c->flag & flag)) { | 
|  | spin_unlock(&f->lock); | 
|  | return; | 
|  | } | 
|  | } | 
|  | f->fd[fd] = NULL; | 
|  | if (fd == f->maxfd) | 
|  | for (i = fd; --i >= 0 && f->fd[i] == 0;) | 
|  | f->maxfd = i; | 
|  | /* hack: give the FD back to VFS */ | 
|  | put_fd(¤t->open_files, fd); | 
|  |  | 
|  | spin_unlock(&f->lock); | 
|  | cclose(c); | 
|  | } | 
|  |  | 
|  | int openmode(int omode) | 
|  | { | 
|  | #if 0 | 
|  | /* this is the old plan9 style.  i think they want to turn exec into read, | 
|  | * and strip off anything higher, and just return the RD/WR style bits.  not | 
|  | * stuff like ORCLOSE.  the lack of OEXCL might be a bug on there part (it's | 
|  | * the only one of their non-RW-related flags that isn't masked out) */ | 
|  | omode &= ~(OTRUNC | OCEXEC | ORCLOSE); | 
|  | if (omode > OEXEC) | 
|  | error(Ebadarg); | 
|  | if (omode == OEXEC) | 
|  | return OREAD; | 
|  | return omode; | 
|  | #endif | 
|  | /* no error checking (we have a shitload of flags anyway), and we return the | 
|  | * basic access modes (RD/WR/ETC) */ | 
|  | if (omode == O_EXEC) | 
|  | return O_RDONLY; | 
|  | return omode & O_ACCMODE; | 
|  | } | 
|  |  | 
|  | static void unlockfgrp(struct fgrp *f) | 
|  | { | 
|  | int ex; | 
|  |  | 
|  | ex = f->exceed; | 
|  | f->exceed = 0; | 
|  | spin_unlock(&f->lock); | 
|  | if (ex) | 
|  | printd("warning: process exceeds %d file descriptors\n", ex); | 
|  | } | 
|  |  | 
|  | int growfd(struct fgrp *f, int fd) | 
|  | {	/* fd is always >= 0 */ | 
|  | struct chan **newfd, **oldfd; | 
|  |  | 
|  | if (fd < f->nfd) | 
|  | return 0; | 
|  | if (fd >= f->nfd + DELTAFD) | 
|  | return -1;	/* out of range */ | 
|  | /* | 
|  | * Unbounded allocation is unwise | 
|  | */ | 
|  | if (f->nfd >= 5000) { | 
|  | Exhausted: | 
|  | printd("no free file descriptors\n"); | 
|  | return -1; | 
|  | } | 
|  | newfd = kzmalloc((f->nfd + DELTAFD) * sizeof(struct chan *), KMALLOC_WAIT); | 
|  | if (newfd == 0) | 
|  | goto Exhausted; | 
|  | oldfd = f->fd; | 
|  | memmove(newfd, oldfd, f->nfd * sizeof(struct chan *)); | 
|  | f->fd = newfd; | 
|  | kfree(oldfd); | 
|  | f->nfd += DELTAFD; | 
|  | if (fd > f->maxfd) { | 
|  | if (fd / 100 > f->maxfd / 100) | 
|  | f->exceed = (fd / 100) * 100; | 
|  | f->maxfd = fd; | 
|  | } | 
|  | printd("GROW \n"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | *  this assumes that the fgrp is locked | 
|  | */ | 
|  | int findfreefd(struct fgrp *f, int start) | 
|  | { | 
|  | int fd; | 
|  |  | 
|  | #if 0  /* this is the normal plan9 way */ | 
|  | for (fd = start; fd < f->nfd; fd++) | 
|  | if (f->fd[fd] == 0) | 
|  | break; | 
|  | #else /* hack: ask the VFS for a free fd */ | 
|  | fd = get_fd(¤t->open_files, start); | 
|  | assert(f->fd[fd] == 0); | 
|  | #endif | 
|  | if (fd >= f->nfd && growfd(f, fd) < 0) | 
|  | return -1; | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | int newfd(struct chan *c) | 
|  | { | 
|  | int fd; | 
|  | struct fgrp *f; | 
|  |  | 
|  | f = current->fgrp; | 
|  | spin_lock(&f->lock); | 
|  | if (f->closed) { | 
|  | spin_unlock(&f->lock); | 
|  | return -1; | 
|  | } | 
|  | fd = findfreefd(f, 0); | 
|  | if (fd < 0) { | 
|  | unlockfgrp(f); | 
|  | return -1; | 
|  | } | 
|  | if (fd > f->maxfd) | 
|  | f->maxfd = fd; | 
|  | f->fd[fd] = c; | 
|  | unlockfgrp(f); | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | static int newfd2(int fd[2], struct chan *c[2]) | 
|  | { | 
|  | struct fgrp *f; | 
|  |  | 
|  | f = current->fgrp; | 
|  | spin_lock(&f->lock); | 
|  | if (f->closed) { | 
|  | spin_unlock(&f->lock); | 
|  | return -1; | 
|  | } | 
|  | fd[0] = findfreefd(f, 0); | 
|  | if (fd[0] < 0) { | 
|  | unlockfgrp(f); | 
|  | return -1; | 
|  | } | 
|  | fd[1] = findfreefd(f, fd[0] + 1); | 
|  | if (fd[1] < 0) { | 
|  | unlockfgrp(f); | 
|  | return -1; | 
|  | } | 
|  | if (fd[1] > f->maxfd) | 
|  | f->maxfd = fd[1]; | 
|  | f->fd[fd[0]] = c[0]; | 
|  | f->fd[fd[1]] = c[1]; | 
|  | unlockfgrp(f); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | struct chan *fdtochan(int fd, int mode, int chkmnt, int iref) | 
|  | { | 
|  | struct chan *c; | 
|  | struct fgrp *f; | 
|  |  | 
|  | c = NULL; | 
|  | f = current->fgrp; | 
|  |  | 
|  | spin_lock(&f->lock); | 
|  | if (f->closed) { | 
|  | spin_unlock(&f->lock); | 
|  | error("File group closed"); | 
|  | } | 
|  | if (fd < 0) { | 
|  | spin_unlock(&f->lock); | 
|  | error("%s: fd < 0", Ebadfd); | 
|  | } | 
|  | if (f->nfd <= fd) { | 
|  | spin_unlock(&f->lock); | 
|  | error("%s: f->nfd %d, fd %d", Ebadfd, f->nfd, fd); | 
|  | } | 
|  | if ((c = f->fd[fd]) == 0) { | 
|  | spin_unlock(&f->lock); | 
|  | error("%s: f->fd[%d] is 0", Ebadfd, fd); | 
|  | } | 
|  | if (iref) | 
|  | kref_get(&c->ref, 1); | 
|  | spin_unlock(&f->lock); | 
|  |  | 
|  | if (chkmnt && (c->flag & CMSG)) { | 
|  | if (iref) | 
|  | cclose(c); | 
|  | error("MSG channel&chkmnt set"); | 
|  | } | 
|  |  | 
|  | if (mode < 0 || c->mode == ORDWR) | 
|  | return c; | 
|  |  | 
|  | if ((mode & OTRUNC) && c->mode == OREAD) { | 
|  | if (iref) | 
|  | cclose(c); | 
|  | error("mode&OTRUNC and mode==READ"); | 
|  | } | 
|  |  | 
|  | #if 0 | 
|  | /* seems not to match akaros. | 
|  | if ((mode & ~OTRUNC) != c->mode) { | 
|  | if (iref) | 
|  | cclose(c); | 
|  | error(Ebadusefd); | 
|  | } | 
|  | */ | 
|  | #endif | 
|  |  | 
|  | return c; | 
|  | } | 
|  |  | 
|  | static long unionread(struct chan *c, void *va, long n) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | int i; | 
|  | long nr; | 
|  | struct mhead *mh; | 
|  | struct mount *mount; | 
|  |  | 
|  | qlock(&c->umqlock); | 
|  | mh = c->umh; | 
|  | rlock(&mh->lock); | 
|  | mount = mh->mount; | 
|  | /* bring mount in sync with c->uri and c->umc */ | 
|  | for (i = 0; mount != NULL && i < c->uri; i++) | 
|  | mount = mount->next; | 
|  |  | 
|  | nr = 0; | 
|  | while (mount != NULL) { | 
|  | /* Error causes component of union to be skipped */ | 
|  | if (mount->to) { | 
|  | /* Careful, this is a NOT waserror(), we're discarding any errors */ | 
|  | if (!waserror()) { | 
|  | if (c->umc == NULL) { | 
|  | c->umc = cclone(mount->to); | 
|  | c->umc = c->umc->dev->open(c->umc, OREAD); | 
|  | } | 
|  |  | 
|  | nr = c->umc->dev->read(c->umc, va, n, c->umc->offset); | 
|  | c->umc->offset += nr; | 
|  | } | 
|  | poperror(); | 
|  | } | 
|  | if (nr > 0) | 
|  | break; | 
|  |  | 
|  | /* Advance to next element */ | 
|  | c->uri++; | 
|  | if (c->umc) { | 
|  | cclose(c->umc); | 
|  | c->umc = NULL; | 
|  | } | 
|  | mount = mount->next; | 
|  | } | 
|  | runlock(&mh->lock); | 
|  | qunlock(&c->umqlock); | 
|  | return nr; | 
|  | } | 
|  |  | 
|  | static void unionrewind(struct chan *c) | 
|  | { | 
|  | qlock(&c->umqlock); | 
|  | c->uri = 0; | 
|  | if (c->umc) { | 
|  | cclose(c->umc); | 
|  | c->umc = NULL; | 
|  | } | 
|  | qunlock(&c->umqlock); | 
|  | } | 
|  |  | 
|  | static char *pathlast(struct path *p) | 
|  | { | 
|  | char *s; | 
|  |  | 
|  | if (p == NULL) | 
|  | return NULL; | 
|  | if (p->len == 0) | 
|  | return NULL; | 
|  | s = strrchr(p->s, '/'); | 
|  | if (s) | 
|  | return s + 1; | 
|  | return p->s; | 
|  | } | 
|  |  | 
|  | static unsigned long | 
|  | dirfixed(uint8_t * p, unsigned char *e, struct dir *d) | 
|  | { | 
|  | int len; | 
|  | struct dev *dev; | 
|  |  | 
|  | len = GBIT16(p) + BIT16SZ; | 
|  | if (p + len > e) | 
|  | return 0; | 
|  |  | 
|  | p += BIT16SZ;	/* ignore size */ | 
|  | dev = devtabget(GBIT16(p), 1);	//XDYNX | 
|  | if (dev != NULL) { | 
|  | d->type = dev->dc; | 
|  | //devtabdecr(dev); | 
|  | } else | 
|  | d->type = -1; | 
|  | p += BIT16SZ; | 
|  | d->dev = GBIT32(p); | 
|  | p += BIT32SZ; | 
|  | d->qid.type = GBIT8(p); | 
|  | p += BIT8SZ; | 
|  | d->qid.vers = GBIT32(p); | 
|  | p += BIT32SZ; | 
|  | d->qid.path = GBIT64(p); | 
|  | p += BIT64SZ; | 
|  | d->mode = GBIT32(p); | 
|  | p += BIT32SZ; | 
|  | d->atime = GBIT32(p); | 
|  | p += BIT32SZ; | 
|  | d->mtime = GBIT32(p); | 
|  | p += BIT32SZ; | 
|  | d->length = GBIT64(p); | 
|  |  | 
|  | return len; | 
|  | } | 
|  |  | 
|  | static char *dirname(uint8_t * p, unsigned long *n) | 
|  | { | 
|  | p += BIT16SZ + BIT16SZ + BIT32SZ + BIT8SZ + BIT32SZ + BIT64SZ | 
|  | + BIT32SZ + BIT32SZ + BIT32SZ + BIT64SZ; | 
|  | *n = GBIT16(p); | 
|  |  | 
|  | return (char *)p + BIT16SZ; | 
|  | } | 
|  |  | 
|  | static unsigned long | 
|  | dirsetname(char *name, unsigned long len, uint8_t * p, unsigned long n, | 
|  | unsigned long maxn) | 
|  | { | 
|  | char *oname; | 
|  | unsigned long nn, olen; | 
|  |  | 
|  | if (n == BIT16SZ) | 
|  | return BIT16SZ; | 
|  |  | 
|  | oname = dirname(p, &olen); | 
|  |  | 
|  | nn = n + len - olen; | 
|  | PBIT16(p, nn - BIT16SZ); | 
|  | if (nn > maxn) | 
|  | return BIT16SZ; | 
|  |  | 
|  | if (len != olen) | 
|  | memmove(oname + len, oname + olen, p + n - (uint8_t *) (oname + olen)); | 
|  | PBIT16((uint8_t *) (oname - 2), len); | 
|  | memmove(oname, name, len); | 
|  |  | 
|  | return nn; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * struct mountfix might have caused the fixed results of the directory read | 
|  | * to overflow the buffer.  Catch the overflow in c->dirrock. | 
|  | */ | 
|  | static void mountrock(struct chan *c, uint8_t * p, unsigned char **pe) | 
|  | { | 
|  | uint8_t *e, *r; | 
|  | int len, n; | 
|  |  | 
|  | e = *pe; | 
|  |  | 
|  | /* find last directory entry */ | 
|  | for (;;) { | 
|  | len = BIT16SZ + GBIT16(p); | 
|  | if (p + len >= e) | 
|  | break; | 
|  | p += len; | 
|  | } | 
|  |  | 
|  | /* save it away */ | 
|  | qlock(&c->rockqlock); | 
|  | if (c->nrock + len > c->mrock) { | 
|  | n = ROUNDUP(c->nrock + len, 1024); | 
|  | r = kzmalloc(n, KMALLOC_WAIT); | 
|  | memmove(r, c->dirrock, c->nrock); | 
|  | kfree(c->dirrock); | 
|  | c->dirrock = r; | 
|  | c->mrock = n; | 
|  | } | 
|  | memmove(c->dirrock + c->nrock, p, len); | 
|  | c->nrock += len; | 
|  | qunlock(&c->rockqlock); | 
|  |  | 
|  | /* drop it */ | 
|  | *pe = p; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Satisfy a directory read with the results saved in c->dirrock. | 
|  | */ | 
|  | static int mountrockread(struct chan *c, uint8_t * op, long n, long *nn) | 
|  | { | 
|  | long dirlen; | 
|  | uint8_t *rp, *erp, *ep, *p; | 
|  |  | 
|  | /* common case */ | 
|  | if (c->nrock == 0) | 
|  | return 0; | 
|  |  | 
|  | /* copy out what we can */ | 
|  | qlock(&c->rockqlock); | 
|  | rp = c->dirrock; | 
|  | erp = rp + c->nrock; | 
|  | p = op; | 
|  | ep = p + n; | 
|  | while (rp + BIT16SZ <= erp) { | 
|  | dirlen = BIT16SZ + GBIT16(rp); | 
|  | if (p + dirlen > ep) | 
|  | break; | 
|  | memmove(p, rp, dirlen); | 
|  | p += dirlen; | 
|  | rp += dirlen; | 
|  | } | 
|  |  | 
|  | if (p == op) { | 
|  | qunlock(&c->rockqlock); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* shift the rest */ | 
|  | if (rp != erp) | 
|  | memmove(c->dirrock, rp, erp - rp); | 
|  | c->nrock = erp - rp; | 
|  |  | 
|  | *nn = p - op; | 
|  | qunlock(&c->rockqlock); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static void mountrewind(struct chan *c) | 
|  | { | 
|  | c->nrock = 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Rewrite the results of a directory read to reflect current | 
|  | * name space bindings and mounts.  Specifically, replace | 
|  | * directory entries for bind and mount points with the results | 
|  | * of statting what is mounted there.  Except leave the old names. | 
|  | */ | 
|  | static long | 
|  | mountfix(struct chan *c, uint8_t * op, long n, long maxn) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | char *name; | 
|  | int nbuf; | 
|  | struct chan *nc; | 
|  | struct mhead *mh; | 
|  | struct mount *mount; | 
|  | unsigned long dirlen, nname, r, rest; | 
|  | long l; | 
|  | uint8_t *buf, *e, *p; | 
|  | struct dir d; | 
|  |  | 
|  | p = op; | 
|  | buf = NULL; | 
|  | nbuf = 0; | 
|  | for (e = &p[n]; p + BIT16SZ < e; p += dirlen) { | 
|  | dirlen = dirfixed(p, e, &d); | 
|  | if (dirlen == 0) | 
|  | break; | 
|  | nc = NULL; | 
|  | mh = NULL; | 
|  | if (findmount(&nc, &mh, d.type, d.dev, d.qid)) { | 
|  | /* | 
|  | * If it's a union directory and the original is | 
|  | * in the union, don't rewrite anything. | 
|  | */ | 
|  | for (mount = mh->mount; mount; mount = mount->next) | 
|  | if (eqchanddq(mount->to, d.type, d.dev, d.qid, 1)) | 
|  | goto Norewrite; | 
|  |  | 
|  | name = dirname(p, &nname); | 
|  | /* | 
|  | * Do the stat but fix the name.  If it fails, | 
|  | * leave old entry. | 
|  | * BUG: If it fails because there isn't room for | 
|  | * the entry, what can we do?  Nothing, really. | 
|  | * Might as well skip it. | 
|  | */ | 
|  | if (buf == NULL) { | 
|  | buf = kzmalloc(4096, KMALLOC_WAIT); | 
|  | nbuf = 4096; | 
|  | } | 
|  | if (waserror()) | 
|  | goto Norewrite; | 
|  | l = nc->dev->stat(nc, buf, nbuf); | 
|  | r = dirsetname(name, nname, buf, l, nbuf); | 
|  | if (r == BIT16SZ) | 
|  | error("dirsetname"); | 
|  | poperror(); | 
|  |  | 
|  | /* | 
|  | * Shift data in buffer to accomodate new entry, | 
|  | * possibly overflowing into rock. | 
|  | */ | 
|  | rest = e - (p + dirlen); | 
|  | if (r > dirlen) { | 
|  | while (p + r + rest > op + maxn) { | 
|  | mountrock(c, p, &e); | 
|  | if (e == p) { | 
|  | dirlen = 0; | 
|  | goto Norewrite; | 
|  | } | 
|  | rest = e - (p + dirlen); | 
|  | } | 
|  | } | 
|  | if (r != dirlen) { | 
|  | memmove(p + r, p + dirlen, rest); | 
|  | dirlen = r; | 
|  | e = p + dirlen + rest; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Rewrite directory entry. | 
|  | */ | 
|  | memmove(p, buf, r); | 
|  |  | 
|  | Norewrite: | 
|  | cclose(nc); | 
|  | putmhead(mh); | 
|  | } | 
|  | } | 
|  | if (buf) | 
|  | kfree(buf); | 
|  |  | 
|  | if (p != e) | 
|  | error("oops in mountfix"); | 
|  |  | 
|  | return e - op; | 
|  | } | 
|  |  | 
|  | extern struct dev procdevtab, rootdevtab; | 
|  | long sysread(int fd, void *p, size_t n, off_t off) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | uint8_t *ep = p; | 
|  | long nn, nnn; | 
|  | struct chan *c; | 
|  | int ispread = 1; | 
|  | printd("%p: ", current->pid); | 
|  | printd("sysread %d %p %d %lld\n", fd, p, n, off); | 
|  |  | 
|  | if (waserror()) { | 
|  | set_errno(EBADF); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | c = fdtochan(fd, OREAD, 1, 1); | 
|  |  | 
|  | poperror(); | 
|  |  | 
|  | if (waserror()) { | 
|  | cclose(c); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The offset is passed through on directories, normally. | 
|  | * Sysseek complains, but pread is used by servers like exportfs, | 
|  | * that shouldn't need to worry about this issue. | 
|  | * | 
|  | * Notice that c->devoffset is the offset that c's dev is seeing. | 
|  | * The number of bytes read on this fd (c->offset) may be different | 
|  | * due to rewritings in mountfix. | 
|  | */ | 
|  | if (off == ~0LL) {	/* use and maintain channel's offset */ | 
|  | off = c->offset; | 
|  | ispread = 0; | 
|  | } | 
|  |  | 
|  | if (c->qid.type & QTDIR) { | 
|  | unsigned char *ents; | 
|  | /* | 
|  | * struct directory read: | 
|  | * rewind to the beginning of the file if necessary; | 
|  | * try to fill the buffer via mountrockread; | 
|  | * clear ispread to always maintain the struct chan offset. | 
|  | */ | 
|  | /* this is a bit of a hack until we resolve akaros direntry format. */ | 
|  | ents = kzmalloc(8192, KMALLOC_WAIT); | 
|  | if (!ents) | 
|  | error(Enomem); | 
|  |  | 
|  | if (off == 0LL) { | 
|  | if (!ispread) { | 
|  | c->offset = 0; | 
|  | c->devoffset = 0; | 
|  | } | 
|  | mountrewind(c); | 
|  | unionrewind(c); | 
|  | } | 
|  | printd("sysread: dir: ispread %d @ %lld\n", ispread, off); | 
|  | /* tell it we have less than we have to make sure it will | 
|  | * fit in the large akaros dirents. | 
|  | */ | 
|  | if (!mountrockread(c, ents, 2048, &nn)) { | 
|  | printd("Rock read failed, going to the source\n"); | 
|  | if (c->umh) | 
|  | nn = unionread(c, ents, 2048); | 
|  | else { | 
|  | if (off != c->offset) | 
|  | error(Edirseek); | 
|  | nn = c->dev->read(c, ents, 2048, c->devoffset); | 
|  | } | 
|  | } else | 
|  | printd("rock read ok\n"); | 
|  | nnn = mountfix(c, ents, nn, n); | 
|  | /* now convert to akaros kdents. This whole thing needs fixin' */ | 
|  | int total, amt = 0, iter = 0; | 
|  | for (total = 0; total < nnn; total += amt) { | 
|  | amt = convM2kdirent(ents, nnn - total, (struct kdirent *)ep); | 
|  | ents += amt; | 
|  | ep = (uint8_t *) ep + sizeof(struct kdirent); | 
|  | } | 
|  |  | 
|  | ispread = 0; | 
|  | nnn = ep - (uint8_t *) p; | 
|  | } else | 
|  | nnn = nn = c->dev->read(c, p, n, off); | 
|  |  | 
|  | if (!ispread) { | 
|  | spin_lock(&c->lock); | 
|  | c->devoffset += nn; | 
|  | c->offset += nnn; | 
|  | spin_unlock(&c->lock); | 
|  | } | 
|  |  | 
|  | poperror(); | 
|  | cclose(c); | 
|  | return nnn; | 
|  | } | 
|  |  | 
|  | long syswrite(int fd, void *p, size_t n, off_t off) | 
|  | { | 
|  | ERRSTACK(1); | 
|  |  | 
|  | int ispwrite = 1; | 
|  | long r = n; | 
|  | struct chan *c; | 
|  |  | 
|  | printd("%p: ", current->pid); | 
|  | printd("syswrite %d %p %d %d\n", fd, p, n, off); | 
|  | n = 0; | 
|  | if (waserror()) { | 
|  | printk("%p: ", current->pid); | 
|  | printk("BADFD: syswrite fd %d: '%s'\n", fd, current_errstr()); | 
|  | set_errno(EBADF); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | c = fdtochan(fd, OWRITE, 1, 1); | 
|  |  | 
|  | poperror(); | 
|  | if (waserror()) { | 
|  | printk("%p: ", current->pid); | 
|  | printk("IO ERROR:syswrite fd %d: '%s'\n", fd, current_errstr()); | 
|  | set_errno(EIO); | 
|  | if (!ispwrite) { | 
|  | spin_lock(&c->lock); | 
|  | c->offset -= n; | 
|  | spin_unlock(&c->lock); | 
|  | } | 
|  | cclose(c); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (c->qid.type & QTDIR) | 
|  | error(Eisdir); | 
|  |  | 
|  | n = r; | 
|  |  | 
|  | if (off == ~0LL) {	/* use and maintain channel's offset */ | 
|  | spin_lock(&c->lock); | 
|  | off = c->offset; | 
|  | c->offset += n; | 
|  | spin_unlock(&c->lock); | 
|  | } | 
|  |  | 
|  | r = c->dev->write(c, p, n, off); | 
|  |  | 
|  | if (!ispwrite && r < n) { | 
|  | spin_lock(&c->lock); | 
|  | c->offset -= n - r; | 
|  | spin_unlock(&c->lock); | 
|  | } | 
|  |  | 
|  | poperror(); | 
|  | cclose(c); | 
|  | return r; | 
|  |  | 
|  | } | 
|  |  | 
|  | int syscreate(char *name, int omode) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | struct chan *c = NULL; | 
|  | int fd; | 
|  | /* if it exists, it is truncated. | 
|  | * if it does not exists, it's created. | 
|  | * so we don't need these flags. | 
|  | */ | 
|  | omode &= ~(O_CREAT | O_TRUNC); | 
|  |  | 
|  | if (waserror()){ | 
|  | printd("syscreate: bad mode %x\n", omode); | 
|  | set_errno(EINVAL); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | openmode(omode);	/* error check only */ | 
|  |  | 
|  | poperror(); | 
|  |  | 
|  | if (waserror()) { | 
|  | set_errno(EEXIST); | 
|  | printd("syscreate fails:%s:\n", current_errstr()); | 
|  | if (c) | 
|  | cclose(c); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | c = namec(name, Acreate, omode, 0); | 
|  | fd = newfd(c); | 
|  | if (fd < 0) | 
|  | error(Enofd); | 
|  | poperror(); | 
|  | printd("syscreate: return %d\n", fd); | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | int sysopen(char *name, int omode) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | struct chan *c = NULL; | 
|  | int fd; | 
|  | int mustdir = 0; | 
|  | printd("%p: ", current->pid); | 
|  | printd("sysopen %s mode %o\n", name, omode); | 
|  | if (omode & O_NONBLOCK)	/* what to do? */ | 
|  | omode &= ~O_NONBLOCK; | 
|  | if (omode & O_DIRECTORY) { | 
|  | omode &= ~O_DIRECTORY; | 
|  | mustdir = 1; | 
|  | } | 
|  | if ((omode & (O_CREATE | O_EXCL)) == (O_CREATE | O_EXCL)) | 
|  | return syscreate(name, omode); | 
|  | /* TODO: plan9 used to check for both CREATE and TRUNC here, calling create | 
|  | * regardless.  This will need work as we add in other devices. */ | 
|  | if (waserror()) { | 
|  | if (omode & O_CREAT) { | 
|  | poperror(); | 
|  | return syscreate(name, omode); | 
|  | } | 
|  | set_errno(ENOENT); | 
|  | printd("error\n"); | 
|  | if (c) | 
|  | cclose(c); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  | openmode(omode);	/* error check only */ | 
|  | c = namec(name, Aopen, omode, 0); | 
|  | fd = newfd(c); | 
|  | if (fd < 0) | 
|  | error(Enofd); | 
|  | poperror(); | 
|  | printd("%p: ", current->pid); | 
|  | printd("sysopen %s returns %d %x\n", name, fd, fd); | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | int sysclose(int fd) | 
|  | { | 
|  | fdtochan(fd, -1, 0, 0); | 
|  | fdclose(fd, 0); | 
|  | printd("%p: ", current->pid); | 
|  | printd("sysclose %d\n", fd); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int sysstat(char *name, uint8_t * statbuf, int len) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | int r; | 
|  | struct chan *c = NULL; | 
|  | char *aname; | 
|  | int fd; | 
|  | uint8_t data[sizeof(struct dir)]; | 
|  |  | 
|  | if (waserror()) { | 
|  | set_errno(ENOENT); | 
|  | if (c) | 
|  | cclose(c); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | c = namec(name, Aaccess, 0, 0); | 
|  |  | 
|  | r = c->dev->stat(c, data, sizeof(data)); | 
|  |  | 
|  | #if 0 | 
|  | /* we don't currently set the path in stat. Plan9/NIX do. */ | 
|  | aname = pathlast(c->path); | 
|  | if (aname) | 
|  | r = dirsetname(aname, strlen(aname), data, r, sizeof(data)); | 
|  | #endif | 
|  | poperror(); | 
|  | cclose(c); | 
|  |  | 
|  | /* now convert for akaros. */ | 
|  | convM2kstat(data, sizeof(data), (struct kstat *)statbuf); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int sysfstat(int fd, uint8_t * statbuf, int len) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | int r; | 
|  | struct chan *c = NULL; | 
|  | uint8_t data[sizeof(struct dir)]; | 
|  |  | 
|  | if (waserror()) { | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | c = fdtochan(fd, -1, 0, 1); | 
|  |  | 
|  | poperror(); | 
|  |  | 
|  | if (waserror()) { | 
|  | cclose(c); | 
|  | nexterror(); | 
|  | } | 
|  | r = c->dev->stat(c, data, sizeof(data)); | 
|  |  | 
|  | poperror(); | 
|  | cclose(c); | 
|  | /* now convert for akaros. */ | 
|  | convM2kstat(data, sizeof(data), (struct kstat *)statbuf); | 
|  | printd("sysfstat fd %d ok\n", fd); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int sysdup(int ofd, int nfd) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | struct chan *nc, *oc; | 
|  | struct fgrp *f; | 
|  |  | 
|  | /* | 
|  | * int dup(int oldfd, int newfd); | 
|  | * if newfd is < 0, pick anything. | 
|  | * | 
|  | * Close after dup'ing, so date > #d/1 works | 
|  | */ | 
|  | if (waserror()) { | 
|  | set_errno(EBADF); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | oc = fdtochan(ofd, -1, 0, 1); | 
|  |  | 
|  | poperror(); | 
|  |  | 
|  | if (nfd != -1) { | 
|  | panic("Need to sync with VFS"); | 
|  | f = current->fgrp; | 
|  | spin_lock(&f->lock); | 
|  | if (f->closed) { | 
|  | spin_unlock(&f->lock); | 
|  | return -1; | 
|  | } | 
|  | if (nfd < 0 || growfd(f, nfd) < 0) { | 
|  | unlockfgrp(f); | 
|  | cclose(oc); | 
|  | error(Ebadfd); | 
|  | } | 
|  | if (nfd > f->maxfd) | 
|  | f->maxfd = nfd; | 
|  |  | 
|  | nc = f->fd[nfd]; | 
|  | f->fd[nfd] = oc; | 
|  | unlockfgrp(f); | 
|  | if (nc != NULL) | 
|  | cclose(nc); | 
|  | } else { | 
|  | if (waserror()) { | 
|  | cclose(oc); | 
|  | nexterror(); | 
|  | } | 
|  | nfd = newfd(oc); | 
|  | if (nfd < 0) | 
|  | error(Enofd); | 
|  | poperror(); | 
|  | } | 
|  |  | 
|  | printd("sysdup %d -> %d\n", ofd, nfd); | 
|  | return nfd; | 
|  | } | 
|  |  | 
|  | /* TODO: 9ns ns inheritance flags: Shared, copied, or empty.  Looks like we're | 
|  | * copying the fgrp, and sharing the pgrp. */ | 
|  | int plan9setup(struct proc *new_proc, struct proc *parent) | 
|  | { | 
|  | struct proc *old_current; | 
|  | struct kref *new_dot_ref; | 
|  | ERRSTACK(1); | 
|  | if (waserror()) { | 
|  | printd("plan9setup failed\n"); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  | if (!parent) { | 
|  | /* We are probably spawned by the kernel directly, and have no parent to | 
|  | * inherit from.  Be sure to set up fgrp/pgrp before calling namec(). | 
|  | * | 
|  | * TODO: One problem is namec wants a current set for things like | 
|  | * genbuf.  So we'll use new_proc for this bootstrapping.  Note | 
|  | * switch_to() also loads the cr3. */ | 
|  | new_proc->fgrp = dupfgrp(NULL); | 
|  | new_proc->pgrp = newpgrp(); | 
|  | old_current = switch_to(new_proc); | 
|  | new_proc->slash = namec("#r", Atodir, 0, 0); | 
|  | switch_back(new_proc, old_current); | 
|  | /* Want the name to be "/" instead of "#r" */ | 
|  | pathclose(new_proc->slash->path); | 
|  | new_proc->slash->path = newpath("/"); | 
|  | new_proc->dot = cclone(new_proc->slash); | 
|  | poperror(); | 
|  | return 0; | 
|  | } | 
|  | /* Copy semantics: do not change this without revisiting proc_destroy, | 
|  | * close_9ns_files, and closefgrp. */ | 
|  | new_proc->fgrp = dupfgrp(parent->fgrp); | 
|  | /* Shared semantics */ | 
|  | kref_get(&parent->pgrp->ref, 1); | 
|  | new_proc->pgrp = parent->pgrp; | 
|  | /* copy semantics on / and . (doesn't make a lot of sense in akaros o/w) */ | 
|  | /* / should never disappear while we hold a ref to parent */ | 
|  | kref_get(&parent->slash->ref, 1); | 
|  | new_proc->slash = parent->slash; | 
|  | /* dot could change concurrently, and we could fail to gain a ref if whoever | 
|  | * decref'd dot triggered the release.  if that did happen, new_proc->dot | 
|  | * should update and we can try again. */ | 
|  | while (!(new_dot_ref = kref_get_not_zero(&parent->dot->ref, 1))) | 
|  | cpu_relax(); | 
|  | /* And now, we can't trust parent->dot, and need to determine our dot from | 
|  | * the ref we obtained. */ | 
|  | new_proc->dot = container_of(new_dot_ref, struct chan, ref); | 
|  | poperror(); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* bindmount -- common to bind and mount, since they're almost the same. | 
|  | * fd | 
|  | * afd -- auth fd, used to authenticate to a server if needed | 
|  | * | 
|  | * arg0 -- *source* of what we are mounting (old_dir / device in linux) | 
|  | * arg1 -- where we are mounting *onto* (new_dir / dir in linux) | 
|  | * flags -- options, i.e. MAFTER, MBEFORE, etc. | 
|  | * spec -- additional third argument used in special cases like dosfs, etc. | 
|  | * | 
|  | * We're not renaming arg0 and arg1 for ease of diffing with the original 9ns | 
|  | * source code (for now). */ | 
|  | int | 
|  | bindmount(int ismount, | 
|  | int fd, | 
|  | int afd, | 
|  | char* arg0, | 
|  | char* arg1, | 
|  | int flag, | 
|  | char* spec) | 
|  | { | 
|  | ERRSTACK(4);	/* it's still complicated. */ | 
|  | int i; | 
|  | struct dev *dev; | 
|  | struct chan *c0, *c1, *ac, *bc; | 
|  | struct{ | 
|  | struct chan	*chan; | 
|  | struct chan	*authchan; | 
|  | char	*spec; | 
|  | int	flags; | 
|  | }bogus; | 
|  |  | 
|  | if (waserror()){ | 
|  | printk("bindmount: %s\n", current_errstr()); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if((flag&~MMASK) || (flag&MORDER)==(MBEFORE|MAFTER)) | 
|  | error(Ebadarg); | 
|  |  | 
|  | bogus.flags = flag & MCACHE; | 
|  |  | 
|  | if(ismount){ | 
|  | if(current->pgrp->noattach) | 
|  | error(Enoattach); | 
|  |  | 
|  | ac = NULL; | 
|  | bc = fdtochan(fd, ORDWR, 0, 1); | 
|  | if(waserror()) { | 
|  | if(ac) | 
|  | cclose(ac); | 
|  | cclose(bc); | 
|  | nexterror(); | 
|  | } | 
|  |  | 
|  | if(afd >= 0) | 
|  | ac = fdtochan(afd, ORDWR, 0, 1); | 
|  | bogus.chan = bc; | 
|  | bogus.authchan = ac; | 
|  |  | 
|  | bogus.spec = spec; | 
|  | if(waserror()) | 
|  | error(Ebadspec); | 
|  | spec = validnamedup(spec, 1); | 
|  | poperror(); | 
|  |  | 
|  | if(waserror()){ | 
|  | kfree(spec); | 
|  | nexterror(); | 
|  | } | 
|  |  | 
|  | dev = devtabget('M', 0);		//XDYNX | 
|  | if(waserror()){ | 
|  | //devtabdecr(dev); | 
|  | nexterror(); | 
|  | } | 
|  | c0 = dev->attach((char*)&bogus); | 
|  | poperror(); | 
|  | //devtabdecr(dev); | 
|  |  | 
|  | poperror();	/* spec */ | 
|  | kfree(spec); | 
|  | poperror();	/* ac bc */ | 
|  | if(ac) | 
|  | cclose(ac); | 
|  | cclose(bc); | 
|  | }else{ | 
|  | bogus.spec = NULL; | 
|  | /* this is the thing you will bind onto the mount.  old_dir (or device) | 
|  | * in linux terms.  *src* in akaros. */ | 
|  | c0 = namec(arg0, Abind, 0, 0); | 
|  | } | 
|  |  | 
|  | if(waserror()){ | 
|  | cclose(c0); | 
|  | nexterror(); | 
|  | } | 
|  |  | 
|  | /* c1 is the target, where we will mount onto.  new_dir, (or just dir) in | 
|  | * linux terms.  *onto* in Akaros */ | 
|  | c1 = namec(arg1, Amount, 0, 0); | 
|  | if(waserror()){ | 
|  | cclose(c1); | 
|  | nexterror(); | 
|  | } | 
|  |  | 
|  | i = cmount(&c0, c1, flag, bogus.spec); | 
|  |  | 
|  | poperror(); | 
|  | cclose(c1); | 
|  | poperror(); | 
|  | cclose(c0); | 
|  | if(ismount) | 
|  | fdclose(fd, 0); | 
|  | poperror(); | 
|  | return i; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * int unmount(char* name, char* old); | 
|  | */ | 
|  | int | 
|  | sysunmount(char *name, char *old) | 
|  | { | 
|  | ERRSTACK(2); | 
|  | int ret; | 
|  | struct chan *cmount, *cmounted; | 
|  |  | 
|  | if (waserror()){ | 
|  | printd("unmount went poorly\n"); | 
|  | poperror(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  |  | 
|  | cmount = namec(old, Amount, 0, 0); | 
|  |  | 
|  | cmounted = NULL; | 
|  | if(name != NULL) { | 
|  | if(waserror()) { | 
|  | cclose(cmount); | 
|  | nexterror(); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * This has to be namec(..., Aopen, ...) because | 
|  | * if arg[0] is something like /srv/cs or /fd/0, | 
|  | * opening it is the only way to get at the real | 
|  | * struct chan underneath. | 
|  | */ | 
|  | cmounted = namec(name, Aopen, OREAD, 0); | 
|  | poperror(); | 
|  | } | 
|  |  | 
|  | if(waserror()) { | 
|  | cclose(cmount); | 
|  | if(cmounted != NULL) | 
|  | cclose(cmounted); | 
|  | nexterror(); | 
|  | } | 
|  |  | 
|  | cunmount(cmount, cmounted); | 
|  | cclose(cmount); | 
|  | if(cmounted != NULL) | 
|  | cclose(cmounted); | 
|  | poperror(); | 
|  | poperror(); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Notes on concurrency: | 
|  | * - Can't hold spinlocks while we call cclose, since it might sleep eventually. | 
|  | * - We're called from proc_destroy, so we could have concurrent openers trying | 
|  | *   to add to the group (other syscalls), hence the "closed" flag. | 
|  | * - dot and slash chans are dealt with in proc_free.  its difficult to close | 
|  | *   and zero those with concurrent syscalls, since those are a source of krefs. | 
|  | * - the memory is freed in proc_free().  need to wait to do it, since we can | 
|  | *   have concurrent accesses to fgrp before free. | 
|  | * - Once we lock and set closed, no further additions can happen.  To simplify | 
|  | *   our closes, we also allow multiple calls to this func (though that should | 
|  | *   never happen with the current code). */ | 
|  | void close_9ns_files(struct proc *p, bool only_cloexec) | 
|  | { | 
|  | struct fgrp *f = p->fgrp; | 
|  |  | 
|  | spin_lock(&f->lock); | 
|  | if (f->closed) { | 
|  | spin_unlock(&f->lock); | 
|  | warn("Unexpected double-close"); | 
|  | return; | 
|  | } | 
|  | if (!only_cloexec) | 
|  | f->closed = TRUE; | 
|  | spin_unlock(&f->lock); | 
|  |  | 
|  | /* maxfd is a legit val, not a +1 */ | 
|  | for (int i = 0; i <= f->maxfd; i++) { | 
|  | if (!f->fd[i]) | 
|  | continue; | 
|  | if (only_cloexec && !(f->fd[i]->flag & CCEXEC)) | 
|  | continue; | 
|  | cclose(f->fd[i]); | 
|  | f->fd[i] = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | void print_chaninfo(struct chan *ch) | 
|  | { | 
|  | char buf[64] = {0}; | 
|  | printk("Chan pathname: %s, Dev: %s, Devinfo: %s\n", | 
|  | ch->path ? ch->path->s : "no path", | 
|  | ch->dev ? ch->dev->name: "no dev", | 
|  | ch->dev ? ch->dev->chaninfo(ch, buf, sizeof(buf)) : "no info"); | 
|  | if (!ch->dev) | 
|  | printk("No dev: intermediate chan? qid.path: %p\n", ch->qid.path); | 
|  | } | 
|  |  | 
|  | void print_9ns_files(struct proc *p) | 
|  | { | 
|  | struct fgrp *f = p->fgrp; | 
|  | spin_lock(&f->lock); | 
|  | printk("9ns files for proc %d:\n", p->pid); | 
|  | /* maxfd is a legit val, not a +1 */ | 
|  | for (int i = 0; i <= f->maxfd; i++) { | 
|  | if (!f->fd[i]) | 
|  | continue; | 
|  | printk("\t9fs %d, ", i); | 
|  | print_chaninfo(f->fd[i]); | 
|  | } | 
|  | spin_unlock(&f->lock); | 
|  | } | 
|  |  | 
|  | int syspipe(int *pfd) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | struct chan *c[2]; | 
|  | int fd[2]; | 
|  | static char *datastr[] = {"data", "data1"}; | 
|  |  | 
|  | /* | 
|  | * int pipe(int fd[2]); | 
|  | */ | 
|  |  | 
|  | c[0] = namec("#P", Atodir, 0, 0); | 
|  | c[1] = NULL; | 
|  | fd[0] = -1; | 
|  | fd[1] = -1; | 
|  |  | 
|  | if(waserror()){ | 
|  | cclose(c[0]); | 
|  | if(c[1]) | 
|  | cclose(c[1]); | 
|  | return -1; | 
|  | } | 
|  | c[1] = cclone(c[0]); | 
|  | if(walk(&c[0], datastr+0, 1, 1, NULL) < 0) | 
|  | error(Egreg); | 
|  | if(walk(&c[1], datastr+1, 1, 1, NULL) < 0) | 
|  | error(Egreg); | 
|  | c[0] = c[0]->dev->open(c[0], ORDWR); | 
|  | c[1] = c[1]->dev->open(c[1], ORDWR); | 
|  | if(newfd2(fd, c) < 0) | 
|  | error(Enofd); | 
|  | poperror(); | 
|  |  | 
|  | pfd[0] = fd[0]; | 
|  | pfd[1] = fd[1]; | 
|  | printd("syspipe returns [%d,%d]\n", | 
|  | fd[0], fd[1]); | 
|  | return 0; | 
|  | } | 
|  |  |