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