/*
 * Copyright(c) 2015-2017 Intel Corporation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <linux/fiemap.h>
#include <setjmp.h>

#define fail() fprintf(stderr, "%s: failed at: %d\n", __func__, __LINE__)

static sigjmp_buf sj_env;
static int sig_count;
/* buf is global in order to avoid gcc memcpy optimization */
static void *buf;

static void sigbus_hdl(int sig, siginfo_t *siginfo, void *ptr)
{
	fprintf(stderr, "** Received a SIGBUS **\n");
	sig_count++;
	siglongjmp(sj_env, 1);
}

static int test_dax_read_err(int fd)
{
	void *base;
	int rc = 0;

	if (fd < 0) {
		fail();
		return -ENXIO;
	}

	if (posix_memalign(&buf, 4096, 4096) != 0)
		return -ENOMEM;

	base = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if (base == MAP_FAILED) {
		perror("mmap");
		rc = -ENXIO;
		goto err_mmap;
	}

	if (sigsetjmp(sj_env, 1)) {
		if (sig_count == 1) {
			fprintf(stderr, "Failed to read from mapped file\n");
			free(buf);
			if (base) {
				if (munmap(base, 4096) < 0) {
					fail();
					return 1;
				}
			}
			return 1;
		}
		return sig_count;
	}

	/* read a page through DAX (should fail due to a bad block) */
	memcpy(buf, base, 4096);

 err_mmap:
	free(buf);
	return rc;
}

/* TODO: disabled till we get clear-on-write in the kernel */
#if 0
static int test_dax_write_clear(int fd)
{
	void *buf;
	int rc = 0;

	if (fd < 0) {
		fail();
		return -ENXIO;
	}

	if (posix_memalign(&buf, 4096, 4096) != 0)
		return -ENOMEM;
	memset(buf, 0, 4096);

	/*
	 * Attempt to write zeroes to the first page of the file using write()
	 * This should clear the pmem errors/bad blocks
	 */
	printf("Attempting to write\n");
	if (write(fd, buf, 4096) < 0)
		rc = errno;

	free(buf);
	return rc;
}
#endif

int main(int argc, char *argv[])
{
	int fd, rc;
	struct sigaction act;

	if (argc < 1)
		return -EINVAL;

	memset(&act, 0, sizeof(act));
	act.sa_sigaction = sigbus_hdl;
	act.sa_flags = SA_SIGINFO;

	if (sigaction(SIGBUS, &act, 0)) {
		fail();
		return 1;
	}

	fd = open(argv[1], O_RDWR | O_DIRECT);

	/* Start the test. First, we do an mmap-read, and expect it to fail */
	rc = test_dax_read_err(fd);
	if (rc == 0) {
		fprintf(stderr, "Expected read to fail, but it succeeded\n");
		rc = -ENXIO;
		goto out;
	}
	if (rc > 1) {
		fprintf(stderr, "Received a second SIGBUS, exiting.\n");
		rc = -ENXIO;
		goto out;
	}
	printf("  mmap-read failed as expected\n");
	rc = 0;

	/* Next, do a regular (O_DIRECT) write() */
	/* TODO: Disable this till we have clear-on-write in the kernel
	 * rc = test_dax_write_clear(fd);
	 *
	 * if (rc)
	 *	perror("write");
	 */

 out:
	if (fd >= 0)
		close(fd);
	return rc;
}
