void exfat_put_node(struct exfat* ef, struct exfat_node* node)
{
	if (--node->references < 0)
	{
		char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
		exfat_get_name(node, buffer, sizeof(buffer) - 1);
		exfat_bug("reference counter of `%s' is below zero", buffer);
	}

	if (node->references == 0)
	{
		/* FIXME handle I/O error */
		if (exfat_flush_node(ef, node) != 0)
			exfat_bug("node flush failed");
		if (node->flags & EXFAT_ATTRIB_UNLINKED)
		{
			/* free all clusters and node structure itself */
			exfat_truncate(ef, node, 0, true);
			free(node);
		}
		/* FIXME handle I/O error */
		if (exfat_flush(ef) != 0)
			exfat_bug("flush failed");
	}
}
Exemple #2
0
static int grow_file(struct exfat* ef, struct exfat_node* node,
		uint32_t current, uint32_t difference)
{
	cluster_t previous;
	cluster_t next;
	uint32_t allocated = 0;

	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, current - 1);
		if (CLUSTER_INVALID(previous))
		{
			exfat_error("invalid cluster 0x%x while growing", previous);
			return -EIO;
		}
	}
	else
	{
		if (node->fptr_index != 0)
			exfat_bug("non-zero pointer index (%u)", node->fptr_index);
		/* file does not have clusters (i.e. is empty), allocate
		   the first one for it */
		previous = allocate_cluster(ef, 0);
		if (CLUSTER_INVALID(previous))
			return -ENOSPC;
		node->fptr_cluster = node->start_cluster = previous;
		allocated = 1;
		/* file consists of only one cluster, so it's contiguous */
		node->flags |= EXFAT_ATTRIB_CONTIGUOUS;
	}

	while (allocated < difference)
	{
		next = allocate_cluster(ef, previous + 1);
		if (CLUSTER_INVALID(next))
		{
			if (allocated != 0)
				shrink_file(ef, node, current + allocated, allocated);
			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;
			node->flags |= EXFAT_ATTRIB_DIRTY;
		}
		set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, next);
		previous = next;
		allocated++;
	}

	set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, EXFAT_CLUSTER_END);
	return 0;
}
Exemple #3
0
static void dirck(struct exfat* ef, const char* path)
{
	struct exfat_node* parent;
	struct exfat_node* node;
	struct exfat_iterator it;
	int rc;
	size_t path_length;
	char* entry_path;

	if (exfat_lookup(ef, &parent, path) != 0)
		exfat_bug("directory '%s' is not found", path);
	if (!(parent->flags & EXFAT_ATTRIB_DIR))
		exfat_bug("'%s' is not a directory (0x%x)", path, parent->flags);
	if (nodeck(ef, parent) != 0)
	{
		exfat_put_node(ef, parent);
		return;
	}

	path_length = strlen(path);
	entry_path = malloc(path_length + 1 + UTF8_BYTES(EXFAT_NAME_MAX) + 1);
	if (entry_path == NULL)
	{
		exfat_put_node(ef, parent);
		exfat_error("out of memory");
		return;
	}
	strcpy(entry_path, path);
	strcat(entry_path, "/");

	rc = exfat_opendir(ef, parent, &it);
	if (rc != 0)
	{
		free(entry_path);
		exfat_put_node(ef, parent);
		return;
	}
	while ((node = exfat_readdir(ef, &it)))
	{
		exfat_get_name(node, entry_path + path_length + 1,
				UTF8_BYTES(EXFAT_NAME_MAX));
		exfat_debug("%s: %s, %"PRIu64" bytes, cluster %u", entry_path,
				IS_CONTIGUOUS(*node) ? "contiguous" : "fragmented",
				node->size, node->start_cluster);
		if (node->flags & EXFAT_ATTRIB_DIR)
		{
			directories_count++;
			dirck(ef, entry_path);
		}
		else
		{
			files_count++;
			nodeck(ef, node);
		}
		exfat_put_node(ef, node);
	}
	exfat_closedir(ef, &it);
	exfat_put_node(ef, parent);
	free(entry_path);
}
static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
                            off64_t deleted_offset)
{
    const struct exfat_node* node;
    const struct exfat_node* last_node;
    uint64_t entries = 1; /* a directory always has at leat 1 entry (EOD) */
    uint64_t new_size;
    struct exfat_entry eod;
    off64_t eod_offset;
    int rc;

    if (!(dir->flags & EXFAT_ATTRIB_DIR))
        exfat_bug("attempted to shrink a file");
    if (!(dir->flags & EXFAT_ATTRIB_CACHED))
        exfat_bug("attempted to shrink uncached directory");

    for (last_node = node = dir->child; node; node = node->next)
    {
        if (deleted_offset < node->entry_offset)
        {
            /* there are other entries after the removed one, no way to shrink
               this directory */
            return 0;
        }
        if (last_node->entry_offset < node->entry_offset)
            last_node = node;
    }

    if (last_node)
    {
        /* offset of the last entry */
        entries += last_node->entry_offset / sizeof(struct exfat_entry);
        /* two subentries with meta info */
        entries += 2;
        /* subentries with file name */
        entries += DIV_ROUND_UP(utf16_length(last_node->name),
                                EXFAT_ENAME_MAX);
    }

    new_size = DIV_ROUND_UP(entries * sizeof(struct exfat_entry),
                            CLUSTER_SIZE(*ef->sb)) * CLUSTER_SIZE(*ef->sb);
    if (new_size == dir->size)
        return 0;
    rc = exfat_truncate(ef, dir, new_size);
    if (rc != 0)
        return rc;

    /* put EOD entry at the end of the last cluster */
    memset(&eod, 0, sizeof(eod));
    eod_offset = new_size - sizeof(struct exfat_entry);
    if (last_node)
        exfat_write_raw(&eod, sizeof(eod),
                        co2o(ef, last_node->entry_cluster, eod_offset), ef->fd);
    else
        exfat_write_raw(&eod, sizeof(eod),
                        co2o(ef, dir->start_cluster, eod_offset), ef->fd);
    return 0;
}
Exemple #5
0
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,
				ef->cmap.size);

	BMAP_CLR(ef->cmap.chunk, cluster - EXFAT_FIRST_DATA_CLUSTER);
	ef->cmap.dirty = true;
}
Exemple #6
0
static int shrink_file(struct exfat* ef, struct exfat_node* node,
		uint32_t current, uint32_t difference)
{
	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,
				current - difference - 1);
		if (CLUSTER_INVALID(last))
		{
			exfat_error("invalid cluster 0x%x while shrinking", last);
			return -EIO;
		}
		previous = exfat_next_cluster(ef, node, last);
		if (!set_next_cluster(ef, IS_CONTIGUOUS(*node), last,
				EXFAT_CLUSTER_END))
			return -EIO;
	}
	else
	{
		previous = node->start_cluster;
		node->start_cluster = EXFAT_CLUSTER_FREE;
	}
	node->fptr_index = 0;
	node->fptr_cluster = node->start_cluster;

	/* free remaining clusters */
	while (difference--)
	{
		if (CLUSTER_INVALID(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, IS_CONTIGUOUS(*node), previous,
				EXFAT_CLUSTER_FREE))
			return -EIO;
		free_cluster(ef, previous);
		previous = next;
	}
	return 0;
}
static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
		off64_t deleted_offset)
{
	const struct exfat_node* node;
	const struct exfat_node* last_node;
	uint64_t entries = 0;
	uint64_t new_size;
	int rc;

	if (!(dir->flags & EXFAT_ATTRIB_DIR))
		exfat_bug("attempted to shrink a file");
	if (!(dir->flags & EXFAT_ATTRIB_CACHED))
		exfat_bug("attempted to shrink uncached directory");

	for (last_node = node = dir->child; node; node = node->next)
	{
		if (deleted_offset < node->entry_offset)
		{
			/* there are other entries after the removed one, no way to shrink
			   this directory */
			return 0;
		}
		if (last_node->entry_offset < node->entry_offset)
			last_node = node;
	}

	if (last_node)
	{
		/* offset of the last entry */
		entries += last_node->entry_offset / sizeof(struct exfat_entry);
		/* two subentries with meta info */
		entries += 2;
		/* subentries with file name */
		entries += DIV_ROUND_UP(utf16_length(last_node->name),
				EXFAT_ENAME_MAX);
	}

	new_size = DIV_ROUND_UP(entries * sizeof(struct exfat_entry),
				 CLUSTER_SIZE(*ef->sb)) * CLUSTER_SIZE(*ef->sb);
	if (new_size == 0) /* directory always has at least 1 cluster */
		new_size = CLUSTER_SIZE(*ef->sb);
	if (new_size == dir->size)
		return 0;
	rc = exfat_truncate(ef, dir, new_size, true);
	if (rc != 0)
		return rc;
	return 0;
}
Exemple #8
0
static void reset_cache(struct exfat* ef, struct exfat_node* node)
{
	char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];

	while (node->child)
	{
		struct exfat_node* p = node->child;
		reset_cache(ef, p);
		tree_detach(p);
		free(p);
	}
	node->flags &= ~EXFAT_ATTRIB_CACHED;
	if (node->references != 0)
	{
		exfat_get_name(node, buffer, sizeof(buffer) - 1);
		exfat_warn("non-zero reference counter (%d) for '%s'",
				node->references, buffer);
	}
	if (node != ef->root && (node->flags & EXFAT_ATTRIB_DIRTY))
	{
		exfat_get_name(node, buffer, sizeof(buffer) - 1);
		exfat_bug("node '%s' is dirty", buffer);
	}
	while (node->references)
		exfat_put_node(ef, node);
}
Exemple #9
0
static int erase_range(struct exfat* ef, struct exfat_node* node,
		uint64_t begin, uint64_t end)
{
	uint64_t cluster_boundary;
	cluster_t cluster;

	if (begin >= end)
		return 0;

	cluster_boundary = (begin | (CLUSTER_SIZE(*ef->sb) - 1)) + 1;
	cluster = exfat_advance_cluster(ef, node,
			begin / CLUSTER_SIZE(*ef->sb));
	if (CLUSTER_INVALID(cluster))
	{
		exfat_error("invalid cluster 0x%x while erasing", cluster);
		return -EIO;
	}
	/* erase from the beginning to the closest cluster boundary */
	erase_raw(ef, MIN(cluster_boundary, end) - begin,
			exfat_c2o(ef, cluster) + begin % CLUSTER_SIZE(*ef->sb));
	/* erase whole clusters */
	while (cluster_boundary < end)
	{
		cluster = exfat_next_cluster(ef, node, cluster);
		/* the cluster cannot be invalid because we have just allocated it */
		if (CLUSTER_INVALID(cluster))
			exfat_bug("invalid cluster 0x%x after allocation", cluster);
		erase_raw(ef, CLUSTER_SIZE(*ef->sb), exfat_c2o(ef, cluster));
		cluster_boundary += CLUSTER_SIZE(*ef->sb);
	}
	return 0;
}
Exemple #10
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 (node->references == 0 && node->parent)
		exfat_bug("no references, node changes can be lost");

	if (node->size == size)
		return 0;

	if (c1 < c2)
		rc = grow_file(ef, node, c1, c2 - c1);
	else if (c1 > c2)
		rc = shrink_file(ef, node, c1, c1 - c2);

	if (rc != 0)
		return rc;

	rc = erase_range(ef, node, node->size, size);
	if (rc != 0)
		return rc;

	exfat_update_mtime(node);
	node->size = size;
	node->flags |= EXFAT_ATTRIB_DIRTY;
	return 0;
}
Exemple #11
0
/*
 * Cluster to sector.
 */
