/*
 * Copyright (C) 2018 Red Hat, Inc. All rights reserved.
 *
 * This file is part of LVM2.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License v.2.1.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef BCACHE_H
#define BCACHE_H

#include <linux/fs.h>
#include <stdint.h>
#include <stdbool.h>

#include "libdevmapper.h"

/*----------------------------------------------------------------*/

// FIXME: move somewhere more sensible
#define container_of(v, t, head) \
    ((t *)((const char *)(v) - (const char *)&((t *) 0)->head))

/*----------------------------------------------------------------*/

enum dir {
	DIR_READ,
	DIR_WRITE
};

typedef uint64_t block_address;
typedef uint64_t sector_t;

typedef void io_complete_fn(void *context, int io_error);

struct io_engine {
	void (*destroy)(struct io_engine *e);
	bool (*issue)(struct io_engine *e, enum dir d, int fd,
		      sector_t sb, sector_t se, void *data, void *context);
	bool (*wait)(struct io_engine *e, io_complete_fn fn);
	unsigned (*max_io)(struct io_engine *e);
};

struct io_engine *create_async_io_engine(void);

/*----------------------------------------------------------------*/

struct bcache;
struct block {
	/* clients may only access these three fields */
	int fd;
	uint64_t index;
	void *data;

	struct bcache *cache;
	struct dm_list list;
	struct dm_list hash;

	unsigned flags;
	unsigned ref_count;
	int error;
	enum dir io_dir;
};

/*
 * Ownership of engine passes.  Engine will be destroyed even if this fails.
 */
struct bcache *bcache_create(sector_t block_size, unsigned nr_cache_blocks,
			     struct io_engine *engine);
void bcache_destroy(struct bcache *cache);

enum bcache_get_flags {
	/*
	 * The block will be zeroed before get_block returns it.  This
	 * potentially avoids a read if the block is not already in the cache.
	 * GF_DIRTY is implicit.
	 */
	GF_ZERO = (1 << 0),

	/*
	 * Indicates the caller is intending to change the data in the block, a
	 * writeback will occur after the block is released.
	 */
	GF_DIRTY = (1 << 1)
};

typedef uint64_t block_address;

unsigned bcache_nr_cache_blocks(struct bcache *cache);
unsigned bcache_max_prefetches(struct bcache *cache);

/*
 * Use the prefetch method to take advantage of asynchronous IO.  For example,
 * if you wanted to read a block from many devices concurrently you'd do
 * something like this:
 *
 * dm_list_iterate_items (dev, &devices)
 * 	bcache_prefetch(cache, dev->fd, block);
 *
 * dm_list_iterate_items (dev, &devices) {
 *	if (!bcache_get(cache, dev->fd, block, &b))
 *		fail();
 *
 *	process_block(b);
 * }
 *
 * It's slightly sub optimal, since you may not run the gets in the order that
 * they complete.  But we're talking a very small difference, and it's worth it
 * to keep callbacks out of this interface.
 */
void bcache_prefetch(struct bcache *cache, int fd, block_address index);

/*
 * Returns true on success.
 */
bool bcache_get(struct bcache *cache, int fd, block_address index,
	        unsigned flags, struct block **result);
void bcache_put(struct block *b);

/*
 * flush() does not attempt to writeback locked blocks.  flush will fail
 * (return false), if any unlocked dirty data cannot be written back.
 */
bool bcache_flush(struct bcache *cache);

/*
 * Removes a block from the cache.  If the block is dirty it will be written
 * back first.  If the block is currently held a warning will be issued, and it
 * will not be removed.
 */
void bcache_invalidate(struct bcache *cache, int fd, block_address index);

/*
 * Invalidates all blocks on the given descriptor.  Call this before closing
 * the descriptor to make sure everything is written back.
 */
void bcache_invalidate_fd(struct bcache *cache, int fd);

/*
 * Prefetches the blocks neccessary to satisfy a byte range.
 */
void bcache_prefetch_bytes(struct bcache *cache, int fd, off_t start, size_t len);

/*
 * Reads and writes the bytes.  Returns false if errors occur.
 */
bool bcache_read_bytes(struct bcache *cache, int fd, off_t start, size_t len, void *data);
bool bcache_write_bytes(struct bcache *cache, int fd, off_t start, size_t len, void *data);
bool bcache_write_zeros(struct bcache *cache, int fd, off_t start, size_t len);

/*----------------------------------------------------------------*/

#endif