Fix up sys*stat
[akaros.git] / kern / src / ns / sysfile.c
index 249edf4..13b46a8 100644 (file)
 #include <smp.h>
 #include <ip.h>
 
-static int
-growfd(struct fgrp *f, int fd)
+static int growfd(struct fgrp *f, int fd)
 {
        int n;
        struct chan **nfd, **ofd;
 
-       if(fd < f->nfd)
+       if (fd < f->nfd)
                return 0;
-       n = f->nfd+DELTAFD;
-       if(n > MAXNFD)
+       n = f->nfd + DELTAFD;
+       if (n > MAXNFD)
                n = MAXNFD;
-       if(fd >= n)
+       if (fd >= n)
                return -1;
        nfd = kzmalloc(n * sizeof(struct chan *), 0);
-       if(nfd == NULL)
+       if (nfd == NULL)
                return -1;
        ofd = f->fd;
-       memmove(nfd, ofd, f->nfd*sizeof(struct chan *));
+       memmove(nfd, ofd, f->nfd * sizeof(struct chan *));
        f->fd = nfd;
        f->nfd = n;
        kfree(ofd);
        return 0;
 }
 
-int
-newfd(struct chan *c)
+int newfd(struct chan *c)
 {
        int i;
        struct fgrp *f = current->fgrp;
 
        spin_lock(&f->lock);
-       for(i=f->minfd; i<f->nfd; i++)
-               if(f->fd[i] == 0)
+       if (f->closed) {
+               spin_unlock(&f->lock);
+               return -1;
+       }
+       /* VFS hack */
+       /* We'd like to ask it to start at f->minfd, but that would require us to
+        * know if we closed anything.  Since we share the FD numbers with the VFS,
+        * there is no way to know that. */
+       i = get_fd(&current->open_files, 0);
+       assert(f->fd[i] == 0);
+#if 0  // 9ns style
+       /* TODO: use a unique integer allocator */
+       for (i = f->minfd; i < f->nfd; i++)
+               if (f->fd[i] == 0)
                        break;
-       if(i >= f->nfd && growfd(f, i) < 0){
+       if (i >= f->nfd && growfd(f, i) < 0) {
                spin_unlock(&f->lock);
                exhausted("file descriptors");
                return -1;
        }
+#endif
        f->minfd = i + 1;
-       if(i > f->maxfd)
+       if (i > f->maxfd)
                f->maxfd = i;
        f->fd[i] = c;
        spin_unlock(&f->lock);
        return i;
 }
 
-struct chan*
-fdtochan(struct fgrp *f, int fd, int mode, int chkmnt, int iref)
+struct chan *fdtochan(struct fgrp *f, int fd, int mode, int chkmnt, int iref)
 {
        struct chan *c;
 
        c = 0;
 
        spin_lock(&f->lock);
-       if(fd<0 || f->maxfd<fd || (c = f->fd[fd])==0) {
+       if (f->closed) {
+               spin_unlock(&f->lock);
+               error("File group closed");
+       }
+       if (fd < 0 || f->maxfd < fd || (c = f->fd[fd]) == 0) {
                spin_unlock(&f->lock);
                error(Ebadfd);
        }
-       if(iref)
+       if (iref)
                kref_get(&c->ref, 1);
        spin_unlock(&f->lock);
 
-       if(chkmnt && (c->flag&CMSG)) {
-               if(iref)
+       if (chkmnt && (c->flag & CMSG)) {
+               if (iref)
                        cclose(c);
                error(Ebadusefd);
        }
 
-       if(mode<0 || c->mode==ORDWR)
+       if (mode < 0 || c->mode == ORDWR)
                return c;
 
-       if((mode&OTRUNC) && c->mode==OREAD) {
-               if(iref)
+       if ((mode & OTRUNC) && IS_RDONLY(c->mode)) {
+               if (iref)
                        cclose(c);
                error(Ebadusefd);
        }
 
-       if((mode&~OTRUNC) != c->mode) {
-               if(iref)
+       /* TODO: this is probably wrong.  if you get this from a dev, in the dev's
+        * open, you are probably saving mode directly, without passing it through
+        * openmode. */
+       if ((mode & ~OTRUNC) != c->mode) {
+               warn("Trunc mode issue: mode %o, mode minus trunc %o, chan mode %o\n",
+                        mode, mode & ~OTRUNC, c->mode);
+               if (iref)
                        cclose(c);
                error(Ebadusefd);
        }
@@ -100,21 +119,22 @@ fdtochan(struct fgrp *f, int fd, int mode, int chkmnt, int iref)
        return c;
 }
 
-long
-kchanio(void *vc, void *buf, int n, int mode)
+long kchanio(void *vc, void *buf, int n, int mode)
 {
-       ERRSTACK(2);
+       ERRSTACK(1);
        int r;
        struct chan *c;
 
        c = vc;
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
-       if(mode == OREAD)
-               r = devtab[c->type]->read(c, buf, n, c->offset);
+       if (IS_RDONLY(mode))
+               r = devtab[c->type].read(c, buf, n, c->offset);
        else
-               r = devtab[c->type]->write(c, buf, n, c->offset);
+               r = devtab[c->type].write(c, buf, n, c->offset);
 
        spin_lock(&c->lock);
        c->offset += r;
@@ -123,51 +143,67 @@ kchanio(void *vc, void *buf, int n, int mode)
        return r;
 }
 
-int
-openmode(uint32_t o)
+int openmode(uint32_t omode)
 {
-       if(o >= (OTRUNC|OCEXEC|ORCLOSE|OEXEC))
+#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 their part (it's
+        * the only one of their non-RW-related flags that isn't masked out) */
+       if (o >= (OTRUNC | OCEXEC | ORCLOSE | OEXEC))
                error(Ebadarg);
-       o &= ~(OTRUNC|OCEXEC|ORCLOSE);
-       if(o > OEXEC)
+       o &= ~(OTRUNC | OCEXEC | ORCLOSE);
+       if (o > OEXEC)
                error(Ebadarg);
-       if(o == OEXEC)
+       if (o == OEXEC)
                return OREAD;
        return o;
+#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;
 }
 
-void
-fdclose(struct fgrp *f, int fd)
+void fdclose(struct fgrp *f, int fd)
 {
        int i;
        struct chan *c;
 
        spin_lock(&f->lock);
+       if (f->closed) {
+               spin_unlock(&f->lock);
+               return;
+       }
        c = f->fd[fd];
-       if(c == 0){
+       if (c == 0) {
                /* can happen for users with shared fd tables */
                spin_unlock(&f->lock);
                return;
        }
        f->fd[fd] = 0;
-       if(fd == f->maxfd)
-               for(i=fd; --i>=0 && f->fd[i]==0; )
+       if (fd == f->maxfd)
+               for (i = fd; --i >= 0 && f->fd[i] == 0;)
                        f->maxfd = i;
-       if(fd < f->minfd)
+       if (fd < f->minfd)
                f->minfd = fd;
+       /* VFS hack: give the FD back to VFS */
+       put_fd(&current->open_files, fd);
        spin_unlock(&f->lock);
        cclose(c);
 }
 
-int
-syschdir(char *path)
+int syschdir(char *path)
 {
-       ERRSTACK(2);
+       ERRSTACK(1);
        struct chan *c;
        struct pgrp *pg;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        c = namec(path, Atodir, 0, 0);
        pg = current->pgrp;
@@ -177,12 +213,13 @@ syschdir(char *path)
        return 0;
 }
 
-int
-fgrpclose(struct fgrp *f, int fd)
+int fgrpclose(struct fgrp *f, int fd)
 {
-       ERRSTACK(2);
-       if(waserror())
+       ERRSTACK(1);
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        /*
         * Take no reference on the chan because we don't really need the
@@ -195,30 +232,30 @@ fgrpclose(struct fgrp *f, int fd)
        return 0;
 }
 
-int
-sysclose(int fd)
+int sysclose(int fd)
 {
        return fgrpclose(current->fgrp, fd);
 }
 
-int
-syscreate(char *path, int mode, uint32_t perm)
+int syscreate(char *path, int mode, uint32_t perm)
 {
        ERRSTACK(2);
        int fd;
        struct chan *c;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
-       openmode(mode&~OEXCL);  /* error check only; OEXCL okay here */
+       openmode(mode & ~OEXCL);        /* error check only; OEXCL okay here */
        c = namec(path, Acreate, mode, perm);
-       if(waserror()) {
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
        fd = newfd(c);
-       if(fd < 0)
+       if (fd < 0)
                error(Enofd);
        poperror();
 
@@ -226,42 +263,49 @@ syscreate(char *path, int mode, uint32_t perm)
        return fd;
 }
 
-int
-sysdup(int old, int new)
+int sysdup(int old, int new)
 {
        ERRSTACK(2);
        int fd;
        struct chan *c, *oc;
        struct fgrp *f = current->fgrp;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        c = fdtochan(current->fgrp, old, -1, 0, 1);
-       if(c->qid.type & QTAUTH)
+       if (c->qid.type & QTAUTH)
                error(Eperm);
        fd = new;
-       if(fd != -1){
+       if (fd != -1) {
+               /* ideally we'll be done with the VFS before we fix this */
+               panic("Need to sync with the VFS");
                spin_lock(&f->lock);
-               if(fd<0 || growfd(f, fd) < 0) {
+               if (f->closed) {
+                       spin_unlock(&f->lock);
+                       return -1;
+               }
+               if (fd < 0 || growfd(f, fd) < 0) {
                        spin_unlock(&f->lock);
                        cclose(c);
                        error(Ebadfd);
                }
-               if(fd > f->maxfd)
+               if (fd > f->maxfd)
                        f->maxfd = fd;
                oc = f->fd[fd];
                f->fd[fd] = c;
                spin_unlock(&f->lock);
-               if(oc)
+               if (oc)
                        cclose(oc);
-       }else{
-               if(waserror()) {
+       } else {
+               if (waserror()) {
                        cclose(c);
                        nexterror();
                }
                fd = newfd(c);
-               if(fd < 0)
+               if (fd < 0)
                        error(Enofd);
                poperror();
        }
@@ -269,65 +313,44 @@ sysdup(int old, int new)
        return fd;
 }
 
-int
-sysfstat(int fd, uint8_t *buf, int n)
-{
-       ERRSTACK(2);
-       struct chan *c;
-
-       if(waserror())
-               return -1;
-
-       c = fdtochan(current->fgrp, fd, -1, 0, 1);
-       if(waserror()) {
-               cclose(c);
-               nexterror();
-       }
-       devtab[c->type]->stat(c, buf, n);
-
-       poperror();
-       cclose(c);
-
-       poperror();
-       return n;
-}
-
-char*
-sysfd2path(int fd)
+char *sysfd2path(int fd)
 {
-       ERRSTACK(2);
+       ERRSTACK(1);
        struct chan *c;
        char *s;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return NULL;
+       }
        c = fdtochan(current->fgrp, fd, -1, 0, 1);
        s = NULL;
-       if(c->name != NULL){
+       if (c->name != NULL) {
                s = kzmalloc(c->name->len + 1, 0);
-               if(s == NULL){
+               if (s == NULL) {
                        cclose(c);
                        error(Enomem);
                }
-               memmove(s, c->name->s, c->name->len+1);
+               memmove(s, c->name->s, c->name->len + 1);
                cclose(c);
        }
        poperror();
        return s;
 }
 
-int
-sysfauth(int fd, char *aname)
+int sysfauth(int fd, char *aname)
 {
        ERRSTACK(2);
        struct chan *c, *ac;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        validname(aname, 0);
        c = fdtochan(current->fgrp, fd, ORDWR, 0, 1);
-       if(waserror()){
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
@@ -338,13 +361,13 @@ sysfauth(int fd, char *aname)
        poperror();     /* c */
        cclose(c);
 
-       if(waserror()){
+       if (waserror()) {
                cclose(ac);
                nexterror();
        }
 
        fd = newfd(ac);
-       if(fd < 0)
+       if (fd < 0)
                error(Enofd);
        poperror();     /* ac */
 
@@ -353,22 +376,23 @@ sysfauth(int fd, char *aname)
        return fd;
 }
 
-int
-sysfversion(int fd, unsigned int msize, char *vers, unsigned int arglen)
+int sysfversion(int fd, unsigned int msize, char *vers, unsigned int arglen)
 {
        ERRSTACK(2);
        int m;
        struct chan *c;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        /* check there's a NUL in the version string */
-       if(arglen==0 || memchr(vers, 0, arglen)==0)
+       if (arglen == 0 || memchr(vers, 0, arglen) == 0)
                error(Ebadarg);
 
        c = fdtochan(current->fgrp, fd, ORDWR, 0, 1);
-       if(waserror()){
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
@@ -382,66 +406,73 @@ sysfversion(int fd, unsigned int msize, char *vers, unsigned int arglen)
        return m;
 }
 
-int
-syspipe(int fd[2])
+int syspipe(int fd[2])
 {
-       ERRSTACK(2);
+       ERRSTACK(1);
        struct dev *d;
        struct fgrp *f;
        struct chan *c[2];
-       static char *names[] = {"data", "data1"};
+       static char *names[] = { "data", "data1" };
 
        f = current->fgrp;
 
-       d = devtab[devno('|', 0)];
+       d = &devtab[devno('|', 0)];
        c[0] = namec("#|", Atodir, 0, 0);
        c[1] = 0;
        fd[0] = -1;
        fd[1] = -1;
-       if(waserror()) {
-               if(c[0] != 0)
+       if (waserror()) {
+               if (c[0] != 0)
                        cclose(c[0]);
-               if(c[1] != 0)
+               if (c[1] != 0)
                        cclose(c[1]);
-               if(fd[0] >= 0)
-                       f->fd[fd[0]]=0;
-               if(fd[1] >= 0)
-                       f->fd[fd[1]]=0;
+               if (fd[0] >= 0) {
+                       /* VFS hack */
+                       put_fd(&current->open_files, fd[0]);
+                       f->fd[fd[0]] = 0;
+               }
+               if (fd[1] >= 0) {
+                       /* VFS hack */
+                       put_fd(&current->open_files, fd[1]);
+                       f->fd[fd[1]] = 0;
+               }
+               poperror();
                return -1;
        }
        c[1] = cclone(c[0]);
-       if(walk(&c[0], &names[0], 1, 1, NULL) < 0)
+       if (walk(&c[0], &names[0], 1, 1, NULL) < 0)
                error(Egreg);
-       if(walk(&c[1], &names[1], 1, 1, NULL) < 0)
+       if (walk(&c[1], &names[1], 1, 1, NULL) < 0)
                error(Egreg);
        c[0] = d->open(c[0], ORDWR);
        c[1] = d->open(c[1], ORDWR);
        fd[0] = newfd(c[0]);
-       if(fd[0] < 0)
+       if (fd[0] < 0)
                error(Enofd);
        fd[1] = newfd(c[1]);
-       if(fd[1] < 0)
+       if (fd[1] < 0)
                error(Enofd);
        poperror();
        return 0;
 }
 
-int
-sysfwstat(int fd, uint8_t *buf, int n)
+int sysfwstat(int fd, uint8_t * buf, int n)
 {
        ERRSTACK(2);
        struct chan *c;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        validstat(buf, n);
        c = fdtochan(current->fgrp, fd, -1, 1, 1);
-       if(waserror()) {
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
-       n = devtab[c->type]->wstat(c, buf, n);
+       n = devtab[c->type].wstat(c, buf, n);
        poperror();
        cclose(c);
 
@@ -449,18 +480,17 @@ sysfwstat(int fd, uint8_t *buf, int n)
        return n;
 }
 
-long
-bindmount(struct chan *c, char *old, int flag, char *spec)
+long bindmount(struct chan *c, char *old, int flag, char *spec)
 {
-       ERRSTACK(2);
+       ERRSTACK(1);
        int ret;
        struct chan *c1;
 
-       if(flag>MMASK || (flag&MORDER) == (MBEFORE|MAFTER))
+       if (flag > MMASK || (flag & MORDER) == (MBEFORE | MAFTER))
                error(Ebadarg);
 
        c1 = namec(old, Amount, 0, 0);
-       if(waserror()){
+       if (waserror()) {
                cclose(c1);
                nexterror();
        }
@@ -471,18 +501,19 @@ bindmount(struct chan *c, char *old, int flag, char *spec)
        return ret;
 }
 
-int
-sysbind(char *new, char *old, int flags)
+int sysbind(char *new, char *old, int flags)
 {
        ERRSTACK(2);
        long r;
        struct chan *c0;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        c0 = namec(new, Abind, 0, 0);
-       if(waserror()) {
+       if (waserror()) {
                cclose(c0);
                nexterror();
        }
@@ -494,33 +525,39 @@ sysbind(char *new, char *old, int flags)
        return r;
 }
 
-int
-sysmount(int fd, int afd, char *old, int flags, char *spec)
+int sysmount(int fd, int afd, char *old, int flags, char *spec)
 {
-       ERRSTACK(2);
+       ERRSTACK(1);
        long r;
-       volatile struct { struct chan *c; } c0;
-       volatile struct { struct chan *c; } bc;
-       volatile struct { struct chan *c; } ac;
+       volatile struct {
+               struct chan *c;
+       } c0;
+       volatile struct {
+               struct chan *c;
+       } bc;
+       volatile struct {
+               struct chan *c;
+       } ac;
        struct mntparam mntparam;
 
        ac.c = NULL;
        bc.c = NULL;
        c0.c = NULL;
-       if(waserror()) {
+       if (waserror()) {
                cclose(ac.c);
                cclose(bc.c);
                cclose(c0.c);
+               poperror();
                return -1;
        }
        bc.c = fdtochan(current->fgrp, fd, ORDWR, 0, 1);
-       if(afd >= 0)
+       if (afd >= 0)
                ac.c = fdtochan(current->fgrp, afd, ORDWR, 0, 1);
        mntparam.chan = bc.c;
        mntparam.authchan = ac.c;
        mntparam.spec = spec;
        mntparam.flags = flags;
-       c0.c = devtab[devno('M', 0)]->attach(( char *)&mntparam);
+       c0.c = devtab[devno('M', 0)].attach((char *)&mntparam);
 
        r = bindmount(c0.c, old, flags, spec);
        poperror();
@@ -531,23 +568,27 @@ sysmount(int fd, int afd, char *old, int flags, char *spec)
        return r;
 }
 
-int
-sysunmount(char *old, char *new)
+int sysunmount(char *old, char *new)
 {
-       ERRSTACK(2);
-       volatile struct { struct chan *c; } cmount;
-       volatile struct { struct chan *c; } cmounted;
+       ERRSTACK(1);
+       volatile struct {
+               struct chan *c;
+       } cmount;
+       volatile struct {
+               struct chan *c;
+       } cmounted;
 
        cmount.c = NULL;
        cmounted.c = NULL;
-       if(waserror()) {
+       if (waserror()) {
                cclose(cmount.c);
                cclose(cmounted.c);
+               poperror();
                return -1;
        }
 
        cmount.c = namec(new, Amount, 0, 0);
-       if(old != NULL && old[0] != '\0') {
+       if (old != NULL && old[0] != '\0') {
                /*
                 * This has to be namec(..., Aopen, ...) because
                 * if arg[0] is something like /srv/cs or /fd/0,
@@ -564,24 +605,25 @@ sysunmount(char *old, char *new)
        return 0;
 }
 
-int
-sysopen(char *path, int mode)
+int sysopen(char *path, int vfs_flags)
 {
        ERRSTACK(2);
        int fd;
        struct chan *c;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
-       openmode(mode);                         /* error check only */
-       c = namec(path, Aopen, mode, 0);
-       if(waserror()){
+       openmode(vfs_flags);    /* error check only */
+       c = namec(path, Aopen, vfs_flags, 0);
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
        fd = newfd(c);
-       if(fd < 0)
+       if (fd < 0)
                error(Enofd);
        poperror();
 
@@ -589,10 +631,9 @@ sysopen(char *path, int mode)
        return fd;
 }
 
-long
-unionread(struct chan *c, void *va, long n)
+long unionread(struct chan *c, void *va, long n)
 {
-       ERRSTACK(2);
+       ERRSTACK(1);
        int i;
        long nr;
        struct mhead *m;
@@ -603,30 +644,41 @@ unionread(struct chan *c, void *va, long n)
        rlock(&m->lock);
        mount = m->mount;
        /* bring mount in sync with c->uri and c->umc */
-       for(i = 0; mount != NULL && i < c->uri; i++)
+       for (i = 0; mount != NULL && i < c->uri; i++)
                mount = mount->next;
 
        nr = 0;
-       while(mount != NULL) {
+       while (mount != NULL) {
                /* Error causes component of union to be skipped */
-               if(mount->to && !waserror()) {
-                       if(c->umc == NULL){
-                               c->umc = cclone(mount->to);
-                               c->umc = devtab[c->umc->type]->open(c->umc, OREAD);
+               if (mount->to) {
+                       /* normally we want to discard the error, but for our ghetto kdirent
+                        * hack, we need to repeat unionread if we saw a Eshort */
+                       if (waserror()) {
+                               if (!strcmp(current_errstr(), Eshort)) {
+                                       runlock(&m->lock);
+                                       qunlock(&c->umqlock);
+                                       nexterror();
+                               }
+                               /* poperror done below for either branch */
+                       } else {
+                               if (c->umc == NULL) {
+                                       c->umc = cclone(mount->to);
+                                       c->umc = devtab[c->umc->type].open(c->umc, OREAD);
+                               }
+
+                               nr = devtab[c->umc->type].read(c->umc, va, n, c->umc->offset);
+                               if (nr < 0)
+                                       nr = 0; /* dev.c can return -1 */
+                               c->umc->offset += nr;
                        }
-       
-                       nr = devtab[c->umc->type]->read(c->umc, va, n, c->umc->offset);
-                       if(nr < 0)
-                               nr = 0; /* dev.c can return -1 */
-                       c->umc->offset += nr;
-                       poperror();
+                       poperror();     /* pop regardless */
                }
-               if(nr > 0)
+               if (nr > 0)
                        break;
 
                /* Advance to next element */
                c->uri++;
-               if(c->umc) {
+               if (c->umc) {
                        cclose(c->umc);
                        c->umc = NULL;
                }
@@ -637,52 +689,91 @@ unionread(struct chan *c, void *va, long n)
        return nr;
 }
 
-static void
-unionrewind(struct chan *c)
+static void unionrewind(struct chan *c)
 {
        qlock(&c->umqlock);
        c->uri = 0;
-       if(c->umc){
+       if (c->umc) {
                cclose(c->umc);
                c->umc = NULL;
        }
        qunlock(&c->umqlock);
 }
 
-static long
-rread(int fd, void *va, long n, int64_t *offp)
+static long rread(int fd, void *va, long n, int64_t * offp)
 {
-       ERRSTACK(2);
+       ERRSTACK(3);
        int dir;
        struct chan *c;
        int64_t off;
 
-       if(waserror())
+       /* dirty dirent hack */
+#define MIN_M_BUF_SZ 58        /* TODO: 59 is the smallest i've seen */
+       void *real_va = va;
+       void *buf_for_M = 0;
+       size_t buf_sz = 0;
+       long real_n = n;
+
+       if (waserror()) {
+               kfree(buf_for_M);
+               poperror();
                return -1;
+       }
 
        c = fdtochan(current->fgrp, fd, OREAD, 1, 1);
-       if(waserror()) {
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
 
-       if(n < 0)
+       if (n < 0)
                error(Etoosmall);
 
        dir = c->qid.type & QTDIR;
-       if(dir && c->umh)
+
+       /* dirty kdirent hack: userspace is expecting kdirents, but all of 9ns
+        * produces Ms.  i'm assuming we're only being asked to read a single
+        * dirent, which is usually the case for calls like readdir() (which just
+        * calls read for a single dirent). */
+       if (dir)
+               assert(n >= sizeof(struct kdirent));
+       buf_sz = 2 * MIN_M_BUF_SZ - 1;
+       /* We need to read exactly one dirent and avoid reading too much from the
+        * underlying dev, so that subsequent reads don't miss any dirents.  So we
+        * start small, and if our buffer is too small (e.g. for long named
+        * dirents), we increase by a minumum amount.  This way, we'll succeed on
+        * the next invocation, but we won't have enough room for more than one
+        * entry. */
+       while (waserror()) {
+               /* FYI: this scheme doesn't work with mounts */
+               if (!dir || strcmp(current_errstr(), Eshort))
+                       nexterror();
+               buf_sz += MIN_M_BUF_SZ;
+               poperror();
+       }
+       if (dir) {
+               if (!buf_for_M)
+                       buf_for_M = kmalloc(buf_sz, KMALLOC_WAIT);
+               else
+                       buf_for_M = krealloc(buf_for_M, buf_sz, KMALLOC_WAIT);
+               va = buf_for_M;
+               n = buf_sz;
+       }
+
+       /* this is the normal plan9 read */
+       if (dir && c->umh)
                n = unionread(c, va, n);
-       else{
-               if(offp == NULL){
+       else {
+               if (offp == NULL) {
                        spin_lock(&c->lock);    /* lock for int64_t assignment */
                        off = c->offset;
                        spin_unlock(&c->lock);
-               }else
+               } else
                        off = *offp;
-               if(off < 0)
+               if (off < 0)
                        error(Enegoff);
-               if(off == 0){
-                       if(offp == NULL){
+               if (off == 0) {
+                       if (offp == NULL) {
                                spin_lock(&c->lock);
                                c->offset = 0;
                                c->dri = 0;
@@ -690,12 +781,19 @@ rread(int fd, void *va, long n, int64_t *offp)
                        }
                        unionrewind(c);
                }
-               n = devtab[c->type]->read(c, va, n, off);
+               n = devtab[c->type].read(c, va, n, off);
                spin_lock(&c->lock);
                c->offset += n;
                spin_unlock(&c->lock);
        }
 
+       /* dirty kdirent hack */
+       if (dir) {
+               convM2kdirent(buf_for_M, buf_sz, real_va, 0);
+               kfree(buf_for_M);
+       }
+       poperror();     /* matching our while(waserror) */
+
        poperror();
        cclose(c);
 
@@ -703,39 +801,38 @@ rread(int fd, void *va, long n, int64_t *offp)
        return n;
 }
 
-long
-sysread(int fd, void *va, long n)
+long sysread(int fd, void *va, long n)
 {
        return rread(fd, va, n, NULL);
 }
 
-long
-syspread(int fd, void *va, long n, int64_t off)
+long syspread(int fd, void *va, long n, int64_t off)
 {
        return rread(fd, va, n, &off);
 }
 
-int
-sysremove(char *path)
+int sysremove(char *path)
 {
        ERRSTACK(2);
        struct chan *c;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        c = namec(path, Aremove, 0, 0);
-       if(waserror()) {
-               c->type = 0;    /* see below */
+       if (waserror()) {
+               c->type = -1;   /* see below */
                cclose(c);
                nexterror();
        }
-       devtab[c->type]->remove(c);
+       devtab[c->type].remove(c);
        /*
         * Remove clunks the fid, but we need to recover the Chan
-        * so fake it up.  rootclose() is known to be a nop.
+        * so fake it up.  -1 aborts the dev's close.
         */
-       c->type = 0;
+       c->type = -1;
        poperror();
        cclose(c);
 
@@ -743,69 +840,70 @@ sysremove(char *path)
        return 0;
 }
 
-int64_t
-sysseek(int fd, int64_t off, int whence)
+int64_t sysseek(int fd, int64_t off, int whence)
 {
        ERRSTACK(2);
        struct dir *dir;
        struct chan *c;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        c = fdtochan(current->fgrp, fd, -1, 1, 1);
-       if(waserror()) {
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
 
-       if(devtab[c->type]->dc == '|')
+       if (devtab[c->type].dc == '|')
                error(Eisstream);
 
-       switch(whence) {
-       case 0:
-               if(c->qid.type & QTDIR){
-                       if(off != 0)
+       switch (whence) {
+               case 0:
+                       if (c->qid.type & QTDIR) {
+                               if (off != 0)
+                                       error(Eisdir);
+                               unionrewind(c);
+                       } else if (off < 0)
+                               error(Enegoff);
+                       spin_lock(&c->lock);    /* lock for int64_t assignment */
+                       c->offset = off;
+                       spin_unlock(&c->lock);
+                       break;
+
+               case 1:
+                       if (c->qid.type & QTDIR)
                                error(Eisdir);
-                       unionrewind(c);
-               }else if(off < 0)
-                       error(Enegoff);
-               spin_lock(&c->lock);    /* lock for int64_t assignment */
-               c->offset = off;
-               spin_unlock(&c->lock);
-               break;
-
-       case 1:
-               if(c->qid.type & QTDIR)
-                       error(Eisdir);
-               spin_lock(&c->lock);    /* lock for read/write update */
-               off += c->offset;
-               if(off < 0){
+                       spin_lock(&c->lock);    /* lock for read/write update */
+                       off += c->offset;
+                       if (off < 0) {
+                               spin_unlock(&c->lock);
+                               error(Enegoff);
+                       }
+                       c->offset = off;
                        spin_unlock(&c->lock);
-                       error(Enegoff);
-               }
-               c->offset = off;
-               spin_unlock(&c->lock);
-               break;
-
-       case 2:
-               if(c->qid.type & QTDIR)
-                       error(Eisdir);
-               dir = chandirstat(c);
-               if(dir == NULL)
-                       error("internal error: stat error in seek");
-               off += dir->length;
-               kfree(dir);
-               if(off < 0)
-                       error(Enegoff);
-               spin_lock(&c->lock);    /* lock for read/write update */
-               c->offset = off;
-               spin_unlock(&c->lock);
-               break;
+                       break;
 
-       default:
-               error(Ebadarg);
-               break;
+               case 2:
+                       if (c->qid.type & QTDIR)
+                               error(Eisdir);
+                       dir = chandirstat(c);
+                       if (dir == NULL)
+                               error("internal error: stat error in seek");
+                       off += dir->length;
+                       kfree(dir);
+                       if (off < 0)
+                               error(Enegoff);
+                       spin_lock(&c->lock);    /* lock for read/write update */
+                       c->offset = off;
+                       spin_unlock(&c->lock);
+                       break;
+
+               default:
+                       error(Ebadarg);
+                       break;
        }
        poperror();
        c->dri = 0;
@@ -814,16 +912,15 @@ sysseek(int fd, int64_t off, int whence)
        return off;
 }
 
-void
-validstat(uint8_t *s, int n)
+void validstat(uint8_t * s, int n)
 {
        int m;
        char buf[64];
 
-       if(statcheck(s, n) < 0)
+       if (statcheck(s, n) < 0)
                error(Ebadstat);
        /* verify that name entry is acceptable */
-       s += STATFIXLEN - 4*BIT16SZ;    /* location of first string */
+       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
@@ -832,80 +929,147 @@ validstat(uint8_t *s, int n)
         */
        m = GBIT16(s);
        s += BIT16SZ;
-       if(m+1 > sizeof buf)
+       if (m + 1 > sizeof buf)
                return;
        memmove(buf, s, m);
        buf[m] = '\0';
        /* name could be '/' */
-       if(strcmp(buf, "/") != 0)
+       if (strcmp(buf, "/") != 0)
                validname(buf, 0);
 }
 
-int
-sysstat(char *path, uint8_t *buf, int n)
+int sysfstat(int fd, uint8_t *buf, int n)
 {
        ERRSTACK(2);
        struct chan *c;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
-       c = namec(path, Aaccess, 0, 0);
-       if(waserror()){
+       c = fdtochan(current->fgrp, fd, -1, 0, 1);
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
-       devtab[c->type]->stat(c, buf, n);
+       devtab[c->type].stat(c, buf, n);
+
        poperror();
        cclose(c);
 
        poperror();
-       return 0;
+       return n;
+}
+
+int sysfstatakaros(int fd, struct kstat *ks)
+{
+       int n = 4096;
+       uint8_t *buf;
+       buf = kmalloc(n, KMALLOC_WAIT);
+       n = sysfstat(fd, buf, n);
+       if (n > 0) {
+               convM2kstat(buf, n, ks);
+               n = 0;
+       }
+       kfree(buf);
+       return n;
 }
 
-static long
-rwrite(int fd, void *va, long n, int64_t *offp)
+int sysstat(char *path, uint8_t *buf, int n)
 {
        ERRSTACK(2);
        struct chan *c;
+
+       if (waserror()) {
+               poperror();
+               return -1;
+       }
+
+       c = namec(path, Aaccess, 0, 0);
+       if (waserror()) {
+               cclose(c);
+               nexterror();
+       }
+       devtab[c->type].stat(c, buf, n);
+       poperror();
+       cclose(c);
+
+       poperror();
+
+       return n;
+}
+
+int sysstatakaros(char *path, struct kstat *ks)
+{
+       int n = 4096;
+       uint8_t *buf;
+       buf = kmalloc(n, KMALLOC_WAIT);
+       n = sysstat(path, buf, n);
+       if (n > 0) {
+               convM2kstat(buf, n, ks);
+               n = 0;
+       }
+       kfree(buf);
+       return n;
+}
+
+static long rwrite(int fd, void *va, long n, int64_t * offp)
+{
+       ERRSTACK(3);
+       struct chan *c;
+       struct dir *dir;
        int64_t off;
        long m;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
        c = fdtochan(current->fgrp, fd, OWRITE, 1, 1);
-       if(waserror()) {
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
-       if(c->qid.type & QTDIR)
+       if (c->qid.type & QTDIR)
                error(Eisdir);
 
-       if(n < 0)
+       if (n < 0)
                error(Etoosmall);
 
-       if(offp == NULL){
+       if (offp == NULL) {
+               /* append changes the offset to the end, and even if we fail later, this
+                * change will persist */
+               if (c->flag & CAPPEND) {
+                       dir = chandirstat(c);
+                       if (!dir)
+                               error("internal error: stat error in append write");
+                       spin_lock(&c->lock);    /* legacy lock for int64 assignment */
+                       c->offset = dir->length;
+                       spin_unlock(&c->lock);
+                       kfree(dir);
+               }
                spin_lock(&c->lock);
                off = c->offset;
                c->offset += n;
                spin_unlock(&c->lock);
-       }else
+       } else
                off = *offp;
 
-       if(waserror()){
-               if(offp == NULL){
+       if (waserror()) {
+               if (offp == NULL) {
                        spin_lock(&c->lock);
                        c->offset -= n;
                        spin_unlock(&c->lock);
                }
                nexterror();
        }
-       if(off < 0)
+       if (off < 0)
                error(Enegoff);
-       m = devtab[c->type]->write(c, va, n, off);
+       m = devtab[c->type].write(c, va, n, off);
        poperror();
 
-       if(offp == NULL && m < n){
+       if (offp == NULL && m < n) {
                spin_lock(&c->lock);
                c->offset -= n - m;
                spin_unlock(&c->lock);
@@ -918,34 +1082,33 @@ rwrite(int fd, void *va, long n, int64_t *offp)
        return n;
 }
 
-long
-syswrite(int fd, void *va, long n)
+long syswrite(int fd, void *va, long n)
 {
        return rwrite(fd, va, n, NULL);
 }
 
-long
-syspwrite(int fd, void *va, long n, int64_t off)
+long syspwrite(int fd, void *va, long n, int64_t off)
 {
        return rwrite(fd, va, n, &off);
 }
 
-int
-syswstat(char *path, uint8_t *buf, int n)
+int syswstat(char *path, uint8_t * buf, int n)
 {
        ERRSTACK(2);
        struct chan *c;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
 
        validstat(buf, n);
        c = namec(path, Aaccess, 0, 0);
-       if(waserror()){
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
-       n = devtab[c->type]->wstat(c, buf, n);
+       n = devtab[c->type].wstat(c, buf, n);
        poperror();
        cclose(c);
 
@@ -953,37 +1116,36 @@ syswstat(char *path, uint8_t *buf, int n)
        return n;
 }
 
-enum
-{
+enum {
        DIRSIZE = STATFIXLEN + 32 * 4,
        DIRREADLIM = 2048,      /* should handle the largest reasonable directory entry */
 };
 
-struct dir*
-chandirstat(struct chan *c)
+struct dir *chandirstat(struct chan *c)
 {
-       ERRSTACK(2);
+       ERRSTACK(1);
        struct dir *d;
        uint8_t *buf;
        int n, nd, i;
 
        nd = DIRSIZE;
-       for(i=0; i<2; i++){     /* should work by the second try */
+       for (i = 0; i < 2; i++) {       /* should work by the second try */
                d = kzmalloc(sizeof(struct dir) + nd, 0);
-               buf = ( uint8_t *)&d[1];
-               if(waserror()){
+               buf = (uint8_t *) & d[1];
+               if (waserror()) {
                        kfree(d);
+                       poperror();
                        return NULL;
                }
-               n = devtab[c->type]->stat(c, buf, nd);
+               n = devtab[c->type].stat(c, buf, nd);
                poperror();
-               if(n < BIT16SZ){
+               if (n < BIT16SZ) {
                        kfree(d);
                        return NULL;
                }
-               nd = GBIT16(( uint8_t *)buf) + BIT16SZ; /* size needed to store whole stat buffer including count */
-               if(nd <= n){
-                       convM2D(buf, n, d, ( char *)&d[1]);
+               nd = GBIT16((uint8_t *) buf) + BIT16SZ; /* size needed to store whole stat buffer including count */
+               if (nd <= n) {
+                       convM2D(buf, n, d, (char *)&d[1]);
                        return d;
                }
                /* else sizeof(Dir)+nd is plenty */
@@ -993,18 +1155,19 @@ chandirstat(struct chan *c)
 
 }
 
-struct dir*
-sysdirstat(char *name)
+struct dir *sysdirstat(char *name)
 {
        ERRSTACK(2);
        struct chan *c;
        struct dir *d;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return NULL;
+       }
 
        c = namec(name, Aaccess, 0, 0);
-       if(waserror()){
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
@@ -1016,18 +1179,19 @@ sysdirstat(char *name)
        return d;
 }
 
-struct dir*
-sysdirfstat(int fd)
+struct dir *sysdirfstat(int fd)
 {
        ERRSTACK(2);
        struct chan *c;
        struct dir *d;
 
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return NULL;
+       }
 
        c = fdtochan(current->fgrp, fd, -1, 0, 1);
-       if(waserror()) {
+       if (waserror()) {
                cclose(c);
                nexterror();
        }
@@ -1039,8 +1203,7 @@ sysdirfstat(int fd)
        return d;
 }
 
-int
-sysdirwstat(char *name, struct dir *dir)
+int sysdirwstat(char *name, struct dir *dir)
 {
        uint8_t *buf;
        int r;
@@ -1050,11 +1213,10 @@ sysdirwstat(char *name, struct dir *dir)
        convD2M(dir, buf, r);
        r = syswstat(name, buf, r);
        kfree(buf);
-       return r < 0? r: 0;
+       return r < 0 ? r : 0;
 }
 
-int
-sysdirfwstat(int fd, struct dir *dir)
+int sysdirfwstat(int fd, struct dir *dir)
 {
        uint8_t *buf;
        int r;
@@ -1064,17 +1226,16 @@ sysdirfwstat(int fd, struct dir *dir)
        convD2M(dir, buf, r);
        r = sysfwstat(fd, buf, r);
        kfree(buf);
-       return r < 0? r: 0;
+       return r < 0 ? r : 0;
 }
 
-static long
-dirpackage(uint8_t *buf, long ts, struct dir **d)
+static long dirpackage(uint8_t * buf, long ts, struct kdirent **d)
 {
        char *s;
-       long ss, i, n, nn, m;
+       long ss, i, n, nn, m = 0;
 
        *d = NULL;
-       if(ts <= 0)
+       if (ts <= 0)
                return ts;
 
        /*
@@ -1082,29 +1243,29 @@ dirpackage(uint8_t *buf, long ts, struct dir **d)
         */
        ss = 0;
        n = 0;
-       for(i = 0; i < ts; i += m){
+       for (i = 0; i < ts; i += m) {
                m = BIT16SZ + GBIT16(&buf[i]);
-               if(statcheck(&buf[i], m) < 0)
+               if (statcheck(&buf[i], m) < 0)
                        break;
                ss += m;
                n++;
        }
 
-       if(i != ts)
+       if (i != ts)
                error("bad directory format");
 
-       *d = kzmalloc(n * sizeof(struct dir) + ss, 0);
-       if(*d == NULL)
+       *d = kzmalloc(n * sizeof(**d) + ss, 0);
+       if (*d == NULL)
                error(Enomem);
 
        /*
         * then convert all buffers
         */
-       s = ( char *)*d + n * sizeof(struct dir);
+       s = (char *)*d + n * sizeof(**d);
        nn = 0;
-       for(i = 0; i < ts; i += m){
-               m = BIT16SZ + GBIT16(( uint8_t *)&buf[i]);
-               if(nn >= n || convM2D(&buf[i], m, *d + nn, s) != m){
+       for (i = 0; i < ts; i += m) {
+               m = BIT16SZ + GBIT16((uint8_t *) & buf[i]);
+               if (nn >= n || /*convM2D */ convM2kdirent(&buf[i], m, *d + nn, s) != m) {
                        kfree(*d);
                        *d = NULL;
                        error("bad directory entry");
@@ -1116,25 +1277,26 @@ dirpackage(uint8_t *buf, long ts, struct dir **d)
        return nn;
 }
 
-long
-sysdirread(int fd, struct dir **d)
+long sysdirread(int fd, struct kdirent **d)
 {
        ERRSTACK(2);
        uint8_t *buf;
        long ts;
 
        *d = NULL;
-       if(waserror())
+       if (waserror()) {
+               poperror();
                return -1;
+       }
        buf = kzmalloc(DIRREADLIM, 0);
-       if(buf == NULL)
+       if (buf == NULL)
                error(Enomem);
-       if(waserror()){
+       if (waserror()) {
                kfree(buf);
                nexterror();
        }
        ts = sysread(fd, buf, DIRREADLIM);
-       if(ts >= 0)
+       if (ts >= 0)
                ts = dirpackage(buf, ts, d);
        poperror();
        kfree(buf);
@@ -1142,16 +1304,16 @@ sysdirread(int fd, struct dir **d)
        return ts;
 }
 
-int
-sysiounit(int fd)
+int sysiounit(int fd)
 {
-       ERRSTACK(2);
+       ERRSTACK(1);
        struct chan *c;
        int n;
 
        c = fdtochan(current->fgrp, fd, -1, 0, 1);
-       if(waserror()){
+       if (waserror()) {
                cclose(c);
+               poperror();
                return 0;       /* n.b. */
        }
        n = c->iounit;
@@ -1196,18 +1358,17 @@ void close_9ns_files(struct proc *p, bool only_cloexec)
        }
 }
 
-void print_chaninfo(struct chan *ch)
+void print_chaninfo(struct chan *c)
 {
-       char buf[64] = {0};
-#if 0
-FIXME
+       char buf[64] = { 0 };
+       bool has_dev = c->type != -1;
        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);
-#endif
+                  c->name ? c->name->s : "no cname",
+                  has_dev ? devtab[c->type].name : "no dev",
+                  has_dev ? devtab[c->type].chaninfo(c, buf, sizeof(buf)) : "no info");
+       if (!has_dev)
+               printk("No dev: intermediate chan? qid.path: %p\n", c->qid.path);
+       printk("\n");
 }
 
 void print_9ns_files(struct proc *p)
@@ -1233,7 +1394,7 @@ int plan9setup(struct proc *new_proc, struct proc *parent)
        struct kref *new_dot_ref;
        ERRSTACK(1);
        if (waserror()) {
-               printd("plan9setup failed\n");
+               printk("plan9setup failed, %s\n", current_errstr());
                poperror();
                return -1;
        }
@@ -1244,14 +1405,16 @@ int plan9setup(struct proc *new_proc, struct proc *parent)
                 * 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->fgrp = newfgrp();
                new_proc->pgrp = newpgrp();
                old_current = switch_to(new_proc);
                new_proc->slash = namec("#r", Atodir, 0, 0);
+               if (!new_proc->slash)
+                       panic("no root device");
                switch_back(new_proc, old_current);
                /* Want the name to be "/" instead of "#r" */
-               pathclose(new_proc->slash->path);
-               new_proc->slash->path = newpath("/");
+               cnameclose(new_proc->slash->name);
+               new_proc->slash->name = newcname("/");
                new_proc->dot = cclone(new_proc->slash);
                poperror();
                return 0;
@@ -1278,3 +1441,72 @@ int plan9setup(struct proc *new_proc, struct proc *parent)
        return 0;
 }
 
+/* Open flags, create modes, access types, file flags, and all that...
+ *
+ * there are a bunch of things here:
+ *             1) file creation flags (e.g. O_TRUNC)
+ *             2) file status flags (e.g. O_APPEND)
+ *             3) file open modes (e.g. O_RDWR)
+ *             4) file descriptor flags (e.g. CLOEXEC)
+ *             5) file creation mode (e.g. S_IRWXU)
+ * the 1-4 are passed in via open's vfs_flags, and the 5 via mode only when
+ * O_CREATE is set.
+ *
+ * file creation flags (1) only matter when creating, but aren't permanent.
+ * O_EXCL, O_DIRECTORY, O_TRUNC, etc.
+ *
+ * file status flags (2) are per struct file/chan.  stuff like O_APPEND,
+ * O_ASYNC, etc.  we convert those to an internal flag bit and store in c->flags
+ *
+ * the open mode (3) matters for a given FD/chan (chan->mode), and should be
+ * stored in the chan. (c->mode) stuff like O_RDONLY.
+ *
+ * the file descriptor flags (4) clearly are in the FD.  note that the same
+ * file/chan can be opened by two different FDs, with different flags.  the only
+ * one anyone uses is CLOEXEC.  while exec may not last long in akaros, i can
+ * imagine similar "never pass to children" flags/meanings.
+ *
+ * the file creation mode (5) matters for the device's permissions; given this,
+ * it should be stored in the device/inode.  ACLs fall under this category.
+ *
+ * finally, only certain categories can be edited afterwards: file status flags
+ * (2), FD flags (4), and file permissions (5).        */
+int fd_getfl(int fd)
+{
+       ERRSTACK(1);
+       struct chan *c;
+       int ret;
+
+       if (waserror()) {
+               poperror();
+               return -1;
+       }
+       c = fdtochan(current->fgrp, fd, -1, 0, 1);
+
+       ret = c->mode;
+       if (c->flag & CAPPEND)
+               ret |= O_APPEND;
+
+       cclose(c);
+       poperror();
+       return ret;
+}
+
+int fd_setfl(int fd, int flags)
+{
+       ERRSTACK(1);
+       struct chan *c;
+
+       if (waserror()) {
+               poperror();
+               return -1;
+       }
+       c = fdtochan(current->fgrp, fd, -1, 0, 1);
+
+       if (flags & O_APPEND)
+               c->flag |= CAPPEND;
+
+       cclose(c);
+       poperror();
+       return 0;
+}