static off_t c2s(const struct exfat* ef, cluster_t cluster)
{
	if (cluster < EXFAT_FIRST_DATA_CLUSTER)
		exfat_bug("invalid cluster number %u", cluster);
	return le32_to_cpu(ef->sb->cluster_sector_start) +
		((off_t) (cluster - EXFAT_FIRST_DATA_CLUSTER) << ef->sb->spc_bits);
}
void exfat_flush_node(struct exfat* ef, struct exfat_node* node)
{
    cluster_t cluster;
    off64_t offset;
    off64_t meta1_offset, meta2_offset;
    struct exfat_entry_meta1 meta1;
    struct exfat_entry_meta2 meta2;

    if (ef->ro)
        exfat_bug("unable to flush node to read-only FS");

    if (node->parent == NULL)
        return; /* do not flush unlinked node */

    cluster = node->entry_cluster;
    offset = node->entry_offset;
    meta1_offset = co2o(ef, cluster, offset);
    next_entry(ef, node->parent, &cluster, &offset);
    meta2_offset = co2o(ef, cluster, offset);

    exfat_read_raw(&meta1, sizeof(meta1), 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, &meta1.mtime_cs);
    exfat_unix2exfat(node->atime, &meta1.adate, &meta1.atime, NULL);

    exfat_read_raw(&meta2, sizeof(meta2), meta2_offset, ef->fd);
    if (meta2.type != EXFAT_ENTRY_FILE_INFO)
        exfat_bug("invalid type of meta2: 0x%hhx", meta2.type);
    meta2.size = meta2.real_size = cpu_to_le64(node->size);
    meta2.start_cluster = cpu_to_le32(node->start_cluster);
    meta2.flags = EXFAT_FLAG_ALWAYS1;
    /* empty files must not be marked as contiguous */
    if (node->size != 0 && IS_CONTIGUOUS(*node))
        meta2.flags |= EXFAT_FLAG_CONTIGUOUS;
    /* name hash remains unchanged, no need to recalculate it */

    meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);

    exfat_write_raw(&meta1, sizeof(meta1), meta1_offset, ef->fd);
    exfat_write_raw(&meta2, sizeof(meta2), meta2_offset, ef->fd);

    node->flags &= ~EXFAT_ATTRIB_DIRTY;
}
Exemple #13
0
cluster_t exfat_next_cluster(const struct exfat* ef,
		const struct exfat_node* node, cluster_t cluster)
{
	le32_t next;
	loff_t fat_offset;

	if (cluster < EXFAT_FIRST_DATA_CLUSTER)
		exfat_bug("bad cluster 0x%x", cluster);

	if (IS_CONTIGUOUS(*node))
		return cluster + 1;
	fat_offset = s2o(ef, le32_to_cpu(ef->sb->fat_sector_start))
		+ cluster * sizeof(cluster_t);
	/* FIXME handle I/O error */
	if (exfat_pread(ef->dev, &next, sizeof(next), fat_offset) < 0)
		exfat_bug("failed to read the next cluster after %#x", cluster);
	return le32_to_cpu(next);
}
Exemple #14
0
void exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
		off_t offset)
{
#ifdef USE_UBLIO
	if (ublio_pwrite(dev->ufh, buffer, size, offset) != size)
#else
	if (pwrite(dev->fd, buffer, size, offset) != size)
#endif
		exfat_bug("failed to write %zu bytes to file at %"PRIu64, size,
				(uint64_t) offset);
}
Exemple #15
0
void exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
		off_t offset)
{
#ifdef USE_UBLIO
	if (ublio_pread(dev->ufh, buffer, size, offset) != size)
#else
	if (pread(dev->fd, buffer, size, offset) != size)
#endif
		exfat_bug("failed to read %zu bytes from file at %"PRIu64, size,
				(uint64_t) offset);
}
off64_t get_position(const struct fs_object* object)
{
	const struct fs_object** pp;
	off64_t position = 0;

	for (pp = objects; *pp; pp++)
	{
		position = ROUND_UP(position, (*pp)->get_alignment());
		if (*pp == object)
			return position;
		position += (*pp)->get_size();
	}
	exfat_bug("unknown object");
}
Exemple #17
0
cluster_t exfat_next_cluster(const struct exfat* ef,
		const struct exfat_node* node, cluster_t cluster)
{
	le32_t next;
	off_t fat_offset;

	if (cluster < EXFAT_FIRST_DATA_CLUSTER)
		exfat_bug("bad cluster 0x%x", cluster);

	if (IS_CONTIGUOUS(*node))
		return cluster + 1;
	fat_offset = s2o(ef, le32_to_cpu(ef->sb->fat_sector_start))
		+ cluster * sizeof(cluster_t);
	exfat_pread(ef->dev, &next, sizeof(next), fat_offset);
	return le32_to_cpu(next);
}
Exemple #18
0
/**
 * This function must be called on rmdir and unlink (after the last
 * exfat_put_node()) to free clusters.
 */
