1*ce58ffd3Schristos /* $NetBSD: exfat.c,v 1.6 2021/09/17 21:06:35 christos Exp $ */
27575c8cfStkusumi
37575c8cfStkusumi /*
47575c8cfStkusumi * Copyright (c) 2017 Conrad Meyer <cem@FreeBSD.org>
57575c8cfStkusumi * All rights reserved.
67575c8cfStkusumi *
77575c8cfStkusumi * Redistribution and use in source and binary forms, with or without
87575c8cfStkusumi * modification, are permitted provided that the following conditions
97575c8cfStkusumi * are met:
107575c8cfStkusumi * 1. Redistributions of source code must retain the above copyright
117575c8cfStkusumi * notice, this list of conditions and the following disclaimer.
127575c8cfStkusumi * 2. Redistributions in binary form must reproduce the above copyright
137575c8cfStkusumi * notice, this list of conditions and the following disclaimer in the
147575c8cfStkusumi * documentation and/or other materials provided with the distribution.
157575c8cfStkusumi *
167575c8cfStkusumi * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
177575c8cfStkusumi * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
187575c8cfStkusumi * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
197575c8cfStkusumi * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
207575c8cfStkusumi * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
217575c8cfStkusumi * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
227575c8cfStkusumi * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
237575c8cfStkusumi * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
247575c8cfStkusumi * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
257575c8cfStkusumi * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
267575c8cfStkusumi * SUCH DAMAGE.
277575c8cfStkusumi */
287575c8cfStkusumi #include <sys/cdefs.h>
29*ce58ffd3Schristos __RCSID("$NetBSD: exfat.c,v 1.6 2021/09/17 21:06:35 christos Exp $");
307575c8cfStkusumi
310cd59d67Stkusumi #include <sys/param.h>
320cd59d67Stkusumi #include <sys/endian.h>
330cd59d67Stkusumi
340cd59d67Stkusumi #include <assert.h>
350cd59d67Stkusumi #include <err.h>
360cd59d67Stkusumi #include <errno.h>
370cd59d67Stkusumi #include <iconv.h>
380cd59d67Stkusumi #include <stdbool.h>
397575c8cfStkusumi #include <stdint.h>
407575c8cfStkusumi #include <stdio.h>
417575c8cfStkusumi #include <stdlib.h>
427575c8cfStkusumi #include <string.h>
437575c8cfStkusumi
447575c8cfStkusumi #include "fstyp.h"
457575c8cfStkusumi
460cd59d67Stkusumi /*
470cd59d67Stkusumi * https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification
480cd59d67Stkusumi */
490cd59d67Stkusumi
507575c8cfStkusumi struct exfat_vbr {
517575c8cfStkusumi char ev_jmp[3];
527575c8cfStkusumi char ev_fsname[8];
537575c8cfStkusumi char ev_zeros[53];
547575c8cfStkusumi uint64_t ev_part_offset;
557575c8cfStkusumi uint64_t ev_vol_length;
567575c8cfStkusumi uint32_t ev_fat_offset;
577575c8cfStkusumi uint32_t ev_fat_length;
587575c8cfStkusumi uint32_t ev_cluster_offset;
597575c8cfStkusumi uint32_t ev_cluster_count;
607575c8cfStkusumi uint32_t ev_rootdir_cluster;
617575c8cfStkusumi uint32_t ev_vol_serial;
627575c8cfStkusumi uint16_t ev_fs_revision;
637575c8cfStkusumi uint16_t ev_vol_flags;
647575c8cfStkusumi uint8_t ev_log_bytes_per_sect;
657575c8cfStkusumi uint8_t ev_log_sect_per_clust;
667575c8cfStkusumi uint8_t ev_num_fats;
677575c8cfStkusumi uint8_t ev_drive_sel;
687575c8cfStkusumi uint8_t ev_percent_used;
697575c8cfStkusumi } __packed;
707575c8cfStkusumi
710cd59d67Stkusumi struct exfat_dirent {
720cd59d67Stkusumi uint8_t xde_type;
730cd59d67Stkusumi #define XDE_TYPE_INUSE_MASK 0x80 /* 1=in use */
740cd59d67Stkusumi #define XDE_TYPE_INUSE_SHIFT 7
750cd59d67Stkusumi #define XDE_TYPE_CATEGORY_MASK 0x40 /* 0=primary */
760cd59d67Stkusumi #define XDE_TYPE_CATEGORY_SHIFT 6
770cd59d67Stkusumi #define XDE_TYPE_IMPORTNC_MASK 0x20 /* 0=critical */
780cd59d67Stkusumi #define XDE_TYPE_IMPORTNC_SHIFT 5
790cd59d67Stkusumi #define XDE_TYPE_CODE_MASK 0x1f
800cd59d67Stkusumi /* InUse=0, ..., TypeCode=0: EOD. */
810cd59d67Stkusumi #define XDE_TYPE_EOD 0x00
820cd59d67Stkusumi #define XDE_TYPE_ALLOC_BITMAP (XDE_TYPE_INUSE_MASK | 0x01)
830cd59d67Stkusumi #define XDE_TYPE_UPCASE_TABLE (XDE_TYPE_INUSE_MASK | 0x02)
840cd59d67Stkusumi #define XDE_TYPE_VOL_LABEL (XDE_TYPE_INUSE_MASK | 0x03)
850cd59d67Stkusumi #define XDE_TYPE_FILE (XDE_TYPE_INUSE_MASK | 0x05)
860cd59d67Stkusumi #define XDE_TYPE_VOL_GUID (XDE_TYPE_INUSE_MASK | XDE_TYPE_IMPORTNC_MASK)
870cd59d67Stkusumi #define XDE_TYPE_STREAM_EXT (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK)
880cd59d67Stkusumi #define XDE_TYPE_FILE_NAME (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | 0x01)
890cd59d67Stkusumi #define XDE_TYPE_VENDOR (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | XDE_TYPE_IMPORTNC_MASK)
900cd59d67Stkusumi #define XDE_TYPE_VENDOR_ALLOC (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | XDE_TYPE_IMPORTNC_MASK | 0x01)
910cd59d67Stkusumi union {
920cd59d67Stkusumi uint8_t xde_generic_[19];
930cd59d67Stkusumi struct exde_primary {
940cd59d67Stkusumi /*
950cd59d67Stkusumi * Count of "secondary" dirents following this one.
960cd59d67Stkusumi *
970cd59d67Stkusumi * A single logical entity may be composed of a
980cd59d67Stkusumi * sequence of several dirents, starting with a primary
990cd59d67Stkusumi * one; the rest are secondary dirents.
1000cd59d67Stkusumi */
1010cd59d67Stkusumi uint8_t xde_secondary_count_;
1020cd59d67Stkusumi uint16_t xde_set_chksum_;
1030cd59d67Stkusumi uint16_t xde_prim_flags_;
1040cd59d67Stkusumi uint8_t xde_prim_generic_[14];
1050cd59d67Stkusumi } __packed xde_primary_;
1060cd59d67Stkusumi struct exde_secondary {
1070cd59d67Stkusumi uint8_t xde_sec_flags_;
1080cd59d67Stkusumi uint8_t xde_sec_generic_[18];
1090cd59d67Stkusumi } __packed xde_secondary_;
1100cd59d67Stkusumi } u;
1110cd59d67Stkusumi uint32_t xde_first_cluster;
1120cd59d67Stkusumi uint64_t xde_data_len;
1130cd59d67Stkusumi } __packed;
1140cd59d67Stkusumi #define xde_generic u.xde_generic_
1150cd59d67Stkusumi #define xde_secondary_count u.xde_primary_.xde_secondary_count
1160cd59d67Stkusumi #define xde_set_chksum u.xde_primary_.xde_set_chksum_
1170cd59d67Stkusumi #define xde_prim_flags u.xde_primary_.xde_prim_flags_
1180cd59d67Stkusumi #define xde_sec_flags u.xde_secondary_.xde_sec_flags_
1190cd59d67Stkusumi _Static_assert(sizeof(struct exfat_dirent) == 32, "spec");
1200cd59d67Stkusumi
1210cd59d67Stkusumi struct exfat_de_label {
1220cd59d67Stkusumi uint8_t xdel_type; /* XDE_TYPE_VOL_LABEL */
1230cd59d67Stkusumi uint8_t xdel_char_cnt; /* Length of UCS-2 label */
1240cd59d67Stkusumi uint16_t xdel_vol_lbl[11];
1250cd59d67Stkusumi uint8_t xdel_reserved[8];
1260cd59d67Stkusumi } __packed;
1270cd59d67Stkusumi _Static_assert(sizeof(struct exfat_de_label) == 32, "spec");
1280cd59d67Stkusumi
1290cd59d67Stkusumi #define MAIN_BOOT_REGION_SECT 0
1300cd59d67Stkusumi #define BACKUP_BOOT_REGION_SECT 12
1310cd59d67Stkusumi
1320cd59d67Stkusumi #define SUBREGION_CHKSUM_SECT 11
1330cd59d67Stkusumi
1340cd59d67Stkusumi #define FIRST_CLUSTER 2
1350cd59d67Stkusumi #define BAD_BLOCK_SENTINEL 0xfffffff7u
1360cd59d67Stkusumi #define END_CLUSTER_SENTINEL 0xffffffffu
1370cd59d67Stkusumi
1380cd59d67Stkusumi static inline void *
read_sectn(FILE * fp,off_t sect,unsigned count,unsigned bytespersec)1390cd59d67Stkusumi read_sectn(FILE *fp, off_t sect, unsigned count, unsigned bytespersec)
1400cd59d67Stkusumi {
1410cd59d67Stkusumi return (read_buf(fp, sect * bytespersec, bytespersec * count));
1420cd59d67Stkusumi }
1430cd59d67Stkusumi
1440cd59d67Stkusumi static inline void *
read_sect(FILE * fp,off_t sect,unsigned bytespersec)1450cd59d67Stkusumi read_sect(FILE *fp, off_t sect, unsigned bytespersec)
1460cd59d67Stkusumi {
1470cd59d67Stkusumi return (read_sectn(fp, sect, 1, bytespersec));
1480cd59d67Stkusumi }
1490cd59d67Stkusumi
1500cd59d67Stkusumi /*
1510cd59d67Stkusumi * Compute the byte-by-byte multi-sector checksum of the given boot region
1520cd59d67Stkusumi * (MAIN or BACKUP), for a given bytespersec (typically 512 or 4096).
1530cd59d67Stkusumi *
1540cd59d67Stkusumi * Endian-safe; result is host endian.
1550cd59d67Stkusumi */
1560cd59d67Stkusumi static int
exfat_compute_boot_chksum(FILE * fp,unsigned region,unsigned bytespersec,uint32_t * result)1570cd59d67Stkusumi exfat_compute_boot_chksum(FILE *fp, unsigned region, unsigned bytespersec,
1580cd59d67Stkusumi uint32_t *result)
1590cd59d67Stkusumi {
1600cd59d67Stkusumi unsigned char *sector;
1610cd59d67Stkusumi unsigned n, sect;
1620cd59d67Stkusumi uint32_t checksum;
1630cd59d67Stkusumi
1640cd59d67Stkusumi checksum = 0;
1650cd59d67Stkusumi for (sect = 0; sect < 11; sect++) {
1660cd59d67Stkusumi sector = read_sect(fp, region + sect, bytespersec);
1670cd59d67Stkusumi if (sector == NULL)
1680cd59d67Stkusumi return (ENXIO);
1690cd59d67Stkusumi for (n = 0; n < bytespersec; n++) {
1700cd59d67Stkusumi if (sect == 0) {
1710cd59d67Stkusumi switch (n) {
1720cd59d67Stkusumi case 106:
1730cd59d67Stkusumi case 107:
1740cd59d67Stkusumi case 112:
1750cd59d67Stkusumi continue;
1760cd59d67Stkusumi }
1770cd59d67Stkusumi }
1780cd59d67Stkusumi checksum = ((checksum & 1) ? 0x80000000u : 0u) +
1790cd59d67Stkusumi (checksum >> 1) + (uint32_t)sector[n];
1800cd59d67Stkusumi }
1810cd59d67Stkusumi free(sector);
1820cd59d67Stkusumi }
1830cd59d67Stkusumi
1840cd59d67Stkusumi *result = checksum;
1850cd59d67Stkusumi return (0);
1860cd59d67Stkusumi }
1870cd59d67Stkusumi
1880cd59d67Stkusumi static void
convert_label(const uint16_t * ucs2label,unsigned ucs2len,char * label_out,size_t label_sz)1890cd59d67Stkusumi convert_label(const uint16_t *ucs2label /* LE */, unsigned ucs2len, char
1900cd59d67Stkusumi *label_out, size_t label_sz)
1910cd59d67Stkusumi {
1920cd59d67Stkusumi const char *label;
1930cd59d67Stkusumi char *label_out_orig;
1940cd59d67Stkusumi iconv_t cd;
1950cd59d67Stkusumi size_t srcleft, rc;
1960cd59d67Stkusumi
1970cd59d67Stkusumi /* Currently hardcoded in fstyp.c as 256 or so. */
1980cd59d67Stkusumi assert(label_sz > 1);
1990cd59d67Stkusumi
2000cd59d67Stkusumi if (ucs2len == 0) {
2010cd59d67Stkusumi /*
2020cd59d67Stkusumi * Kind of seems bogus, but the spec allows an empty label
2030cd59d67Stkusumi * entry with the same meaning as no label.
2040cd59d67Stkusumi */
2050cd59d67Stkusumi return;
2060cd59d67Stkusumi }
2070cd59d67Stkusumi
2080cd59d67Stkusumi if (ucs2len > 11) {
2090cd59d67Stkusumi warnx("exfat: Bogus volume label length: %u", ucs2len);
2100cd59d67Stkusumi return;
2110cd59d67Stkusumi }
2120cd59d67Stkusumi
2130cd59d67Stkusumi /* dstname="" means convert to the current locale. */
2140cd59d67Stkusumi cd = iconv_open("", EXFAT_ENC);
2150cd59d67Stkusumi if (cd == (iconv_t)-1) {
2160cd59d67Stkusumi warn("exfat: Could not open iconv");
2170cd59d67Stkusumi return;
2180cd59d67Stkusumi }
2190cd59d67Stkusumi
2200cd59d67Stkusumi label_out_orig = label_out;
2210cd59d67Stkusumi
2220cd59d67Stkusumi /* Dummy up the byte pointer and byte length iconv's API wants. */
2230cd59d67Stkusumi label = (const void *)ucs2label;
2240cd59d67Stkusumi srcleft = ucs2len * sizeof(*ucs2label);
2250cd59d67Stkusumi
2260cd59d67Stkusumi rc = iconv(cd, __UNCONST(&label), &srcleft, &label_out,
2270cd59d67Stkusumi &label_sz);
2280cd59d67Stkusumi if (rc == (size_t)-1) {
2290cd59d67Stkusumi warn("exfat: iconv()");
2300cd59d67Stkusumi *label_out_orig = '\0';
2310cd59d67Stkusumi } else {
2320cd59d67Stkusumi /* NUL-terminate result (iconv advances label_out). */
2330cd59d67Stkusumi if (label_sz == 0)
2340cd59d67Stkusumi label_out--;
2350cd59d67Stkusumi *label_out = '\0';
2360cd59d67Stkusumi }
2370cd59d67Stkusumi
2380cd59d67Stkusumi iconv_close(cd);
2390cd59d67Stkusumi }
2400cd59d67Stkusumi
2410cd59d67Stkusumi /*
2420cd59d67Stkusumi * Using the FAT table, look up the next cluster in this chain.
2430cd59d67Stkusumi */
2440cd59d67Stkusumi static uint32_t
exfat_fat_next(FILE * fp,const struct exfat_vbr * ev,unsigned BPS,uint32_t cluster)2450cd59d67Stkusumi exfat_fat_next(FILE *fp, const struct exfat_vbr *ev, unsigned BPS,
2460cd59d67Stkusumi uint32_t cluster)
2470cd59d67Stkusumi {
2480cd59d67Stkusumi uint32_t fat_offset_sect, clsect, clsectoff;
2490cd59d67Stkusumi uint32_t *fatsect, nextclust;
2500cd59d67Stkusumi
2510cd59d67Stkusumi fat_offset_sect = le32toh(ev->ev_fat_offset);
2520cd59d67Stkusumi clsect = fat_offset_sect + (cluster / (BPS / (uint32_t)sizeof(cluster)));
2535f234ad7Sfox clsectoff = (cluster % (BPS / (uint32_t)sizeof(cluster)));
2540cd59d67Stkusumi
2550cd59d67Stkusumi /* XXX This is pretty wasteful without a block cache for the FAT. */
2560cd59d67Stkusumi fatsect = read_sect(fp, clsect, BPS);
2570cd59d67Stkusumi nextclust = le32toh(fatsect[clsectoff]);
2580cd59d67Stkusumi free(fatsect);
2590cd59d67Stkusumi
2600cd59d67Stkusumi return (nextclust);
2610cd59d67Stkusumi }
2620cd59d67Stkusumi
2630cd59d67Stkusumi static void
exfat_find_label(FILE * fp,const struct exfat_vbr * ev,unsigned BPS,char * label_out,size_t label_sz)2640cd59d67Stkusumi exfat_find_label(FILE *fp, const struct exfat_vbr *ev, unsigned BPS,
2650cd59d67Stkusumi char *label_out, size_t label_sz)
2660cd59d67Stkusumi {
2670cd59d67Stkusumi uint32_t rootdir_cluster, sects_per_clust, cluster_offset_sect;
2680cd59d67Stkusumi off_t rootdir_sect;
2690cd59d67Stkusumi struct exfat_dirent *declust, *it;
2700cd59d67Stkusumi
2710cd59d67Stkusumi cluster_offset_sect = le32toh(ev->ev_cluster_offset);
2720cd59d67Stkusumi rootdir_cluster = le32toh(ev->ev_rootdir_cluster);
2730cd59d67Stkusumi sects_per_clust = (1u << ev->ev_log_sect_per_clust);
2740cd59d67Stkusumi
2750cd59d67Stkusumi if (rootdir_cluster < FIRST_CLUSTER) {
2760cd59d67Stkusumi warnx("%s: invalid rootdir cluster %u < %d", __func__,
2770cd59d67Stkusumi rootdir_cluster, FIRST_CLUSTER);
2780cd59d67Stkusumi return;
2790cd59d67Stkusumi }
2800cd59d67Stkusumi
2810cd59d67Stkusumi
2820cd59d67Stkusumi for (; rootdir_cluster != END_CLUSTER_SENTINEL;
2830cd59d67Stkusumi rootdir_cluster = exfat_fat_next(fp, ev, BPS, rootdir_cluster)) {
2840cd59d67Stkusumi if (rootdir_cluster == BAD_BLOCK_SENTINEL) {
2850cd59d67Stkusumi warnx("%s: Bogus bad block in root directory chain",
2860cd59d67Stkusumi __func__);
2870cd59d67Stkusumi return;
2880cd59d67Stkusumi }
2890cd59d67Stkusumi
2900cd59d67Stkusumi rootdir_sect = (rootdir_cluster - FIRST_CLUSTER) *
2910cd59d67Stkusumi sects_per_clust + cluster_offset_sect;
2920cd59d67Stkusumi declust = read_sectn(fp, rootdir_sect, sects_per_clust, BPS);
2930cd59d67Stkusumi for (it = declust;
2940cd59d67Stkusumi it < declust + (sects_per_clust * BPS / sizeof(*it)); it++) {
2950cd59d67Stkusumi bool eod = false;
2960cd59d67Stkusumi
2970cd59d67Stkusumi /*
2980cd59d67Stkusumi * Simplistic directory traversal; doesn't do any
2990cd59d67Stkusumi * validation of "MUST" requirements in spec.
3000cd59d67Stkusumi */
3010cd59d67Stkusumi switch (it->xde_type) {
3020cd59d67Stkusumi case XDE_TYPE_EOD:
3030cd59d67Stkusumi eod = true;
3040cd59d67Stkusumi break;
3050cd59d67Stkusumi case XDE_TYPE_VOL_LABEL: {
3060cd59d67Stkusumi struct exfat_de_label *lde = (void*)it;
3070cd59d67Stkusumi convert_label(lde->xdel_vol_lbl,
3080cd59d67Stkusumi lde->xdel_char_cnt, label_out, label_sz);
3090cd59d67Stkusumi free(declust);
3100cd59d67Stkusumi return;
3110cd59d67Stkusumi }
3120cd59d67Stkusumi }
3130cd59d67Stkusumi
3140cd59d67Stkusumi if (eod)
3150cd59d67Stkusumi break;
3160cd59d67Stkusumi }
3170cd59d67Stkusumi free(declust);
3180cd59d67Stkusumi }
3190cd59d67Stkusumi }
3200cd59d67Stkusumi
3217575c8cfStkusumi int
fstyp_exfat(FILE * fp,char * label,size_t size)3227575c8cfStkusumi fstyp_exfat(FILE *fp, char *label, size_t size)
3237575c8cfStkusumi {
3247575c8cfStkusumi struct exfat_vbr *ev;
3250cd59d67Stkusumi uint32_t *cksect;
3260cd59d67Stkusumi unsigned bytespersec;
3270cd59d67Stkusumi uint32_t chksum;
3280cd59d67Stkusumi int error;
3290cd59d67Stkusumi
330ed5947a0Stkusumi error = 1;
3310cd59d67Stkusumi cksect = NULL;
3327575c8cfStkusumi ev = (struct exfat_vbr *)read_buf(fp, 0, 512);
3337575c8cfStkusumi if (ev == NULL || strncmp(ev->ev_fsname, "EXFAT ", 8) != 0)
334ed5947a0Stkusumi goto out;
3357575c8cfStkusumi
3360cd59d67Stkusumi if (ev->ev_log_bytes_per_sect < 9 || ev->ev_log_bytes_per_sect > 12) {
3370cd59d67Stkusumi warnx("exfat: Invalid BytesPerSectorShift");
338ed5947a0Stkusumi goto out;
3390cd59d67Stkusumi }
3400cd59d67Stkusumi
3410cd59d67Stkusumi bytespersec = (1u << ev->ev_log_bytes_per_sect);
3420cd59d67Stkusumi
3430cd59d67Stkusumi error = exfat_compute_boot_chksum(fp, MAIN_BOOT_REGION_SECT,
3440cd59d67Stkusumi bytespersec, &chksum);
3450cd59d67Stkusumi if (error != 0)
346ed5947a0Stkusumi goto out;
3470cd59d67Stkusumi
3480cd59d67Stkusumi cksect = read_sect(fp, MAIN_BOOT_REGION_SECT + SUBREGION_CHKSUM_SECT,
3490cd59d67Stkusumi bytespersec);
3500cd59d67Stkusumi
3517575c8cfStkusumi /*
3520cd59d67Stkusumi * Technically the entire sector should be full of repeating 4-byte
3530cd59d67Stkusumi * checksum pattern, but we only verify the first.
3547575c8cfStkusumi */
3550cd59d67Stkusumi if (chksum != le32toh(cksect[0])) {
3560cd59d67Stkusumi warnx("exfat: Found checksum 0x%08x != computed 0x%08x",
3570cd59d67Stkusumi le32toh(cksect[0]), chksum);
358ed5947a0Stkusumi error = 1;
359ed5947a0Stkusumi goto out;
3600cd59d67Stkusumi }
3610cd59d67Stkusumi
3620cd59d67Stkusumi if (show_label)
3630cd59d67Stkusumi exfat_find_label(fp, ev, bytespersec, label, size);
3640cd59d67Stkusumi
365ed5947a0Stkusumi out:
3660cd59d67Stkusumi free(cksect);
3677575c8cfStkusumi free(ev);
368ed5947a0Stkusumi return (error != 0);
3697575c8cfStkusumi }
370