xref: /illumos-gate/usr/src/uts/common/fs/pcfs/pc_dir.c (revision 257873cfc1dd3337766407f80397db60a56f2f5a)
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