#include "compat.h"

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_ASM_PAGE_H
#include <asm/page.h>
#endif

#ifdef HAVE_SYS_SWAP_H
#include <sys/swap.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif

extern char *optarg;

typedef struct {
	char *pathname;
	size_t swapsize;
	int active;
} swap_t;

int daemonize(void) {
#ifndef DEBUG
	pid_t pid;

	chdir("/");

	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	setsid();

	pid = fork();

	if (pid > 0) {
		exit(EXIT_SUCCESS);
	}

	if (pid < 0) {
//		syslog(LOG_ERR, "Fork failed while daemonizing: %s", strerror(errno));
		exit(EXIT_FAILURE);
	}
#endif
	return(0);
}

int swapd_init_stats(void) {
#ifdef HAVE_LIBSTATGRAB
	/* Initialize the libstatgrab interface. */
	if (sg_init() != 0) {
		/* Failure. */
		return(0);
	}
#endif
	/* No initialization is needed for Linux proc interface. */

	/* Success! */
	return(1);
}

int64_t swapd_get_free_mem(void) {
	int64_t retval = -1;
#ifdef HAVE_LIBSTATGRAB
	sg_mem_stats *meminfo = NULL;
	sg_swap_stats *swapinfo = NULL;

	meminfo = sg_get_mem_stats();
	if (meminfo == NULL) {
		return(-1);
	}

	swapinfo = sg_get_swap_stats();
	if (swapinfo == NULL) {
		return(-1);
	}

	retval = meminfo->free + meminfo->cache + swapinfo->free;

	free(meminfo);
	free(swapinfo);

	return(retval);
#else
	FILE *meminfofd = NULL;
	char buf[128] = {0};
	char *buf_part = NULL, *buf_whole = NULL;
	uint64_t meminfo_part = 0, meminfo = 0;

	meminfofd = fopen("/proc/meminfo", "r");
	while (1) {
		fgets(buf, sizeof(buf) - 1, meminfofd);
		if (feof(meminfofd)) {
			break;
		}

		buf_whole = buf;
		buf_part = strsep(&buf_whole, ":");
		if (buf_part == NULL || buf_whole == NULL) {
			continue;
		}
		meminfo_part = strtoull(buf_whole, NULL, 10) * 1024;
		if (strcmp(buf_part, "MemFree") == 0 ||
		    strcmp(buf_part, "Buffers") == 0 ||
		    strcmp(buf_part, "Cached") == 0 ||
		    strcmp(buf_part, "SwapFree") == 0) {
			meminfo += meminfo_part;
		}
	}

	fclose(meminfofd);

	if (meminfo != 0) {
		retval = meminfo;
	}

	return(retval);
#endif
}

int swapd_call_mkswap(const char *swapfile) {
	char cmdline[8192] = {0};
	int fd;

	snprintf(cmdline, sizeof(cmdline) - 1, "mkswap \"%s\"", swapfile);
	SPOTVAR_S(cmdline);
	system(cmdline);

	fd = open(swapfile, O_RDONLY);
	fsync(fd);
	close(fd);

	return(0);
}

int swapd_swapon(const char *swapfile) {
	int swaponret = -1;

	swaponret = swapon(swapfile, 0);

	return(swaponret);
}

swap_t *swapd_mkswap(const char *swapdir, size_t swapsize, const char *pathname) {
	swap_t *retval = NULL;
#ifdef PATH_MAX
	char swapfilepath[PATH_MAX + 1] = {0};
#else
	char swapfilepath[1024] = {0};
#endif
#ifdef PAGE_SIZE
	char databuf[(PAGE_SIZE * 2)] = {0};
#else
	char databuf[4096] = {0};
#endif
	int64_t numbufs = 0, bufcnt = 0;
	size_t realswapsize = 0;
	int swapfd = -1;
	int swaponret = -1;

	if (pathname == NULL) {
		/* Create a temporary file. */
		snprintf(swapfilepath, sizeof(swapfilepath) - 1, "%s/.swapXXXXXX", swapdir);

		swapfd = mkstemp(swapfilepath);
		if (swapfd < 0) {
			return(NULL);
		}

		/* Fill the file with empty data. */
		numbufs = (swapsize / sizeof(databuf)) + 1;	
		realswapsize = numbufs * sizeof(databuf);

		for (bufcnt = 0; bufcnt < numbufs; bufcnt++) {
			write(swapfd, databuf, sizeof(databuf));
		}
		close(swapfd);

		/* Initialize the swap signature. */
		swapd_call_mkswap(swapfilepath);
	} else {
		strncpy(swapfilepath, pathname, sizeof(swapfilepath));
	}

	/* Start swaping. */
	swaponret = swapd_swapon(swapfilepath);
	if (swaponret < 0) {
		unlink(swapfilepath);
		return(NULL);
	}

	retval = malloc(sizeof(*retval));
	if (retval == NULL) {
		return(NULL);
	}

	retval->pathname = strdup(swapfilepath);
	retval->swapsize = realswapsize;
	retval->active = 1;

	if (retval->pathname == NULL) {
		free(retval);
		return(NULL);
	}

	return(retval);
}

