xref: /illumos-gate/usr/src/boot/libsa/dosfs.c (revision afa937a6c8732de9a6e3c2830b45f93829ff5aff)
1 /*
2  * Copyright (c) 1996, 1998 Robert Nordier
3  * Copyright 2024 MNX Cloud, Inc.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in
13  *    the documentation and/or other materials provided with the
14  *    distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
17  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
22  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
24  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
26  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 
31 /*
32  * Readonly filesystem for Microsoft FAT12/FAT16/FAT32 filesystems,
33  * also supports VFAT.
34  */
35 
36 #include <sys/types.h>
37 #include <string.h>
38 #include <stddef.h>
39 
40 #include "stand.h"
41 
42 #include "dosfs.h"
43 
44 
45 static int dos_open(const char *, struct open_file *);
46 static int dos_close(struct open_file *);
47 static int dos_read(struct open_file *, void *, size_t, size_t *);
48 static off_t dos_seek(struct open_file *, off_t offset, int);
49 static int dos_stat(struct open_file *, struct stat *);
50 static int dos_readdir(struct open_file *, struct dirent *);
51 
52 struct fs_ops dosfs_fsops = {
53 	.fs_name = "dosfs",
54 	.fo_open = dos_open,
55 	.fo_close = dos_close,
56 	.fo_read = dos_read,
57 	.fo_write = null_write,
58 	.fo_seek = dos_seek,
59 	.fo_stat = dos_stat,
60 	.fo_readdir = dos_readdir
61 };
62 
63 #define	SECSIZ	512		/* sector size */
64 #define	SSHIFT	9		/* SECSIZ shift */
65 #define	DEPSEC	16		/* directory entries per sector */
66 #define	DSHIFT	4		/* DEPSEC shift */
67 #define	LOCLUS	2		/* lowest cluster number */
68 #define	FATBLKSZ	0x20000	/* size of block in the FAT cache buffer */
69 
70 /* DOS "BIOS Parameter Block" */
71 typedef struct {
72 	uchar_t secsiz[2];	/* sector size */
73 	uchar_t spc;		/* sectors per cluster */
74 	uchar_t ressec[2];	/* reserved sectors */
75 	uchar_t fats;		/* FATs */
76 	uchar_t dirents[2];	/* root directory entries */
77 	uchar_t secs[2];	/* total sectors */
78 	uchar_t media;		/* media descriptor */
79 	uchar_t spf[2];		/* sectors per FAT */
80 	uchar_t spt[2];		/* sectors per track */
81 	uchar_t heads[2];	/* drive heads */
82 	uchar_t hidsec[4];	/* hidden sectors */
83 	uchar_t lsecs[4];	/* huge sectors */
84 	uchar_t lspf[4];	/* huge sectors per FAT */
85 	uchar_t xflg[2];	/* flags */
86 	uchar_t vers[2];	/* filesystem version */
87 	uchar_t rdcl[4];	/* root directory start cluster */
88 	uchar_t infs[2];	/* filesystem info sector */
89 	uchar_t bkbs[2];	/* backup boot sector */
90 } DOS_BPB;
91 
92 /* Initial portion of DOS boot sector */
93 typedef struct {
94 	uchar_t jmp[3];		/* usually 80x86 'jmp' opcode */
95 	uchar_t oem[8];		/* OEM name and version */
96 	DOS_BPB bpb;		/* BPB */
97 } DOS_BS;
98 
99 /* Supply missing "." and ".." root directory entries */
100 static const char *const dotstr[2] = {".", ".."};
101 static DOS_DE dot[2] = {
102 	{".       ", "   ", FA_DIR, {0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
103 	{0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}},
104 	{"..      ", "   ", FA_DIR, {0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
105 	{0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}}
106 };
107 
108 /* The usual conversion macros to avoid multiplication and division */
109 #define	bytsec(n)	((n) >> SSHIFT)
110 #define	secbyt(s)	((s) << SSHIFT)
111 #define	entsec(e)	((e) >> DSHIFT)
112 #define	bytblk(fs, n)	((n) >> (fs)->bshift)
113 #define	blkbyt(fs, b)	((b) << (fs)->bshift)
114 #define	secblk(fs, s)	((s) >> ((fs)->bshift - SSHIFT))
115 #define	blksec(fs, b)	((b) << ((fs)->bshift - SSHIFT))
116 
117 /* Convert cluster number to offset within filesystem */
118 #define	blkoff(fs, b)	(secbyt((fs)->lsndta) + blkbyt(fs, (b) - LOCLUS))
119 
120 /* Convert cluster number to logical sector number */
121 #define	blklsn(fs, b)	((fs)->lsndta + blksec(fs, (b) - LOCLUS))
122 
123 /* Convert cluster number to offset within FAT */
124 #define	fatoff(sz, c)	((sz) == 12 ? (c) + ((c) >> 1) :  \
125 			(sz) == 16 ? (c) << 1 :          \
126 			(c) << 2)
127 
128 /* Does cluster number reference a valid data cluster? */
129 #define	okclus(fs, c)	((c) >= LOCLUS && (c) <= (fs)->xclus)
130 
131 /* Get start cluster from directory entry */
132 #define	stclus(sz, de)	((sz) != 32 ? (uint_t)cv2((de)->clus) :          \
133 			((uint_t)cv2((de)->dex.h_clus) << 16) |  \
134 			cv2((de)->clus))
135 
136 static int parsebs(DOS_FS *, DOS_BS *);
137 static int namede(DOS_FS *, const char *, DOS_DE **);
138 static int lookup(DOS_FS *, uint_t, const char *, DOS_DE **);
139 static void cp_xdnm(uchar_t *, DOS_XDE *);
140 static void cp_sfn(uchar_t *, DOS_DE *);
141 static off_t fsize(DOS_FS *, DOS_DE *);
142 static int fatcnt(DOS_FS *, uint_t);
143 static int fatget(DOS_FS *, uint_t *);
144 static int fatend(uint_t, uint_t);
145 static int ioread(DOS_FS *, uint64_t, void *, size_t);
146 static int ioget(struct open_file *, daddr_t, void *, size_t);
147 
148 static int
149 dos_read_fatblk(DOS_FS *fs, struct open_file *fd, uint_t blknum)
150 {
151 	int err;
152 	size_t io_size;
153 	daddr_t offset_in_fat, max_offset_in_fat;
154 
155 	offset_in_fat = ((daddr_t)blknum) * FATBLKSZ;
156 	max_offset_in_fat = secbyt((daddr_t)fs->spf);
157 	io_size = FATBLKSZ;
158 	if (offset_in_fat > max_offset_in_fat)
159 		offset_in_fat = max_offset_in_fat;
160 	if (offset_in_fat + io_size > max_offset_in_fat)
161 		io_size = ((size_t)(max_offset_in_fat - offset_in_fat));
162 
163 	if (io_size != 0) {
164 		err = ioget(fd, fs->lsnfat + bytsec(offset_in_fat),
165 		    fs->fatbuf, io_size);
166 		if (err != 0) {
167 			fs->fatbuf_blknum = ((uint_t)(-1));
168 			return (err);
169 		}
170 	}
171 
172 	if (io_size < FATBLKSZ)
173 		memset(fs->fatbuf + io_size, 0, FATBLKSZ - io_size);
174 
175 	fs->fatbuf_blknum = blknum;
176 	return (0);
177 }
178 
179 /*
180  * Mount DOS filesystem
181  */
182 static int
183 dos_mount(DOS_FS *fs, struct open_file *fd)
184 {
185 	int err;
186 	uchar_t *buf;
187 
188 	bzero(fs, sizeof (DOS_FS));
189 	fs->fd = fd;
190 
191 	if ((buf = malloc(secbyt(1))) == NULL)
192 		return (errno);
193 	if ((err = ioget(fs->fd, 0, buf, secbyt(1))) ||
194 	    (err = parsebs(fs, (DOS_BS *)buf))) {
195 		free(buf);
196 		return (err);
197 	}
198 	free(buf);
199 
200 	if ((fs->fatbuf = malloc(FATBLKSZ)) == NULL)
201 		return (errno);
202 	err = dos_read_fatblk(fs, fd, 0);
203 	if (err != 0) {
204 		free(fs->fatbuf);
205 		return (err);
206 	}
207 
208 	fs->root = dot[0];
209 	fs->root.name[0] = ' ';
210 	if (fs->fatsz == 32) {
211 		fs->root.clus[0] = fs->rdcl & 0xff;
212 		fs->root.clus[1] = (fs->rdcl >> 8) & 0xff;
213 		fs->root.dex.h_clus[0] = (fs->rdcl >> 16) & 0xff;
214 		fs->root.dex.h_clus[1] = (fs->rdcl >> 24) & 0xff;
215 	}
216 	return (0);
217 }
218 
219 /*
220  * Unmount mounted filesystem
221  */
222 static int
223 dos_unmount(DOS_FS *fs)
224 {
225 	if (fs->links)
226 		return (EBUSY);
227 	free(fs->fatbuf);
228 	free(fs);
229 	return (0);
230 }
231 
232 /*
233  * Open DOS file
234  */
235 static int
236 dos_open(const char *path, struct open_file *fd)
237 {
238 	DOS_DE *de;
239 	DOS_FILE *f;
240 	DOS_FS *fs;
241 	uint_t size, clus;
242 	int err;
243 
244 	/* Allocate mount structure, associate with open */
245 	if ((fs = malloc(sizeof (DOS_FS))) == NULL)
246 		return (errno);
247 	if ((err = dos_mount(fs, fd))) {
248 		free(fs);
249 		return (err);
250 	}
251 
252 	if ((err = namede(fs, path, &de))) {
253 		dos_unmount(fs);
254 		return (err);
255 	}
256 
257 	clus = stclus(fs->fatsz, de);
258 	size = cv4(de->size);
259 
260 	if ((!(de->attr & FA_DIR) && (!clus != !size)) ||
261 	    ((de->attr & FA_DIR) && size) ||
262 	    (clus && !okclus(fs, clus))) {
263 		dos_unmount(fs);
264 		return (EINVAL);
265 	}
266 	if ((f = malloc(sizeof (DOS_FILE))) == NULL) {
267 		err = errno;
268 		dos_unmount(fs);
269 		return (err);
270 	}
271 	bzero(f, sizeof (DOS_FILE));
272 	f->fs = fs;
273 	fs->links++;
274 	f->de = *de;
275 	fd->f_fsdata = f;
276 	return (0);
277 }
278 
279 /*
280  * Read from file
281  */
282 static int
283 dos_read(struct open_file *fd, void *buf, size_t nbyte, size_t *resid)
284 {
285 	off_t size;
286 	uint64_t off;
287 	size_t nb;
288 	uint_t clus, c, cnt, n;
289 	DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
290 	int err = 0;
291 
292 	/*
293 	 * as ioget() can be called *a lot*, use twiddle here.
294 	 * also 4 seems to be good value not to slow loading down too much:
295 	 * with 270MB file (~540k ioget() calls, twiddle can easily waste
296 	 * 4-5 sec.
297 	 */
298 	twiddle(4);
299 	nb = nbyte;
300 	if ((size = fsize(f->fs, &f->de)) == -1)
301 		return (EINVAL);
302 	if (nb > (n = size - f->offset))
303 		nb = n;
304 	off = f->offset;
305 	if ((clus = stclus(f->fs->fatsz, &f->de)))
306 		off &= f->fs->bsize - 1;
307 	c = f->c;
308 	cnt = nb;
309 	while (cnt) {
310 		n = 0;
311 		if (!c) {
312 			if ((c = clus))
313 				n = bytblk(f->fs, f->offset);
314 		} else if (!off)
315 			n++;
316 		while (n--) {
317 			if ((err = fatget(f->fs, &c)))
318 				goto out;
319 			if (!okclus(f->fs, c)) {
320 				err = EINVAL;
321 				goto out;
322 			}
323 		}
324 		if (!clus || (n = f->fs->bsize - off) > cnt)
325 			n = cnt;
326 		if (c != 0)
327 			off += blkoff(f->fs, (uint64_t)c);
328 		else
329 			off += secbyt(f->fs->lsndir);
330 		err = ioread(f->fs, off, buf, n);
331 		if (err != 0)
332 			goto out;
333 		f->offset += n;
334 		f->c = c;
335 		off = 0;
336 		buf = (char *)buf + n;
337 		cnt -= n;
338 	}
339 out:
340 	if (resid)
341 		*resid = nbyte - nb + cnt;
342 	return (err);
343 }
344 
345 /*
346  * Reposition within file
347  */
348 static off_t
349 dos_seek(struct open_file *fd, off_t offset, int whence)
350 {
351 	off_t off;
352 	uint_t size;
353 	DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
354 
355 	size = cv4(f->de.size);
356 	switch (whence) {
357 	case SEEK_SET:
358 		off = 0;
359 		break;
360 	case SEEK_CUR:
361 		off = f->offset;
362 		break;
363 	case SEEK_END:
364 		off = size;
365 		break;
366 	default:
367 		errno = EINVAL;
368 		return (-1);
369 	}
370 	off += offset;
371 	if (off < 0 || off > size) {
372 		errno = EINVAL;
373 		return (-1);
374 	}
375 	f->offset = (uint_t)off;
376 	f->c = 0;
377 	return (off);
378 }
379 
380 /*
381  * Close open file
382  */
383 static int
384 dos_close(struct open_file *fd)
385 {
386 	DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
387 	DOS_FS *fs = f->fs;
388 
389 	f->fs->links--;
390 	free(f);
391 	dos_unmount(fs);
392 	return (0);
393 }
394 
395 /*
396  * Return some stat information on a file.
397  */
398 static int
399 dos_stat(struct open_file *fd, struct stat *sb)
400 {
401 	DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
402 
403 	/* only important stuff */
404 	sb->st_mode = f->de.attr & FA_DIR ? S_IFDIR | 0555 : S_IFREG | 0444;
405 	sb->st_nlink = 1;
406 	sb->st_uid = 0;
407 	sb->st_gid = 0;
408 	if ((sb->st_size = fsize(f->fs, &f->de)) == -1)
409 		return (EINVAL);
410 	return (0);
411 }
412 
413 static int
414 dos_checksum(unsigned char *name, unsigned char *ext)
415 {
416 	int x, i;
417 	char buf[11];
418 
419 	bcopy(name, buf, 8);
420 	bcopy(ext, buf+8, 3);
421 	x = 0;
422 	for (i = 0; i < 11; i++) {
423 		x = ((x & 1) << 7) | (x >> 1);
424 		x += buf[i];
425 		x &= 0xff;
426 	}
427 	return (x);
428 }
429 
430 static int
431 dos_readdir(struct open_file *fd, struct dirent *d)
432 {
433 	uchar_t fn[261];
434 	DOS_DIR dd;
435 	size_t res;
436 	uint_t chk, x, xdn;
437 	int err;
438 
439 	x = chk = 0;
440 	while (1) {
441 		xdn = x;
442 		x = 0;
443 		err = dos_read(fd, &dd, sizeof (dd), &res);
444 		if (err)
445 			return (err);
446 		if (res == sizeof (dd))
447 			return (ENOENT);
448 		if (dd.de.name[0] == 0)
449 			return (ENOENT);
450 
451 		/* Skip deleted entries */
452 		if (dd.de.name[0] == 0xe5)
453 			continue;
454 
455 		/* Check if directory entry is volume label */
456 		if (dd.de.attr & FA_LABEL) {
457 			/*
458 			 * If volume label set, check if the current entry is
459 			 * extended entry (FA_XDE) for long file names.
460 			 */
461 			if ((dd.de.attr & FA_MASK) == FA_XDE) {
462 				/*
463 				 * Read through all following extended entries
464 				 * to get the long file name. 0x40 marks the
465 				 * last entry containing part of long file name.
466 				 */
467 				if (dd.xde.seq & 0x40)
468 					chk = dd.xde.chk;
469 				else if (dd.xde.seq != xdn - 1 ||
470 				    dd.xde.chk != chk)
471 					continue;
472 				x = dd.xde.seq & ~0x40;
473 				if (x < 1 || x > 20) {
474 					x = 0;
475 					continue;
476 				}
477 				cp_xdnm(fn, &dd.xde);
478 			} else {
479 				/* skip only volume label entries */
480 				continue;
481 			}
482 		} else {
483 			if (xdn == 1) {
484 				x = dos_checksum(dd.de.name, dd.de.ext);
485 				if (x == chk)
486 					break;
487 			} else {
488 				cp_sfn(fn, &dd.de);
489 				break;
490 			}
491 			x = 0;
492 		}
493 	}
494 
495 	d->d_fileno = (dd.de.clus[1] << 8) + dd.de.clus[0];
496 	d->d_reclen = sizeof (*d);
497 	d->d_type = (dd.de.attr & FA_DIR) ? DT_DIR : DT_REG;
498 	memcpy(d->d_name, fn, sizeof (d->d_name));
499 	return (0);
500 }
501 
502 /*
503  * Parse DOS boot sector
504  */
505 static int
506 parsebs(DOS_FS *fs, DOS_BS *bs)
507 {
508 	uint_t sc;
509 
510 	if ((bs->jmp[0] != 0x69 &&
511 	    bs->jmp[0] != 0xe9 &&
512 	    (bs->jmp[0] != 0xeb || bs->jmp[2] != 0x90)) ||
513 	    bs->bpb.media < 0xf0)
514 		return (EINVAL);
515 	if (cv2(bs->bpb.secsiz) != SECSIZ)
516 		return (EINVAL);
517 	if (!(fs->spc = bs->bpb.spc) || fs->spc & (fs->spc - 1))
518 		return (EINVAL);
519 	fs->bsize = secbyt(fs->spc);
520 	fs->bshift = ffs(fs->bsize) - 1;
521 	if ((fs->spf = cv2(bs->bpb.spf))) {
522 		if (bs->bpb.fats != 2)
523 			return (EINVAL);
524 		if (!(fs->dirents = cv2(bs->bpb.dirents)))
525 			return (EINVAL);
526 	} else {
527 		if (!(fs->spf = cv4(bs->bpb.lspf)))
528 			return (EINVAL);
529 		if (!bs->bpb.fats || bs->bpb.fats > 16)
530 			return (EINVAL);
531 		if ((fs->rdcl = cv4(bs->bpb.rdcl)) < LOCLUS)
532 			return (EINVAL);
533 	}
534 	if (!(fs->lsnfat = cv2(bs->bpb.ressec)))
535 		return (EINVAL);
536 	fs->lsndir = fs->lsnfat + fs->spf * bs->bpb.fats;
537 	fs->lsndta = fs->lsndir + entsec(fs->dirents);
538 	if (!(sc = cv2(bs->bpb.secs)) && !(sc = cv4(bs->bpb.lsecs)))
539 		return (EINVAL);
540 	if (fs->lsndta > sc)
541 		return (EINVAL);
542 	if ((fs->xclus = secblk(fs, sc - fs->lsndta) + 1) < LOCLUS)
543 		return (EINVAL);
544 	fs->fatsz = fs->dirents ? fs->xclus < 0xff6 ? 12 : 16 : 32;
545 	sc = (secbyt(fs->spf) << 1) / (fs->fatsz >> 2) - 1;
546 	if (fs->xclus > sc)
547 		fs->xclus = sc;
548 	return (0);
549 }
550 
551 /*
552  * Return directory entry from path
553  */
554 static int
555 namede(DOS_FS *fs, const char *path, DOS_DE **dep)
556 {
557 	char name[256];
558 	DOS_DE *de;
559 	char *s;
560 	size_t n;
561 	int err;
562 
563 	err = 0;
564 	de = &fs->root;
565 	while (*path) {
566 		while (*path == '/')
567 			path++;
568 		if (*path == '\0')
569 			break;
570 		if (!(s = strchr(path, '/')))
571 			s = strchr(path, 0);
572 		if ((n = s - path) > 255)
573 			return (ENAMETOOLONG);
574 		memcpy(name, path, n);
575 		name[n] = 0;
576 		path = s;
577 		if (!(de->attr & FA_DIR))
578 			return (ENOTDIR);
579 		if ((err = lookup(fs, stclus(fs->fatsz, de), name, &de)))
580 			return (err);
581 	}
582 	*dep = de;
583 	return (0);
584 }
585 
586 /*
587  * Lookup path segment
588  */
589 static int
590 lookup(DOS_FS *fs, uint_t clus, const char *name, DOS_DE **dep)
591 {
592 	static DOS_DIR dir[DEPSEC];
593 	uchar_t lfn[261];
594 	uchar_t sfn[13];
595 	uint_t nsec, lsec, xdn, chk, sec, ent, x;
596 	int err, ok;
597 
598 	if (!clus)
599 		for (ent = 0; ent < 2; ent++)
600 			if (!strcasecmp(name, dotstr[ent])) {
601 				*dep = dot + ent;
602 				return (0);
603 			}
604 	if (!clus && fs->fatsz == 32)
605 		clus = fs->rdcl;
606 	nsec = !clus ? entsec(fs->dirents) : fs->spc;
607 	lsec = 0;
608 	xdn = chk = 0;
609 	for (;;) {
610 		if (!clus && !lsec)
611 			lsec = fs->lsndir;
612 		else if (okclus(fs, clus))
613 			lsec = blklsn(fs, clus);
614 		else
615 			return (EINVAL);
616 
617 		for (sec = 0; sec < nsec; sec++) {
618 			if ((err = ioget(fs->fd, lsec + sec, dir, secbyt(1))))
619 				return (err);
620 			for (ent = 0; ent < DEPSEC; ent++) {
621 				if (dir[ent].de.name[0] == 0)
622 					return (ENOENT);
623 				if (dir[ent].de.name[0] == 0xe5) {
624 					xdn = 0;
625 					continue;
626 				}
627 				if ((dir[ent].de.attr & FA_MASK) == FA_XDE) {
628 					x = dir[ent].xde.seq;
629 					if (x & 0x40 ||
630 					    (x + 1 == xdn &&
631 					    dir[ent].xde.chk == chk)) {
632 						if (x & 0x40) {
633 							chk = dir[ent].xde.chk;
634 							x &= ~0x40;
635 						}
636 						if (x >= 1 && x <= 20) {
637 							cp_xdnm(lfn,
638 							    &dir[ent].xde);
639 							xdn = x;
640 							continue;
641 						}
642 					}
643 				} else if (!(dir[ent].de.attr & FA_LABEL)) {
644 					if ((ok = xdn == 1)) {
645 						x = dos_checksum(
646 						    dir[ent].de.name,
647 						    dir[ent].de.ext);
648 						ok = chk == x &&
649 						    !strcasecmp(name,
650 						    (const char *)lfn);
651 					}
652 					if (!ok) {
653 						cp_sfn(sfn, &dir[ent].de);
654 						ok = !strcasecmp(name,
655 						    (const char *)sfn);
656 					}
657 					if (ok) {
658 						*dep = &dir[ent].de;
659 						return (0);
660 					}
661 				}
662 				xdn = 0;
663 			}
664 		}
665 		if (!clus)
666 			break;
667 		if ((err = fatget(fs, &clus)))
668 			return (err);
669 		if (fatend(fs->fatsz, clus))
670 			break;
671 	}
672 	return (ENOENT);
673 }
674 
675 /*
676  * Copy name from extended directory entry
677  */
678 static void
679 cp_xdnm(uchar_t *lfn, DOS_XDE *xde)
680 {
681 	static struct {
682 		uint_t off;
683 		uint_t dim;
684 	} ix[3] = {
685 		{ offsetof(DOS_XDE, name1), sizeof (xde->name1) / 2},
686 		{ offsetof(DOS_XDE, name2), sizeof (xde->name2) / 2},
687 		{ offsetof(DOS_XDE, name3), sizeof (xde->name3) / 2}
688 	};
689 	uchar_t *p;
690 	uint_t n, x, c;
691 
692 	lfn += 13 * ((xde->seq & ~0x40) - 1);
693 	for (n = 0; n < 3; n++)
694 		for (p = (uchar_t *)xde + ix[n].off, x = ix[n].dim; x;
695 		    p += 2, x--) {
696 			if ((c = cv2(p)) && (c < 32 || c > 127))
697 				c = '?';
698 			if (!(*lfn++ = c))
699 				return;
700 		}
701 	if (xde->seq & 0x40)
702 		*lfn = 0;
703 }
704 
705 /*
706  * Copy short filename
707  */
708 static void
709 cp_sfn(uchar_t *sfn, DOS_DE *de)
710 {
711 	uchar_t *p;
712 	int j, i;
713 
714 	p = sfn;
715 	if (*de->name != ' ') {
716 		for (j = 7; de->name[j] == ' '; j--)
717 			;
718 		for (i = 0; i <= j; i++)
719 			*p++ = de->name[i];
720 		if (*de->ext != ' ') {
721 			*p++ = '.';
722 			for (j = 2; de->ext[j] == ' '; j--)
723 				;
724 			for (i = 0; i <= j; i++)
725 				*p++ = de->ext[i];
726 		}
727 	}
728 	*p = '\0';
729 	if (*sfn == 5)
730 		*sfn = 0xe5;
731 }
732 
733 /*
734  * Return size of file in bytes
735  */
736 static off_t
737 fsize(DOS_FS *fs, DOS_DE *de)
738 {
739 	ulong_t size;
740 	uint_t c;
741 	int n;
742 
743 	if (!(size = cv4(de->size)) && de->attr & FA_DIR) {
744 		if (!(c = cv2(de->clus)))
745 			size = fs->dirents * sizeof (DOS_DE);
746 		else {
747 			if ((n = fatcnt(fs, c)) == -1)
748 				return (n);
749 			size = blkbyt(fs, n);
750 		}
751 	}
752 	return (size);
753 }
754 
755 /*
756  * Count number of clusters in chain
757  */
758 static int
759 fatcnt(DOS_FS *fs, uint_t c)
760 {
761 	int n;
762 
763 	for (n = 0; okclus(fs, c); n++)
764 		if (fatget(fs, &c))
765 			return (-1);
766 	return (fatend(fs->fatsz, c) ? n : -1);
767 }
768 
769 /*
770  * Get next cluster in cluster chain. Use in core fat cache unless
771  * the number of current 128K block in FAT has changed.
772  */
773 static int
774 fatget(DOS_FS *fs, uint_t *c)
775 {
776 	uint_t val_in, val_out, offset, blknum, nbyte;
777 	const uchar_t *p_entry;
778 	int err;
779 
780 	/* check input value to prevent overflow in fatoff() */
781 	val_in = *c;
782 	if (val_in & 0xf0000000)
783 		return (EINVAL);
784 
785 	/* ensure that current 128K FAT block is cached */
786 	offset = fatoff(fs->fatsz, val_in);
787 	nbyte = fs->fatsz != 32 ? 2 : 4;
788 	if (offset + nbyte > secbyt(fs->spf))
789 		return (EINVAL);
790 	blknum = offset / FATBLKSZ;
791 	offset %= FATBLKSZ;
792 	if (offset + nbyte > FATBLKSZ)
793 		return (EINVAL);
794 	if (blknum != fs->fatbuf_blknum) {
795 		err = dos_read_fatblk(fs, fs->fd, blknum);
796 		if (err != 0)
797 			return (err);
798 	}
799 	p_entry = fs->fatbuf + offset;
800 
801 	/* extract cluster number from FAT entry */
802 	switch (fs->fatsz) {
803 	case 32:
804 		val_out = cv4(p_entry);
805 		val_out &= 0x0fffffff;
806 		break;
807 	case 16:
808 		val_out = cv2(p_entry);
809 		break;
810 	case 12:
811 		val_out = cv2(p_entry);
812 		if (val_in & 1)
813 			val_out >>= 4;
814 		else
815 			val_out &= 0xfff;
816 		break;
817 	default:
818 		return (EINVAL);
819 	}
820 	*c = val_out;
821 	return (0);
822 }
823 
824 /*
825  * Is cluster an end-of-chain marker?
826  */
827 static int
828 fatend(uint_t sz, uint_t c)
829 {
830 	return (c > (sz == 12 ? 0xff7U : sz == 16 ? 0xfff7U : 0xffffff7));
831 }
832 
833 /*
834  * Offset-based I/O primitive
835  */
836 static int
837 ioread(DOS_FS *fs, uint64_t offset, void *buf, size_t nbyte)
838 {
839 	char *s;
840 	size_t n;
841 	int err;
842 	uint64_t off;
843 	uchar_t local_buf[SECSIZ];
844 
845 	s = buf;
846 	if ((off = offset & (SECSIZ - 1))) {
847 		offset -= off;
848 		if ((n = SECSIZ - off) > nbyte)
849 			n = nbyte;
850 		err = ioget(fs->fd, bytsec(offset), local_buf,
851 		    sizeof (local_buf));
852 		if (err != 0)
853 			return (err);
854 		memcpy(s, local_buf + off, n);
855 		offset += SECSIZ;
856 		s += n;
857 		nbyte -= n;
858 	}
859 	n = nbyte & (SECSIZ - 1);
860 	if (nbyte -= n) {
861 		if ((err = ioget(fs->fd, bytsec(offset), s, nbyte)))
862 			return (err);
863 		offset += nbyte;
864 		s += nbyte;
865 	}
866 	if (n != 0) {
867 		err = ioget(fs->fd, bytsec(offset), local_buf,
868 		    sizeof (local_buf));
869 		if (err != 0)
870 			return (err);
871 		memcpy(s, local_buf, n);
872 	}
873 	return (0);
874 }
875 
876 /*
877  * Sector-based I/O primitive
878  */
879 static int
880 ioget(struct open_file *fd, daddr_t lsec, void *buf, size_t size)
881 {
882 	size_t rsize;
883 	int rv;
884 
885 	/* Make sure we get full read or error. */
886 	rsize = 0;
887 	rv = (fd->f_dev->dv_strategy)(fd->f_devdata, F_READ, lsec,
888 	    size, buf, &rsize);
889 	if ((rv == 0) && (size != rsize))
890 		rv = EIO;
891 	return (rv);
892 }
893