/*
 * This software is copyright (c) 2008, 2009 by Leon Timmermans <leont@cpan.org>.
 *
 * This is free software; you can redistribute it and/or modify it under
 * the same terms as perl itself.
 *
 */

#if defined(linux) && !defined(_GNU_SOURCE)
#		define _GNU_SOURCE
#endif

#include <assert.h>
#include "mmap-compat.c"

#ifndef MIN
#	define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

#define PERL_NO_GET_CONTEXT
#define PERL_REENTR_API 1
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "perliol.h"
#define NEED_mg_findext
#define NEED_sv_unmagicext
#include "ppport.h"

#ifndef SvPV_free
#	define SvPV_free(arg) sv_setpvn_mg(arg, NULL, 0);
#endif

#ifndef SV_CHECK_THINKFIRST_COW_DROP
#define SV_CHECK_THINKFIRST_COW_DROP(sv) SV_CHECK_THINKFIRST(sv)
#endif

struct mmap_info {
	void* real_address;
	void* fake_address;
	size_t real_length;
	size_t fake_length;
	int flags;
#ifdef USE_ITHREADS
	perl_mutex count_mutex;
	perl_mutex data_mutex;
	PerlInterpreter* owner;
	perl_cond cond;
	int count;
#endif
};

#define die_sys(format) Perl_croak(aTHX_ format, strerror(errno))

static void reset_var(SV* var, struct mmap_info* info) {
	SvPVX(var) = info->fake_address;
	SvLEN(var) = 0;
	SvCUR(var) = info->fake_length;
	SvPOK_only_UTF8(var);
}

static void S_mmap_fixup(pTHX_ SV* var, struct mmap_info* info, const char* string, STRLEN len) {
	if (ckWARN(WARN_SUBSTR)) {
		Perl_warn(aTHX_ "Writing directly to a memory mapped file is not recommended");
		if (SvCUR(var) > info->fake_length)
			Perl_warn(aTHX_ "Truncating new value to size of the memory map");
	}

	if (string && len)
		Copy(string, info->fake_address, MIN(len, info->fake_length), char);
	SV_CHECK_THINKFIRST_COW_DROP(var);
	if (SvROK(var))
		sv_unref_flags(var, SV_IMMEDIATE_UNREF);
	if (SvPOK(var))
		SvPV_free(var);
	reset_var(var, info);
}
#define mmap_fixup(var, info, string, len) S_mmap_fixup(aTHX_ var, info, string, len)

static int mmap_write(pTHX_ SV* var, MAGIC* magic) {
	struct mmap_info* info = (struct mmap_info*) magic->mg_ptr;
	if (info->real_length) {
		if (!SvOK(var))
			mmap_fixup(var, info, NULL, 0);
		else if (!SvPOK(var)) {
			STRLEN len;
			const char* string = SvPV(var, len);
			mmap_fixup(var, info, string, len);
		}
		else if (SvPVX(var) != info->fake_address)
			mmap_fixup(var, info, SvPVX(var), SvCUR(var));
		else {
			if (ckWARN(WARN_SUBSTR) && SvCUR(var) != info->fake_length) {
				Perl_warn(aTHX_ "Writing directly to a memory mapped file is not recommended");
				SvCUR(var) = info->fake_length;
			}
			SvPOK_only_UTF8(var);
		}
	}
	else {
		if (!SvPOK(var) || sv_len(var) != 0) {
			sv_setpvn(var, "", 0);
			if (ckWARN(WARN_SUBSTR))
				Perl_warn(aTHX_ "Can't overwrite an empty map");
		}
		SvPOK_only_UTF8(var);
	}
	return 0;
}

static int mmap_clear(pTHX_ SV* var, MAGIC* magic) {
	Perl_die(aTHX_ "Can't clear a mapped variable");
	return 0;
}

