Example #1
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;
}
static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
		struct iterator* it)
{
	/* move iterator to the next entry in the directory */
	it->offset += sizeof(struct exfat_entry);
	/* fetch the next cluster if needed */
	if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
	{
		/* reached the end of directory; the caller should check this
		   condition too */
		if (it->offset >= parent->size)
			return 0;
		it->cluster = exfat_next_cluster(ef, parent, it->cluster);
		if (CLUSTER_INVALID(it->cluster))
		{
			exfat_error("invalid cluster 0x%x while reading directory",
					it->cluster);
			return 1;
		}
		if (exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
				exfat_c2o(ef, it->cluster)) < 0)
		{
			exfat_error("failed to read the next directory cluster %#x",
					it->cluster);
			return 1;
		}
	}
	return 0;
}
Example #3
0
int exfat_flush(struct exfat* ef)
{
	if (ef->cmap.dirty)
	{
		if (exfat_pwrite(ef->dev, ef->cmap.chunk,
				BMAP_SIZE(ef->cmap.chunk_size),
				exfat_c2o(ef, ef->cmap.start_cluster)) < 0)
		{
			exfat_error("failed to write clusters bitmap");
			return -EIO;
		}
		ef->cmap.dirty = false;
	}
	return 0;
}
Example #4
0
ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
		const void* buffer, size_t size, off64_t offset)
{
	cluster_t cluster;
	const char* bufp = buffer;
	off64_t lsize, loffset, remainder;

	if (offset > node->size)
		if (exfat_truncate(ef, node, offset, true) != 0)
			return -1;
	if (offset + size > node->size)
		if (exfat_truncate(ef, node, offset + size, false) != 0)
			return -1;
	if (size == 0)
		return 0;

	cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
	if (CLUSTER_INVALID(cluster))
	{
		exfat_error("invalid cluster 0x%x while writing", cluster);
		return -1;
	}

	loffset = offset % CLUSTER_SIZE(*ef->sb);
	remainder = size;
	while (remainder > 0)
	{
		if (CLUSTER_INVALID(cluster))
		{
			exfat_error("invalid cluster 0x%x while writing", cluster);
			return -1;
		}
		lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
		if (exfat_pwrite(ef->dev, bufp, lsize,
				exfat_c2o(ef, cluster) + loffset) < 0)
		{
			exfat_error("failed to write cluster %#x", cluster);
			ef->was_dirty = true; // Leaves the volume "mounted", to force chkdsk on it.
			return -1;
		}
		bufp += lsize;
		loffset = 0;
		remainder -= lsize;
		cluster = exfat_next_cluster(ef, node, cluster);
	}
	exfat_update_mtime(node);
	return size - remainder;
}
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;
}
Example #6
0
ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
		void* buffer, size_t size, off64_t offset)
{
	cluster_t cluster;
	char* bufp = buffer;
	off64_t lsize, loffset, remainder;

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

	cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
	if (CLUSTER_INVALID(cluster))
	{
		exfat_error("invalid cluster 0x%x while reading", cluster);
		return -1;
	}

	loffset = offset % CLUSTER_SIZE(*ef->sb);
	remainder = MIN(size, node->size - offset);
	while (remainder > 0)
	{
		if (CLUSTER_INVALID(cluster))
		{
			exfat_error("invalid cluster 0x%x while reading", cluster);
			return -1;
		}
		lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
		if (exfat_pread(ef->dev, bufp, lsize,
					exfat_c2o(ef, cluster) + loffset) < 0)
		{
			exfat_error("failed to read cluster %#x", cluster);
			return -1;
		}
		bufp += lsize;
		loffset = 0;
		remainder -= lsize;
		cluster = exfat_next_cluster(ef, node, cluster);
	}
	if (!ef->ro && !ef->noatime)
		exfat_update_atime(node);
	return MIN(size, node->size - offset) - remainder;
}
static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
                            struct iterator* it)
{
    /* move iterator to the next entry in the directory */
    it->offset += sizeof(struct exfat_entry);
    /* fetch the next cluster if needed */
    if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
    {
        it->cluster = exfat_next_cluster(ef, parent, it->cluster);
        if (CLUSTER_INVALID(it->cluster))
        {
            exfat_error("invalid cluster while reading directory");
            return 1;
        }
        exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
                       exfat_c2o(ef, it->cluster), ef->fd);
    }
    return 0;
}
Example #8
0
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)
	{
		int rc = exfat_truncate(ef, node, offset + size);
		if (rc != 0)
			return rc;
	}
	if (size == 0)
		return 0;

	cluster = exfat_advance_cluster(ef, node, 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);
	}
	exfat_update_mtime(node);
	return size - remainder;
}
Example #9
0
ssize_t exfat_read(const struct exfat* ef, struct exfat_node* node,
		void* buffer, size_t size, off_t offset)
{
	cluster_t cluster;
	char* bufp = buffer;
	off_t lsize, loffset, remainder;

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

	cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
	if (CLUSTER_INVALID(cluster))
	{
		exfat_error("got invalid cluster");
		return -1;
	}

	loffset = offset % CLUSTER_SIZE(*ef->sb);
	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);
	}
	if (!ef->ro && !ef->noatime)
		exfat_update_atime(node);
	return size - remainder;
}
Example #10
0
void exfat_flush_cmap(struct exfat* ef)
{
	exfat_pwrite(ef->dev, ef->cmap.chunk, (ef->cmap.chunk_size + 7) / 8,
			exfat_c2o(ef, ef->cmap.start_cluster));
	ef->cmap.dirty = false;
}
/**
 * Cluster + offset from the beginning of the directory to absolute offset.
 */
