#ifndef lint static char sccsid[] = "@(#)pc_vnodeops.c 1.1 94/10/31 SMI"; #endif /* * Copyright (c) 1989 by Sun Microsystems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int pcfs_open(); static int pcfs_close(); static int pcfs_rdwr(); static int pcfs_getattr(); static int pcfs_setattr(); static int pcfs_access(); static int pcfs_lookup(); static int pcfs_create(); static int pcfs_remove(); static int pcfs_rename(); static int pcfs_mkdir(); static int pcfs_rmdir(); static int pcfs_readdir(); static int pcfs_fsync(); static int pcfs_inactive(); static int pcfs_getpage(); static int pcfs_putpage(); static int pcfs_map(); static int pcfs_cmp(); static int pcfs_realvp(); #ifdef notdef static int pcfs_fid(); #endif notdef static int pcfs_cntl(); extern int pcfs_invalop(); /* * vnode op vectors for files, directories, and invalid files. */ struct vnodeops pcfs_fvnodeops = { pcfs_open, pcfs_close, pcfs_rdwr, pcfs_invalop, pcfs_invalop, pcfs_getattr, pcfs_setattr, pcfs_access, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_fsync, pcfs_inactive, pcfs_invalop, pcfs_invalop, /* VOP_FID */ pcfs_getpage, pcfs_putpage, pcfs_map, pcfs_invalop, pcfs_cmp, pcfs_realvp, pcfs_cntl, }; struct vnodeops pcfs_dvnodeops = { pcfs_open, pcfs_close, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_getattr, pcfs_invalop, /* can't we support this op? one problem is to */ pcfs_access, /* make attr of '.', '..' and the real dir the same.*/ pcfs_lookup, pcfs_create, pcfs_remove, pcfs_invalop, pcfs_rename, pcfs_mkdir, pcfs_rmdir, pcfs_readdir, pcfs_invalop, pcfs_invalop, pcfs_invalop, pcfs_inactive, pcfs_invalop, pcfs_invalop, /* VOP_FID */ pcfs_getpage, pcfs_putpage, pcfs_invalop, pcfs_invalop, pcfs_cmp, pcfs_realvp, pcfs_cntl, }; /*ARGSUSED*/ static int pcfs_open(vpp, flag, cred) struct vnode **vpp; int flag; struct ucred *cred; { return (0); } /* * files are sync'ed on close to keep floppy up to date */ /*ARGSUSED*/ static int pcfs_close(vp, flag, count, cred) struct vnode *vp; int flag; int count; struct ucred *cred; { return (0); } /* * read or write a vnode */ /*ARGSUSED*/ static int pcfs_rdwr(vp, uiop, rw, ioflag, cred) struct vnode *vp; struct uio *uiop; enum uio_rw rw; int ioflag; struct ucred *cred; { register struct pcfs *fsp; register struct pcnode *pcp; int error; fsp = VFSTOPCFS(vp->v_vfsp); error = pc_lockfs(fsp); if (error) return (error); if ((pcp = VTOPC (vp)) == NULL) { pc_unlockfs (fsp); return (EIO); } if ((ioflag & IO_APPEND) && (rw == UIO_WRITE)) { /* * in append mode start at end of file. */ uiop->uio_offset = pcp->pc_size; } error = rwpcp(pcp, uiop, rw, ioflag); pc_unlockfs(fsp); if (error) { PCFSDEBUG(3) printf("pcfs_rdwr: io error = %d\n", error); } return (error); } int pc_bzero(pcp, off, len, flag) register struct pcnode *pcp; u_int off, len, flag; { u_int o; int err; u_int zlen; addr_t addr; struct vnode *vp = PCTOV(pcp); for (; len > 0; off += MAXBSIZE, len -= zlen) { o = off & MAXBOFFSET; addr = segmap_getmap(segkmap, vp, off & MAXBMASK); zlen = MIN(MAXBSIZE - o, len); if (o) { off = off & MAXBMASK; err = as_fault(&kas, addr + o, zlen, F_SOFTLOCK, S_OTHER); if (err) { (void) segmap_release(segkmap, addr, 0); if (FC_CODE(err) == FC_OBJERR) return (FC_ERRNO(err)); else return (EIO); } } else { segmap_pagecreate(segkmap, addr + o, zlen, 1); } (void) bzero(addr + o, zlen); (void) as_fault(&kas, addr + o, zlen, F_SOFTUNLOCK, S_WRITE); if (flag & IO_SYNC) { (void) segmap_release(segkmap, addr, SM_WRITE); } else { (void) segmap_release(segkmap, addr, 0); } } return (0); } static int rwpcp(pcp, uio, rw, ioflag) register struct pcnode *pcp; register struct uio *uio; enum uio_rw rw; int ioflag; { struct vnode *vp; struct pcfs *fsp; daddr_t lbn; /* logical block number */ daddr_t bn; /* phys block number */ register int n; register u_int off; addr_t base; int mapon, bsize, pagecreate; int error = 0; u_int size; PCFSDEBUG(6) printf("rwpcp pcp=%x off=%d resid=%d rw=%d size=%d\n", pcp, uio->uio_offset, uio->uio_resid, rw, pcp->pc_size); if (rw != UIO_READ && rw != UIO_WRITE) panic("rwpcp"); if (uio->uio_offset < 0 || (uio->uio_offset + uio->uio_resid) < 0) return (EINVAL); if (uio->uio_resid == 0) return (0); if (rw == UIO_WRITE && uio->uio_offset + uio->uio_resid > u.u_rlimit[RLIMIT_FSIZE].rlim_cur) { psignal(u.u_procp, SIGXFSZ); return (EFBIG); } #ifdef notdef if ((ioflag & IOSYNC) && !pc_verify(pcp)) return (EIO); #endif notdef vp = PCTOV(pcp); fsp = VFSTOPCFS(vp->v_vfsp); bsize = fsp->pcfs_clsize; size = roundup(pcp->pc_size, fsp->pcfs_clsize); do { off = uio->uio_offset & MAXBMASK; mapon = uio->uio_offset & MAXBOFFSET; n = MIN(MAXBSIZE - mapon, uio->uio_resid); lbn = (uio->uio_offset + n -1)/ bsize; if (rw == UIO_READ) { int diff; diff = pcp->pc_size - uio->uio_offset; if (diff <= 0) return (0); if (diff < n) n = diff; } base = segmap_getmap(segkmap, vp, off); pagecreate = 0; if (rw == UIO_WRITE) { if (uio->uio_offset + n > size) { error = pc_balloc(pcp, lbn, &bn); if (error) { if (error == ENOSPC) { /* * Because the page size is many times * the DOS blk size (clsize), we may * have allocated a few clusters before * we run out of space. So we have to * return those clusters here. */ int save; save = pcp->pc_size; pcp->pc_size = uio->uio_offset + n; (void) pc_bfree(pcp, (short) (size/fsp->pcfs_clsize)); pcp->pc_size = save; } (void) segmap_release(segkmap, base, 0); PCFSDEBUG (2) printf("rwpcp error1=%d\n", error); return (error); } if (mapon == 0) { segmap_pagecreate(segkmap, base, (u_int)n, 0); pagecreate = 1; } if (size < uio->uio_offset) { /* * Has to zero out date between size and * uio_offset */ pcp->pc_size = uio->uio_offset + n; error = pc_bzero(pcp, size, (u_int) uio->uio_offset-size, (u_int) ioflag); if (error) { (void) segmap_release(segkmap, base, 0); (void) pc_bfree(pcp, (short) (size/fsp->pcfs_clsize)); pcp->pc_size = size; return (error); } pagecreate = 1; } } else if (n == MAXBSIZE) { segmap_pagecreate(segkmap, base, (u_int)n, 0); pagecreate = 1; } if (uio->uio_offset + n > pcp->pc_size) { size = uio->uio_offset + n; pcp->pc_size = size; } } error = uiomove(base + mapon, n, rw, uio); if (pagecreate && uio->uio_offset < roundup(off + mapon + n, fsp->pcfs_clsize)) { int nzero, nmoved; nmoved = uio->uio_offset - (off + mapon); nzero = roundup(mapon + n, fsp->pcfs_clsize) - nmoved; (void) kzero(base + mapon + nmoved, (u_int)nzero); } if (error == 0) { u_int flags = 0; if (rw == UIO_READ) { if (n + mapon == MAXBSIZE || uio->uio_offset == pcp->pc_size) flags = SM_DONTNEED; } else if (rw == UIO_WRITE) { if ((ioflag & IO_SYNC) /* %% || type == IFDIR */) { flags = SM_WRITE; } else if (n + mapon == MAXBSIZE) { flags = SM_WRITE|SM_ASYNC|SM_DONTNEED; } } error = segmap_release(segkmap, base, flags); } else { (void) segmap_release(segkmap, base, 0); } if (rw == UIO_WRITE) pc_mark(pcp); } while (error == 0 && uio->uio_resid > 0 && n != 0); #ifdef notdef if ((ioflag & IO_SYNC) && (rw == UIO_WRITE) && (pcp->pc_flags & PC_MOD)) pc_nodeupdate(pcp); #endif notdef return (error); } /*ARGSUSED*/ static int pcfs_getattr(vp, vap, cred) struct vnode *vp; register struct vattr *vap; struct ucred *cred; { register struct pcnode *pcp; register struct pcfs *fsp; int error; char attr; fsp = VFSTOPCFS(vp->v_vfsp); error = pc_lockfs(fsp); if (error) return (error); if ((pcp = VTOPC (vp)) == NULL) { pc_unlockfs (fsp); return (EIO); } /* * Copy from pcnode. */ vap->va_type = vp->v_type; attr = pcp->pc_entry.pcd_attr; if (attr & (PCA_HIDDEN|PCA_SYSTEM)) vap->va_mode = 0; else if (attr & PCA_RDONLY) vap->va_mode = 0555; else vap->va_mode = 0777; if (attr & PCA_DIR) vap->va_mode |= S_IFDIR; else vap->va_mode |= S_IFREG; vap->va_uid = u.u_uid; vap->va_gid = u.u_gid; vap->va_fsid = fsp->pcfs_devvp->v_rdev; vap->va_nodeid = pc_makenodeid(pcp->pc_eblkno, pcp->pc_eoffset, &pcp->pc_entry); vap->va_nlink = 1; vap->va_size = pcp->pc_size; pc_pcttotv(&pcp->pc_entry.pcd_mtime, &vap->va_mtime); vap->va_ctime = vap->va_atime = vap->va_mtime; vap->va_rdev = -1; vap->va_blocks = howmany(pcp->pc_size, DEV_BSIZE); vap->va_blocksize = fsp->pcfs_clsize; pc_unlockfs(fsp); return (0); } /*ARGSUSED*/ static int pcfs_setattr(vp, vap, cred) struct vnode *vp; register struct vattr *vap; struct ucred *cred; { register struct pcnode *pcp; int error, error2; /* * cannot set these attributes */ if ((vap->va_nlink != -1) || (vap->va_blocksize != -1) || (vap->va_rdev != -1) || (vap->va_blocks != -1) || (vap->va_fsid != -1) || (vap->va_nodeid != -1) || (vap->va_uid != (uid_t)-1) || (vap->va_gid != (uid_t)-1) || ((int)vap->va_type != -1)) { return (EINVAL); } error = pc_lockfs(VFSTOPCFS(vp->v_vfsp)); if (error) return (error); if ((pcp = VTOPC (vp)) == NULL) { pc_unlockfs (VFSTOPCFS (vp -> v_vfsp)); return (EIO); } /* * Change file access modes. * If nobody has write permision, file is marked readonly. * Otherwise file is writeable by anyone. */ if (vap->va_mode != (u_short)-1) { if ((vap->va_mode & 0222) == 0) pcp->pc_entry.pcd_attr |= PCA_RDONLY; else pcp->pc_entry.pcd_attr &= ~PCA_RDONLY; pcp->pc_flags |= PC_CHG; } /* * Truncate file. Must have write permission and not be a directory. */ if (vap->va_size != (u_long)-1) { if (pcp->pc_entry.pcd_attr & PCA_DIR) { error = EISDIR; goto out; } if (pcp->pc_entry.pcd_attr & PCA_RDONLY) { error = EACCES; goto out; } error = pc_truncate(pcp, (long)vap->va_size); if (error) goto out; } /* * Change file modified times. */ if (vap->va_mtime.tv_sec != -1) { /* * If SysV-compatible option to set access and * modified times if root, owner, or write access, * use current time rather than va_mtime. * * XXX - va_mtime.tv_usec == -1 flags this. */ pc_tvtopct((vap->va_mtime.tv_usec == -1) ? &time : &vap->va_mtime, &pcp->pc_entry.pcd_mtime); pcp->pc_flags |= PC_CHG; } out: error2 = pc_nodesync (pcp); pc_unlockfs(VFSTOPCFS(vp->v_vfsp)); return ((error2) ? error2 : error); } /*ARGSUSED*/ static int pcfs_access(vp, mode, cred) struct vnode *vp; int mode; struct ucred *cred; { struct pcnode * pcp; if ((pcp = VTOPC (vp)) == NULL) return (EIO); if ((mode & VWRITE) && (pcp -> pc_entry . pcd_attr & PCA_RDONLY)) return (EACCES); else return (0); } /*ARGSUSED*/ static int pcfs_fsync(vp, cred) struct vnode *vp; struct ucred *cred; { struct pcnode * pcp; int error; error = pc_lockfs(VFSTOPCFS(vp->v_vfsp)); if (error) return (error); if ((pcp = VTOPC (vp)) == NULL) { pc_unlockfs (VFSTOPCFS (vp -> v_vfsp)); return (EIO); } error = pc_nodesync (pcp); pc_unlockfs(VFSTOPCFS(vp->v_vfsp)); return (error); } /*ARGSUSED*/ static int pcfs_inactive(vp, cred) struct vnode *vp; struct ucred *cred; { register struct pcnode *pcp; struct pcfs * fsp; /* * Here's the scoop. V_count is 0 while we're waiting to lock the * file system. This means that if any other process operates on * this node while we're waiting it can rerelease it. So we * mark this node as release pending. */ if (vp->v_count) return (0); if ((pcp = VTOPC (vp)) != NULL) { if (pcp -> pc_flags & PC_RELE_PEND) return (0); pcp->pc_flags |= PC_RELE_PEND; } fsp = VFSTOPCFS (vp -> v_vfsp); PC_LOCKFS (fsp); /* * Check again to confirm that no intervening I/O error * with a subsequent pc_diskchanged() call has released * the pcnode. If it has then release the vnode as above. */ if ((pcp = VTOPC (vp)) != NULL) pcp->pc_flags &= ~PC_RELE_PEND; if (! vp->v_count) { if (pcp == NULL) { kmem_free((caddr_t) vp, sizeof (struct vnode)); } else { pc_rele (pcp); } } PC_UNLOCKFS (fsp); return (0); } /*ARGSUSED*/ static int pcfs_lookup(dvp, nm, vpp, cred, pnp, flags) struct vnode *dvp; char *nm; struct vnode **vpp; struct ucred *cred; struct pathname *pnp; int flags; { struct pcnode *pcp; register int error; error = pc_lockfs(VFSTOPCFS(dvp->v_vfsp)); if (error) return (error); if (VTOPC(dvp) == NULL) { pc_unlockfs (VFSTOPCFS (dvp->v_vfsp)); return (EIO); } error = pc_dirlook(VTOPC(dvp), nm, &pcp); if (!error) { *vpp = PCTOV(pcp); pcp->pc_flags |= PC_EXTERNAL; } pc_unlockfs(VFSTOPCFS(dvp->v_vfsp)); return (error); } /*ARGSUSED*/ static int pcfs_create(dvp, nm, vap, exclusive, mode, vpp, cred) struct vnode *dvp; char *nm; struct vattr *vap; enum vcexcl exclusive; int mode; struct vnode **vpp; struct ucred *cred; { register int error; struct pcnode *pcp; struct vnode * vp; /* * can't create directories. use pcfs_mkdir. */ if (vap->va_type == VDIR) return (EISDIR); pcp = (struct pcnode *) 0; error = pc_lockfs(VFSTOPCFS(dvp->v_vfsp)); if (error) return (error); if (VTOPC(dvp) == NULL) { pc_unlockfs (VFSTOPCFS (dvp -> v_vfsp)); return (EIO); } error = pc_direnter(VTOPC(dvp), nm, vap, &pcp); /* * if file exists and this is a nonexclusive create, * check for access permissions */ if (error == EEXIST) { vp = PCTOV (pcp); if (exclusive == NONEXCL) { if (vp -> v_type == VDIR) { error = EISDIR; } else if (mode) { error = pcfs_access(PCTOV(pcp), mode, (struct ucred *) 0); } else { error = 0; } } if (error) { #ifdef notdef if (vp->v_count == 1) { vp->v_count = 0; if (!(pcp->pc_flags & PC_RELE_PEND)) pc_rele(pcp); } else #endif notdef VN_RELE(PCTOV(pcp)); } } if (error) { pc_unlockfs(VFSTOPCFS(dvp->v_vfsp)); return (error); } /* * truncate regular files, if required */ if ((vp->v_type == VREG) && (vap->va_size == 0)) { error = pc_truncate(pcp, 0L); if (error) { VN_RELE(PCTOV(pcp)); pc_unlockfs(VFSTOPCFS(dvp->v_vfsp)); return (error); } if (error = pc_nodesync(pcp)) { pc_unlockfs (VFSTOPCFS (dvp->v_vfsp)); return (error); } } *vpp = PCTOV(pcp); pcp->pc_flags |= PC_EXTERNAL; pc_unlockfs(VFSTOPCFS(dvp->v_vfsp)); return (0); } /*ARGSUSED*/ static int pcfs_remove(vp, nm, cred) struct vnode *vp; char *nm; struct ucred *cred; { register int error; struct pcnode * pcp; error = pc_lockfs(VFSTOPCFS(vp->v_vfsp)); if (error) return (error); if ((pcp = VTOPC (vp)) == NULL) { pc_unlockfs (VFSTOPCFS (vp -> v_vfsp)); return (EIO); } error = pc_dirremove (pcp, nm, VREG); pc_unlockfs(VFSTOPCFS(vp->v_vfsp)); return (error); } /* * Rename a file or directory * This rename is restricted to only rename files within a directory. * XX should make rename more general */ /*ARGSUSED*/ static int pcfs_rename(sdvp, snm, tdvp, tnm, cred) struct vnode *sdvp; /* old (source) parent vnode */ char *snm; /* old (source) entry name */ struct vnode *tdvp; /* new (target) parent vnode */ char *tnm; /* new (target) entry name */ struct ucred *cred; { register struct pcnode *dp; /* parent pcnode */ register int error; if (((dp = VTOPC(sdvp)) == NULL) || (VTOPC (tdvp) == NULL)) { return (EIO); } /* * make sure source and target directories are the same */ if (dp->pc_scluster != VTOPC(tdvp)->pc_scluster) return (EXDEV); /* XXX */ /* * make sure we can muck with this directory. */ error = pcfs_access(sdvp, VWRITE, (struct ucred *) 0); if (error) { return (error); } error = pc_lockfs (VFSTOPCFS (sdvp -> v_vfsp)); if (error) return (error); if ((VTOPC(sdvp) == NULL) || (VTOPC (tdvp) == NULL)) { pc_unlockfs (VFSTOPCFS (sdvp -> v_vfsp)); return (EIO); } error = pc_rename(dp, snm, tnm); pc_unlockfs (VFSTOPCFS (sdvp -> v_vfsp)); return (error); } /*ARGSUSED*/ static int pcfs_mkdir(dvp, nm, vap, vpp, cred) struct vnode *dvp; char *nm; register struct vattr *vap; struct vnode **vpp; struct ucred *cred; { struct pcnode *pcp; register int error; error = pc_lockfs(VFSTOPCFS(dvp->v_vfsp)); if (error) return (error); if (VTOPC (dvp) == NULL) { pc_unlockfs (VFSTOPCFS (dvp -> v_vfsp)); return (EIO); } error = pc_direnter(VTOPC(dvp), nm, vap, &pcp); if (!error) { pcp -> pc_flags |= PC_EXTERNAL; *vpp = PCTOV(pcp); } else if (error == EEXIST) { VN_RELE(PCTOV(pcp)); } pc_unlockfs (VFSTOPCFS (dvp -> v_vfsp)); return (error); } /*ARGSUSED*/ static int pcfs_rmdir(dvp, nm, cred) struct vnode *dvp; char *nm; struct ucred *cred; { struct pcfs * fsp; struct pcnode * pcp; register int error; fsp = VFSTOPCFS (dvp -> v_vfsp); if (error = pc_lockfs (fsp)) return (error); if ((pcp = VTOPC (dvp)) == NULL) { pc_unlockfs (fsp); return (EIO); } error = pc_dirremove (pcp, nm, VDIR); pc_unlockfs (fsp); return (error); } /* * read entries in a directory. * we must convert pc format to unix format */ /*ARGSUSED*/ static int pcfs_readdir(dvp, uiop, cred) struct vnode *dvp; register struct uio *uiop; struct ucred *cred; { register char *fp, *tp; register struct pcnode *pcp; struct pcdir *ep; struct buf *bp = NULL; register char c; register int i; long offset; register int n; register int boff; struct dirent d; int error, direntsiz; char *strcpy(); direntsiz = sizeof (struct dirent) - MAXNAMLEN - 1 + roundup((PCFNAMESIZE + PCFEXTSIZE + 2), sizeof (int)); if ((uiop->uio_iovcnt != 1) || (uiop->uio_offset % direntsiz)) return (EINVAL); error = pc_lockfs(VFSTOPCFS(dvp->v_vfsp)); if (error) return (error); if ((pcp = VTOPC(dvp)) == NULL) { pc_unlockfs (VFSTOPCFS (dvp->v_vfsp)); return (EIO); } /* * verify that the dp is still valid on the disk */ (void) pc_verify(pcp); n = uiop->uio_resid / direntsiz; offset = (uiop->uio_offset / direntsiz) * sizeof (struct pcdir); d.d_reclen = direntsiz; if (dvp->v_flag & VROOT) { /* * kludge up entries for "." and ".." in the root. */ if (offset == 0 && n) { d.d_off = uiop->uio_offset + direntsiz; d.d_fileno = -1; d.d_namlen = 1; (void) strcpy(d.d_name, "."); (void) uiomove((caddr_t)&d, direntsiz, UIO_READ, uiop); offset = sizeof (struct pcdir); n--; } if (offset == sizeof (struct pcdir) && n) { d.d_off = uiop->uio_offset + direntsiz; d.d_fileno = -1; d.d_namlen = 2; (void) strcpy(d.d_name, ".."); (void) uiomove((caddr_t)&d, direntsiz, UIO_READ, uiop); offset = 2 * sizeof (struct pcdir); n--; } offset -= 2 * sizeof (struct pcdir); } for (; n--; ep++, offset += sizeof (struct pcdir)) { boff = pc_blkoff(VFSTOPCFS(dvp->v_vfsp), offset); if (boff == 0 || bp == NULL || boff >= bp->b_bcount) { if (bp != NULL) { brelse(bp); bp = NULL; } error = pc_blkatoff(pcp, offset, &bp, &ep); if (error) { /* * When is an error not an error? */ if (error == ENOENT) error = 0; break; } } if (ep->pcd_filename[0] == PCD_UNUSED) break; /* * Don't display label because it may contain funny characters. */ if ((ep->pcd_filename[0] == PCD_ERASED) || (ep->pcd_attr & (PCA_HIDDEN | PCA_SYSTEM | PCA_LABEL))) { uiop->uio_offset += direntsiz; continue; } d.d_fileno = pc_makenodeid(bp->b_blkno, boff, ep); tp = &d.d_name[0]; fp = &ep->pcd_filename[0]; i = PCFNAMESIZE; while (i-- && ((c = *fp) != ' ')) { if (!(c == '.' || pc_validchar(c))) { tp = &d.d_name[0]; break; } *tp++ = tolower(c); fp++; } fp = &ep->pcd_ext[0]; if (tp != &d.d_name[0] && *fp != ' ') { *tp++ = '.'; i = PCFEXTSIZE; while (i-- && ((c = *fp) != ' ')) { if (!pc_validchar(c)) { tp = &d.d_name[0]; break; } *tp++ = tolower(c); fp++; } } *tp = 0; d.d_namlen = tp - &d.d_name[0]; d.d_off = uiop->uio_offset + direntsiz; if (d.d_namlen) { (void) uiomove((caddr_t)&d, direntsiz, UIO_READ, uiop); } else { uiop->uio_offset += direntsiz; } } if (bp) brelse(bp); pc_unlockfs(VFSTOPCFS(dvp->v_vfsp)); return (error); } /* * Called from pvn_getpages or pcfs_getpage to get a particular page. * When we are called the pcfs is already locked. If rw == S_WRITE * and the block is not currently allocated we need to allocate the * needed block(s). * * bsize is either 4k or 8k. To handle the case of 4k bsize and 8k pages * we will do two reads to get the data and don't bother with read ahead. * Thus having 4k file systems on a Sun-3 works, but it is not recommended. * * XXX - should handle arbritrary file system block and page sizes. */ /*ARGSUSED*/ static int pcfs_getapage(vp, off, protp, pl, plsz, seg, addr, rw, cred) struct vnode *vp; register u_int off; u_int *protp; struct page *pl[]; /* NULL if async IO is requested */ u_int plsz; struct seg *seg; addr_t addr; enum seg_rw rw; struct ucred *cred; { register struct pcnode * pcp; register struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp); struct vnode *devvp; struct page *pp, *pp2, **ppp, *pagefound; daddr_t lbn, bn; u_int io_off, io_len; u_int lbnoff, blksz; int i, err, nio; u_int pgoff; dev_t dev; if ((pcp = VTOPC (vp)) == NULL) return (EIO); VFS_RECORD(vp->v_vfsp, VS_GETPAGE, VS_CALL); devvp = fsp->pcfs_devvp; dev = devvp->v_rdev; reread: pagefound = NULL; pgoff = i = 0; lbn = pc_lblkno(fsp, off); lbnoff = off & ~(fsp->pcfs_clsize -1); if (pl != NULL) pl[0] = NULL; err = 0; if ((pagefound = page_find(vp, off)) == NULL) { struct buf *bp; /* * Need to really do disk IO to get the page(s). */ VFS_RECORD(vp->v_vfsp, VS_GETPAGE, VS_MISS); blksz = pc_blksize(fsp, pcp, off); pp = pvn_kluster(vp, off, seg, addr, &io_off, &io_len, lbnoff, blksz, 0); if (pp == NULL) panic("pcfs_getapage pvn_kluster"); if (pl != NULL) { register int sz; if (plsz >= io_len) { /* * Everything fits, set up to load * up and hold all the pages. */ pp2 = pp; sz = io_len; } else { /* * Set up to load plsz worth * starting at the needed page. */ for (pp2 = pp; pp2->p_offset != off; pp2 = pp2->p_next) { } sz = plsz; } ppp = pl; do { PAGE_HOLD(pp2); *ppp++ = pp2; pp2 = pp2->p_next; sz -= PAGESIZE; } while (sz > 0); *ppp = NULL; /* terminate list */ } nio = MIN(PAGESIZE/fsp->pcfs_clsize, howmany(pcp->pc_size-lbnoff, fsp->pcfs_clsize)); PCFSDEBUG(6) printf("pc_getapage off=%d nio=%d\n", off, nio); if (nio > 1) pp->p_nio = nio; for (i = 0, pgoff = 0; i < nio; ++i, ++lbn) { /* ?? should also use read-ahead */ err = pc_bmap(pcp, lbn, &bn, (daddr_t *) 0); if (err) { /* * Need to call pvn_fail() nio times * if there were multiple IO requests * for this page. */ while (pp->p_nio > 1) pvn_fail(pp, B_READ); pvn_fail(pp, B_READ); goto out; } blksz = pc_blksize(fsp, pcp, lbnoff); bp = pageio_setup(pp, blksz, devvp, pl == NULL ? (B_ASYNC | B_READ) : B_READ); bp->b_dev = dev; bp->b_blkno = bn; bp->b_un.b_addr = (caddr_t)pgoff; (*bdevsw[major(dev)].d_strategy)(bp); lbnoff += blksz; pgoff += blksz; u.u_ru.ru_majflt++; if (seg == segkmap) u.u_ru.ru_inblock++; /* count as `read' */ cnt.v_pgin++; cnt.v_pgpgin += btopr(blksz); if (pl != NULL) { if (err == 0) err = biowait(bp); else (void) biowait(bp); pageio_done(bp); } if (err) { while (pp->p_nio > 1) pvn_fail(pp, B_READ); pvn_fail(pp, B_READ); goto out; } } if (pgoff != PAGESIZE) { pagezero(pp->p_prev, pgoff, PAGESIZE - pgoff); } } else { PCFSDEBUG(6) printf("pc_getapage page found, off=%d\n", off); } out: if (pagefound != NULL) { int s; /* * We need to be careful here because if the page was * previously on the free list, we might have already * lost it at interrupt level. */ s = splvm(); if (pagefound->p_vnode == vp && pagefound->p_offset == off) { /* * If the page is still intransit or if * it is on the free list call page_lookup * to try and wait for / reclaim the page. */ if (pagefound->p_intrans || pagefound->p_free) pagefound = page_lookup(vp, off); } (void) splx(s); if (pagefound == NULL || pagefound->p_offset != off || pagefound->p_vnode != vp || pagefound->p_gone) { goto reread; } PAGE_HOLD(pagefound); pl[0] = pagefound; pl[1] = NULL; u.u_ru.ru_minflt++; } if (err && pl != NULL) { for (ppp = pl; *ppp != NULL; *ppp++ = NULL) PAGE_RELE(*ppp); } return (err); } /* * Return all the pages from [off..off+len) in given file */ static int pcfs_getpage(vp, off, len, protp, pl, plsz, seg, addr, rw, cred) struct vnode *vp; u_int off, len; u_int *protp; struct page *pl[]; u_int plsz; struct seg *seg; addr_t addr; enum seg_rw rw; struct ucred *cred; { struct pcnode * pcp; register struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp); int err; if ((pcp = VTOPC (vp)) == NULL) return (EIO); if (vp->v_type != VREG) { PCFSDEBUG (2) printf("pcfs_getpage doesn't support non-regular files now.\n"); return (EINVAL); } /* * Normally fail if faulting beyond EOF, *except* if this * is an internal access of pcfs data. This condition is * detected by testing the faulting segment against segkmap. * Since accessing the file through segkmap is only done * in places in the kernel which have knowledge of the * current file length, these places deal with EOF themselves. * For example, bmap may be faulting in pages beyond the * current EOF when it is creating pages needed for extending * the length of the file. */ if (off + len > pcp->pc_size + PAGEOFFSET && seg != segkmap) { return (EFAULT); /* beyond EOF */ } if (protp != NULL) *protp = PROT_ALL; if (off & PAGEOFFSET) { printf("pcfs: off %d\n", off); panic("pcfs_getpage1"); } if (off +len > roundup(pcp->pc_size, PAGESIZE)) { /* ?? */ printf("pcfs: off %d pc_size %d\n", off, pcp->pc_size); panic("pcfs_getpage2"); } err = pc_lockfs(fsp); if (err) return (err); if ((pcp = VTOPC (vp)) == NULL) { pc_unlockfs (fsp); return (EIO); } if (len <= PAGESIZE) { err = pcfs_getapage(vp, off, protp, pl, plsz, seg, addr, rw, cred); } else { err = pvn_getpages(pcfs_getapage, vp, off, len, protp, pl, plsz, seg, addr, rw, cred); } #ifdef notdef /* * ?? * If the inode is not already marked for IACC (in rwip() for read) * and the inode is not marked for no access time update (in rwip() * for write) then update the inode access time and mod time now. */ if ((ip->i_flag & (IACC | INOACC)) == 0) { if (rw != S_OTHER) { ip->i_flag |= IACC; } if (rw == S_WRITE) { ip->i_flag |= IUPD; } ITIMES(ip); } #endif notdef pc_unlockfs(fsp); return (err); } /* * Flags are composed of {B_ASYNC, B_INVAL, B_FREE, B_DONTNEED, B_FORCE} * If len == 0, do from off to EOF. * * The normal cases should be len == 0 & off == 0 (entire vp list), * len == MAXBSIZE (from segmap_release actions), and len == PAGESIZE * (from pageout). * */ /*ARGSUSED*/ static int pcfs_putpage(vp, off, len, flags, cred) register struct vnode *vp; u_int off, len; int flags; struct ucred *cred; { register struct pcnode *pcp; register struct page *pp; register struct pcfs *fsp; struct page *dirty; struct vnode *devvp; register u_int io_off; daddr_t lbn, bn; u_int lbn_off; u_int blksz, pgoff; int vpcount, nio, i; int err; dev_t dev; #ifdef VFSSTATS VFS_RECORD(vp->v_vfsp, VS_PUTPAGE, VS_CALL); #endif if ((pcp = VTOPC (vp)) == NULL) return (EIO); if (vp->v_pages == NULL || off >= pcp->pc_size) return (0); vpcount = vp->v_count; VN_HOLD(vp); fsp = VFSTOPCFS(vp->v_vfsp); err = pc_lockfs(fsp); if (err) return (err); if ((pcp = VTOPC (vp)) == NULL) { pc_unlockfs (fsp); return (EIO); } again: if (len == 0) { /* * Search the entire vp list for pages >= off */ dirty = pvn_vplist_dirty(vp, off, flags); } else { /* * Do a range from [off...off + len) via page_find. */ if (off >= pcp->pc_size) { dirty = NULL; } else { u_int fsize, eoff; fsize = (pcp->pc_size + MAXBOFFSET) & MAXBMASK; eoff = MIN(off + len, fsize); dirty = pvn_range_dirty(vp, off, eoff, (u_int)(off), (u_int)(eoff), flags); } } /* * Now pp will have the list of kept dirty pages marked for * write back. All the pages on the pp list need to still * be dealt with here. Verify that we can really can do the * write back to the filesystem and if not and we have some * dirty pages, return an error condition. */ if ((vp->v_vfsp->vfs_flag & VFS_RDONLY) && dirty != NULL) err = EROFS; else err = 0; if (dirty != NULL) { /* * ?? * If the modified time on the inode has not already been * set elsewhere (i.e. for write/setattr) or this is * a call from msync (B_FORCE) we set the time now. * This gives us approximate modified times for mmap'ed files * which are modified via stores in the user address space. */ pc_tvtopct(&time, &pcp->pc_entry.pcd_mtime); pcp->pc_flags |= PC_MOD | PC_CHG; devvp = fsp->pcfs_devvp; dev = devvp->v_rdev; } /* * Handle all the dirty pages. */ pp = NULL; while (err == 0 && dirty != NULL) { /* * Pull off a contiguous chunk that fits in one lbn. */ io_off = dirty->p_offset; PCFSDEBUG(6) printf("pc_putpage writing dirty page off=%d\n", io_off); lbn = pc_lblkno(fsp, io_off); VFS_RECORD(vp->v_vfsp, VS_PUTPAGE, VS_MISS); pp = dirty; lbn_off = io_off & ~(fsp->pcfs_clsize - 1); page_sub(&dirty, pp); /* IO may be asynch, so need to set nio first */ nio = MIN(PAGESIZE/fsp->pcfs_clsize, howmany(pcp->pc_size-lbn_off, fsp->pcfs_clsize)); if (nio > 1) { pp->p_nio = nio; } else { pp->p_nio = 0; } for (i = 0, pgoff = 0; i < nio; ++i, ++lbn) { struct buf *bp; /* * ?? * Normally the blocks should already be allocated for * any dirty pages. */ err = pc_bmap(pcp, lbn, &bn, (daddr_t *) 0); if (err) break; blksz = pc_blksize(fsp, pcp, lbn_off); lbn_off += blksz; bp = pageio_setup(pp, blksz, devvp, B_WRITE | flags); if (bp == NULL) { pvn_fail(pp, B_WRITE | flags); err = ENOMEM; break; } bp->b_dev = dev; bp->b_blkno = bn; bp->b_un.b_addr = (addr_t)pgoff; (*bdevsw[major(dev)].d_strategy)(bp); u.u_ru.ru_oublock++; /* * If async, assume that pvn_done will * handle the pages when IO is done */ if ((flags & B_ASYNC) == 0) { err = biowait(bp); pageio_done(bp); } if (err) break; pgoff += blksz; } if (err) break; pp = NULL; } if (err != 0) { if (dirty != NULL) pvn_fail(dirty, B_WRITE | flags); } else if (off == 0 && (len == 0 || len >= pcp->pc_size)) { /* * If doing "synchronous invalidation", make * sure that all the pages are actually gone. */ if ((flags & (B_INVAL | B_ASYNC)) == B_INVAL && (vp->v_pages != NULL)) goto again; } /* * Instead of using VN_RELE here we are careful to only call * the inactive routine if the vnode reference count is now zero, * but it wasn't zero coming into putpage. This is to prevent * recursively calling the inactive routine on a vnode that * is already considered in the `inactive' state. * XXX - inactive is a relative term here (sigh). */ if (--vp->v_count == 0 && vpcount > 0) pc_rele(pcp); /* ?? */ pc_unlockfs(fsp); return (err); } /*ARGSUSED*/ static int pcfs_map(vp, off, as, addrp, len, prot, maxprot, flags, cred) struct vnode *vp; u_int off; struct as *as; addr_t *addrp; u_int len; u_int prot, maxprot; u_int flags; struct ucred *cred; { struct segvn_crargs vn_a; VFS_RECORD(vp->v_vfsp, VS_MAP, VS_CALL); if ((int)off < 0 || (int)(off + len) < 0) return (EINVAL); if (vp->v_type != VREG) return (ENODEV); if ((flags & MAP_FIXED) == 0) { map_addr(addrp, len, (off_t)off, 1); if (*addrp == NULL) return (ENOMEM); } else { /* * User specified address - blow away any previous mappings */ (void) as_unmap(as, *addrp, len); } vn_a.vp = vp; vn_a.offset = off; vn_a.type = flags & MAP_TYPE; vn_a.prot = prot; vn_a.maxprot = maxprot; vn_a.cred = cred; vn_a.amp = NULL; return (as_map(as, *addrp, len, segvn_create, (caddr_t)&vn_a)); } static int pcfs_cmp(vp1, vp2) struct vnode *vp1, *vp2; { VFS_RECORD(vp1->v_vfsp, VS_CMP, VS_CALL); return (vp1 == vp2); } /*ARGSUSED*/ static int pcfs_realvp(vp, vpp) struct vnode *vp; struct vnode **vpp; { VFS_RECORD(vp->v_vfsp, VS_REALVP, VS_CALL); return (EINVAL); } #ifdef notdef static int pcfs_fid(vp, fidpp) struct vnode *vp; struct fid **fidpp; { register struct pcfid *pcfid; struct pcnode *pcp; VFS_RECORD(vp->v_vfsp, VS_FID, VS_CALL); if ((pcp = VTOPC (vp)) == NULL) return (EIO); pcfid = (struct pcfid *)kmem_zalloc(sizeof (struct pcfid)); pcfid->pcfid_len = sizeof (struct pcfid) - sizeof (u_short); pcfid->pcfid_fileno = pc_makenodeid(pcp->pc_eblkno, pcp->pc_eoffset, &(pcp->pc_entry)); pcfid->pcfid_gen = pcp->pc_entry.pcd_gen; *fidpp = (struct fid *)pcfid; return (0); } #endif notdef /*ARGSUSED*/ static int pcfs_cntl(vp, cmd, idata, odata, iflag, oflag) struct vnode *vp; int cmd, iflag, oflag; caddr_t idata, odata; { switch (cmd) { default: return (EINVAL); case _PC_LINK_MAX: *(int *)odata = 1; break; case _PC_NAME_MAX: if (vp->v_type == VREG) return (EINVAL); *(int *)odata = PCFNAMESIZE+PCFEXTSIZE+1; break; case _PC_PATH_MAX: if (vp->v_type == VREG) return (EINVAL); *(int *)odata = PCMAXPATHLEN; break; case _PC_CHOWN_RESTRICTED: *(int *)odata = 1; break; case _PC_NO_TRUNC: *(int *)odata = 0; break; } return (0); } int pcfs_badop() { panic("pcfs_badop"); } int pcfs_invalop() { PCFSDEBUG(1) printf("pcfs_invalop\n"); return (EINVAL); }