static int mmap_free(pTHX_ SV* var, MAGIC* magic) {
	struct mmap_info* info = (struct mmap_info*) magic->mg_ptr;
#ifdef USE_ITHREADS
	MUTEX_LOCK(&info->count_mutex);
	if (--info->count == 0) {
		if (info->real_length && munmap(info->real_address, info->real_length) == -1)
			die_sys("Could not unmap: %s");
		COND_DESTROY(&info->cond);
		MUTEX_DESTROY(&info->data_mutex);
		MUTEX_UNLOCK(&info->count_mutex);
		MUTEX_DESTROY(&info->count_mutex);
		PerlMemShared_free(info);
	}
	else {
		if (info->real_length && msync(info->real_address, info->real_length, MS_ASYNC) == -1)
			die_sys("Could not sync: %s");
		MUTEX_UNLOCK(&info->count_mutex);
	}
#else
	if (info->real_length && munmap(info->real_address, info->real_length) == -1)
		die_sys("Could not unmap: %s");
	PerlMemShared_free(info);
#endif 
	SvREADONLY_off(var);
	SvPVX(var) = NULL;
	SvCUR(var) = 0;
	return 0;
}

#ifdef USE_ITHREADS
static int mmap_dup(pTHX_ MAGIC* magic, CLONE_PARAMS* param) {
	struct mmap_info* info = (struct mmap_info*) magic->mg_ptr;
	MUTEX_LOCK(&info->count_mutex);
	assert(info->count);
	++info->count;
	MUTEX_UNLOCK(&info->count_mutex);
	return 0;
}
#else
#define mmap_dup 0
#endif

#ifdef MGf_LOCAL
static int mmap_local(pTHX_ SV* var, MAGIC* magic) {
	Perl_croak(aTHX_ "Can't localize file map");
}
#define mmap_local_tail , mmap_local
#else
#define mmap_local_tail
#endif

static const MGVTBL mmap_table  = { 0, mmap_write, 0, mmap_clear, mmap_free, 0, mmap_dup mmap_local_tail };

static Off_t S_sv_to_offset(pTHX_ SV* var) {
#if IV_SIZE >= 8
	return (Off_t)SvUV(var);
#else
	return (Off_t)floor(SvNV(var) + 0.5); /* hic sunt dracones */
#endif
}
#define sv_to_offset(var) S_sv_to_offset(aTHX_ var)

static void check_new_variable(pTHX_ SV* var) {
	if (SvTYPE(var) > SVt_PVMG && SvTYPE(var) != SVt_PVLV)
		Perl_croak(aTHX_ "Trying to map into a nonscalar!\n");
	SV_CHECK_THINKFIRST_COW_DROP(var);
	if (SvREADONLY(var))
		Perl_croak(aTHX_ "%s", PL_no_modify);
	if (SvMAGICAL(var) && mg_findext(var, PERL_MAGIC_ext, &mmap_table))
		sv_unmagicext(var, PERL_MAGIC_ext, (MGVTBL*)&mmap_table);
	if (SvROK(var))
		sv_unref_flags(var, SV_IMMEDIATE_UNREF);
	if (SvNIOK(var))
		SvNIOK_off(var);
	if (SvPOK(var)) 
		SvPV_free(var);
	SvUPGRADE(var, SVt_PVMG);
}

static void* do_mapping(pTHX_ size_t length, int prot, int flags, int fd, Off_t offset) {
	void* address;
	address = mmap(0, length, prot, flags | MAP_VARIABLE, fd, offset);
	if (address == MAP_FAILED)
		die_sys("Could not map: %s");
	return address;
}

static void S_set_mmap_info(pTHX_ struct mmap_info* magical, void* address, size_t length, ptrdiff_t correction) {
	magical->real_address = address;
	magical->fake_address = (char*)address + correction;
	magical->real_length = length + correction;
	magical->fake_length = length;
#ifdef USE_ITHREADS
	MUTEX_INIT(&magical->count_mutex);
	MUTEX_INIT(&magical->data_mutex);
	COND_INIT(&magical->cond);
	magical->count = 1;
#endif
}
#define set_mmap_info(magical, addres, length, correction) S_set_mmap_info(aTHX_ magical, addres, length, correction)

