xref: /illumos-gate/usr/src/cmd/fs.d/pcfs/fstyp/fstyp.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T
23  *	  All Rights Reserved
24  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  * Copyright 2024 MNX Cloud, Inc.
27  */
28 
29 /*
30  * Portions of this source code were derived from Berkeley 4.3 BSD
31  * under license from the Regents of the University of California.
32  */
33 
34 /*
35  * libfstyp module for pcfs
36  */
37 #include <sys/param.h>
38 #include <sys/types.h>
39 #include <sys/mntent.h>
40 #include <errno.h>
41 #include <sys/fs/pc_fs.h>
42 #include <sys/fs/pc_label.h>
43 #include <sys/fs/pc_dir.h>
44 #include <sys/stat.h>
45 #include <sys/vfs.h>
46 #include <sys/dkio.h>
47 #include <stdio.h>
48 #include <unistd.h>
49 #include <string.h>
50 #include <strings.h>
51 #include <sys/mnttab.h>
52 #include <locale.h>
53 #include <libfstyp_module.h>
54 
55 #define	PC_LABEL_SIZE 11
56 
57 /* for the <sys/fs/pc_dir.h> PCDL_IS_LFN macro */
58 int	enable_long_filenames = 1;
59 
60 struct fstyp_fat16_bs {
61 	uint8_t		f_drvnum;
62 	uint8_t		f_reserved1;
63 	uint8_t		f_bootsig;
64 	uint8_t		f_volid[4];
65 	uint8_t		f_label[11];
66 	uint8_t		f_typestring[8];
67 };
68 
69 struct fstyp_fat32_bs {
70 	uint32_t	f_fatlength;
71 	uint16_t	f_flags;
72 	uint8_t		f_major;
73 	uint8_t		f_minor;
74 	uint32_t	f_rootcluster;
75 	uint16_t	f_infosector;
76 	uint16_t	f_backupboot;
77 	uint8_t		f_reserved2[12];
78 	uint8_t		f_drvnum;
79 	uint8_t		f_reserved1;
80 	uint8_t		f_bootsig;
81 	uint8_t		f_volid[4];
82 	uint8_t		f_label[11];
83 	uint8_t		f_typestring[8];
84 };
85 
86 typedef struct fstyp_pcfs {
87 	int		fd;
88 	off_t		offset;
89 	nvlist_t	*attr;
90 	struct bootsec	bs;
91 	struct fstyp_fat16_bs bs16;
92 	struct fstyp_fat32_bs bs32;
93 	ushort_t	bps;
94 	int		fattype;
95 	char		volume_label[PC_LABEL_SIZE + 1];
96 
97 	/* parameters derived or calculated per FAT spec */
98 	ulong_t		FATSz;
99 	ulong_t		TotSec;
100 	ulong_t		RootDirSectors;
101 	ulong_t		FirstDataSector;
102 	ulong_t		DataSec;
103 	ulong_t		CountOfClusters;
104 } fstyp_pcfs_t;
105 
106 
107 /* We should eventually make the structs "packed" so these won't be needed */
108 #define	PC_BPSEC(h)	ltohs((h)->bs.bps[0])
109 #define	PC_RESSEC(h)	ltohs((h)->bs.res_sec[0])
110 #define	PC_NROOTENT(h)	ltohs((h)->bs.rdirents[0])
111 #define	PC_NSEC(h)	ltohs((h)->bs.numsect[0])
112 #define	PC_DRVNUM(h)	(FSTYP_IS_32(h) ? (h)->bs32.f_drvnum : \
113 			    (h)->bs16.f_drvnum)
114 #define	PC_VOLID(a)	(FSTYP_IS_32(h) ? ltohi((h)->bs32.f_volid[0]) : \
115 			    ltohi((h)->bs16.f_volid[0]))
116 #define	PC_LABEL_ADDR(a) (FSTYP_IS_32(h) ? \
117 			    &((h)->bs32.f_label[0]) : &((h)->bs16.f_label[0]))
118 
119 #define	FSTYP_IS_32(h)	((h)->fattype == 32)
120 
121 #define	FSTYP_MAX_CLUSTER_SIZE	(64 * 1024)	/* though officially 32K */
122 #define	FSTYP_MAX_DIR_SIZE	(65536 * 32)
123 
124 static int	read_bootsec(fstyp_pcfs_t *h);
125 static int	valid_media(fstyp_pcfs_t *h);
126 static int	well_formed(fstyp_pcfs_t *h);
127 static void	calculate_parameters(fstyp_pcfs_t *h);
128 static void	determine_fattype(fstyp_pcfs_t *h);
129 static void	get_label(fstyp_pcfs_t *h);
130 static void	get_label_16(fstyp_pcfs_t *h);
131 static void	get_label_32(fstyp_pcfs_t *h);
132 static int	next_cluster_32(fstyp_pcfs_t *h, int n);
133 static boolean_t dir_find_label(fstyp_pcfs_t *h, struct pcdir *d, int nent);
134 static int	is_pcfs(fstyp_pcfs_t *h);
135 static int	dumpfs(fstyp_pcfs_t *h, FILE *fout, FILE *ferr);
136 static int	get_attr(fstyp_pcfs_t *h);
137 
138 int	fstyp_mod_init(int fd, off_t offset, fstyp_mod_handle_t *handle);
139 void	fstyp_mod_fini(fstyp_mod_handle_t handle);
140 int	fstyp_mod_ident(fstyp_mod_handle_t handle);
141 int	fstyp_mod_get_attr(fstyp_mod_handle_t handle, nvlist_t **attrp);
142 int	fstyp_mod_dump(fstyp_mod_handle_t handle, FILE *fout, FILE *ferr);
143 
144 int
145 fstyp_mod_init(int fd, off_t offset, fstyp_mod_handle_t *handle)
146 {
147 	struct fstyp_pcfs *h;
148 
149 	if ((h = calloc(1, sizeof (struct fstyp_pcfs))) == NULL) {
150 		return (FSTYP_ERR_NOMEM);
151 	}
152 	h->fd = fd;
153 	h->offset = offset;
154 
155 	*handle = (fstyp_mod_handle_t)h;
156 	return (0);
157 }
158 
159 void
160 fstyp_mod_fini(fstyp_mod_handle_t handle)
161 {
162 	struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle;
163 
164 	if (h->attr == NULL) {
165 		nvlist_free(h->attr);
166 		h->attr = NULL;
167 	}
168 	free(h);
169 }
170 
171 int
172 fstyp_mod_ident(fstyp_mod_handle_t handle)
173 {
174 	struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle;
175 
176 	return (is_pcfs(h));
177 }
178 
179 int
180 fstyp_mod_get_attr(fstyp_mod_handle_t handle, nvlist_t **attrp)
181 {
182 	struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle;
183 	int error;
184 
185 	if (h->attr == NULL) {
186 		if (nvlist_alloc(&h->attr, NV_UNIQUE_NAME_TYPE, 0)) {
187 			return (FSTYP_ERR_NOMEM);
188 		}
189 		if ((error = get_attr(h)) != 0) {
190 			nvlist_free(h->attr);
191 			h->attr = NULL;
192 			return (error);
193 		}
194 	}
195 
196 	*attrp = h->attr;
197 	return (0);
198 }
199 
200 int
201 fstyp_mod_dump(fstyp_mod_handle_t handle, FILE *fout, FILE *ferr)
202 {
203 	struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle;
204 
205 	return (dumpfs(h, fout, ferr));
206 }
207 
208 
209 /*
210  * Read in boot sector. Convert into host endianness where possible.
211  */
212 static int
213 read_bootsec(fstyp_pcfs_t *h)
214 {
215 	struct dk_minfo dkminfo;
216 	char  *buf;
217 	size_t size = PC_SECSIZE;
218 
219 	if (ioctl(h->fd, DKIOCGMEDIAINFO, &dkminfo) != -1) {
220 		if (dkminfo.dki_lbsize != 0)
221 			size = dkminfo.dki_lbsize;
222 	}
223 
224 	buf = malloc(size);
225 	if (buf == NULL)
226 		return (FSTYP_ERR_NOMEM);
227 
228 	(void) lseek(h->fd, h->offset, SEEK_SET);
229 	if (read(h->fd, buf, size) != (ssize_t)size) {
230 		free(buf);
231 		return (FSTYP_ERR_IO);
232 	}
233 
234 	bcopy(buf, &h->bs, sizeof (h->bs));
235 	bcopy(buf + sizeof (struct bootsec), &h->bs16, sizeof (h->bs16));
236 	bcopy(buf + sizeof (struct bootsec), &h->bs32, sizeof (h->bs32));
237 	free(buf);
238 
239 	h->bs.fatsec = ltohs(h->bs.fatsec);
240 	h->bs.spt = ltohs(h->bs.spt);
241 	h->bs.nhead = ltohs(h->bs.nhead);
242 	h->bs.hiddensec = ltohi(h->bs.hiddensec);
243 	h->bs.totalsec = ltohi(h->bs.totalsec);
244 
245 	h->bs32.f_fatlength = ltohi(h->bs32.f_fatlength);
246 	h->bs32.f_flags = ltohs(h->bs32.f_flags);
247 	h->bs32.f_rootcluster = ltohi(h->bs32.f_rootcluster);
248 	h->bs32.f_infosector = ltohs(h->bs32.f_infosector);
249 	h->bs32.f_backupboot = ltohs(h->bs32.f_backupboot);
250 
251 	h->bps = PC_BPSEC(h);
252 
253 	return (0);
254 }
255 
256 static int
257 valid_media(fstyp_pcfs_t *h)
258 {
259 	switch (h->bs.mediadesriptor) {
260 	case MD_FIXED:
261 	case SS8SPT:
262 	case DS8SPT:
263 	case SS9SPT:
264 	case DS9SPT:
265 	case DS18SPT:
266 	case DS9_15SPT:
267 		return (1);
268 	default:
269 		return (0);
270 	}
271 }
272 
273 static int
274 well_formed(fstyp_pcfs_t *h)
275 {
276 	int fatmatch;
277 
278 	if (h->bs16.f_bootsig == 0x29) {
279 		fatmatch = ((h->bs16.f_typestring[0] == 'F' &&
280 		    h->bs16.f_typestring[1] == 'A' &&
281 		    h->bs16.f_typestring[2] == 'T') &&
282 		    (h->bs.fatsec > 0) &&
283 		    ((PC_NSEC(h) == 0 && h->bs.totalsec > 0) ||
284 		    PC_NSEC(h) > 0));
285 	} else if (h->bs32.f_bootsig == 0x29) {
286 		fatmatch = ((h->bs32.f_typestring[0] == 'F' &&
287 		    h->bs32.f_typestring[1] == 'A' &&
288 		    h->bs32.f_typestring[2] == 'T') &&
289 		    (h->bs.fatsec == 0 && h->bs32.f_fatlength > 0) &&
290 		    ((PC_NSEC(h) == 0 && h->bs.totalsec > 0) ||
291 		    PC_NSEC(h) > 0));
292 	} else {
293 		fatmatch = (PC_NSEC(h) > 0 && h->bs.fatsec > 0);
294 	}
295 
296 	return (fatmatch && h->bps > 0 && h->bps % 512 == 0 &&
297 	    h->bs.spcl > 0 && PC_RESSEC(h) >= 1 && h->bs.nfat > 0);
298 }
299 
300 static void
301 calculate_parameters(fstyp_pcfs_t *h)
302 {
303 	if (PC_NSEC(h) != 0) {
304 		h->TotSec = PC_NSEC(h);
305 	} else {
306 		h->TotSec = h->bs.totalsec;
307 	}
308 	if (h->bs.fatsec != 0) {
309 		h->FATSz = h->bs.fatsec;
310 	} else {
311 		h->FATSz = h->bs32.f_fatlength;
312 	}
313 	if ((h->bps == 0) || (h->bs.spcl == 0)) {
314 		return;
315 	}
316 	h->RootDirSectors =
317 	    ((PC_NROOTENT(h) * 32) + (h->bps - 1)) / h->bps;
318 	h->FirstDataSector =
319 	    PC_RESSEC(h) + h->bs.nfat * h->FATSz + h->RootDirSectors;
320 	h->DataSec = h->TotSec - h->FirstDataSector;
321 	h->CountOfClusters = h->DataSec / h->bs.spcl;
322 }
323 
324 static void
325 determine_fattype(fstyp_pcfs_t *h)
326 {
327 	if (h->CountOfClusters == 0) {
328 		h->fattype = 0;
329 		return;
330 	}
331 
332 	if (h->CountOfClusters < 4085) {
333 		h->fattype = 12;
334 	} else if (h->CountOfClusters < 65525) {
335 		h->fattype = 16;
336 	} else {
337 		h->fattype = 32;
338 	}
339 }
340 
341 static void
342 get_label(fstyp_pcfs_t *h)
343 {
344 	/*
345 	 * Use label from the boot sector by default.
346 	 * Can overwrite later with the one from root directory.
347 	 */
348 	(void) memcpy(h->volume_label, PC_LABEL_ADDR(h), PC_LABEL_SIZE);
349 	h->volume_label[PC_LABEL_SIZE] = '\0';
350 
351 	if (h->fattype == 0) {
352 		return;
353 	} else if (FSTYP_IS_32(h)) {
354 		get_label_32(h);
355 	} else {
356 		get_label_16(h);
357 	}
358 }
359 
360 /*
361  * Get volume label from the root directory entry.
362  * In FAT12/16 the root directory is of fixed size.
363  * It immediately follows the FATs
364  */
365 static void
366 get_label_16(fstyp_pcfs_t *h)
367 {
368 	ulong_t	FirstRootDirSecNum;
369 	int	secsize;
370 	off_t	offset;
371 	uint8_t	buf[PC_SECSIZE * 4];
372 	int	i;
373 	int	nent, resid;
374 
375 	if ((secsize = h->bps) > sizeof (buf)) {
376 		return;
377 	}
378 
379 	FirstRootDirSecNum = PC_RESSEC(h) + h->bs.nfat * h->bs.fatsec;
380 	offset = h->offset + FirstRootDirSecNum * secsize;
381 	resid = PC_NROOTENT(h);
382 
383 	for (i = 0; i < h->RootDirSectors; i++) {
384 		(void) lseek(h->fd, offset, SEEK_SET);
385 		if (read(h->fd, buf, secsize) != secsize) {
386 			return;
387 		}
388 
389 		nent = secsize / sizeof (struct pcdir);
390 		if (nent > resid) {
391 			nent = resid;
392 		}
393 		if (dir_find_label(h, (struct pcdir *)buf, nent)) {
394 			return;
395 		}
396 
397 		resid -= nent;
398 		offset += PC_SECSIZE;
399 	}
400 }
401 
402 /*
403  * Get volume label from the root directory entry.
404  * In FAT32 root is a usual directory, a cluster chain.
405  * It starts at BPB_RootClus.
406  */
407 static void
408 get_label_32(fstyp_pcfs_t *h)
409 {
410 	off_t	offset;
411 	int	clustersize;
412 	int	n;
413 	ulong_t	FirstSectorofCluster;
414 	uint8_t	*buf;
415 	int	nent;
416 	int	cnt = 0;
417 
418 	clustersize = h->bs.spcl * h->bps;
419 	if ((clustersize == 0) || (clustersize > FSTYP_MAX_CLUSTER_SIZE) ||
420 	    ((buf = calloc(1, clustersize)) == NULL)) {
421 		return;
422 	}
423 
424 	for (n = h->bs32.f_rootcluster; n != 0; n = next_cluster_32(h, n)) {
425 		FirstSectorofCluster =
426 		    (n - 2) * h->bs.spcl + h->FirstDataSector;
427 		offset = h->offset + FirstSectorofCluster * h->bps;
428 		(void) lseek(h->fd, offset, SEEK_SET);
429 		if (read(h->fd, buf, clustersize) != clustersize) {
430 			break;
431 		}
432 
433 		nent = clustersize / sizeof (struct pcdir);
434 		if (dir_find_label(h, (struct pcdir *)buf, nent)) {
435 			break;
436 		}
437 
438 		if (++cnt > FSTYP_MAX_DIR_SIZE / clustersize) {
439 			break;
440 		}
441 	}
442 
443 	free(buf);
444 }
445 
446 /*
447  * Get a FAT entry pointing to the next file cluster
448  */
449 int
450 next_cluster_32(fstyp_pcfs_t *h, int n)
451 {
452 	uint8_t	buf[PC_SECSIZE];
453 	ulong_t	ThisFATSecNum;
454 	ulong_t	ThisFATEntOffset;
455 	off_t	offset;
456 	uint32_t val;
457 	int	next = 0;
458 
459 	ThisFATSecNum = PC_RESSEC(h) + (n * 4) / h->bps;
460 	ThisFATEntOffset = (n * 4) % h->bps;
461 	offset = h->offset + ThisFATSecNum * h->bps;
462 
463 	(void) lseek(h->fd, offset, SEEK_SET);
464 	if (read(h->fd, buf, sizeof (buf)) == sizeof (buf)) {
465 		val = buf[ThisFATEntOffset] & 0x0fffffff;
466 		next = ltohi(val);
467 	}
468 
469 	return (next);
470 }
471 
472 /*
473  * Given an array of pcdir structs, find one containing volume label.
474  */
475 static boolean_t
476 dir_find_label(fstyp_pcfs_t *h, struct pcdir *d, int nent)
477 {
478 	int	i;
479 
480 	for (i = 0; i < nent; i++, d++) {
481 		if (PCDL_IS_LFN(d))
482 			continue;
483 		if ((d->pcd_filename[0] != PCD_UNUSED) &&
484 		    (d->pcd_filename[0] != PCD_ERASED) &&
485 		    ((d->pcd_attr & (PCA_LABEL | PCA_DIR)) == PCA_LABEL) &&
486 		    (d->un.pcd_scluster_hi == 0) &&
487 		    (d->pcd_scluster_lo == 0)) {
488 			(void) memcpy(h->volume_label, d->pcd_filename,
489 			    PC_LABEL_SIZE);
490 			h->volume_label[PC_LABEL_SIZE] = '\0';
491 			return (B_TRUE);
492 		}
493 	}
494 	return (B_FALSE);
495 }
496 
497 static int
498 is_pcfs(fstyp_pcfs_t *h)
499 {
500 	int	error;
501 
502 	if ((error = read_bootsec(h)) != 0) {
503 		return (error);
504 	}
505 	if (!valid_media(h)) {
506 		return (FSTYP_ERR_NO_MATCH);
507 	}
508 	if (!well_formed(h)) {
509 		return (FSTYP_ERR_NO_MATCH);
510 	}
511 
512 	calculate_parameters(h);
513 	determine_fattype(h);
514 	get_label(h);
515 
516 	return (0);
517 }
518 
519 static int
520 dumpfs(fstyp_pcfs_t *h, FILE *fout, FILE *ferr __unused)
521 {
522 	/*
523 	 * If fat type was not detected, then the other data is
524 	 * likely bogus.
525 	 */
526 	if (h->fattype == 0)
527 		return (FSTYP_ERR_NO_MATCH);
528 
529 	(void) fprintf(fout, "Filesystem type: FAT%d\n", h->fattype);
530 	(void) fprintf(fout,
531 	    "Bytes Per Sector  %d\t\tSectors Per Cluster    %d\n",
532 	    h->bps, h->bs.spcl);
533 	(void) fprintf(fout,
534 	    "Reserved Sectors  %d\t\tNumber of FATs         %d\n",
535 	    (unsigned short)PC_RESSEC(h), h->bs.nfat);
536 	(void) fprintf(fout,
537 	    "Root Dir Entries  %d\t\tNumber of Sectors      %d\n",
538 	    (unsigned short)PC_NROOTENT(h), h->TotSec);
539 	(void) fprintf(fout,
540 	    "Sectors Per FAT   %d\t\tSectors Per Track      %d\n",
541 	    h->FATSz, h->bs.spt);
542 	(void) fprintf(fout,
543 	    "Number of Heads   %d\t\tNumber Hidden Sectors  %d\n",
544 	    h->bs.nhead, h->bs.hiddensec);
545 	(void) fprintf(fout, "Volume ID: 0x%x\n", PC_VOLID(h));
546 	(void) fprintf(fout, "Volume Label: %s\n", h->volume_label);
547 	(void) fprintf(fout, "Drive Number: 0x%x\n", PC_DRVNUM(h));
548 	(void) fprintf(fout, "Media Type: 0x%x   ", h->bs.mediadesriptor);
549 
550 	switch (h->bs.mediadesriptor) {
551 	case MD_FIXED:
552 		(void) fprintf(fout, "\"Fixed\" Disk\n");
553 		break;
554 	case SS8SPT:
555 		(void) fprintf(fout, "Single Sided, 8 Sectors Per Track\n");
556 		break;
557 	case DS8SPT:
558 		(void) fprintf(fout, "Double Sided, 8 Sectors Per Track\n");
559 		break;
560 	case SS9SPT:
561 		(void) fprintf(fout, "Single Sided, 9 Sectors Per Track\n");
562 		break;
563 	case DS9SPT:
564 		(void) fprintf(fout, "Double Sided, 9 Sectors Per Track\n");
565 		break;
566 	case DS18SPT:
567 		(void) fprintf(fout, "Double Sided, 18 Sectors Per Track\n");
568 		break;
569 	case DS9_15SPT:
570 		(void) fprintf(fout, "Double Sided, 9-15 Sectors Per Track\n");
571 		break;
572 	default:
573 		(void) fprintf(fout, "Unknown Media Type\n");
574 	}
575 
576 	return (0);
577 }
578 
579 #define	ADD_STRING(h, name, value) \
580 	if (nvlist_add_string(h->attr, name, value) != 0) { \
581 		return (FSTYP_ERR_NOMEM); \
582 	}
583 
584 #define	ADD_UINT32(h, name, value) \
585 	if (nvlist_add_uint32(h->attr, name, value) != 0) { \
586 		return (FSTYP_ERR_NOMEM); \
587 	}
588 
589 #define	ADD_UINT64(h, name, value) \
590 	if (nvlist_add_uint64(h->attr, name, value) != 0) { \
591 		return (FSTYP_ERR_NOMEM); \
592 	}
593 
594 #define	ADD_BOOL(h, name, value) \
595 	if (nvlist_add_boolean_value(h->attr, name, value) != 0) { \
596 		return (FSTYP_ERR_NOMEM); \
597 	}
598 
599 static int
600 get_attr(fstyp_pcfs_t *h)
601 {
602 	char	s[64];
603 
604 	ADD_UINT32(h, "bytes_per_sector", h->bps);
605 	ADD_UINT32(h, "sectors_per_cluster", h->bs.spcl);
606 	ADD_UINT32(h, "reserved_sectors", PC_RESSEC(h));
607 	ADD_UINT32(h, "fats", h->bs.nfat);
608 	ADD_UINT32(h, "root_entry_count", PC_NROOTENT(h));
609 	ADD_UINT32(h, "total_sectors_16", PC_NSEC(h));
610 	ADD_UINT32(h, "media", h->bs.mediadesriptor);
611 	ADD_UINT32(h, "fat_size_16", h->bs.fatsec);
612 	ADD_UINT32(h, "sectors_per_track", h->bs.spt);
613 	ADD_UINT32(h, "heads", h->bs.nhead);
614 	ADD_UINT32(h, "hidden_sectors", h->bs.hiddensec);
615 	ADD_UINT32(h, "total_sectors_32", h->bs.totalsec);
616 	ADD_UINT32(h, "drive_number", PC_DRVNUM(h));
617 	ADD_UINT32(h, "volume_id", PC_VOLID(h));
618 	ADD_STRING(h, "volume_label", h->volume_label);
619 	if (FSTYP_IS_32(h)) {
620 		ADD_UINT32(h, "fat_size_32", h->bs32.f_fatlength);
621 	}
622 	ADD_UINT32(h, "total_sectors", h->TotSec);
623 	ADD_UINT32(h, "fat_size", h->FATSz);
624 	ADD_UINT32(h, "count_of_clusters", h->CountOfClusters);
625 	ADD_UINT32(h, "fat_entry_size", h->fattype);
626 
627 	ADD_BOOL(h, "gen_clean", B_TRUE);
628 	if (PC_VOLID(a) != 0) {
629 		(void) snprintf(s, sizeof (s), "%08x", PC_VOLID(a));
630 		ADD_STRING(h, "gen_guid", s);
631 	}
632 	(void) snprintf(s, sizeof (s), "%d", h->fattype);
633 	ADD_STRING(h, "gen_version", s);
634 	ADD_STRING(h, "gen_volume_label", h->volume_label);
635 
636 	return (0);
637 }
638