#ifndef lint static char sccsid[] = "@(#)pc_dir.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 /* * slot structure is used by the directory search routine to return * the results of the search. If the search is successful sl_blkno and * sl_offset reflect the disk address of the entry and sl_ep points to * the actual entry data in buffer sl_bp. sl_flags is set to whether the * entry is dot or dotdot. If the search is unsuccessful sl_blkno and * sl_offset points to an empty directory slot if there are any. Otherwise * it is set to -1. */ struct slot { enum {SL_NONE, SL_FOUND, SL_EXTEND} sl_status; /* slot status */ daddr_t sl_blkno; /* disk block number which has entry */ int sl_offset; /* offset of entry within block */ struct buf *sl_bp; /* buffer containing entry data */ struct pcdir *sl_ep; /* pointer to entry data */ int sl_flags; /* flags (see below) */ }; #define SL_DOT 1 /* entry point to self */ #define SL_DOTDOT 2 /* entry points to parent */ /* * Lookup a name in a directory. Return a pointer to the pc_node * which represents the entry. */ int pc_dirlook(dp, namep, pcpp) register struct pcnode *dp; /* parent directory */ char *namep; /* name to lookup */ struct pcnode **pcpp; /* result */ { struct vnode * vp; struct slot slot; int error; PCFSDEBUG(7) printf("pc_dirlook (dp %x name %s pcpp %x)\n", dp, namep, pcpp); if (!(dp->pc_entry.pcd_attr & PCA_DIR)) { return (ENOTDIR); } /* * The root directory does not have "." and ".." entries, * so they are faked here. */ vp = PCTOV(dp); if (vp->v_flag & VROOT) { if (bcmp(namep, ".", 2) == 0 || bcmp(namep, "..", 3) == 0) { *pcpp = pc_getnode(VFSTOPCFS(vp->v_vfsp), (daddr_t)0, 0, (struct pcdir *)0); return (0); } } error = pc_findentry(dp, namep, &slot); if (error == 0) { *pcpp = pc_getnode(VFSTOPCFS(vp->v_vfsp), slot.sl_blkno, slot.sl_offset, slot.sl_ep); brelse(slot.sl_bp); } else if (error == EINVAL) { error = ENOENT; } return (error); } /* * Enter a name in a directory. */ pc_direnter(dp, namep, vap, pcpp) register struct pcnode *dp; /* directory to make entry in */ register char *namep; /* name of entry */ struct vattr *vap; /* attributes of new entry */ struct pcnode **pcpp; { register struct pcfs *fsp; register int error; struct slot slot; struct vnode * vp; PCFSDEBUG(7) printf("pcdirentry(dp %x, name %s, vap %x, pcpp %x\n", dp, namep, vap, pcpp); if (pcpp != NULL) *pcpp = NULL; if (!(dp->pc_entry.pcd_attr & PCA_DIR)) { return (ENOTDIR); } /* * If name is "." or "..", just look it up. */ if (*namep == '.') { if (pcpp) { error = pc_dirlook(dp, namep, pcpp); if (error) return (error); } return (EEXIST); } if (dp->pc_entry.pcd_attr & (PCA_HIDDEN | PCA_SYSTEM)) { return (EPERM); } /* * Make sure directory has not been removed while fs was unlocked. */ if (dp->pc_entry.pcd_filename[0] == PCD_ERASED) { return (ENOENT); } vp = PCTOV(dp); fsp = VFSTOPCFS(vp->v_vfsp); error = pc_findentry(dp, namep, &slot); if (error == 0) { if (pcpp) { *pcpp = pc_getnode(fsp, slot.sl_blkno, slot.sl_offset, slot.sl_ep); error = EEXIST; } brelse(slot.sl_bp); } else if (error == ENOENT) { struct pcdir direntry; /* * The entry does not exist. Check write permission in * directory to see if entry can be created. */ if (dp->pc_entry.pcd_attr & PCA_RDONLY) { return (EPERM); } error = 0; /* * Make sure there is a slot. */ if (slot.sl_status == SL_NONE) panic("pc_direnter: no slot\n"); if (slot.sl_status == SL_EXTEND) { /* * There is no slot in the directory, so try to * extend it. */ error = pc_balloc(dp, pc_lblkno(fsp, slot.sl_offset), &slot.sl_blkno); if (error) return (error); slot.sl_offset = 0; } /* * Make an entry from the supplied attributes. */ error = pc_makedirentry(dp, namep, vap, &direntry); if (error) return (error); /* * Get a pcnode for the new entry. */ *pcpp = pc_getnode(fsp, slot.sl_blkno, slot.sl_offset, &direntry); /* * Write out the new entry in the parent directory. */ error = pc_syncfat(fsp); if (! error) { error = pc_nodeupdate(*pcpp); } /* * %% why not update the mtime in dp? because '..' update * problem. (dp could be pointing to the '..' of a child * of the real directory, instead of the real directory itself. * Then we end up updating '..' entry. */ } return (error); } /* * Template for "." and ".." directory entries. */ static struct { struct pcdir t_dot; /* dot entry */ struct pcdir t_dotdot; /* dotdot entry */ } dirtemplate = { { ". ", " ", PCA_DIR, 0, "", {0, 0}, 0, 0 }, { ".. ", " ", PCA_DIR, 0, "", {0, 0}, 0, 0 } }; /* * Convert an attributes structure into a pc directory entry. */ int pc_makedirentry(dp, namep, vap, ep) struct pcnode *dp; /* parent directory */ char *namep; /* name of new node */ register struct vattr *vap; /* attributes of new node */ register struct pcdir *ep; /* new directory entry */ { struct vnode * vp; int error; bzero((caddr_t)ep, sizeof (struct pcdir)); error = pc_parsename(namep, ep->pcd_filename, ep->pcd_ext); if (error) return (error); pc_tvtopct(&time, &ep->pcd_mtime); ep->pcd_size = 0; ep->pcd_attr = 0; if ((vap->va_mode & 0222) == 0) ep->pcd_attr |= PCA_RDONLY; if (vap->va_type == VDIR) { register struct buf *bp; register struct pcfs *fsp; short cn; vp = PCTOV(dp); fsp = VFSTOPCFS(vp->v_vfsp); ep->pcd_attr |= PCA_DIR; /* * Make dot and dotdot entries for a new directory. */ switch (cn = pc_alloccluster(fsp)) { case PCF_FREECLUSTER: return (ENOSPC); case PCF_ERRORCLUSTER: return (EIO); } bp = bread(fsp->pcfs_devvp, pc_cldaddr(fsp, cn), fsp->pcfs_clsize); if (bp->b_flags & (B_ERROR | B_INVAL)) { pc_setcluster(fsp, cn, PCF_FREECLUSTER); if (bp->b_flags & B_ERROR) { PCFSDEBUG(1) printf("pc_makedirentry "); pc_diskchanged(fsp); return (EIO); } brelse(bp); return (EIO); } bp->b_flags |= B_NOCACHE; /* don't cache */ dirtemplate.t_dot.pcd_scluster = ep->pcd_scluster = htols(cn); dirtemplate.t_dotdot.pcd_scluster = dp->pc_entry.pcd_scluster; dirtemplate.t_dot.pcd_mtime = dirtemplate.t_dotdot.pcd_mtime = ep->pcd_mtime; bcopy((caddr_t)&dirtemplate, bp->b_un.b_addr, sizeof (dirtemplate)); bwrite(bp); if (bp->b_flags & B_ERROR) { pc_diskchanged(fsp); return (EIO); } } else { ep->pcd_scluster = 0; } return (0); } /* * Remove a name from a directory. */ pc_dirremove(dp, namep, type) register struct pcnode *dp; char *namep; enum vtype type; { struct slot slot; register struct pcnode *pcp; register int error; struct vnode * vp; vp = PCTOV(dp); if (!(dp->pc_entry.pcd_attr & PCA_DIR)) { return (ENOTDIR); } if (dp->pc_entry.pcd_attr & (PCA_RDONLY | PCA_HIDDEN | PCA_SYSTEM)) { return (EPERM); } error = pc_findentry(dp, namep, &slot); if (error) return (error); if (slot.sl_flags == SL_DOT) { error = EINVAL; } else if (slot.sl_flags == SL_DOTDOT) { error = ENOTEMPTY; } else { pcp = pc_getnode(VFSTOPCFS(vp->v_vfsp), slot.sl_blkno, slot.sl_offset, slot.sl_ep); } if (error) { brelse(slot.sl_bp); return (error); } if (type == VDIR) { if (pcp->pc_entry.pcd_attr & PCA_DIR) { if (!pc_dirempty(pcp)) { error = ENOTEMPTY; } } else { error = ENOTDIR; } } else { if (pcp->pc_entry.pcd_attr & PCA_DIR) error = EISDIR; } if (error == 0) { /* * Mark the in core node and on disk entry * as removed. The slot may then be reused. * The files clusters will be deallocated * when the last reference goes away. */ slot.sl_ep->pcd_filename[0] = pcp->pc_entry.pcd_filename[0] = PCD_ERASED; pcp->pc_eblkno = -1; bwrite(slot.sl_bp); if (slot.sl_bp->b_flags & B_ERROR) { pc_diskchanged(VFSTOPCFS(vp->v_vfsp)); return (EIO); } else if (type == VDIR) { /* %% delete '.', '..' now so that */ (void) pc_truncate(pcp, 0L); if (pc_syncfat(VFSTOPCFS(vp->v_vfsp))) { return (EIO); } } } else { brelse(slot.sl_bp); } /* * If this is the only reference release it now. */ #ifdef notdef if (PCTOV(pcp)->v_count == 1) { PCTOV(pcp)->v_count = 0; if (!(pcp->pc_flags & PC_RELE_PEND)) pc_rele(pcp); } else #endif notdef VN_RELE(PCTOV(pcp)); return (error); } /* * Ascertain whether a directory is empty. */ int pc_dirempty(pcp) register struct pcnode *pcp; { struct buf *bp; struct pcdir *ep; register long offset; register int boff; register char c; int error; struct vnode * vp; vp = PCTOV(pcp); bp = NULL; for (offset = 0; /* */; offset += sizeof (struct pcdir)) { /* * If offset is on a block boundary, * read in the next directory block. * Release previous if it exists. */ boff = pc_blkoff(VFSTOPCFS(vp->v_vfsp), offset); if (boff == 0 || bp == NULL || boff >= bp->b_bcount) { if (bp != NULL) brelse(bp); if (error = pc_blkatoff(pcp, offset, &bp, &ep)) { return (error); } } c = ep->pcd_filename[0]; if (c == PCD_UNUSED) break; if ((c != '.') && (c != PCD_ERASED)) { brelse(bp); return (0); } ep++; } if (bp != NULL) brelse(bp); return (1); } /* * Rename a file within a directory. * Target cannot exist (for now). */ pc_rename(dp, snm, tnm) register struct pcnode *dp; /* parent directory */ char *snm; /* source file name */ char *tnm; /* target file name */ { register struct pcnode *pcp; /* pcnode we are trying to rename */ struct slot slot; char tfname[PCFNAMESIZE]; char tfext[PCFEXTSIZE]; register int error; struct vnode * vp; vp = PCTOV(dp); PCFSDEBUG(6) printf("pc_rename(0x%x, %s, %s)\n", dp, snm, tnm); if (!(dp->pc_entry.pcd_attr & PCA_DIR)) { return (ENOTDIR); } /* * No dot or dotdot. */ if (*snm == '.' || *tnm == '.') return (EINVAL); /* * Get the source node. */ error = pc_findentry(dp, snm, &slot); if (error) { return (error); } pcp = pc_getnode(VFSTOPCFS(vp->v_vfsp), slot.sl_blkno, slot.sl_offset, slot.sl_ep); brelse(slot.sl_bp); /* * Parse the target name. */ error = pc_parsename(tnm, tfname, tfext); if (error) goto out; /* * See if source and target names are different. */ if (bcmp(tfname, pcp->pc_entry.pcd_filename, PCFNAMESIZE) == 0 && bcmp(tfext, pcp->pc_entry.pcd_ext, PCFEXTSIZE) == 0) { goto out; } /* * see if the target exists */ error = pc_findentry(dp, tnm, &slot); if (error == 0) { /* * Target exists. */ brelse(slot.sl_bp); error = EEXIST; } else if (error == ENOENT) { /* * Rename the source. */ bcopy(tfname, pcp->pc_entry.pcd_filename, PCFNAMESIZE); bcopy(tfext, pcp->pc_entry.pcd_ext, PCFEXTSIZE); if (error = pc_nodeupdate(pcp)) { return (error); } } out: /* * If this is the only reference release it now. */ #ifdef notdef if (PCTOV(pcp)->v_count == 1) { PCTOV(pcp)->v_count = 0; if (!(pcp->pc_flags & PC_RELE_PEND)) { pc_rele(pcp); } } else #endif notdef VN_RELE(PCTOV(pcp)); return (error); } /* * Search a directory for an entry. * The directory should be locked as this routine * will sleep on I/O while searching. */ static int pc_findentry(dp, namep, slotp) register struct pcnode *dp; /* parent directory */ char *namep; /* name to lookup */ struct slot *slotp; { register long offset; struct pcdir *ep; register int boff; int error; char fname[PCFNAMESIZE]; char fext[PCFEXTSIZE]; struct vnode * vp; vp = PCTOV(dp); PCFSDEBUG(7) printf("pc_findentry: looking for %s in dir 0x%x\n", namep, dp); slotp->sl_status = SL_NONE; if (!(dp->pc_entry.pcd_attr & PCA_DIR)) { return (ENOTDIR); } /* * Verify that the dp is still valid on the disk */ error = pc_verify(dp); if (error) return (error); error = pc_parsename(namep, fname, fext); if (error) return (error); slotp->sl_bp = NULL; for (offset = 0; /* */; ep++, offset += sizeof (struct pcdir)) { /* * If offset is on a block boundary, * read in the next directory block. * Release previous if it exists. */ boff = pc_blkoff(VFSTOPCFS(vp->v_vfsp), offset); if (boff == 0 || slotp->sl_bp == NULL || boff >= slotp->sl_bp->b_bcount) { if (slotp->sl_bp != NULL) { brelse(slotp->sl_bp); slotp->sl_bp = NULL; } error = pc_blkatoff(dp, offset, &slotp->sl_bp, &ep); if (error == ENOENT && slotp->sl_status == SL_NONE) { slotp->sl_status = SL_EXTEND; slotp->sl_offset = offset; } if (error) return (error); } if ((ep->pcd_filename[0] == PCD_UNUSED) || (ep->pcd_filename[0] == PCD_ERASED)) { /* * note empty slots, in case name is not found */ if (slotp->sl_status == SL_NONE) { slotp->sl_status = SL_FOUND; slotp->sl_blkno = slotp->sl_bp->b_blkno; slotp->sl_offset = boff; } /* * If unused we've hit the end of the directory */ if (ep->pcd_filename[0] == PCD_UNUSED) break; else continue; } /* * Hidden files do not participate in the search */ if (ep->pcd_attr & (PCA_HIDDEN | PCA_SYSTEM | PCA_LABEL)) continue; if ((bcmp(fname, ep->pcd_filename, PCFNAMESIZE) == 0) && (bcmp(fext, ep->pcd_ext, PCFEXTSIZE) == 0)) { /* * found the file */ if (fname[0] == '.') { if (fname[1] == '.') slotp->sl_flags = SL_DOTDOT; else slotp->sl_flags = SL_DOT; } else { slotp->sl_flags = 0; } slotp->sl_blkno = slotp->sl_bp->b_blkno; slotp->sl_offset = boff; slotp->sl_ep = ep; return (0); } } if (slotp->sl_bp != NULL) { brelse(slotp->sl_bp); slotp->sl_bp = NULL; } return (ENOENT); } /* * Obtain the block at offset "offset" in file pcp. */ int pc_blkatoff(pcp, offset, bpp, epp) register struct pcnode *pcp; register long offset; struct buf **bpp; struct pcdir **epp; { register struct pcfs *fsp; register struct buf *bp; int size; int error; daddr_t bn; fsp = VFSTOPCFS(PCTOV(pcp)->v_vfsp); size = pc_blksize(fsp, pcp, offset); if (pc_blkoff(fsp, offset) >= size) { return (ENOENT); } error = pc_bmap(pcp, pc_lblkno(fsp, offset), &bn, (daddr_t *)0); if (error) return (error); bp = bread(fsp->pcfs_devvp, bn, size); if (bp->b_flags & (B_ERROR | B_INVAL)) { PCFSDEBUG(1) printf("pc_blkatoff "); if (bp->b_flags & B_ERROR) pc_diskchanged(fsp); else brelse(bp); return (EIO); } bp->b_flags |= B_NOCACHE; /* don't cache */ if (epp) { *epp = (struct pcdir *)(bp->b_un.b_addr + pc_blkoff(fsp, offset)); } *bpp = bp; return (0); } /* * Parse user filename into the pc form of "filename.extension". * If names are too long for the format they are truncated silently. * Tests for characters that are invalid in PCDOS and converts to upper case. */ static int pc_parsename(namep, fnamep, fextp) register char *namep; register char *fnamep; register char *fextp; { register int n; register char c; n = PCFNAMESIZE; c = *namep++; if (c == 0) return (EINVAL); if (c == '.') { /* * check for "." and "..". */ *fnamep++ = c; n--; if (c = *namep++) { if ((c != '.') || (c = *namep)) /* ".x" or "..x" */ return (EINVAL); *fnamep++ = '.'; n--; } } else { /* * filename up to '.' */ do { if (n-- > 0) { c = toupper(c); if (!pc_validchar(c)) return (EINVAL); *fnamep++ = c; } } while ((c = *namep++) && c != '.'); } while (n-- > 0) { /* fill with blanks */ *fnamep++ = ' '; } /* * remainder is extension */ n = PCFEXTSIZE; if (c == '.') { while ((c = *namep++) && n--) { c = toupper(c); if (!pc_validchar(c)) return (EINVAL); *fextp++ = c; } } while (n-- > 0) { /* fill with blanks */ *fextp++ = ' '; } return (0); }