struct chan *fdtochan(struct fgrp *f, int fd, int mode, int chkmnt, int iref) { struct chan *c; c = 0; spin_lock(&f->lock); 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); set_errno(EBADF); error("Bad FD %d\n", fd); } if (iref) chan_incref(c); spin_unlock(&f->lock); if (chkmnt && (c->flag & CMSG)) { if (iref) cclose(c); error(Ebadusefd); } if (mode < 0 || c->mode == ORDWR) { return c; } if ((mode & OTRUNC) && IS_RDONLY(c->mode)) { if (iref) cclose(c); error(Ebadusefd); } /* 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); } return c; }
/* Adds a tap with the file/qid of the underlying device for the requested FD. * The FD must be a chan, and the device must support the filter requested. * * Returns -1 or some other device-specific non-zero number on failure, 0 on * success. */ int add_fd_tap(struct proc *p, struct fd_tap_req *tap_req) { struct fd_table *fdt = &p->open_files; struct fd_tap *tap; int ret = 0; struct chan *chan; int fd = tap_req->fd; if (fd < 0) { set_errno(EBADF); return -1; } tap = kzmalloc(sizeof(struct fd_tap), MEM_WAIT); tap->proc = p; tap->fd = fd; tap->filter = tap_req->filter; tap->ev_q = tap_req->ev_q; tap->ev_id = tap_req->ev_id; tap->data = tap_req->data; spin_lock(&fdt->lock); if (fd >= fdt->max_fdset) { set_errno(ENFILE); goto out_with_lock; } if (!GET_BITMASK_BIT(fdt->open_fds->fds_bits, fd)) { set_errno(EBADF); goto out_with_lock; } if (!fdt->fd[fd].fd_chan) { set_error(EINVAL, "Can't tap a VFS file"); goto out_with_lock; } chan = fdt->fd[fd].fd_chan; if (fdt->fd[fd].fd_tap) { set_error(EBUSY, "FD %d already has a tap", fd); goto out_with_lock; } if (!devtab[chan->type].tapfd) { set_error(ENOSYS, "Device %s does not handle taps", devtab[chan->type].name); goto out_with_lock; } /* need to keep chan alive for our call to the device. someone else * could come in and close the FD and the chan, once we unlock */ chan_incref(chan); tap->chan = chan; /* One for the FD table, one for us to keep the removal of *this* tap from * happening until we've attempted to register with the device. */ kref_init(&tap->kref, tap_full_release, 2); fdt->fd[fd].fd_tap = tap; /* As soon as we unlock, another thread can come in and remove our old tap * from the table and decref it. Our ref keeps us from removing it yet, * as well as keeps the memory safe. However, a new tap can be installed * and registered with the device before we even attempt to register. The * devices should be able to handle multiple, distinct taps, even if they * happen to have the same {proc, fd} tuple. */ spin_unlock(&fdt->lock); /* For refcnting fans, the tap ref is weak/uncounted. We'll protect the * memory and call the device when tap is being released. */ ret = devtab[chan->type].tapfd(chan, tap, FDTAP_CMD_ADD); if (ret) { /* we failed, so we need to make sure *our* tap is removed. We haven't * decreffed, so we know our tap pointer is unique. */ spin_lock(&fdt->lock); if (fdt->fd[fd].fd_tap == tap) { fdt->fd[fd].fd_tap = 0; /* normally we can't decref a tap while holding a lock, but we * know we have another reference so this won't trigger a release */ kref_put(&tap->kref); } spin_unlock(&fdt->lock); /* Regardless of whether someone else removed it or not, *we* are the * only ones that know that registration failed and that we shouldn't * remove it. Since we still hold a ref, we can change the release * method to skip the device dereg. */ tap->kref.release = tap_min_release; } kref_put(&tap->kref); return ret; out_with_lock: spin_unlock(&fdt->lock); kfree(tap); return -1; }