static off64_t co2o(struct exfat* ef, cluster_t cluster, off64_t offset)
{
	return exfat_c2o(ef, cluster) + offset % CLUSTER_SIZE(*ef->sb);
}
/*
 * Reads one entry in directory at position pointed by iterator and fills
 * node structure.
 */
static int readdir(struct exfat* ef, const struct exfat_node* parent,
		struct exfat_node** node, struct iterator* it)
{
	int rc = -EIO;
	const struct exfat_entry* entry;
	const struct exfat_entry_meta1* meta1;
	const struct exfat_entry_meta2* meta2;
	const struct exfat_entry_name* file_name;
	const struct exfat_entry_upcase* upcase;
	const struct exfat_entry_bitmap* bitmap;
	const struct exfat_entry_label* label;
	uint8_t continuations = 0;
	le16_t* namep = NULL;
	uint16_t reference_checksum = 0;
	uint16_t actual_checksum = 0;
	uint64_t real_size = 0;

	*node = NULL;

	for (;;)
	{
		if (it->offset >= parent->size)
		{
			if (continuations != 0)
			{
				exfat_error("expected %hhu continuations", continuations);
				goto error;
			}
			return -ENOENT; /* that's OK, means end of directory */
		}

		entry = get_entry_ptr(ef, it);
		switch (entry->type)
		{
		case EXFAT_ENTRY_FILE:
			if (continuations != 0)
			{
				exfat_error("expected %hhu continuations before new entry",
						continuations);
				goto error;
			}
			meta1 = (const struct exfat_entry_meta1*) entry;
			continuations = meta1->continuations;
			/* each file entry must have at least 2 continuations:
			   info and name */
			if (continuations < 2)
			{
				exfat_error("too few continuations (%hhu)", continuations);
				goto error;
			}
			if (continuations > 1 +
					DIV_ROUND_UP(EXFAT_NAME_MAX, EXFAT_ENAME_MAX))
			{
				exfat_error("too many continuations (%hhu)", continuations);
				goto error;
			}
			reference_checksum = le16_to_cpu(meta1->checksum);
			actual_checksum = exfat_start_checksum(meta1);
			*node = allocate_node();
			if (*node == NULL)
			{
				rc = -ENOMEM;
				goto error;
			}
			/* new node has zero reference counter */
			(*node)->entry_cluster = it->cluster;
			(*node)->entry_offset = it->offset;
			init_node_meta1(*node, meta1);
			namep = (*node)->name;
			break;

		case EXFAT_ENTRY_FILE_INFO:
			if (continuations < 2)
			{
				exfat_error("unexpected continuation (%hhu)",
						continuations);
				goto error;
			}
			meta2 = (const struct exfat_entry_meta2*) entry;
			if (meta2->flags & ~(EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS))
			{
				exfat_error("unknown flags in meta2 (0x%hhx)", meta2->flags);
				goto error;
			}
			init_node_meta2(*node, meta2);
			actual_checksum = exfat_add_checksum(entry, actual_checksum);
			real_size = le64_to_cpu(meta2->real_size);
			/* empty files must be marked as non-contiguous */
			if ((*node)->size == 0 && (meta2->flags & EXFAT_FLAG_CONTIGUOUS))
			{
				exfat_error("empty file marked as contiguous (0x%hhx)",
						meta2->flags);
				goto error;
			}
			/* directories must be aligned on at cluster boundary */
			if (((*node)->flags & EXFAT_ATTRIB_DIR) &&
				(*node)->size % CLUSTER_SIZE(*ef->sb) != 0)
			{
				exfat_error("directory has invalid size %"PRIu64" bytes",
						(*node)->size);
				goto error;
			}
			--continuations;
			break;

		case EXFAT_ENTRY_FILE_NAME:
			if (continuations == 0)
			{
				exfat_error("unexpected continuation");
				goto error;
			}
			file_name = (const struct exfat_entry_name*) entry;
			actual_checksum = exfat_add_checksum(entry, actual_checksum);

			memcpy(namep, file_name->name,
					MIN(EXFAT_ENAME_MAX,
						((*node)->name + EXFAT_NAME_MAX - namep)) *
					sizeof(le16_t));
			namep += EXFAT_ENAME_MAX;
			if (--continuations == 0)
			{
				/*
				   There are two fields that contain file size. Maybe they
				   plan to add compression support in the future and one of
				   those fields is visible (uncompressed) size and the other
				   is real (compressed) size. Anyway, currently it looks like
				   exFAT does not support compression and both fields must be
				   equal.

				   There is an exception though: pagefile.sys (its real_size
				   is always 0).
				*/
				if (real_size != (*node)->size)
				{
					char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];

					exfat_get_name(*node, buffer, sizeof(buffer) - 1);
					exfat_error("`%s' real size does not equal to size "
							"(%"PRIu64" != %"PRIu64")", buffer,
							real_size, (*node)->size);
					goto error;
				}
				if (actual_checksum != reference_checksum)
				{
					char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];

					exfat_get_name(*node, buffer, sizeof(buffer) - 1);
					exfat_error("`%s' has invalid checksum (0x%hx != 0x%hx)",
							buffer, actual_checksum, reference_checksum);
					goto error;
				}
				if (fetch_next_entry(ef, parent, it) != 0)
					goto error;
				return 0; /* entry completed */
			}
			break;

