Пример #1
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);
}
Пример #2
0
static int prepare_super_block(const struct exfat* ef)
{
	if (le16_to_cpu(ef->sb->volume_state) & EXFAT_STATE_MOUNTED)
		exfat_warn("volume was not unmounted cleanly");

	if (ef->ro)
		return 0;

#if defined(__AROS__) || defined(AMIGA)
	return 0;
#else
	ef->sb->volume_state = cpu_to_le16(
			le16_to_cpu(ef->sb->volume_state) | EXFAT_STATE_MOUNTED);
	return commit_super_block(ef);
#endif
}
Пример #3
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);
		}
	}
}
Пример #4
0
static void reset_cache(struct exfat* ef, struct exfat_node* node)
{
	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)
	{
		char buffer[EXFAT_NAME_MAX + 1];
		exfat_get_name(node, buffer, EXFAT_NAME_MAX);
		exfat_warn("non-zero reference counter (%d) for `%s'",
				node->references, buffer);
	}
	while (node->references)
		exfat_put_node(ef, node);
}
Пример #5
0
static void reset_cache(struct exfat* ef, struct exfat_node* node)
{
    struct exfat_node* child;
    struct exfat_node* next;

    for (child = node->child; child; child = next)
    {
        reset_cache(ef, child);
        next = child->next;
        free(child);
    }
    if (node->references != 0)
    {
        char buffer[EXFAT_NAME_MAX + 1];
        exfat_get_name(node, buffer, EXFAT_NAME_MAX);
        exfat_warn("non-zero reference counter (%d) for `%s'",
                   node->references, buffer);
    }
    while (node->references--)
        exfat_put_node(ef, node);
    node->child = NULL;
    node->flags &= ~EXFAT_ATTRIB_CACHED;
}
Пример #6
0
struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
{
	struct exfat_dev* dev;
	struct stat stbuf;
#ifdef USE_UBLIO
	struct ublio_param up;
#endif

	dev = malloc(sizeof(struct exfat_dev));
	if (dev == NULL)
	{
		exfat_error("failed to allocate memory for device structure");
		return NULL;
	}

	switch (mode)
	{
	case EXFAT_MODE_RO:
		dev->fd = open_ro(spec);
		if (dev->fd == -1)
		{
			free(dev);
			exfat_error("failed to open '%s' in read-only mode: %s", spec,
					strerror(errno));
			return NULL;
		}
		dev->mode = EXFAT_MODE_RO;
		break;
	case EXFAT_MODE_RW:
		dev->fd = open_rw(spec);
		if (dev->fd == -1)
		{
			free(dev);
			exfat_error("failed to open '%s' in read-write mode: %s", spec,
					strerror(errno));
			return NULL;
		}
		dev->mode = EXFAT_MODE_RW;
		break;
	case EXFAT_MODE_ANY:
		dev->fd = open_rw(spec);
		if (dev->fd != -1)
		{
			dev->mode = EXFAT_MODE_RW;
			break;
		}
		dev->fd = open_ro(spec);
		if (dev->fd != -1)
		{
			dev->mode = EXFAT_MODE_RO;
			exfat_warn("'%s' is write-protected, mounting read-only", spec);
			break;
		}
		free(dev);
		exfat_error("failed to open '%s': %s", spec, strerror(errno));
		return NULL;
	}

	if (fstat(dev->fd, &stbuf) != 0)
	{
		close(dev->fd);
		free(dev);
		exfat_error("failed to fstat '%s'", spec);
		return NULL;
	}
	if (!S_ISBLK(stbuf.st_mode) &&
		!S_ISCHR(stbuf.st_mode) &&
		!S_ISREG(stbuf.st_mode))
	{
		close(dev->fd);
		free(dev);
		exfat_error("'%s' is neither a device, nor a regular file", spec);
		return NULL;
	}

#if defined(__APPLE__)
	if (!S_ISREG(stbuf.st_mode))
	{
		uint32_t block_size = 0;
		uint64_t blocks = 0;

		if (ioctl(dev->fd, DKIOCGETBLOCKSIZE, &block_size) != 0)
		{
			close(dev->fd);
			free(dev);
			exfat_error("failed to get block size");
			return NULL;
		}
		if (ioctl(dev->fd, DKIOCGETBLOCKCOUNT, &blocks) != 0)
		{
			close(dev->fd);
			free(dev);
			exfat_error("failed to get blocks count");
			return NULL;
		}
		dev->size = blocks * block_size;
	}
	else
#elif defined(__OpenBSD__)
	if (!S_ISREG(stbuf.st_mode))
	{
		struct disklabel lab;
		struct partition* pp;
		char* partition;

		if (ioctl(dev->fd, DIOCGDINFO, &lab) == -1)
		{
			close(dev->fd);
			free(dev);
			exfat_error("failed to get disklabel");
			return NULL;
		}

		/* Don't need to check that partition letter is valid as we won't get
		   this far otherwise. */
		partition = strchr(spec, '\0') - 1;
		pp = &(lab.d_partitions[*partition - 'a']);
		dev->size = DL_GETPSIZE(pp) * lab.d_secsize;

		if (pp->p_fstype != FS_NTFS)
			exfat_warn("partition type is not 0x07 (NTFS/exFAT); "
					"you can fix this with fdisk(8)");
	}
	else
#endif
	{
		/* works for Linux, FreeBSD, Solaris */
		dev->size = exfat_seek(dev, 0, SEEK_END);
		if (dev->size <= 0)
		{
			close(dev->fd);
			free(dev);
			exfat_error("failed to get size of '%s'", spec);
			return NULL;
		}
		if (exfat_seek(dev, 0, SEEK_SET) == -1)
		{
			close(dev->fd);
			free(dev);
			exfat_error("failed to seek to the beginning of '%s'", spec);
			return NULL;
		}
	}

#ifdef USE_UBLIO
	memset(&up, 0, sizeof(struct ublio_param));
	up.up_blocksize = 256 * 1024;
	up.up_items = 64;
	up.up_grace = 32;
	up.up_priv = &dev->fd;

	dev->pos = 0;
	dev->ufh = ublio_open(&up);
	if (dev->ufh == NULL)
	{
		close(dev->fd);
		free(dev);
		exfat_error("failed to initialize ublio");
		return NULL;
	}
#endif

	return dev;
}
Пример #7
0
/*
 * 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 valid_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);
			valid_size = le64_to_cpu(meta2->valid_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)
			{
				if (!check_node(*node, actual_checksum, reference_checksum,
						valid_size))
					goto error;
				if (!fetch_next_entry(ef, parent, it))
					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))
				break; /* deleted entry, ignore it */
			if (!(entry->type & EXFAT_ENTRY_OPTIONAL))
			{
				exfat_error("unknown entry type %#hhx", entry->type);
				goto error;
			}
			/* optional entry, warn and skip */
			exfat_warn("unknown entry type %#hhx", entry->type);
			if (continuations == 0)
			{
				exfat_error("unexpected continuation");
				goto error;
			}
			--continuations;
			break;
		}

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

