*/
#include "exfat.h"
+#include <errno.h>
+#include <string.h>
/*
* Cluster to block.
return (off_t) block << ef->sb->block_bits;
}
+/*
+ * Size in bytes to size in clusters (rounded upwards).
+ */
+static uint32_t bytes2clusters(const struct exfat* ef, uint64_t bytes)
+{
+ uint64_t cluster_size = CLUSTER_SIZE(*ef->sb);
+ return (bytes + cluster_size - 1) / cluster_size;
+}
+
cluster_t exfat_next_cluster(const struct exfat* ef,
const struct exfat_node* node, cluster_t cluster)
{
}
return cluster;
}
+
+static cluster_t find_bit_and_set(uint8_t* bitmap, uint64_t size_in_bits)
+{
+ uint32_t byte;
+
+ /* FIXME this is too straightforward algorithm that often produces
+ fragmentation */
+ for (byte = 0; byte < size_in_bits / 8; byte++)
+ if (bitmap[byte] != 0xff)
+ {
+ const uint32_t last_bit = MIN(8, size_in_bits - byte * 8);
+ uint32_t bit;
+
+ for (bit = 0; bit < last_bit; bit++)
+ if (!(bitmap[byte] & (1u << bit)))
+ {
+ bitmap[byte] |= (1u << bit);
+ return byte * 8 + bit;
+ }
+ }
+
+ return EXFAT_CLUSTER_END;
+}
+
+static void flush_cmap(struct exfat* ef)
+{
+ exfat_write_raw(ef->cmap.chunk, (ef->cmap.chunk_size + 7) / 8,
+ exfat_c2o(ef, ef->cmap.start_cluster), ef->fd);
+}
+
+static void set_next_cluster(const struct exfat* ef, int contiguous,
+ cluster_t current, cluster_t next)
+{
+ off_t fat_offset;
+
+ if (contiguous)
+ return;
+ fat_offset = b2o(ef, le32_to_cpu(ef->sb->fat_block_start))
+ + current * sizeof(cluster_t);
+ exfat_write_raw(&next, sizeof(next), fat_offset, ef->fd);
+}
+
+static void erase_cluster(struct exfat* ef, cluster_t cluster)
+{
+ const int block_size = BLOCK_SIZE(*ef->sb);
+ const int blocks_in_cluster = CLUSTER_SIZE(*ef->sb) / block_size;
+ int i;
+
+ for (i = 0; i < blocks_in_cluster; i++)
+ exfat_write_raw(ef->zero_block, block_size,
+ exfat_c2o(ef, cluster) + i * block_size, ef->fd);
+}
+
+static cluster_t allocate_cluster(struct exfat* ef)
+{
+ cluster_t cluster = find_bit_and_set(ef->cmap.chunk, ef->cmap.chunk_size);
+
+ if (cluster == EXFAT_CLUSTER_END)
+ {
+ exfat_error("no free space left");
+ return EXFAT_CLUSTER_END;
+ }
+
+ erase_cluster(ef, cluster);
+ /* FIXME no need to flush immediately */
+ flush_cmap(ef);
+ /* FIXME update percentage of used space */
+ return cluster;
+}
+
+static void free_cluster(struct exfat* ef, cluster_t cluster)
+{
+ if (CLUSTER_INVALID(cluster))
+ exfat_bug("attempting to free invalid cluster");
+
+ ef->cmap.chunk[cluster / 8] &= ~(1u << cluster % 8);
+ /* FIXME no need to flush immediately */
+ flush_cmap(ef);
+}
+
+static void make_noncontiguous(const struct exfat* ef, cluster_t first,
+ cluster_t last)
+{
+ cluster_t c;
+
+ for (c = first; c < last; c++)
+ set_next_cluster(ef, 0, c, c + 1);
+}
+
+static int grow_file(struct exfat* ef, struct exfat_node* node,
+ uint32_t difference)
+{
+ cluster_t previous;
+ cluster_t next;
+
+
+ if (difference == 0)
+ exfat_bug("zero clusters count passed");
+
+ if (node->start_cluster != EXFAT_CLUSTER_FREE)
+ {
+ /* get the last cluster of the file */
+ previous = exfat_advance_cluster(ef, node, node->start_cluster,
+ bytes2clusters(ef, node->size) - 1);
+ if (CLUSTER_INVALID(previous))
+ {
+ exfat_error("invalid cluster in file");
+ return -EIO;
+ }
+ }
+ else
+ {
+ /* file does not have clusters (i.e. is empty), allocate
+ the first one for it */
+ previous = allocate_cluster(ef);
+ if (CLUSTER_INVALID(previous))
+ return -ENOSPC;
+ node->start_cluster = previous;
+ difference--;
+ /* file consists of only one cluster, so it's contiguous */
+ node->flags |= EXFAT_ATTRIB_CONTIGUOUS;
+ }
+
+ while (difference--)
+ {
+ next = allocate_cluster(ef);
+ if (CLUSTER_INVALID(next))
+ return -ENOSPC;
+ if (next != previous - 1 && IS_CONTIGUOUS(*node))
+ {
+ /* it's a pity, but we are not able to keep the file contiguous
+ anymore */
+ make_noncontiguous(ef, node->start_cluster, previous);
+ node->flags &= ~EXFAT_ATTRIB_CONTIGUOUS;
+ }
+ set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, next);
+ previous = next;
+ }
+
+ set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, EXFAT_CLUSTER_END);
+ return 0;
+}
+
+static int shrink_file(struct exfat* ef, struct exfat_node* node,
+ uint32_t difference)
+{
+ uint32_t current = bytes2clusters(ef, node->size);
+ cluster_t previous;
+ cluster_t next;
+
+ if (difference == 0)
+ exfat_bug("zero difference passed");
+ if (node->start_cluster == EXFAT_CLUSTER_FREE)
+ exfat_bug("unable to shrink empty file (%u clusters)", current);
+ if (current < difference)
+ exfat_bug("file underflow (%u < %u)", current, difference);
+
+ /* crop the file */
+ if (current > difference)
+ {
+ cluster_t last = exfat_advance_cluster(ef, node, node->start_cluster,
+ current - difference - 1);
+ if (CLUSTER_INVALID(last))
+ {
+ exfat_error("invalid cluster in file");
+ return -EIO;
+ }
+ previous = exfat_next_cluster(ef, node, last);
+ set_next_cluster(ef, IS_CONTIGUOUS(*node), last, EXFAT_CLUSTER_END);
+ }
+ else
+ {
+ previous = node->start_cluster;
+ node->start_cluster = EXFAT_CLUSTER_FREE;
+ }
+
+ /* free remaining clusters */
+ while (difference--)
+ {
+ if (CLUSTER_INVALID(previous))
+ {
+ exfat_error("invalid cluster in file");
+ return -EIO;
+ }
+ next = exfat_next_cluster(ef, node, previous);
+ set_next_cluster(ef, IS_CONTIGUOUS(*node), previous,
+ EXFAT_CLUSTER_FREE);
+ free_cluster(ef, previous);
+ previous = next;
+ }
+ return 0;
+}
+
+int exfat_truncate(struct exfat* ef, struct exfat_node* node, uint64_t size)
+{
+ uint32_t c1 = bytes2clusters(ef, node->size);
+ uint32_t c2 = bytes2clusters(ef, size);
+ int rc = 0;
+
+ if (c1 < c2)
+ rc = grow_file(ef, node, c2 - c1);
+ else if (c1 > c2)
+ rc = shrink_file(ef, node, c1 - c2);
+
+ if (rc != 0)
+ return rc;
+
+ if (node->size != size)
+ {
+ node->size = size;
+ /* FIXME no need to flush immediately */
+ exfat_flush_node(ef, node);
+ }
+ return 0;
+}
struct exfat_node* prev;
int references;
+ off_t meta1_offset, meta2_offset;
cluster_t start_cluster;
int flags;
uint64_t size;
le16_t* upcase;
size_t upcase_chars;
struct exfat_node* root;
+ struct
+ {
+ cluster_t start_cluster;
+ uint32_t size; /* in bits */
+ uint8_t* chunk;
+ uint32_t chunk_size; /* in bits */
+ }
+ cmap;
+ void* zero_block;
};
/* in-core nodes iterator */
void exfat_debug(const char* format, ...);
void exfat_read_raw(void* buffer, size_t size, off_t offset, int fd);
+void exfat_write_raw(const void* buffer, size_t size, off_t offset, int fd);
ssize_t exfat_read(const struct exfat* ef, const struct exfat_node* node,
void* buffer, size_t size, off_t offset);
+ssize_t exfat_write(struct exfat* ef, struct exfat_node* node,
+ const void* buffer, size_t size, off_t offset);
int exfat_opendir(struct exfat* ef, struct exfat_node* dir,
struct exfat_iterator* it);
const struct exfat_node* node, cluster_t cluster);
cluster_t exfat_advance_cluster(const struct exfat* ef,
const struct exfat_node* node, cluster_t cluster, uint32_t count);
+cluster_t exfat_allocate_cluster(struct exfat* ef, cluster_t previous);
+void exfat_free_cluster(struct exfat* ef, cluster_t cluster,
+ cluster_t previous);
+int exfat_truncate(struct exfat* ef, struct exfat_node* node, uint64_t size);
void exfat_stat(const struct exfat_node* node, struct stat *stbuf);
time_t exfat_exfat2unix(le16_t date, le16_t time);
void exfat_put_node(struct exfat_node* node);
int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir);
void exfat_reset_cache(struct exfat* ef);
+void exfat_flush_node(struct exfat* ef, const struct exfat_node* node);
int exfat_mount(struct exfat* ef, const char* spec);
void exfat_unmount(struct exfat* ef);
#define EXFAT_FIRST_DATA_CLUSTER 2
+#define EXFAT_CLUSTER_FREE 0 /* free cluster */
#define EXFAT_CLUSTER_BAD 0xfffffff7 /* cluster contains bad block */
#define EXFAT_CLUSTER_END 0xffffffff /* final cluster of file or directory */
uint8_t flag; /* fragmented or contiguous */
uint8_t __unknown1;
uint8_t name_length;
- le16_t hash;
+ le16_t name_hash;
uint8_t __unknown[14];
le32_t start_cluster;
le64_t size; /* in bytes */
exfat_bug("failed to read %zu bytes from file at %llu", size, offset);
}
+void exfat_write_raw(const void* buffer, size_t size, off_t offset, int fd)
+{
+ if (pwrite(fd, buffer, size, offset) != size)
+ exfat_bug("failed to write %zu bytes to file at %llu", size, offset);
+}
+
ssize_t exfat_read(const struct exfat* ef, const struct exfat_node* node,
void* buffer, size_t size, off_t offset)
{
remainder = MIN(size, node->size - offset);
while (remainder > 0)
{
+ if (CLUSTER_INVALID(cluster))
+ {
+ exfat_error("got invalid cluster");
+ return -1;
+ }
lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
exfat_read_raw(bufp, lsize, exfat_c2o(ef, cluster) + loffset, ef->fd);
bufp += lsize;
loffset = 0;
remainder -= lsize;
cluster = exfat_next_cluster(ef, node, cluster);
+ }
+ return size - remainder;
+}
+
+ssize_t exfat_write(struct exfat* ef, struct exfat_node* node,
+ const void* buffer, size_t size, off_t offset)
+{
+ cluster_t cluster;
+ const char* bufp = buffer;
+ off_t lsize, loffset, remainder;
+
+ if (offset + size > node->size)
+ exfat_truncate(ef, node, offset + size);
+ if (size == 0)
+ return 0;
+
+ cluster = exfat_advance_cluster(ef, node, node->start_cluster,
+ offset / CLUSTER_SIZE(*ef->sb));
+ if (CLUSTER_INVALID(cluster))
+ {
+ exfat_error("got invalid cluster");
+ return -1;
+ }
+
+ loffset = offset % CLUSTER_SIZE(*ef->sb);
+ remainder = size;
+ while (remainder > 0)
+ {
if (CLUSTER_INVALID(cluster))
{
exfat_error("got invalid cluster");
return -1;
}
+ lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
+ exfat_write_raw(bufp, lsize, exfat_c2o(ef, cluster) + loffset, ef->fd);
+ bufp += lsize;
+ loffset = 0;
+ remainder -= lsize;
+ cluster = exfat_next_cluster(ef, node, cluster);
}
+ /* FIXME update modification time */
+ /* FIXME no need to flush immediately */
+ exfat_flush_node(ef, node);
return size - remainder;
}
exfat_error("memory allocation failed");
return -ENOMEM;
}
+ memset(ef->sb, 0, sizeof(struct exfat_super_block));
ef->fd = open(spec, O_RDWR);
if (ef->fd < 0)
return -EIO;
}
- ef->upcase = NULL;
- ef->upcase_chars = 0;
+ ef->zero_block = malloc(BLOCK_SIZE(*ef->sb));
+ if (ef->zero_block == NULL)
+ {
+ close(ef->fd);
+ free(ef->sb);
+ exfat_error("failed to allocate zero block");
+ return -ENOMEM;
+ }
+ memset(ef->zero_block, 0, BLOCK_SIZE(*ef->sb));
ef->root = malloc(sizeof(struct exfat_node));
if (ef->root == NULL)
{
+ free(ef->zero_block);
close(ef->fd);
free(ef->sb);
exfat_error("failed to allocate root node");
exfat_put_node(ef->root);
exfat_reset_cache(ef);
ef->root = NULL;
+ free(ef->zero_block);
+ ef->zero_block = NULL;
+ free(ef->cmap.chunk);
+ ef->cmap.chunk = NULL;
close(ef->fd);
ef->fd = 0;
free(ef->sb);
}
memset(*node, 0, sizeof(struct exfat_node));
/* new node has zero reference counter */
+ (*node)->meta1_offset = exfat_c2o(ef, it->cluster) +
+ (it->offset - sizeof(struct exfat_entry)) %
+ CLUSTER_SIZE(*ef->sb);
(*node)->flags = le16_to_cpu(file->attrib);
(*node)->mtime = exfat_exfat2unix(file->mdate, file->mtime);
(*node)->atime = exfat_exfat2unix(file->adate, file->atime);
}
file_info = (const struct exfat_file_info*) entry;
actual_checksum = exfat_add_checksum(entry, actual_checksum);
+ (*node)->meta2_offset = exfat_c2o(ef, it->cluster) +
+ (it->offset - sizeof(struct exfat_entry)) %
+ CLUSTER_SIZE(*ef->sb);
(*node)->size = le64_to_cpu(file_info->size);
(*node)->start_cluster = le32_to_cpu(file_info->start_cluster);
if (file_info->flag == EXFAT_FLAG_CONTIGUOUS)
(le32_to_cpu(ef->sb->cluster_count) + 7) / 8);
return -EIO;
}
+ ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
+ ef->cmap.size = le32_to_cpu(ef->sb->cluster_count);
+ /* FIXME bitmap can be rather big, up to 512 MB */
+ ef->cmap.chunk_size = ef->cmap.size;
+ ef->cmap.chunk = malloc(le64_to_cpu(bitmap->size));
+ if (ef->cmap.chunk == NULL)
+ {
+ exfat_error("failed to allocate clusters map chunk "
+ "(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
+ return -ENOMEM;
+ }
+
+ exfat_read_raw(ef->cmap.chunk, le64_to_cpu(bitmap->size),
+ exfat_c2o(ef, ef->cmap.start_cluster), ef->fd);
break;
case EXFAT_ENTRY_LABEL:
{
reset_cache(ef->root);
}
+
+void exfat_flush_node(struct exfat* ef, const struct exfat_node* node)
+{
+ struct exfat_file meta1;
+ struct exfat_file_info meta2;
+ uint16_t checksum;
+ uint8_t i;
+
+ exfat_read_raw(&meta1, sizeof(meta1), node->meta1_offset, ef->fd);
+ if (meta1.type != EXFAT_ENTRY_FILE)
+ exfat_bug("invalid type of meta1: 0x%hhx", meta1.type);
+ meta1.attrib = cpu_to_le16(node->flags);
+ exfat_unix2exfat(node->mtime, &meta1.mdate, &meta1.mtime);
+ exfat_unix2exfat(node->atime, &meta1.adate, &meta1.atime);
+
+ exfat_read_raw(&meta2, sizeof(meta2), node->meta2_offset, ef->fd);
+ if (meta2.type != EXFAT_ENTRY_FILE_INFO)
+ exfat_bug("invalid type of meta2: 0x%hhx", meta2.type);
+ meta2.size = cpu_to_le64(node->size);
+ meta2.start_cluster = cpu_to_le32(node->start_cluster);
+ meta2.flag = (IS_CONTIGUOUS(*node) ?
+ EXFAT_FLAG_CONTIGUOUS : EXFAT_FLAG_FRAGMENTED);
+ /* FIXME name hash */
+
+ checksum = exfat_start_checksum(&meta1);
+ checksum = exfat_add_checksum(&meta2, checksum);
+ for (i = 1; i < meta1.continuations; i++)
+ {
+ struct exfat_file_name name = {EXFAT_ENTRY_FILE_NAME, 0};
+ memcpy(name.name, node->name + (i - 1) * EXFAT_ENAME_MAX,
+ EXFAT_ENAME_MAX * sizeof(le16_t));
+ checksum = exfat_add_checksum(&name, checksum);
+ }
+ meta1.checksum = cpu_to_le16(checksum);
+
+ exfat_write_raw(&meta1, sizeof(meta1), node->meta1_offset, ef->fd);
+ exfat_write_raw(&meta2, sizeof(meta2), node->meta2_offset, ef->fd);
+}