aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNamjae Jeon <linkinjeon@kernel.org>2023-10-26 14:59:33 +0900
committerGitHub <noreply@github.com>2023-10-26 14:59:33 +0900
commit977a3d21b2ee73b8046cc2f761cadf3a8e45f4b9 (patch)
treea4750231adb6f4a6b2741288cf38fbb9b24ed0f9
parent8593314715f4c0e531ee7cb26bdd4cd06e8a3f7f (diff)
parent8fe26c50703fba6eb3046e42b674eb0a41da2119 (diff)
downloadexfatprogs-upstream-master.tar.gz
Merge pull request #239 from exfatprogs/exfat-nextupstream-master
Merge exfat-next into mater branch
-rw-r--r--NEWS17
-rw-r--r--dump/dump.c1
-rw-r--r--exfat2img/exfat2img.c2
-rw-r--r--fsck/fsck.c283
-rw-r--r--fsck/fsck.h2
-rw-r--r--fsck/repair.c139
-rw-r--r--fsck/repair.h7
-rw-r--r--include/exfat_dir.h23
-rw-r--r--include/exfat_ondisk.h9
-rw-r--r--include/libexfat.h18
-rw-r--r--include/version.h2
-rw-r--r--lib/exfat_dir.c78
-rw-r--r--lib/libexfat.c163
-rw-r--r--manpages/mkfs.exfat.820
-rw-r--r--manpages/tune.exfat.812
-rw-r--r--mkfs/mkfs.c56
-rw-r--r--tests/directory_no_clu/exfat.img.tar.xzbin3900 -> 0 bytes
-rw-r--r--tests/duplicated_name/exfat.img.tar.xzbin0 -> 3940 bytes
-rwxr-xr-xtests/test_fsck.sh11
-rw-r--r--tune/tune.c19
20 files changed, 613 insertions, 249 deletions
diff --git a/NEWS b/NEWS
index f371d62..055343a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,20 @@
+exfatprogs 1.2.2 - released 2023-10-26
+======================================
+
+CHANGES :
+ * exfat2img: Allow dumps for read-only devices.
+ * fsck.exfat: Revert Repairing zero size directory.
+
+NEW FEATURES :
+ * fsck.exfat: Repair duplicated filename.
+ * mkfs.exfat: Add the option "q" to print only error messages.
+ * mkfs.exfat: Add the option "U" to set volume GUID.
+ * tune.exfat: Add the option "U" / "-u" to set or print volume GUID.
+
+BUG FIXES:
+ * fsck.exfat: Fix some out-of-bounds memory accesses.
+ * fsck.exfat: Change not to delete volume GUID directory entry.
+
exfatprogs 1.2.1 - released 2023-05-17
======================================
diff --git a/dump/dump.c b/dump/dump.c
index 3d77bb9..8c96376 100644
--- a/dump/dump.c
+++ b/dump/dump.c
@@ -223,6 +223,7 @@ int main(int argc, char *argv[])
bool version_only = false;
init_user_input(&ui);
+ ui.writeable = false;
if (!setlocale(LC_CTYPE, ""))
exfat_err("failed to init locale/codeset\n");
diff --git a/exfat2img/exfat2img.c b/exfat2img/exfat2img.c
index be1f0f3..3f83588 100644
--- a/exfat2img/exfat2img.c
+++ b/exfat2img/exfat2img.c
@@ -319,7 +319,7 @@ static int read_file_dentry_set(struct exfat_de_iter *iter,
if (!node)
return -ENOMEM;
- for (i = 2; i <= file_de->file_num_ext; i++) {
+ for (i = 2; i <= MIN(file_de->file_num_ext, 1 + MAX_NAME_DENTRIES); i++) {
ret = exfat_de_iter_get(iter, i, &dentry);
if (ret || dentry->type != EXFAT_NAME)
break;
diff --git a/fsck/fsck.c b/fsck/fsck.c
index cd9ee9a..77272aa 100644
--- a/fsck/fsck.c
+++ b/fsck/fsck.c
@@ -103,31 +103,21 @@ static void usage(char *name)
})
static int check_clus_chain(struct exfat_de_iter *de_iter,
- struct exfat_inode *node)
+ struct exfat_inode *node)
{
struct exfat *exfat = de_iter->exfat;
struct exfat_dentry *stream_de;
- clus_t clus, prev, next, new_clus;
+ clus_t clus, prev, next;
uint64_t count, max_count;
- int err;
clus = node->first_clus;
prev = EXFAT_EOF_CLUSTER;
count = 0;
max_count = DIV_ROUND_UP(node->size, exfat->clus_size);
- if (node->size == 0 && node->first_clus == EXFAT_FREE_CLUSTER) {
- /* locate a cluster for the empty dir if the dir starts with EXFAT_FREE_CLUSTER */
- if (node->attr & ATTR_SUBDIR) {
- if (repair_file_ask(de_iter, node,
- ER_DE_FIRST_CLUS,
- "size %#" PRIx64 ", but the first cluster %#x",
- node->size, node->first_clus))
- goto allocate_cluster;
- return -EINVAL;
- }
+ if (node->size == 0 && node->first_clus == EXFAT_FREE_CLUSTER)
return 0;
- }
+
/* the first cluster is wrong */
if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) ||
(node->size > 0 && !exfat_heap_clus(exfat, node->first_clus))) {
@@ -225,38 +215,6 @@ static int check_clus_chain(struct exfat_de_iter *de_iter,
}
return 0;
-allocate_cluster:
- exfat_de_iter_get_dirty(de_iter, 1, &stream_de);
- err = exfat_find_free_cluster(exfat, exfat->start_clu, &new_clus);
- if (err) {
- exfat->start_clu = EXFAT_FIRST_CLUSTER;
- exfat_err("failed to find a free cluster\n");
- return -ENOSPC;
- }
- exfat->start_clu = new_clus;
-
- if (exfat_set_fat(exfat, new_clus, EXFAT_EOF_CLUSTER))
- return -EIO;
-
- /* zero out the new cluster */
- if (exfat_write(exfat->blk_dev->dev_fd, exfat->zero_cluster,
- exfat->clus_size, exfat_c2o(exfat, new_clus)) !=
- (ssize_t)exfat->clus_size) {
- exfat_err("failed to fill new cluster with zeroes\n");
- return -EIO;
- }
-
- /* modify the number of cluster form 0 to 1 */
- count = 1;
- stream_de->stream_start_clu = cpu_to_le32(new_clus);
- stream_de->stream_size = cpu_to_le64(count * exfat->clus_size);
- stream_de->stream_valid_size = cpu_to_le64(count * exfat->clus_size);
- stream_de->dentry.stream.flags |= EXFAT_SF_CONTIGUOUS;
- node->first_clus = new_clus;
- node->size = count * exfat->clus_size;
- node->is_contiguous = true;
- exfat_bitmap_set(exfat->alloc_bitmap, new_clus);
- return 1;
truncate_file:
node->size = count * exfat->clus_size;
if (!exfat_heap_clus(exfat, prev))
@@ -677,6 +635,35 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node)
return valid ? ret : -EINVAL;
}
+static int handle_duplicated_filename(struct exfat_de_iter *iter,
+ struct exfat_inode *inode)
+{
+ 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 (ret)
+ return ret;
+
+ free(filter.out.dentry_set);
+
+ /* Hash is same, but filename is not same */
+ 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,
+ ER_DE_DUPLICATED_NAME, "filename is duplicated");
+}
+
static int check_name_dentry_set(struct exfat_de_iter *iter,
struct exfat_inode *inode)
{
@@ -707,77 +694,16 @@ static int check_name_dentry_set(struct exfat_de_iter *iter,
return -EINVAL;
}
}
- return 0;
-}
-static int check_bad_char(char w)
-{
- return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') ||
- (w == '>') || (w == '|') || (w == '"') || (w == ':') ||
- (w == '/') || (w == '\\');
-}
+ if (BITMAP_GET(iter->name_hash_bitmap, hash)) {
+ int ret = handle_duplicated_filename(iter, inode);
-static char *get_rename_from_user(struct exfat_de_iter *iter)
-{
- char *rename = malloc(ENTRY_NAME_MAX + 2);
-
- if (!rename)
- return NULL;
-
-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;
- 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;
- }
- }
-
- exfat_de_iter_flush(iter);
- err = exfat_lookup_file(iter->exfat, iter->parent, rename, &filter);
- if (!err) {
- printf("file(%s) already exists, retry to insert name\n", rename);
- goto retry;
- }
- }
-
- return rename;
-}
-
-static char *generate_rename(struct exfat_de_iter *iter)
-{
- char *rename;
-
- if (iter->dot_name_num > DOT_NAME_NUM_MAX)
- return NULL;
-
- rename = malloc(ENTRY_NAME_MAX + 1);
- if (!rename)
- return NULL;
-
- while (1) {
- struct exfat_lookup_filter filter;
- int err;
-
- snprintf(rename, ENTRY_NAME_MAX + 1, "FILE%07d.CHK",
- iter->dot_name_num++);
- err = exfat_lookup_file(iter->exfat, iter->parent, rename,
- &filter);
- if (!err)
- continue;
- break;
- }
+ if (ret)
+ return ret;
+ } else
+ BITMAP_SET(iter->name_hash_bitmap, hash);
- return rename;
+ return 0;
}
const __le16 MSDOS_DOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), 0, };
@@ -788,8 +714,6 @@ static int handle_dot_dotdot_filename(struct exfat_de_iter *iter,
int strm_name_len)
{
char *filename;
- char error_msg[150];
- int num;
if (!memcmp(dentry->name_unicode, MSDOS_DOT, strm_name_len * 2))
filename = ".";
@@ -799,56 +723,8 @@ static int handle_dot_dotdot_filename(struct exfat_de_iter *iter,
else
return 0;
- sprintf(error_msg, "ERROR: '%s' filename is not allowed.\n"
- " [1] Insert the name you want to rename.\n"
- " [2] Automatically renames filename.\n"
- " [3] Bypass this check(No repair)\n", filename);
-ask_again:
- num = exfat_repair_ask(&exfat_fsck, ER_DE_DOT_NAME,
- error_msg);
- if (num) {
- __le16 utf16_name[ENTRY_NAME_MAX];
- char *rename = NULL;
- __u16 hash;
- struct exfat_dentry *stream_de;
- int name_len, ret;
-
- switch (num) {
- case 1:
- rename = get_rename_from_user(iter);
- break;
- case 2:
- rename = generate_rename(iter);
- break;
- case 3:
- break;
- default:
- exfat_info("select 1 or 2 number instead of %d\n", num);
- goto ask_again;
- }
-
- if (!rename)
- return -EINVAL;
-
- exfat_info("%s filename is renamed to %s\n", filename, 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;
-
- memcpy(dentry->name_unicode, utf16_name, ENTRY_NAME_MAX * 2);
- name_len = exfat_utf16_len(utf16_name, ENTRY_NAME_MAX * 2);
- hash = exfat_calc_name_hash(iter->exfat, utf16_name, (int)name_len);
- exfat_de_iter_get_dirty(iter, 1, &stream_de);
- stream_de->stream_name_len = (__u8)name_len;
- stream_de->stream_name_hash = cpu_to_le16(hash);
- }
-
- return 0;
+ return exfat_repair_rename_ask(&exfat_fsck, iter, filename,
+ ER_DE_DOT_NAME, "filename is not allowed");
}
static int read_file_dentry_set(struct exfat_de_iter *iter,
@@ -898,7 +774,7 @@ static int read_file_dentry_set(struct exfat_de_iter *iter,
if (!node)
return -ENOMEM;
- for (i = 2; i <= file_de->file_num_ext; i++) {
+ for (i = 2; i <= MIN(file_de->file_num_ext, 1 + MAX_NAME_DENTRIES); i++) {
ret = exfat_de_iter_get(iter, i, &dentry);
if (ret || dentry->type != EXFAT_NAME) {
if (i > 2 && repair_file_ask(iter, NULL, ER_DE_NAME,
@@ -1004,6 +880,7 @@ static int read_bitmap(struct exfat *exfat)
{
struct exfat_lookup_filter filter = {
.in.type = EXFAT_BITMAP,
+ .in.dentry_count = 0,
.in.filter = NULL,
.in.param = NULL,
};
@@ -1078,6 +955,7 @@ static int read_upcase_table(struct exfat *exfat)
{
struct exfat_lookup_filter filter = {
.in.type = EXFAT_UPCASE,
+ .in.dentry_count = 0,
.in.filter = NULL,
.in.param = NULL,
};
@@ -1171,6 +1049,10 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir)
else if (ret)
return ret;
+ de_iter->name_hash_bitmap = fsck->name_hash_bitmap;
+ memset(fsck->name_hash_bitmap, 0,
+ EXFAT_BITMAP_SIZE(EXFAT_MAX_HASH_COUNT));
+
while (1) {
ret = exfat_de_iter_get(de_iter, 0, &dentry);
if (ret == EOF) {
@@ -1211,6 +1093,7 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir)
case EXFAT_VOLUME:
case EXFAT_BITMAP:
case EXFAT_UPCASE:
+ case EXFAT_GUID:
if (dir == exfat->root)
break;
/* fallthrough */
@@ -1300,6 +1183,12 @@ static int exfat_filesystem_check(struct exfat_fsck *fsck)
return -ENOENT;
}
+ fsck->name_hash_bitmap = malloc(EXFAT_BITMAP_SIZE(EXFAT_MAX_HASH_COUNT));
+ if (!fsck->name_hash_bitmap) {
+ exfat_err("failed to allocate name hash bitmap\n");
+ return -ENOMEM;
+ }
+
list_add(&exfat->root->list, &exfat->dir_list);
while (!list_empty(&exfat->dir_list)) {
@@ -1328,6 +1217,7 @@ static int exfat_filesystem_check(struct exfat_fsck *fsck)
}
out:
exfat_free_dir_list(exfat);
+ free(fsck->name_hash_bitmap);
return ret;
}
@@ -1427,9 +1317,40 @@ static int rescue_orphan_clusters(struct exfat_fsck *fsck)
struct exfat_dentry_loc loc;
struct exfat_lookup_filter lf = {
.in.type = EXFAT_INVAL,
+ .in.dentry_count = 0,
.in.filter = NULL,
};
+ clu_count = le32_to_cpu(exfat->bs->bsx.clu_count);
+
+ /* find clusters which are not marked as free, but not allocated to
+ * any files.
+ */
+ disk_b = (bitmap_t *)exfat->disk_bitmap;
+ alloc_b = (bitmap_t *)exfat->alloc_bitmap;
+ ohead_b = (bitmap_t *)exfat->ohead_bitmap;
+ for (i = 0; i < EXFAT_BITMAP_SIZE(clu_count) / sizeof(bitmap_t); i++)
+ ohead_b[i] = disk_b[i] & ~alloc_b[i];
+
+ /* no orphan clusters */
+ if (exfat_bitmap_find_one(exfat, exfat->ohead_bitmap,
+ EXFAT_FIRST_CLUSTER, &s_clu))
+ return 0;
+
+ err = exfat_create_file(exfat_fsck.exfat,
+ exfat_fsck.exfat->root,
+ "LOST+FOUND",
+ ATTR_SUBDIR);
+ if (err) {
+ exfat_err("failed to create LOST+FOUND directory\n");
+ return err;
+ }
+
+ if (fsync(exfat_fsck.exfat->blk_dev->dev_fd) != 0) {
+ exfat_err("failed to sync()\n");
+ return -EIO;
+ }
+
err = read_lostfound(exfat, &lostfound);
if (err) {
exfat_err("failed to find LOST+FOUND\n");
@@ -1455,17 +1376,6 @@ static int rescue_orphan_clusters(struct exfat_fsck *fsck)
}
dset[1].dentry.stream.flags |= EXFAT_SF_CONTIGUOUS;
- clu_count = le32_to_cpu(exfat->bs->bsx.clu_count);
-
- /* find clusters which are not marked as free, but not allocated to
- * any files.
- */
- disk_b = (bitmap_t *)exfat->disk_bitmap;
- alloc_b = (bitmap_t *)exfat->alloc_bitmap;
- ohead_b = (bitmap_t *)exfat->ohead_bitmap;
- for (i = 0; i < EXFAT_BITMAP_SIZE(clu_count) / sizeof(bitmap_t); i++)
- ohead_b[i] = disk_b[i] & ~alloc_b[i];
-
/* create temporary files and allocate contiguous orphan clusters
* to each file.
*/
@@ -1657,23 +1567,6 @@ int main(int argc, char * const argv[])
goto out;
}
- if (exfat_fsck.options & FSCK_OPTS_RESCUE_CLUS) {
- ret = exfat_create_file(exfat_fsck.exfat,
- exfat_fsck.exfat->root,
- "LOST+FOUND",
- ATTR_SUBDIR);
- if (ret) {
- exfat_err("failed to create lost+found directory\n");
- goto out;
- }
-
- if (fsync(exfat_fsck.exfat->blk_dev->dev_fd) != 0) {
- ret = -EIO;
- exfat_err("failed to sync()\n");
- goto out;
- }
- }
-
exfat_debug("verifying directory entries...\n");
ret = exfat_filesystem_check(&exfat_fsck);
if (ret)
diff --git a/fsck/fsck.h b/fsck/fsck.h
index 53003f6..ee0cb30 100644
--- a/fsck/fsck.h
+++ b/fsck/fsck.h
@@ -28,6 +28,8 @@ struct exfat_fsck {
enum fsck_ui_options options;
bool dirty:1;
bool dirty_fat:1;
+
+ char *name_hash_bitmap;
};
off_t exfat_c2o(struct exfat *exfat, unsigned int clus);
diff --git a/fsck/repair.c b/fsck/repair.c
index ab46f85..f983ee1 100644
--- a/fsck/repair.c
+++ b/fsck/repair.c
@@ -6,12 +6,12 @@
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
+#include <errno.h>
#include "exfat_ondisk.h"
#include "libexfat.h"
#include "repair.h"
#include "exfat_fs.h"
-#include "exfat_dir.h"
#include "fsck.h"
struct exfat_repair_problem {
@@ -54,6 +54,7 @@ static struct exfat_repair_problem problems[] = {
{ER_DE_NAME_HASH, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
{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_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},
@@ -61,7 +62,6 @@ static struct exfat_repair_problem problems[] = {
{ER_FILE_LARGER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0},
{ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0},
{ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX, 0, 0, 0},
- {ER_DE_FIRST_CLUS, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}
};
static struct exfat_repair_problem *find_problem(er_problem_code_t prcode)
@@ -158,3 +158,138 @@ int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode,
}
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)
+{
+ char *rename = malloc(ENTRY_NAME_MAX + 2);
+
+ if (!rename)
+ return NULL;
+
+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;
+ 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;
+ }
+ }
+
+ exfat_de_iter_flush(iter);
+ err = exfat_lookup_file(iter->exfat, iter->parent, rename, &filter);
+ if (!err) {
+ printf("file(%s) already exists, retry to insert name\n", rename);
+ goto retry;
+ }
+ }
+
+ return rename;
+}
+
+static char *generate_rename(struct exfat_de_iter *iter)
+{
+ char *rename;
+
+ if (iter->invalid_name_num > INVALID_NAME_NUM_MAX)
+ return NULL;
+
+ rename = malloc(ENTRY_NAME_MAX + 1);
+ if (!rename)
+ return NULL;
+
+ while (1) {
+ struct exfat_lookup_filter filter;
+ int err;
+
+ snprintf(rename, ENTRY_NAME_MAX + 1, "FILE%07d.CHK",
+ iter->invalid_name_num++);
+ err = exfat_lookup_file(iter->exfat, iter->parent, rename,
+ &filter);
+ if (!err)
+ continue;
+ break;
+ }
+
+ return rename;
+}
+
+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)
+{
+ int num;
+
+ask_again:
+ num = exfat_repair_ask(fsck, prcode, "ERROR: '%s' %s.\n%s",
+ old_name, error_msg,
+ " [1] Insert the name you want to rename.\n"
+ " [2] Automatically renames filename.\n"
+ " [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, i;
+
+ switch (num) {
+ case 1:
+ rename = get_rename_from_user(iter);
+ break;
+ case 2:
+ rename = generate_rename(iter);
+ break;
+ case 3:
+ break;
+ default:
+ exfat_info("select 1 or 2 number instead of %d\n", num);
+ goto ask_again;
+ }
+
+ if (!rename)
+ 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);
+ exfat_de_iter_get_dirty(iter, 1, &dentry);
+ dentry->stream_name_len = (__u8)ret;
+ dentry->stream_name_hash = cpu_to_le16(hash);
+
+ exfat_de_iter_get_dirty(iter, 0, &dentry);
+ i = dentry->file_num_ext;
+ dentry->file_num_ext = 2;
+
+ for (; i > 2; i--) {
+ exfat_de_iter_get_dirty(iter, i, &dentry);
+ dentry->type &= EXFAT_DELETE;
+ }
+ }
+
+ return 0;
+}
diff --git a/fsck/repair.h b/fsck/repair.h
index ea89747..634cc49 100644
--- a/fsck/repair.h
+++ b/fsck/repair.h
@@ -5,6 +5,8 @@
#ifndef _REPAIR_H
#define _REPAIR_H
+#include "exfat_dir.h"
+
#define ER_BS_CHECKSUM 0x00000001
#define ER_BS_BOOT_REGION 0x00000002
#define ER_DE_CHECKSUM 0x00001001
@@ -16,6 +18,7 @@
#define ER_DE_NAME_HASH 0x00001031
#define ER_DE_NAME_LEN 0x00001032
#define ER_DE_DOT_NAME 0x00001033
+#define ER_DE_DUPLICATED_NAME 0x00001034
#define ER_FILE_VALID_SIZE 0x00002001
#define ER_FILE_INVALID_CLUS 0x00002002
#define ER_FILE_FIRST_CLUS 0x00002003
@@ -23,11 +26,13 @@
#define ER_FILE_LARGER_SIZE 0x00002005
#define ER_FILE_DUPLICATED_CLUS 0x00002006
#define ER_FILE_ZERO_NOFAT 0x00002007
-#define ER_DE_FIRST_CLUS 0x00002008
+
typedef unsigned int er_problem_code_t;
struct exfat_fsck;
int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode,
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);
#endif
diff --git a/include/exfat_dir.h b/include/exfat_dir.h
index 12e1546..d450c61 100644
--- a/include/exfat_dir.h
+++ b/include/exfat_dir.h
@@ -25,13 +25,16 @@ struct exfat_de_iter {
off_t de_file_offset;
off_t next_read_offset;
int max_skip_dentries;
-#define DOT_NAME_NUM_MAX 9999999
- unsigned int dot_name_num;
+#define INVALID_NAME_NUM_MAX 9999999
+ unsigned int invalid_name_num;
+
+ char *name_hash_bitmap; /* bitmap of children's name hashes */
};
struct exfat_lookup_filter {
struct {
uint8_t type;
+ int dentry_count;
/* return 0 if matched, return 1 if not matched,
* otherwise return errno
*/
@@ -43,8 +46,15 @@ struct exfat_lookup_filter {
struct exfat_dentry *dentry_set;
int dentry_count;
off_t file_offset;
- /* device offset where the dentry_set locates, or
- * the empty slot locates or EOF if not found.
+ /*
+ * If the dentry_set found:
+ * - device offset where the dentry_set locates.
+ * If the dentry_set not found:
+ * - device offset where the first empty dentry_set locates
+ * if in.dentry_count > 0 and there are enough empty dentry.
+ * - device offset where the last empty dentry_set locates
+ * if in.dentry_count = 0 or no enough empty dentry.
+ * - EOF if no empty dentry_set.
*/
off_t dev_offset;
} out;
@@ -65,6 +75,10 @@ int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent,
struct exfat_lookup_filter *filter);
int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent,
const char *name, struct exfat_lookup_filter *filter_out);
+int exfat_lookup_file_by_utf16name(struct exfat *exfat,
+ struct exfat_inode *parent,
+ __le16 *utf16_name,
+ struct exfat_lookup_filter *filter_out);
int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent,
const char *name, unsigned short attr);
@@ -75,7 +89,6 @@ int exfat_update_file_dentry_set(struct exfat *exfat,
int exfat_build_file_dentry_set(struct exfat *exfat, const char *name,
unsigned short attr, struct exfat_dentry **dentry_set,
int *dentry_count);
-int exfat_find_free_cluster(struct exfat *exfat, clus_t start, clus_t *new_clu);
int exfat_add_dentry_set(struct exfat *exfat, struct exfat_dentry_loc *loc,
struct exfat_dentry *dset, int dcount,
bool need_next_loc);
diff --git a/include/exfat_ondisk.h b/include/exfat_ondisk.h
index d1786bf..42cdadf 100644
--- a/include/exfat_ondisk.h
+++ b/include/exfat_ondisk.h
@@ -40,6 +40,7 @@
/* exFAT allows 8388608(256MB) directory entries */
#define MAX_EXFAT_DENTRIES 8388608
#define MIN_FILE_DENTRIES 3
+#define MAX_NAME_DENTRIES 17
/* dentry types */
#define MSDOS_DELETED 0xE5 /* deleted mark */
@@ -133,6 +134,7 @@ struct pbr {
};
#define VOLUME_LABEL_MAX_LEN 11
+#define VOLUME_GUID_LEN 16
#define ENTRY_NAME_MAX 15
struct exfat_dentry {
@@ -190,6 +192,13 @@ struct exfat_dentry {
__le32 start_clu;
__le64 size;
} __attribute__((packed)) upcase; /* up-case table directory entry */
+ struct {
+ __u8 num_ext;
+ __le16 checksum;
+ __u16 flags;
+ __u8 guid[VOLUME_GUID_LEN];
+ __u8 reserved[10];
+ } __attribute__((packed)) guid; /* volume GUID directory entry */
} __attribute__((packed)) dentry;
} __attribute__((packed));
diff --git a/include/libexfat.h b/include/libexfat.h
index 0623501..9e853a8 100644
--- a/include/libexfat.h
+++ b/include/libexfat.h
@@ -45,6 +45,8 @@ typedef __u32 clus_t;
#define EXFAT_SET_VOLUME_LABEL 0x02
#define EXFAT_GET_VOLUME_SERIAL 0x03
#define EXFAT_SET_VOLUME_SERIAL 0x04
+#define EXFAT_GET_VOLUME_GUID 0x05
+#define EXFAT_SET_VOLUME_GUID 0x06
#define EXFAT_MAX_SECTOR_SIZE 4096
@@ -52,6 +54,8 @@ typedef __u32 clus_t;
(pbr)->bsx.sect_per_clus_bits))
#define EXFAT_SECTOR_SIZE(pbr) (1 << (pbr)->bsx.sect_size_bits)
+#define EXFAT_MAX_HASH_COUNT (UINT16_MAX + 1)
+
enum {
BOOT_SEC_IDX = 0,
EXBOOT_SEC_IDX,
@@ -84,6 +88,7 @@ struct exfat_user_input {
__u16 volume_label[VOLUME_LABEL_MAX_LEN];
int volume_label_len;
unsigned int volume_serial;
+ const char *guid;
};
struct exfat;
@@ -102,18 +107,24 @@ typedef __u32 bitmap_t;
#define EXFAT_BITMAP_SIZE(__c_count) \
(DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t))
+#define BITMAP_GET(bmap, bit) \
+ (((bitmap_t *)(bmap))[BIT_ENTRY(bit)] & BIT_MASK(bit))
+
+#define BITMAP_SET(bmap, bit) \
+ (((bitmap_t *)(bmap))[BIT_ENTRY(bit)] |= BIT_MASK(bit))
+
static inline bool exfat_bitmap_get(char *bmap, clus_t c)
{
clus_t cc = c - EXFAT_FIRST_CLUSTER;
- return ((bitmap_t *)(bmap))[BIT_ENTRY(cc)] & BIT_MASK(cc);
+ return BITMAP_GET(bmap, cc);
}
static inline void exfat_bitmap_set(char *bmap, clus_t c)
{
clus_t cc = c - EXFAT_FIRST_CLUSTER;
- (((bitmap_t *)(bmap))[BIT_ENTRY(cc)] |= BIT_MASK(cc));
+ BITMAP_SET(bmap, cc);
}
static inline void exfat_bitmap_clear(char *bmap, clus_t c)
@@ -147,6 +158,9 @@ ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len,
off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd);
int exfat_read_volume_label(struct exfat *exfat);
int exfat_set_volume_label(struct exfat *exfat, char *label_input);
+int __exfat_set_volume_guid(struct exfat_dentry *dentry, const char *guid);
+int exfat_read_volume_guid(struct exfat *exfat);
+int exfat_set_volume_guid(struct exfat *exfat, const char *guid);
int exfat_read_sector(struct exfat_blk_dev *bd, void *buf,
unsigned int sec_off);
int exfat_write_sector(struct exfat_blk_dev *bd, void *buf,
diff --git a/include/version.h b/include/version.h
index f0b250c..f3cf9e2 100644
--- a/include/version.h
+++ b/include/version.h
@@ -5,6 +5,6 @@
#ifndef _VERSION_H
-#define EXFAT_PROGS_VERSION "1.2.1"
+#define EXFAT_PROGS_VERSION "1.2.2"
#endif /* !_VERSION_H */
diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c
index 7c145f4..98e820f 100644
--- a/lib/exfat_dir.c
+++ b/lib/exfat_dir.c
@@ -237,7 +237,7 @@ int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat,
iter->de_file_offset = 0;
iter->next_read_offset = iter->read_size;
iter->max_skip_dentries = 0;
- iter->dot_name_num = 0;
+ iter->invalid_name_num = 0;
if (iter->parent->size == 0)
return EOF;
@@ -356,9 +356,8 @@ int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent,
struct exfat_dentry *dentry = NULL;
off_t free_file_offset = 0, free_dev_offset = 0;
struct exfat_de_iter de_iter;
- int dentry_count;
+ int dentry_count, empty_dentry_count = 0;
int retval;
- bool last_is_free = false;
bd = exfat_alloc_buffer(2, exfat->clus_size, exfat->sect_size);
if (!bd)
@@ -379,6 +378,12 @@ int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent,
goto out;
}
+ if (!IS_EXFAT_DELETED(dentry->type)) {
+ if (filter->in.dentry_count == 0 ||
+ empty_dentry_count < filter->in.dentry_count)
+ empty_dentry_count = 0;
+ }
+
dentry_count = 1;
if (dentry->type == filter->in.type) {
retval = 0;
@@ -407,18 +412,17 @@ int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent,
} else if (retval < 0) {
goto out;
}
- last_is_free = false;
- } else if ((dentry->type == EXFAT_LAST ||
- IS_EXFAT_DELETED(dentry->type))) {
- if (!last_is_free) {
+ } else if (IS_EXFAT_DELETED(dentry->type)) {
+ if (empty_dentry_count == 0) {
free_file_offset =
exfat_de_iter_file_offset(&de_iter);
free_dev_offset =
exfat_de_iter_device_offset(&de_iter);
- last_is_free = true;
}
- } else {
- last_is_free = false;
+
+ if (filter->in.dentry_count == 0 ||
+ empty_dentry_count < filter->in.dentry_count)
+ empty_dentry_count++;
}
exfat_de_iter_advance(&de_iter, dentry_count);
@@ -430,7 +434,7 @@ out:
exfat_de_iter_file_offset(&de_iter);
filter->out.dev_offset =
exfat_de_iter_device_offset(&de_iter);
- } else if (retval == EOF && last_is_free) {
+ } else if (retval == EOF && empty_dentry_count) {
filter->out.file_offset = free_file_offset;
filter->out.dev_offset = free_dev_offset;
} else {
@@ -472,7 +476,7 @@ static int filter_lookup_file(struct exfat_de_iter *de_iter,
if (retval || name_de->type != EXFAT_NAME)
return 1;
- len = MIN(name_len, ENTRY_NAME_MAX);
+ len = MIN(name_len + 1, ENTRY_NAME_MAX);
if (memcmp(name_de->dentry.name.unicode_0_14,
name, len * 2) != 0)
return 1;
@@ -485,19 +489,17 @@ static int filter_lookup_file(struct exfat_de_iter *de_iter,
return 0;
}
-int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent,
- const char *name, struct exfat_lookup_filter *filter_out)
+int exfat_lookup_file_by_utf16name(struct exfat *exfat,
+ struct exfat_inode *parent,
+ __le16 *utf16_name,
+ struct exfat_lookup_filter *filter_out)
{
int retval;
- __le16 utf16_name[PATH_MAX + 2] = {0, };
-
- retval = (int)exfat_utf16_enc(name, utf16_name, sizeof(utf16_name));
- if (retval < 0)
- return retval;
filter_out->in.type = EXFAT_FILE;
filter_out->in.filter = filter_lookup_file;
filter_out->in.param = utf16_name;
+ filter_out->in.dentry_count = 0;
retval = exfat_lookup_dentry_set(exfat, parent, filter_out);
if (retval < 0)
@@ -506,6 +508,20 @@ int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent,
return 0;
}
+int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent,
+ const char *name, struct exfat_lookup_filter *filter_out)
+{
+ int retval;
+ __le16 utf16_name[PATH_MAX + 2] = {0, };
+
+ retval = (int)exfat_utf16_enc(name, utf16_name, sizeof(utf16_name));
+ if (retval < 0)
+ return retval;
+
+ return exfat_lookup_file_by_utf16name(exfat, parent, utf16_name,
+ filter_out);
+}
+
void exfat_calc_dentry_checksum(struct exfat_dentry *dentry,
uint16_t *checksum, bool primary)
{
@@ -514,12 +530,17 @@ void exfat_calc_dentry_checksum(struct exfat_dentry *dentry,
bytes = (uint8_t *)dentry;
- *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[0];
- *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[1];
+ /* use += to avoid promotion to int; UBSan complaints about signed overflow */
+ *checksum = (*checksum << 15) | (*checksum >> 1);
+ *checksum += bytes[0];
+ *checksum = (*checksum << 15) | (*checksum >> 1);
+ *checksum += bytes[1];
i = primary ? 4 : 2;
- for (; i < sizeof(*dentry); i++)
- *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[i];
+ for (; i < sizeof(*dentry); i++) {
+ *checksum = (*checksum << 15) | (*checksum >> 1);
+ *checksum += bytes[i];
+ }
}
static uint16_t calc_dentry_set_checksum(struct exfat_dentry *dset, int dcount)
@@ -548,8 +569,11 @@ uint16_t exfat_calc_name_hash(struct exfat *exfat,
ch = exfat->upcase_table[le16_to_cpu(name[i])];
ch = cpu_to_le16(ch);
- chksum = ((chksum << 15) | (chksum >> 1)) + (ch & 0xFF);
- chksum = ((chksum << 15) | (chksum >> 1)) + (ch >> 8);
+ /* use += to avoid promotion to int; UBSan complaints about signed overflow */
+ chksum = (chksum << 15) | (chksum >> 1);
+ chksum += ch & 0xFF;
+ chksum = (chksum << 15) | (chksum >> 1);
+ chksum += ch >> 8;
}
return chksum;
}
@@ -678,7 +702,7 @@ int exfat_update_file_dentry_set(struct exfat *exfat,
return 0;
}
-int exfat_find_free_cluster(struct exfat *exfat,
+static int find_free_cluster(struct exfat *exfat,
clus_t start, clus_t *new_clu)
{
clus_t end = le32_to_cpu(exfat->bs->bsx.clu_count) +
@@ -805,7 +829,7 @@ static int exfat_alloc_cluster(struct exfat *exfat, struct exfat_inode *inode,
if ((need_dset && !inode->dentry_set) || inode->is_contiguous)
return -EINVAL;
- err = exfat_find_free_cluster(exfat, exfat->start_clu, new_clu);
+ err = find_free_cluster(exfat, exfat->start_clu, new_clu);
if (err) {
exfat->start_clu = EXFAT_FIRST_CLUSTER;
exfat_err("failed to find an free cluster\n");
diff --git a/lib/libexfat.c b/lib/libexfat.c
index 4fd4ac6..d7b8344 100644
--- a/lib/libexfat.c
+++ b/lib/libexfat.c
@@ -412,6 +412,7 @@ int exfat_read_volume_label(struct exfat *exfat)
__le16 disk_label[VOLUME_LABEL_MAX_LEN];
struct exfat_lookup_filter filter = {
.in.type = EXFAT_VOLUME,
+ .in.dentry_count = 0,
.in.filter = NULL,
};
@@ -453,6 +454,7 @@ int exfat_set_volume_label(struct exfat *exfat, char *label_input)
struct exfat_lookup_filter filter = {
.in.type = EXFAT_VOLUME,
+ .in.dentry_count = 1,
.in.filter = NULL,
};
@@ -492,6 +494,167 @@ int exfat_set_volume_label(struct exfat *exfat, char *label_input)
return err;
}
+static inline void print_guid(const char *msg, const __u8 *guid)
+{
+ exfat_info("%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[5], guid[7],
+ guid[8], guid[9], guid[10], guid[11],
+ guid[12], guid[13], guid[14], guid[15]);
+}
+
+static int set_guid(__u8 *guid, const char *input)
+{
+ int i, j, zero_len = 0;
+ int len = strlen(input);
+
+ if (len != VOLUME_GUID_LEN * 2 && len != VOLUME_GUID_LEN * 2 + 4) {
+ exfat_err("invalid format for volume guid\n");
+ return -EINVAL;
+ }
+
+ for (i = 0, j = 0; i < len; i++) {
+ unsigned char ch = input[i];
+
+ if (ch >= '0' && ch <= '9')
+ ch -= '0';
+ else if (ch >= 'a' && ch <= 'f')
+ ch -= 'a' - 0xA;
+ else if (ch >= 'A' && ch <= 'F')
+ ch -= 'A' - 0xA;
+ else if (ch == '-' && len == VOLUME_GUID_LEN * 2 + 4 &&
+ (i == 8 || i == 13 || i == 18 || i == 23))
+ continue;
+ else {
+ exfat_err("invalid character '%c' for volume GUID\n", ch);
+ return -EINVAL;
+ }
+
+ if (j & 1)
+ guid[j >> 1] |= ch;
+ else
+ guid[j >> 1] = ch << 4;
+
+ j++;
+
+ if (ch == 0)
+ zero_len++;
+ }
+
+ if (zero_len == VOLUME_GUID_LEN * 2) {
+ exfat_err("%s is invalid for volume GUID\n", input);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int exfat_read_volume_guid(struct exfat *exfat)
+{
+ int err;
+ uint16_t checksum = 0;
+ struct exfat_dentry *dentry;
+ struct exfat_lookup_filter filter = {
+ .in.type = EXFAT_GUID,
+ .in.dentry_count = 1,
+ .in.filter = NULL,
+ };
+
+ err = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+ if (err)
+ return err;
+
+ dentry = filter.out.dentry_set;
+ exfat_calc_dentry_checksum(dentry, &checksum, true);
+
+ if (cpu_to_le16(checksum) == dentry->dentry.guid.checksum)
+ print_guid("GUID", dentry->dentry.guid.guid);
+ else
+ exfat_info("GUID is corrupted, please delete it or set a new one\n");
+
+ free(dentry);
+
+ return err;
+}
+
+int __exfat_set_volume_guid(struct exfat_dentry *dentry, const char *guid)
+{
+ int err;
+ uint16_t checksum = 0;
+
+ memset(dentry, 0, sizeof(*dentry));
+ dentry->type = EXFAT_GUID;
+
+ err = set_guid(dentry->dentry.guid.guid, guid);
+ if (err)
+ return err;
+
+ exfat_calc_dentry_checksum(dentry, &checksum, true);
+ dentry->dentry.guid.checksum = cpu_to_le16(checksum);
+
+ return 0;
+}
+
+/*
+ * Create/Update/Delete GUID dentry in root directory
+ *
+ * create/update GUID if @guid is not NULL.
+ * delete GUID if @guid is NULL.
+ */
+int exfat_set_volume_guid(struct exfat *exfat, const char *guid)
+{
+ struct exfat_dentry *dentry;
+ struct exfat_dentry_loc loc;
+ int err;
+
+ struct exfat_lookup_filter filter = {
+ .in.type = EXFAT_GUID,
+ .in.dentry_count = 1,
+ .in.filter = NULL,
+ };
+
+ err = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+ if (!err) {
+ /* GUID entry is found */
+ dentry = filter.out.dentry_set;
+ } else {
+ /* no GUID to delete */
+ if (guid == NULL)
+ return 0;
+
+ dentry = calloc(1, sizeof(*dentry));
+ if (!dentry)
+ return -ENOMEM;
+ }
+
+ if (guid) {
+ /* Set GUID */
+ err = __exfat_set_volume_guid(dentry, guid);
+ if (err)
+ goto out;
+ } else {
+ /* Delete GUID */
+ dentry->type &= ~EXFAT_INVAL;
+ }
+
+ loc.parent = exfat->root;
+ loc.file_offset = filter.out.file_offset;
+ loc.dev_offset = filter.out.dev_offset;
+ err = exfat_add_dentry_set(exfat, &loc, dentry, 1, false);
+ if (!err) {
+ if (guid)
+ print_guid("new GUID", dentry->dentry.guid.guid);
+ else
+ exfat_info("GUID is deleted\n");
+ }
+
+out:
+ free(dentry);
+
+ return err;
+}
+
int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off)
{
int ret;
diff --git a/manpages/mkfs.exfat.8 b/manpages/mkfs.exfat.8
index 9f867d3..76a0065 100644
--- a/manpages/mkfs.exfat.8
+++ b/manpages/mkfs.exfat.8
@@ -17,6 +17,9 @@ mkfs.exfat \- create an exFAT filesystem
.B \-L
.I volume_label
] [
+.B \-U
+.I volume_guid
+] [
.B \-\-pack\-bitmap
] [
.B \-v
@@ -67,12 +70,23 @@ _
>128 GiB \[<=]512 GiB 256 KiB 32 MiB
>512 GiB \[<=]2 TiB 512 KiB 64 MiB
.TE
+The default is always 1 MiB.
.TP
.BR \-c ", " \-\-cluster\-size =\fIsize\fR
Specifies the cluster size of the exFAT file system.
The \fIsize\fR argument is specified in bytes or may be specified with
\fBm\fR/\fBM\fR suffix for mebibytes or \fBk\fR/\fBK\fR suffix for kibibytes
and must be a power of two.
+The default value is described in the following table:
+.TS
+center;
+cb1s6cb,nnn.
+Card Capacity Range Cluster Size
+_
+ \[<=]256 MiB 4 KiB
+>256 MiB \[<=]32 GiB 32 KiB
+>32 GiB 128 KiB
+.TE
.TP
.BR \-f ", " \-\-full\-format
Performs a full format.
@@ -84,6 +98,9 @@ Prints the help and exit.
.BR \-L ", " \-\-volume\-label =\fIlabel\fR
Specifies the volume label to be associated with the exFAT filesystem.
.TP
+.BR \-U ", " \-\-volume\-guid =\fIguid\fR
+Specifies the volume GUID to be associated with the exFAT filesystem.
+.TP
.B \-\-pack\-bitmap
Attempts to relocate the exFAT allocation bitmap so that it ends at the
alignment boundary immediately following the FAT rather than beginning at that
@@ -101,6 +118,9 @@ allocation unit with the FAT.
If there is insufficient space for the bitmap there, then this option will have
no effect, and the bitmap will be aligned at the boundary as by default.
.TP
+.BR \-q ", " \-\-quiet
+Prints only error messages while creating the exFAT filesystem.
+.TP
.BR \-v ", " \-\-verbose
Prints verbose debugging information while creating the exFAT filesystem.
.TP
diff --git a/manpages/tune.exfat.8 b/manpages/tune.exfat.8
index 865dc07..b57c746 100644
--- a/manpages/tune.exfat.8
+++ b/manpages/tune.exfat.8
@@ -10,6 +10,12 @@ tune.exfat \- adjust tunable filesystem parameters on an exFAT filesystem
.B \-L
.I set-label
] [
+.B \-u
+.I print-guid
+] [
+.B \-U
+.I set-guid
+] [
.B \-i
.I print-serial
] [
@@ -33,6 +39,12 @@ Print the volume label of the exFAT filesystem.
.BI \-L " set-label"
Set the volume label of the filesystem to the provided argument.
.TP
+.BI \-u " print-guid"
+Print the volume GUID of the exFAT filesystem.
+.TP
+.BI \-U " set-guid"
+Set the volume GUID of the filesystem to the provided argument.
+.TP
.BI \-i " print-serial"
Print the volume serial of the exFAT filesystem.
.TP
diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c
index f9e5bb4..773c64e 100644
--- a/mkfs/mkfs.c
+++ b/mkfs/mkfs.c
@@ -337,8 +337,8 @@ static int exfat_create_bitmap(struct exfat_blk_dev *bd)
static int exfat_create_root_dir(struct exfat_blk_dev *bd,
struct exfat_user_input *ui)
{
- struct exfat_dentry ed[3] = {0};
- int dentries_len = sizeof(struct exfat_dentry) * 3;
+ struct exfat_dentry ed[4] = {0};
+ int dentries_len = sizeof(ed);
int nbytes;
/* Set volume label entry */
@@ -347,17 +347,29 @@ static int exfat_create_root_dir(struct exfat_blk_dev *bd,
memcpy(ed[0].vol_label, ui->volume_label, ui->volume_label_len);
ed[0].vol_char_cnt = ui->volume_label_len/2;
+ /* Set volume GUID entry */
+ if (ui->guid) {
+ if (__exfat_set_volume_guid(&ed[1], ui->guid))
+ return -1;
+ } else {
+ /*
+ * Since a single empty entry cannot be allocated for a
+ * file, this can reserve the entry for volume GUID.
+ */
+ ed[1].type = EXFAT_GUID & ~EXFAT_INVAL;
+ }
+
/* Set bitmap entry */
- ed[1].type = EXFAT_BITMAP;
- ed[1].bitmap_flags = 0;
- ed[1].bitmap_start_clu = cpu_to_le32(EXFAT_FIRST_CLUSTER);
- ed[1].bitmap_size = cpu_to_le64(finfo.bitmap_byte_len);
+ ed[2].type = EXFAT_BITMAP;
+ ed[2].bitmap_flags = 0;
+ ed[2].bitmap_start_clu = cpu_to_le32(EXFAT_FIRST_CLUSTER);
+ ed[2].bitmap_size = cpu_to_le64(finfo.bitmap_byte_len);
/* Set upcase table entry */
- ed[2].type = EXFAT_UPCASE;
- ed[2].upcase_checksum = cpu_to_le32(0xe619d30d);
- ed[2].upcase_start_clu = cpu_to_le32(finfo.ut_start_clu);
- ed[2].upcase_size = cpu_to_le64(EXFAT_UPCASE_TABLE_SIZE);
+ ed[3].type = EXFAT_UPCASE;
+ ed[3].upcase_checksum = cpu_to_le32(0xe619d30d);
+ ed[3].upcase_start_clu = cpu_to_le32(finfo.ut_start_clu);
+ ed[3].upcase_size = cpu_to_le64(EXFAT_UPCASE_TABLE_SIZE);
nbytes = pwrite(bd->dev_fd, ed, dentries_len, finfo.root_byte_off);
if (nbytes != dentries_len) {
@@ -373,11 +385,13 @@ static void usage(void)
{
fputs("Usage: mkfs.exfat\n"
"\t-L | --volume-label=label Set volume label\n"
+ "\t-U | --volume-guid=guid Set volume GUID\n"
"\t-c | --cluster-size=size(or suffixed by 'K' or 'M') Specify cluster size\n"
"\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-V | --version Show version\n"
+ "\t-q | --quiet Print only errors\n"
"\t-v | --verbose Print debug\n"
"\t-h | --help Show help\n",
stderr);
@@ -389,11 +403,13 @@ static void usage(void)
static const struct option opts[] = {
{"volume-label", required_argument, NULL, 'L' },
+ {"volume-guid", required_argument, NULL, 'U' },
{"cluster-size", required_argument, NULL, 'c' },
{"boundary-align", required_argument, NULL, 'b' },
{"pack-bitmap", no_argument, NULL, PACK_BITMAP },
{"full-format", no_argument, NULL, 'f' },
{"version", no_argument, NULL, 'V' },
+ {"quiet", no_argument, NULL, 'q' },
{"verbose", no_argument, NULL, 'v' },
{"help", no_argument, NULL, 'h' },
{"?", no_argument, NULL, '?' },
@@ -605,6 +621,7 @@ int main(int argc, char *argv[])
struct exfat_blk_dev bd;
struct exfat_user_input ui;
bool version_only = false;
+ bool quiet = false;
init_user_input(&ui);
@@ -612,7 +629,7 @@ int main(int argc, char *argv[])
exfat_err("failed to init locale/codeset\n");
opterr = 0;
- while ((c = getopt_long(argc, argv, "n:L:c:b:fVvh", opts, NULL)) != EOF)
+ while ((c = getopt_long(argc, argv, "n:L:U:c:b:fVqvh", opts, NULL)) != EOF)
switch (c) {
/*
* Make 'n' option fallthrough to 'L' option for for backward
@@ -629,6 +646,10 @@ int main(int argc, char *argv[])
ui.volume_label_len = ret;
break;
}
+ case 'U':
+ if (*optarg != '\0' && *optarg != '\r')
+ ui.guid = optarg;
+ break;
case 'c':
ret = parse_size(optarg);
if (ret < 0)
@@ -664,6 +685,10 @@ int main(int argc, char *argv[])
case 'V':
version_only = true;
break;
+ case 'q':
+ print_level = EXFAT_ERROR;
+ quiet = true;
+ break;
case 'v':
print_level = EXFAT_DEBUG;
break;
@@ -673,9 +698,12 @@ int main(int argc, char *argv[])
usage();
}
- show_version();
- if (version_only)
+ if (version_only) {
+ show_version();
exit(EXIT_FAILURE);
+ } else if (!quiet) {
+ show_version();
+ }
if (argc - optind != 1) {
usage();
@@ -708,6 +736,6 @@ out:
if (!ret)
exfat_info("\nexFAT format complete!\n");
else
- exfat_info("\nexFAT format fail!\n");
+ exfat_err("\nexFAT format fail!\n");
return ret;
}
diff --git a/tests/directory_no_clu/exfat.img.tar.xz b/tests/directory_no_clu/exfat.img.tar.xz
deleted file mode 100644
index 6c92ca5..0000000
--- a/tests/directory_no_clu/exfat.img.tar.xz
+++ /dev/null
Binary files differ
diff --git a/tests/duplicated_name/exfat.img.tar.xz b/tests/duplicated_name/exfat.img.tar.xz
new file mode 100644
index 0000000..ceb046f
--- /dev/null
+++ b/tests/duplicated_name/exfat.img.tar.xz
Binary files differ
diff --git a/tests/test_fsck.sh b/tests/test_fsck.sh
index c53d8f3..66f4cfb 100755
--- a/tests/test_fsck.sh
+++ b/tests/test_fsck.sh
@@ -43,6 +43,17 @@ for TESTCASE_DIR in $TESTCASE_DIRS; do
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
diff --git a/tune/tune.c b/tune/tune.c
index 135f624..f883556 100644
--- a/tune/tune.c
+++ b/tune/tune.c
@@ -20,6 +20,8 @@ static void usage(void)
fprintf(stderr, "Usage: tune.exfat\n");
fprintf(stderr, "\t-l | --print-label Print volume label\n");
fprintf(stderr, "\t-L | --set-label=label Set volume label\n");
+ fprintf(stderr, "\t-u | --print-guid Print volume GUID\n");
+ fprintf(stderr, "\t-U | --set-guid=guid Set volume GUID\n");
fprintf(stderr, "\t-i | --print-serial Print volume serial\n");
fprintf(stderr, "\t-I | --set-serial=value Set volume serial\n");
fprintf(stderr, "\t-V | --version Show version\n");
@@ -32,6 +34,8 @@ static void usage(void)
static struct option opts[] = {
{"print-label", no_argument, NULL, 'l' },
{"set-label", required_argument, NULL, 'L' },
+ {"print-guid", no_argument, NULL, 'u' },
+ {"set-guid", required_argument, NULL, 'U' },
{"print-serial", no_argument, NULL, 'i' },
{"set-serial", required_argument, NULL, 'I' },
{"version", no_argument, NULL, 'V' },
@@ -59,7 +63,7 @@ int main(int argc, char *argv[])
exfat_err("failed to init locale/codeset\n");
opterr = 0;
- while ((c = getopt_long(argc, argv, "I:iL:lVvh", opts, NULL)) != EOF)
+ while ((c = getopt_long(argc, argv, "I:iL:lU:uVvh", opts, NULL)) != EOF)
switch (c) {
case 'l':
flags = EXFAT_GET_VOLUME_LABEL;
@@ -69,6 +73,14 @@ int main(int argc, char *argv[])
optarg);
flags = EXFAT_SET_VOLUME_LABEL;
break;
+ case 'u':
+ flags = EXFAT_GET_VOLUME_GUID;
+ break;
+ case 'U':
+ if (*optarg != '\0' && *optarg != '\r')
+ ui.guid = optarg;
+ flags = EXFAT_SET_VOLUME_GUID;
+ break;
case 'i':
flags = EXFAT_GET_VOLUME_SERIAL;
break;
@@ -140,6 +152,11 @@ int main(int argc, char *argv[])
ret = exfat_read_volume_label(exfat);
else if (flags == EXFAT_SET_VOLUME_LABEL)
ret = exfat_set_volume_label(exfat, label_input);
+ else if (flags == EXFAT_GET_VOLUME_GUID)
+ ret = exfat_read_volume_guid(exfat);
+ else if (flags == EXFAT_SET_VOLUME_GUID)
+ ret = exfat_set_volume_guid(exfat, ui.guid);
+
close_fd_out:
close(bd.dev_fd);
if (exfat)