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