		case EXFAT_ENTRY_UPCASE:
			if (ef->upcase != NULL)
				break;
			upcase = (const struct exfat_entry_upcase*) entry;
			if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
			{
				exfat_error("invalid cluster 0x%x in upcase table",
						le32_to_cpu(upcase->start_cluster));
				goto error;
			}
			if (le64_to_cpu(upcase->size) == 0 ||
				le64_to_cpu(upcase->size) > 0xffff * sizeof(uint16_t) ||
				le64_to_cpu(upcase->size) % sizeof(uint16_t) != 0)
			{
				exfat_error("bad upcase table size (%"PRIu64" bytes)",
						le64_to_cpu(upcase->size));
				goto error;
			}
			ef->upcase = malloc(le64_to_cpu(upcase->size));
			if (ef->upcase == NULL)
			{
				exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
						le64_to_cpu(upcase->size));
				rc = -ENOMEM;
				goto error;
			}
			ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);

			if (exfat_pread(ef->dev, ef->upcase, le64_to_cpu(upcase->size),
					exfat_c2o(ef, le32_to_cpu(upcase->start_cluster))) < 0)
			{
				exfat_error("failed to read upper case table "
						"(%"PRIu64" bytes starting at cluster %#x)",
						le64_to_cpu(upcase->size),
						le32_to_cpu(upcase->start_cluster));
				goto error;
			}
			break;

		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))
			{
				exfat_error("invalid cluster 0x%x in clusters bitmap",
						ef->cmap.start_cluster);
				goto error;
			}
			ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
				EXFAT_FIRST_DATA_CLUSTER;
			if (le64_to_cpu(bitmap->size) < DIV_ROUND_UP(ef->cmap.size, 8))
			{
				exfat_error("invalid clusters bitmap size: %"PRIu64
						" (expected at least %u)",
						le64_to_cpu(bitmap->size),
						DIV_ROUND_UP(ef->cmap.size, 8));
				goto error;
			}
			/* FIXME bitmap can be rather big, up to 512 MB */
			ef->cmap.chunk_size = ef->cmap.size;
			ef->cmap.chunk = malloc(BMAP_SIZE(ef->cmap.chunk_size));
			if (ef->cmap.chunk == NULL)
			{
				exfat_error("failed to allocate clusters bitmap chunk "
						"(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
				rc = -ENOMEM;
				goto error;
			}

			if (exfat_pread(ef->dev, ef->cmap.chunk,
					BMAP_SIZE(ef->cmap.chunk_size),
					exfat_c2o(ef, ef->cmap.start_cluster)) < 0)
			{
				exfat_error("failed to read clusters bitmap "
						"(%"PRIu64" bytes starting at cluster %#x)",
						le64_to_cpu(bitmap->size), ef->cmap.start_cluster);
				goto error;
			}
			break;

		case EXFAT_ENTRY_LABEL:
			label = (const struct exfat_entry_label*) entry;
			if (label->length > EXFAT_ENAME_MAX)
			{
				exfat_error("too long label (%hhu chars)", label->length);
				goto error;
			}
			if (utf16_to_utf8(ef->label, label->name,
						sizeof(ef->label) - 1, EXFAT_ENAME_MAX) != 0)
				goto error;
			break;

		default:
			if (entry->type & EXFAT_ENTRY_VALID)
			{
				exfat_error("unknown entry type 0x%hhx", entry->type);
				goto error;
			}
			break;
		}

		if (fetch_next_entry(ef, parent, it) != 0)
			goto error;
	}
	/* we never reach here */

error:
	free(*node);
	*node = NULL;
	return rc;
}