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-08-15 22:39:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/exfatprogs (Old) and /work/SRC/openSUSE:Factory/.exfatprogs.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "exfatprogs" Sat Aug 15 22:39:40 2026 rev:26 rq:1371161 version:1.4.3 Changes: -------- --- /work/SRC/openSUSE:Factory/exfatprogs/exfatprogs.changes 2026-06-16 13:46:59.421110250 +0200 +++ /work/SRC/openSUSE:Factory/.exfatprogs.new.1258/exfatprogs.changes 2026-08-15 22:39:46.039772547 +0200 @@ -1,0 +2,24 @@ +Fri Aug 14 06:48:40 UTC 2026 - Michael Vetter <[email protected]> + +- Update to 1.4.3: + Changes: + * exfatprogs: honor the user's full locale for diagnostic messages + and date formatting. + * mkfs.exfat: report final fsync errors and suppress completion + messages with "-q". + * fsck.exfat: increase scan speed when scanning large unused + directory tails. + Bug fixes: + * exfatprogs: fix building DOS attribute utilities with non-Bash shells. + * dump.exfat and fsck.exfat: ignore reserved allocation-bitmap bits + when counting clusters. + * fsck.exfat: reject invalid sector sizes without crashing. + * mkfs.exfat: notify the kernel after creating partition tables. + * mkfs.exfat: generate GUIDs with correct version and variant fields. + * mkfs.exfat and tune.exfat: use standard GUID byte order for GUID + input and output. + * libexfat: fix directory iterator alignment for large offsets. + * exfatprogs: prevent allocation bitmap size overflow near the + maximum cluster count. + +------------------------------------------------------------------- Old: ---- 1.4.2.tar.gz New: ---- 1.4.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ exfatprogs.spec ++++++ --- /var/tmp/diff_new_pack.IyYfoA/_old 2026-08-15 22:39:46.819800011 +0200 +++ /var/tmp/diff_new_pack.IyYfoA/_new 2026-08-15 22:39:46.820800046 +0200 @@ -17,7 +17,7 @@ Name: exfatprogs -Version: 1.4.2 +Version: 1.4.3 Release: 0 Summary: Utilities for exFAT file system maintenance License: GPL-2.0-or-later ++++++ 1.4.2.tar.gz -> 1.4.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/.github/workflows/c-cpp.yml new/exfatprogs-1.4.3/.github/workflows/c-cpp.yml --- old/exfatprogs-1.4.2/.github/workflows/c-cpp.yml 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/.github/workflows/c-cpp.yml 2026-08-14 04:37:18.000000000 +0200 @@ -67,9 +67,15 @@ - name: Build run: | export "CC=${{ matrix.cc }}" - ./autogen.sh && ./configure + ./autogen.sh && ./configure --enable-tests make -j "$(nproc)" sudo make install + - name: Run internal tests with Valgrind + run: | + set -e + pushd ./lib/tests + TEST_PREOP="valgrind --error-exitcode=1" ./run-all.sh + popd - name: Run fsck with Valgrind run: | pushd tests @@ -223,9 +229,16 @@ CFLAGS: --static PKG_CONFIG_PATH: /usr/${{ matrix.host }}/local/lib/pkgconfig run: | - ./configure --host=${{ matrix.host }} + ./configure --host=${{ matrix.host }} --enable-tests make -j$((`nproc`+1)) + - name: Run internal tests + run: | + set -e + pushd ./lib/tests + ./run-all.sh + popd + - name: Test foreign filesystem detection env: EXFAT_TTY_OVERRIDE: 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/NEWS new/exfatprogs-1.4.3/NEWS --- old/exfatprogs-1.4.2/NEWS 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/NEWS 2026-08-14 04:37:18.000000000 +0200 @@ -1,3 +1,27 @@ +exfatprogs 1.4.3 - released 2026-08-14 +====================================== + +CHANGES : + * exfatprogs: honor the user's full locale for diagnostic messages + and date formatting. + * mkfs.exfat: report final fsync errors and suppress completion + messages with "-q". + * fsck.exfat: increase scan speed when scanning large unused + directory tails. + +BUG FIXES : + * exfatprogs: fix building DOS attribute utilities with non-Bash shells. + * dump.exfat and fsck.exfat: ignore reserved allocation-bitmap bits + when counting clusters. + * fsck.exfat: reject invalid sector sizes without crashing. + * mkfs.exfat: notify the kernel after creating partition tables. + * mkfs.exfat: generate GUIDs with correct version and variant fields. + * mkfs.exfat and tune.exfat: use standard GUID byte order for GUID + input and output. + * libexfat: fix directory iterator alignment for large offsets. + * exfatprogs: prevent allocation bitmap size overflow near the + maximum cluster count. + exfatprogs 1.4.2 - released 2026-06-15 ====================================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/README.md new/exfatprogs-1.4.3/README.md --- old/exfatprogs-1.4.2/README.md 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/README.md 2026-08-14 04:37:18.000000000 +0200 @@ -116,8 +116,7 @@ [exfat-fuse fsck]: https://github.com/relan/exfat ## Sending feedback -If you have any issues, please create [issues][1] or contact to [Namjae Jeon](mailto:[email protected]) and -[Hyunchul Lee](mailto:[email protected]). +If you have any issues, please create [issues][1] or contact to [exfat mailing list](mailto:[email protected]). [Contributions][2] are also welcome. [1]: https://github.com/exfatprogs/exfatprogs/issues diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/configure.ac new/exfatprogs-1.4.3/configure.ac --- old/exfatprogs-1.4.2/configure.ac 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/configure.ac 2026-08-14 04:37:18.000000000 +0200 @@ -37,7 +37,7 @@ ]) AC_CHECK_HEADERS([fts.h], [have_fts_h="yes"]) -AM_CONDITIONAL([ENABLE_DOSATTR], [test "x$have_fts_h" == "xyes"]) +AM_CONDITIONAL([ENABLE_DOSATTR], [test "x$have_fts_h" = "xyes"]) AM_COND_IF( [ENABLE_DOSATTR], [AC_CHECK_LIB([fts], [fts_open])], @@ -51,4 +51,10 @@ PKG_CHECK_MODULES([BLKID], [blkid >= 2.20], [AC_DEFINE([HAVE_BLKID], [1], [Define if libblkid is from util-linux])], [PKG_CHECK_MODULES([EXT2_BLKID], [blkid], [AC_DEFINE([HAVE_EXT2_BLKID], [1], [Define if libblkid is from libext2])])]) +AC_ARG_ENABLE([tests], + AS_HELP_STRING([--enable-tests], [build internal test programs]) + [enable_tests=yes] +) +AM_CONDITIONAL([ENABLE_TESTS], [test "x$enable_tests" = xyes]) + AC_OUTPUT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/defrag/defrag.c new/exfatprogs-1.4.3/defrag/defrag.c --- old/exfatprogs-1.4.2/defrag/defrag.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/defrag/defrag.c 2026-08-14 04:37:18.000000000 +0200 @@ -972,8 +972,7 @@ /* step-0: Parameter Processing and Mode Recognition */ - if (!setlocale(LC_CTYPE, "")) - exfat_err("failed to init locale/codeset\n"); + setlocale(LC_ALL, ""); /* * Recognizable command format: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/dosattr/chdosattr.c new/exfatprogs-1.4.3/dosattr/chdosattr.c --- old/exfatprogs-1.4.2/dosattr/chdosattr.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/dosattr/chdosattr.c 2026-08-14 04:37:18.000000000 +0200 @@ -241,8 +241,7 @@ static int c; static int retval; - setlocale(LC_MESSAGES, ""); - setlocale(LC_CTYPE, ""); + setlocale(LC_ALL, ""); if (argc <= 1) usage(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/dosattr/lsdosattr.c new/exfatprogs-1.4.3/dosattr/lsdosattr.c --- old/exfatprogs-1.4.2/dosattr/lsdosattr.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/dosattr/lsdosattr.c 2026-08-14 04:37:18.000000000 +0200 @@ -210,8 +210,7 @@ #define MY_FTSOPTS (FTS_PHYSICAL | FTS_SEEDOT) static int c, retval; - setlocale(LC_MESSAGES, ""); - setlocale(LC_CTYPE, ""); + setlocale(LC_ALL, ""); if (!(argc && *argv)) usage(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/dump/dump.c new/exfatprogs-1.4.3/dump/dump.c --- old/exfatprogs-1.4.2/dump/dump.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/dump/dump.c 2026-08-14 04:37:18.000000000 +0200 @@ -198,7 +198,8 @@ return -EIO; } - used_clus = exfat_count_used_clusters(exfat->disk_bitmap, (size_t)bitmap_len); + used_clus = exfat_count_used_clusters(exfat->disk_bitmap, (size_t)bitmap_len, + exfat->clus_count); exfat_info("\n---------------- Show the statistics ----------------\n"); dump_field("Cluster size", "%u", bd->cluster_size); @@ -900,8 +901,7 @@ exfat_init_user_input(&ui); ui.writeable = false; - if (!setlocale(LC_CTYPE, "")) - exfat_err("failed to init locale/codeset\n"); + setlocale(LC_ALL, ""); opterr = 0; while ((c = getopt_long(argc, argv, "iVhd:s:rc", opts, NULL)) != EOF) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/fsck/fsck.c new/exfatprogs-1.4.3/fsck/fsck.c --- old/exfatprogs-1.4.2/fsck/fsck.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/fsck/fsck.c 2026-08-14 04:37:18.000000000 +0200 @@ -513,8 +513,8 @@ bool ignore_bad_fs_name) { struct pbr *boot_sect; - unsigned int sect_size; - int ret; + unsigned int sect_size = 0; /* zero if invalid */ + int ret = -EINVAL; /* First, find out the exfat sector size */ boot_sect = malloc(sizeof(*boot_sect)); @@ -534,12 +534,14 @@ return -ENOTSUP; } - sect_size = 1 << boot_sect->bsx.sect_size_bits; + /* check boot regions */ + if (9 <= boot_sect->bsx.sect_size_bits && boot_sect->bsx.sect_size_bits <= 12) { + sect_size = 1 << boot_sect->bsx.sect_size_bits; + ret = read_boot_region(blkdev, bs, BOOT_SEC_IDX, sect_size, true); + } else + exfat_err("invalid sector size\n"); free(boot_sect); - /* check boot regions */ - ret = read_boot_region(blkdev, bs, - BOOT_SEC_IDX, sect_size, true); if (ret == -EINVAL && exfat_repair_ask(&exfat_fsck, ER_BS_BOOT_REGION, "boot region is corrupted. try to restore the region from backup" @@ -547,7 +549,7 @@ const unsigned int sector_sizes[] = {512, 4096, 1024, 2048}; unsigned int i; - if (sect_size >= 512 && sect_size <= EXFAT_MAX_SECTOR_SIZE) { + if (sect_size) { ret = read_boot_region(blkdev, bs, BACKUP_BOOT_SEC_IDX, sect_size, false); @@ -1302,12 +1304,63 @@ return retval; } -/* - * Checks whether there are other directory entries following the unused - * directory entries. If so, sets the unused directory entries to the deleted - * directory entries(Type 0x7F). - */ -static int check_unused_dentry(struct exfat_de_iter *de_iter, +static int scan_unused_dentry(struct exfat_fsck *fsck, + struct exfat_de_iter *de_iter, struct exfat_inode *dir) +{ + struct exfat *exfat = de_iter->exfat; + off_t file_offset; + clus_t clus; + clus_t clus_idx; + unsigned int clus_offset; + unsigned int size; + unsigned int offset; + int err; + + file_offset = exfat_de_iter_file_offset(de_iter); + if ((uint64_t)file_offset >= dir->size || + dir->size - (uint64_t)file_offset <= DENTRY_SIZE) + return 0; + + if (!fsck->scan_buffer) + return -ENOMEM; + + file_offset += DENTRY_SIZE; + clus_idx = file_offset / exfat->clus_size; + clus_offset = file_offset % exfat->clus_size; + err = exfat_get_clus(exfat, dir, clus_idx, &clus); + if (err) + return err; + + while ((uint64_t)file_offset < dir->size) { + size = MIN(fsck->scan_size, exfat->clus_size - clus_offset); + size = MIN(size, (unsigned int)(dir->size - file_offset)); + if (!exfat_read_full(exfat->blk_dev->dev_fd, fsck->scan_buffer, size, + exfat_c2o(exfat, clus) + clus_offset)) + return -EIO; + + for (offset = 0; offset + DENTRY_SIZE <= size; + offset += DENTRY_SIZE) { + if (fsck->scan_buffer[offset] != EXFAT_LAST) + return 1; + } + + file_offset += size; + clus_offset += size; + if (clus_offset == exfat->clus_size && + (uint64_t)file_offset < dir->size) { + err = exfat_get_inode_next_clus(exfat, dir, clus, &clus); + if (err) + return err; + if (clus == EXFAT_EOF_CLUSTER) + return -EIO; + clus_offset = 0; + } + } + + return 0; +} + +static int check_unused_dentry_slow(struct exfat_de_iter *de_iter, struct exfat_inode *dir) { int ret, i; @@ -1353,6 +1406,23 @@ return 1; } +/* + * Checks whether there are other directory entries following the unused + * directory entries. If so, sets the unused directory entries to the deleted + * directory entries(Type 0x7F). + */ +static int check_unused_dentry(struct exfat_fsck *fsck, + struct exfat_de_iter *de_iter, struct exfat_inode *dir) +{ + int ret; + + ret = scan_unused_dentry(fsck, de_iter, dir); + if (ret <= 0) + return ret; + + return check_unused_dentry_slow(de_iter, dir); +} + static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) { struct exfat *exfat = fsck->exfat; @@ -1409,7 +1479,7 @@ } break; case EXFAT_LAST: - ret = check_unused_dentry(de_iter, dir); + ret = check_unused_dentry(fsck, de_iter, dir); if (ret < 0) { exfat_stat.error_count++; break; @@ -1921,8 +1991,7 @@ print_level = EXFAT_ERROR; - if (!setlocale(LC_CTYPE, "")) - exfat_err("failed to init locale/codeset\n"); + setlocale(LC_ALL, ""); opterr = 0; while ((c = getopt_long(argc, argv, "arynpbsPVvh", opts, NULL)) != EOF) { @@ -2052,6 +2121,13 @@ goto err; } + exfat_fsck.scan_size = MIN(exfat_fsck.exfat->clus_size, 128 * KB); + exfat_fsck.scan_buffer = malloc(exfat_fsck.scan_size); + if (!exfat_fsck.scan_buffer) { + ret = -ENOMEM; + goto err; + } + if ((exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) && exfat_mark_volume_dirty(exfat_fsck.exfat, true)) { ret = -EIO; @@ -2067,7 +2143,8 @@ if (exfat_fsck.options & FSCK_OPTS_PROGRESS_BAR) { used_clus_count = exfat_count_used_clusters(exfat_fsck.exfat->disk_bitmap, - (size_t)exfat_fsck.exfat->disk_bitmap_size); + (size_t)exfat_fsck.exfat->disk_bitmap_size, + exfat_fsck.exfat->clus_count); progress_init(&exfat_fsck.progress_bar, 0, used_clus_count, 0); } @@ -2115,6 +2192,7 @@ if (exfat_fsck.buffer_desc) exfat_free_buffer(exfat_fsck.exfat, exfat_fsck.buffer_desc); + free(exfat_fsck.scan_buffer); if (exfat_fsck.exfat) exfat_free_exfat(exfat_fsck.exfat); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/fsck/fsck.h new/exfatprogs-1.4.3/fsck/fsck.h --- old/exfatprogs-1.4.2/fsck/fsck.h 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/fsck/fsck.h 2026-08-14 04:37:18.000000000 +0200 @@ -27,6 +27,8 @@ struct exfat *exfat; struct exfat_de_iter de_iter; struct buffer_desc *buffer_desc; /* cluster * 2 */ + unsigned char *scan_buffer; + unsigned int scan_size; enum fsck_ui_options options; int mbr; /* negative to clear, positive to put */ bool dirty:1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/include/libexfat.h new/exfatprogs-1.4.3/include/libexfat.h --- old/exfatprogs-1.4.2/include/libexfat.h 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/include/libexfat.h 2026-08-14 04:37:18.000000000 +0200 @@ -125,22 +125,14 @@ struct exfat_user_input { const char *dev_name; - bool writeable; + const char *guid; + unsigned char *fat_table_buff; unsigned int sector_size; unsigned int cluster_size; unsigned int sec_per_clu; unsigned int boundary_align; - bool pack_bitmap; - bool quick; - bool force; - 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; - + int volume_label_len; struct { const char *file; const unsigned char *table; @@ -148,10 +140,15 @@ size_t len; void (*free)(struct exfat_user_input *ui); } upcase; - const char *bootcode_msg; - enum exfat_part_table_type part_table; + __u16 volume_label[VOLUME_LABEL_MAX_LEN]; + bool writeable:1; + bool pack_bitmap:1; + bool quick:1; + bool force:1; + bool verify:1; + bool discard:1; }; /* Returns true if the option used or the option argument is not an empty string */ @@ -163,27 +160,36 @@ struct exfat; struct exfat_inode; -#ifdef WORDS_BIGENDIAN -typedef __u8 bitmap_t; +#ifdef __BITS_PER_LONG +#define BITS_PER __BITS_PER_LONG #else -typedef __u32 bitmap_t; +/* Non-Linux polyfill(use C23) */ +#define BITS_PER ULONG_WIDTH #endif -#define BITS_PER (sizeof(bitmap_t) * 8) -#define BIT_MASK(__c) (1 << ((__c) % BITS_PER)) -#define BIT_ENTRY(__c) ((__c) / BITS_PER) +#if BITS_PER == 64 +typedef __u64 bitmap_t; +#define BIT_MASK(__c) cpu_to_le64((bitmap_t)1 << ((__c) % BITS_PER)) +#elif BITS_PER == 32 +typedef __u32 bitmap_t; +#define BIT_MASK(__c) cpu_to_le32((bitmap_t)1 << ((__c) % BITS_PER)) +#else +#error "BITS_PER neither 32 or 64" +#endif +#define BIT_ENTRY(__c) ((__c) / BITS_PER) +#define BITMAP_WORD_AT(bmap, bit) ((bitmap_t *)(bmap))[BIT_ENTRY(bit)] #define EXFAT_BITMAP_SIZE(__c_count) \ - (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t)) + (DIV_ROUND_UP((uint64_t)__c_count, BITS_PER) * sizeof(bitmap_t)) #define BITMAP_GET(bmap, bit) \ - (((bitmap_t *)(bmap))[BIT_ENTRY(bit)] & BIT_MASK(bit)) + (BITMAP_WORD_AT(bmap, bit) & BIT_MASK(bit)) #define BITMAP_SET(bmap, bit) \ - (((bitmap_t *)(bmap))[BIT_ENTRY(bit)] |= BIT_MASK(bit)) + (BITMAP_WORD_AT(bmap, bit) |= BIT_MASK(bit)) #define BITMAP_CLEAR(bmap, bit) \ - (((bitmap_t *)(bmap))[BIT_ENTRY(bit)] &= ~BIT_MASK(bit)) + (BITMAP_WORD_AT(bmap, bit) &= ~BIT_MASK(bit)) static inline bool exfat_bitmap_get(unsigned char *bmap, clus_t c) { @@ -202,7 +208,8 @@ static inline void exfat_bitmap_clear(unsigned char *bmap, clus_t c) { clus_t cc = c - EXFAT_FIRST_CLUSTER; - (((bitmap_t *)(bmap))[BIT_ENTRY(cc)] &= ~BIT_MASK(cc)); + + BITMAP_CLEAR(bmap, cc); } void exfat_bitmap_set_range(struct exfat *exfat, unsigned char *bitmap, @@ -212,9 +219,16 @@ int exfat_bitmap_find_one(struct exfat *exfat, unsigned char *bmap, clus_t start_clu, clus_t *next); /* - * Count ones in the bitmap. The function won't handle unaligned bitmaps. + * Count ones in the bitmap + * + * Reads up to the smallest of `size` bytes or the bytes inferred from + * `total_clus` are read from `bitmap`. + * + * The function won't handle unaligned memory access. `bitmap` is required to be + * at a word boundary(unsigned long). */ -unsigned int exfat_count_used_clusters(const void *bitmap, const size_t bitmap_len); +unsigned int exfat_count_used_clusters(const void *bitmap, const size_t size, + const unsigned int total_clus); void show_version(void); @@ -426,8 +440,8 @@ #define exfat_print_guid(f, msg, guid) \ f("%s: %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",\ (msg), \ - (guid)[0], (guid)[1], (guid)[2], (guid)[3], \ - (guid)[4], (guid)[5], (guid)[6], (guid)[7], \ + (guid)[3], (guid)[2], (guid)[1], (guid)[0], \ + (guid)[5], (guid)[4], (guid)[7], (guid)[6], \ (guid)[8], (guid)[9], (guid)[10], (guid)[11], \ (guid)[12], (guid)[13], (guid)[14], (guid)[15]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/include/version.h new/exfatprogs-1.4.3/include/version.h --- old/exfatprogs-1.4.2/include/version.h 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/include/version.h 2026-08-14 04:37:18.000000000 +0200 @@ -5,7 +5,7 @@ #ifndef _VERSION_H -#define EXFAT_PROGS_VERSION "1.4.2" -#define EXFAT_PROGS_RELEASE_DATE "2026-06-15" +#define EXFAT_PROGS_VERSION "1.4.3" +#define EXFAT_PROGS_RELEASE_DATE "2026-08-14" #endif /* !_VERSION_H */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/label/label.c new/exfatprogs-1.4.3/label/label.c --- old/exfatprogs-1.4.2/label/label.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/label/label.c 2026-08-14 04:37:18.000000000 +0200 @@ -47,8 +47,7 @@ exfat_init_blk_dev_info(&bd); exfat_init_user_input(&ui); - if (!setlocale(LC_CTYPE, "")) - exfat_err("failed to init locale/codeset\n"); + setlocale(LC_ALL, ""); if (argc == 2) flags = EXFAT_GET_VOLUME_LABEL; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/lib/Makefile.am new/exfatprogs-1.4.3/lib/Makefile.am --- old/exfatprogs-1.4.2/lib/Makefile.am 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/lib/Makefile.am 2026-08-14 04:37:18.000000000 +0200 @@ -2,3 +2,12 @@ noinst_LIBRARIES = libexfat.a libexfat_a_SOURCES = libexfat.c exfat_fs.c exfat_dir.c utils.c + +if ENABLE_TESTS + +noinst_PROGRAMS = tests/suite0000-bitmap + +tests_suite0000_bitmap_SOURCES = tests/suite0000-bitmap.c +tests_suite0000_bitmap_LDADD = libexfat.a + +endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/lib/exfat_dir.c new/exfatprogs-1.4.3/lib/exfat_dir.c --- old/exfatprogs-1.4.2/lib/exfat_dir.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/lib/exfat_dir.c 2026-08-14 04:37:18.000000000 +0200 @@ -390,7 +390,7 @@ out: iter->max_skip_dentries = 0; iter->de_file_offset = file_offset; - iter->next_read_offset = (file_offset & ~(iter->read_size - 1)) + iter->read_size; + iter->next_read_offset = (file_offset & ~((off_t)iter->read_size - 1)) + iter->read_size; return 0; } @@ -763,8 +763,8 @@ } } - dset[1].dentry.stream.valid_size = cpu_to_le64(ccount * exfat->clus_size); - dset[1].dentry.stream.size = cpu_to_le64(ccount * exfat->clus_size); + dset[1].dentry.stream.valid_size = cpu_to_le64((uint64_t)ccount * exfat->clus_size); + dset[1].dentry.stream.size = cpu_to_le64((uint64_t)ccount * exfat->clus_size); if (start_clu) dset[1].dentry.stream.start_clu = cpu_to_le32(start_clu); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/lib/libexfat.c new/exfatprogs-1.4.3/lib/libexfat.c --- old/exfatprogs-1.4.2/lib/libexfat.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/lib/libexfat.c 2026-08-14 04:37:18.000000000 +0200 @@ -115,17 +115,35 @@ start_clu, next, 1); } -unsigned int exfat_count_used_clusters(const void *bitmap, const size_t bitmap_len) +unsigned int exfat_count_used_clusters(const void *bitmap, const size_t size, + const unsigned int total_clus) { - const size_t lc = bitmap_len / sizeof(unsigned long); + const size_t content_len = DIV_ROUND_UP(total_clus, 8); + const size_t lb_index = content_len - 1; + const size_t calc_len = MIN(size, content_len); + const unsigned int last_bits = total_clus % CHAR_BIT; + const size_t lc = calc_len / sizeof(unsigned long); unsigned int ret = 0; assert((uintptr_t)bitmap % sizeof(unsigned long) == 0); for (size_t i = 0; i < lc; i++) - ret += __builtin_popcountl(((unsigned long*)bitmap)[i]); - for (size_t i = lc * sizeof(unsigned long); i < bitmap_len; i++) - ret += __builtin_popcountl(((unsigned char*)bitmap)[i]); + ret += __builtin_popcountl(((unsigned long *)bitmap)[i]); + for (size_t i = lc * sizeof(unsigned long); i < calc_len; i++) + ret += __builtin_popcountl(((unsigned char *)bitmap)[i]); + + /* + * Subtract the garbage ones in the last byte that might have been + * counted. This is much simpler than trying to mask the last byte on + * the fly. + */ + if (lb_index < calc_len && last_bits != 0) { + const unsigned char gmask = (1 << last_bits) - 1; + unsigned char lb = ((unsigned char *)bitmap)[lb_index]; + + lb &= ~gmask; + ret -= __builtin_popcountl(lb); + } return ret; } @@ -978,6 +996,7 @@ static int set_guid(__u8 *guid, const char *input) { + __u8 buf[EXFAT_GUID_LEN]; int i, j, zero_len = 0; int len = strlen(input); @@ -1004,9 +1023,9 @@ } if (j & 1) - guid[j >> 1] |= ch; + buf[j >> 1] |= ch; else - guid[j >> 1] = ch << 4; + buf[j >> 1] = ch << 4; j++; @@ -1019,6 +1038,27 @@ return -EINVAL; } + guid[0] = buf[3]; + guid[1] = buf[2]; + guid[2] = buf[1]; + guid[3] = buf[0]; + + guid[4] = buf[5]; + guid[5] = buf[4]; + + guid[6] = buf[7]; + guid[7] = buf[6]; + + guid[8] = buf[8]; + guid[9] = buf[9]; + + guid[10] = buf[10]; + guid[11] = buf[11]; + guid[12] = buf[12]; + guid[13] = buf[13]; + guid[14] = buf[14]; + guid[15] = buf[15]; + return 0; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/lib/tests/run-all.sh new/exfatprogs-1.4.3/lib/tests/run-all.sh --- old/exfatprogs-1.4.2/lib/tests/run-all.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/exfatprogs-1.4.3/lib/tests/run-all.sh 2026-08-14 04:37:18.000000000 +0200 @@ -0,0 +1,32 @@ +#!/bin/sh + +main() +{ + local tot=0 + local ok=0 + local fail=0 + + while read tc + do + tot=$(( tot + 1 )) + + echo -n "$tc: " + eval $TEST_PREOP "$tc" $TEST_POSTOP + if [ "$?" -eq 0 ]; then + echo "PASS" + ok=$(( ok + 1 )) + else + echo "FAIL" + fail=$(( fail + 1 )) + fi + done + + echo "PASS: $ok, FAIL: $fail (TOTAL: $tot)" + + if [ "$fail" -gt 0 ]; then + exit 1 + fi + exit 0 +} + +find . -mindepth 1 -maxdepth 1 -executable -name 'suite[[:digit:]]*' | sort | main diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/lib/tests/suite0000-bitmap.c new/exfatprogs-1.4.3/lib/tests/suite0000-bitmap.c --- old/exfatprogs-1.4.2/lib/tests/suite0000-bitmap.c 1970-01-01 01:00:00.000000000 +0100 +++ new/exfatprogs-1.4.3/lib/tests/suite0000-bitmap.c 2026-08-14 04:37:18.000000000 +0200 @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <assert.h> + +#include "exfat_ondisk.h" +#include "libexfat.h" + +/* exfat_count_used_clusters() out of bounds checks. */ +static void test_count_used_clusters_0(void) +{ + static bitmap_t bm[2]; + const static unsigned int tot_clus = sizeof(bm) / sizeof(bm[0]) * BITS_PER; + unsigned int ret; + + memset(&bm, 0xFF, sizeof(bm)); + + /* This is no-op. */ + ret = exfat_count_used_clusters(&bm, 0, tot_clus); + assert(ret == 0); + + /* normal use cases */ + + ret = exfat_count_used_clusters(&bm, sizeof(bm), tot_clus); + assert(ret == tot_clus); + + for (unsigned int i = 0; i <= tot_clus; i++) { + ret = exfat_count_used_clusters(&bm, sizeof(bm), i); + assert(ret == i); + } + + /* alloc len < content len */ + for (unsigned int i = tot_clus + 1; i < tot_clus + 10; i++) { + ret = exfat_count_used_clusters(&bm, sizeof(bm), i); + assert(ret == tot_clus); + } +} + +/* Tests if exfat_count_used_clusters() doesn't count the garbage ones at the end. */ +static void test_count_used_clusters_1(void) +{ + static const unsigned int END = 0x1000; + bool seen_garbage = false; + + for (unsigned int clus_cnt = 1; clus_cnt < END; clus_cnt++) { + const size_t bm_len = DIV_ROUND_UP(clus_cnt, 8); + const size_t bm_size = EXFAT_BITMAP_SIZE(clus_cnt); + unsigned char *m = malloc(bm_size); + unsigned int ret; + + assert(bm_len <= bm_size); + + /* All ones. */ + memset(m, UCHAR_MAX, bm_size); + ret = exfat_count_used_clusters(m, bm_len, clus_cnt); + assert(ret == clus_cnt); + + /* All valid bits zeros, all garbage ones. */ + for (clus_t i = 0, j = EXFAT_FIRST_CLUSTER; i < clus_cnt; i++, j++) + exfat_bitmap_clear(m, j); + ret = exfat_count_used_clusters(m, bm_len, clus_cnt); + assert(ret == 0); + + /* Make sure this test is valid(garbage should still be there). */ + if (m[bm_len - 1] != 0) + seen_garbage = true; + + free(m); + } + + assert(seen_garbage); +} + +/* Tests bitmap manipulation functions. */ +static void test_bitmap_func(void) +{ + static const clus_t ARR[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 1023, + 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1032, + 65543, 65544, + 0 + }; + + for (const clus_t *cc = ARR; *cc != 0; cc++) { + const size_t bm_len = DIV_ROUND_UP(*cc, 8); + const size_t bm_size = EXFAT_BITMAP_SIZE(*cc); + void *m = calloc(bm_size, 1); + unsigned int clus_cnt; + + assert(bm_len > 0 && bm_len <= bm_size); + + /* Fault the pages just for the sake of it. */ + clus_cnt = exfat_count_used_clusters(m, bm_len, *cc); + assert(clus_cnt == 0); + + /* 1x1 */ + for (clus_t i = 0, j = EXFAT_FIRST_CLUSTER; i < *cc; i++, j++) { + bool b; + + /* Test set(). */ + exfat_bitmap_set(m, j); + b = exfat_bitmap_get(m, j); + clus_cnt = exfat_count_used_clusters(m, bm_len, *cc); + assert(b); + assert(clus_cnt == 1); + + /* Test clear(). */ + exfat_bitmap_clear(m, j); + clus_cnt = exfat_count_used_clusters(m, bm_len, *cc); + b = exfat_bitmap_get(m, j); + assert(!b); + assert(clus_cnt == 0); + } + + /* SxS */ + for (clus_t i = 0, j = EXFAT_FIRST_CLUSTER; i < *cc - 1; i++, j++) { + bool b1, b2; + + /* Test set(). */ + exfat_bitmap_set(m, j); + b1 = exfat_bitmap_get(m, j); + b2 = exfat_bitmap_get(m, j + 1); + assert(b1 && !b2); + clus_cnt = exfat_count_used_clusters(m, bm_len, *cc); + assert(clus_cnt == 1); + + exfat_bitmap_set(m, j + 1); + b1 = exfat_bitmap_get(m, j); + b2 = exfat_bitmap_get(m, j + 1); + assert(b1 && b2); + clus_cnt = exfat_count_used_clusters(m, bm_len, *cc); + assert(clus_cnt == 2); + + /* Test clear(). */ + exfat_bitmap_clear(m, j); + b1 = exfat_bitmap_get(m, j); + b2 = exfat_bitmap_get(m, j + 1); + assert(!b1 && b2); + clus_cnt = exfat_count_used_clusters(m, bm_len, *cc); + assert(clus_cnt == 1); + + exfat_bitmap_clear(m, j + 1); + b1 = exfat_bitmap_get(m, j); + b2 = exfat_bitmap_get(m, j + 1); + assert(!b1 && !b2); + clus_cnt = exfat_count_used_clusters(m, bm_len, *cc); + assert(clus_cnt == 0); + } + + free(m); + } +} + +/* + * Data-driven test of bitmap manipulation functions against endianness confusion + * + * This doesn't really mean anything on LE machines as the on-disk format is + * already in LE. Make sure this is run on BE machines. + */ +static void test_bitmap_endian(void) +{ + static const unsigned char EXPECTED_1[] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + static const unsigned char EXPECTED_2[] = { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 + }; + static const unsigned char EXPECTED_3[] = { + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 + }; + unsigned char buf[sizeof(EXPECTED_1)]; + int ret; + + assert(sizeof(buf) % sizeof(bitmap_t) == 0); + assert(sizeof(buf) == sizeof(EXPECTED_1)); + assert(sizeof(buf) == sizeof(EXPECTED_2)); + assert(sizeof(buf) == sizeof(EXPECTED_3)); + + memset(buf, 0, sizeof(buf)); + exfat_bitmap_set(buf, EXFAT_FIRST_CLUSTER + 0); + ret = memcmp(buf, EXPECTED_1, sizeof(buf)); + assert(ret == 0); + + memset(buf, 0, sizeof(buf)); + exfat_bitmap_set(buf, EXFAT_FIRST_CLUSTER + 32); + ret = memcmp(buf, EXPECTED_2, sizeof(buf)); + assert(ret == 0); + + memset(buf, 0, sizeof(buf)); + exfat_bitmap_set(buf, EXFAT_FIRST_CLUSTER + 0); + exfat_bitmap_set(buf, EXFAT_FIRST_CLUSTER + 32); + ret = memcmp(buf, EXPECTED_3, sizeof(buf)); + assert(ret == 0); +} + +/* Tests bitmap size calculation near the maximum cluster count. */ +static void test_bitmap_size(void) +{ + const clus_t clus_cnt = EXFAT_MAX_NUM_CLUSTER - 1; + const size_t expected = + DIV_ROUND_UP((uint64_t)clus_cnt, BITS_PER) * + sizeof(bitmap_t); + const size_t actual = EXFAT_BITMAP_SIZE(clus_cnt); + + assert(expected != 0); + assert(actual == expected); +} + +int main(int argc, char *argv[]) +{ + test_bitmap_size(); + test_count_used_clusters_0(); + test_count_used_clusters_1(); + test_bitmap_func(); + test_bitmap_endian(); + + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/mkfs/mkfs.c new/exfatprogs-1.4.3/mkfs/mkfs.c --- old/exfatprogs-1.4.2/mkfs/mkfs.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/mkfs/mkfs.c 2026-08-14 04:37:18.000000000 +0200 @@ -11,6 +11,7 @@ #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> +#include <linux/fs.h> #include <fcntl.h> #include <getopt.h> #include <inttypes.h> @@ -1446,8 +1447,7 @@ exfat_init_blk_dev_info(&bd); exfat_init_user_input(&ui); - if (!setlocale(LC_CTYPE, "")) - exfat_err("failed to init locale/codeset\n"); + setlocale(LC_ALL, ""); opterr = 0; while ((c = getopt_long(argc, argv, "n:L:U:s:c:b:P:fFCKVqvh", opts, NULL)) != EOF) @@ -1695,8 +1695,24 @@ } } - exfat_info("Synchronizing...\n"); + if (!quiet) + exfat_info("Synchronizing...\n"); ret = fsync(bd.dev_fd); + if (ret) { + exfat_err("Sync failed: %s\n", strerror(errno)); + goto out; + } + + if (ui.part_table && bd.isblk) { + errno = 0; + if (ioctl(bd.dev_fd, BLKRRPART) != 0) { + exfat_err("BLKRRPART ioctl(): %s\n", strerror(errno)); + exfat_err("Failed to inform the kernel of the new partition table.\n" + "The volume may not show up and you'll have to reconnect " + "the device or reboot to be able to use the volume.\n"); + } + } + out: if (ret && gptwo_tried) { exfat_info("Wiping out GPT structures...\n"); @@ -1718,8 +1734,10 @@ exfat_deinit_blk_dev_info(&bd); exfat_deinit_user_input(&ui); - if (!ret) - exfat_info("\nexFAT format complete!\n"); + if (!ret) { + if (!quiet) + exfat_info("\nexFAT format complete!\n"); + } else exfat_err("\nexFAT format fail!\n"); return ret ? EXIT_FAILURE : EXIT_SUCCESS; @@ -1793,8 +1811,8 @@ uint32_t a; uint16_t b; uint8_t ver[2]; - uint16_t c; - uint32_t d; + uint8_t var[2]; + uint32_t c; } parts; } __attribute__((__packed__)) rnd = { 0, }; struct timespec ts[2] = { 0, }; @@ -1824,8 +1842,9 @@ if (memcmp(&rnd, zm, 16) == 0) continue; - rnd.parts.ver[0] &= 0xF0; - rnd.parts.ver[0] |= 0x04; + rnd.parts.ver[1] &= 0x0F; + rnd.parts.ver[1] |= 0x40; + rnd.parts.var[0] = (rnd.parts.var[0] & 0x3F) | 0x80; memcpy(out, &rnd, 16); return; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/exfatprogs-1.4.2/tune/tune.c new/exfatprogs-1.4.3/tune/tune.c --- old/exfatprogs-1.4.2/tune/tune.c 2026-06-15 02:56:33.000000000 +0200 +++ new/exfatprogs-1.4.3/tune/tune.c 2026-08-14 04:37:18.000000000 +0200 @@ -60,8 +60,7 @@ exfat_init_blk_dev_info(&bd); exfat_init_user_input(&ui); - if (!setlocale(LC_CTYPE, "")) - exfat_err("failed to init locale/codeset\n"); + setlocale(LC_ALL, ""); opterr = 0; while ((c = getopt_long(argc, argv, "I:iL:lU:uVvh", opts, NULL)) != EOF)