int swapd_swapoff(swap_t *swapfile) {
	int swapoffret = -1;

	CHECKPOINT;

	if (swapfile == NULL) {
		return(-1);
	}

	swapoffret = swapoff(swapfile->pathname);

	if (swapoffret < 0) {
		swapfile->active = 0;
		return(0);
	}

	return(-1);
}

int swapd_cleanup(const char *swapdir) {
	DIR *dh = NULL;
	struct dirent *dent = NULL;

	chdir(swapdir);
	dh = opendir(".");

	if (dh == NULL) {
		return(-1);
	}

	while (1) {
		dent = readdir(dh);
		if (dent == NULL) {
			break;
		}
		if (strlen(dent->d_name) == 11 && strncmp(dent->d_name, ".swap", 5) == 0) {
			SPOTVAR_S(dent->d_name);
		}
	}

	closedir(dh);

	return(0);
}

int main(int argc, char **argv) {
	swap_t *swaps[1024], *swapinfo = NULL;
	int64_t freemem = -1;
	int64_t minfree = 89595904, maxfree = 0;
	size_t swapsize = (32 * 1024 * 1024);
	char *swapdir = "/var/tmp";
	char *swapfile = NULL;
	int ch = -1, i = 0;
	int active_swaps = 0;
	int swapoffret = -1;

	if (!swapd_init_stats()) {
		return(EXIT_FAILURE);
	}

	for (i = 0; i < (sizeof(swaps) / sizeof(swaps[0])); i++) {
		swaps[i] = NULL;
	}

	/* Process config file.. */
	/* Process arguments.. */
	while ((ch = getopt(argc, argv, "m:s:d:M:")) != -1) {
		switch (ch) {
			case 'M':
				maxfree = strtoull(optarg, NULL, 10);
				SPOTVAR_LLU(maxfree);
				break;
			case 'm':
				minfree = strtoull(optarg, NULL, 10);
				SPOTVAR_LLU(minfree);
				break;
			case 's':
				swapsize = strtoull(optarg, NULL, 10);
				break;
			case 'd':
				swapdir = strdup(optarg);
				break;
		}
	}

	/* Fix illegal values. */
	if (maxfree < (minfree + swapsize)) {
		CHECKPOINT;
		maxfree = 0;
	}

	daemonize();

	swapd_cleanup(swapdir);

	while (1) {
		sleep(5);

		freemem = swapd_get_free_mem();
		if (freemem == -1) {
			continue;
		}

		SPOTVAR_LL(freemem);

		if (freemem < minfree) {
			swapfile = NULL;
			for (i = 0; i < (sizeof(swaps) / sizeof(swaps[0])); i++) {
				if (swaps[i] != NULL) {
					if (swaps[i]->active == 0 && swaps[i]->pathname != NULL) {
						swapfile = strdup(swaps[i]->pathname);
						free(swaps[i]->pathname);
						free(swaps[i]);
						swaps[i] = NULL;
						break;
					}
				}
			}

			swapinfo = swapd_mkswap(swapdir, swapsize, swapfile);

			if (swapfile != NULL) {
				free(swapfile);
			}

			if (swapinfo != NULL) {
				for (i = 0; i < (sizeof(swaps) / sizeof(swaps[0])); i++) {
					if (swaps[i] == NULL) {
						swaps[i] = swapinfo;
						break;
					}
				}
				active_swaps++;
			}
		}

		if (freemem > maxfree && maxfree > 0) {
			SPOTVAR_I(active_swaps);
			if (active_swaps > 0) {
				for (i = 0; i < (sizeof(swaps) / sizeof(swaps[0])); i++) {
					if (swaps[i] != NULL) {
						if (swaps[i]->active == 0) continue;
						swapoffret = swapd_swapoff(swaps[i]);
						if (swapoffret >= 0) {
							active_swaps--;
							break;
						}
					}
				}
			}
		}

	}

	return(EXIT_FAILURE);
}