static struct mmap_info* initialize_mmap_info(pTHX_ void* address, size_t length, ptrdiff_t correction, int flags) {
	struct mmap_info* magical = PerlMemShared_malloc(sizeof *magical);
	set_mmap_info(magical, address, length, correction);
	magical->flags = flags;
	return magical;
}

static void add_magic(pTHX_ SV* var, struct mmap_info* magical, int writable, int utf8) {
	MAGIC* magic = sv_magicext(var, NULL, PERL_MAGIC_ext, &mmap_table, (const char*) magical, 0);
#ifdef MGf_LOCAL
	magic->mg_flags |= MGf_LOCAL;
#endif
#ifdef USE_ITHREADS
	magic->mg_flags |= MGf_DUP;
#endif
	SvTAINTED_on(var);
	if (utf8 && !sv_utf8_decode(var))
		Perl_croak(aTHX_ "Invalid utf8 in memory mapping");
	if (!writable)
		SvREADONLY_on(var);
}

static int _is_mappable(pTHX_ int fd) {
	Stat_t info;
	return Fstat(fd, &info) == 0 && (S_ISREG(info.st_mode) || S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
}

#define is_mappable(fd) _is_mappable(aTHX_ fd)

static struct mmap_info* S_get_mmap_magic(pTHX_ SV* var, const char* funcname) {
	MAGIC* magic;
	if (!SvMAGICAL(var) || (magic = mg_findext(var, PERL_MAGIC_ext, &mmap_table)) == NULL)
		Perl_croak(aTHX_ "Could not %s: this variable is not memory mapped", funcname);
	return (struct mmap_info*) magic->mg_ptr;
}
#define get_mmap_magic(var, funcname) S_get_mmap_magic(aTHX_ var, funcname)

#ifdef USE_ITHREADS
static void magic_end(pTHX_ void* pre_info) {
	struct mmap_info* info = (struct mmap_info*) pre_info;
	info->owner = NULL;
	MUTEX_UNLOCK(&info->data_mutex);
}
#endif

typedef struct { const char* key; size_t length; int value; } map[];

static map prots = {
	{ STR_WITH_LEN("<"), PROT_READ },
	{ STR_WITH_LEN("+<"), PROT_READ | PROT_WRITE },
	{ STR_WITH_LEN(">"), PROT_WRITE },
	{ STR_WITH_LEN("+>"), PROT_READ | PROT_WRITE },
};

static int S_protection_pvn(pTHX_ const char* mode, size_t mode_len) {
	int i;
	for (i = 0; i < sizeof prots / sizeof *prots; ++i) {
		if (prots[i].length == mode_len && strnEQ(mode, prots[i].key, mode_len))
			return prots[i].value;
	}
	Perl_croak(aTHX_ "No such mode '%s' known", mode);
}
#define protection_pvn(mode, mode_len) S_protection_pvn(aTHX_ mode, mode_len)

static int S_protection_sv(pTHX_ SV* mode_sv) {
	STRLEN mode_len;
	const char* mode = SvPV(mode_sv, mode_len);
	const char* end = memchr(mode, ':', mode_len);
	return protection_pvn(mode, end ? end - mode : mode_len);
}
#define protection_sv(mode) S_protection_sv(aTHX_ mode)

#define MAP_CONSTANT(cons) newCONSTSUB(stash, #cons, newSVuv(cons))
#define ADVISE_CONSTANT(key, value) hv_store(advise_constants, key, sizeof key - 1, newSVuv(value), 0)

#define EMPTY_MAP(info) ((info)->real_length == 0)

static void S_boot(pTHX) {
	HV* stash = get_hv("File::Map::", FALSE);
	HV* advise_constants = newHV();

	MAP_CONSTANT(PROT_NONE);
	MAP_CONSTANT(PROT_READ);
	MAP_CONSTANT(PROT_WRITE);
	MAP_CONSTANT(PROT_EXEC);
	MAP_CONSTANT(MAP_ANONYMOUS);
	MAP_CONSTANT(MAP_SHARED);
	MAP_CONSTANT(MAP_PRIVATE);
	MAP_CONSTANT(MAP_ANON);
	MAP_CONSTANT(MAP_FILE);
	/**/
	
	hv_store(PL_modglobal, "File::Map::ADVISE_CONSTANTS", 27, (SV*)advise_constants, 0);
	ADVISE_CONSTANT("normal", MADV_NORMAL);
	ADVISE_CONSTANT("random", MADV_RANDOM);
	ADVISE_CONSTANT("sequential", MADV_SEQUENTIAL);
	ADVISE_CONSTANT("willneed", MADV_WILLNEED);
	ADVISE_CONSTANT("dontneed", MADV_DONTNEED);
	/* Linux specific advices */
#ifdef MADV_REMOVE
	ADVISE_CONSTANT("remove", MADV_REMOVE);
#endif
#ifdef MADV_DONTFORK
	ADVISE_CONSTANT("dontfork", MADV_DONTFORK);
#endif
#ifdef MADV_DOFORK
	ADVISE_CONSTANT("dofork", MADV_DOFORK);
#endif
#ifdef MADV_MERGEABLE
	ADVISE_CONSTANT("mergeable", MADV_MERGEABLE);
#endif
#ifdef MADV_UNMERGEABLE
	ADVISE_CONSTANT("unmergeable", MADV_UNMERGEABLE);
#endif
	/* BSD, Mac OS X & Solaris specific advice */
#ifdef MADV_FREE
	ADVISE_CONSTANT("free", MADV_FREE);
#endif
	/* FreeBSD specific advices */
#ifdef MADV_NOSYNC
	ADVISE_CONSTANT("nosync", MADV_NOSYNC);
#endif
#ifdef MADV_AUTOSYNC
	ADVISE_CONSTANT("autosync", MADV_AUTOSYNC);
#endif
#ifdef MADV_NOCORE
	ADVISE_CONSTANT("nocore", MADV_NOCORE);
#endif
#ifdef MADV_CORE
	ADVISE_CONSTANT("core", MADV_CORE);
#endif
#ifdef MADV_PROTECT
	ADVISE_CONSTANT("protect", MADV_PROTECT);
#endif
#ifdef MADV_SPACEAVAIL
	ADVISE_CONSTANT("spaceavail", MADV_SPACEAVAIL);
#endif
}
#define boot() S_boot(aTHX)

#if PTRSIZE == 8 && (defined(WIN32) || defined(__CYGWIN__))
#ifndef ULLONG_MAX
#define PTR_MAX _UI64_MAX /* MS Platform SDK crt */
#else
#define PTR_MAX ULLONG_MAX
#endif
#else
#define PTR_MAX ULONG_MAX
#endif

void S_mmap_impl(pTHX_ SV* var, size_t length, int prot, int flags, int fd, Off_t offset, int utf8) {
	check_new_variable(aTHX_ var);

	ptrdiff_t correction = offset % page_size();
	void* address;
	struct mmap_info* magical;
	if (length > PTR_MAX - correction)
		Perl_croak(aTHX_ "can't map: length + offset overflows");

	if (length)
		address = do_mapping(aTHX_ length + correction, prot, flags, fd, offset - correction);
	else {
		if (!is_mappable(fd)) {
			errno = EACCES;
			die_sys("Could not map: %s");
		}
		address = "";
		correction = 0;
	}

	magical = initialize_mmap_info(aTHX_ address, length, correction, flags);
	reset_var(var, magical);
	SvSETMAGIC(var);
	add_magic(aTHX_ var, magical, prot & PROT_WRITE, utf8);
}
#define mmap_impl(var, length, prot, flags, fd, offset, utf8) S_mmap_impl(aTHX_ var, length, prot, flags, fd, offset, utf8)

static const map mappable = {
	{ STR_WITH_LEN("unix"), 1 },
	{ STR_WITH_LEN("perlio"), 1 },
	{ STR_WITH_LEN("crlf"), 1 },
	{ STR_WITH_LEN("stdio"), 1 },
	{ STR_WITH_LEN("flock"), 1 },
	{ STR_WITH_LEN("creat"), 1 },
	{ STR_WITH_LEN("mmap"), 1 },
};

static int S_map_get(pTHX_ const map table, size_t table_size, const char* name, int fallback) {
	int i;
	for (i = 0; i < table_size; ++i) {
		if (strEQ(name, table[i].key))
			return table[i].value;
	}
	return fallback;
}
#define map_get(table, name, default) S_map_get(aTHX_ table, sizeof table / sizeof *table, name, default)

int S_check_layers(pTHX_ PerlIO* fh) {
	PerlIO* current;
	if (PerlIO_fileno(fh) < 0)
		Perl_croak(aTHX_ "Can't map fake filehandle");
	for (current = fh; *current; current = PerlIONext(current)) {
		if (!map_get(mappable, (*current)->tab->name, 0) || (*current)->flags & PERLIO_F_CRLF)
			Perl_croak(aTHX_ "Shouldn't map non-binary filehandle");
	}
	return (*fh)->flags & PERLIO_F_UTF8;
}
#define check_layers(fh) S_check_layers(aTHX_ fh)

size_t S_get_length(pTHX_ PerlIO* fh, Off_t offset, SV* length_sv) {
	Stat_t info;
	Fstat(PerlIO_fileno(fh), &info);
	size_t length = SvOK(length_sv) ? SvIV(length_sv) : info.st_size - offset;
	size_t end = offset + length;
	if (offset < 0 || end > info.st_size && !S_ISCHR(info.st_mode))
		Perl_croak(aTHX_ "Window (%ld,%lu) is outside the file", offset, length);
	return length;
}
#define get_length(fh, offset, length) S_get_length(aTHX_ fh, offset, length)

#define READONLY sv_2mortal(newSVpvs("<"))
#define undef &PL_sv_undef

void S_map_handle(pTHX_ SV* var, PerlIO* fh, SV* mode, Off_t offset, SV* length_sv) {
	int utf8 = check_layers(fh);
	size_t length = get_length(fh, offset, length_sv);
	mmap_impl(var, length, protection_sv(mode), MAP_SHARED | MAP_FILE, PerlIO_fileno(fh), offset, utf8);
}
#define map_handle(var, fh, mode, offset, length) S_map_handle(aTHX_ var, fh, mode, offset, length)

void S_map_file(pTHX_ SV* var, SV* filename, SV* mode, Off_t offset, SV* length_sv) {
	STRLEN mode_len;
	const char* mode_raw = SvPV(mode, mode_len);
	if (memchr(mode_raw, ':', mode_len) == NULL) {
		SV* newmode = sv_2mortal(newSVsv(mode));
		sv_catpvs(newmode, ":raw");
		mode_raw = SvPV(newmode, mode_len);
	}
	GV* gv = MUTABLE_GV(sv_2mortal(newSV_type(SVt_NULL)));
	gv_init_pvn(gv, CopSTASH(PL_curcop),  "__ANONIO__", 10, GV_ADDMULTI);
	if (!do_openn(gv, mode_raw, mode_len, 0, 0, 0, NULL, &filename, 1))
		Perl_croak(aTHX_ "Couldn't open file %s: %s", SvPV_nolen(filename), strerror(errno));
	map_handle(var, IoIFP(GvIO(gv)), mode, offset, length_sv);
}
#define map_file(var, filename, mode, offset, length) S_map_file(aTHX_ var, filename, mode, offset, length)

static const map flags = {
	{ STR_WITH_LEN("shared") , MAP_SHARED },
	{ STR_WITH_LEN("private"), MAP_PRIVATE },
};

void S_map_anonymous(pTHX_ SV* var, size_t length, const char* flag_name) {
	int flag = map_get(flags, flag_name, -1);
	if (flag == -1)
		Perl_croak(aTHX_ "No such flag '%s'", flag_name);
	if (length == 0)
		Perl_croak(aTHX_ "Zero length specified for anonymous map");
	mmap_impl(var, length, PROT_READ | PROT_WRITE, flag | MAP_ANONYMOUS, -1, 0, 0);
}
#define map_anonymous(var, length, flag_name) S_map_anonymous(aTHX_ var, length, flag_name)

void S_sys_map(pTHX_ SV* var, size_t length, int protection, int flags, SV* fh, Off_t offset) {
	if (flags & MAP_ANONYMOUS)
		mmap_impl(var, length, protection, flags, -1, offset, 0);
	else {
		PerlIO* pio = IoIFP(sv_2io(fh)); // XXX error check
		int utf8 = check_layers(pio);
		int fd = PerlIO_fileno(pio);
		mmap_impl(var, length, protection, flags, fd, offset, utf8);
	}
}
#define sys_map(var, length, protection, flags, fh, offset) S_sys_map(aTHX_ var, length, protection, flags, fh, offset)

void S_sync(pTHX_ SV* var, bool sync) {
	struct mmap_info* info = get_mmap_magic(var, "sync");
	if (EMPTY_MAP(info))
		return;
	if (SvREADONLY(var) && ckWARN(WARN_IO))
		Perl_warn(aTHX_ "Syncing a readonly map makes no sense");
	if (msync(info->real_address, info->real_length, sync ? MS_SYNC : MS_ASYNC ) == -1)
		die_sys("Could not sync: %s");
}
#define sync(var, sync) S_sync(aTHX_ var, sync)

#ifdef __linux__
void S_remap(pTHX_ SV* var, size_t new_size) {
	struct mmap_info* info = get_mmap_magic(var, "remap");
	ptrdiff_t correction = info->real_length - info->fake_length;
	void* new_address;
CODE:
#ifdef USE_ITHREADS
	if (info->count != 1)
		Perl_croak(aTHX_ "Can't remap a shared mapping");
#endif
	if (EMPTY_MAP(info))
		Perl_croak(aTHX_ "Can't remap empty map"); /* XXX */
	if (new_size == 0)
		Perl_croak(aTHX_ "Can't remap to zero");
	if ((info->flags & (MAP_ANONYMOUS|MAP_SHARED)) == (MAP_ANONYMOUS|MAP_SHARED))
		Perl_croak(aTHX_ "Can't remap a shared anonymous mapping");
	if ((new_address = mremap(info->real_address, info->real_length, new_size + correction, MREMAP_MAYMOVE)) == MAP_FAILED)
		die_sys("Could not remap: %s");
	set_mmap_info(info, new_address, new_size, correction);
	reset_var(var, info);
}
#define remap(var, new_size) S_remap(aTHX_ var, new_size)
#endif

void S_unmap(pTHX_ SV* var) {
	get_mmap_magic(var, "unmap");
	sv_unmagicext(var, PERL_MAGIC_ext, (MGVTBL*)&mmap_table);
}
#define unmap(var) S_unmap(aTHX_ var)

void S_pin(pTHX_ struct mmap_info* info) {
#ifndef VMS
	if (EMPTY_MAP(info))
		return;
	if (mlock(info->real_address, info->real_length) == -1)
		die_sys("Could not pin: %s");
#else
	Perl_croak(aTHX_ "pin not implemented on VMS");
#endif
}
#define pin(var) S_pin(aTHX_ var)

void S_unpin(pTHX_ struct mmap_info* info) {
#ifndef VMS
	if (EMPTY_MAP(info))
		return;
	if (munlock(info->real_address, info->real_length) == -1)
		die_sys("Could not unpin: %s");
#else
	Perl_croak(aTHX_ "unpin not implemented on VMS");
#endif
}
#define unpin(var) S_unpin(aTHX_ var)

void S_advise(pTHX_ struct mmap_info* info, SV* name) {
	HV* constants = (HV*) *hv_fetch(PL_modglobal, "File::Map::ADVISE_CONSTANTS", 27, 0);
	HE* value = hv_fetch_ent(constants, name, 0, 0);

	if (EMPTY_MAP(info))
		return;
	if (!value) {
		if (ckWARN(WARN_PORTABLE))
			Perl_warn(aTHX_ "Unknown advice '%s'", SvPV_nolen(name));
	}
	else if (madvise(info->real_address, info->real_length, SvUV(HeVAL(value))) == -1)
		die_sys("Could not advice: %s");
}
#define advise(var, name) S_advise(aTHX_ var, name)

void S_protect(pTHX_ SV* var, SV* prot) {
	struct mmap_info* info = get_mmap_magic(var, "protect");
	int prot_val = SvIOK(prot) ? SvIV(prot) : protection_sv(prot);
	if (!EMPTY_MAP(info))
		mprotect(info->real_address, info->real_length, prot_val);
	if (prot_val & PROT_WRITE)
		SvREADONLY_off(var);
	else
		SvREADONLY_on(var);
}
#define protect(var, prot) S_protect(aTHX_ var, prot)

void S_lock_map(pTHX_ struct mmap_info* info) {
#ifdef USE_ITHREADS
	LEAVE;
	SAVEDESTRUCTOR_X(magic_end, info);
	MUTEX_LOCK(&info->data_mutex);
	info->owner = aTHX;
	ENTER;
#endif
}
#define lock_map(var) S_lock_map(aTHX_ var)

#ifdef USE_ITHREADS
SV* S_wait_until(pTHX_ SV* block, SV* var) {
	struct mmap_info* info = get_mmap_magic(var, "wait_until");
	if (info->owner != aTHX)
		Perl_croak(aTHX_ "Trying to wait on an unlocked map");
	SAVESPTR(DEFSV);
	DEFSV = var;
	dSP;
	while (1) {
		PUSHMARK(SP);
		call_sv(block, G_SCALAR | G_NOARGS);
		SPAGAIN;
		SV* result = POPs;
		if (SvTRUE(result))
			return SvREFCNT_inc(result);
		COND_WAIT(&info->cond, &info->data_mutex);
	}
}
#define wait_until(block, var) S_wait_until(aTHX_ block, var)

void S_notify(pTHX_ struct mmap_info* info) {
	if (info->owner != aTHX)
		Perl_croak(aTHX_ "Trying to notify on an unlocked map");
	COND_SIGNAL(&info->cond);
}
#define notify(var) S_notify(aTHX_ var)

void S_broadcast(pTHX_ struct mmap_info* info) {
	if (info->owner != aTHX)
		Perl_croak(aTHX_ "Trying to broadcast on an unlocked map");
	COND_BROADCAST(&info->cond);
}
#define broadcast(var) S_broadcast(aTHX_ var)
#endif

MODULE = File::Map				PACKAGE = File::Map

PROTOTYPES: DISABLED

BOOT:
    boot();

void map_file(SV* var, SV* filename, SV* mode = READONLY, Off_t offset = 0, SV* length = undef)

void map_handle(SV* var, PerlIO* fh, SV* mode = READONLY, Off_t offset = 0, SV* length = undef)

void map_anonymous(SV* var, size_t length, const char* flag_name = "shared")

void sys_map(SV* var, size_t length, int protection, int flags, SV* fh = undef, Off_t offset = 0)

void sync(SV* var, bool sync = TRUE)

#ifdef __linux__
void remap(SV* var, size_t new_size)

#endif

void unmap(SV* var)

void pin(struct mmap_info* var)

void unpin(struct mmap_info* var)

void advise(struct mmap_info* var, SV* name)

void protect(SV* var, SV* prot)

void lock_map(struct mmap_info* var)

#ifdef USE_ITHREADS
SV* wait_until(SV* block, SV* var)
	PROTOTYPE: &@

void notify(struct mmap_info* var)

void broadcast(struct mmap_info* var)

#endif /* USE ITHREADS */