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;
}
Esempio n. 2
0
File: node.c Progetto: ZeroJim/twrp
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_pread(ef->dev, &meta1, sizeof(meta1), meta1_offset);
	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_pread(ef->dev, &meta2, sizeof(meta2), meta2_offset);
	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_pwrite(ef->dev, &meta1, sizeof(meta1), meta1_offset);
	exfat_pwrite(ef->dev, &meta2, sizeof(meta2), meta2_offset);

	node->flags &= ~EXFAT_ATTRIB_DIRTY;
}
Esempio n. 3
0
static bool verify_vbr_checksum(struct exfat_dev* dev, void* sector,
		fbx_off_t sector_size)
{
	uint32_t vbr_checksum;
	int i;

	if (exfat_pread(dev, sector, sector_size, 0) < 0)
	{
		exfat_error("failed to read boot sector");
		return false;
	}
	vbr_checksum = exfat_vbr_start_checksum(sector, sector_size);
	for (i = 1; i < 11; i++)
	{
		if (exfat_pread(dev, sector, sector_size, i * sector_size) < 0)
		{
			exfat_error("failed to read VBR sector");
			return false;
		}
		vbr_checksum = exfat_vbr_add_checksum(sector, sector_size,
				vbr_checksum);
	}
	if (exfat_pread(dev, sector, sector_size, i * sector_size) < 0)
	{
		exfat_error("failed to read VBR checksum sector");
		return false;
	}
	for (i = 0; i < sector_size / sizeof(vbr_checksum); i++)
		if (le32_to_cpu(((const le32_t*) sector)[i]) != vbr_checksum)
		{
			exfat_error("invalid VBR checksum 0x%x (expected 0x%x)",
					le32_to_cpu(((const le32_t*) sector)[i]), vbr_checksum);
			return false;
		}
	return true;
}
Esempio n. 4
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);
}
Esempio n. 5
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);
}
Esempio n. 6
0
File: node.c Progetto: ZeroJim/twrp
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_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
			exfat_c2o(ef, it->cluster));
	return 0;
}
Esempio n. 7
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;
}
/*
 * 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;
}
Esempio n. 9
0
int exfat_mount(struct exfat* ef, const char* spec, const char* options)
{
	int rc;
	enum exfat_mode mode;

	exfat_tzset();
	memset(ef, 0, sizeof(struct exfat));

	parse_options(ef, options);

	if (match_option(options, "ro"))
		mode = EXFAT_MODE_RO;
	else if (match_option(options, "ro_fallback"))
		mode = EXFAT_MODE_ANY;
	else
		mode = EXFAT_MODE_RW;
	ef->dev = exfat_open(spec, mode);
	if (ef->dev == NULL)
		return -EIO;
	if (exfat_get_mode(ef->dev) == EXFAT_MODE_RO)
	{
		if (mode == EXFAT_MODE_ANY)
			ef->ro = -1;
		else
			ef->ro = 1;
	}

	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");
		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");
		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");
		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);
		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);
		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");
		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);
		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);
		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);
		return -EIO;
	}
	if (le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb) >
			exfat_get_size(ef->dev))
	{
		/* 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_get_size(ef->dev));
	}

	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");
		return -ENOMEM;
	}
	memset(ef->root, 0, sizeof(struct exfat_node));
	ef->root->flags = EXFAT_ATTRIB_DIR;
	ef->root->start_cluster = le32_to_cpu(ef->sb->rootdir_cluster);
	ef->root->fptr_cluster = ef->root->start_cluster;
	ef->root->name[0] = cpu_to_le16('\0');
	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);
		return -EIO;
	}
	/* exFAT does not have time attributes for the root directory */
	ef->root->mtime = 0;
	ef->root->atime = 0;
	/* always keep at least 1 reference to the root node */
	exfat_get_node(ef->root);

	rc = exfat_cache_directory(ef, ef->root);
	if (rc != 0)
		goto error;
	if (ef->upcase == NULL)
	{
		exfat_error("upcase table is not found");
		goto error;
	}
	if (ef->cmap.chunk == NULL)
	{
		exfat_error("clusters bitmap is not found");
		goto error;
	}

	if (prepare_super_block(ef) != 0)
		goto error;

	return 0;

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);
	return -EIO;
}