* exFAT Defragmentation Tool - defrag.exfat
* Usage: defrag.exfat [-a] /dev/sdX
* Effect: defragment or assess the entire exFAT-formated device
* Feature: support safe interruption via Ctrl+C
* Copyright (C) 2025 Haodong Xia <3394797836@qq.com>
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <getopt.h>
#include <locale.h>
#include <sys/wait.h>
#include "defrag.h"
#include "exfat_ondisk.h"
#include "libexfat.h"
#include "exfat_dir.h"
#include "exfat_fs.h"
static volatile sig_atomic_t interrupt_received;
static uint32_t interrupt_point;
static void sigint_handler(int sig)
{
(void)sig;
interrupt_received = 1;
}
* Read or write the FAT table.
* Returns 0 on success.
*/
static int exfat_rw_FAT(struct exfat *exfat, uint32_t *list, uint32_t list_len, bool read)
{
off_t offset;
size_t len_1, len_2;
uint32_t i;
offset = (off_t)le32_to_cpu(exfat->bs->bsx.fat_offset) << exfat->bs->bsx.sect_size_bits;
len_1 = (size_t)(list_len * sizeof(uint32_t));
if (read) {
len_2 = exfat_read(exfat->blk_dev->dev_fd, list, len_1, offset);
if (len_2 != len_1)
return -EIO;
for (i = 0; i < list_len; i++)
list[i] = le32_to_cpu(list[i]);
} else {
for (i = 0; i < list_len; i++)
list[i] = cpu_to_le32(list[i]);
len_2 = exfat_write(exfat->blk_dev->dev_fd, list, len_1, offset);
if (len_2 != len_1)
return -EIO;
}
return 0;
}
* Read or write the allocation bitmap.
* Returns 0 on success.
*/
static int exfat_rw_bitmap(struct exfat *exfat, bool read)
{
ssize_t rw_len = 0;
struct exfat_dentry *dentry;
struct exfat_lookup_filter filter = {
.in.type = EXFAT_BITMAP,
.in.dentry_count = 0,
.in.filter = NULL,
.in.param = NULL,
};
int retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
if (retval != 0)
return retval;
dentry = filter.out.dentry_set;
if (le64_to_cpu(dentry->bitmap_size) < DIV_ROUND_UP(exfat->clus_count, 8)) {
exfat_err("invalid size of alloc_bitmap. 0x%" PRIx64 "\n",
le64_to_cpu(dentry->bitmap_size));
return -EINVAL;
}
if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) {
exfat_err("invalid start cluster of alloc_bitmap. 0x%x\n",
le32_to_cpu(dentry->bitmap_start_clu));
return -EINVAL;
}
exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu);
exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8);
free(filter.out.dentry_set);
if (read) {
rw_len = exfat_read(exfat->blk_dev->dev_fd, exfat->alloc_bitmap,
exfat->disk_bitmap_size,
exfat_c2o(exfat, exfat->disk_bitmap_clus));
if (rw_len != exfat->disk_bitmap_size)
return -EIO;
} else {
rw_len = exfat_write(exfat->blk_dev->dev_fd, exfat->alloc_bitmap,
exfat->disk_bitmap_size,
exfat_c2o(exfat, exfat->disk_bitmap_clus));
if (rw_len != exfat->disk_bitmap_size)
return -EIO;
}
return 0;
}
* From libexfat.c
* Updates the boot sector checksum.
*/
static int exfat_update_boot_checksum(struct exfat_blk_dev *bd, bool is_backup)
{
unsigned int checksum = 0;
int ret, sec_idx, backup_sec_idx = 0;
unsigned char *buf;
buf = malloc(bd->sector_size);
if (!buf) {
exfat_err("Cannot allocate pbr: out of memory\n");
return -1;
}
if (is_backup)
backup_sec_idx = BACKUP_BOOT_SEC_IDX;
for (sec_idx = BOOT_SEC_IDX; sec_idx < CHECKSUM_SEC_IDX; sec_idx++) {
bool is_boot_sec = false;
ret = exfat_read_sector(bd, buf, sec_idx + backup_sec_idx);
if (ret < 0) {
exfat_err("sector(%d) read failed\n", sec_idx);
ret = -1;
goto free_buf;
}
if (sec_idx == BOOT_SEC_IDX)
is_boot_sec = true;
boot_calc_checksum(buf, bd->sector_size, is_boot_sec, &checksum);
}
ret = exfat_write_checksum_sector(bd, NULL, checksum, is_backup);
free_buf:
free(buf);
return ret;
}
* Adds a new virtual node to set->next_clus and sets its value to 'data'.
* Returns 0 on success.
*/
static int add_virtual_node(struct cluster_info_set *set, uint32_t data)
{
uint32_t offset = set->num_phys_clus;
uint32_t index = set->num_clus_chain;
size_t new_size;
uint32_t *new_next_clus;
if (index % VIRT_SPACE_ALIGN == 0) {
new_size = (offset + index + VIRT_SPACE_ALIGN) * sizeof(uint32_t);
new_next_clus = realloc(set->next_clus, new_size);
if (new_next_clus == NULL) {
exfat_err("memory run out in %s", __func__);
return -ENOMEM;
}
set->next_clus = new_next_clus;
}
set->next_clus[offset + index] = data;
set->num_clus_chain++;
return 0;
}
* Sets FAT entries for a contiguous cluster chain to ensure correctness.
*/
static int set_physical_nodes(struct cluster_info_set *set,
uint32_t begin, uint32_t count, uint32_t data)
{
uint32_t i;
if (begin < EXFAT_FIRST_CLUSTER || begin + count >= set->num_phys_clus) {
exfat_err("%s: invalid parameter \"begin = %u, count = %u\"\n", __func__,
begin, count);
return -1;
}
for (i = begin; i < begin + count; i++)
set->next_clus[i] = data++;
set->next_clus[begin + count] = EXFAT_EOF_CLUSTER;
return 0;
}
* Builds the 'prev_clus' array from the 'next_clus' links.
*/
static void next_to_prev(struct cluster_info_set *set)
{
uint32_t cur_clu, last_clu;
uint32_t n_clus = set->num_phys_clus;
uint32_t n_file = set->num_clus_chain;
uint32_t i;
for (i = n_clus; i < n_clus + n_file; i++) {
cur_clu = i;
last_clu = EXFAT_EOF_CLUSTER;
while (cur_clu != EXFAT_EOF_CLUSTER) {
set->prev_clus[cur_clu] = last_clu;
last_clu = cur_clu;
cur_clu = set->next_clus[cur_clu];
}
}
}
* Implementation of BFS_read_file_tree.
* Returns 0 on success.
*/
static int BFS_read_children(struct exfat_defrag *defrag, struct exfat_inode *inode)
{
int ret;
uint16_t attr, checksum;
uint32_t first_clu, dentry_count, i, FAT_entry_count;
uint64_t size;
bool is_contiguous;
struct exfat_de_iter de_iter;
struct exfat_dentry *first_dentry = NULL, *dentry = NULL;
struct exfat_inode *child_inode = NULL;
struct cluster_info_set *set = &(defrag->clu_info_set);
ret = exfat_de_iter_init(&de_iter, defrag->exfat, inode, defrag->dentry_buffer);
if (ret == EOF)
return 0;
else if (ret != 0) {
exfat_err("fail in exfat_de_iter_init\n");
return -1;
}
while (1) {
checksum = 0;
dentry_count = 1;
ret = exfat_de_iter_get(&de_iter, 0, &first_dentry);
if (ret == EOF)
goto success;
else if (ret != 0) {
exfat_err("fail in exfat_de_iter_get first\n");
goto fail;
}
switch (first_dentry->type) {
case EXFAT_FILE:
dentry_count += first_dentry->file_num_ext;
attr = le16_to_cpu(first_dentry->file_attr);
ret = exfat_de_iter_get(&de_iter, 1, &dentry);
if (ret != 0 || dentry->type != EXFAT_STREAM) {
exfat_err("fail in exfat_de_iter_get second\n");
goto fail;
}
first_clu = le32_to_cpu(dentry->stream_start_clu);
is_contiguous = dentry->stream_flags & EXFAT_SF_CONTIGUOUS;
size = le64_to_cpu(dentry->stream_size);
if (size != 0) {
if (!exfat_heap_clus(defrag->exfat, first_clu)) {
exfat_err("%s: invalid first_clu = %u\n", __func__,
first_clu);
goto fail;
}
ret = add_virtual_node(set, first_clu);
if (ret != 0)
goto fail;
if (is_contiguous) {
FAT_entry_count =
(uint32_t)((size - 1) / defrag->exfat->clus_size);
if (set_physical_nodes(set, first_clu,
FAT_entry_count, first_clu + 1) < 0)
goto fail;
}
if (attr & ATTR_SUBDIR) {
child_inode = exfat_alloc_inode(attr);
if (child_inode == NULL)
goto fail;
child_inode->parent = inode;
child_inode->dentry_count = dentry_count;
child_inode->first_clus = first_clu;
child_inode->is_contiguous = is_contiguous;
child_inode->size = size;
list_add_tail(&child_inode->list, &defrag->exfat->dir_list);
}
}
exfat_calc_dentry_checksum(first_dentry, &checksum, true);
exfat_calc_dentry_checksum(dentry, &checksum, false);
for (i = 2; i < dentry_count; i++) {
ret = exfat_de_iter_get(&de_iter, i, &dentry);
if (ret != 0) {
exfat_err("%s: fail in exfat_de_iter_get extended\n",
__func__);
goto fail;
}
exfat_calc_dentry_checksum(dentry, &checksum, false);
if (dentry->type != EXFAT_VENDOR_ALLOC)
continue;
first_clu = le32_to_cpu(dentry->vendor_alloc_start_clu);
is_contiguous = dentry->vendor_alloc_flags & EXFAT_SF_CONTIGUOUS;
size = le64_to_cpu(dentry->vendor_alloc_size);
if (size == 0)
continue;
if (!exfat_heap_clus(defrag->exfat, first_clu)) {
exfat_err("%s: invalid first_clu = %u\n", __func__,
first_clu);
goto fail;
}
ret = add_virtual_node(set, first_clu);
if (ret != 0)
goto fail;
if (is_contiguous) {
FAT_entry_count =
(uint32_t)((size - 1) / defrag->exfat->clus_size);
if (set_physical_nodes(set, first_clu,
FAT_entry_count, first_clu + 1) < 0)
goto fail;
}
}
if (first_dentry->file_checksum != checksum) {
exfat_err("%s: invalid checksum, please run fsck.exfat to check and repair\n",
__func__);
goto fail;
}
break;
case EXFAT_BITMAP:
first_clu = le32_to_cpu(first_dentry->bitmap_start_clu);
ret = add_virtual_node(set, first_clu);
if (ret != 0)
goto fail;
size = le32_to_cpu(first_dentry->bitmap_size);
FAT_entry_count = (uint32_t)((size - 1) / defrag->exfat->clus_size);
if (set_physical_nodes(set, first_clu, FAT_entry_count, first_clu + 1) < 0)
goto fail;
break;
case EXFAT_UPCASE:
first_clu = le32_to_cpu(first_dentry->upcase_start_clu);
ret = add_virtual_node(set, first_clu);
if (ret != 0)
goto fail;
size = le32_to_cpu(first_dentry->upcase_size);
FAT_entry_count = (uint32_t)((size - 1) / defrag->exfat->clus_size);
if (set_physical_nodes(set, first_clu, FAT_entry_count, first_clu + 1) < 0)
goto fail;
break;
case EXFAT_LAST:
goto success;
default:
break;
}
exfat_de_iter_advance(&de_iter, dentry_count);
}
success:
exfat_de_iter_flush(&de_iter);
return 0;
fail:
exfat_de_iter_flush(&de_iter);
return -1;
}
* BFS traversal to read directory entries and populate virtual head nodes in next_clus.
*/
static int BFS_read_file_tree(struct exfat_defrag *defrag)
{
struct exfat *exfat = defrag->exfat;
struct exfat_inode *parent;
struct cluster_info_set *set = &(defrag->clu_info_set);
uint32_t i, n_clus, n_chain, tmp_clu;
list_add(&exfat->root->list, &exfat->dir_list);
if (add_virtual_node(set, exfat->root->first_clus) < 0)
goto err;
while (!list_empty(&exfat->dir_list)) {
parent = list_entry(exfat->dir_list.next, struct exfat_inode, list);
if (!(parent->attr & ATTR_SUBDIR)) {
exfat_err("%s: parent_inode is not dir\n", __func__);
goto err;
}
if (BFS_read_children(defrag, parent) < 0)
goto err;
list_del(&parent->list);
if (parent != exfat->root)
exfat_free_inode(parent);
}
n_clus = set->num_phys_clus;
n_chain = set->num_clus_chain;
for (i = n_clus; i < n_clus + n_chain; i++) {
tmp_clu = set->next_clus[i];
while (1) {
if (!exfat_heap_clus(exfat, tmp_clu)) {
exfat_err("%s: invalid fat entry = %u\n", __func__, tmp_clu);
goto err;
}
if (!exfat_bitmap_get(exfat->alloc_bitmap, tmp_clu)) {
exfat_err("%s: cluster %u should be alloced\n", __func__, tmp_clu);
goto err;
}
tmp_clu = set->next_clus[tmp_clu];
if (tmp_clu == EXFAT_EOF_CLUSTER)
break;
}
}
return 0;
err:
exfat_free_dir_list(exfat);
return -1;
}
* Implementation of BFS_write_file_tree.
* Returns 0 on success.
*/
static int BFS_write_children(struct exfat_defrag *defrag,
struct exfat_inode *inode, uint32_t *index)
{
int ret = 0;
uint16_t attr, checksum;
uint32_t first_clu, dentry_count, i;
struct exfat_de_iter de_iter;
struct exfat_dentry *first_dentry = NULL, *dentry = NULL;
struct exfat_inode *child_inode = NULL;
struct cluster_info_set *set = &(defrag->clu_info_set);
ret = exfat_de_iter_init(&de_iter, defrag->exfat, inode, defrag->dentry_buffer);
if (ret == EOF)
return 0;
else if (ret != 0) {
exfat_err("fail in exfat_de_iter_init\n");
return -1;
}
while (1) {
first_clu = set->next_clus[(*index)];
checksum = 0;
dentry_count = 1;
ret = exfat_de_iter_get_dirty(&de_iter, 0, &first_dentry);
if (ret == EOF)
goto success;
else if (ret != 0) {
exfat_err("fail in exfat_de_iter_get first\n");
goto fail;
}
switch (first_dentry->type) {
case EXFAT_FILE:
dentry_count += first_dentry->file_num_ext;
attr = le16_to_cpu(first_dentry->file_attr);
ret = exfat_de_iter_get_dirty(&de_iter, 1, &dentry);
if (ret != 0 || dentry->type != EXFAT_STREAM) {
exfat_err("fail in exfat_de_iter_get second\n");
goto fail;
}
if (le64_to_cpu(dentry->stream_size) != 0) {
dentry->stream_start_clu = cpu_to_le32(first_clu);
if ((*index) <= interrupt_point)
dentry->stream_flags |= EXFAT_SF_CONTIGUOUS;
else
dentry->stream_flags &= ~EXFAT_SF_CONTIGUOUS;
(*index)++;
if (attr & ATTR_SUBDIR) {
child_inode = exfat_alloc_inode(attr);
if (child_inode == NULL)
goto fail;
child_inode->parent = inode;
child_inode->dentry_count = dentry_count;
child_inode->first_clus = first_clu;
child_inode->is_contiguous = true;
child_inode->size = le64_to_cpu(dentry->stream_size);
list_add_tail(&child_inode->list, &defrag->exfat->dir_list);
}
}
exfat_calc_dentry_checksum(first_dentry, &checksum, true);
exfat_calc_dentry_checksum(dentry, &checksum, false);
for (i = 2; i < dentry_count; i++) {
ret = exfat_de_iter_get_dirty(&de_iter, i, &dentry);
if (ret != 0) {
exfat_err("%s: fail in exfat_de_iter_get extended\n",
__func__);
goto fail;
}
exfat_calc_dentry_checksum(dentry, &checksum, false);
if (dentry->type == EXFAT_VENDOR_ALLOC) {
dentry->vendor_alloc_start_clu =
cpu_to_le32(set->next_clus[(*index)]);
if ((*index) <= interrupt_point)
dentry->vendor_alloc_flags |= EXFAT_SF_CONTIGUOUS;
else
dentry->vendor_alloc_flags &= ~EXFAT_SF_CONTIGUOUS;
(*index)++;
}
}
first_dentry->file_checksum = cpu_to_le16(checksum);
break;
case EXFAT_BITMAP:
first_dentry->bitmap_start_clu = cpu_to_le32(first_clu);
(*index)++;
break;
case EXFAT_UPCASE:
first_dentry->upcase_start_clu = cpu_to_le32(first_clu);
(*index)++;
break;
case EXFAT_LAST:
goto success;
default:
break;
}
exfat_de_iter_advance(&de_iter, dentry_count);
}
success:
exfat_de_iter_flush(&de_iter);
return 0;
fail:
exfat_de_iter_flush(&de_iter);
return -1;
}
* BFS traversal to write directory entries using virtual head node info in next_clus.
*/
static int BFS_write_file_tree(struct exfat_defrag *defrag)
{
struct exfat *exfat = defrag->exfat;
struct exfat_inode *parent;
struct cluster_info_set *set = &(defrag->clu_info_set);
uint32_t index = set->num_phys_clus;
exfat->root->first_clus = set->next_clus[index++];
exfat->root->is_contiguous = true;
list_add(&exfat->root->list, &exfat->dir_list);
exfat->bs->bsx.root_cluster = exfat->root->first_clus;
if (exfat_write_sector(exfat->blk_dev, exfat->bs, BOOT_SEC_IDX) < 0) {
exfat_err("%s: fail in update boot sector\n", __func__);
goto err;
}
if (exfat_write_sector(exfat->blk_dev, exfat->bs, BACKUP_BOOT_SEC_IDX) < 0) {
exfat_err("%s: fail in update backup boot sector\n", __func__);
goto err;
}
if (exfat_update_boot_checksum(exfat->blk_dev, false) < 0) {
exfat_err("%s: fail in update boot sector checksum\n", __func__);
goto err;
}
if (exfat_update_boot_checksum(exfat->blk_dev, true) < 0) {
exfat_err("%s: fail in update backup boot sector checksum\n", __func__);
goto err;
}
while (!list_empty(&exfat->dir_list)) {
parent = list_entry(exfat->dir_list.next, struct exfat_inode, list);
if (!(parent->attr & ATTR_SUBDIR)) {
exfat_err("%s: parent_inode is not dir\n", __func__);
goto err;
}
if (BFS_write_children(defrag, parent, &index) < 0)
goto err;
list_del(&parent->list);
if (parent != exfat->root)
exfat_free_inode(parent);
}
return 0;
err:
exfat_free_dir_list(exfat);
return -1;
}
* Read or write a single cluster.
* Returns 0 on success.
*/
static int exfat_rw_clu(struct exfat *exfat, void *tmp, uint32_t cluster, bool read)
{
off_t offset;
size_t rw_len;
offset = (off_t)exfat_c2o(exfat, cluster);
if (read)
rw_len = exfat_read(exfat->blk_dev->dev_fd, tmp, (size_t)exfat->clus_size, offset);
else
rw_len = exfat_write(exfat->blk_dev->dev_fd, tmp, (size_t)exfat->clus_size, offset);
if (rw_len != (size_t)exfat->clus_size)
return -EIO;
return 0;
}
* Swap two clusters at the data level.
* Returns 0 on success.
*/
static int data_swap(struct exfat_defrag *defrag, uint32_t clu_1,
uint32_t clu_2, bool clu_1_not_free)
{
struct exfat *exfat = defrag->exfat;
void *tmp1 = defrag->tmp_clus;
void *tmp2 = defrag->tmp_clus + exfat->clus_size;
int ret = 0;
if (clu_1_not_free) {
ret = exfat_rw_clu(exfat, tmp1, clu_1, true);
if (ret < 0)
goto out;
ret = exfat_rw_clu(exfat, tmp2, clu_2, true);
if (ret < 0)
goto out;
ret = exfat_rw_clu(exfat, tmp1, clu_2, false);
if (ret < 0)
goto out;
ret = exfat_rw_clu(exfat, tmp2, clu_1, false);
if (ret < 0)
goto out;
} else {
ret = exfat_rw_clu(exfat, tmp2, clu_2, true);
if (ret < 0)
goto out;
ret = exfat_rw_clu(exfat, tmp2, clu_1, false);
if (ret < 0)
goto out;
}
out:
return ret;
}
static void clus_connect(struct cluster_info_set *set, uint32_t clu_1, uint32_t clu_2)
{
set->next_clus[clu_1] = clu_2;
if (clu_2 != EXFAT_EOF_CLUSTER)
set->prev_clus[clu_2] = clu_1;
}
static void clus_insert(struct cluster_info_set *set, uint32_t clu_1_prev,
uint32_t clu_1_next, uint32_t clu_2)
{
clus_connect(set, clu_1_prev, clu_2);
clus_connect(set, clu_2, clu_1_next);
}
* Special case for adjacent clusters during swap:
* Before: clu_0 -> clu_1 -> clu_2 -> clu_3
* After: clu_0 -> clu_2 -> clu_1 -> clu_3
*/
static void clus_exchange(struct cluster_info_set *set, uint32_t clu_0,
uint32_t clu_1, uint32_t clu_2, uint32_t clu_3)
{
clus_connect(set, clu_1, clu_3);
clus_connect(set, clu_0, clu_2);
clus_connect(set, clu_2, clu_1);
}
* Swap two clusters at both data and metadata levels.
* Returns 0 on success.
*/
static int exfat_swap_clus(struct exfat_defrag *defrag, uint32_t clu_1, uint32_t clu_2)
{
char *bitmap = defrag->exfat->alloc_bitmap;
bool clu_1_not_free = exfat_bitmap_get(bitmap, clu_1);
struct cluster_info_set *set = &(defrag->clu_info_set);
uint32_t clu_1_next = set->next_clus[clu_1];
uint32_t clu_1_prev = set->prev_clus[clu_1];
uint32_t clu_2_next = set->next_clus[clu_2];
uint32_t clu_2_prev = set->prev_clus[clu_2];
if (data_swap(defrag, clu_1, clu_2, clu_1_not_free) < 0)
return -EIO;
if (clu_1_not_free) {
if (clu_1_next == clu_2) {
clus_exchange(set, clu_1_prev, clu_1, clu_2, clu_2_next);
} else if (clu_2_next == clu_1) {
clus_exchange(set, clu_2_prev, clu_2, clu_1, clu_1_next);
} else {
clus_insert(set, clu_1_prev, clu_1_next, clu_2);
clus_insert(set, clu_2_prev, clu_2_next, clu_1);
}
} else {
clus_connect(set, clu_2_prev, clu_1);
clus_connect(set, clu_1, clu_2_next);
set->next_clus[clu_2] = EXFAT_FREE_CLUSTER;
set->prev_clus[clu_2] = EXFAT_FREE_CLUSTER;
exfat_bitmap_set(bitmap, clu_1);
exfat_bitmap_clear(bitmap, clu_2);
}
return 0;
}
* Core defragmentation logic.
* 1. exfat_defrag uses exfat_swap_clus for cluster swapping.
* 2. exfat_swap_clus uses data_swap for data exchange.
* 3. exfat_swap_clus uses clus_connect, clus_exchange,
* clus_insert for metadata update.
* Returns 0 on success.
*/
static int exfat_defrag(struct exfat_defrag *defrag)
{
int ret = 0;
struct cluster_info_set *set = &(defrag->clu_info_set);
uint32_t n_clus = set->num_phys_clus;
uint32_t n_file = set->num_clus_chain;
uint32_t scan_ptr = EXFAT_RESERVED_CLUSTERS, exchanged_ptr = 0;
uint32_t i;
exfat_info("Defragmentation is in progress -- please keep the device connected.\n");
exfat_info("WARNING: Removing or powering off the device during execution may corrupt the file system.\n");
exfat_info("If you do not want to wait, just press Ctrl+C to terminate it safely.\n");
for (i = n_clus; i < n_clus + n_file; i++) {
exchanged_ptr = set->next_clus[i];
while (1) {
if (exchanged_ptr == EXFAT_EOF_CLUSTER)
break;
if (scan_ptr != exchanged_ptr) {
ret = exfat_swap_clus(defrag, scan_ptr, exchanged_ptr);
if (ret < 0)
goto out;
}
exchanged_ptr = set->next_clus[scan_ptr];
scan_ptr++;
}
exfat_info("\r\033[Kdefrag.exfat has processed %u / %u tasks",
i - n_clus + 1, n_file);
fflush(stdout);
if (interrupt_received) {
interrupt_point = i;
exfat_info("\nReceived termination signal! Writing back metadata...\n");
goto out;
}
}
exfat_info("\nWriting back metadata...\n");
out:
return ret;
}
* Evaluate fragmentation level from two perspectives:
* cluster chain contiguity and free cluster contiguity
*/
static void exfat_defrag_assess(struct exfat_defrag *defrag)
{
uint32_t i, n_chain, n_clus, cur_clu, last_clu;
uint32_t num_alloc_breakpoint = 0, num_free_breakpoint = 0;
uint32_t num_free_clus = 0, num_alloc_clus = 0;
double alloc_space_rate, free_space_rate;
double alloc_space_frag_rate = 0.0, free_space_frag_rate = 0.0, overall_frag_rate = 0.0;
struct exfat *exfat = defrag->exfat;
struct cluster_info_set *set = &(defrag->clu_info_set);
n_chain = set->num_clus_chain;
n_clus = set->num_phys_clus;
for (i = n_clus; i < n_clus + n_chain; i++) {
cur_clu = set->next_clus[i];
last_clu = EXFAT_EOF_CLUSTER;
while (cur_clu != EXFAT_EOF_CLUSTER) {
if (last_clu != EXFAT_EOF_CLUSTER && last_clu + 1 != cur_clu)
num_alloc_breakpoint++;
last_clu = cur_clu;
cur_clu = set->next_clus[cur_clu];
}
}
last_clu = 0;
for (cur_clu = EXFAT_FIRST_CLUSTER; cur_clu < exfat->clus_count + EXFAT_FIRST_CLUSTER;
cur_clu++) {
if (!exfat_bitmap_get(exfat->alloc_bitmap, cur_clu)) {
num_free_clus++;
if (last_clu != 0 && cur_clu != last_clu + 1)
num_free_breakpoint++;
last_clu = cur_clu;
}
}
num_alloc_clus = exfat->clus_count - num_free_clus;
if (exfat->clus_count == 0) {
exfat_err("%s: invalid cluster count == 0\n", __func__);
return;
}
alloc_space_rate = (double)(num_alloc_clus * 100) / exfat->clus_count;
free_space_rate = (double)(num_free_clus * 100) / exfat->clus_count;
if (num_alloc_clus > 0) {
alloc_space_frag_rate = (double)(num_alloc_breakpoint * 100) / num_alloc_clus;
overall_frag_rate += (alloc_space_frag_rate * alloc_space_rate) / 100;
}
if (num_free_clus > 0) {
free_space_frag_rate = (double)(num_free_breakpoint * 100) / num_free_clus;
overall_frag_rate += (free_space_frag_rate * free_space_rate) / 100;
}
exfat_info("Fragmentation Assessment Report:\n");
exfat_info("1. Allocated space: cluster count = %u (%.2lf%%), breakpoint count = %u, fragmentation rate = %.2lf%%\n",
num_alloc_clus, alloc_space_rate, num_alloc_breakpoint,
alloc_space_frag_rate);
exfat_info("2. Free space: cluster count = %u (%.2lf%%), breakpoint count = %u, fragmentation rate = %.2lf%%\n",
num_free_clus, free_space_rate, num_free_breakpoint, free_space_frag_rate);
exfat_info("3. Overall weighted fragmentation rate = %.2lf%% (Reference: 0%% ~ Light ~ %u%% ~ Moderate ~ %u%% ~ Severe ~ 100%%)\n",
overall_frag_rate, WATERMARK_1, WATERMARK_2);
}
static struct option opts[] = {
{"force", no_argument, NULL, 'f' },
{"assess", no_argument, NULL, 'a' },
{"version", no_argument, NULL, 'v' },
{"help", no_argument, NULL, 'h' },
{NULL, 0, NULL, 0 }
};
static void usage(char *name, int exit_code)
{
fprintf(stderr, "Usage: %s\n", name);
fprintf(stderr, "\tno option Perform defragmentation with \"fsck warning\"\n");
fprintf(stderr, "\t-f | --force Perform defragmentation\n");
fprintf(stderr, "\t-a | --assess Assess fragmentation status\n");
fprintf(stderr, "\t-v | --version Show version\n");
fprintf(stderr, "\t-h | --help Show help\n");
exit(exit_code);
}
int main(int argc, char *argv[])
{
struct exfat_defrag defrag;
struct exfat_user_input ui;
struct exfat_blk_dev bd;
struct cluster_info_set *set = &(defrag.clu_info_set);
int ret = 0, c = 0;
bool only_assessment = false;
bool only_show_version = false;
bool show_fsck_warning = true;
memset(&ui, 0, sizeof(ui));
memset(&bd, 0, sizeof(bd));
bd.dev_fd = -1;
if (!setlocale(LC_CTYPE, ""))
exfat_err("failed to init locale/codeset\n");
* Recognizable command format:
* 1. defrag.exfat /dev/sdX Perform defragmentation with "fsck warning"
* 2. defrag.exfat -f /dev/sdX Perform defragmentation
* 3. defrag.exfat -a /dev/sdX Assess fragmentation status
* 4. defrag.exfat -h Usage information
* 5. defrag.exfat -v Show version
*/
while ((c = getopt_long(argc, argv, "afhv", opts, NULL)) != EOF) {
switch (c) {
case 'f':
show_fsck_warning = false;
break;
case 'a':
only_assessment = true;
break;
case 'v':
only_show_version = true;
break;
case 'h':
usage(argv[0], 0);
break;
default:
exfat_err("Invalid command! Please refer to the help information below or consult the manual.\n");
usage(argv[0], 2);
}
}
show_version();
if (only_show_version)
return ret;
if (optind != argc - 1) {
exfat_err("Invalid command! Please refer to the help information below or consult the manual.\n");
usage(argv[0], 2);
}
if ((!only_assessment) && show_fsck_warning) {
exfat_info("\nWARNING: To ensure data safety and consistency, we strongly recommend\n");
exfat_info("running \"fsck.exfat\" to check and repair the device before defragmentation.\n");
exfat_info("Proceed without filesystem check? [y/N]: ");
fflush(stdout);
c = fgetc(stdin);
if (!(c == 'Y' || c == 'y')) {
exfat_info("Defrag aborted.\n");
return 1;
}
}
ui.dev_name = argv[optind];
if (only_assessment)
ui.writeable = false;
else
ui.writeable = true;
if (exfat_get_blk_dev_info(&ui, &bd) < 0) {
exfat_err("fail in exfat_get_blk_dev_info\n");
ret = 1;
goto out;
}
memset(&defrag, 0, sizeof(defrag));
defrag.exfat = exfat_alloc_exfat(&bd, NULL, NULL);
if (defrag.exfat == NULL) {
exfat_err("fail in exfat_alloc_exfat\n");
ret = 1;
goto out;
}
set->num_phys_clus = defrag.exfat->clus_count + EXFAT_FIRST_CLUSTER;
set->num_clus_chain = 0;
set->next_clus = calloc(set->num_phys_clus, sizeof(uint32_t));
if (set->next_clus == NULL) {
exfat_err("memory run out in defrag.cluster_info_set->next_clus\n");
ret = 1;
goto fsync_and_free;
}
defrag.tmp_clus = calloc(2, defrag.exfat->clus_size);
if (defrag.tmp_clus == NULL) {
exfat_err("memory run out in defrag.tmp_clus\n");
ret = 1;
goto fsync_and_free;
}
defrag.dentry_buffer = exfat_alloc_buffer(defrag.exfat);
if (defrag.dentry_buffer == NULL) {
exfat_err("memory run out in defrag.dentry_buffer\n");
ret = 1;
goto fsync_and_free;
}
if (exfat_rw_bitmap(defrag.exfat, true) < 0) {
exfat_err("fail in exfat_read_bitmap\n");
ret = 1;
goto fsync_and_free;
}
if (exfat_rw_FAT(defrag.exfat, set->next_clus, set->num_phys_clus, true) < 0) {
exfat_err("fail in exfat_read_FAT\n");
ret = 1;
goto fsync_and_free;
}
if (BFS_read_file_tree(&defrag) < 0) {
exfat_err("fail in BFS_read_file_tree\n");
ret = 1;
goto fsync_and_free;
}
if (only_assessment) {
exfat_defrag_assess(&defrag);
goto fsync_and_free;
}
set->prev_clus = calloc(set->num_phys_clus + set->num_clus_chain, sizeof(uint32_t));
if (set->prev_clus == NULL) {
exfat_err("memory run out in defrag.cluster_info_set->prev_clus\n");
ret = 1;
goto fsync_and_free;
}
next_to_prev(set);
interrupt_point = 0;
interrupt_received = 0;
signal(SIGINT, sigint_handler);
signal(SIGTERM, sigint_handler);
if (exfat_defrag(&defrag) < 0) {
exfat_err("fail in exfat_defrag\n");
ret = 1;
goto fsync_and_free;
}
if (BFS_write_file_tree(&defrag) < 0) {
exfat_err("fail in BFS_write_file_tree\n");
ret = 1;
goto fsync_and_free;
}
if (exfat_rw_bitmap(defrag.exfat, false) < 0) {
exfat_err("fail in exfat_write_bitmap\n");
ret = 1;
goto fsync_and_free;
}
if (exfat_rw_FAT(defrag.exfat, set->next_clus, set->num_phys_clus, false) < 0) {
exfat_err("fail in exfat_write_FAT\n");
ret = 1;
goto fsync_and_free;
}
fsync_and_free:
fsync(bd.dev_fd);
if (set->next_clus)
free(set->next_clus);
if (set->prev_clus)
free(set->prev_clus);
if (defrag.tmp_clus)
free(defrag.tmp_clus);
if (defrag.dentry_buffer)
exfat_free_buffer(defrag.exfat, defrag.dentry_buffer);
if (defrag.exfat)
exfat_free_exfat(defrag.exfat);
out:
if (bd.dev_fd >= 0)
close(bd.dev_fd);
if (ret == 0) {
if (only_assessment)
exfat_info("Assessment has completed successfully!\n");
else
exfat_info("Defragmentation has completed successfully!\n");
}
return ret;
}