Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package exfatprogs for openSUSE:Factory checked in at 2026-03-11 20:50:30 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/exfatprogs (Old) and /work/SRC/openSUSE:Factory/.exfatprogs.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "exfatprogs" Wed Mar 11 20:50:30 2026 rev:23 rq:1338190 version:1.3.2 Changes: -------- --- /work/SRC/openSUSE:Factory/exfatprogs/exfatprogs.changes 2026-01-21 14:11:46.224866956 +0100 +++ /work/SRC/openSUSE:Factory/.exfatprogs.new.8177/exfatprogs.changes 2026-03-11 20:51:21.780195359 +0100 @@ -1,0 +2,21 @@ +Wed Mar 11 05:32:16 UTC 2026 - Michael Vetter <[email protected]> + +- Update to 1.3.2: + Features: + * fsck.exfat: add an option to show a progress bar + while checking a filesystem. + Changes: + * mkfs.exfat: discard blocks prior to write outs by + default. + * mkfs.exfat: add a read-after-write verification for + the volume boot record. + * exfatprogs: adjust utility exit codes and add log + messages for malloc() failures. + Bug fixes: + * dump.exfat: handle paths including '.', '..', and + repeated '/'. + * fsck.exfat: convert 0x80 entries into deleted file + entries to avoid bogus dentry errors. + * fsck.exfat: fix an uninitialized variable warning. + +------------------------------------------------------------------- Old: ---- 1.3.1.tar.gz New: ---- 1.3.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ exfatprogs.spec ++++++ --- /var/tmp/diff_new_pack.MhuKtw/_old 2026-03-11 20:51:22.432222348 +0100 +++ /var/tmp/diff_new_pack.MhuKtw/_new 2026-03-11 20:51:22.432222348 +0100 @@ -17,7 +17,7 @@ Name: exfatprogs -Version: 1.3.1 +Version: 1.3.2 Release: 0 Summary: Utilities for exFAT file system maintenance License: GPL-2.0-or-later ++++++ 1.3.1.tar.gz -> 1.3.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/.github/workflows/c-cpp.yml new/exfatprogs-1.3.2/.github/workflows/c-cpp.yml --- old/exfatprogs-1.3.1/.github/workflows/c-cpp.yml 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/.github/workflows/c-cpp.yml 2026-03-10 02:29:29.000000000 +0100 @@ -23,7 +23,7 @@ linux-headers-$(uname -r) xz-utils \ gcc-mips-linux-gnu qemu-system-mips \ qemu-user - git clone https://github.com/namjaejeon/linux-exfat-oot + sudo apt-get install -y linux-modules-extra-$(uname -r) export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH export PATH=/usr/local/lib:$PATH - name: build test & install exfatprogs @@ -46,12 +46,8 @@ sudo -E ./test_fsck.sh - name: create file/director test run: | - cd linux-exfat-oot - make > /dev/null - sudo make install > /dev/null sudo modprobe exfat sudo mkdir -p /mnt/test - cd .. truncate -s 10G test.img sudo losetup /dev/loop22 test.img sudo mkfs.exfat /dev/loop22 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/NEWS new/exfatprogs-1.3.2/NEWS --- old/exfatprogs-1.3.1/NEWS 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/NEWS 2026-03-10 02:29:29.000000000 +0100 @@ -1,3 +1,25 @@ +exfatprogs 1.3.2 - release 2026-03-09 +=================================== + +NEW FEATURES : + * fsck.exfat: add an option to show a progress bar + while checking a filesystem. + +CHANGES : + * mkfs.exfat: discard blocks prior to write outs by + default. + * mkfs.exfat: add a read-after-write verification for + the volume boot record. + * exfatprogs: adjust utility exit codes and add log + messages for malloc() failures. + +BUG FIXES : + * dump.exfat: handle paths including '.', '..', and + repeated '/'. + * fsck.exfat: convert 0x80 entries into deleted file + entries to avoid bogus dentry errors. + * fsck.exfat: fix an uninitialized variable warning. + exfatprogs 1.3.1 - released 2025-12-15 ===================================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/defrag/defrag.c new/exfatprogs-1.3.2/defrag/defrag.c --- old/exfatprogs-1.3.1/defrag/defrag.c 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/defrag/defrag.c 2026-03-10 02:29:29.000000000 +0100 @@ -167,7 +167,7 @@ boot_calc_checksum(buf, bd->sector_size, is_boot_sec, &checksum); } - ret = exfat_write_checksum_sector(bd, checksum, is_backup); + ret = exfat_write_checksum_sector(bd, NULL, checksum, is_backup); free_buf: free(buf); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/dump/dump.c new/exfatprogs-1.3.2/dump/dump.c --- old/exfatprogs-1.3.1/dump/dump.c 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/dump/dump.c 2026-03-10 02:29:29.000000000 +0100 @@ -240,6 +240,17 @@ return 0; } +static void exfat_free_inode_chain(struct exfat_inode *inode) +{ + struct exfat_inode *parent; + + while (inode) { + parent = inode->parent; + exfat_free_inode(inode); + inode = parent; + } +} + /* * Get the first level file name from a given path * @@ -253,28 +264,25 @@ */ static int get_name_from_path(const char *path, char *name, size_t name_size) { - int i; - int name_len = 0; - int path_len = strlen(path); - - if (path_len == 0) - return 0; + const char *p = path; + size_t len = 0; - for (i = 0; i <= path_len && name_len + 1 < name_size; i++, path++) { - if (*path == '/' || *path == '\0') { - if (name_len == 0) - continue; + while (*p == '/') + p++; - name[name_len] = 0; - return i; - } + if (*p == '\0') { + name[0] = '\0'; + return p - path; + } - name[name_len] = *path; - name_len++; + while (*p != '/' && *p != '\0') { + if (len + 1 < name_size) + name[len++] = *p; + p++; } - name[0] = 0; - return 0; + name[len] = '\0'; + return p - path; } /* @@ -296,60 +304,91 @@ { int len, ret; char name[PATH_MAX + 1]; - struct exfat_inode *inode; + struct exfat_inode *cur_inode, *new_inode, *tmp; struct exfat_dentry *dentry_set; struct exfat_lookup_filter filter; const char *p_path = path; - inode = exfat_alloc_inode(ATTR_SUBDIR); - if (!inode) + cur_inode = exfat_alloc_inode(ATTR_SUBDIR); + if (!cur_inode) return -ENOMEM; - *inode = *exfat->root; - *dir_is_contiguous = inode->is_contiguous; + cur_inode->parent = NULL; + *cur_inode = *exfat->root; + *dir_is_contiguous = cur_inode->is_contiguous; - do { - if ((inode->attr & ATTR_SUBDIR) == 0 && *p_path != '\0') { + while (*p_path) { + if ((cur_inode->attr & ATTR_SUBDIR) == 0 && *p_path != '\0') { ret = -ENOENT; goto free_inode; } len = get_name_from_path(p_path, name, sizeof(name)); p_path += len; - if (name[0] == '\0' || len == 0) { - *new = inode; - return 0; + if (name[0] == '\0' || len == 0) + goto out; + + if (strcmp(name, ".") == 0) + continue; + + if (strcmp(name, "..") == 0) { + if (!cur_inode->parent) { + ret = -EINVAL; + goto free_inode; + } + tmp = cur_inode; + cur_inode = cur_inode->parent; + exfat_free_inode(tmp); + continue; + } + + new_inode = exfat_alloc_inode(ATTR_SUBDIR); + if (!new_inode) { + ret = -ENOMEM; + goto free_inode; } - ret = exfat_utf16_enc(name, inode->name, NAME_BUFFER_SIZE); - if (ret < 0) + new_inode->parent = cur_inode; + + ret = exfat_utf16_enc(name, new_inode->name, NAME_BUFFER_SIZE); + if (ret < 0) { + exfat_free_inode(new_inode); goto free_inode; + } - ret = exfat_lookup_file_by_utf16name(exfat, inode, inode->name, + ret = exfat_lookup_file_by_utf16name(exfat, cur_inode, new_inode->name, &filter); if (ret) { if (ret == EOF) ret = -ENOENT; + exfat_free_inode(new_inode); goto free_inode; } + /* fill new inode from lookup result */ dentry_set = filter.out.dentry_set; - if (inode->dentry_set) - free(inode->dentry_set); - inode->dentry_set = dentry_set; - inode->dev_offset = filter.out.dev_offset; - inode->dentry_count = filter.out.dentry_count; - inode->attr = dentry_set[0].file_attr; - inode->first_clus = le32_to_cpu(dentry_set[1].stream_start_clu); - *dir_is_contiguous = inode->is_contiguous; - inode->is_contiguous = + new_inode->dentry_set = dentry_set; + new_inode->dev_offset = filter.out.dev_offset; + new_inode->dentry_count = filter.out.dentry_count; + new_inode->attr = dentry_set[0].file_attr; + new_inode->first_clus = le32_to_cpu(dentry_set[1].stream_start_clu); + new_inode->is_contiguous = (dentry_set[1].stream_flags & EXFAT_SF_CONTIGUOUS); - inode->size = le64_to_cpu(dentry_set[1].stream_size); - } while (1); + new_inode->size = le64_to_cpu(dentry_set[1].stream_size); -free_inode: - exfat_free_inode(inode); + cur_inode = new_inode; + } + +out: + if (cur_inode->parent) { + *dir_is_contiguous = cur_inode->parent->is_contiguous; + exfat_free_inode_chain(cur_inode->parent); + } + *new = cur_inode; + return 0; +free_inode: + exfat_free_inode_chain(cur_inode); return ret; } @@ -950,5 +989,5 @@ close(bd.dev_fd); out: - return ret; + return ret ? EXIT_FAILURE : EXIT_SUCCESS; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/fsck/fsck.c new/exfatprogs-1.3.2/fsck/fsck.c --- old/exfatprogs-1.3.1/fsck/fsck.c 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/fsck/fsck.c 2026-03-10 02:29:29.000000000 +0100 @@ -59,6 +59,7 @@ {"help", no_argument, NULL, 'h' }, {"?", no_argument, NULL, '?' }, {"ignore-bad-fs", no_argument, NULL, 'b' }, + {"progress", no_argument, NULL, 'P' }, {NULL, 0, NULL, 0 } }; @@ -72,6 +73,7 @@ fprintf(stderr, "\t-a Repair automatically\n"); fprintf(stderr, "\t-b | --ignore-bad-fs Try to recover even if exfat is not found\n"); fprintf(stderr, "\t-s | --rescue Assign orphaned clusters to files\n"); + fprintf(stderr, "\t-P | --progress Show progress bar\n"); fprintf(stderr, "\t-V | --version Show version\n"); fprintf(stderr, "\t-v | --verbose Print debug\n"); fprintf(stderr, "\t-h | --help Show help\n"); @@ -167,6 +169,8 @@ clus)) return -EINVAL; } + if (exfat_fsck.options & FSCK_OPTS_PROGRESS_BAR) + progress_update(&exfat_fsck.progress_bar, 1); /* This cluster is allocated or not */ if (exfat_get_inode_next_clus(exfat, node, clus, &next)) @@ -245,14 +249,12 @@ struct exfat_inode *node, clus_t *clus_count) { - clus_t clus, next, prev = EXFAT_EOF_CLUSTER; + clus_t clus = node->first_clus, next, prev = EXFAT_EOF_CLUSTER; + *clus_count = 0; if (!exfat_heap_clus(exfat, node->first_clus)) goto out_trunc; - clus = node->first_clus; - *clus_count = 0; - do { if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) { if (exfat_repair_ask(&exfat_fsck, @@ -936,7 +938,7 @@ break; if (need_delete) { exfat_de_iter_get_dirty(iter, i, &dentry); - dentry->type &= EXFAT_DELETE; + dentry->type = EXFAT_DELETE; } } *skip_dentries = i; @@ -1424,7 +1426,7 @@ struct exfat_dentry *dentry; exfat_de_iter_get_dirty(de_iter, 0, &dentry); - dentry->type &= EXFAT_DELETE; + dentry->type = EXFAT_DELETE; } break; } @@ -1756,6 +1758,18 @@ exfat_stat.fixed_count); } +static clus_t count_bitmap_set_bits(struct exfat *exfat) +{ + clus_t count = 0; + size_t i, bytes = exfat->disk_bitmap_size; + + for (i = 0; i + sizeof(uint32_t) <= bytes; i += sizeof(uint32_t)) + count += __builtin_popcount(*(uint32_t *)(exfat->disk_bitmap + i)); + for (; i < bytes; i++) + count += __builtin_popcount(exfat->disk_bitmap[i]); + return count; +} + int main(int argc, char * const argv[]) { struct fsck_user_input ui; @@ -1764,6 +1778,7 @@ struct exfat_inode *root; int c, ret, exit_code; bool version_only = false; + clus_t used_clus_count; memset(&ui, 0, sizeof(ui)); memset(&bd, 0, sizeof(bd)); @@ -1774,7 +1789,7 @@ exfat_err("failed to init locale/codeset\n"); opterr = 0; - while ((c = getopt_long(argc, argv, "arynpbsVvh", opts, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "arynpbsPVvh", opts, NULL)) != EOF) { switch (c) { case 'n': if (ui.options & FSCK_OPTS_REPAIR_ALL) @@ -1803,6 +1818,9 @@ case 's': ui.options |= FSCK_OPTS_RESCUE_CLUS; break; + case 'P': + ui.options |= FSCK_OPTS_PROGRESS_BAR; + break; case 'V': version_only = true; break; @@ -1818,7 +1836,8 @@ } show_version(); - if (optind != argc - 1) + if (optind != argc - 1 || + (ui.options & FSCK_OPTS_REPAIR_ASK && ui.options & FSCK_OPTS_PROGRESS_BAR)) usage(argv[0]); if (version_only) @@ -1879,6 +1898,11 @@ goto out; } + if (exfat_fsck.options & FSCK_OPTS_PROGRESS_BAR) { + used_clus_count = count_bitmap_set_bits(exfat_fsck.exfat); + progress_init(&exfat_fsck.progress_bar, 0, used_clus_count, 0); + } + exfat_debug("verifying directory entries...\n"); ret = exfat_filesystem_check(&exfat_fsck); if (ret) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/fsck/fsck.h new/exfatprogs-1.3.2/fsck/fsck.h --- old/exfatprogs-1.3.1/fsck/fsck.h 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/fsck/fsck.h 2026-03-10 02:29:29.000000000 +0100 @@ -6,6 +6,7 @@ #define _FSCK_H #include "list.h" +#include "utils.h" enum fsck_ui_options { FSCK_OPTS_REPAIR_ASK = 0x01, @@ -16,6 +17,7 @@ FSCK_OPTS_REPAIR_ALL = 0x0f, FSCK_OPTS_IGNORE_BAD_FS_NAME = 0x10, FSCK_OPTS_RESCUE_CLUS = 0x20, + FSCK_OPTS_PROGRESS_BAR = 0x40, }; struct exfat; @@ -30,6 +32,7 @@ bool dirty_fat:1; char *name_hash_bitmap; + struct progress_bar progress_bar; }; off_t exfat_c2o(struct exfat *exfat, unsigned int clus); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/include/libexfat.h new/exfatprogs-1.3.2/include/libexfat.h --- old/exfatprogs-1.3.1/include/libexfat.h 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/include/libexfat.h 2026-03-10 02:29:29.000000000 +0100 @@ -68,6 +68,7 @@ struct exfat_blk_dev { int dev_fd; + int verify_fd; unsigned long long offset; unsigned long long size; unsigned int sector_size; @@ -75,6 +76,7 @@ unsigned long long num_sectors; unsigned int num_clusters; unsigned int cluster_size; + bool isblk; }; struct exfat_user_input { @@ -86,10 +88,13 @@ unsigned int boundary_align; bool pack_bitmap; bool quick; + bool verify; + bool discard; __u16 volume_label[VOLUME_LABEL_MAX_LEN]; int volume_label_len; unsigned int volume_serial; const char *guid; + unsigned char *fat_table_buff; }; struct exfat; @@ -155,6 +160,7 @@ ssize_t exfat_read(int fd, void *buf, size_t size, off_t offset); ssize_t exfat_write(int fd, void *buf, size_t size, off_t offset); ssize_t exfat_write_zero(int fd, size_t size, off_t offset); +int exfat_discard_blocks(int fd, uint64_t start, uint64_t len); size_t exfat_utf16_len(const __le16 *str, size_t max_size); ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size); @@ -171,7 +177,8 @@ int exfat_write_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off); int exfat_write_checksum_sector(struct exfat_blk_dev *bd, - unsigned int checksum, bool is_backup); + struct exfat_user_input *ui, unsigned int checksum, + bool is_backup); char *exfat_conv_volume_label(struct exfat_dentry *vol_entry); int exfat_show_volume_serial(int fd); int exfat_set_volume_serial(struct exfat_blk_dev *bd, @@ -193,7 +200,9 @@ int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs); int exfat_parse_ulong(const char *s, unsigned long *out); int exfat_check_name(__le16 *utf16_name, int len); - +int exfat_check_written_data(struct exfat_blk_dev *bd, const void *buf, + size_t len, off_t off, + const char *what); /* * Exfat Print */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/include/utils.h new/exfatprogs-1.3.2/include/utils.h --- old/exfatprogs-1.3.1/include/utils.h 1970-01-01 01:00:00.000000000 +0100 +++ new/exfatprogs-1.3.2/include/utils.h 2026-03-10 02:29:29.000000000 +0100 @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 Hyunchul Lee <[email protected]> + * + * Portions of the progress bar code derived from ntfsprogs-plus and modified for exfatprogs. + */ + +#ifndef _EXFAT_UTILS_H +#define _EXFAT_UTILS_H + +#include <stdint.h> + +struct progress_bar { + uint32_t start; + uint32_t stop; + uint32_t current; + uint32_t resolution; +#ifdef PROG_CALC_FLOAT + float unit; +#else + uint64_t total; +#endif +}; + +void progress_init(struct progress_bar *p, uint32_t start, uint32_t stop, uint32_t res); +void progress_update(struct progress_bar *p, uint32_t current); +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/include/version.h new/exfatprogs-1.3.2/include/version.h --- old/exfatprogs-1.3.1/include/version.h 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/include/version.h 2026-03-10 02:29:29.000000000 +0100 @@ -5,6 +5,6 @@ #ifndef _VERSION_H -#define EXFAT_PROGS_VERSION "1.3.1" +#define EXFAT_PROGS_VERSION "1.3.2" #endif /* !_VERSION_H */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/label/label.c new/exfatprogs-1.3.2/label/label.c --- old/exfatprogs-1.3.1/label/label.c 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/label/label.c 2026-03-10 02:29:29.000000000 +0100 @@ -127,5 +127,5 @@ close_fd_out: close(bd.dev_fd); out: - return ret; + return ret ? EXIT_FAILURE : EXIT_SUCCESS; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/lib/Android.bp new/exfatprogs-1.3.2/lib/Android.bp --- old/exfatprogs-1.3.1/lib/Android.bp 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/lib/Android.bp 2026-03-10 02:29:29.000000000 +0100 @@ -7,6 +7,7 @@ "libexfat.c", "exfat_fs.c", "exfat_dir.c", + "utils.c", ], defaults: ["exfatprogs-defaults"], } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/lib/Makefile.am new/exfatprogs-1.3.2/lib/Makefile.am --- old/exfatprogs-1.3.1/lib/Makefile.am 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/lib/Makefile.am 2026-03-10 02:29:29.000000000 +0100 @@ -1,4 +1,4 @@ AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common noinst_LIBRARIES = libexfat.a -libexfat_a_SOURCES = libexfat.c exfat_fs.c exfat_dir.c +libexfat_a_SOURCES = libexfat.c exfat_fs.c exfat_dir.c utils.c diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/lib/libexfat.c new/exfatprogs-1.3.2/lib/libexfat.c --- old/exfatprogs-1.3.1/lib/libexfat.c 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/lib/libexfat.c 2026-03-10 02:29:29.000000000 +0100 @@ -3,6 +3,9 @@ * Copyright (C) 2019 Namjae Jeon <[email protected]> */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> @@ -134,6 +137,7 @@ memset(ui, 0, sizeof(struct exfat_user_input)); ui->writeable = true; ui->quick = true; + ui->discard = true; } int exfat_get_blk_dev_info(struct exfat_user_input *ui, @@ -163,6 +167,8 @@ char pathname[sizeof("/sys/dev/block/4294967295:4294967295/start")]; FILE *fp; + bd->isblk = true; + snprintf(pathname, sizeof(pathname), "/sys/dev/block/%u:%u/start", major(st.st_rdev), minor(st.st_rdev)); fp = fopen(pathname, "r"); @@ -206,6 +212,16 @@ ret = 0; bd->dev_fd = fd; + + if (ui->verify) { + bd->verify_fd = open(ui->dev_name, O_RDONLY|O_DIRECT); + if (bd->verify_fd < 0) { + exfat_err("open %s O_DIRECT failed:%s\n", ui->dev_name, + strerror(errno)); + close(fd); + ret = -1; + } + } out: return ret; } @@ -238,6 +254,15 @@ return 0; } +int exfat_discard_blocks(int fd, uint64_t start, uint64_t len) +{ + uint64_t range[2] = { start, len }; + + if (ioctl(fd, BLKDISCARD, &range) < 0) + return errno; + return 0; +} + size_t exfat_utf16_len(const __le16 *str, size_t max_size) { size_t i = 0; @@ -410,8 +435,10 @@ __le16 disk_label[VOLUME_LABEL_MAX_LEN]; volume_label = malloc(VOLUME_LABEL_BUFFER_SIZE); - if (!volume_label) + if (!volume_label) { + exfat_err("Cannot allocate volume_label: out of memory\n"); return NULL; + } memcpy(disk_label, vol_entry->vol_label, sizeof(disk_label)); memset(volume_label, 0, VOLUME_LABEL_BUFFER_SIZE); @@ -684,6 +711,45 @@ return err; } +int exfat_check_written_data(struct exfat_blk_dev *bd, + const void *buf, size_t len, + off_t off, const char *what) +{ + void *verify; + ssize_t n; + size_t sector = bd->sector_size; + int ret = 0; + + ret = fsync(bd->dev_fd); + if (ret) + return ret; + + off_t aligned_off = off & ~(sector - 1); + size_t head = off - aligned_off; + size_t aligned_len = ((head + len + sector - 1) / sector) * sector; + + if (posix_memalign(&verify, sector, aligned_len)) + return -ENOMEM; + memset(verify, 0, aligned_len); + + n = exfat_read(bd->verify_fd, verify, aligned_len, aligned_off); + if (n != (ssize_t)aligned_len) { + exfat_debug("%s verify read failed (off=%llu, len=%zu)\n", + what, (unsigned long long)aligned_off, + aligned_len); + ret = -EIO; + } + + if (memcmp(buf, (unsigned char *)verify + head, len) != 0) { + exfat_debug("%s verify mismatch (off=%llu)\n", + what, (unsigned long long)off); + ret = -EIO; + } + + free(verify); + return ret; +} + int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off) { int ret; @@ -715,7 +781,8 @@ } int exfat_write_checksum_sector(struct exfat_blk_dev *bd, - unsigned int checksum, bool is_backup) + struct exfat_user_input *ui, unsigned int checksum, + bool is_backup) { __le32 *checksum_buf; int ret = 0; @@ -723,8 +790,10 @@ unsigned int sec_idx = CHECKSUM_SEC_IDX; checksum_buf = malloc(bd->sector_size); - if (!checksum_buf) + if (!checksum_buf) { + exfat_err("Cannot allocate checksum_buf: out of memory\n"); return -1; + } if (is_backup) sec_idx += BACKUP_BOOT_SEC_IDX; @@ -738,6 +807,17 @@ goto free; } + if (ui && ui->verify) { + ret = exfat_check_written_data(bd, + checksum_buf, bd->sector_size, + sec_idx * bd->sector_size, + "checksum sector"); + if (ret) { + exfat_err("checksum sector verification failed (read-back mismatch)\n"); + goto free; + } + } + free: free(checksum_buf); return ret; @@ -775,7 +855,8 @@ return ret; } -static int exfat_update_boot_checksum(struct exfat_blk_dev *bd, bool is_backup) +static int exfat_update_boot_checksum(struct exfat_blk_dev *bd, + struct exfat_user_input *ui, bool is_backup) { unsigned int checksum = 0; int ret, sec_idx, backup_sec_idx = 0; @@ -807,7 +888,7 @@ &checksum); } - ret = exfat_write_checksum_sector(bd, checksum, is_backup); + ret = exfat_write_checksum_sector(bd, ui, checksum, is_backup); free_buf: free(buf); @@ -861,13 +942,13 @@ goto free_ppbr; } - ret = exfat_update_boot_checksum(bd, 0); + ret = exfat_update_boot_checksum(bd, ui, 0); if (ret < 0) { exfat_err("main checksum update failed\n"); goto free_ppbr; } - ret = exfat_update_boot_checksum(bd, 1); + ret = exfat_update_boot_checksum(bd, ui, 1); if (ret < 0) exfat_err("backup checksum update failed\n"); free_ppbr: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/lib/utils.c new/exfatprogs-1.3.2/lib/utils.c --- old/exfatprogs-1.3.1/lib/utils.c 1970-01-01 01:00:00.000000000 +0100 +++ new/exfatprogs-1.3.2/lib/utils.c 2026-03-10 02:29:29.000000000 +0100 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Hyunchul Lee <[email protected]> + * + * Portions of the progress bar code derived from ntfsprogs-plus and modified for exfatprogs. + */ + +#include <stdio.h> +#include <inttypes.h> + +#include "utils.h" + +void progress_init(struct progress_bar *p, uint32_t start, uint32_t stop, uint32_t res) +{ + uint64_t total; + + p->start = start; + p->stop = stop; + p->current = start; + + total = stop - start + 1; + if (total <= 0) + total = 1; + +#ifdef PROG_CALC_FLOAT + p->unit = 100.0 / total; + + if (((res * 100 * 100) / total) == 0) + p->resolution = (int)(total / (100 * 100.0)); // 0.01 resolution + else + p->resolution = res; +#else + p->total = total; + + if (((res * 100) / p->total) == 0) { + p->resolution = p->total / 100; + if (!p->resolution) + p->resolution = 1; + } else + p->resolution = res; +#endif +} + +void progress_update(struct progress_bar *p, uint32_t update) +{ +#ifdef PROG_CALC_FLOAT + float percent; +#else + uint32_t percent; +#endif + + p->current += update; + if (p->current != p->stop) { + if ((p->current - p->start) % p->resolution) + return; +#ifdef PROG_CALC_FLOAT + percent = p->unit * p->current; + printf("%6.2f percent completed\r", percent); +#else + percent = (p->current * 100) / p->total; + printf("%3u percent completed\r", percent); +#endif + } else + printf("100.00 percent completed\n"); + fflush(stdout); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/manpages/fsck.exfat.8 new/exfatprogs-1.3.2/manpages/fsck.exfat.8 --- old/exfatprogs-1.3.1/manpages/fsck.exfat.8 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/manpages/fsck.exfat.8 2026-03-10 02:29:29.000000000 +0100 @@ -16,6 +16,8 @@ ] [ .B \-b ] [ +.B \-P +] [ .B \-v ] .I device @@ -59,6 +61,9 @@ .BI \-p Repair the filesystem without user interaction if it can be done safely. .TP +.BI \-P +Show progress bar while checking the filesystem. Cannot be used with -r option. +.TP .BI \-r Repair the filesystem interactively. .TP diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/manpages/mkfs.exfat.8 new/exfatprogs-1.3.2/manpages/mkfs.exfat.8 --- old/exfatprogs-1.3.1/manpages/mkfs.exfat.8 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/manpages/mkfs.exfat.8 2026-03-10 02:29:29.000000000 +0100 @@ -16,6 +16,8 @@ ] [ .B \-f ] [ +.B \-C +] [ .B \-h ] [ .B \-L @@ -103,6 +105,20 @@ .BR \-f ", " \-\-full\-format Performs a full format. This zeros the entire disk device while creating the exFAT filesystem. +.TE +.TP +.BR \-K ", " \-\-no\-discard +Do not attempt to discard blocks. +.TP +.BR \-C ", " \-\-verify\-written +Verify filesystem metadata by reading it back after writing. +This option performs read-back verification of critical exFAT metadata +(boot record, FAT, allocation bitmap, upcase table, and root directory) +after each formatting stage. +It is useful for detecting broken storage devices or devices that falsely +report successful writes without actually persisting data. +This option may slightly degrade formatting performance and is therefore +disabled by default. .TP .BR \-h ", " \-\-help Prints the help and exit. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/mkfs/mkfs.c new/exfatprogs-1.3.2/mkfs/mkfs.c --- old/exfatprogs-1.3.1/mkfs/mkfs.c 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/mkfs/mkfs.c 2026-03-10 02:29:29.000000000 +0100 @@ -127,6 +127,17 @@ goto free_ppbr; } + if (ui->verify) { + ret = exfat_check_written_data(bd, + ppbr, bd->sector_size, + sec_idx * bd->sector_size, + "boot sector"); + if (ret) { + exfat_err("boot sector verification failed (read-back mismatch)\n"); + goto free_ppbr; + } + } + boot_calc_checksum((unsigned char *)ppbr, bd->sector_size, true, checksum); @@ -136,7 +147,8 @@ } static int exfat_write_extended_boot_sectors(struct exfat_blk_dev *bd, - unsigned int *checksum, bool is_backup) + struct exfat_user_input *ui, unsigned int *checksum, + bool is_backup) { char *peb; __le16 *peb_signature; @@ -145,8 +157,10 @@ unsigned int sec_idx = EXBOOT_SEC_IDX; peb = malloc(bd->sector_size); - if (!peb) + if (!peb) { + exfat_err("Cannot allocate peb: out of memory\n"); return -1; + } if (is_backup) sec_idx += BACKUP_BOOT_SEC_IDX; @@ -161,6 +175,17 @@ goto free_peb; } + if (ui->verify) { + ret = exfat_check_written_data(bd, + peb, bd->sector_size, + (sec_idx - 1) * bd->sector_size, + "extended boot sector"); + if (ret) { + exfat_err("extended boot sector verification failed (read-back mismatch)\n"); + goto free_peb; + } + } + boot_calc_checksum((unsigned char *) peb, bd->sector_size, false, checksum); } @@ -171,7 +196,8 @@ } static int exfat_write_oem_sector(struct exfat_blk_dev *bd, - unsigned int *checksum, bool is_backup) + struct exfat_user_input *ui, unsigned int *checksum, + bool is_backup) { char *oem; int ret = 0; @@ -191,6 +217,17 @@ goto free_oem; } + if (ui->verify) { + ret = exfat_check_written_data(bd, + oem, bd->sector_size, + sec_idx * bd->sector_size, + "oem sector"); + if (ret) { + exfat_err("oem sector verification failed (read-back mismatch)\n"); + goto free_oem; + } + } + boot_calc_checksum((unsigned char *)oem, bd->sector_size, false, checksum); @@ -202,6 +239,17 @@ goto free_oem; } + if (ui->verify) { + ret = exfat_check_written_data(bd, + oem, bd->sector_size, + (sec_idx + 1) * bd->sector_size, + "reserved sector"); + if (ret) { + exfat_err("reserved sector verification failed (read-back mismatch)\n"); + goto free_oem; + } + } + boot_calc_checksum((unsigned char *)oem, bd->sector_size, false, checksum); @@ -219,18 +267,18 @@ ret = exfat_write_boot_sector(bd, ui, &checksum, is_backup); if (ret) return ret; - ret = exfat_write_extended_boot_sectors(bd, &checksum, is_backup); + ret = exfat_write_extended_boot_sectors(bd, ui, &checksum, is_backup); if (ret) return ret; - ret = exfat_write_oem_sector(bd, &checksum, is_backup); + ret = exfat_write_oem_sector(bd, ui, &checksum, is_backup); if (ret) return ret; - return exfat_write_checksum_sector(bd, checksum, is_backup); + return exfat_write_checksum_sector(bd, ui, checksum, is_backup); } -static int write_fat_entry(int fd, __le32 clu, - unsigned long long offset) +static int write_fat_entry(struct exfat_user_input *ui, int fd, + __le32 clu, unsigned long long offset) { int nbyte; off_t fat_entry_offset = finfo.fat_byte_off + (offset * sizeof(__le32)); @@ -242,6 +290,11 @@ return -1; } + if (ui->verify) { + memcpy(ui->fat_table_buff + offset * sizeof(__le32), + (__u8 *)&clu, sizeof(__le32)); + } + return 0; } @@ -254,12 +307,12 @@ count = clu + round_up(length, ui->cluster_size) / ui->cluster_size; for (; clu < count - 1; clu++) { - ret = write_fat_entry(fd, cpu_to_le32(clu + 1), clu); + ret = write_fat_entry(ui, fd, cpu_to_le32(clu + 1), clu); if (ret) return ret; } - ret = write_fat_entry(fd, cpu_to_le32(EXFAT_EOF_CLUSTER), clu); + ret = write_fat_entry(ui, fd, cpu_to_le32(EXFAT_EOF_CLUSTER), clu); if (ret) return ret; @@ -270,52 +323,87 @@ struct exfat_user_input *ui) { int ret, clu; + unsigned int fat_table_entries = 0; + + if (ui->verify) { + fat_table_entries = + EXFAT_FIRST_CLUSTER + + DIV_ROUND_UP(finfo.bitmap_byte_len, ui->cluster_size) + + DIV_ROUND_UP(finfo.ut_byte_len, ui->cluster_size) + + DIV_ROUND_UP(finfo.root_byte_len, ui->cluster_size); + + ui->fat_table_buff = calloc(fat_table_entries, sizeof(__le32)); + if (!ui->fat_table_buff) + return -ENOMEM; + } /* fat entry 0 should be media type field(0xF8) */ - ret = write_fat_entry(bd->dev_fd, cpu_to_le32(0xfffffff8), 0); + ret = write_fat_entry(ui, bd->dev_fd, cpu_to_le32(0xfffffff8), 0); if (ret) { exfat_err("fat 0 entry write failed\n"); - return ret; + goto free_fat_table_buff; } /* fat entry 1 is historical precedence(0xFFFFFFFF) */ - ret = write_fat_entry(bd->dev_fd, cpu_to_le32(0xffffffff), 1); + ret = write_fat_entry(ui, bd->dev_fd, cpu_to_le32(0xffffffff), 1); if (ret) { exfat_err("fat 1 entry write failed\n"); - return ret; + goto free_fat_table_buff; } /* write bitmap entries */ clu = write_fat_entries(ui, bd->dev_fd, EXFAT_FIRST_CLUSTER, finfo.bitmap_byte_len); - if (clu < 0) - return ret; + if (clu < 0) { + ret = clu; + goto free_fat_table_buff; + } /* write upcase table entries */ clu = write_fat_entries(ui, bd->dev_fd, clu + 1, finfo.ut_byte_len); - if (clu < 0) - return ret; + if (clu < 0) { + ret = clu; + goto free_fat_table_buff; + } /* write root directory entries */ clu = write_fat_entries(ui, bd->dev_fd, clu + 1, finfo.root_byte_len); - if (clu < 0) - return ret; + if (clu < 0) { + ret = clu; + goto free_fat_table_buff; + } finfo.used_clu_cnt = clu + 1 - EXFAT_FIRST_CLUSTER; exfat_debug("Total used cluster count : %d\n", finfo.used_clu_cnt); + if (ui->verify) { + ret = exfat_check_written_data(bd, + ui->fat_table_buff, fat_table_entries * sizeof(__le32), + finfo.fat_byte_off, + "fat table"); + if (ret) + exfat_err("fat table verification failed (read-back mismatch)\n"); + } + +free_fat_table_buff: + if (ui->verify) + free(ui->fat_table_buff); return ret; } -static int exfat_create_bitmap(struct exfat_blk_dev *bd) +static int exfat_create_bitmap(struct exfat_blk_dev *bd, + struct exfat_user_input *ui) { char *bitmap; unsigned int full_bytes, rem_bits, zero_offset; unsigned int nbytes; + int ret = 0; bitmap = malloc(finfo.bitmap_byte_len); - if (!bitmap) + if (!bitmap) { + exfat_err("Cannot allocate bitmap: out of memory\n"); return -1; + } full_bytes = finfo.used_clu_cnt / 8; rem_bits = finfo.used_clu_cnt % 8; @@ -340,6 +428,18 @@ return -1; } + if (ui->verify) { + ret = exfat_check_written_data(bd, + bitmap, finfo.bitmap_byte_len, + finfo.bitmap_byte_off, + "bitmap"); + if (ret) { + exfat_err("bitmap verification failed (read-back mismatch)\n"); + free(bitmap); + return ret; + } + } + free(bitmap); return 0; } @@ -396,6 +496,17 @@ return -1; } + if (ui->verify) { + ret = exfat_check_written_data(bd, + ed, dentries_len, + finfo.root_byte_off, + "root directory"); + if (ret) { + exfat_err("root directory verification failed (read-back mismatch)\n"); + return ret; + } + + } return 0; } @@ -409,6 +520,8 @@ "\t-b | --boundary-align=size(or suffixed by 'K' or 'M') Specify boundary alignment\n" "\t --pack-bitmap Move bitmap into FAT segment\n" "\t-f | --full-format Full format\n" + "\t-C | --check-written Verify written filesystem metadata by read-back\n" + "\t-K | --no-discard Do not discard blocks\n" "\t-V | --version Show version\n" "\t-q | --quiet Print only errors\n" "\t-v | --verbose Print debug\n" @@ -428,6 +541,8 @@ {"boundary-align", required_argument, NULL, 'b' }, {"pack-bitmap", no_argument, NULL, PACK_BITMAP }, {"full-format", no_argument, NULL, 'f' }, + {"check-written", no_argument, NULL, 'C' }, + {"no-discard", no_argument, NULL, 'K' }, {"version", no_argument, NULL, 'V' }, {"quiet", no_argument, NULL, 'q' }, {"verbose", no_argument, NULL, 'v' }, @@ -553,6 +668,56 @@ return 0; } +static void exfat_discard_dev(struct exfat_blk_dev *bd, + struct exfat_user_input *ui) +{ + uint64_t offset = 0; + uint64_t tmp_step; + int err; + /* Discard the device 2G at a time */ + const uint64_t step = 2ULL << 30; + const uint64_t count = bd->num_sectors * bd->sector_size; + + if (!ui->discard || !bd->isblk) { + exfat_debug("no-discard requested or the device is a file\n"); + return; + } + + /* + * The block discarding happens in smaller batches so it can be + * interrupted prematurely + */ + while (offset < count) { + tmp_step = count - offset; + if (step < tmp_step) + tmp_step = step; + + err = exfat_discard_blocks(bd->dev_fd, offset, tmp_step); + /* + * We intentionally ignore errors from the discard ioctl. It is + * not necessary for the mkfs functionality but just an + * optimization. However we should stop on error. + */ + if (err == 0) { + if (offset == 0) { + exfat_info("Discarding blocks: "); + exfat_debug("BLKDISCARD: "); + } + exfat_debug("%"PRIu64"-%"PRIu64" ", offset, offset + tmp_step); + fflush(stdout); + } else { + exfat_debug("BLKDISCARD: %s\n", strerror(err)); + if (offset > 0) + exfat_info("\n"); + return; + } + + offset += tmp_step; + } + if (offset > 0) + exfat_info("done\n"); +} + static int make_exfat(struct exfat_blk_dev *bd, struct exfat_user_input *ui) { int ret; @@ -580,13 +745,13 @@ return ret; exfat_info("Allocation bitmap creation: "); - ret = exfat_create_bitmap(bd); + ret = exfat_create_bitmap(bd, ui); exfat_info("%s\n", ret ? "failed" : "done"); if (ret) return ret; exfat_info("Upcase table creation: "); - ret = exfat_create_upcase_table(bd); + ret = exfat_create_upcase_table(bd, ui); exfat_info("%s\n", ret ? "failed" : "done"); if (ret) return ret; @@ -640,7 +805,7 @@ exfat_err("failed to init locale/codeset\n"); opterr = 0; - while ((c = getopt_long(argc, argv, "n:L:U:s:c:b:fVqvh", opts, NULL)) != EOF) + while ((c = getopt_long(argc, argv, "n:L:U:s:c:b:fCKVqvh", opts, NULL)) != EOF) switch (c) { /* * Make 'n' option fallthrough to 'L' option for for backward @@ -709,6 +874,12 @@ case 'f': ui.quick = false; break; + case 'C': + ui.verify = true; + break; + case 'K': + ui.discard = false; + break; case 'V': version_only = true; break; @@ -753,6 +924,12 @@ if (ret) goto close; + exfat_discard_dev(&bd, &ui); + /* + * Zeroing out still needs to be conducted as per JESD84-B51 6.6.9: + * "content of an explicitly erased memory range shall be ‘0’ or ‘1’ + * depending on different memory technology," + */ ret = exfat_zero_out_disk(&bd, &ui); if (ret) goto close; @@ -765,10 +942,12 @@ ret = fsync(bd.dev_fd); close: close(bd.dev_fd); + if (ui.verify) + close(bd.verify_fd); out: if (!ret) exfat_info("\nexFAT format complete!\n"); else exfat_err("\nexFAT format fail!\n"); - return ret; + return ret ? EXIT_FAILURE : EXIT_SUCCESS; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/mkfs/mkfs.h new/exfatprogs-1.3.2/mkfs/mkfs.h --- old/exfatprogs-1.3.1/mkfs/mkfs.h 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/mkfs/mkfs.h 2026-03-10 02:29:29.000000000 +0100 @@ -27,6 +27,6 @@ extern struct exfat_mkfs_info finfo; -int exfat_create_upcase_table(struct exfat_blk_dev *bd); +int exfat_create_upcase_table(struct exfat_blk_dev *bd, struct exfat_user_input *ui); #endif /* !_MKFS_H */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/mkfs/upcase.c new/exfatprogs-1.3.2/mkfs/upcase.c --- old/exfatprogs-1.3.1/mkfs/upcase.c 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/mkfs/upcase.c 2026-03-10 02:29:29.000000000 +0100 @@ -13,14 +13,28 @@ #include "mkfs.h" #include "upcase_table.h" -int exfat_create_upcase_table(struct exfat_blk_dev *bd) +int exfat_create_upcase_table(struct exfat_blk_dev *bd, + struct exfat_user_input *ui) { int nbytes; + int ret; nbytes = pwrite(bd->dev_fd, default_upcase_table, EXFAT_UPCASE_TABLE_SIZE, finfo.ut_byte_off); if (nbytes != EXFAT_UPCASE_TABLE_SIZE) return -1; + if (ui->verify) { + ret = exfat_check_written_data(bd, + default_upcase_table, + EXFAT_UPCASE_TABLE_SIZE, + finfo.ut_byte_off, + "upcase table"); + if (ret) { + exfat_err("upcase table verification failed (read-back mismatch)\n"); + return ret; + } + } + return 0; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.3.1/tune/tune.c new/exfatprogs-1.3.2/tune/tune.c --- old/exfatprogs-1.3.1/tune/tune.c 2025-12-15 04:54:48.000000000 +0100 +++ new/exfatprogs-1.3.2/tune/tune.c 2026-03-10 02:29:29.000000000 +0100 @@ -152,5 +152,5 @@ if (exfat) exfat_free_exfat(exfat); out: - return ret; + return ret ? EXIT_FAILURE : EXIT_SUCCESS; }
