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