Esempio n. 1
0
static int commit_super_block(const struct exfat* ef)
{
	if (exfat_pwrite(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0) < 0)
	{
		exfat_error("failed to write super block");
		return 1;
	}
	return exfat_fsync(ef->dev);
}
Esempio n. 2
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;
}
Esempio n. 3
0
static int fuse_exfat_readdir(const char* path, void* buffer,
		fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* fi)
{
	struct exfat_node* parent;
	struct exfat_node* node;
	struct exfat_iterator it;
	int rc;
	char name[UTF8_BYTES(EXFAT_NAME_MAX) + 1];

	exfat_debug("[%s] %s", __func__, path);

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

	filler(buffer, ".", NULL, 0);
	filler(buffer, "..", NULL, 0);

	rc = exfat_opendir(&ef, parent, &it);
	if (rc != 0)
	{
		exfat_put_node(&ef, parent);
		exfat_error("failed to open directory '%s'", path);
		return rc;
	}
	while ((node = exfat_readdir(&ef, &it)))
	{
		exfat_get_name(node, name, sizeof(name) - 1);
		exfat_debug("[%s] %s: %s, %"PRId64" bytes, cluster 0x%x", __func__,
				name, IS_CONTIGUOUS(*node) ? "contiguous" : "fragmented",
				node->size, node->start_cluster);
		filler(buffer, name, NULL, 0);
		exfat_put_node(&ef, node);
	}
	exfat_closedir(&ef, &it);
	exfat_put_node(&ef, parent);
	return 0;
}
Esempio n. 4
0
static int uct_write(struct exfat_dev* dev)
{
	if (exfat_write(dev, upcase_table, sizeof(upcase_table)) < 0)
	{
		exfat_error("failed to write upcase table of %zu bytes",
				sizeof(upcase_table));
		return 1;
	}
	return 0;
}
Esempio n. 5
0
int exfat_close(struct exfat_dev* dev)
{
	int rc = 0;

#ifdef USE_UBLIO
	if (ublio_close(dev->ufh) != 0)
	{
		exfat_error("failed to close ublio");
		rc = -EIO;
	}
#endif
	if (close(dev->fd) != 0)
	{
		exfat_error("failed to close device: %s", strerror(errno));
		rc = -EIO;
	}
	free(dev);
	return rc;
}
Esempio n. 6
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;
}
Esempio n. 7
0
static cluster_t fat_write_entry(struct exfat_dev* dev, cluster_t cluster,
		cluster_t value)
{
	le32_t fat_entry = cpu_to_le32(value);
	if (exfat_write(dev, &fat_entry, sizeof(fat_entry)) < 0)
	{
		exfat_error("failed to write FAT entry 0x%x", value);
		return 0;
	}
	return cluster + 1;
}
static struct exfat_node* allocate_node(void)
{
	struct exfat_node* node = malloc(sizeof(struct exfat_node));
	if (node == NULL)
	{
		exfat_error("failed to allocate node");
		return NULL;
	}
	memset(node, 0, sizeof(struct exfat_node));
	return node;
}
static char* add_fsname_option(char* options, const char* spec)
{
	char spec_abs[PATH_MAX];

	if (realpath(spec, spec_abs) == NULL)
	{
		free(options);
		exfat_error("failed to get absolute path for `%s'", spec);
		return NULL;
	}
	return add_option(options, "fsname", spec_abs);
}
Esempio n. 10
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;
}
Esempio n. 11
0
int exfat_fsync(struct exfat_dev* dev)
{
#ifdef USE_UBLIO
	if (ublio_fsync(dev->ufh) != 0)
#else
	if (fsync(dev->fd) != 0)
#endif
	{
		exfat_error("fsync failed");
		return 1;
	}
	return 0;
}
static int erase_object(struct exfat_dev* dev, const void* block,
		size_t block_size, off64_t start, off64_t size)
{
	const off64_t block_count = DIV_ROUND_UP(size, block_size);
	off64_t i;

	if (exfat_seek(dev, start, SEEK_SET) == (off64_t) -1)
	{
		exfat_error("seek to 0x%"PRIx64" failed", start);
		return 1;
	}
	for (i = 0; i < size; i += block_size)
	{
		if (exfat_write(dev, block, MIN(size - i, block_size)) < 0)
		{
			exfat_error("failed to erase block %"PRIu64"/%"PRIu64
					" at 0x%"PRIx64, i + 1, block_count, start);
			return 1;
		}
	}
	return 0;
}
Esempio n. 13
0
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;
	}
	if (exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
			exfat_c2o(ef, it->cluster)) < 0)
	{
		exfat_error("failed to read directory cluster %#x", it->cluster);
		return -EIO;
	}
	return 0;
}
Esempio n. 14
0
static uint32_t setup_volume_serial(uint32_t user_defined)
{
	struct timeval now;

	if (user_defined != 0)
		return user_defined;

	if (gettimeofday(&now, NULL) != 0)
	{
		exfat_error("failed to form volume id");
		return 0;
	}
	return (now.tv_sec << 20) | now.tv_usec;
}
Esempio n. 15
0
static bool erase_entry(struct exfat* ef, struct exfat_node* node)
{
	cluster_t cluster = node->entry_cluster;
	off_t offset = node->entry_offset;
	int name_entries = DIV_ROUND_UP(utf16_length(node->name), EXFAT_ENAME_MAX);
	uint8_t entry_type;

	entry_type = EXFAT_ENTRY_FILE & ~EXFAT_ENTRY_VALID;
	if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 0)
	{
		exfat_error("failed to erase meta1 entry");
		return false;
	}

	if (!next_entry(ef, node->parent, &cluster, &offset))
		return false;
	entry_type = EXFAT_ENTRY_FILE_INFO & ~EXFAT_ENTRY_VALID;
	if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 0)
	{
		exfat_error("failed to erase meta2 entry");
		return false;
	}

	while (name_entries--)
	{
		if (!next_entry(ef, node->parent, &cluster, &offset))
			return false;
		entry_type = EXFAT_ENTRY_FILE_NAME & ~EXFAT_ENTRY_VALID;
		if (exfat_pwrite(ef->dev, &entry_type, 1,
				co2o(ef, cluster, offset)) < 0)
		{
			exfat_error("failed to erase name entry");
			return false;
		}
	}
	return true;
}
Esempio n. 16
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. 17
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;
}
Esempio n. 18
0
static bool next_entry(struct exfat* ef, const struct exfat_node* parent,
		cluster_t* cluster, off_t* offset)
{
	*offset += sizeof(struct exfat_entry);
	if (*offset % CLUSTER_SIZE(*ef->sb) == 0)
	{
		*cluster = exfat_next_cluster(ef, parent, *cluster);
		if (CLUSTER_INVALID(*cluster))
		{
			exfat_error("invalid cluster %#x while getting next entry",
					*cluster);
			return false;
		}
	}
	return true;
}
Esempio n. 19
0
static char* add_user_option(char* options)
{
	struct passwd* pw;

	if (getuid() == 0)
		return options;

	pw = getpwuid(getuid());
	if (pw == NULL || pw->pw_name == NULL)
	{
		free(options);
		exfat_error("failed to determine username");
		return NULL;
	}
	return add_option(options, "user", pw->pw_name);
}
static int create(struct exfat_dev* dev)
{
	const struct fs_object** pp;
	off64_t position = 0;

	for (pp = objects; *pp; pp++)
	{
		position = ROUND_UP(position, (*pp)->get_alignment());
		if (exfat_seek(dev, position, SEEK_SET) == (off64_t) -1)
		{
			exfat_error("seek to 0x%"PRIx64" failed", position);
			return 1;
		}
		if ((*pp)->write(dev) != 0)
			return 1;
		position += (*pp)->get_size();
	}
	return 0;
}
Esempio n. 21
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)
    {
        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;
}
Esempio n. 22
0
File: main.c Progetto: draekko/exfat
static char* add_fsname_option(char* options, const char* spec)
{
	/* escaped string cannot be more than twice as big as the original one */
	char* escaped = malloc(strlen(spec) * 2 + 1);

	if (escaped == NULL)
	{
		free(options);
		exfat_error("failed to allocate escaped string for %s", spec);
		return NULL;
	}

	/* on some platforms (e.g. Android, Solaris) device names can contain
	   commas */
	escape(escaped, spec);
	options = add_option(options, "fsname", escaped);
	free(escaped);
	return options;
}
Esempio n. 23
0
static bool set_next_cluster(const struct exfat* ef, bool contiguous,
		cluster_t current, cluster_t next)
{
	loff_t fat_offset;
	le32_t next_le32;

	if (contiguous)
		return true;
	fat_offset = s2o(ef, le32_to_cpu(ef->sb->fat_sector_start))
		+ current * sizeof(cluster_t);
	next_le32 = cpu_to_le32(next);
	if (exfat_pwrite(ef->dev, &next_le32, sizeof(next_le32), fat_offset) < 0)
	{
		exfat_error("failed to write the next cluster %#x after %#x", next,
				current);
		return false;
	}
	return true;
}
Esempio n. 24
0
static cluster_t allocate_cluster(struct exfat* ef, cluster_t hint)
{
	cluster_t cluster;

	hint -= EXFAT_FIRST_DATA_CLUSTER;
	if (hint >= ef->cmap.chunk_size)
		hint = 0;

	cluster = find_bit_and_set(ef->cmap.chunk, hint, ef->cmap.chunk_size);
	if (cluster == EXFAT_CLUSTER_END)
		cluster = find_bit_and_set(ef->cmap.chunk, 0, hint);
	if (cluster == EXFAT_CLUSTER_END)
	{
		exfat_error("no free space left");
		return EXFAT_CLUSTER_END;
	}

	ef->cmap.dirty = true;
	return cluster;
}
static int check_size(off64_t volume_size)
{
	const struct fs_object** pp;
	off64_t position = 0;

	for (pp = objects; *pp; pp++)
	{
		position = ROUND_UP(position, (*pp)->get_alignment());
		position += (*pp)->get_size();
	}

	if (position > volume_size)
	{
		struct exfat_human_bytes vhb;

		exfat_humanize_bytes(volume_size, &vhb);
		exfat_error("too small device (%"PRIu64" %s)", vhb.value, vhb.unit);
		return 1;
	}

	return 0;

}
Esempio n. 26
0
File: node.c Progetto: ZeroJim/twrp
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;
		}
		exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
				exfat_c2o(ef, it->cluster));
	}
	return 0;
}
static char* add_option(char* options, const char* name, const char* value)
{
	size_t size;

	if (value)
		size = strlen(options) + strlen(name) + strlen(value) + 3;
	else
		size = strlen(options) + strlen(name) + 2;

	options = realloc(options, size);
	if (options == NULL)
	{
		exfat_error("failed to reallocate options string");
		return NULL;
	}
	strcat(options, ",");
	strcat(options, name);
	if (value)
	{
		strcat(options, "=");
		strcat(options, value);
	}
	return options;
}
Esempio n. 28
0
int 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 (!(node->flags & EXFAT_ATTRIB_DIRTY))
		return 0; /* no need to flush */

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

	if (node->parent == NULL)
		return 0; /* 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);

	if (exfat_pread(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0)
	{
		exfat_error("failed to read meta1 entry on flush");
		return -EIO;
	}
	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);

	if (exfat_pread(ef->dev, &meta2, sizeof(meta2), meta2_offset) < 0)
	{
		exfat_error("failed to read meta2 entry on flush");
		return -EIO;
	}
	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);

	if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0)
	{
		exfat_error("failed to write meta1 entry on flush");
		return -EIO;
	}
	if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2), meta2_offset) < 0)
	{
		exfat_error("failed to write meta2 entry on flush");
		return -EIO;
	}

	node->flags &= ~EXFAT_ATTRIB_DIRTY;
	return 0;
}
int main(int argc, char* argv[])
{
	const char* spec = NULL;
	char** pp;
	int spc_bits = -1;
	const char* volume_label = NULL;
	uint32_t volume_serial = 0;
	uint64_t first_sector = 0;
	struct exfat_dev* dev;

	printf("mkexfatfs %u.%u.%u\n",
			EXFAT_VERSION_MAJOR, EXFAT_VERSION_MINOR, EXFAT_VERSION_PATCH);

	for (pp = argv + 1; *pp; pp++)
	{
		if (strcmp(*pp, "-s") == 0)
		{
			pp++;
			if (*pp == NULL)
				usage(argv[0]);
			spc_bits = logarithm2(atoi(*pp));
			if (spc_bits < 0)
			{
				exfat_error("invalid option value: `%s'", *pp);
				return 1;
			}
		}
		else if (strcmp(*pp, "-n") == 0)
		{
			pp++;
			if (*pp == NULL)
				usage(argv[0]);
			volume_label = *pp;
		}
		else if (strcmp(*pp, "-i") == 0)
		{
			pp++;
			if (*pp == NULL)
				usage(argv[0]);
			volume_serial = strtol(*pp, NULL, 16);
		}
		else if (strcmp(*pp, "-p") == 0)
		{
			pp++;
			if (*pp == NULL)
				usage(argv[0]);
			first_sector = strtoll(*pp, NULL, 10);
		}
		else if (strcmp(*pp, "-v") == 0)
		{
			puts("Copyright (C) 2011-2013  Andrew Nayenko");
			return 0;
		}
		else if (spec == NULL)
			spec = *pp;
		else
			usage(argv[0]);
	}
	if (spec == NULL)
		usage(argv[0]);

	dev = exfat_open(spec, EXFAT_MODE_RW);
	if (dev == NULL)
		return 1;
	if (setup(dev, 9, spc_bits, volume_label, volume_serial,
				first_sector) != 0)
	{
		exfat_close(dev);
		return 1;
	}
	if (exfat_close(dev) != 0)
		return 1;
	printf("File system created successfully.\n");
	return 0;
}
Esempio n. 30
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;
}