int exfat_cleanup_node(struct exfat* ef, struct exfat_node* node)
{
	int rc = 0;

	if (node->references != 0)
		exfat_bug("unable to cleanup a node with %d references",
				node->references);

	if (node->flags & EXFAT_ATTRIB_UNLINKED)
	{
		/* free all clusters and node structure itself */
		rc = exfat_truncate(ef, node, 0, true);
		/* free the node even in case of error or its memory will be lost */
		free(node);
	}
	return rc;
}
static int opendir(struct exfat* ef, const struct exfat_node* dir,
                   struct iterator* it)
{
    if (!(dir->flags & EXFAT_ATTRIB_DIR))
        exfat_bug("not a directory");
    it->cluster = dir->start_cluster;
    it->offset = 0;
    it->contiguous = IS_CONTIGUOUS(*dir);
    it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
    if (it->chunk == NULL)
    {
        exfat_error("out of memory");
        return -ENOMEM;
    }
    exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
                   exfat_c2o(ef, it->cluster), ef->fd);
    return 0;
}
Exemple #20
0
void exfat_put_node(struct exfat* ef, struct exfat_node* node)
{
	char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];

	--node->references;
	if (node->references < 0)
	{
		exfat_get_name(node, buffer, sizeof(buffer) - 1);
		exfat_bug("reference counter of '%s' is below zero", buffer);
	}
	else if (node->references == 0 && node != ef->root)
	{
		if (node->flags & EXFAT_ATTRIB_DIRTY)
		{
			exfat_get_name(node, buffer, sizeof(buffer) - 1);
			exfat_warn("dirty node '%s' with zero references", buffer);
		}
	}
}
void exfat_put_node(struct exfat* ef, struct exfat_node* node)
{
    if (--node->references < 0)
    {
        char buffer[EXFAT_NAME_MAX + 1];
        exfat_get_name(node, buffer, EXFAT_NAME_MAX);
        exfat_bug("reference counter of `%s' is below zero", buffer);
    }

    if (node->references == 0)
    {
        if (node->flags & EXFAT_ATTRIB_DIRTY)
            exfat_flush_node(ef, node);
        if (node->flags & EXFAT_ATTRIB_UNLINKED)
        {
            /* free all clusters and node structure itself */
            exfat_truncate(ef, node, 0);
            free(node);
        }
        if (ef->cmap.dirty)
            exfat_flush_cmap(ef);
    }
}
Exemple #22
0
void exfat_read_raw(void* buffer, size_t size, off_t offset, int fd)
{
	if (pread(fd, buffer, size, offset) != size)
		exfat_bug("failed to read %zu bytes from file at %"PRIu64, size,
				(uint64_t) offset);
}
Exemple #23
0
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 %"PRIu64, size,
				(uint64_t) offset);
}