# other stuff
EXTRA_DIST = \
include \
+ tests \
Android.bp \
lib/Android.bp \
mkfs/Android.bp \
# other stuff
EXTRA_DIST = \
include \
+ tests \
Android.bp \
lib/Android.bp \
mkfs/Android.bp \
+exfatprogs 1.2.5 - released 2024-08-06
+======================================
+
+CHANGES :
+ * exfatprogs: remove the limitation that the device
+ path length cannot exceed 254 bytes.
+ * exfatprogs: include the test images in the release
+ package.
+
+NEW FEATURES :
+ * fsck.exfat: check and repair the filename which has
+ invalid characters.
+
+BUG FIXES :
+ * tune.exfat: check whether the volume has invalid
+ characters correctly.
+ * fsck.exfat: check whether the filename and volume
+ has invalid characters correctly.
+ * fsck.exfat: fix endianess issues which happen
+ in the big-endian system.
+
exfatprogs 1.2.4 - released 2024-06-17
======================================
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for exfatprogs 1.2.4.
+# Generated by GNU Autoconf 2.69 for exfatprogs 1.2.5.
#
# Report bugs to <linkinjeon@kernel.org>.
#
# Identity of this package.
PACKAGE_NAME='exfatprogs'
PACKAGE_TARNAME='exfatprogs'
-PACKAGE_VERSION='1.2.4'
-PACKAGE_STRING='exfatprogs 1.2.4'
+PACKAGE_VERSION='1.2.5'
+PACKAGE_STRING='exfatprogs 1.2.5'
PACKAGE_BUGREPORT='linkinjeon@kernel.org'
PACKAGE_URL='https://github.com/exfatprogs/exfatprogs'
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures exfatprogs 1.2.4 to adapt to many kinds of systems.
+\`configure' configures exfatprogs 1.2.5 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of exfatprogs 1.2.4:";;
+ short | recursive ) echo "Configuration of exfatprogs 1.2.5:";;
esac
cat <<\_ACEOF
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-exfatprogs configure 1.2.4
+exfatprogs configure 1.2.5
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by exfatprogs $as_me 1.2.4, which was
+It was created by exfatprogs $as_me 1.2.5, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
# Define the identity of the package.
PACKAGE='exfatprogs'
- VERSION='1.2.4'
+ VERSION='1.2.5'
cat >>confdefs.h <<_ACEOF
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by exfatprogs $as_me 1.2.4, which was
+This file was extended by exfatprogs $as_me 1.2.5, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-exfatprogs config.status 1.2.4
+exfatprogs config.status 1.2.5
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
if (version_only)
exit(EXIT_FAILURE);
- if (argc < 2)
+ if (argc - optind != 1)
usage();
- memset(ui.dev_name, 0, sizeof(ui.dev_name));
- snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", argv[1]);
+ ui.dev_name = argv[1];
ret = exfat_get_blk_dev_info(&ui, &bd);
if (ret < 0)
}
memset(&ui, 0, sizeof(ui));
- snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", blkdev_path);
+ ui.dev_name = blkdev_path;
if (restore)
ui.writeable = true;
else
{
int ret;
struct exfat_lookup_filter filter;
- char filename[PATH_MAX + 1] = {0};
ret = exfat_lookup_file_by_utf16name(iter->exfat, iter->parent,
inode->name, &filter);
if (exfat_de_iter_device_offset(iter) == filter.out.dev_offset)
return 0;
- ret = exfat_utf16_dec(inode->name, NAME_BUFFER_SIZE, filename,
- PATH_MAX);
- if (ret < 0) {
- exfat_err("failed to decode filename\n");
- return ret;
- }
-
- return exfat_repair_rename_ask(&exfat_fsck, iter, filename,
+ return exfat_repair_rename_ask(&exfat_fsck, iter, inode->name,
ER_DE_DUPLICATED_NAME, "filename is duplicated");
}
exfat_de_iter_get(iter, 1, &stream_de);
name_len = exfat_utf16_len(inode->name, NAME_BUFFER_SIZE);
- if (stream_de->stream_name_len != name_len) {
+ if (name_len && stream_de->stream_name_len != name_len) {
if (repair_file_ask(iter, NULL, ER_DE_NAME_LEN,
"the name length of a file is wrong")) {
exfat_de_iter_get_dirty(iter, 1, &stream_de);
}
}
+ ret = exfat_check_name(inode->name, stream_de->stream_name_len);
+ if (ret != stream_de->stream_name_len) {
+ char err_msg[36];
+
+ snprintf(err_msg, sizeof(err_msg),
+ "filename has invalid character '%c'",
+ le16_to_cpu(inode->name[ret]));
+
+ return exfat_repair_rename_ask(&exfat_fsck, iter, inode->name,
+ ER_DE_INVALID_NAME, err_msg);
+ }
+
hash = exfat_calc_name_hash(iter->exfat, inode->name, (int)name_len);
if (cpu_to_le16(hash) != stream_de->stream_name_hash) {
if (repair_file_ask(iter, NULL, ER_DE_NAME_HASH,
return ret;
}
-const __le16 MSDOS_DOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), 0, };
-const __le16 MSDOS_DOTDOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), cpu_to_le16(46), 0, };
-
static int handle_dot_dotdot_filename(struct exfat_de_iter *iter,
- struct exfat_dentry *dentry,
+ __le16 *filename,
int strm_name_len)
{
- char *filename;
+ int i;
- if (!memcmp(dentry->name_unicode, MSDOS_DOT, strm_name_len * 2))
- filename = ".";
- else if (!memcmp(dentry->name_unicode, MSDOS_DOTDOT,
- strm_name_len * 2))
- filename = "..";
- else
+ for (i = 0; i < strm_name_len; i++) {
+ if (filename[i] != UTF16_DOT)
+ return 0;
+ }
+
+ if (filename[i])
return 0;
return exfat_repair_rename_ask(&exfat_fsck, iter, filename,
}
if (file_de->file_num_ext == 2 && stream_de->stream_name_len <= 2) {
- ret = handle_dot_dotdot_filename(iter, dentry,
+ ret = handle_dot_dotdot_filename(iter, node->name,
stream_de->stream_name_len);
if (ret < 0) {
*skip_dentries = file_de->file_num_ext + 1;
exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8);
exfat_bitmap_set_range(exfat, exfat->alloc_bitmap,
- le64_to_cpu(dentry->bitmap_start_clu),
+ le32_to_cpu(dentry->bitmap_start_clu),
DIV_ROUND_UP(exfat->disk_bitmap_size,
exfat->clus_size));
free(filter.out.dentry_set);
ch = le16_to_cpu(in_table[i]);
if (ch == 0xFFFF && i + 1 < in_len) {
- uint16_t len = le16_to_cpu(in_table[++i]);
-
- k += len;
+ ++i;
+ k += le16_to_cpu(in_table[i]);
} else {
out_table[k++] = ch;
}
exfat_fsck.options = ui.options;
- snprintf(ui.ei.dev_name, sizeof(ui.ei.dev_name), "%s", argv[optind]);
+ ui.ei.dev_name = argv[optind];
ret = exfat_get_blk_dev_info(&ui.ei, &bd);
if (ret < 0) {
exfat_err("failed to open %s. %d\n", ui.ei.dev_name, ret);
{ER_DE_NAME_LEN, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
{ER_DE_DOT_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4},
{ER_DE_DUPLICATED_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4},
+ {ER_DE_INVALID_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4},
{ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
{ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0},
{ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0},
return repair;
}
-static int check_bad_char(char w)
-{
- return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') ||
- (w == '>') || (w == '|') || (w == '"') || (w == ':') ||
- (w == '/') || (w == '\\');
-}
-
-static char *get_rename_from_user(struct exfat_de_iter *iter)
+static int get_rename_from_user(struct exfat_de_iter *iter,
+ __le16 *utf16_name, int name_size)
{
+ int len = 0;
char *rename = malloc(ENTRY_NAME_MAX + 2);
if (!rename)
- return NULL;
+ return -ENOMEM;
retry:
/* +2 means LF(Line Feed) and NULL terminator */
memset(rename, 0x1, ENTRY_NAME_MAX + 2);
printf("New name: ");
if (fgets(rename, ENTRY_NAME_MAX + 2, stdin)) {
- int i, len, err;
+ int err;
struct exfat_lookup_filter filter;
len = strlen(rename);
/* Remove LF in filename */
rename[len - 1] = '\0';
- for (i = 0; i < len - 1; i++) {
- if (check_bad_char(rename[i])) {
- printf("filename contain invalid character(%c)\n", rename[i]);
- goto retry;
- }
+
+ memset(utf16_name, 0, name_size);
+ len = exfat_utf16_enc(rename, utf16_name, name_size);
+ if (len < 0)
+ goto out;
+
+ err = exfat_check_name(utf16_name, len >> 1);
+ if (err != len >> 1) {
+ printf("filename contain invalid character(%c)\n",
+ le16_to_cpu(utf16_name[err]));
+ goto retry;
}
exfat_de_iter_flush(iter);
}
}
- return rename;
+out:
+ free(rename);
+
+ return len;
}
-static char *generate_rename(struct exfat_de_iter *iter)
+static int generate_rename(struct exfat_de_iter *iter, __le16 *utf16_name,
+ int name_size)
{
+ int err;
char *rename;
if (iter->invalid_name_num > INVALID_NAME_NUM_MAX)
- return NULL;
+ return -ERANGE;
rename = malloc(ENTRY_NAME_MAX + 1);
if (!rename)
- return NULL;
+ return -ENOMEM;
while (1) {
struct exfat_lookup_filter filter;
- int err;
snprintf(rename, ENTRY_NAME_MAX + 1, "FILE%07d.CHK",
iter->invalid_name_num++);
break;
}
- return rename;
+ memset(utf16_name, 0, name_size);
+ err = exfat_utf16_enc(rename, utf16_name, name_size);
+ free(rename);
+
+ return err;
}
int exfat_repair_rename_ask(struct exfat_fsck *fsck, struct exfat_de_iter *iter,
- char *old_name, er_problem_code_t prcode, char *error_msg)
+ __le16 *uname, er_problem_code_t prcode, char *error_msg)
{
int num;
+ char old_name[PATH_MAX + 1] = {0};
+
+ if (exfat_utf16_dec(uname, NAME_BUFFER_SIZE, old_name, PATH_MAX) <= 0) {
+ exfat_err("failed to decode filename\n");
+ return -EINVAL;
+ }
ask_again:
num = exfat_repair_ask(fsck, prcode, "ERROR: '%s' %s.\n%s",
" [3] Bypass this check(No repair)\n");
if (num) {
__le16 utf16_name[ENTRY_NAME_MAX];
- char *rename = NULL;
__u16 hash;
struct exfat_dentry *dentry;
int ret;
switch (num) {
case 1:
- rename = get_rename_from_user(iter);
+ ret = get_rename_from_user(iter, utf16_name,
+ sizeof(utf16_name));
break;
case 2:
- rename = generate_rename(iter);
+ ret = generate_rename(iter, utf16_name,
+ sizeof(utf16_name));
break;
case 3:
- break;
+ return -EINVAL;
default:
exfat_info("select 1 or 2 number instead of %d\n", num);
goto ask_again;
}
- if (!rename)
+ if (ret < 0)
return -EINVAL;
- exfat_info("%s filename is renamed to %s\n", old_name, rename);
-
exfat_de_iter_get_dirty(iter, 2, &dentry);
- memset(utf16_name, 0, sizeof(utf16_name));
- ret = exfat_utf16_enc(rename, utf16_name, sizeof(utf16_name));
- free(rename);
- if (ret < 0)
- return ret;
-
ret >>= 1;
memcpy(dentry->name_unicode, utf16_name, ENTRY_NAME_MAX * 2);
hash = exfat_calc_name_hash(iter->exfat, utf16_name, ret);
#define ER_DE_NAME_LEN 0x00001032
#define ER_DE_DOT_NAME 0x00001033
#define ER_DE_DUPLICATED_NAME 0x00001034
+#define ER_DE_INVALID_NAME 0x00001035
#define ER_FILE_VALID_SIZE 0x00002001
#define ER_FILE_INVALID_CLUS 0x00002002
#define ER_FILE_FIRST_CLUS 0x00002003
const char *fmt, ...);
int exfat_repair_rename_ask(struct exfat_fsck *fsck, struct exfat_de_iter *iter,
- char *old_name, er_problem_code_t prcode, char *error_msg);
+ __le16 *uname, er_problem_code_t prcode, char *error_msg);
#endif
#define _EXFAT_H
#include <stdint.h>
+#include <byteswap.h>
#include <linux/fs.h>
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
+#define UTF16_NULL 0x0000
+
#ifdef WORDS_BIGENDIAN
-#define cpu_to_le16(x) ((((x) >> 8) & 0xffu) | (((x) & 0xffu) << 8))
-#define cpu_to_le32(x) \
- ((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) | \
- (((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24))
-#define cpu_to_le64(x) (cpu_to_le32((uint64_t)(x)) << 32 | \
- cpu_to_le32((uint64_t)(x) >> 32))
+#define cpu_to_le16(x) bswap_16(x)
+#define cpu_to_le32(x) bswap_32(x)
+#define cpu_to_le64(x) bswap_64(x)
+
+#define UTF16_DOT 0x2E00 /* . */
+#define UTF16_SLASH 0x2F00 /* / */
#else
#define cpu_to_le16(x) (x)
#define cpu_to_le32(x) (x)
#define cpu_to_le64(x) (x)
+
+#define UTF16_DOT 0x002E /* . */
+#define UTF16_SLASH 0x002F /* / */
#endif
#define le64_to_cpu(x) ((uint64_t)cpu_to_le64(x))
};
struct exfat_user_input {
- char dev_name[255];
+ const char *dev_name;
bool writeable;
unsigned int sector_size;
unsigned int cluster_size;
int exfat_root_clus_count(struct exfat *exfat);
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);
/*
* Exfat Print
#ifndef _VERSION_H
-#define EXFAT_PROGS_VERSION "1.2.4"
+#define EXFAT_PROGS_VERSION "1.2.5"
#endif /* !_VERSION_H */
if (version_only)
exit(EXIT_FAILURE);
- if (argc < 2)
+ if (argc - optind != 1)
usage();
- memset(ui.dev_name, 0, sizeof(ui.dev_name));
- snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", argv[serial_mode + 1]);
+ ui.dev_name = argv[serial_mode + 1];
ret = exfat_get_blk_dev_info(&ui, &bd);
if (ret < 0)
for (i = 0; i < len; i++) {
ch = exfat->upcase_table[le16_to_cpu(name[i])];
- ch = cpu_to_le16(ch);
/* use += to avoid promotion to int; UBSan complaints about signed overflow */
chksum = (chksum << 15) | (chksum >> 1);
dset[1].dentry.stream.name_len = (__u8)name_len;
dset[1].dentry.stream.name_hash =
- exfat_calc_name_hash(exfat, utf16_name, name_len);
+ cpu_to_le16(exfat_calc_name_hash(exfat, utf16_name, name_len));
for (i = 2; i < dcount; i++) {
dset[i].type = EXFAT_NAME;
int depth, i;
int name_len;
__le16 *utf16_path;
- static const __le16 utf16_slash = cpu_to_le16(0x002F);
- static const __le16 utf16_null = cpu_to_le16(0x0000);
size_t in_size;
ctx->local_path[0] = '\0';
memcpy((char *)utf16_path, (char *)ctx->ancestors[i]->name,
name_len * 2);
utf16_path += name_len;
- memcpy((char *)utf16_path, &utf16_slash, sizeof(utf16_slash));
+ *utf16_path = UTF16_SLASH;
utf16_path++;
}
if (depth > 1)
utf16_path--;
- memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null));
+ *utf16_path = UTF16_NULL;
utf16_path++;
in_size = (utf16_path - ctx->utf16_path) * sizeof(__le16);
volume_label, sizeof(volume_label));
if (volume_label_len < 0) {
exfat_err("failed to encode volume label\n");
- free(pvol);
- return -1;
+ err = -1;
+ goto out;
}
- memcpy(pvol->vol_label, volume_label, volume_label_len);
pvol->vol_char_cnt = volume_label_len/2;
+ err = exfat_check_name(volume_label, pvol->vol_char_cnt);
+ if (err != pvol->vol_char_cnt) {
+ exfat_err("volume label contain invalid character(%c)\n",
+ le16_to_cpu(label_input[err]));
+ err = -1;
+ goto out;
+ }
+
+ memcpy(pvol->vol_label, volume_label, volume_label_len);
loc.parent = exfat->root;
loc.file_offset = filter.out.file_offset;
err = exfat_add_dentry_set(exfat, &loc, pvol, dcount, false);
exfat_info("new label: %s\n", label_input);
+out:
free(pvol);
return err;
goto free_ppbr;
}
- exfat_info("volume serial : 0x%x\n", ppbr->bsx.vol_serial);
+ exfat_info("volume serial : 0x%x\n", le32_to_cpu(ppbr->bsx.vol_serial));
free_ppbr:
free(ppbr);
exfat->bs->bsx.sect_size_bits;
offset += sizeof(clus_t) * clus;
+ next_clus = cpu_to_le32(next_clus);
+
if (exfat_write(exfat->blk_dev->dev_fd, &next_clus, sizeof(next_clus),
offset) != sizeof(next_clus))
return -EIO;
return 0;
}
+
+static inline int check_bad_utf16_char(unsigned short w)
+{
+ return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') ||
+ (w == '>') || (w == '|') || (w == '"') || (w == ':') ||
+ (w == '/') || (w == '\\');
+}
+
+int exfat_check_name(__le16 *utf16_name, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (check_bad_utf16_char(le16_to_cpu(utf16_name[i])))
+ break;
+ }
+
+ return i;
+}
goto out;
}
- memset(ui.dev_name, 0, sizeof(ui.dev_name));
- snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", argv[optind]);
+ ui.dev_name = argv[optind];
ret = exfat_get_blk_dev_info(&ui, &bd);
if (ret < 0)
--- /dev/null
+#!/usr/bin/env bash
+
+TESTCASE_DIR=$1
+NEED_LOOPDEV=$2
+IMAGE_FILE=exfat.img
+FSCK_PROG=${FSCK1:-"fsck.exfat"}
+FSCK_PROG_2=${FSCK2:-"fsck.exfat"}
+FSCK_OPTS="-y -s"
+PASS_COUNT=0
+
+cleanup() {
+ echo ""
+ echo "Passed ${PASS_COUNT} of ${TEST_COUNT}"
+ if [ ${PASS_COUNT} -ne ${TEST_COUNT} ]; then
+ exit 1
+ else
+ exit 0
+ fi
+}
+
+if [ $# -eq 0 ]; then
+ TESTCASE_DIRS=$(find . -mindepth 1 -maxdepth 1 -type d)
+ TEST_COUNT=$(find . -mindepth 1 -maxdepth 1 -type d | wc -l)
+else
+ TESTCASE_DIRS=$@
+ TEST_COUNT=$#
+fi
+
+for TESTCASE_DIR in $TESTCASE_DIRS; do
+ if [ ! -e "${TESTCASE_DIR}/${IMAGE_FILE}.tar.xz" ]; then
+ TEST_COUNT=$((TEST_COUNT - 1))
+ continue
+ fi
+
+ echo "Running ${TESTCASE_DIR}"
+ echo "-----------------------------------"
+
+ # Set up image file as loop device
+ tar -C . -xf "${TESTCASE_DIR}/${IMAGE_FILE}.tar.xz"
+ if [ $NEED_LOOPDEV ]; then
+ DEV_FILE=$(losetup -f "${IMAGE_FILE}" --show)
+ else
+ DEV_FILE=$IMAGE_FILE
+ fi
+
+ # Run fsck to detect corruptions
+ $FSCK_PROG "$DEV_FILE" | grep -q "ERROR:\|corrupted"
+ if [ $? -ne 0 ]; then
+ echo ""
+ echo "Failed to detect corruption for ${TESTCASE_DIR}"
+ if [ $NEED_LOOPDEV ]; then
+ losetup -d "${DEV_FILE}"
+ fi
+ cleanup
+ fi
+
+ # Run fsck for repair
+ $FSCK_PROG $FSCK_OPTS "$DEV_FILE"
+ if [ $? -ne 1 ] && [ $? -ne 0 ]; then
+ echo ""
+ echo "Failed to repair ${TESTCASE_DIR}"
+ if [ $NEED_LOOPDEV ]; then
+ losetup -d "${DEV_FILE}"
+ fi
+ cleanup
+ fi
+
+ echo ""
+ # Run fsck again
+ $FSCK_PROG_2 "$DEV_FILE"
+ if [ $? -ne 0 ]; then
+ echo ""
+ echo "Failed, corrupted ${TESTCASE_DIR}"
+ if [ $NEED_LOOPDEV ]; then
+ losetup -d "${DEV_FILE}"
+ fi
+ cleanup
+ fi
+
+ echo ""
+ echo "Passed ${TESTCASE_DIR}"
+ PASS_COUNT=$((PASS_COUNT + 1))
+
+ if [ $NEED_LOOPDEV ]; then
+ losetup -d "${DEV_FILE}"
+ fi
+done
+cleanup
if (version_only)
exit(EXIT_FAILURE);
- if (argc < 3)
+ if (argc < 3 || argc - optind != 1)
usage();
- memset(ui.dev_name, 0, sizeof(ui.dev_name));
- snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", argv[argc - 1]);
+ ui.dev_name = argv[argc - 1];
ret = exfat_get_blk_dev_info(&ui, &bd);
if (ret < 0)