+static errcode_t unix_set_option(io_channel channel, const char *option,
+ const char *arg)
+{
+ struct unix_private_data *data;
+ unsigned long long tmp;
+ char *end;
+
+ EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+ data = (struct unix_private_data *) channel->private_data;
+ EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
+
+ if (!strcmp(option, "offset")) {
+ if (!arg)
+ return EXT2_ET_INVALID_ARGUMENT;
+
+ tmp = strtoull(arg, &end, 0);
+ if (*end)
+ return EXT2_ET_INVALID_ARGUMENT;
+ data->offset = tmp;
+ if (data->offset < 0)
+ return EXT2_ET_INVALID_ARGUMENT;
+ return 0;
+ }
+ return EXT2_ET_INVALID_ARGUMENT;
+}
+
+#if defined(__linux__) && !defined(BLKDISCARD)
+#define BLKDISCARD _IO(0x12,119)
+#endif
+
+static errcode_t unix_discard(io_channel channel, unsigned long long block,
+ unsigned long long count)
+{
+ struct unix_private_data *data;
+ int ret;
+
+ EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+ data = (struct unix_private_data *) channel->private_data;
+ EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
+
+ if (channel->flags & CHANNEL_FLAGS_BLOCK_DEVICE) {
+#ifdef BLKDISCARD
+ __u64 range[2];
+
+ range[0] = (__u64)(block) * channel->block_size + data->offset;
+ range[1] = (__u64)(count) * channel->block_size;
+
+ ret = ioctl(data->dev, BLKDISCARD, &range);
+#else
+ goto unimplemented;
+#endif
+ } else {
+#if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_PUNCH_HOLE)
+ /*
+ * If we are not on block device, try to use punch hole
+ * to reclaim free space.
+ */
+ ret = fallocate(data->dev,
+ FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ (off_t)(block) * channel->block_size + data->offset,
+ (off_t)(count) * channel->block_size);
+#else
+ goto unimplemented;
+#endif
+ }
+ if (ret < 0) {
+ if (errno == EOPNOTSUPP)
+ goto unimplemented;
+ return errno;
+ }
+ return 0;
+unimplemented:
+ return EXT2_ET_UNIMPLEMENTED;
+}
+
+/*
+ * If we know about ZERO_RANGE, try that before we try PUNCH_HOLE because
+ * ZERO_RANGE doesn't unmap preallocated blocks. We prefer fallocate because
+ * it always invalidates page cache, and libext2fs requires that reads after
+ * ZERO_RANGE return zeroes.
+ */
+static int __unix_zeroout(int fd, off_t offset, off_t len)
+{
+ int ret = -1;
+
+#if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_ZERO_RANGE)
+ ret = fallocate(fd, FALLOC_FL_ZERO_RANGE, offset, len);
+ if (ret == 0)
+ return 0;
+#endif
+#if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_PUNCH_HOLE) && defined(FALLOC_FL_KEEP_SIZE)
+ ret = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ offset, len);
+ if (ret == 0)
+ return 0;
+#endif
+ errno = EOPNOTSUPP;
+ return ret;
+}
+
+/* parameters might not be used if OS doesn't support zeroout */
+#if __GNUC_PREREQ (4, 6)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+static errcode_t unix_zeroout(io_channel channel, unsigned long long block,
+ unsigned long long count)
+{
+ struct unix_private_data *data;
+ int ret;
+
+ EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+ data = (struct unix_private_data *) channel->private_data;
+ EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
+
+ if (safe_getenv("UNIX_IO_NOZEROOUT"))
+ goto unimplemented;
+
+ if (!(channel->flags & CHANNEL_FLAGS_BLOCK_DEVICE)) {
+ /* Regular file, try to use truncate/punch/zero. */
+ struct stat statbuf;
+
+ if (count == 0)
+ return 0;
+ /*
+ * If we're trying to zero a range past the end of the file,
+ * extend the file size, then truncate everything.
+ */
+ ret = fstat(data->dev, &statbuf);
+ if (ret)
+ goto err;
+ if ((unsigned long long) statbuf.st_size <
+ (block + count) * channel->block_size + data->offset) {
+ ret = ftruncate(data->dev,
+ (block + count) * channel->block_size + data->offset);
+ if (ret)
+ goto err;
+ }
+ }
+
+ ret = __unix_zeroout(data->dev,
+ (off_t)(block) * channel->block_size + data->offset,
+ (off_t)(count) * channel->block_size);
+err:
+ if (ret < 0) {
+ if (errno == EOPNOTSUPP)
+ goto unimplemented;
+ return errno;
+ }
+ return 0;
+unimplemented:
+ return EXT2_ET_UNIMPLEMENTED;
+}
+#if __GNUC_PREREQ (4, 6)
+#pragma GCC diagnostic pop
+#endif
+
+static struct struct_io_manager struct_unix_manager = {
+ .magic = EXT2_ET_MAGIC_IO_MANAGER,
+ .name = "Unix I/O Manager",
+ .open = unix_open,
+ .close = unix_close,
+ .set_blksize = unix_set_blksize,
+ .read_blk = unix_read_blk,
+ .write_blk = unix_write_blk,
+ .flush = unix_flush,
+ .write_byte = unix_write_byte,
+ .set_option = unix_set_option,
+ .get_stats = unix_get_stats,
+ .read_blk64 = unix_read_blk64,
+ .write_blk64 = unix_write_blk64,
+ .discard = unix_discard,
+ .cache_readahead = unix_cache_readahead,
+ .zeroout = unix_zeroout,
+};
+
+io_manager unix_io_manager = &struct_unix_manager;
+
+static struct struct_io_manager struct_unixfd_manager = {
+ .magic = EXT2_ET_MAGIC_IO_MANAGER,
+ .name = "Unix fd I/O Manager",
+ .open = unixfd_open,
+ .close = unix_close,
+ .set_blksize = unix_set_blksize,
+ .read_blk = unix_read_blk,
+ .write_blk = unix_write_blk,
+ .flush = unix_flush,
+ .write_byte = unix_write_byte,
+ .set_option = unix_set_option,
+ .get_stats = unix_get_stats,
+ .read_blk64 = unix_read_blk64,
+ .write_blk64 = unix_write_blk64,
+ .discard = unix_discard,
+ .cache_readahead = unix_cache_readahead,
+ .zeroout = unix_zeroout,
+};
+
+io_manager unixfd_io_manager = &struct_unixfd_manager;