/* $OpenBSD: fuse_vnops.c,v 1.61 2020/06/11 09:18:43 mpi Exp $ */ /* * Copyright (c) 2012-2013 Sylvestre Gallon * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fusefs_node.h" #include "fusefs.h" /* Prototypes for fusefs vnode ops */ int fusefs_kqfilter(void *); int fusefs_lookup(void *); int fusefs_open(void *); int fusefs_close(void *); int fusefs_access(void *); int fusefs_getattr(void *); int fusefs_setattr(void *); int fusefs_ioctl(void *); int fusefs_link(void *); int fusefs_symlink(void *); int fusefs_readdir(void *); int fusefs_readlink(void *); int fusefs_inactive(void *); int fusefs_reclaim(void *); int fusefs_print(void *); int fusefs_create(void *); int fusefs_mknod(void *); int fusefs_read(void *); int fusefs_write(void *); int fusefs_poll(void *); int fusefs_remove(void *); int fusefs_rename(void *); int fusefs_mkdir(void *); int fusefs_rmdir(void *); int fusefs_strategy(void *); int fusefs_lock(void *); int fusefs_unlock(void *); int fusefs_islocked(void *); int fusefs_advlock(void *); int fusefs_fsync(void *); /* Prototypes for fusefs kqfilter */ int filt_fusefsread(struct knote *, long); int filt_fusefswrite(struct knote *, long); int filt_fusefsvnode(struct knote *, long); void filt_fusefsdetach(struct knote *); const struct vops fusefs_vops = { .vop_lookup = fusefs_lookup, .vop_create = fusefs_create, .vop_mknod = fusefs_mknod, .vop_open = fusefs_open, .vop_close = fusefs_close, .vop_access = fusefs_access, .vop_getattr = fusefs_getattr, .vop_setattr = fusefs_setattr, .vop_read = fusefs_read, .vop_write = fusefs_write, .vop_ioctl = fusefs_ioctl, .vop_poll = fusefs_poll, .vop_kqfilter = fusefs_kqfilter, .vop_fsync = fusefs_fsync, .vop_remove = fusefs_remove, .vop_link = fusefs_link, .vop_rename = fusefs_rename, .vop_mkdir = fusefs_mkdir, .vop_rmdir = fusefs_rmdir, .vop_symlink = fusefs_symlink, .vop_readdir = fusefs_readdir, .vop_readlink = fusefs_readlink, .vop_abortop = vop_generic_abortop, .vop_inactive = fusefs_inactive, .vop_reclaim = fusefs_reclaim, .vop_lock = fusefs_lock, .vop_unlock = fusefs_unlock, .vop_bmap = vop_generic_bmap, .vop_strategy = fusefs_strategy, .vop_print = fusefs_print, .vop_islocked = fusefs_islocked, .vop_pathconf = spec_pathconf, .vop_advlock = fusefs_advlock, }; const struct filterops fusefsread_filtops = { .f_flags = FILTEROP_ISFD, .f_attach = NULL, .f_detach = filt_fusefsdetach, .f_event = filt_fusefsread, }; const struct filterops fusefswrite_filtops = { .f_flags = FILTEROP_ISFD, .f_attach = NULL, .f_detach = filt_fusefsdetach, .f_event = filt_fusefswrite, }; const struct filterops fusefsvnode_filtops = { .f_flags = FILTEROP_ISFD, .f_attach = NULL, .f_detach = filt_fusefsdetach, .f_event = filt_fusefsvnode, }; int fusefs_kqfilter(void *v) { struct vop_kqfilter_args *ap = v; struct vnode *vp = ap->a_vp; struct knote *kn = ap->a_kn; switch (kn->kn_filter) { case EVFILT_READ: kn->kn_fop = &fusefsread_filtops; break; case EVFILT_WRITE: kn->kn_fop = &fusefswrite_filtops; break; case EVFILT_VNODE: kn->kn_fop = &fusefsvnode_filtops; break; default: return (EINVAL); } kn->kn_hook = (caddr_t)vp; klist_insert(&vp->v_selectinfo.si_note, kn); return (0); } void filt_fusefsdetach(struct knote *kn) { struct vnode *vp = (struct vnode *)kn->kn_hook; klist_remove(&vp->v_selectinfo.si_note, kn); } int filt_fusefsread(struct knote *kn, long hint) { struct vnode *vp = (struct vnode *)kn->kn_hook; struct fusefs_node *ip = VTOI(vp); /* * filesystem is gone, so set the EOF flag and schedule * the knote for deletion */ if (hint == NOTE_REVOKE) { kn->kn_flags |= (EV_EOF | EV_ONESHOT); return (1); } kn->kn_data = ip->filesize - foffset(kn->kn_fp); if (kn->kn_data == 0 && kn->kn_sfflags & NOTE_EOF) { kn->kn_fflags |= NOTE_EOF; return (1); } if (kn->kn_flags & __EV_POLL) return (1); return (kn->kn_data != 0); } int filt_fusefswrite(struct knote *kn, long hint) { /* * filesystem is gone, so set the EOF flag and schedule * the knote for deletion */ if (hint == NOTE_REVOKE) { kn->kn_flags |= (EV_EOF | EV_ONESHOT); return (1); } kn->kn_data = 0; return (1); } int filt_fusefsvnode(struct knote *kn, long int hint) { if (kn->kn_sfflags & hint) kn->kn_fflags |= hint; if (hint == NOTE_REVOKE) { kn->kn_flags |= EV_EOF; return (1); } return (kn->kn_fflags != 0); } /* * FUSE file systems can maintain a file handle for each VFS file descriptor * that is opened. The OpenBSD VFS does not make file descriptors visible to * us so we fake it by mapping open flags to file handles. * There is no way for FUSE to know which file descriptor is being used * by an application for a file operation. We only maintain 3 descriptors, * one each for O_RDONLY, O_WRONLY and O_RDWR. When reading and writing, the * first open descriptor is used and this may well not be the one that was set * by FUSE open and may have even been opened by another application. */ int fusefs_open(void *v) { struct vop_open_args *ap; struct fusefs_node *ip; struct fusefs_mnt *fmp; struct vnode *vp; enum fufh_type fufh_type = FUFH_RDONLY; int flags; int error; int isdir; ap = v; vp = ap->a_vp; ip = VTOI(vp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) return (ENXIO); isdir = 0; if (vp->v_type == VDIR) isdir = 1; else { if ((ap->a_mode & FREAD) && (ap->a_mode & FWRITE)) fufh_type = FUFH_RDWR; else if (ap->a_mode & (FWRITE)) fufh_type = FUFH_WRONLY; /* * Due to possible attribute caching, there is no * reliable way to determine if the file was modified * externally (e.g. network file system) so clear the * UVM cache to ensure that it is not stale. The file * can still become stale later on read but this will * satisfy most situations. */ uvm_vnp_uncache(vp); } /* already open i think all is ok */ if (ip->fufh[fufh_type].fh_type != FUFH_INVALID) return (0); /* * The file has already been created and/or truncated so FUSE dictates * that no creation and truncation flags are passed to open. */ flags = OFLAGS(ap->a_mode) & ~(O_CREAT|O_EXCL|O_TRUNC); error = fusefs_file_open(fmp, ip, fufh_type, flags, isdir, ap->a_p); return (error); } int fusefs_close(void *v) { struct vop_close_args *ap; struct fusefs_node *ip; struct fusefs_mnt *fmp; struct fusebuf *fbuf; enum fufh_type fufh_type = FUFH_RDONLY; int error = 0; ap = v; ip = VTOI(ap->a_vp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) return (0); /* * The file or directory may have been opened more than once so there * is no reliable way to determine when to ask the FUSE daemon to * release its file descriptor. For files, ask the daemon to flush any * buffers to disk now. All open file descriptors will be released on * VOP_INACTIVE(9). */ if (ap->a_vp->v_type == VDIR) return (0); /* Implementing flush is optional so don't error. */ if (fmp->undef_op & UNDEF_FLUSH) return (0); /* Only flush writeable file descriptors. */ if ((ap->a_fflag & FREAD) && (ap->a_fflag & FWRITE)) fufh_type = FUFH_RDWR; else if (ap->a_fflag & (FWRITE)) fufh_type = FUFH_WRONLY; else return (0); if (ip->fufh[fufh_type].fh_type == FUFH_INVALID) return (EBADF); fbuf = fb_setup(0, ip->ufs_ino.i_number, FBT_FLUSH, ap->a_p); fbuf->fb_io_fd = ip->fufh[fufh_type].fh_id; error = fb_queue(fmp->dev, fbuf); fb_delete(fbuf); if (error == ENOSYS) { fmp->undef_op |= UNDEF_FLUSH; /* Implementing flush is optional so don't error. */ return (0); } return (error); } int fusefs_access(void *v) { struct vop_access_args *ap; struct fusefs_node *ip; struct fusefs_mnt *fmp; struct ucred *cred; struct vattr vattr; struct proc *p; int error = 0; ap = v; p = ap->a_p; cred = p->p_ucred; ip = VTOI(ap->a_vp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; /* * Only user that mounted the file system can access it unless * allow_other mount option was specified. */ if (!fmp->allow_other && cred->cr_uid != fmp->mp->mnt_stat.f_owner) return (EACCES); if (!fmp->sess_init) return (ENXIO); /* * Disallow write attempts on filesystems mounted read-only; * unless the file is a socket, fifo, or a block or character * device resident on the filesystem. */ if ((ap->a_mode & VWRITE) && (fmp->mp->mnt_flag & MNT_RDONLY)) { switch (ap->a_vp->v_type) { case VREG: case VDIR: case VLNK: return (EROFS); default: break; } } if ((error = VOP_GETATTR(ap->a_vp, &vattr, cred, p)) != 0) return (error); return (vaccess(ap->a_vp->v_type, vattr.va_mode & ALLPERMS, vattr.va_uid, vattr.va_gid, ap->a_mode, ap->a_cred)); } int fusefs_getattr(void *v) { struct vop_getattr_args *ap = v; struct vnode *vp = ap->a_vp; struct fusefs_mnt *fmp; struct vattr *vap = ap->a_vap; struct proc *p = ap->a_p; struct ucred *cred = p->p_ucred; struct fusefs_node *ip; struct fusebuf *fbuf; struct stat *st; int error = 0; ip = VTOI(vp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; /* * Only user that mounted the file system can access it unless * allow_other mount option was specified. Return dummy values * for the root inode in this situation. */ if (!fmp->allow_other && cred->cr_uid != fmp->mp->mnt_stat.f_owner) { memset(vap, 0, sizeof(*vap)); vap->va_type = VNON; if (vp->v_mount->mnt_flag & MNT_RDONLY) vap->va_mode = S_IRUSR | S_IXUSR; else vap->va_mode = S_IRWXU; vap->va_nlink = 1; vap->va_uid = fmp->mp->mnt_stat.f_owner; vap->va_gid = fmp->mp->mnt_stat.f_owner; vap->va_fsid = fmp->mp->mnt_stat.f_fsid.val[0]; vap->va_fileid = ip->ufs_ino.i_number; vap->va_size = S_BLKSIZE; vap->va_blocksize = S_BLKSIZE; vap->va_atime.tv_sec = fmp->mp->mnt_stat.f_ctime; vap->va_mtime.tv_sec = fmp->mp->mnt_stat.f_ctime; vap->va_ctime.tv_sec = fmp->mp->mnt_stat.f_ctime; vap->va_rdev = fmp->dev; vap->va_bytes = S_BLKSIZE; return (0); } if (!fmp->sess_init) return (ENXIO); fbuf = fb_setup(0, ip->ufs_ino.i_number, FBT_GETATTR, p); error = fb_queue(fmp->dev, fbuf); if (error) { fb_delete(fbuf); return (error); } st = &fbuf->fb_attr; memset(vap, 0, sizeof(*vap)); vap->va_type = IFTOVT(st->st_mode); vap->va_mode = st->st_mode & ~S_IFMT; vap->va_nlink = st->st_nlink; vap->va_uid = st->st_uid; vap->va_gid = st->st_gid; vap->va_fsid = fmp->mp->mnt_stat.f_fsid.val[0]; vap->va_fileid = st->st_ino; vap->va_size = st->st_size; vap->va_blocksize = st->st_blksize; vap->va_atime = st->st_atim; vap->va_mtime = st->st_mtim; vap->va_ctime = st->st_ctim; vap->va_rdev = st->st_rdev; vap->va_bytes = st->st_blocks * S_BLKSIZE; fb_delete(fbuf); return (error); } int fusefs_setattr(void *v) { struct vop_setattr_args *ap = v; struct vattr *vap = ap->a_vap; struct vnode *vp = ap->a_vp; struct fusefs_node *ip = VTOI(vp); struct ucred *cred = ap->a_cred; struct proc *p = ap->a_p; struct fusefs_mnt *fmp; struct fusebuf *fbuf; struct fb_io *io; int error = 0; fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; /* * Check for unsettable attributes. */ if ((vap->va_type != VNON) || (vap->va_nlink != VNOVAL) || (vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) || (vap->va_blocksize != VNOVAL) || (vap->va_rdev != VNOVAL) || ((int)vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL)) return (EINVAL); if (!fmp->sess_init) return (ENXIO); if (fmp->undef_op & UNDEF_SETATTR) return (ENOSYS); fbuf = fb_setup(sizeof(*io), ip->ufs_ino.i_number, FBT_SETATTR, p); io = fbtod(fbuf, struct fb_io *); io->fi_flags = 0; if (vap->va_uid != (uid_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) { error = EROFS; goto out; } fbuf->fb_attr.st_uid = vap->va_uid; io->fi_flags |= FUSE_FATTR_UID; } if (vap->va_gid != (gid_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) { error = EROFS; goto out; } fbuf->fb_attr.st_gid = vap->va_gid; io->fi_flags |= FUSE_FATTR_GID; } if (vap->va_size != VNOVAL) { /* * Disallow write attempts on read-only file systems; * unless the file is a socket, fifo, or a block or * character device resident on the file system. */ switch (vp->v_type) { case VDIR: error = EISDIR; goto out; case VLNK: case VREG: if (vp->v_mount->mnt_flag & MNT_RDONLY) { error = EROFS; goto out; } break; default: break; } fbuf->fb_attr.st_size = vap->va_size; io->fi_flags |= FUSE_FATTR_SIZE; } if (vap->va_atime.tv_nsec != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) { error = EROFS; goto out; } fbuf->fb_attr.st_atim = vap->va_atime; io->fi_flags |= FUSE_FATTR_ATIME; } if (vap->va_mtime.tv_nsec != VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) { error = EROFS; goto out; } fbuf->fb_attr.st_mtim = vap->va_mtime; io->fi_flags |= FUSE_FATTR_MTIME; } /* XXX should set a flag if (vap->va_vaflags & VA_UTIMES_CHANGE) */ if (vap->va_mode != (mode_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) { error = EROFS; goto out; } /* * chmod returns EFTYPE if the effective user ID is not the * super-user, the mode includes the sticky bit (S_ISVTX), and * path does not refer to a directory */ if (cred->cr_uid != 0 && vp->v_type != VDIR && (vap->va_mode & S_ISTXT)) { error = EFTYPE; goto out; } fbuf->fb_attr.st_mode = vap->va_mode & ALLPERMS; io->fi_flags |= FUSE_FATTR_MODE; } if (!io->fi_flags) { goto out; } error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) fmp->undef_op |= UNDEF_SETATTR; goto out; } /* truncate was successful, let uvm know */ if (vap->va_size != VNOVAL && vap->va_size != ip->filesize) { ip->filesize = vap->va_size; uvm_vnp_setsize(vp, vap->va_size); } VN_KNOTE(ap->a_vp, NOTE_ATTRIB); out: fb_delete(fbuf); return (error); } int fusefs_ioctl(void *v) { return (ENOTTY); } int fusefs_link(void *v) { struct vop_link_args *ap = v; struct vnode *dvp = ap->a_dvp; struct vnode *vp = ap->a_vp; struct componentname *cnp = ap->a_cnp; struct proc *p = cnp->cn_proc; struct fusefs_mnt *fmp; struct fusefs_node *ip; struct fusefs_node *dip; struct fusebuf *fbuf; int error = 0; ip = VTOI(vp); dip = VTOI(dvp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) { VOP_ABORTOP(dvp, cnp); error = ENXIO; goto out2; } if (fmp->undef_op & UNDEF_LINK) { VOP_ABORTOP(dvp, cnp); error = ENOSYS; goto out2; } if (vp->v_type == VDIR) { VOP_ABORTOP(dvp, cnp); error = EPERM; goto out2; } if (dvp->v_mount != vp->v_mount) { VOP_ABORTOP(dvp, cnp); error = EXDEV; goto out2; } if (dvp != vp && (error = vn_lock(vp, LK_EXCLUSIVE))) { VOP_ABORTOP(dvp, cnp); goto out2; } fbuf = fb_setup(cnp->cn_namelen + 1, dip->ufs_ino.i_number, FBT_LINK, p); fbuf->fb_io_ino = ip->ufs_ino.i_number; memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) fmp->undef_op |= UNDEF_LINK; fb_delete(fbuf); goto out1; } fb_delete(fbuf); VN_KNOTE(vp, NOTE_LINK); VN_KNOTE(dvp, NOTE_WRITE); out1: if (dvp != vp) VOP_UNLOCK(vp); out2: vput(dvp); return (error); } int fusefs_symlink(void *v) { struct vop_symlink_args *ap = v; struct vnode **vpp = ap->a_vpp; struct componentname *cnp = ap->a_cnp; struct vnode *dvp = ap->a_dvp; struct proc *p = cnp->cn_proc; char *target = ap->a_target; struct fusefs_node *dp; struct fusefs_mnt *fmp; struct fusebuf *fbuf; struct vnode *tdp; int error = 0; int len; dp = VTOI(dvp); fmp = (struct fusefs_mnt *)dp->ufs_ino.i_ump; if (!fmp->sess_init) { error = ENXIO; goto bad; } if (fmp->undef_op & UNDEF_SYMLINK) { error = ENOSYS; goto bad; } len = strlen(target) + 1; fbuf = fb_setup(len + cnp->cn_namelen + 1, dp->ufs_ino.i_number, FBT_SYMLINK, p); memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; memcpy(&fbuf->fb_dat[cnp->cn_namelen + 1], target, len); error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) fmp->undef_op |= UNDEF_SYMLINK; fb_delete(fbuf); goto bad; } if ((error = VFS_VGET(fmp->mp, fbuf->fb_ino, &tdp))) { fb_delete(fbuf); goto bad; } tdp->v_type = VLNK; VN_KNOTE(ap->a_dvp, NOTE_WRITE); *vpp = tdp; fb_delete(fbuf); vput(tdp); bad: vput(dvp); return (error); } int fusefs_readdir(void *v) { struct vop_readdir_args *ap = v; struct fusefs_node *ip; struct fusefs_mnt *fmp; struct fusebuf *fbuf; struct vnode *vp; struct proc *p; struct uio *uio; int error = 0, eofflag = 0, diropen = 0; vp = ap->a_vp; uio = ap->a_uio; p = uio->uio_procp; ip = VTOI(vp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) return (ENXIO); if (uio->uio_resid < sizeof(struct dirent)) return (EINVAL); if (ip->fufh[FUFH_RDONLY].fh_type == FUFH_INVALID) { error = fusefs_file_open(fmp, ip, FUFH_RDONLY, O_RDONLY, 1, p); if (error) return (error); diropen = 1; } while (uio->uio_resid > 0) { fbuf = fb_setup(0, ip->ufs_ino.i_number, FBT_READDIR, p); fbuf->fb_io_fd = ip->fufh[FUFH_RDONLY].fh_id; fbuf->fb_io_off = uio->uio_offset; fbuf->fb_io_len = MIN(uio->uio_resid, fmp->max_read); error = fb_queue(fmp->dev, fbuf); if (error) { /* * dirent was larger than residual space left in * buffer. */ if (error == ENOBUFS) error = 0; fb_delete(fbuf); break; } /* ack end of readdir */ if (fbuf->fb_len == 0) { eofflag = 1; fb_delete(fbuf); break; } if ((error = uiomove(fbuf->fb_dat, fbuf->fb_len, uio))) { fb_delete(fbuf); break; } fb_delete(fbuf); } if (!error && ap->a_eofflag != NULL) *ap->a_eofflag = eofflag; if (diropen) fusefs_file_close(fmp, ip, FUFH_RDONLY, O_RDONLY, 1, p); return (error); } int fusefs_inactive(void *v) { struct vop_inactive_args *ap = v; struct vnode *vp = ap->a_vp; struct proc *p = ap->a_p; struct fusefs_node *ip = VTOI(vp); struct fusefs_filehandle *fufh = NULL; struct fusefs_mnt *fmp; int type, flags; fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; /* Close all open file handles. */ for (type = 0; type < FUFH_MAXTYPE; type++) { fufh = &(ip->fufh[type]); if (fufh->fh_type != FUFH_INVALID) { /* * FUSE file systems expect the same flags to be sent * on release that were sent on open. We don't have a * record of them so make a best guess. */ switch (type) { case FUFH_RDONLY: flags = O_RDONLY; break; case FUFH_WRONLY: flags = O_WRONLY; break; default: flags = O_RDWR; } fusefs_file_close(fmp, ip, fufh->fh_type, flags, (vp->v_type == VDIR), p); } } VOP_UNLOCK(vp); /* Don't return error to prevent kernel panic in vclean(9). */ return (0); } int fusefs_readlink(void *v) { struct vop_readlink_args *ap = v; struct vnode *vp = ap->a_vp; struct fusefs_node *ip; struct fusefs_mnt *fmp; struct fusebuf *fbuf; struct uio *uio; struct proc *p; int error = 0; ip = VTOI(vp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; uio = ap->a_uio; p = uio->uio_procp; if (!fmp->sess_init) return (ENXIO); if (fmp->undef_op & UNDEF_READLINK) return (ENOSYS); fbuf = fb_setup(0, ip->ufs_ino.i_number, FBT_READLINK, p); error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) fmp->undef_op |= UNDEF_READLINK; fb_delete(fbuf); return (error); } error = uiomove(fbuf->fb_dat, fbuf->fb_len, uio); fb_delete(fbuf); return (error); } int fusefs_reclaim(void *v) { struct vop_reclaim_args *ap = v; struct vnode *vp = ap->a_vp; struct proc *p = ap->a_p; struct fusefs_node *ip = VTOI(vp); struct fusefs_filehandle *fufh = NULL; struct fusefs_mnt *fmp; struct fusebuf *fbuf; int type, error = 0; fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; /* Close opened files. */ for (type = 0; type < FUFH_MAXTYPE; type++) { fufh = &(ip->fufh[type]); if (fufh->fh_type != FUFH_INVALID) { printf("fusefs: vnode being reclaimed is valid\n"); fusefs_file_close(fmp, ip, fufh->fh_type, type, (vp->v_type == VDIR), ap->a_p); } } /* * If the fuse connection is opened ask libfuse to free the vnodes. */ if (fmp->sess_init && ip->ufs_ino.i_number != FUSE_ROOTINO) { fbuf = fb_setup(0, ip->ufs_ino.i_number, FBT_RECLAIM, p); error = fb_queue(fmp->dev, fbuf); if (error) printf("fusefs: vnode reclaim failed: %d\n", error); fb_delete(fbuf); } /* * Remove the inode from its hash chain. */ ufs_ihashrem(&ip->ufs_ino); free(ip, M_FUSEFS, sizeof(*ip)); vp->v_data = NULL; /* Must return success otherwise kernel panic in vclean(9). */ return (0); } int fusefs_print(void *v) { struct vop_print_args *ap = v; struct vnode *vp = ap->a_vp; struct fusefs_node *ip = VTOI(vp); /* Complete the information given by vprint(). */ printf("tag VT_FUSE, hash id %u ", ip->ufs_ino.i_number); printf("\n"); return (0); } int fusefs_create(void *v) { struct vop_create_args *ap = v; struct componentname *cnp = ap->a_cnp; struct vnode **vpp = ap->a_vpp; struct vnode *dvp = ap->a_dvp; struct vattr *vap = ap->a_vap; struct proc *p = cnp->cn_proc; struct vnode *tdp = NULL; struct fusefs_mnt *fmp; struct fusefs_node *ip; struct fusebuf *fbuf; int error = 0; mode_t mode; ip = VTOI(dvp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; mode = MAKEIMODE(vap->va_type, vap->va_mode); if (!fmp->sess_init) return (ENXIO); if (fmp->undef_op & UNDEF_MKNOD) return (ENOSYS); fbuf = fb_setup(cnp->cn_namelen + 1, ip->ufs_ino.i_number, FBT_MKNOD, p); fbuf->fb_io_mode = mode; memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) fmp->undef_op |= UNDEF_MKNOD; goto out; } if ((error = VFS_VGET(fmp->mp, fbuf->fb_ino, &tdp))) goto out; tdp->v_type = IFTOVT(fbuf->fb_io_mode); *vpp = tdp; VN_KNOTE(ap->a_dvp, NOTE_WRITE); out: fb_delete(fbuf); return (error); } int fusefs_mknod(void *v) { struct vop_mknod_args *ap = v; struct componentname *cnp = ap->a_cnp; struct vnode **vpp = ap->a_vpp; struct vnode *dvp = ap->a_dvp; struct vattr *vap = ap->a_vap; struct proc *p = cnp->cn_proc; struct vnode *tdp = NULL; struct fusefs_mnt *fmp; struct fusefs_node *ip; struct fusebuf *fbuf; int error = 0; ip = VTOI(dvp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) return (ENXIO); if (fmp->undef_op & UNDEF_MKNOD) return (ENOSYS); fbuf = fb_setup(cnp->cn_namelen + 1, ip->ufs_ino.i_number, FBT_MKNOD, p); fbuf->fb_io_mode = MAKEIMODE(vap->va_type, vap->va_mode); if (vap->va_rdev != VNOVAL) fbuf->fb_io_rdev = vap->va_rdev; memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) fmp->undef_op |= UNDEF_MKNOD; goto out; } if ((error = VFS_VGET(fmp->mp, fbuf->fb_ino, &tdp))) goto out; tdp->v_type = IFTOVT(fbuf->fb_io_mode); *vpp = tdp; VN_KNOTE(ap->a_dvp, NOTE_WRITE); /* Remove inode so that it will be reloaded by VFS_VGET and * checked to see if it is an alias of an existing entry in * the inode cache. */ vput(*vpp); (*vpp)->v_type = VNON; vgone(*vpp); *vpp = NULL; out: fb_delete(fbuf); return (error); } int fusefs_read(void *v) { struct vop_read_args *ap = v; struct vnode *vp = ap->a_vp; struct uio *uio = ap->a_uio; struct proc *p = uio->uio_procp; struct fusefs_node *ip; struct fusefs_mnt *fmp; struct fusebuf *fbuf = NULL; size_t size; int error=0; ip = VTOI(vp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) return (ENXIO); if (uio->uio_resid == 0) return (error); if (uio->uio_offset < 0) return (EINVAL); while (uio->uio_resid > 0) { fbuf = fb_setup(0, ip->ufs_ino.i_number, FBT_READ, p); size = MIN(uio->uio_resid, fmp->max_read); fbuf->fb_io_fd = fusefs_fd_get(ip, FUFH_RDONLY); fbuf->fb_io_off = uio->uio_offset; fbuf->fb_io_len = size; error = fb_queue(fmp->dev, fbuf); if (error) break; error = uiomove(fbuf->fb_dat, ulmin(size, fbuf->fb_len), uio); if (error) break; if (fbuf->fb_len < size) break; fb_delete(fbuf); fbuf = NULL; } fb_delete(fbuf); return (error); } int fusefs_write(void *v) { struct vop_write_args *ap = v; struct vnode *vp = ap->a_vp; struct uio *uio = ap->a_uio; struct proc *p = uio->uio_procp; struct ucred *cred = p->p_ucred; struct vattr vattr; int ioflag = ap->a_ioflag; struct fusefs_node *ip; struct fusefs_mnt *fmp; struct fusebuf *fbuf = NULL; size_t len, diff; int error=0; ip = VTOI(vp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) return (ENXIO); if (uio->uio_resid == 0) return (error); if (ioflag & IO_APPEND) { if ((error = VOP_GETATTR(vp, &vattr, cred, p)) != 0) return (error); uio->uio_offset = vattr.va_size; } while (uio->uio_resid > 0) { len = MIN(uio->uio_resid, fmp->max_read); fbuf = fb_setup(len, ip->ufs_ino.i_number, FBT_WRITE, p); fbuf->fb_io_fd = fusefs_fd_get(ip, FUFH_WRONLY); fbuf->fb_io_off = uio->uio_offset; fbuf->fb_io_len = len; if ((error = uiomove(fbuf->fb_dat, len, uio))) { printf("fusefs: uio error %i\n", error); break; } error = fb_queue(fmp->dev, fbuf); if (error) break; diff = len - fbuf->fb_io_len; if (fbuf->fb_io_len > len) { error = EINVAL; break; } uio->uio_resid += diff; uio->uio_offset -= diff; if (uio->uio_offset > ip->filesize) { ip->filesize = uio->uio_offset; uvm_vnp_setsize(vp, uio->uio_offset); } uvm_vnp_uncache(vp); fb_delete(fbuf); fbuf = NULL; } fb_delete(fbuf); return (error); } int fusefs_poll(void *v) { struct vop_poll_args *ap = v; /* * We should really check to see if I/O is possible. */ return (ap->a_events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)); } int fusefs_rename(void *v) { struct vop_rename_args *ap = v; struct vnode *tvp = ap->a_tvp; struct vnode *tdvp = ap->a_tdvp; struct vnode *fvp = ap->a_fvp; struct vnode *fdvp = ap->a_fdvp; struct componentname *tcnp = ap->a_tcnp; struct componentname *fcnp = ap->a_fcnp; struct proc *p = fcnp->cn_proc; struct fusefs_node *ip, *dp; struct fusefs_mnt *fmp; struct fusebuf *fbuf; int error = 0; #ifdef DIAGNOSTIC if ((tcnp->cn_flags & HASBUF) == 0 || (fcnp->cn_flags & HASBUF) == 0) panic("fusefs_rename: no name"); #endif /* * Check for cross-device rename. */ if ((fvp->v_mount != tdvp->v_mount) || (tvp && (fvp->v_mount != tvp->v_mount))) { error = EXDEV; abortit: VOP_ABORTOP(tdvp, tcnp); /* XXX, why not in NFS? */ if (tdvp == tvp) vrele(tdvp); else vput(tdvp); if (tvp) vput(tvp); VOP_ABORTOP(fdvp, fcnp); /* XXX, why not in NFS? */ vrele(fdvp); vrele(fvp); return (error); } /* * If source and dest are the same, do nothing. */ if (tvp == fvp) { error = 0; goto abortit; } if ((error = vn_lock(fvp, LK_EXCLUSIVE | LK_RETRY)) != 0) goto abortit; dp = VTOI(fdvp); ip = VTOI(fvp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; /* * Be sure we are not renaming ".", "..", or an alias of ".". This * leads to a crippled directory tree. It's pretty tough to do a * "ls" or "pwd" with the "." directory entry missing, and "cd .." * doesn't work if the ".." entry is missing. */ if (fvp->v_type == VDIR) { /* * Avoid ".", "..", and aliases of "." for obvious reasons. */ if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') || dp == ip || (fcnp->cn_flags & ISDOTDOT) || (tcnp->cn_flags & ISDOTDOT)) { VOP_UNLOCK(fvp); error = EINVAL; goto abortit; } } VN_KNOTE(fdvp, NOTE_WRITE); /* XXX right place? */ if (!fmp->sess_init) { error = ENXIO; VOP_UNLOCK(fvp); goto abortit; } if (fmp->undef_op & UNDEF_RENAME) { error = ENOSYS; VOP_UNLOCK(fvp); goto abortit; } fbuf = fb_setup(fcnp->cn_namelen + tcnp->cn_namelen + 2, dp->ufs_ino.i_number, FBT_RENAME, p); memcpy(fbuf->fb_dat, fcnp->cn_nameptr, fcnp->cn_namelen); fbuf->fb_dat[fcnp->cn_namelen] = '\0'; memcpy(fbuf->fb_dat + fcnp->cn_namelen + 1, tcnp->cn_nameptr, tcnp->cn_namelen); fbuf->fb_dat[fcnp->cn_namelen + tcnp->cn_namelen + 1] = '\0'; fbuf->fb_io_ino = VTOI(tdvp)->ufs_ino.i_number; error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) { fmp->undef_op |= UNDEF_RENAME; } fb_delete(fbuf); VOP_UNLOCK(fvp); goto abortit; } fb_delete(fbuf); VN_KNOTE(fvp, NOTE_RENAME); VOP_UNLOCK(fvp); if (tdvp == tvp) vrele(tdvp); else vput(tdvp); if (tvp) vput(tvp); vrele(fdvp); vrele(fvp); return (error); } int fusefs_mkdir(void *v) { struct vop_mkdir_args *ap = v; struct vnode *dvp = ap->a_dvp; struct vnode **vpp = ap->a_vpp; struct componentname *cnp = ap->a_cnp; struct vattr *vap = ap->a_vap; struct proc *p = cnp->cn_proc; struct vnode *tdp = NULL; struct fusefs_node *ip; struct fusefs_mnt *fmp; struct fusebuf *fbuf; int error = 0; ip = VTOI(dvp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) { error = ENXIO; goto out; } if (fmp->undef_op & UNDEF_MKDIR) { error = ENOSYS; goto out; } fbuf = fb_setup(cnp->cn_namelen + 1, ip->ufs_ino.i_number, FBT_MKDIR, p); fbuf->fb_io_mode = MAKEIMODE(vap->va_type, vap->va_mode); memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) fmp->undef_op |= UNDEF_MKDIR; fb_delete(fbuf); goto out; } if ((error = VFS_VGET(fmp->mp, fbuf->fb_ino, &tdp))) { fb_delete(fbuf); goto out; } tdp->v_type = IFTOVT(fbuf->fb_io_mode); *vpp = tdp; VN_KNOTE(ap->a_dvp, NOTE_WRITE | NOTE_LINK); fb_delete(fbuf); out: vput(dvp); return (error); } int fusefs_rmdir(void *v) { struct vop_rmdir_args *ap = v; struct vnode *vp = ap->a_vp; struct vnode *dvp = ap->a_dvp; struct componentname *cnp = ap->a_cnp; struct proc *p = cnp->cn_proc; struct fusefs_node *ip, *dp; struct fusefs_mnt *fmp; struct fusebuf *fbuf; int error; ip = VTOI(vp); dp = VTOI(dvp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) { error = ENXIO; goto out; } if (fmp->undef_op & UNDEF_RMDIR) { error = ENOSYS; goto out; } VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK); fbuf = fb_setup(cnp->cn_namelen + 1, dp->ufs_ino.i_number, FBT_RMDIR, p); memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) fmp->undef_op |= UNDEF_RMDIR; if (error != ENOTEMPTY) VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK); fb_delete(fbuf); goto out; } vput(dvp); dvp = NULL; fb_delete(fbuf); out: if (dvp) vput(dvp); VN_KNOTE(vp, NOTE_DELETE); vput(vp); return (error); } int fusefs_remove(void *v) { struct vop_remove_args *ap = v; struct vnode *vp = ap->a_vp; struct vnode *dvp = ap->a_dvp; struct componentname *cnp = ap->a_cnp; struct proc *p = cnp->cn_proc; struct fusefs_node *ip; struct fusefs_node *dp; struct fusefs_mnt *fmp; struct fusebuf *fbuf; int error = 0; ip = VTOI(vp); dp = VTOI(dvp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) { error = ENXIO; goto out; } if (fmp->undef_op & UNDEF_REMOVE) { error = ENOSYS; goto out; } fbuf = fb_setup(cnp->cn_namelen + 1, dp->ufs_ino.i_number, FBT_UNLINK, p); memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; error = fb_queue(fmp->dev, fbuf); if (error) { if (error == ENOSYS) fmp->undef_op |= UNDEF_REMOVE; fb_delete(fbuf); goto out; } VN_KNOTE(vp, NOTE_DELETE); VN_KNOTE(dvp, NOTE_WRITE); fb_delete(fbuf); out: if (dvp == vp) vrele(vp); else vput(vp); vput(dvp); return (error); } int fusefs_strategy(void *v) { return (0); } int fusefs_lock(void *v) { struct vop_lock_args *ap = v; struct vnode *vp = ap->a_vp; return rrw_enter(&VTOI(vp)->ufs_ino.i_lock, ap->a_flags & LK_RWFLAGS); } int fusefs_unlock(void *v) { struct vop_unlock_args *ap = v; struct vnode *vp = ap->a_vp; rrw_exit(&VTOI(vp)->ufs_ino.i_lock); return 0; } int fusefs_islocked(void *v) { struct vop_islocked_args *ap = v; return rrw_status(&VTOI(ap->a_vp)->ufs_ino.i_lock); } int fusefs_advlock(void *v) { struct vop_advlock_args *ap = v; struct fusefs_node *ip = VTOI(ap->a_vp); return (lf_advlock(&ip->ufs_ino.i_lockf, ip->filesize, ap->a_id, ap->a_op, ap->a_fl, ap->a_flags)); } int fusefs_fsync(void *v) { struct vop_fsync_args *ap = v; struct vnode *vp = ap->a_vp; struct proc *p = ap->a_p; struct fusefs_node *ip; struct fusefs_mnt *fmp; struct fusefs_filehandle *fufh; struct fusebuf *fbuf; int type, error = 0; /* * Can't write to directory file handles so no need to fsync. * FUSE has fsyncdir but it doesn't make sense on OpenBSD. */ if (vp->v_type == VDIR) return (0); ip = VTOI(vp); fmp = (struct fusefs_mnt *)ip->ufs_ino.i_ump; if (!fmp->sess_init) return (ENXIO); /* Implementing fsync is optional so don't error. */ if (fmp->undef_op & UNDEF_FSYNC) return (0); /* Sync all writeable file descriptors. */ for (type = 0; type < FUFH_MAXTYPE; type++) { fufh = &(ip->fufh[type]); if (fufh->fh_type == FUFH_WRONLY || fufh->fh_type == FUFH_RDWR) { fbuf = fb_setup(0, ip->ufs_ino.i_number, FBT_FSYNC, p); fbuf->fb_io_fd = fufh->fh_id; /* Always behave as if ap->a_waitfor = MNT_WAIT. */ error = fb_queue(fmp->dev, fbuf); fb_delete(fbuf); if (error) break; } } if (error == ENOSYS) { fmp->undef_op |= UNDEF_FSYNC; /* Implementing fsync is optional so don't error. */ return (0); } return (error); }