xref: /freebsd/stand/libsa/dosfs.c (revision e7c0cb720898fd36487c4e6b0aff52435084e6bf)
1 /*
2  * Copyright (c) 1996, 1998 Robert Nordier
3  * All rights reserved.
4  * Copyright 2024 MNX Cloud, Inc.
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 /*
30  * Readonly filesystem for Microsoft FAT12/FAT16/FAT32 filesystems,
31  * also supports VFAT.
32  */
33 
34 #include <sys/types.h>
35 #include <sys/disk.h>
36 #include <string.h>
37 #include <stddef.h>
38 
39 #include "stand.h"
40 
41 #include "dosfs.h"
42 
43 typedef struct dos_mnt {
44 	char			*dos_dev;
45 	DOS_FS			*dos_fs;
46 	int			dos_fd;
47 	STAILQ_ENTRY(dos_mnt)	dos_link;
48 } dos_mnt_t;
49 
50 typedef STAILQ_HEAD(dos_mnt_list, dos_mnt) dos_mnt_list_t;
51 static dos_mnt_list_t mnt_list = STAILQ_HEAD_INITIALIZER(mnt_list);
52 
53 static int	dos_open(const char *path, struct open_file *fd);
54 static int	dos_close(struct open_file *fd);
55 static int	dos_read(struct open_file *fd, void *buf, size_t size, size_t *resid);
56 static off_t	dos_seek(struct open_file *fd, off_t offset, int whence);
57 static int	dos_stat(struct open_file *fd, struct stat *sb);
58 static int	dos_readdir(struct open_file *fd, struct dirent *d);
59 static int	dos_mount(const char *dev, const char *path, void **data);
60 static int	dos_unmount(const char *dev, void *data);
61 
62 struct fs_ops dosfs_fsops = {
63 	.fs_name = "dosfs",
64 	.fo_open = dos_open,
65 	.fo_close = dos_close,
66 	.fo_read = dos_read,
67 	.fo_write = null_write,
68 	.fo_seek = dos_seek,
69 	.fo_stat = dos_stat,
70 	.fo_readdir = dos_readdir,
71 	.fo_mount = dos_mount,
72 	.fo_unmount = dos_unmount
73 };
74 
75 #define LOCLUS    2             /* lowest cluster number */
76 #define FATBLKSZ  0x20000       /* size of block in the FAT cache buffer */
77 
78 /* DOS "BIOS Parameter Block" */
79 typedef struct {
80 	u_char secsiz[2];           /* sector size */
81 	u_char spc;                 /* sectors per cluster */
82 	u_char ressec[2];           /* reserved sectors */
83 	u_char fats;                /* FATs */
84 	u_char dirents[2];          /* root directory entries */
85 	u_char secs[2];             /* total sectors */
86 	u_char media;               /* media descriptor */
87 	u_char spf[2];              /* sectors per FAT */
88 	u_char spt[2];              /* sectors per track */
89 	u_char heads[2];            /* drive heads */
90 	u_char hidsec[4];           /* hidden sectors */
91 	u_char lsecs[4];            /* huge sectors */
92 	union {
93 		struct {
94 			u_char drvnum;		/* Int 13 drive number */
95 			u_char rsvd1;		/* Reserved */
96 			u_char bootsig;		/* Boot signature (0x29) */
97 			u_char volid[4];	/* Volume serial number */
98 			u_char vollab[11];	/* Volume label */
99 			u_char fstype[8];	/* Informational */
100 		} f12_f16;
101 		struct {
102 			u_char lspf[4];		/* huge sectors per FAT */
103 			u_char xflg[2];		/* flags */
104 			u_char vers[2];		/* filesystem version */
105 			u_char rdcl[4];		/* root directory cluster */
106 			u_char infs[2];		/* filesystem info sector */
107 			u_char bkbs[2];		/* backup boot sector */
108 			u_char reserved[12];	/* Reserved */
109 			u_char drvnum;		/* Int 13 drive number */
110 			u_char rsvd1;		/* Reserved */
111 			u_char bootsig;		/* Boot signature (0x29) */
112 			u_char volid[4];	/* Volume serial number */
113 			u_char vollab[11];	/* Volume label */
114 			u_char fstype[8];	/* Informational */
115 		} f32;
116 	} fstype;
117 } DOS_BPB;
118 
119 typedef struct {
120 	u_char fsi_leadsig[4];		/* Value 0x41615252 */
121 	u_char fsi_reserved1[480];
122 	u_char fsi_structsig[4];	/* Value 0x61417272 */
123 	u_char fsi_free_count[4];	/* Last known free cluster count */
124 	u_char fsi_next_free[4];	/* First free cluster */
125 	u_char fsi_reserved2[12];
126 	u_char fsi_trailsig[4];		/* Value 0xAA550000 */
127 } DOS_FSINFO;
128 
129 /* Initial portion of DOS boot sector */
130 typedef struct {
131 	u_char jmp[3];              /* usually 80x86 'jmp' opcode */
132 	u_char oem[8];              /* OEM name and version */
133 	DOS_BPB bpb;                /* BPB */
134 } DOS_BS;
135 
136 /* Supply missing "." and ".." root directory entries */
137 static const char *const dotstr[2] = {".", ".."};
138 static DOS_DE dot[2] = {
139 	{".       ", "   ", FA_DIR, {0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
140 	    {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}},
141 	{"..      ", "   ", FA_DIR, {0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
142 	    {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}}
143 };
144 
145 /* The usual conversion macros to avoid multiplication and division */
146 #define bytsec(fs, n)	((n) >> (fs)->sshift)
147 #define secbyt(fs, s)	((s) << (fs)->sshift)
148 #define depsec(fs)	(1 << (fs)->dshift)
149 #define entsec(fs, e)	((e) >> (fs)->dshift)
150 #define bytblk(fs, n)	((n) >> (fs)->bshift)
151 #define blkbyt(fs, b)	((b) << (fs)->bshift)
152 #define secblk(fs, s)	((s) >> ((fs)->bshift - (fs)->sshift))
153 #define blksec(fs, b)	((b) << ((fs)->bshift - (fs)->sshift))
154 
155 /* Convert cluster number to offset within filesystem */
156 #define blkoff(fs, b)	(secbyt(fs, (fs)->lsndta) + \
157 			blkbyt(fs, (b) - LOCLUS))
158 
159 /* Convert cluster number to logical sector number */
160 #define blklsn(fs, b)  ((fs)->lsndta + blksec(fs, (b) - LOCLUS))
161 
162 /* Convert cluster number to offset within FAT */
163 #define fatoff(sz, c)  ((sz) == 12 ? (c) + ((c) >> 1) :  \
164                         (sz) == 16 ? (c) << 1 :          \
165 			(c) << 2)
166 
167 /* Does cluster number reference a valid data cluster? */
168 #define okclus(fs, c)  ((c) >= LOCLUS && (c) <= (fs)->xclus)
169 
170 /* Get start cluster from directory entry */
171 #define stclus(sz, de)  ((sz) != 32 ? (u_int)cv2((de)->clus) :	\
172                          ((u_int)cv2((de)->dex.h_clus) << 16) |	\
173 			 cv2((de)->clus))
174 
175 static int parsebs(DOS_FS *, DOS_BS *);
176 static int namede(DOS_FS *, const char *, DOS_DE **);
177 static int lookup(DOS_FS *, u_int, const char *, DOS_DE **);
178 static void cp_xdnm(u_char *, DOS_XDE *);
179 static void cp_sfn(u_char *, DOS_DE *);
180 static off_t fsize(DOS_FS *, DOS_DE *);
181 static int fatcnt(DOS_FS *, u_int);
182 static int fatget(DOS_FS *, u_int *);
183 static int fatend(u_int, u_int);
184 static int ioread(DOS_FS *, uint64_t, void *, size_t);
185 static int ioget(DOS_FS *, daddr_t, void *, size_t);
186 
187 static int
dos_read_fatblk(DOS_FS * fs,u_int blknum)188 dos_read_fatblk(DOS_FS *fs, u_int blknum)
189 {
190 	int err;
191 	size_t io_size;
192 	daddr_t offset_in_fat, max_offset_in_fat;
193 
194 	offset_in_fat = ((daddr_t)blknum) * FATBLKSZ;
195 	max_offset_in_fat = secbyt(fs, (daddr_t)fs->spf);
196 	io_size = FATBLKSZ;
197 	if (offset_in_fat > max_offset_in_fat)
198 		offset_in_fat = max_offset_in_fat;
199 	if (offset_in_fat + io_size > max_offset_in_fat)
200 		io_size = ((size_t)(max_offset_in_fat - offset_in_fat));
201 
202 	if (io_size != 0) {
203 		err = ioget(fs, fs->lsnfat + bytsec(fs, offset_in_fat),
204 		    fs->fatbuf, io_size);
205 		if (err != 0) {
206 			fs->fatbuf_blknum = ((u_int)(-1));
207 			return (err);
208 		}
209 	}
210 	if (io_size < FATBLKSZ)
211 		memset(fs->fatbuf + io_size, 0, FATBLKSZ - io_size);
212 
213 	fs->fatbuf_blknum = blknum;
214 	return (0);
215 }
216 
217 /*
218  * Mount DOS filesystem
219  */
220 static int
dos_mount_impl(DOS_FS * fs,struct open_file * fd)221 dos_mount_impl(DOS_FS *fs, struct open_file *fd)
222 {
223 	int err;
224 	unsigned secsz;
225 	u_char *buf;
226 
227 	fs->fd = fd;
228 
229 	err = ioctl(fd->f_id, DIOCGSECTORSIZE, &secsz);
230 	if (err != 0) {
231 		return (err);
232 	}
233 
234 	buf = malloc(secsz);
235 	if (buf == NULL)
236 		return (errno);
237 
238 	if ((err = ioget(fs, 0, buf, secsz)) ||
239 	    (err = parsebs(fs, (DOS_BS *)buf))) {
240 		free(buf);
241 		return (err);
242 	}
243 	fs->secbuf = buf;
244 
245 	if ((fs->fatbuf = malloc(FATBLKSZ)) == NULL) {
246 		free(buf);
247 		return (errno);
248 	}
249 	err = dos_read_fatblk(fs, 0);
250 	if (err != 0) {
251 		free(buf);
252 		free(fs->fatbuf);
253 		return (err);
254 	}
255 
256 	fs->root = dot[0];
257 	fs->root.name[0] = ' ';
258 	if (fs->fatsz == 32) {
259 		fs->root.clus[0] = fs->rdcl & 0xff;
260 		fs->root.clus[1] = (fs->rdcl >> 8) & 0xff;
261 		fs->root.dex.h_clus[0] = (fs->rdcl >> 16) & 0xff;
262 		fs->root.dex.h_clus[1] = (fs->rdcl >> 24) & 0xff;
263 	}
264 	return (0);
265 }
266 
267 static int
dos_mount(const char * dev,const char * path,void ** data)268 dos_mount(const char *dev, const char *path, void **data)
269 {
270 	char *fs;
271 	dos_mnt_t *mnt;
272 	struct open_file *f;
273 	DOS_FILE *df;
274 
275 	errno = 0;
276 	mnt = calloc(1, sizeof(*mnt));
277 	if (mnt == NULL)
278 		return (errno);
279 	mnt->dos_fd = -1;
280 	mnt->dos_dev = strdup(dev);
281 	if (mnt->dos_dev == NULL)
282 		goto done;
283 
284 	if (asprintf(&fs, "%s%s", dev, path) < 0)
285 		goto done;
286 
287 	mnt->dos_fd = open(fs, O_RDONLY);
288 	free(fs);
289 	if (mnt->dos_fd == -1)
290 		goto done;
291 
292 	f = fd2open_file(mnt->dos_fd);
293 	if (strcmp(f->f_ops->fs_name, "dosfs") == 0) {
294 		df = f->f_fsdata;
295 		mnt->dos_fs = df->fs;
296 		STAILQ_INSERT_TAIL(&mnt_list, mnt, dos_link);
297 	} else {
298                 errno = ENXIO;
299 	}
300 
301 done:
302 	if (errno != 0) {
303 		free(mnt->dos_dev);
304 		if (mnt->dos_fd >= 0)
305 			close(mnt->dos_fd);
306 		free(mnt);
307 	} else {
308 		*data = mnt;
309 	}
310 
311 	return (errno);
312 }
313 
314 static int
dos_unmount(const char * dev __unused,void * data)315 dos_unmount(const char *dev __unused, void *data)
316 {
317 	dos_mnt_t *mnt = data;
318 
319 	STAILQ_REMOVE(&mnt_list, mnt, dos_mnt, dos_link);
320 	free(mnt->dos_dev);
321 	close(mnt->dos_fd);
322 	free(mnt);
323 	return (0);
324 }
325 
326 /*
327  * Unmount mounted filesystem
328  */
329 static int
dos_unmount_impl(DOS_FS * fs)330 dos_unmount_impl(DOS_FS *fs)
331 {
332 	if (fs->links)
333 		return (EBUSY);
334 	free(fs->secbuf);
335 	free(fs->fatbuf);
336 	free(fs);
337 	return (0);
338 }
339 
340 /*
341  * Open DOS file
342  */
343 static int
dos_open(const char * path,struct open_file * fd)344 dos_open(const char *path, struct open_file *fd)
345 {
346 	DOS_DE *de;
347 	DOS_FILE *f;
348 	DOS_FS *fs = NULL;
349 	dos_mnt_t *mnt;
350 	const char *dev;
351 	u_int size, clus;
352 	int err;
353 
354 	dev = devformat((struct devdesc *)fd->f_devdata);
355 	STAILQ_FOREACH(mnt, &mnt_list, dos_link) {
356 		if (strcmp(dev, mnt->dos_dev) == 0)
357 			break;
358 	}
359 
360 	if (mnt == NULL) {
361 		/* Allocate mount structure, associate with open */
362 		if ((fs = calloc(1, sizeof(DOS_FS))) == NULL)
363 			return (errno);
364 		if ((err = dos_mount_impl(fs, fd))) {
365 			free(fs);
366 			return (err);
367 		}
368 	} else {
369 		fs = mnt->dos_fs;
370 	}
371 
372 	if ((err = namede(fs, path, &de))) {
373 		if (mnt == NULL)
374 			dos_unmount_impl(fs);
375 		return (err);
376 	}
377 
378 	clus = stclus(fs->fatsz, de);
379 	size = cv4(de->size);
380 
381 	if ((!(de->attr & FA_DIR) && (!clus != !size)) ||
382 	    ((de->attr & FA_DIR) && size) ||
383 	    (clus && !okclus(fs, clus))) {
384 		if (mnt == NULL)
385 			dos_unmount_impl(fs);
386 		return (EINVAL);
387 	}
388 	if ((f = calloc(1, sizeof(DOS_FILE))) == NULL) {
389 		err = errno;
390 		if (mnt == NULL)
391 			dos_unmount_impl(fs);
392 		return (err);
393 	}
394 	f->fs = fs;
395 	fs->links++;
396 	f->de = *de;
397 	fd->f_fsdata = f;
398 	return (0);
399 }
400 
401 /*
402  * Read from file
403  */
404 static int
dos_read(struct open_file * fd,void * buf,size_t nbyte,size_t * resid)405 dos_read(struct open_file *fd, void *buf, size_t nbyte, size_t *resid)
406 {
407 	off_t size;
408 	uint64_t off;
409 	size_t nb;
410 	u_int clus, c, cnt, n;
411 	DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
412 	int err = 0;
413 
414 	/*
415 	 * as ioget() can be called *a lot*, use twiddle here.
416 	 * also 4 seems to be good value not to slow loading down too much:
417 	 * with 270MB file (~540k ioget() calls, twiddle can easily waste
418 	 * 4-5 sec.
419 	 */
420 	twiddle(4);
421 	nb = nbyte;
422 	if ((size = fsize(f->fs, &f->de)) == -1)
423 		return (EINVAL);
424 	if (nb > (n = size - f->offset))
425 		nb = n;
426 	off = f->offset;
427 	if ((clus = stclus(f->fs->fatsz, &f->de)))
428 		off &= f->fs->bsize - 1;
429 	c = f->c;
430 	cnt = nb;
431 	while (cnt) {
432 		n = 0;
433 		if (!c) {
434 			if ((c = clus))
435 				n = bytblk(f->fs, f->offset);
436 		} else if (!off)
437 			n++;
438 		while (n--) {
439 			if ((err = fatget(f->fs, &c)))
440 				goto out;
441 			if (!okclus(f->fs, c)) {
442 				err = EINVAL;
443 				goto out;
444 			}
445 		}
446 		if (!clus || (n = f->fs->bsize - off) > cnt)
447 			n = cnt;
448 		if (c != 0)
449 			off += blkoff(f->fs, (uint64_t)c);
450 		else
451 			off += secbyt(f->fs, f->fs->lsndir);
452 		err = ioread(f->fs, off, buf, n);
453 		if (err != 0)
454 			goto out;
455 		f->offset += n;
456 		f->c = c;
457 		off = 0;
458 		buf = (char *)buf + n;
459 		cnt -= n;
460 	}
461 out:
462 	if (resid)
463 		*resid = nbyte - nb + cnt;
464 	return (err);
465 }
466 
467 /*
468  * Reposition within file
469  */
470 static off_t
dos_seek(struct open_file * fd,off_t offset,int whence)471 dos_seek(struct open_file *fd, off_t offset, int whence)
472 {
473 	off_t off;
474 	u_int size;
475 	DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
476 
477 	size = cv4(f->de.size);
478 	switch (whence) {
479 	case SEEK_SET:
480 		off = 0;
481 		break;
482 	case SEEK_CUR:
483 		off = f->offset;
484 		break;
485 	case SEEK_END:
486 		off = size;
487 		break;
488 	default:
489 		errno = EINVAL;
490 		return (-1);
491 	}
492 	off += offset;
493 	if (off < 0 || off > size) {
494 		errno = EINVAL;
495 		return (-1);
496 	}
497 	f->offset = (u_int)off;
498 	f->c = 0;
499 	return (off);
500 }
501 
502 /*
503  * Close open file
504  */
505 static int
dos_close(struct open_file * fd)506 dos_close(struct open_file *fd)
507 {
508 	DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
509 	DOS_FS *fs = f->fs;
510 
511 	f->fs->links--;
512 	free(f);
513 	dos_unmount_impl(fs);
514 	return (0);
515 }
516 
517 /*
518  * Return some stat information on a file.
519  */
520 static int
dos_stat(struct open_file * fd,struct stat * sb)521 dos_stat(struct open_file *fd, struct stat *sb)
522 {
523 	DOS_FILE *f = (DOS_FILE *)fd->f_fsdata;
524 
525 	/* only important stuff */
526 	sb->st_mode = f->de.attr & FA_DIR ? S_IFDIR | 0555 : S_IFREG | 0444;
527 	sb->st_nlink = 1;
528 	sb->st_uid = 0;
529 	sb->st_gid = 0;
530 	if ((sb->st_size = fsize(f->fs, &f->de)) == -1)
531 		return (EINVAL);
532 	return (0);
533 }
534 
535 static int
dos_checksum(unsigned char * name,unsigned char * ext)536 dos_checksum(unsigned char *name, unsigned char *ext)
537 {
538 	int x, i;
539 	char buf[11];
540 
541 	bcopy(name, buf, 8);
542 	bcopy(ext, buf+8, 3);
543 	x = 0;
544 	for (i = 0; i < 11; i++) {
545 		x = ((x & 1) << 7) | (x >> 1);
546 		x += buf[i];
547 		x &= 0xff;
548 	}
549 	return (x);
550 }
551 
552 static int
dos_readdir(struct open_file * fd,struct dirent * d)553 dos_readdir(struct open_file *fd, struct dirent *d)
554 {
555 	/* DOS_FILE *f = (DOS_FILE *)fd->f_fsdata; */
556 	u_char fn[261];
557 	DOS_DIR dd;
558 	size_t res;
559 	u_int chk, x, xdn;
560 	int err;
561 
562 	x = chk = 0;
563 	for (;;) {
564 		xdn = x;
565 		x = 0;
566 		err = dos_read(fd, &dd, sizeof(dd), &res);
567 		if (err)
568 			return (err);
569 		if (res == sizeof(dd))
570 			return (ENOENT);
571 		if (dd.de.name[0] == 0)
572 			return (ENOENT);
573 
574 		/* Skip deleted entries */
575 		if (dd.de.name[0] == 0xe5)
576 			continue;
577 
578 		/* Check if directory entry is volume label */
579 		if (dd.de.attr & FA_LABEL) {
580 			/*
581 			 * If volume label set, check if the current entry is
582 			 * extended entry (FA_XDE) for long file names.
583 			 */
584 			if ((dd.de.attr & FA_MASK) == FA_XDE) {
585 				/*
586 				 * Read through all following extended entries
587 				 * to get the long file name. 0x40 marks the
588 				 * last entry containing part of long file name.
589 				 */
590 				if (dd.xde.seq & 0x40)
591 					chk = dd.xde.chk;
592 				else if (dd.xde.seq != xdn - 1 ||
593 				    dd.xde.chk != chk)
594 					continue;
595 				x = dd.xde.seq & ~0x40;
596 				if (x < 1 || x > 20) {
597 					x = 0;
598 					continue;
599 				}
600 				cp_xdnm(fn, &dd.xde);
601 			} else {
602 				/* skip only volume label entries */
603 				continue;
604 			}
605 		} else {
606 			if (xdn == 1) {
607 				x = dos_checksum(dd.de.name, dd.de.ext);
608 				if (x == chk)
609 					break;
610 			} else {
611 				cp_sfn(fn, &dd.de);
612 				break;
613 			}
614 			x = 0;
615 		}
616 	}
617 
618 	d->d_fileno = (dd.de.clus[1] << 8) + dd.de.clus[0];
619 	d->d_reclen = sizeof(*d);
620 	d->d_type = (dd.de.attr & FA_DIR) ? DT_DIR : DT_REG;
621 	memcpy(d->d_name, fn, sizeof(d->d_name));
622 	return (0);
623 }
624 
625 /*
626  * Parse DOS boot sector
627  */
628 static int
parsebs(DOS_FS * fs,DOS_BS * bs)629 parsebs(DOS_FS *fs, DOS_BS *bs)
630 {
631 	u_int sc, RootDirSectors;
632 
633 	if (bs->bpb.media < 0xf0)
634 		return (EINVAL);
635 
636 	/* Check supported sector sizes */
637 	switch (cv2(bs->bpb.secsiz)) {
638 	case 512:
639 	case 1024:
640 	case 2048:
641 	case 4096:
642 		fs->sshift = ffs(cv2(bs->bpb.secsiz)) - 1;
643 		break;
644 
645 	default:
646 		return (EINVAL);
647 	}
648 
649 	if (!(fs->spc = bs->bpb.spc) || fs->spc & (fs->spc - 1))
650 		return (EINVAL);
651 	fs->bsize = secbyt(fs, fs->spc);
652 	fs->bshift = ffs(fs->bsize) - 1;
653 	fs->dshift = ffs(secbyt(fs, 1) / sizeof (DOS_DE)) - 1;
654 	fs->dirents = cv2(bs->bpb.dirents);
655 	fs->spf = cv2(bs->bpb.spf);
656 	fs->lsnfat = cv2(bs->bpb.ressec);
657 
658 	if (fs->spf != 0) {
659 		if (bs->bpb.fats != 2)
660 			return (EINVAL);
661 		if (fs->dirents == 0)
662 			return (EINVAL);
663 	} else {
664 		fs->spf = cv4(bs->bpb.fstype.f32.lspf);
665 		if (fs->spf == 0)
666 			return (EINVAL);
667 		if (bs->bpb.fats == 0 || bs->bpb.fats > 16)
668 			return (EINVAL);
669 		fs->rdcl = cv4(bs->bpb.fstype.f32.rdcl);
670 		if (fs->rdcl < LOCLUS)
671 			return (EINVAL);
672 	}
673 
674 	RootDirSectors = ((fs->dirents * sizeof (DOS_DE)) +
675 	    (secbyt(fs, 1) - 1)) / secbyt(fs, 1);
676 
677 	fs->lsndir = fs->lsnfat + fs->spf * bs->bpb.fats;
678 	fs->lsndta = fs->lsndir + RootDirSectors;
679 	if (!(sc = cv2(bs->bpb.secs)) && !(sc = cv4(bs->bpb.lsecs)))
680 		return (EINVAL);
681 	if (fs->lsndta > sc)
682 		return (EINVAL);
683 	if ((fs->xclus = secblk(fs, sc - fs->lsndta) + 1) < LOCLUS)
684 		return (EINVAL);
685 	fs->fatsz = fs->dirents ? fs->xclus < 0xff6 ? 12 : 16 : 32;
686 	sc = (secbyt(fs, fs->spf) << 1) / (fs->fatsz >> 2) - 1;
687 	if (fs->xclus > sc)
688 		fs->xclus = sc;
689 	return (0);
690 }
691 
692 /*
693  * Return directory entry from path
694  */
695 static int
namede(DOS_FS * fs,const char * path,DOS_DE ** dep)696 namede(DOS_FS *fs, const char *path, DOS_DE **dep)
697 {
698 	char name[256];
699 	DOS_DE *de;
700 	char *s;
701 	size_t n;
702 	int err;
703 
704 	err = 0;
705 	de = &fs->root;
706 	while (*path) {
707 		while (*path == '/')
708 			path++;
709 		if (*path == '\0')
710 			break;
711 		if (!(s = strchr(path, '/')))
712 			s = strchr(path, 0);
713 		if ((n = s - path) > 255)
714 			return (ENAMETOOLONG);
715 		memcpy(name, path, n);
716 		name[n] = 0;
717 		path = s;
718 		if (!(de->attr & FA_DIR))
719 			return (ENOTDIR);
720 		if ((err = lookup(fs, stclus(fs->fatsz, de), name, &de)))
721 			return (err);
722 	}
723 	*dep = de;
724 	return (0);
725 }
726 
727 /*
728  * Lookup path segment
729  */
730 static int
lookup(DOS_FS * fs,u_int clus,const char * name,DOS_DE ** dep)731 lookup(DOS_FS *fs, u_int clus, const char *name, DOS_DE **dep)
732 {
733 	DOS_DIR *dir;
734 	u_char lfn[261];
735 	u_char sfn[13];
736 	u_int nsec, lsec, xdn, chk, sec, ent, x;
737 	int err, ok;
738 
739 	if (!clus)
740 		for (ent = 0; ent < 2; ent++)
741 			if (!strcasecmp(name, dotstr[ent])) {
742 				*dep = dot + ent;
743 				return (0);
744 		}
745 	if (!clus && fs->fatsz == 32)
746 		clus = fs->rdcl;
747 	nsec = !clus ? entsec(fs, fs->dirents) : fs->spc;
748 	lsec = 0;
749 	xdn = chk = 0;
750 	dir = (DOS_DIR *)fs->secbuf;
751 	for (;;) {
752 		if (!clus && !lsec)
753 			lsec = fs->lsndir;
754 		else if (okclus(fs, clus))
755 			lsec = blklsn(fs, clus);
756 		else
757 			return (EINVAL);
758 		for (sec = 0; sec < nsec; sec++) {
759 			if ((err = ioget(fs, lsec + sec, dir,
760 			    secbyt(fs, 1))))
761 				return (err);
762 			for (ent = 0; ent < depsec(fs); ent++) {
763 				if (!*dir[ent].de.name)
764 					return (ENOENT);
765 				if (*dir[ent].de.name != 0xe5) {
766 					if ((dir[ent].de.attr & FA_MASK) ==
767 					    FA_XDE) {
768 						x = dir[ent].xde.seq;
769 						if (x & 0x40 || (x + 1 == xdn &&
770 						    dir[ent].xde.chk == chk)) {
771 							if (x & 0x40) {
772 								chk = dir[ent].xde.chk;
773 								x &= ~0x40;
774 							}
775 							if (x >= 1 && x <= 20) {
776 								cp_xdnm(lfn, &dir[ent].xde);
777 								xdn = x;
778 								continue;
779 							}
780 						}
781 					} else if (!(dir[ent].de.attr &
782 					    FA_LABEL)) {
783 						if ((ok = xdn == 1)) {
784 							x = dos_checksum(
785 							    dir[ent].de.name,
786 							    dir[ent].de.ext);
787 							ok = chk == x &&
788 							!strcasecmp(name,
789 							    (const char *)lfn);
790 						}
791 						if (!ok) {
792 							cp_sfn(sfn,
793 							    &dir[ent].de);
794 							ok = !strcasecmp(name,
795 							    (const char *)sfn);
796 						}
797 						if (ok) {
798 							*dep = &dir[ent].de;
799 							return (0);
800 						}
801 					}
802 				}
803 				xdn = 0;
804 			}
805 		}
806 		if (!clus)
807 			break;
808 		if ((err = fatget(fs, &clus)))
809 			return (err);
810 		if (fatend(fs->fatsz, clus))
811 			break;
812 	}
813 	return (ENOENT);
814 }
815 
816 /*
817  * Copy name from extended directory entry
818  */
819 static void
cp_xdnm(u_char * lfn,DOS_XDE * xde)820 cp_xdnm(u_char *lfn, DOS_XDE *xde)
821 {
822 	static struct {
823 		u_int off;
824 		u_int dim;
825 	} ix[3] = {
826 		{offsetof(DOS_XDE, name1), sizeof(xde->name1) / 2},
827 		{offsetof(DOS_XDE, name2), sizeof(xde->name2) / 2},
828 		{offsetof(DOS_XDE, name3), sizeof(xde->name3) / 2}
829 	};
830 	u_char *p;
831 	u_int n, x, c;
832 
833 	lfn += 13 * ((xde->seq & ~0x40) - 1);
834 	for (n = 0; n < 3; n++)
835 		for (p = (u_char *)xde + ix[n].off, x = ix[n].dim; x;
836 		    p += 2, x--) {
837 			if ((c = cv2(p)) && (c < 32 || c > 127))
838 				c = '?';
839 			if (!(*lfn++ = c))
840 				return;
841 		}
842 	if (xde->seq & 0x40)
843 		*lfn = 0;
844 }
845 
846 /*
847  * Copy short filename
848  */
849 static void
cp_sfn(u_char * sfn,DOS_DE * de)850 cp_sfn(u_char *sfn, DOS_DE *de)
851 {
852 	u_char *p;
853 	int j, i;
854 
855 	p = sfn;
856 	if (*de->name != ' ') {
857 		for (j = 7; de->name[j] == ' '; j--)
858 			;
859 		for (i = 0; i <= j; i++)
860 			*p++ = de->name[i];
861 		if (*de->ext != ' ') {
862 			*p++ = '.';
863 			for (j = 2; de->ext[j] == ' '; j--)
864 				;
865 			for (i = 0; i <= j; i++)
866 				*p++ = de->ext[i];
867 		}
868 	}
869 	*p = 0;
870 	if (*sfn == 5)
871 		*sfn = 0xe5;
872 }
873 
874 /*
875  * Return size of file in bytes
876  */
877 static off_t
fsize(DOS_FS * fs,DOS_DE * de)878 fsize(DOS_FS *fs, DOS_DE *de)
879 {
880 	u_long size;
881 	u_int c;
882 	int n;
883 
884 	if (!(size = cv4(de->size)) && de->attr & FA_DIR) {
885 		if (!(c = stclus(fs->fatsz, de))) {
886 			size = fs->dirents * sizeof(DOS_DE);
887 		} else {
888 			if ((n = fatcnt(fs, c)) == -1)
889 				return (n);
890 			size = blkbyt(fs, n);
891 		}
892 	}
893 	return (size);
894 }
895 
896 /*
897  * Count number of clusters in chain
898  */
899 static int
fatcnt(DOS_FS * fs,u_int c)900 fatcnt(DOS_FS *fs, u_int c)
901 {
902 	int n;
903 
904 	for (n = 0; okclus(fs, c); n++)
905 		if (fatget(fs, &c))
906 			return (-1);
907 	return (fatend(fs->fatsz, c) ? n : -1);
908 }
909 
910 /*
911  * Get next cluster in cluster chain. Use in core fat cache unless
912  * the number of current 128K block in FAT has changed.
913  */
914 static int
fatget(DOS_FS * fs,u_int * c)915 fatget(DOS_FS *fs, u_int *c)
916 {
917 	u_int val_in, val_out, offset, blknum, nbyte;
918 	const u_char *p_entry;
919 	int err;
920 
921 	/* check input value to prevent overflow in fatoff() */
922 	val_in = *c;
923 	if (val_in & 0xf0000000)
924 		return (EINVAL);
925 
926 	/* ensure that current 128K FAT block is cached */
927 	offset = fatoff(fs->fatsz, val_in);
928 	nbyte = fs->fatsz != 32 ? 2 : 4;
929 	if (offset + nbyte > secbyt(fs, fs->spf))
930 		return (EINVAL);
931 	blknum = offset / FATBLKSZ;
932 	offset %= FATBLKSZ;
933 	if (offset + nbyte > FATBLKSZ)
934 		return (EINVAL);
935 	if (blknum != fs->fatbuf_blknum) {
936 		err = dos_read_fatblk(fs, blknum);
937 		if (err != 0)
938 			return (err);
939 	}
940 	p_entry = fs->fatbuf + offset;
941 
942 	/* extract cluster number from FAT entry */
943 	switch (fs->fatsz) {
944 	case 32:
945 		val_out = cv4(p_entry);
946 		val_out &= 0x0fffffff;
947 		break;
948 	case 16:
949 		val_out = cv2(p_entry);
950 		break;
951 	case 12:
952 		val_out = cv2(p_entry);
953 		if (val_in & 1)
954 			val_out >>= 4;
955 		else
956 			val_out &= 0xfff;
957 		break;
958 	default:
959 		return (EINVAL);
960 	}
961 	*c = val_out;
962 	return (0);
963 }
964 
965 /*
966  * Is cluster an end-of-chain marker?
967  */
968 static int
fatend(u_int sz,u_int c)969 fatend(u_int sz, u_int c)
970 {
971 	return (c > (sz == 12 ? 0xff7U : sz == 16 ? 0xfff7U : 0xffffff7));
972 }
973 
974 /*
975  * Offset-based I/O primitive
976  */
977 static int
ioread(DOS_FS * fs,uint64_t offset,void * buf,size_t nbyte)978 ioread(DOS_FS *fs, uint64_t offset, void *buf, size_t nbyte)
979 {
980 	char *s;
981 	size_t n, secsiz;
982 	int err;
983 	uint64_t off;
984 
985 	secsiz = secbyt(fs, 1);
986 	s = buf;
987 	if ((off = offset & (secsiz - 1))) {
988 		offset -= off;
989 		if ((n = secsiz - off) > nbyte)
990 			n = nbyte;
991 		err = ioget(fs, bytsec(fs, offset), fs->secbuf, secsiz);
992 		if (err != 0)
993 			return (err);
994 		memcpy(s, fs->secbuf + off, n);
995 		offset += secsiz;
996 		s += n;
997 		nbyte -= n;
998 	}
999 	n = nbyte & (secsiz - 1);
1000 	if (nbyte -= n) {
1001 		if ((err = ioget(fs, bytsec(fs, offset), s, nbyte)))
1002 			return (err);
1003 		offset += nbyte;
1004 		s += nbyte;
1005 	}
1006 	if (n != 0) {
1007 		err = ioget(fs, bytsec(fs, offset), fs->secbuf, secsiz);
1008 		if (err != 0)
1009 			return (err);
1010 		memcpy(s, fs->secbuf, n);
1011 	}
1012 	return (0);
1013 }
1014 
1015 /*
1016  * Sector-based I/O primitive. Note, since strategy functions are operating
1017  * in terms of 512B sectors, we need to do necessary conversion here.
1018  */
1019 static int
ioget(DOS_FS * fs,daddr_t lsec,void * buf,size_t size)1020 ioget(DOS_FS *fs, daddr_t lsec, void *buf, size_t size)
1021 {
1022 	size_t rsize;
1023 	int rv;
1024 	struct open_file *fd = fs->fd;
1025 
1026 	/* Make sure we get full read or error. */
1027 	rsize = 0;
1028 	/* convert native sector number to 512B sector number. */
1029 	lsec = secbyt(fs, lsec) >> 9;
1030 	rv = (fd->f_dev->dv_strategy)(fd->f_devdata, F_READ, lsec,
1031 	    size, buf, &rsize);
1032 	if ((rv == 0) && (size != rsize))
1033 		rv = EIO;
1034 	return (rv);
1035 }
1036