+1.2.7 (2017-06-05)
+
+* Fixed handling of two last clusters: operations with files that occupy these
+clusters could fail.
+* Fixed crash when started with stdin, stdout or stderr closed.
+
1.2.6 (2017-01-28)
* Operations with directories (except initial listing) now make less
#
AC_INIT([Free exFAT implementation],
- [1.2.6],
+ [1.2.7],
[relan@users.noreply.github.com],
[exfat],
[https://github.com/relan/exfat])
{
off_t lsize;
- if (CLUSTER_INVALID(cluster))
+ if (CLUSTER_INVALID(*ef.sb, cluster))
{
exfat_error("'%s' has invalid cluster %#x", path, cluster);
rc = 1;
while (clusters--)
{
- if (CLUSTER_INVALID(c))
+ if (CLUSTER_INVALID(*ef->sb, c))
{
char name[EXFAT_UTF8_NAME_BUFFER_MAX];
exfat_put_node(ef, parent);
return;
}
- while ((node = exfat_readdir(ef, &it)))
+ while ((node = exfat_readdir(&it)))
{
exfat_get_name(node, entry_path + path_length + 1);
exfat_debug("%s: %s, %"PRIu64" bytes, cluster %u", entry_path,
exfat_error("failed to open directory '%s'", path);
return rc;
}
- while ((node = exfat_readdir(&ef, &it)))
+ while ((node = exfat_readdir(&it)))
{
exfat_get_name(node, name);
exfat_debug("[%s] %s: %s, %"PRId64" bytes, cluster 0x%x", __func__,
for (i = node->fptr_index; i < count; i++)
{
node->fptr_cluster = exfat_next_cluster(ef, node, node->fptr_cluster);
- if (CLUSTER_INVALID(node->fptr_cluster))
+ if (CLUSTER_INVALID(*ef->sb, node->fptr_cluster))
break; /* the caller should handle this and print appropriate
error message */
}
static void free_cluster(struct exfat* ef, cluster_t cluster)
{
- if (CLUSTER_INVALID(cluster))
- exfat_bug("freeing invalid cluster 0x%x", cluster);
if (cluster - EXFAT_FIRST_DATA_CLUSTER >= ef->cmap.size)
- exfat_bug("freeing non-existing cluster 0x%x (0x%x)", cluster,
+ exfat_bug("caller must check cluster validity (%#x, %#x)", cluster,
ef->cmap.size);
BMAP_CLR(ef->cmap.chunk, cluster - EXFAT_FIRST_DATA_CLUSTER);
{
/* get the last cluster of the file */
previous = exfat_advance_cluster(ef, node, current - 1);
- if (CLUSTER_INVALID(previous))
+ if (CLUSTER_INVALID(*ef->sb, previous))
{
exfat_error("invalid cluster 0x%x while growing", previous);
return -EIO;
/* file does not have clusters (i.e. is empty), allocate
the first one for it */
previous = allocate_cluster(ef, 0);
- if (CLUSTER_INVALID(previous))
+ if (CLUSTER_INVALID(*ef->sb, previous))
return -ENOSPC;
node->fptr_cluster = node->start_cluster = previous;
allocated = 1;
while (allocated < difference)
{
next = allocate_cluster(ef, previous + 1);
- if (CLUSTER_INVALID(next))
+ if (CLUSTER_INVALID(*ef->sb, next))
{
if (allocated != 0)
shrink_file(ef, node, current + allocated, allocated);
{
cluster_t last = exfat_advance_cluster(ef, node,
current - difference - 1);
- if (CLUSTER_INVALID(last))
+ if (CLUSTER_INVALID(*ef->sb, last))
{
exfat_error("invalid cluster 0x%x while shrinking", last);
return -EIO;
/* free remaining clusters */
while (difference--)
{
- if (CLUSTER_INVALID(previous))
+ if (CLUSTER_INVALID(*ef->sb, previous))
{
exfat_error("invalid cluster 0x%x while freeing after shrink",
previous);
return -EIO;
}
+
next = exfat_next_cluster(ef, node, previous);
if (!set_next_cluster(ef, node->is_contiguous, previous,
EXFAT_CLUSTER_FREE))
cluster_boundary = (begin | (CLUSTER_SIZE(*ef->sb) - 1)) + 1;
cluster = exfat_advance_cluster(ef, node,
begin / CLUSTER_SIZE(*ef->sb));
- if (CLUSTER_INVALID(cluster))
+ if (CLUSTER_INVALID(*ef->sb, cluster))
{
exfat_error("invalid cluster 0x%x while erasing", cluster);
return -EIO;
{
cluster = exfat_next_cluster(ef, node, cluster);
/* the cluster cannot be invalid because we have just allocated it */
- if (CLUSTER_INVALID(cluster))
+ if (CLUSTER_INVALID(*ef->sb, cluster))
exfat_bug("invalid cluster 0x%x after allocation", cluster);
if (!erase_raw(ef, CLUSTER_SIZE(*ef->sb), exfat_c2o(ef, cluster)))
return -EIO;
#define SECTOR_SIZE(sb) (1 << (sb).sector_bits)
#define CLUSTER_SIZE(sb) (SECTOR_SIZE(sb) << (sb).spc_bits)
-#define CLUSTER_INVALID(c) \
- ((c) < EXFAT_FIRST_DATA_CLUSTER || (c) > EXFAT_LAST_DATA_CLUSTER)
+#define CLUSTER_INVALID(sb, c) ((c) < EXFAT_FIRST_DATA_CLUSTER || \
+ (c) - EXFAT_FIRST_DATA_CLUSTER >= le32_to_cpu((sb).cluster_count))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int exfat_opendir(struct exfat* ef, struct exfat_node* dir,
struct exfat_iterator* it);
void exfat_closedir(struct exfat* ef, struct exfat_iterator* it);
-struct exfat_node* exfat_readdir(struct exfat* ef, struct exfat_iterator* it);
+struct exfat_node* exfat_readdir(struct exfat_iterator* it);
int exfat_lookup(struct exfat* ef, struct exfat_node** node,
const char* path);
int exfat_split(struct exfat* ef, struct exfat_node** parent,
off_t size; /* in bytes */
};
+static bool is_open(int fd)
+{
+ return fcntl(fd, F_GETFD) != -1;
+}
+
static int open_ro(const char* spec)
{
return open(spec, O_RDONLY);
struct exfat_dev* dev;
struct stat stbuf;
+ /* The system allocates file descriptors sequentially. If we have been
+ started with stdin (0), stdout (1) or stderr (2) closed, the system
+ will give us descriptor 0, 1 or 2 later when we open block device,
+ FUSE communication pipe, etc. As a result, functions using stdin,
+ stdout or stderr will actualy work with a different thing and can
+ corrupt it. Protect descriptors 0, 1 and 2 from such misuse. */
+ while (!is_open(STDIN_FILENO)
+ || !is_open(STDOUT_FILENO)
+ || !is_open(STDERR_FILENO))
+ {
+ /* we don't need those descriptors, let them leak */
+ if (open("/dev/null", O_RDWR) == -1)
+ {
+ exfat_error("failed to open /dev/null");
+ return NULL;
+ }
+ }
+
dev = malloc(sizeof(struct exfat_dev));
if (dev == NULL)
{
return 0;
cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
- if (CLUSTER_INVALID(cluster))
+ if (CLUSTER_INVALID(*ef->sb, cluster))
{
exfat_error("invalid cluster 0x%x while reading", cluster);
return -EIO;
remainder = MIN(size, node->size - offset);
while (remainder > 0)
{
- if (CLUSTER_INVALID(cluster))
+ if (CLUSTER_INVALID(*ef->sb, cluster))
{
exfat_error("invalid cluster 0x%x while reading", cluster);
return -EIO;
return 0;
cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
- if (CLUSTER_INVALID(cluster))
+ if (CLUSTER_INVALID(*ef->sb, cluster))
{
exfat_error("invalid cluster 0x%x while writing", cluster);
return -EIO;
remainder = size;
while (remainder > 0)
{
- if (CLUSTER_INVALID(cluster))
+ if (CLUSTER_INVALID(*ef->sb, cluster))
{
exfat_error("invalid cluster 0x%x while writing", cluster);
return -EIO;
it->current = NULL;
}
-struct exfat_node* exfat_readdir(struct exfat* ef, struct exfat_iterator* it)
+struct exfat_node* exfat_readdir(struct exfat_iterator* it)
{
if (it->current == NULL)
it->current = it->parent->child;
rc = exfat_opendir(ef, parent, &it);
if (rc != 0)
return rc;
- while ((*node = exfat_readdir(ef, &it)))
+ while ((*node = exfat_readdir(&it)))
{
if (compare_name(ef, buffer, (*node)->name) == 0)
{
clusters);
return 0;
}
- if (CLUSTER_INVALID(rootdir_cluster))
+ if (CLUSTER_INVALID(*ef->sb, rootdir_cluster))
{
exfat_error("bad cluster %#x while reading root directory",
rootdir_cluster);
return commit_super_block(ef);
}
+static void exfat_free(struct exfat* ef)
+{
+ exfat_close(ef->dev); /* first of all, close the descriptor */
+ ef->dev = NULL; /* struct exfat_dev is freed by exfat_close() */
+ free(ef->root);
+ ef->root = NULL;
+ free(ef->zero_cluster);
+ ef->zero_cluster = NULL;
+ free(ef->cmap.chunk);
+ ef->cmap.chunk = NULL;
+ free(ef->upcase);
+ ef->upcase = NULL;
+ free(ef->sb);
+ ef->sb = NULL;
+}
+
int exfat_mount(struct exfat* ef, const char* spec, const char* options)
{
int rc;
ef->sb = malloc(sizeof(struct exfat_super_block));
if (ef->sb == NULL)
{
- exfat_close(ef->dev);
exfat_error("failed to allocate memory for the super block");
+ exfat_free(ef);
return -ENOMEM;
}
memset(ef->sb, 0, sizeof(struct exfat_super_block));
if (exfat_pread(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0) < 0)
{
- exfat_close(ef->dev);
- free(ef->sb);
exfat_error("failed to read boot sector");
+ exfat_free(ef);
return -EIO;
}
if (memcmp(ef->sb->oem_name, "EXFAT ", 8) != 0)
{
- exfat_close(ef->dev);
- free(ef->sb);
exfat_error("exFAT file system is not found");
+ exfat_free(ef);
return -EIO;
}
/* sector cannot be smaller than 512 bytes */
if (ef->sb->sector_bits < 9)
{
- exfat_close(ef->dev);
exfat_error("too small sector size: 2^%hhd", ef->sb->sector_bits);
- free(ef->sb);
+ exfat_free(ef);
return -EIO;
}
/* officially exFAT supports cluster size up to 32 MB */
if ((int) ef->sb->sector_bits + (int) ef->sb->spc_bits > 25)
{
- exfat_close(ef->dev);
exfat_error("too big cluster size: 2^(%hhd+%hhd)",
ef->sb->sector_bits, ef->sb->spc_bits);
- free(ef->sb);
+ exfat_free(ef);
return -EIO;
}
ef->zero_cluster = malloc(CLUSTER_SIZE(*ef->sb));
if (ef->zero_cluster == NULL)
{
- exfat_close(ef->dev);
- free(ef->sb);
exfat_error("failed to allocate zero sector");
+ exfat_free(ef);
return -ENOMEM;
}
/* use zero_cluster as a temporary buffer for VBR checksum verification */
if (!verify_vbr_checksum(ef->dev, ef->zero_cluster, SECTOR_SIZE(*ef->sb)))
{
- free(ef->zero_cluster);
- exfat_close(ef->dev);
- free(ef->sb);
+ exfat_free(ef);
return -EIO;
}
memset(ef->zero_cluster, 0, CLUSTER_SIZE(*ef->sb));
if (ef->sb->version.major != 1 || ef->sb->version.minor != 0)
{
- free(ef->zero_cluster);
- exfat_close(ef->dev);
exfat_error("unsupported exFAT version: %hhu.%hhu",
ef->sb->version.major, ef->sb->version.minor);
- free(ef->sb);
+ exfat_free(ef);
return -EIO;
}
if (ef->sb->fat_count != 1)
{
- free(ef->zero_cluster);
- exfat_close(ef->dev);
exfat_error("unsupported FAT count: %hhu", ef->sb->fat_count);
- free(ef->sb);
+ exfat_free(ef);
return -EIO;
}
if (le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb) >
{
/* this can cause I/O errors later but we don't fail mounting to let
user rescue data */
- exfat_warn("file system is larger than underlying device: "
- "%"PRIu64" > %"PRIu64,
- le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb),
+ exfat_warn("file system in sectors is larger than device: "
+ "%"PRIu64" * %d > %"PRIu64,
+ le64_to_cpu(ef->sb->sector_count), SECTOR_SIZE(*ef->sb),
exfat_get_size(ef->dev));
}
+ if ((off_t) le32_to_cpu(ef->sb->cluster_count) * CLUSTER_SIZE(*ef->sb) >
+ exfat_get_size(ef->dev))
+ {
+ exfat_error("file system in clusters is larger than device: "
+ "%u * %d > %"PRIu64,
+ le32_to_cpu(ef->sb->cluster_count), CLUSTER_SIZE(*ef->sb),
+ exfat_get_size(ef->dev));
+ exfat_free(ef);
+ return -EIO;
+ }
ef->root = malloc(sizeof(struct exfat_node));
if (ef->root == NULL)
{
- free(ef->zero_cluster);
- exfat_close(ef->dev);
- free(ef->sb);
exfat_error("failed to allocate root node");
+ exfat_free(ef);
return -ENOMEM;
}
memset(ef->root, 0, sizeof(struct exfat_node));
ef->root->size = rootdir_size(ef);
if (ef->root->size == 0)
{
- free(ef->root);
- free(ef->zero_cluster);
- exfat_close(ef->dev);
- free(ef->sb);
+ exfat_free(ef);
return -EIO;
}
/* exFAT does not have time attributes for the root directory */
error:
exfat_put_node(ef, ef->root);
exfat_reset_cache(ef);
- free(ef->root);
- free(ef->zero_cluster);
- exfat_close(ef->dev);
- free(ef->sb);
+ exfat_free(ef);
return -EIO;
}
exfat_flush(ef); /* ignore return code */
exfat_put_node(ef, ef->root);
exfat_reset_cache(ef);
- free(ef->root);
- ef->root = NULL;
finalize_super_block(ef);
- exfat_close(ef->dev); /* close descriptor immediately after fsync */
- ef->dev = NULL;
- free(ef->zero_cluster);
- ef->zero_cluster = NULL;
- free(ef->cmap.chunk);
- ef->cmap.chunk = NULL;
- free(ef->sb);
- ef->sb = NULL;
- free(ef->upcase);
- ef->upcase = NULL;
+ exfat_free(ef); /* will close the descriptor */
}
return true;
}
-static bool check_node(const struct exfat_node* node, le16_t actual_checksum,
- int cluster_size, const struct exfat_entry_meta1* meta1,
+static bool check_node(const struct exfat* ef, struct exfat_node* node,
+ le16_t actual_checksum, const struct exfat_entry_meta1* meta1,
const struct exfat_entry_meta2* meta2)
{
+ int cluster_size = CLUSTER_SIZE(*ef->sb);
+ uint64_t clusters_heap_size =
+ (uint64_t) le32_to_cpu(ef->sb->cluster_count) * cluster_size;
char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
bool ret = true;
node->start_cluster);
ret = false;
}
- if (node->size > 0 && CLUSTER_INVALID(node->start_cluster))
+ if (node->size > 0 && CLUSTER_INVALID(*ef->sb, node->start_cluster))
{
exfat_get_name(node, buffer);
exfat_error("'%s' points to invalid cluster %#x", buffer,
ret = false;
}
+ /* File or directory cannot be larger than clusters heap. */
+ if (node->size > clusters_heap_size)
+ {
+ exfat_get_name(node, buffer);
+ exfat_error("'%s' is larger than clusters heap: %"PRIu64" > %"PRIu64,
+ buffer, node->size, clusters_heap_size);
+ ret = false;
+ }
+
/* Empty file or directory must be marked as non-contiguous. */
if (node->size == 0 && node->is_contiguous)
{
return ret;
}
-static int parse_file_entries(struct exfat* ef, struct exfat_node* parent,
- struct exfat_node* node, const struct exfat_entry* entries, int n)
+static int parse_file_entries(struct exfat* ef, struct exfat_node* node,
+ const struct exfat_entry* entries, int n)
{
const struct exfat_entry_meta1* meta1;
const struct exfat_entry_meta2* meta2;
init_node_meta2(node, meta2);
init_node_name(node, entries + 2, mandatory_entries - 2);
- if (!check_node(node, exfat_calc_checksum(entries, n),
- CLUSTER_SIZE(*ef->sb), meta1, meta2))
+ if (!check_node(ef, node, exfat_calc_checksum(entries, n), meta1, meta2))
return -EIO;
return 0;
return -ENOMEM;
(*node)->entry_offset = *offset;
- rc = parse_file_entries(ef, parent, *node, entries, n);
+ rc = parse_file_entries(ef, *node, entries, n);
if (rc != 0)
{
free(*node);
if (ef->upcase != NULL)
break;
upcase = (const struct exfat_entry_upcase*) &entry;
- if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
+ if (CLUSTER_INVALID(*ef->sb, le32_to_cpu(upcase->start_cluster)))
{
exfat_error("invalid cluster 0x%x in upcase table",
le32_to_cpu(upcase->start_cluster));
case EXFAT_ENTRY_BITMAP:
bitmap = (const struct exfat_entry_bitmap*) &entry;
ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
- if (CLUSTER_INVALID(ef->cmap.start_cluster))
+ if (CLUSTER_INVALID(*ef->sb, ef->cmap.start_cluster))
{
exfat_error("invalid cluster 0x%x in clusters bitmap",
ef->cmap.start_cluster);
return -EIO;
}
- ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
- EXFAT_FIRST_DATA_CLUSTER;
+ ef->cmap.size = le32_to_cpu(ef->sb->cluster_count);
if (le64_to_cpu(bitmap->size) < DIV_ROUND_UP(ef->cmap.size, 8))
{
exfat_error("invalid clusters bitmap size: %"PRIu64
}
static int commit_entry(struct exfat* ef, struct exfat_node* dir,
- const le16_t* name, cluster_t cluster, off_t offset, uint16_t attrib)
+ const le16_t* name, off_t offset, uint16_t attrib)
{
struct exfat_node* node;
const size_t name_length = utf16_length(name);
{
struct exfat_node* dir;
struct exfat_node* existing;
- cluster_t cluster = EXFAT_CLUSTER_BAD;
off_t offset = -1;
le16_t name[EXFAT_NAME_MAX + 1];
int rc;
exfat_put_node(ef, dir);
return rc;
}
- rc = commit_entry(ef, dir, name, cluster, offset, attrib);
+ rc = commit_entry(ef, dir, name, offset, attrib);
if (rc != 0)
{
exfat_put_node(ef, dir);
}
static int rename_entry(struct exfat* ef, struct exfat_node* dir,
- struct exfat_node* node, const le16_t* name, cluster_t new_cluster,
- off_t new_offset)
+ struct exfat_node* node, const le16_t* name, off_t new_offset)
{
const size_t name_length = utf16_length(name);
const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
struct exfat_node* node;
struct exfat_node* existing;
struct exfat_node* dir;
- cluster_t cluster = EXFAT_CLUSTER_BAD;
off_t offset = -1;
le16_t name[EXFAT_NAME_MAX + 1];
int rc;
exfat_put_node(ef, node);
return rc;
}
- rc = rename_entry(ef, dir, node, name, cluster, offset);
+ rc = rename_entry(ef, dir, node, name, offset);
if (rc != 0)
{
exfat_put_node(ef, dir);