error:
	free(*node);
	*node = NULL;
	return rc;
}
Пример #8
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;
}
Пример #9
0
struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
{
	struct exfat_dev* dev;
	struct stat stbuf;
#ifdef USE_UBLIO
	struct ublio_param up;
#endif

	dev = malloc(sizeof(struct exfat_dev));
	if (dev == NULL)
	{
		exfat_error("failed to allocate memory for device structure");
		return NULL;
	}

	switch (mode)
	{
	case EXFAT_MODE_RO:
		dev->fd = open_ro(spec);
		if (dev->fd == -1)
		{
			free(dev);
			exfat_error("failed to open `%s' in read-only mode", spec);
			return NULL;
		}
		dev->mode = EXFAT_MODE_RO;
		break;
	case EXFAT_MODE_RW:
		dev->fd = open_rw(spec);
		if (dev->fd == -1)
		{
			free(dev);
			exfat_error("failed to open `%s' in read-write mode", spec);
			return NULL;
		}
		dev->mode = EXFAT_MODE_RW;
		break;
	case EXFAT_MODE_ANY:
		dev->fd = open_rw(spec);
		if (dev->fd != -1)
		{
			dev->mode = EXFAT_MODE_RW;
			break;
		}
		dev->fd = open_ro(spec);
		if (dev->fd != -1)
		{
			dev->mode = EXFAT_MODE_RO;
			exfat_warn("`%s' is write-protected, mounting read-only", spec);
			break;
		}
		free(dev);
		exfat_error("failed to open `%s'", spec);
		return NULL;
	}

	if (fstat(dev->fd, &stbuf) != 0)
	{
		close(dev->fd);
		free(dev);
		exfat_error("failed to fstat `%s'", spec);
		return NULL;
	}
	if (!S_ISBLK(stbuf.st_mode) &&
		!S_ISCHR(stbuf.st_mode) &&
		!S_ISREG(stbuf.st_mode))
	{
		close(dev->fd);
		free(dev);
		exfat_error("`%s' is neither a device, nor a regular file", spec);
		return NULL;
	}

#ifdef __APPLE__
	if (!S_ISREG(stbuf.st_mode))
	{
		uint32_t block_size = 0;
		uint64_t blocks = 0;

		if (ioctl(dev->fd, DKIOCGETBLOCKSIZE, &block_size) != 0)
		{
			close(dev->fd);
			free(dev);
			exfat_error("failed to get block size");
			return NULL;
		}
		if (ioctl(dev->fd, DKIOCGETBLOCKCOUNT, &blocks) != 0)
		{
			close(dev->fd);
			free(dev);
			exfat_error("failed to get blocks count");
			return NULL;
		}
		dev->size = blocks * block_size;
	}
	else
#endif
	{
		/* works for Linux, FreeBSD, Solaris */
		dev->size = exfat_seek(dev, 0, SEEK_END);
		if (dev->size <= 0)
		{
			close(dev->fd);
			free(dev);
			exfat_error("failed to get size of `%s'", spec);
			return NULL;
		}
		if (exfat_seek(dev, 0, SEEK_SET) == -1)
		{
			close(dev->fd);
			free(dev);
			exfat_error("failed to seek to the beginning of `%s'", spec);
			return NULL;
		}
	}

#ifdef USE_UBLIO
	memset(&up, 0, sizeof(struct ublio_param));
	up.up_blocksize = 256 * 1024;
	up.up_items = 64;
	up.up_grace = 32;
	up.up_priv = &dev->fd;

	dev->pos = 0;
	dev->ufh = ublio_open(&up);
	if (dev->ufh == NULL)
	{
		close(dev->fd);
		free(dev);
		exfat_error("failed to initialize ublio");
		return NULL;
	}
#endif

	return dev;
}