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