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