#include "spadfs.h"

#ifdef CHECK_BUFFER_LEAKS
static void spadfs_track_buffer(SPADFS *fs, struct buffer_head *bh, const char *msg);
#else
#define spadfs_track_buffer(fs, bh, msg) do { } while (0)
#endif

void *SPADFS_READ_SECTOR(SPADFS *fs, __d_off secno, struct buffer_head **bhp, const char *msg)
{
	cond_resched();
	if (__likely((*bhp = sb_bread(fs->s, secno >> fs->sectors_per_block_bits)) != NULL)) {
		spadfs_track_buffer(fs, *bhp, msg);
		return (char *)(*bhp)->b_data + (((unsigned)secno << 9) & ((512 << fs->sectors_per_block_bits) - 512));
	}
	SPADFS_ERROR(fs, TXFLAGS_IO_READ_ERROR, "READ ERROR, BLOCK %LX AT %s", (unsigned long long)secno, msg);
	return ERR_PTR(-EIO);
}

void *SPADFS_GET_NEW_SECTOR(SPADFS *fs, __d_off secno, struct buffer_head **bhp, const char *msg)
{
	cond_resched();
	if (__unlikely((unsigned)secno & ((1 << fs->sectors_per_block_bits) - 1))) {
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "UNALIGNED GET NEW SECTOR %LX AT %s", (unsigned long long)secno, msg);
		return ERR_PTR(-EFSERROR);
	}
	if (__likely((*bhp = sb_getblk(fs->s, secno >> fs->sectors_per_block_bits)) != NULL)) {
		if (!buffer_uptodate(*bhp)) wait_on_buffer(*bhp);
		set_buffer_uptodate(*bhp);
		spadfs_track_buffer(fs, *bhp, msg);
		return (*bhp)->b_data;
	}
	SPADFS_ERROR(fs, TXFLAGS_IO_READ_ERROR, "CAN'T GET NEW SECTOR %LX AT %s", (unsigned long long)secno, msg);
	return ERR_PTR(-ENOMEM);
}

#ifdef CHECK_BUFFER_LEAKS

struct tracked_buffer {
	struct hlist_node list;
	sector_t block;
	const char *msg;
};

static void spadfs_track_buffer(SPADFS *fs, struct buffer_head *bh, const char *msg)
{
	struct tracked_buffer *tb;
	tb = kmalloc(sizeof(struct tracked_buffer), GFP_NOFS);
	down(&fs->buffer_track_lock);
	if (__likely(tb != NULL)) {
		tb->block = bh->b_blocknr;
		tb->msg = msg;
		hlist_add_head(&tb->list, &fs->buffer_list);
	} else {
		fs->buffer_oom_events++;
	}
	up(&fs->buffer_track_lock);
}

void spadfs_brelse(SPADFS *fs, struct buffer_head *bh)
{
	spadfs_drop_reference(fs, bh);
	__brelse(bh);
}

void spadfs_drop_reference(SPADFS *fs, struct buffer_head *bh)
{
	struct tracked_buffer *tb;
	struct hlist_node *n;
	down(&fs->buffer_track_lock);
	hlist_for_each_entry(tb, n, &fs->buffer_list, list) {
		if (tb->block == bh->b_blocknr) {
			hlist_del(&tb->list);
			kfree(tb);
			goto found;
		}
	}
	BUG_ON(!fs->buffer_oom_events);
	fs->buffer_oom_events--;
	found:
	up(&fs->buffer_track_lock);
}

void spadfs_buffer_leaks_init(SPADFS *fs)
{
	init_MUTEX(&fs->buffer_track_lock);
	INIT_HLIST_HEAD(&fs->buffer_list);
	fs->buffer_oom_events = 0;
}

void spadfs_buffer_leaks_done(SPADFS *fs)
{
	while (__unlikely(!hlist_empty(&fs->buffer_list))) {
		struct tracked_buffer *tb = list_entry(fs->buffer_list.first, struct tracked_buffer, list);
		printk(KERN_ERR "SPADFS INTERNAL ERROR: BUFFER %llX LEAKED AT %s\n", (unsigned long long)tb->block, tb->msg);
		hlist_del(&tb->list);
		kfree(tb);
	}
	if (__unlikely(fs->buffer_oom_events != 0)) {
		printk(KERN_ERR "SPADFS INTERNAL ERROR: %lX UNKNOWN BUFFER LEAKED\n", fs->buffer_oom_events);
	}
}

#endif
