Home | History | Annotate | Download | only in pcfs
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 
     22 /*
     23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 #include <sys/param.h>
     28 #include <sys/errno.h>
     29 #include <sys/systm.h>
     30 #include <sys/sysmacros.h>
     31 #include <sys/buf.h>
     32 #include <sys/vfs.h>
     33 #include <sys/kmem.h>
     34 #include <sys/vnode.h>
     35 #include <sys/debug.h>
     36 #include <sys/cmn_err.h>
     37 #include <sys/sunddi.h>
     38 #include <sys/fs/pc_label.h>
     39 #include <sys/fs/pc_fs.h>
     40 #include <sys/fs/pc_dir.h>
     41 #include <sys/fs/pc_node.h>
     42 
     43 static int pc_makedirentry(struct pcnode *dp, struct pcdir *direntries,
     44     int	ndirentries, struct vattr *vap, offset_t offset);
     45 static int pc_dirempty(struct pcnode *);
     46 static int pc_findentry(struct pcnode *, char *, struct pcslot *, offset_t *);
     47 static int pc_parsename(char *, char *, char *);
     48 static int pc_remove_long_fn(struct pcnode *pcp,
     49     offset_t lfn_offset);
     50 static int generate_short_name(struct pcnode *dp, char *namep,
     51     struct pcdir *ep);
     52 static struct pcdir *pc_name_to_pcdir(struct pcnode *dp, char *namep,
     53     int ndirentries, int *errret);
     54 static offset_t pc_find_free_space(struct pcnode *pcp, int ndirentries);
     55 static int direntries_needed(struct pcnode *dp, char *namep);
     56 static int pc_is_short_file_name(char *namep, int foldcase);
     57 static int shortname_exists(struct pcnode *dp, char *fname, char *fext);
     58 static int pc_dirfixdotdot(struct pcnode *cdp, struct pcnode *opdp,
     59     struct pcnode *npdp);
     60 /*
     61  * Tunables
     62  */
     63 int enable_long_filenames = 1;
     64 
     65 /*
     66  * Lookup a name in a directory. Return a pointer to the pc_node
     67  * which represents the entry.
     68  */
     69 int
     70 pc_dirlook(
     71 	struct pcnode *dp,		/* parent directory */
     72 	char *namep,			/* name to lookup */
     73 	struct pcnode **pcpp)		/* result */
     74 {
     75 	struct vnode *vp;
     76 	struct pcslot slot;
     77 	int error;
     78 
     79 	PC_DPRINTF2(4, "pc_dirlook (dp %p name %s)\n", (void *)dp, namep);
     80 
     81 	if (!(dp->pc_entry.pcd_attr & PCA_DIR)) {
     82 		return (ENOTDIR);
     83 	}
     84 	vp = PCTOV(dp);
     85 	/*
     86 	 * check now for changed disk, before any return(0)
     87 	 */
     88 	if (error = pc_verify(VFSTOPCFS(vp->v_vfsp)))
     89 		return (error);
     90 
     91 	/*
     92 	 * Null component name is synonym for directory being searched.
     93 	 */
     94 	if (*namep == '\0') {
     95 		VN_HOLD(vp);
     96 		*pcpp = dp;
     97 		return (0);
     98 	}
     99 	/*
    100 	 * The root directory does not have "." and ".." entries,
    101 	 * so they are faked here.
    102 	 */
    103 	if (vp->v_flag & VROOT) {
    104 		if (bcmp(namep, ".", 2) == 0 || bcmp(namep, "..", 3) == 0) {
    105 			VN_HOLD(vp);
    106 			*pcpp = dp;
    107 			return (0);
    108 		}
    109 	}
    110 	error = pc_findentry(dp, namep, &slot, NULL);
    111 	if (error == 0) {
    112 		*pcpp = pc_getnode(VFSTOPCFS(vp->v_vfsp),
    113 		    slot.sl_blkno, slot.sl_offset, slot.sl_ep);
    114 		brelse(slot.sl_bp);
    115 		PC_DPRINTF1(4, "pc_dirlook: FOUND pcp=%p\n", (void *)*pcpp);
    116 	} else if (error == EINVAL) {
    117 		error = ENOENT;
    118 	}
    119 	return (error);
    120 }
    121 
    122 /*
    123  * Enter a name in a directory.
    124  */
    125 int
    126 pc_direnter(
    127 	struct pcnode *dp,		/* directory to make entry in */
    128 	char *namep,			/* name of entry */
    129 	struct vattr *vap,		/* attributes of new entry */
    130 	struct pcnode **pcpp)
    131 {
    132 	int error;
    133 	struct pcslot slot;
    134 	struct vnode *vp = PCTOV(dp);
    135 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
    136 	offset_t offset;
    137 	daddr_t	blkno;
    138 	int	boff;
    139 	struct buf *bp = NULL;
    140 	struct pcdir *ep;
    141 
    142 	PC_DPRINTF4(4, "pc_dirent(dp %p, name %s, vap %p, pcpp %p\n",
    143 	    (void *)dp, namep, (void *)vap, (void *)pcpp);
    144 
    145 	if (pcpp != NULL)
    146 		*pcpp = NULL;
    147 	/*
    148 	 * Leading spaces are not allowed in DOS.
    149 	 */
    150 	if (*namep == ' ')
    151 		return (EINVAL);
    152 	/*
    153 	 * If name is "." or "..", just look it up.
    154 	 */
    155 	if (PC_NAME_IS_DOT(namep) || PC_NAME_IS_DOTDOT(namep)) {
    156 		if (pcpp) {
    157 			error = pc_dirlook(dp, namep, pcpp);
    158 			if (error)
    159 				return (error);
    160 		}
    161 		return (EEXIST);
    162 	}
    163 	if (PCA_IS_HIDDEN(fsp, dp->pc_entry.pcd_attr)) {
    164 		return (EPERM);
    165 	}
    166 	/*
    167 	 * Make sure directory has not been removed while fs was unlocked.
    168 	 */
    169 	if (dp->pc_entry.pcd_filename[0] == PCD_ERASED) {
    170 		return (ENOENT);
    171 	}
    172 	error = pc_findentry(dp, namep, &slot, NULL);
    173 	if (error == 0) {
    174 		if (pcpp) {
    175 			*pcpp =
    176 			    pc_getnode(fsp, slot.sl_blkno, slot.sl_offset,
    177 			    slot.sl_ep);
    178 			error = EEXIST;
    179 		}
    180 		brelse(slot.sl_bp);
    181 	} else if (error == ENOENT) {
    182 		struct pcdir *direntries;
    183 		int	ndirentries;
    184 
    185 		/*
    186 		 * The entry does not exist. Check write permission in
    187 		 * directory to see if entry can be created.
    188 		 */
    189 		if (dp->pc_entry.pcd_attr & PCA_RDONLY) {
    190 			return (EPERM);
    191 		}
    192 		error = 0;
    193 		/*
    194 		 * Make sure there is a slot.
    195 		 */
    196 		if (slot.sl_status == SL_NONE)
    197 			panic("pc_direnter: no slot\n");
    198 		ndirentries = direntries_needed(dp, namep);
    199 		if (ndirentries == -1) {
    200 			return (EINVAL);
    201 		}
    202 
    203 		offset = pc_find_free_space(dp, ndirentries);
    204 		if (offset == -1) {
    205 			return (ENOSPC);
    206 		}
    207 
    208 		/*
    209 		 * Make an entry from the supplied attributes.
    210 		 */
    211 		direntries = pc_name_to_pcdir(dp, namep, ndirentries, &error);
    212 		if (direntries == NULL) {
    213 			return (error);
    214 		}
    215 		error = pc_makedirentry(dp, direntries, ndirentries, vap,
    216 		    offset);
    217 		kmem_free(direntries, ndirentries * sizeof (struct pcdir));
    218 		if (error) {
    219 			return (error);
    220 		}
    221 		offset += (ndirentries - 1)  * sizeof (struct pcdir);
    222 		boff = pc_blkoff(fsp, offset);
    223 		error = pc_blkatoff(dp, offset, &bp, &ep);
    224 		if (error) {
    225 			return (error);
    226 		}
    227 		blkno = pc_daddrdb(fsp, bp->b_blkno);
    228 		/*
    229 		 * Get a pcnode for the new entry.
    230 		 */
    231 		*pcpp = pc_getnode(fsp, blkno, boff, ep);
    232 		brelse(bp);
    233 		if (vap->va_type == VDIR)
    234 			(*pcpp)->pc_size = fsp->pcfs_clsize;
    235 
    236 		/*
    237 		 * Write out the new entry in the parent directory.
    238 		 */
    239 		error = pc_syncfat(fsp);
    240 		if (!error) {
    241 			error = pc_nodeupdate(*pcpp);
    242 		}
    243 	}
    244 	return (error);
    245 }
    246 
    247 /*
    248  * Template for "." and ".." directory entries.
    249  */
    250 static struct {
    251 	struct pcdir t_dot;		/* dot entry */
    252 	struct pcdir t_dotdot;		/* dotdot entry */
    253 } dirtemplate = {
    254 	{
    255 		".       ",
    256 		"   ",
    257 		PCA_DIR
    258 	},
    259 	{
    260 		"..      ",
    261 		"   ",
    262 		PCA_DIR
    263 	}
    264 };
    265 
    266 /*
    267  * Convert an attributes structure into the short filename entry
    268  * and write out the whole entry.
    269  */
    270 static int
    271 pc_makedirentry(struct pcnode *dp, struct pcdir *direntries,
    272     int ndirentries, struct vattr *vap, offset_t offset)
    273 {
    274 	struct vnode *vp = PCTOV(dp);
    275 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
    276 	int error;
    277 	struct pcdir *ep;
    278 	int	boff;
    279 	int	i;
    280 	struct buf *bp = NULL;
    281 	timestruc_t now;
    282 
    283 	if (vap != NULL && vap->va_mask & (AT_ATIME|AT_MTIME))
    284 		return (EOPNOTSUPP);
    285 
    286 	ep = &direntries[ndirentries - 1];
    287 	gethrestime(&now);
    288 	if (error = pc_tvtopct(&now, &ep->pcd_mtime))
    289 		return (error);
    290 
    291 	ep->pcd_crtime = ep->pcd_mtime;
    292 	ep->pcd_ladate = ep->pcd_mtime.pct_date;
    293 	ep->pcd_crtime_msec = 0;
    294 	ep->pcd_size = 0;
    295 	ep->pcd_attr = 0;
    296 	/*
    297 	 * Fields we don't use.
    298 	 */
    299 	ep->pcd_ntattr = 0;
    300 	if (!IS_FAT32(fsp))
    301 		ep->un.pcd_eattr = 0;
    302 
    303 	if (vap && ((vap->va_mode & 0222) == 0))
    304 		ep->pcd_attr |=  PCA_RDONLY;
    305 	if (vap && (vap->va_type == VDIR)) {
    306 		pc_cluster32_t cn;
    307 
    308 		ep->pcd_attr |= PCA_DIR;
    309 		/*
    310 		 * Make dot and dotdot entries for a new directory.
    311 		 */
    312 		cn = pc_alloccluster(fsp, 0);
    313 		switch (cn) {
    314 		case PCF_FREECLUSTER:
    315 			return (ENOSPC);
    316 		case PCF_ERRORCLUSTER:
    317 			return (EIO);
    318 		}
    319 		bp = ngeteblk(fsp->pcfs_clsize);
    320 		bp->b_edev = fsp->pcfs_xdev;
    321 		bp->b_dev = cmpdev(bp->b_edev);
    322 		bp->b_blkno = pc_cldaddr(fsp, cn);
    323 		clrbuf(bp);
    324 		pc_setstartcluster(fsp, ep, cn);
    325 		pc_setstartcluster(fsp, &dirtemplate.t_dot, cn);
    326 		cn = pc_getstartcluster(fsp, &dp->pc_entry);
    327 		pc_setstartcluster(fsp, &dirtemplate.t_dotdot, cn);
    328 		dirtemplate.t_dot.pcd_mtime =
    329 		    dirtemplate.t_dotdot.pcd_mtime = ep->pcd_mtime;
    330 		dirtemplate.t_dot.pcd_crtime =
    331 		    dirtemplate.t_dotdot.pcd_crtime = ep->pcd_crtime;
    332 		dirtemplate.t_dot.pcd_ladate =
    333 		    dirtemplate.t_dotdot.pcd_ladate = ep->pcd_ladate;
    334 		dirtemplate.t_dot.pcd_crtime_msec =
    335 		    dirtemplate.t_dotdot.pcd_crtime_msec = 0;
    336 		bcopy(&dirtemplate,
    337 		    bp->b_un.b_addr, sizeof (dirtemplate));
    338 		bwrite2(bp);
    339 		error = geterror(bp);
    340 		brelse(bp);
    341 		if (error) {
    342 			PC_DPRINTF0(1, "pc_makedirentry error");
    343 			pc_mark_irrecov(fsp);
    344 			return (EIO);
    345 		}
    346 	} else {
    347 		pc_setstartcluster(fsp, ep, 0);
    348 	}
    349 	bp = NULL;
    350 	for (i = 0, ep = NULL; i < ndirentries; i++, ep++) {
    351 		boff = pc_blkoff(fsp, offset);
    352 		if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
    353 			if (bp != NULL) {
    354 				/* always modified */
    355 				bwrite2(bp);
    356 				error = geterror(bp);
    357 				brelse(bp);
    358 				if (error)
    359 					return (error);
    360 				bp = NULL;
    361 			}
    362 			error = pc_blkatoff(dp, offset, &bp, &ep);
    363 			if (error)
    364 				return (error);
    365 		}
    366 
    367 		*ep = direntries[i];
    368 		offset += sizeof (struct pcdir);
    369 	}
    370 	if (bp != NULL) {
    371 		/* always modified */
    372 		bwrite2(bp);
    373 		error = geterror(bp);
    374 		brelse(bp);
    375 		if (error)
    376 			return (error);
    377 	}
    378 	return (0);
    379 }
    380 
    381 /*
    382  * Remove a name from a directory.
    383  */
    384 int
    385 pc_dirremove(
    386 	struct pcnode *dp,
    387 	char *namep,
    388 	struct vnode *cdir,
    389 	enum vtype type,
    390 	caller_context_t *ctp)
    391 {
    392 	struct pcslot slot;
    393 	struct pcnode *pcp;
    394 	int error;
    395 	struct vnode *vp = PCTOV(dp);
    396 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
    397 	offset_t lfn_offset = -1;
    398 
    399 	PC_DPRINTF2(4, "pc_dirremove (dp %p name %s)\n", (void *)dp, namep);
    400 	if ((dp->pc_entry.pcd_attr & PCA_RDONLY) ||
    401 	    PCA_IS_HIDDEN(fsp, dp->pc_entry.pcd_attr)) {
    402 		return (EPERM);
    403 	}
    404 	error = pc_findentry(dp, namep, &slot, &lfn_offset);
    405 	if (error)
    406 		return (error);
    407 	if (slot.sl_flags == SL_DOT) {
    408 		error = EINVAL;
    409 	} else if (slot.sl_flags == SL_DOTDOT) {
    410 		error = ENOTEMPTY;
    411 	} else {
    412 		pcp =
    413 		    pc_getnode(VFSTOPCFS(vp->v_vfsp),
    414 		    slot.sl_blkno, slot.sl_offset, slot.sl_ep);
    415 	}
    416 	if (error) {
    417 		brelse(slot.sl_bp);
    418 		return (error);
    419 	}
    420 	if (type == VDIR) {
    421 		if (pcp->pc_entry.pcd_attr & PCA_DIR) {
    422 			if (PCTOV(pcp) == cdir)
    423 				error = EINVAL;
    424 			else if (!pc_dirempty(pcp))
    425 				error = ENOTEMPTY;
    426 		} else {
    427 			error = ENOTDIR;
    428 		}
    429 	} else {
    430 		if (pcp->pc_entry.pcd_attr & PCA_DIR)
    431 			error = EISDIR;
    432 	}
    433 	if (error == 0) {
    434 		/*
    435 		 * Mark the in core node and on disk entry
    436 		 * as removed. The slot may then be reused.
    437 		 * The files clusters will be deallocated
    438 		 * when the last reference goes away.
    439 		 */
    440 		pcp->pc_eblkno = -1;
    441 		pcp->pc_entry.pcd_filename[0] = PCD_ERASED;
    442 		if (lfn_offset != -1) {
    443 			brelse(slot.sl_bp);
    444 			error = pc_remove_long_fn(dp, lfn_offset);
    445 			if (error) {
    446 				VN_RELE(PCTOV(pcp));
    447 				pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
    448 				return (EIO);
    449 			}
    450 		} else {
    451 			slot.sl_ep->pcd_filename[0] = PCD_ERASED;
    452 			bwrite2(slot.sl_bp);
    453 			error = geterror(slot.sl_bp);
    454 			brelse(slot.sl_bp);
    455 		}
    456 		if (error) {
    457 			VN_RELE(PCTOV(pcp));
    458 			pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
    459 			return (EIO);
    460 		} else if (type == VDIR) {
    461 			error = pc_truncate(pcp, 0L);
    462 		}
    463 
    464 	} else {
    465 		brelse(slot.sl_bp);
    466 	}
    467 
    468 	if (error == 0) {
    469 		if (type == VDIR) {
    470 			vnevent_rmdir(PCTOV(pcp), vp, namep, ctp);
    471 		} else {
    472 			vnevent_remove(PCTOV(pcp), vp, namep, ctp);
    473 		}
    474 	}
    475 
    476 	VN_RELE(PCTOV(pcp));
    477 
    478 	return (error);
    479 }
    480 
    481 /*
    482  * Determine whether a directory is empty.
    483  */
    484 static int
    485 pc_dirempty(struct pcnode *pcp)
    486 {
    487 	struct buf *bp;
    488 	struct pcdir *ep;
    489 	offset_t offset;
    490 	int boff;
    491 	char c;
    492 	int error;
    493 	struct vnode *vp;
    494 
    495 	vp = PCTOV(pcp);
    496 	bp = NULL;
    497 
    498 	offset = 0;
    499 	for (;;) {
    500 
    501 		/*
    502 		 * If offset is on a block boundary,
    503 		 * read in the next directory block.
    504 		 * Release previous if it exists.
    505 		 */
    506 		boff = pc_blkoff(VFSTOPCFS(vp->v_vfsp), offset);
    507 		if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
    508 			if (bp != NULL)
    509 				brelse(bp);
    510 			if (error = pc_blkatoff(pcp, offset, &bp, &ep)) {
    511 				return (error);
    512 			}
    513 		}
    514 		if (PCDL_IS_LFN(ep)) {
    515 			error = pc_extract_long_fn(pcp, NULL, &ep, &offset,
    516 			    &bp);
    517 			/*
    518 			 * EINVAL means the lfn was invalid, so start with
    519 			 * the next entry. Otherwise, an error occurred _or_
    520 			 * the lfn is valid, either of which means the
    521 			 * directory is not empty.
    522 			 */
    523 			if (error == EINVAL)
    524 				continue;
    525 			else {
    526 				if (bp)
    527 					brelse(bp);
    528 				return (error);
    529 			}
    530 		}
    531 		c = ep->pcd_filename[0];
    532 		if (c == PCD_UNUSED)
    533 			break;
    534 		if ((c != '.') && (c != PCD_ERASED)) {
    535 			brelse(bp);
    536 			return (0);
    537 		}
    538 		if ((c == '.') && !PC_SHORTNAME_IS_DOT(ep->pcd_filename) &&
    539 		    !PC_SHORTNAME_IS_DOTDOT(ep->pcd_filename)) {
    540 			brelse(bp);
    541 			return (0);
    542 		}
    543 		ep++;
    544 		offset += sizeof (struct pcdir);
    545 	}
    546 	if (bp != NULL)
    547 		brelse(bp);
    548 	return (1);
    549 }
    550 
    551 /*
    552  * Rename a file.
    553  */
    554 int
    555 pc_rename(
    556 	struct pcnode *dp,		/* parent directory */
    557 	struct pcnode *tdp,		/* target directory */
    558 	char *snm,			/* source file name */
    559 	char *tnm,			/* target file name */
    560 	caller_context_t *ctp)
    561 {
    562 	struct pcnode *pcp;	/* pcnode we are trying to rename */
    563 	struct pcnode *tpcp;	/* pcnode that's in our way */
    564 	struct pcslot slot;
    565 	int error;
    566 	struct vnode *vp = PCTOV(dp);
    567 	struct vnode *svp = NULL;
    568 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
    569 	int filecasechange = 0;
    570 	int oldisdir = 0;
    571 
    572 	PC_DPRINTF3(4, "pc_rename(0x%p, %s, %s)\n", (void *)dp, snm, tnm);
    573 	/*
    574 	 * Leading spaces are not allowed in DOS.
    575 	 */
    576 	if (*tnm == ' ')
    577 		return (EINVAL);
    578 	/*
    579 	 * No dot or dotdot.
    580 	 */
    581 	if (PC_NAME_IS_DOT(snm) || PC_NAME_IS_DOTDOT(snm) ||
    582 	    PC_NAME_IS_DOT(tnm) || PC_NAME_IS_DOTDOT(tnm))
    583 		return (EINVAL);
    584 	/*
    585 	 * Get the source node.  We'll jump back to here if trying to
    586 	 * move on top of an existing file, after deleting that file.
    587 	 */
    588 top:
    589 	error = pc_findentry(dp, snm, &slot, NULL);
    590 	if (error) {
    591 		return (error);
    592 	}
    593 	pcp = pc_getnode(VFSTOPCFS(vp->v_vfsp),
    594 	    slot.sl_blkno, slot.sl_offset, slot.sl_ep);
    595 
    596 	brelse(slot.sl_bp);
    597 
    598 	if (pcp)
    599 		svp = PCTOV(pcp);
    600 
    601 	/*
    602 	 * is the rename invalid, i.e. rename("a", "a/a")
    603 	 */
    604 	if (pcp == tdp) {
    605 		if (svp)
    606 			VN_RELE(svp);
    607 		return (EINVAL);
    608 	}
    609 
    610 	/*
    611 	 * Are we just changing the case of an existing name?
    612 	 */
    613 	if ((dp->pc_scluster == tdp->pc_scluster) &&
    614 	    (u8_strcmp(snm, tnm, 0, U8_STRCMP_CI_UPPER, U8_UNICODE_LATEST,
    615 	    &error) == 0)) {
    616 		filecasechange = 1;
    617 	}
    618 
    619 	/*
    620 	 * u8_strcmp detected an illegal character
    621 	 */
    622 	if (error)
    623 		return (EINVAL);
    624 
    625 	oldisdir = pcp->pc_entry.pcd_attr & PCA_DIR;
    626 
    627 	/*
    628 	 * see if the target exists
    629 	 */
    630 	error = pc_findentry(tdp, tnm, &slot, NULL);
    631 	if (error == 0 && filecasechange == 0) {
    632 		/*
    633 		 * Target exists.  If it's a file, delete it.  If it's
    634 		 * a directory, bail.
    635 		 */
    636 		int newisdir;
    637 
    638 		tpcp = pc_getnode(VFSTOPCFS(vp->v_vfsp),
    639 		    slot.sl_blkno, slot.sl_offset, slot.sl_ep);
    640 
    641 		newisdir = tpcp->pc_entry.pcd_attr & PCA_DIR;
    642 
    643 		brelse(slot.sl_bp);
    644 		vnevent_rename_dest(PCTOV(tpcp), PCTOV(tdp), tnm, ctp);
    645 		VN_RELE(PCTOV(tpcp));
    646 
    647 		/*
    648 		 * Error cases (from rename(2)):
    649 		 * old is dir, new is dir: EEXIST
    650 		 * old is dir, new is nondir: ENOTDIR
    651 		 * old is nondir, new is dir: EISDIR
    652 		 */
    653 		if (!newisdir) {
    654 			if (oldisdir) {
    655 				error = ENOTDIR;
    656 			} else {
    657 				/* nondir/nondir, remove target */
    658 				error = pc_dirremove(tdp, tnm,
    659 				    (struct vnode *)NULL, VREG, ctp);
    660 				if (error == 0) {
    661 					VN_RELE(PCTOV(pcp));
    662 					goto top;
    663 				}
    664 			}
    665 		} else if (oldisdir) {
    666 			/* dir/dir, remove target */
    667 			error = pc_dirremove(tdp, tnm,
    668 			    (struct vnode *)NULL, VDIR, ctp);
    669 			if (error == 0) {
    670 				VN_RELE(PCTOV(pcp));
    671 				goto top;
    672 			}
    673 			/* Follow rename(2)'s spec... */
    674 			if (error == ENOTEMPTY) {
    675 				error = EEXIST;
    676 			}
    677 		} else {
    678 			/* nondir/dir, bail */
    679 			error = EISDIR;
    680 		}
    681 	}
    682 
    683 	if ((error == 0) || (error == ENOENT)) {
    684 		offset_t lfn_offset = -1;
    685 		daddr_t	blkno;
    686 		struct pcdir *direntries;
    687 		struct pcdir *ep;
    688 		int	ndirentries;
    689 		pc_cluster16_t pct_lo;
    690 		pc_cluster16_t pct_hi;
    691 		offset_t offset;
    692 		int	boff;
    693 		struct buf *bp = NULL;
    694 		uchar_t	attr;
    695 		int	size;
    696 		struct pctime mtime;
    697 		struct pctime crtime;
    698 		uchar_t	ntattr;
    699 		ushort_t ladate;
    700 		ushort_t eattr;
    701 		uchar_t	crtime_msec;
    702 
    703 		/*
    704 		 * Rename the source.
    705 		 */
    706 		/*
    707 		 * Delete the old name, and create a new name.
    708 		 */
    709 		if (filecasechange == 1 && error == 0)
    710 			brelse(slot.sl_bp);
    711 		ndirentries = direntries_needed(tdp, tnm);
    712 		if (ndirentries == -1) {
    713 			VN_RELE(PCTOV(pcp));
    714 			return (EINVAL);
    715 		}
    716 		/*
    717 		 * first see if we have enough space to create the new
    718 		 * name before destroying the old one.
    719 		 */
    720 		offset = pc_find_free_space(tdp, ndirentries);
    721 		if (offset == -1) {
    722 			VN_RELE(PCTOV(pcp));
    723 			return (ENOSPC);
    724 		}
    725 
    726 		error = pc_findentry(dp, snm, &slot, &lfn_offset);
    727 		if (error) {
    728 			VN_RELE(PCTOV(pcp));
    729 			return (error);
    730 		}
    731 		pct_lo = slot.sl_ep->pcd_scluster_lo;
    732 		if (IS_FAT32(fsp))
    733 			pct_hi = slot.sl_ep->un.pcd_scluster_hi;
    734 		else
    735 			eattr = slot.sl_ep->un.pcd_eattr;
    736 		size = slot.sl_ep->pcd_size;
    737 		attr = slot.sl_ep->pcd_attr;
    738 		mtime = slot.sl_ep->pcd_mtime;
    739 		crtime = slot.sl_ep->pcd_crtime;
    740 		crtime_msec = slot.sl_ep->pcd_crtime_msec;
    741 		ntattr = slot.sl_ep->pcd_ntattr;
    742 		ladate = slot.sl_ep->pcd_ladate;
    743 
    744 		if (lfn_offset != -1) {
    745 			brelse(slot.sl_bp);
    746 			error = pc_remove_long_fn(dp, lfn_offset);
    747 			if (error) {
    748 				VN_RELE(PCTOV(pcp));
    749 				pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
    750 				return (error);
    751 			}
    752 		} else {
    753 			slot.sl_ep->pcd_filename[0] =
    754 			    pcp->pc_entry.pcd_filename[0] = PCD_ERASED;
    755 			bwrite2(slot.sl_bp);
    756 			error = geterror(slot.sl_bp);
    757 			brelse(slot.sl_bp);
    758 		}
    759 		if (error) {
    760 			VN_RELE(PCTOV(pcp));
    761 			pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
    762 			return (EIO);
    763 		}
    764 
    765 		/*
    766 		 * Make an entry from the supplied attributes.
    767 		 */
    768 		direntries = pc_name_to_pcdir(tdp, tnm, ndirentries, &error);
    769 		if (direntries == NULL) {
    770 			VN_RELE(PCTOV(pcp));
    771 			return (error);
    772 		}
    773 		error = pc_makedirentry(tdp, direntries, ndirentries, NULL,
    774 		    offset);
    775 		kmem_free(direntries, ndirentries * sizeof (struct pcdir));
    776 		if (error) {
    777 			VN_RELE(PCTOV(pcp));
    778 			return (error);
    779 		}
    780 		/* advance to short name */
    781 		offset += (ndirentries - 1)  * sizeof (struct pcdir);
    782 		boff = pc_blkoff(fsp, offset);
    783 		error = pc_blkatoff(tdp, offset, &bp, &ep);
    784 		if (error) {
    785 			VN_RELE(PCTOV(pcp));
    786 			return (error);
    787 		}
    788 		blkno = pc_daddrdb(fsp, bp->b_blkno);
    789 		ep->pcd_scluster_lo = pct_lo;
    790 		if (IS_FAT32(fsp))
    791 			ep->un.pcd_scluster_hi = pct_hi;
    792 		else
    793 			ep->un.pcd_eattr = eattr;
    794 		ep->pcd_size = size;
    795 		ep->pcd_attr = attr;
    796 		ep->pcd_mtime = mtime;
    797 		ep->pcd_crtime = crtime;
    798 		ep->pcd_crtime_msec = crtime_msec;
    799 		ep->pcd_ntattr = ntattr;
    800 		ep->pcd_ladate = ladate;
    801 		bwrite2(bp);
    802 		error = geterror(bp);
    803 		pcp->pc_eblkno = blkno;
    804 		pcp->pc_eoffset = boff;
    805 		pcp->pc_entry = *ep;
    806 		pcp->pc_flags |= PC_CHG;
    807 		brelse(bp);
    808 		if (error) {
    809 			VN_RELE(PCTOV(pcp));
    810 			pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
    811 			return (EIO);
    812 		}
    813 		/* No need to fix ".." if we're renaming within a dir */
    814 		if (oldisdir && dp != tdp) {
    815 			if ((error = pc_dirfixdotdot(pcp, dp, tdp)) != 0) {
    816 				VN_RELE(PCTOV(pcp));
    817 				return (error);
    818 			}
    819 		}
    820 		if ((error = pc_nodeupdate(pcp)) != 0) {
    821 			VN_RELE(PCTOV(pcp));
    822 			return (error);
    823 		}
    824 	}
    825 out:
    826 	vnevent_rename_src(PCTOV(pcp), PCTOV(dp), snm, ctp);
    827 	if (dp != tdp) {
    828 		vnevent_rename_dest_dir(PCTOV(tdp), ctp);
    829 	}
    830 
    831 	VN_RELE(PCTOV(pcp));
    832 
    833 	return (error);
    834 }
    835 
    836 /*
    837  * Fix the ".." entry of the child directory so that it points to the
    838  * new parent directory instead of the old one.
    839  */
    840 static int
    841 pc_dirfixdotdot(struct pcnode *dp,	/* child directory being moved */
    842 	struct pcnode *opdp,		/* old parent directory */
    843 	struct pcnode *npdp)		/* new parent directory */
    844 {
    845 	pc_cluster32_t cn;
    846 	struct vnode *vp = PCTOV(dp);
    847 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
    848 	int error = 0;
    849 	struct buf *bp = NULL;
    850 	struct pcdir *ep = NULL;
    851 	struct pcdir *tep = NULL;
    852 
    853 	/*
    854 	 * set the new child's ".." directory entry starting cluster to
    855 	 * point to the new parent's starting cluster
    856 	 */
    857 	ASSERT(opdp != npdp);
    858 	error = pc_blkatoff(dp, (offset_t)0, &bp, &ep);
    859 	if (error) {
    860 		PC_DPRINTF0(1, "pc_dirfixdotdot: error in blkatoff\n");
    861 		return (error);
    862 	}
    863 	tep = ep;
    864 	ep++;
    865 	if (!PC_SHORTNAME_IS_DOT(tep->pcd_filename) &&
    866 	    !PC_SHORTNAME_IS_DOTDOT(ep->pcd_filename)) {
    867 		PC_DPRINTF0(1, "pc_dirfixdotdot: mangled directory entry\n");
    868 		error = ENOTDIR;
    869 		return (error);
    870 	}
    871 	cn = pc_getstartcluster(fsp, &npdp->pc_entry);
    872 	pc_setstartcluster(fsp, ep, cn);
    873 
    874 	bwrite2(bp);
    875 	error = geterror(bp);
    876 	brelse(bp);
    877 	if (error) {
    878 		PC_DPRINTF0(1, "pc_dirfixdotdot: error in write\n");
    879 		pc_mark_irrecov(fsp);
    880 		return (EIO);
    881 	}
    882 	return (0);
    883 }
    884 
    885 
    886 /*
    887  * Search a directory for an entry.
    888  * The directory should be locked as this routine
    889  * will sleep on I/O while searching.
    890  */
    891 static int
    892 pc_findentry(
    893 	struct pcnode *dp,		/* parent directory */
    894 	char *namep,			/* name to lookup */
    895 	struct pcslot *slotp,
    896 	offset_t *lfn_offset)
    897 {
    898 	offset_t offset;
    899 	struct pcdir *ep = NULL;
    900 	int boff;
    901 	int error;
    902 	struct vnode *vp;
    903 	struct pcfs *fsp;
    904 
    905 	vp = PCTOV(dp);
    906 	PC_DPRINTF2(6, "pc_findentry: looking for %s in dir 0x%p\n", namep,
    907 	    (void *)dp);
    908 	slotp->sl_status = SL_NONE;
    909 	if (!(dp->pc_entry.pcd_attr & PCA_DIR)) {
    910 		return (ENOTDIR);
    911 	}
    912 	/*
    913 	 * Verify that the dp is still valid on the disk
    914 	 */
    915 	fsp = VFSTOPCFS(vp->v_vfsp);
    916 	error = pc_verify(fsp);
    917 	if (error)
    918 		return (error);
    919 
    920 	slotp->sl_bp = NULL;
    921 	offset = 0;
    922 	for (;;) {
    923 		/*
    924 		 * If offset is on a block boundary,
    925 		 * read in the next directory block.
    926 		 * Release previous if it exists.
    927 		 */
    928 		boff = pc_blkoff(fsp, offset);
    929 		if (boff == 0 || slotp->sl_bp == NULL ||
    930 		    boff >= slotp->sl_bp->b_bcount) {
    931 			if (slotp->sl_bp != NULL) {
    932 				brelse(slotp->sl_bp);
    933 				slotp->sl_bp = NULL;
    934 			}
    935 			error = pc_blkatoff(dp, offset, &slotp->sl_bp, &ep);
    936 			if (error == ENOENT && slotp->sl_status == SL_NONE) {
    937 				slotp->sl_status = SL_EXTEND;
    938 				slotp->sl_offset = (int)offset;
    939 			}
    940 			if (error)
    941 				return (error);
    942 		}
    943 		if ((ep->pcd_filename[0] == PCD_UNUSED) ||
    944 		    (ep->pcd_filename[0] == PCD_ERASED)) {
    945 			/*
    946 			 * note empty slots, in case name is not found
    947 			 */
    948 			if (slotp->sl_status == SL_NONE) {
    949 				slotp->sl_status = SL_FOUND;
    950 				slotp->sl_blkno = pc_daddrdb(fsp,
    951 				    slotp->sl_bp->b_blkno);
    952 				slotp->sl_offset = boff;
    953 			}
    954 			/*
    955 			 * If unused we've hit the end of the directory
    956 			 */
    957 			if (ep->pcd_filename[0] == PCD_UNUSED)
    958 				break;
    959 			offset += sizeof (struct pcdir);
    960 			ep++;
    961 			continue;
    962 		}
    963 		if (PCDL_IS_LFN(ep)) {
    964 			offset_t t = offset;
    965 			if (pc_match_long_fn(dp, namep, &ep,
    966 			    slotp, &offset) == 0) {
    967 				if (lfn_offset != NULL)
    968 					*lfn_offset = t;
    969 				return (0);
    970 			}
    971 			continue;
    972 		}
    973 		if (pc_match_short_fn(dp, namep, &ep, slotp, &offset) == 0)
    974 			return (0);
    975 	}
    976 	if (slotp->sl_bp != NULL) {
    977 		brelse(slotp->sl_bp);
    978 		slotp->sl_bp = NULL;
    979 	}
    980 	return (ENOENT);
    981 }
    982 
    983 /*
    984  * Obtain the block at offset "offset" in file pcp.
    985  */
    986 int
    987 pc_blkatoff(
    988 	struct pcnode *pcp,
    989 	offset_t offset,
    990 	struct buf **bpp,
    991 	struct pcdir **epp)
    992 {
    993 	struct pcfs *fsp;
    994 	struct buf *bp;
    995 	int size;
    996 	int error;
    997 	daddr_t bn;
    998 
    999 	fsp = VFSTOPCFS(PCTOV(pcp)->v_vfsp);
   1000 	size = pc_blksize(fsp, pcp, offset);
   1001 	if (pc_blkoff(fsp, offset) >= size) {
   1002 		PC_DPRINTF0(5, "pc_blkatoff: ENOENT\n");
   1003 		return (ENOENT);
   1004 	}
   1005 	error = pc_bmap(pcp, pc_lblkno(fsp, offset), &bn, (uint_t *)0);
   1006 	if (error)
   1007 		return (error);
   1008 
   1009 	bp = bread(fsp->pcfs_xdev, bn, size);
   1010 	if (bp->b_flags & B_ERROR) {
   1011 		PC_DPRINTF0(1, "pc_blkatoff: error\n");
   1012 		brelse(bp);
   1013 		pc_mark_irrecov(fsp);
   1014 		return (EIO);
   1015 	}
   1016 	if (epp) {
   1017 		*epp =
   1018 		    (struct pcdir *)(bp->b_un.b_addr + pc_blkoff(fsp, offset));
   1019 	}
   1020 	*bpp = bp;
   1021 	return (0);
   1022 }
   1023 
   1024 /*
   1025  * Parse user filename into the pc form of "filename.extension".
   1026  * If names are too long for the format (and enable_long_filenames is set)
   1027  * it returns EINVAL (since either this name was read from the disk (so
   1028  * it must fit), _or_ we're trying to match a long file name (so we
   1029  * should fail).  Tests for characters that are invalid in PCDOS and
   1030  * converts to upper case (unless foldcase is 0).
   1031  */
   1032 static int
   1033 pc_parsename(
   1034 	char *namep,
   1035 	char *fnamep,
   1036 	char *fextp)
   1037 {
   1038 	int n;
   1039 	char c;
   1040 
   1041 	n = PCFNAMESIZE;
   1042 	c = *namep++;
   1043 	if (c == 0)
   1044 		return (EINVAL);
   1045 	if (c == '.') {
   1046 		/*
   1047 		 * check for "." and "..".
   1048 		 */
   1049 		*fnamep++ = c;
   1050 		n--;
   1051 		if (c = *namep++) {
   1052 			if ((c != '.') || (c = *namep)) /* ".x" or "..x" */
   1053 				return (EINVAL);
   1054 			*fnamep++ = '.';
   1055 			n--;
   1056 		}
   1057 	} else {
   1058 		/*
   1059 		 * filename up to '.'
   1060 		 */
   1061 		do {
   1062 			if (n-- > 0) {
   1063 				c = toupper(c);
   1064 				if (!pc_validchar(c))
   1065 					return (EINVAL);
   1066 				*fnamep++ = c;
   1067 			} else {
   1068 				/* not short */
   1069 				if (enable_long_filenames)
   1070 					return (EINVAL);
   1071 			}
   1072 		} while ((c = *namep++) != '\0' && c != '.');
   1073 	}
   1074 	while (n-- > 0) {		/* fill with blanks */
   1075 		*fnamep++ = ' ';
   1076 	}
   1077 	/*
   1078 	 * remainder is extension
   1079 	 */
   1080 	n = PCFEXTSIZE;
   1081 	if (c == '.') {
   1082 		while ((c = *namep++) != '\0' && n--) {
   1083 			c = toupper(c);
   1084 			if (!pc_validchar(c))
   1085 				return (EINVAL);
   1086 			*fextp++ = c;
   1087 		}
   1088 		if (enable_long_filenames && (c != '\0')) {
   1089 			/* not short */
   1090 			return (EINVAL);
   1091 		}
   1092 	}
   1093 	while (n-- > 0) {		/* fill with blanks */
   1094 		*fextp++ = ' ';
   1095 	}
   1096 	return (0);
   1097 }
   1098 
   1099 /*
   1100  * Match a long filename entry with 'namep'. Also return failure
   1101  * if the long filename isn't valid.
   1102  */
   1103 int
   1104 pc_match_long_fn(struct pcnode *pcp, char *namep, struct pcdir **epp,
   1105     struct pcslot *slotp, offset_t *offset)
   1106 {
   1107 	struct pcdir *ep = (struct pcdir *)*epp;
   1108 	struct vnode *vp = PCTOV(pcp);
   1109 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
   1110 	int	error = 0;
   1111 	char	lfn[PCMAXNAMLEN+1];
   1112 
   1113 	error = pc_extract_long_fn(pcp, lfn, epp, offset, &slotp->sl_bp);
   1114 	if (error) {
   1115 		if (error == EINVAL) {
   1116 			return (ENOENT);
   1117 		} else
   1118 			return (error);
   1119 	}
   1120 	ep = *epp;
   1121 	if ((u8_strcmp(lfn, namep, 0, U8_STRCMP_CI_UPPER,
   1122 	    U8_UNICODE_LATEST, &error) == 0) && (error == 0)) {
   1123 		/* match */
   1124 		slotp->sl_flags = 0;
   1125 		slotp->sl_blkno = pc_daddrdb(fsp, slotp->sl_bp->b_blkno);
   1126 		slotp->sl_offset = pc_blkoff(fsp, *offset);
   1127 		slotp->sl_ep = ep;
   1128 		return (0);
   1129 	}
   1130 	*offset += sizeof (struct pcdir);
   1131 	ep++;
   1132 	*epp = ep;
   1133 	/* If u8_strcmp detected an error it's sufficient to rtn ENOENT */
   1134 	return (ENOENT);
   1135 }
   1136 
   1137 /*
   1138  * Match a short filename entry with namep.
   1139  */
   1140 int
   1141 pc_match_short_fn(struct pcnode *pcp, char *namep, struct pcdir **epp,
   1142     struct pcslot *slotp, offset_t *offset)
   1143 {
   1144 	char fname[PCFNAMESIZE];
   1145 	char fext[PCFEXTSIZE];
   1146 	struct pcdir *ep = *epp;
   1147 	int	error;
   1148 	struct vnode *vp = PCTOV(pcp);
   1149 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
   1150 	int boff = pc_blkoff(fsp, *offset);
   1151 
   1152 	if (PCA_IS_HIDDEN(fsp, ep->pcd_attr)) {
   1153 		*offset += sizeof (struct pcdir);
   1154 		ep++;
   1155 		*epp = ep;
   1156 		return (ENOENT);
   1157 	}
   1158 
   1159 	error = pc_parsename(namep, fname, fext);
   1160 	if (error) {
   1161 		*offset += sizeof (struct pcdir);
   1162 		ep++;
   1163 		*epp = ep;
   1164 		return (error);
   1165 	}
   1166 
   1167 	if ((bcmp(fname, ep->pcd_filename, PCFNAMESIZE) == 0) &&
   1168 	    (bcmp(fext, ep->pcd_ext, PCFEXTSIZE) == 0)) {
   1169 		/*
   1170 		 * found the file
   1171 		 */
   1172 		if (fname[0] == '.') {
   1173 			if (fname[1] == '.')
   1174 				slotp->sl_flags = SL_DOTDOT;
   1175 			else
   1176 				slotp->sl_flags = SL_DOT;
   1177 		} else {
   1178 			slotp->sl_flags = 0;
   1179 		}
   1180 		slotp->sl_blkno =
   1181 		    pc_daddrdb(fsp, slotp->sl_bp->b_blkno);
   1182 		slotp->sl_offset = boff;
   1183 		slotp->sl_ep = ep;
   1184 		return (0);
   1185 	}
   1186 	*offset += sizeof (struct pcdir);
   1187 	ep++;
   1188 	*epp = ep;
   1189 	return (ENOENT);
   1190 }
   1191 
   1192 /*
   1193  * Remove a long filename entry starting at lfn_offset. It must be
   1194  * a valid entry or we wouldn't have gotten here. Also remove the
   1195  * short filename entry.
   1196  */
   1197 static int
   1198 pc_remove_long_fn(struct pcnode *pcp, offset_t lfn_offset)
   1199 {
   1200 	struct vnode *vp = PCTOV(pcp);
   1201 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
   1202 	int boff;
   1203 	struct buf *bp = NULL;
   1204 	struct pcdir *ep = NULL;
   1205 	int	error = 0;
   1206 
   1207 	/*
   1208 	 * if we're in here, we know that the lfn is in the proper format
   1209 	 * of <series-of-lfn-entries> followed by <sfn-entry>
   1210 	 */
   1211 	for (;;) {
   1212 		boff = pc_blkoff(fsp, lfn_offset);
   1213 		if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
   1214 			if (bp != NULL) {
   1215 				bwrite2(bp);
   1216 				error = geterror(bp);
   1217 				brelse(bp);
   1218 				if (error)
   1219 					return (error);
   1220 				bp = NULL;
   1221 			}
   1222 			error = pc_blkatoff(pcp, lfn_offset, &bp, &ep);
   1223 			if (error)
   1224 				return (error);
   1225 		}
   1226 		if (!PCDL_IS_LFN(ep)) {
   1227 			/* done */
   1228 			break;
   1229 		}
   1230 		/* zap it */
   1231 		ep->pcd_filename[0] = PCD_ERASED;
   1232 		ep->pcd_attr = 0;
   1233 		lfn_offset += sizeof (struct pcdir);
   1234 		ep++;
   1235 	}
   1236 	/* now we're on the short entry */
   1237 
   1238 	ep->pcd_filename[0] = PCD_ERASED;
   1239 	ep->pcd_attr = 0;
   1240 
   1241 	if (bp != NULL) {
   1242 		bwrite2(bp);
   1243 		error = geterror(bp);
   1244 		brelse(bp);
   1245 		if (error)
   1246 			return (error);
   1247 	}
   1248 	return (0);
   1249 }
   1250 
   1251 /*
   1252  * Find (and allocate) space in the directory denoted by
   1253  * 'pcp'. for 'ndirentries' pcdir structures.
   1254  * Return the offset at which to start, or -1 for failure.
   1255  */
   1256 static offset_t
   1257 pc_find_free_space(struct pcnode *pcp, int ndirentries)
   1258 {
   1259 	offset_t offset = 0;
   1260 	offset_t spaceneeded = ndirentries * sizeof (struct pcdir);
   1261 	offset_t spaceoffset;
   1262 	offset_t spaceavail = 0;
   1263 	int boff;
   1264 	struct buf *bp = NULL;
   1265 	struct vnode *vp = PCTOV(pcp);
   1266 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
   1267 	struct pcdir *ep;
   1268 	int	error;
   1269 
   1270 	spaceoffset = offset;
   1271 	while (spaceneeded > spaceavail) {
   1272 		/*
   1273 		 * If offset is on a block boundary,
   1274 		 * read in the next directory block.
   1275 		 * Release previous if it exists.
   1276 		 */
   1277 		boff = pc_blkoff(fsp, offset);
   1278 		if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
   1279 			if (bp != NULL) {
   1280 				brelse(bp);
   1281 				bp = NULL;
   1282 			}
   1283 			error = pc_blkatoff(pcp, offset, &bp, &ep);
   1284 			if (error == ENOENT) {
   1285 				daddr_t bn;
   1286 
   1287 				/* extend directory */
   1288 				if (!IS_FAT32(fsp) && (vp->v_flag & VROOT))
   1289 					return (-1);
   1290 				while (spaceneeded > spaceavail) {
   1291 					error = pc_balloc(pcp,
   1292 					    pc_lblkno(fsp, offset), 1, &bn);
   1293 					if (error)
   1294 						return (-1);
   1295 					pcp->pc_size += fsp->pcfs_clsize;
   1296 					spaceavail += fsp->pcfs_clsize;
   1297 					offset += fsp->pcfs_clsize;
   1298 				}
   1299 				return (spaceoffset);
   1300 			}
   1301 			if (error)
   1302 				return (-1);
   1303 		}
   1304 		if ((ep->pcd_filename[0] == PCD_UNUSED) ||
   1305 		    (ep->pcd_filename[0] == PCD_ERASED)) {
   1306 			offset += sizeof (struct pcdir);
   1307 			spaceavail += sizeof (struct pcdir);
   1308 			ep++;
   1309 			continue;
   1310 		}
   1311 		offset += sizeof (struct pcdir);
   1312 		spaceavail = 0;
   1313 		spaceoffset = offset;
   1314 		ep++;
   1315 	}
   1316 	if (bp != NULL) {
   1317 		brelse(bp);
   1318 	}
   1319 	return (spaceoffset);
   1320 }
   1321 
   1322 /*
   1323  * Return how many long filename entries are needed.
   1324  * A maximum of PCLFNCHUNKSIZE characters per entry, plus one for a
   1325  * short filename.
   1326  */
   1327 static int
   1328 direntries_needed(struct pcnode *dp, char *namep)
   1329 {
   1330 	struct pcdir ep;
   1331 	uint16_t *w2_str;
   1332 	size_t  u8l, u16l;
   1333 	int ret;
   1334 
   1335 	if (enable_long_filenames == 0) {
   1336 		return (1);
   1337 	}
   1338 	if (pc_is_short_file_name(namep, 0)) {
   1339 		(void) pc_parsename(namep, ep.pcd_filename, ep.pcd_ext);
   1340 		if (!shortname_exists(dp, ep.pcd_filename, ep.pcd_ext)) {
   1341 			return (1);
   1342 		}
   1343 	}
   1344 	if (pc_valid_long_fn(namep, 1)) {
   1345 		/*
   1346 		 * convert to UTF-16 or UNICODE for calculating the entries
   1347 		 * needed. Conversion will consume at the most 512 bytes
   1348 		 */
   1349 		u16l = PCMAXNAMLEN + 1;
   1350 		w2_str = (uint16_t *)kmem_zalloc(PCMAXNAM_UTF16, KM_SLEEP);
   1351 		u8l = strlen(namep);
   1352 		ret = uconv_u8tou16((const uchar_t *)namep, &u8l,
   1353 		    w2_str, &u16l, UCONV_OUT_LITTLE_ENDIAN);
   1354 		kmem_free((caddr_t)w2_str, PCMAXNAM_UTF16);
   1355 		if (ret == 0) {
   1356 			ret = 1 + u16l / PCLFNCHUNKSIZE;
   1357 			if (u16l % PCLFNCHUNKSIZE != 0)
   1358 				ret++;
   1359 			return (ret);
   1360 		}
   1361 	}
   1362 	return (-1);
   1363 }
   1364 
   1365 /*
   1366  * Allocate and return an array of pcdir structures for the passed-in
   1367  * name. ndirentries tells how many are required (including the short
   1368  * filename entry). Just allocate and fill them in properly here so they
   1369  * can be written out.
   1370  */
   1371 static struct pcdir *
   1372 pc_name_to_pcdir(struct pcnode *dp, char *namep, int ndirentries, int *errret)
   1373 {
   1374 	struct pcdir *bpcdir;
   1375 	struct pcdir *ep;
   1376 	struct pcdir_lfn *lep;
   1377 	int	i;
   1378 	uchar_t	cksum;
   1379 	int	nchars;
   1380 	int	error = 0;
   1381 	char	*nameend;
   1382 	uint16_t *w2_str;
   1383 	size_t  u8l, u16l;
   1384 	int ret;
   1385 
   1386 	bpcdir = kmem_zalloc(ndirentries * sizeof (struct pcdir), KM_SLEEP);
   1387 	ep = &bpcdir[ndirentries - 1];
   1388 	if (ndirentries == 1) {
   1389 		(void) pc_parsename(namep, ep->pcd_filename, ep->pcd_ext);
   1390 		return (bpcdir);
   1391 	}
   1392 
   1393 	/* Here we need to convert to UTF-16 or UNICODE for writing */
   1394 
   1395 	u16l = PCMAXNAMLEN + 1;
   1396 	w2_str = (uint16_t *)kmem_zalloc(PCMAXNAM_UTF16, KM_SLEEP);
   1397 	u8l = strlen(namep);
   1398 	ret = uconv_u8tou16((const uchar_t *)namep, &u8l, w2_str, &u16l,
   1399 	    UCONV_OUT_LITTLE_ENDIAN);
   1400 	if (ret != 0) {
   1401 		kmem_free((caddr_t)w2_str, PCMAXNAM_UTF16);
   1402 		*errret = ret;
   1403 		return (NULL);
   1404 	}
   1405 	nameend = (char *)(w2_str + u16l);
   1406 	u16l %= PCLFNCHUNKSIZE;
   1407 	if (u16l != 0) {
   1408 		nchars = u16l + 1;
   1409 		nameend += 2;
   1410 	} else {
   1411 		nchars = PCLFNCHUNKSIZE;
   1412 	}
   1413 	nchars *= sizeof (uint16_t);
   1414 
   1415 	/* short file name */
   1416 	error = generate_short_name(dp, namep, ep);
   1417 	if (error) {
   1418 		kmem_free(bpcdir, ndirentries * sizeof (struct pcdir));
   1419 		*errret = error;
   1420 		return (NULL);
   1421 	}
   1422 	cksum = pc_checksum_long_fn(ep->pcd_filename, ep->pcd_ext);
   1423 	for (i = 0; i < (ndirentries - 1); i++) {
   1424 		/* long file name */
   1425 		nameend -= nchars;
   1426 		lep = (struct pcdir_lfn *)&bpcdir[i];
   1427 		set_long_fn_chunk(lep, nameend, nchars);
   1428 		lep->pcdl_attr = PCDL_LFN_BITS;
   1429 		lep->pcdl_checksum = cksum;
   1430 		lep->pcdl_ordinal = (uchar_t)(ndirentries - i - 1);
   1431 		nchars = PCLFNCHUNKSIZE * sizeof (uint16_t);
   1432 	}
   1433 	kmem_free((caddr_t)w2_str, PCMAXNAM_UTF16);
   1434 	lep = (struct pcdir_lfn *)&bpcdir[0];
   1435 	lep->pcdl_ordinal |= 0x40;
   1436 	return (bpcdir);
   1437 }
   1438 
   1439 static int
   1440 generate_short_name(struct pcnode *dp, char *namep, struct pcdir *inep)
   1441 {
   1442 	int	rev;
   1443 	int	nchars;
   1444 	int	i, j;
   1445 	char	*dot = NULL;
   1446 	char	fname[PCFNAMESIZE+1];
   1447 	char	fext[PCFEXTSIZE+1];
   1448 	char	scratch[8];
   1449 	int	error = 0;
   1450 	struct	pcslot slot;
   1451 	char	shortname[20];
   1452 	int	force_tilde = 0;
   1453 
   1454 	/*
   1455 	 * generate a unique short file name based on the long input name.
   1456 	 *
   1457 	 * Say, for "This is a very long filename.txt" generate
   1458 	 * "THISIS~1.TXT", or "THISIS~2.TXT" if that's already there.
   1459 	 * Skip invalid short name characters in the long name, plus
   1460 	 * a couple NT skips (space and reverse backslash).
   1461 	 *
   1462 	 * Unfortunately, since this name would be hidden by the normal
   1463 	 * lookup routine, we need to look for it ourselves. But luckily
   1464 	 * we don't need to look at the lfn entries themselves.
   1465 	 */
   1466 	force_tilde = !pc_is_short_file_name(namep, 1);
   1467 
   1468 	/*
   1469 	 * Strip off leading invalid characters.
   1470 	 * We need this because names like '.login' are now ok, but the
   1471 	 * short name needs to be something like LOGIN~1.
   1472 	 */
   1473 	for (; *namep != '\0'; namep++) {
   1474 		if (*namep == ' ')
   1475 			continue;
   1476 		if (!pc_validchar(*namep) && !pc_validchar(toupper(*namep)))
   1477 			continue;
   1478 		break;
   1479 	}
   1480 	dot = strrchr(namep, '.');
   1481 	if (dot != NULL) {
   1482 		dot++;
   1483 		for (j = 0, i = 0; j < PCFEXTSIZE; i++) {
   1484 			if (dot[i] == '\0')
   1485 				break;
   1486 			/* skip valid, but not generally good characters */
   1487 			if (dot[i] == ' ' || dot[i] == '\\')
   1488 				continue;
   1489 			if (pc_validchar(dot[i]))
   1490 				fext[j++] = dot[i];
   1491 			else if (pc_validchar(toupper(dot[i])))
   1492 				fext[j++] = toupper(dot[i]);
   1493 		}
   1494 		for (i = j; i < PCFEXTSIZE; i++)
   1495 			fext[i] = ' ';
   1496 		dot--;
   1497 	} else {
   1498 		for (i = 0; i < PCFEXTSIZE; i++) {
   1499 			fext[i] = ' ';
   1500 		}
   1501 	}
   1502 	/*
   1503 	 * We know we're a long name, not a short name (or we wouldn't
   1504 	 * be here at all. But if uppercasing ourselves would be a short
   1505 	 * name, then we can possibly avoid the ~N format.
   1506 	 */
   1507 	if (!force_tilde)
   1508 		rev = 0;
   1509 	else
   1510 		rev = 1;
   1511 	for (;;) {
   1512 		bzero(fname, sizeof (fname));
   1513 		nchars = PCFNAMESIZE;
   1514 		if (rev) {
   1515 			nchars--; /* ~ */
   1516 			i = rev;
   1517 			do {
   1518 				nchars--;
   1519 				i /= 10;
   1520 			} while (i);
   1521 			if (nchars <= 0) {
   1522 				return (ENOSPC);
   1523 			}
   1524 		}
   1525 		for (j = 0, i = 0; j < nchars; i++) {
   1526 			if ((&namep[i] == dot) || (namep[i] == '\0'))
   1527 				break;
   1528 			/* skip valid, but not generally good characters */
   1529 			if (namep[i] == ' ' || namep[i] == '\\')
   1530 				continue;
   1531 			if (pc_validchar(namep[i]))
   1532 				fname[j++] = namep[i];
   1533 			else if (pc_validchar(toupper(namep[i])))
   1534 				fname[j++] = toupper(namep[i]);
   1535 		}
   1536 		if (rev) {
   1537 			(void) sprintf(scratch, "~%d", rev);
   1538 			(void) strcat(fname, scratch);
   1539 		}
   1540 		for (i = strlen(fname); i < PCFNAMESIZE; i++)
   1541 			fname[i] = ' ';
   1542 		/* now see if it exists */
   1543 		(void) pc_fname_ext_to_name(shortname, fname, fext, 0);
   1544 		error = pc_findentry(dp, shortname, &slot, NULL);
   1545 		if (error == 0) {
   1546 			/* found it */
   1547 			brelse(slot.sl_bp);
   1548 			rev++;
   1549 			continue;
   1550 		}
   1551 		if (!shortname_exists(dp, fname, fext))
   1552 			break;
   1553 		rev++;
   1554 	}
   1555 	(void) strncpy(inep->pcd_filename, fname, PCFNAMESIZE);
   1556 	(void) strncpy(inep->pcd_ext, fext, PCFEXTSIZE);
   1557 	return (0);
   1558 }
   1559 
   1560 /*
   1561  * Returns 1 if the passed-in filename is a short name, 0 if not.
   1562  */
   1563 static int
   1564 pc_is_short_file_name(char *namep, int foldcase)
   1565 {
   1566 	int	i;
   1567 	char	c;
   1568 
   1569 	for (i = 0; i < PCFNAMESIZE; i++, namep++) {
   1570 		if (*namep == '\0')
   1571 			return (1);
   1572 		if (*namep == '.')
   1573 			break;
   1574 		if (foldcase)
   1575 			c = toupper(*namep);
   1576 		else
   1577 			c = *namep;
   1578 		if (!pc_validchar(c))
   1579 			return (0);
   1580 	}
   1581 	if (*namep == '\0')
   1582 		return (1);
   1583 	if (*namep != '.')
   1584 		return (0);
   1585 	namep++;
   1586 	for (i = 0; i < PCFEXTSIZE; i++, namep++) {
   1587 		if (*namep == '\0')
   1588 			return (1);
   1589 		if (foldcase)
   1590 			c = toupper(*namep);
   1591 		else
   1592 			c = *namep;
   1593 		if (!pc_validchar(c))
   1594 			return (0);
   1595 	}
   1596 	/* we should be done. If not... */
   1597 	if (*namep == '\0')
   1598 		return (1);
   1599 	return (0);
   1600 
   1601 }
   1602 
   1603 /*
   1604  * We call this when we want to see if a short filename already exists
   1605  * in the filesystem as part of a long filename. When creating a short
   1606  * name (FILENAME.TXT from the user, or when generating one for a long
   1607  * filename), we cannot allow one that is part of a long filename.
   1608  * pc_findentry will find all the names that are visible (long or short),
   1609  * but will not crack any long filename entries.
   1610  */
   1611 static int
   1612 shortname_exists(struct pcnode *dp, char *fname, char *fext)
   1613 {
   1614 	struct buf *bp = NULL;
   1615 	int	offset = 0;
   1616 	int	match = 0;
   1617 	struct pcdir *ep;
   1618 	struct vnode *vp = PCTOV(dp);
   1619 	struct pcfs *fsp = VFSTOPCFS(vp->v_vfsp);
   1620 	int	boff;
   1621 	int	error = 0;
   1622 
   1623 	for (;;) {
   1624 		boff = pc_blkoff(fsp, offset);
   1625 		if (boff == 0 || bp == NULL || boff >= bp->b_bcount) {
   1626 			if (bp != NULL) {
   1627 				brelse(bp);
   1628 				bp = NULL;
   1629 			}
   1630 			error = pc_blkatoff(dp, offset, &bp, &ep);
   1631 			if (error == ENOENT)
   1632 				break;
   1633 			if (error) {
   1634 				return (1);
   1635 			}
   1636 		}
   1637 		if (PCDL_IS_LFN(ep) ||
   1638 		    (ep->pcd_filename[0] == PCD_ERASED)) {
   1639 			offset += sizeof (struct pcdir);
   1640 			ep++;
   1641 			continue;
   1642 		}
   1643 		if (ep->pcd_filename[0] == PCD_UNUSED)
   1644 			break;
   1645 		/*
   1646 		 * in use, and a short file name (either standalone
   1647 		 * or associated with a long name
   1648 		 */
   1649 		if ((bcmp(fname, ep->pcd_filename, PCFNAMESIZE) == 0) &&
   1650 		    (bcmp(fext, ep->pcd_ext, PCFEXTSIZE) == 0)) {
   1651 			match = 1;
   1652 			break;
   1653 		}
   1654 		offset += sizeof (struct pcdir);
   1655 		ep++;
   1656 	}
   1657 	if (bp) {
   1658 		brelse(bp);
   1659 		bp = NULL;
   1660 	}
   1661 	return (match);
   1662 }
   1663 
   1664 pc_cluster32_t
   1665 pc_getstartcluster(struct pcfs *fsp, struct pcdir *ep)
   1666 {
   1667 	if (IS_FAT32(fsp)) {
   1668 		pc_cluster32_t cn;
   1669 		pc_cluster16_t hi16;
   1670 		pc_cluster16_t lo16;
   1671 
   1672 		hi16 = ltohs(ep->un.pcd_scluster_hi);
   1673 		lo16 = ltohs(ep->pcd_scluster_lo);
   1674 		cn = (hi16 << 16) | lo16;
   1675 		return (cn);
   1676 	} else {
   1677 		return (ltohs(ep->pcd_scluster_lo));
   1678 	}
   1679 }
   1680 
   1681 void
   1682 pc_setstartcluster(struct pcfs *fsp, struct pcdir *ep, pc_cluster32_t cln)
   1683 {
   1684 	if (IS_FAT32(fsp)) {
   1685 		pc_cluster16_t hi16;
   1686 		pc_cluster16_t lo16;
   1687 
   1688 		hi16 = (cln >> 16) & 0xFFFF;
   1689 		lo16 = cln & 0xFFFF;
   1690 		ep->un.pcd_scluster_hi = htols(hi16);
   1691 		ep->pcd_scluster_lo = htols(lo16);
   1692 	} else {
   1693 		pc_cluster16_t cln16;
   1694 
   1695 		cln16 = (pc_cluster16_t)cln;
   1696 		ep->pcd_scluster_lo = htols(cln16);
   1697 	}
   1698 }
   1699