#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <pthread.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <libudev.h>
#include <mpath_persist.h>

#include "util.h"
#include "debug.h"
#include "memory.h"
#include "checkers.h"
#include "vector.h"
#include "structs.h"
#include "log.h"

size_t
strchop(char *str)
{
	int i;

	for (i=strlen(str)-1; i >=0 && isspace(str[i]); --i) ;
	str[++i] = '\0';
	return strlen(str);
}

int
basenamecpy (const char *src, char *dst, size_t size)
{
	const char *p, *e;

	if (!src || !dst || !strlen(src))
		return 0;

	p = basename(src);

	for (e = p + strlen(p) - 1; e >= p && isspace(*e); --e) ;
	if (e < p || (size_t)(e - p) > size - 2)
		return 0;

	strlcpy(dst, p, e - p + 2);
	return strlen(dst);
}

int
filepresent (char * run) {
	struct stat buf;

	if(!stat(run, &buf))
		return 1;
	return 0;
}

char *get_next_string(char **temp, char *split_char)
{
	char *token = NULL;
	token = strsep(temp, split_char);
	while (token != NULL && !strcmp(token, ""))
		token = strsep(temp, split_char);
	return token;
}

int
get_word (char * sentence, char ** word)
{
	char * p;
	int len;
	int skip = 0;

	if (word)
		*word = NULL;

	while (*sentence ==  ' ') {
		sentence++;
		skip++;
	}
	if (*sentence == '\0')
		return 0;

	p = sentence;

	while (*p !=  ' ' && *p != '\0')
		p++;

	len = (int) (p - sentence);

	if (!word)
		return skip + len;

	*word = MALLOC(len + 1);

	if (!*word) {
		condlog(0, "get_word : oom");
		return 0;
	}
	strncpy(*word, sentence, len);
	strchop(*word);
	condlog(5, "*word = %s, len = %i", *word, len);

	if (*p == '\0')
		return 0;

	return skip + len;
}

size_t strlcpy(char *dst, const char *src, size_t size)
{
	size_t bytes = 0;
	char *q = dst;
	const char *p = src;
	char ch;

	while ((ch = *p++)) {
		if (bytes+1 < size)
			*q++ = ch;
		bytes++;
	}

	/* If size == 0 there is no space for a final null... */
	if (size)
		*q = '\0';
	return bytes;
}

size_t strlcat(char *dst, const char *src, size_t size)
{
	size_t bytes = 0;
	char *q = dst;
	const char *p = src;
	char ch;

	while (bytes < size && *q) {
		q++;
		bytes++;
	}
	if (bytes == size)
		return (bytes + strlen(src));

	while ((ch = *p++)) {
		if (bytes+1 < size)
		*q++ = ch;
		bytes++;
	}

	*q = '\0';
	return bytes;
}

int devt2devname(char *devname, int devname_len, char *devt)
{
	FILE *fd;
	unsigned int tmpmaj, tmpmin, major, minor;
	char dev[FILE_NAME_SIZE];
	char block_path[PATH_SIZE];
	struct stat statbuf;

	memset(block_path, 0, sizeof(block_path));
	memset(dev, 0, sizeof(dev));
	if (sscanf(devt, "%u:%u", &major, &minor) != 2) {
		condlog(0, "Invalid device number %s", devt);
		return 1;
	}

	if (devname_len > FILE_NAME_SIZE)
		devname_len = FILE_NAME_SIZE;

	if (stat("/sys/dev/block", &statbuf) == 0) {
		/* Newer kernels have /sys/dev/block */
		sprintf(block_path,"/sys/dev/block/%u:%u", major, minor);
		dev[FILE_NAME_SIZE - 1] = '\0';
		if (lstat(block_path, &statbuf) == 0) {
			if (S_ISLNK(statbuf.st_mode) &&
			    readlink(block_path, dev, FILE_NAME_SIZE-1) > 0) {
				char *p = strrchr(dev, '/');

				if (!p) {
					condlog(0, "No sysfs entry for %s",
						block_path);
					return 1;
				}
				p++;
				strlcpy(devname, p, devname_len);
				return 0;
			}
		}
		condlog(4, "%s is invalid", block_path);
		return 1;
	}
	memset(block_path, 0, sizeof(block_path));

	if (!(fd = fopen("/proc/partitions", "r"))) {
		condlog(0, "Cannot open /proc/partitions");
		return 1;
	}

	while (!feof(fd)) {
		int r = fscanf(fd,"%u %u %*d %s",&tmpmaj, &tmpmin, dev);
		if (!r) {
			r = fscanf(fd,"%*s\n");
			continue;
		}
		if (r != 3)
			continue;

		if ((major == tmpmaj) && (minor == tmpmin)) {
			if (safe_sprintf(block_path, "/sys/block/%s", dev)) {
				condlog(0, "device name %s is too long", dev);
				fclose(fd);
				return 1;
			}
			break;
		}
	}
	fclose(fd);

	if (strncmp(block_path,"/sys/block", 10)) {
		condlog(3, "No device found for %u:%u", major, minor);
		return 1;
	}

	if (stat(block_path, &statbuf) < 0) {
		condlog(0, "No sysfs entry for %s", block_path);
		return 1;
	}

	if (S_ISDIR(statbuf.st_mode) == 0) {
		condlog(0, "sysfs entry %s is not a directory", block_path);
		return 1;
	}
	basenamecpy((const char *)block_path, devname, devname_len);
	return 0;
}

/* This function returns a pointer inside of the supplied pathname string.
 * If is_path_device is true, it may also modify the supplied string */
char *convert_dev(char *name, int is_path_device)
{
	char *ptr;

	if (!name)
		return NULL;
	if (is_path_device) {
		ptr = strstr(name, "cciss/");
		if (ptr) {
			ptr += 5;
			*ptr = '!';
		}
	}
	if (!strncmp(name, "/dev/", 5) && strlen(name) > 5)
		ptr = name + 5;
	else
		ptr = name;
	return ptr;
}

dev_t parse_devt(const char *dev_t)
{
	int maj, min;

	if (sscanf(dev_t,"%d:%d", &maj, &min) != 2)
		return 0;

	return makedev(maj, min);
}

void
setup_thread_attr(pthread_attr_t *attr, size_t stacksize, int detached)
{
	int ret;

	ret = pthread_attr_init(attr);
	assert(ret == 0);
	if (stacksize < PTHREAD_STACK_MIN)
		stacksize = PTHREAD_STACK_MIN;
	ret = pthread_attr_setstacksize(attr, stacksize);
	assert(ret == 0);
	if (detached) {
		ret = pthread_attr_setdetachstate(attr,
						  PTHREAD_CREATE_DETACHED);
		assert(ret == 0);
	}
}

int systemd_service_enabled_in(const char *dev, const char *prefix)
{
	char path[PATH_SIZE], file[PATH_MAX], service[PATH_SIZE];
	DIR *dirfd;
	struct dirent *d;
	int found = 0;

	snprintf(service, PATH_SIZE, "multipathd.service");
	snprintf(path, PATH_SIZE, "%s/systemd/system", prefix);
	condlog(3, "%s: checking for %s in %s", dev, service, path);

	dirfd = opendir(path);
	if (dirfd == NULL)
		return 0;

	while ((d = readdir(dirfd)) != NULL) {
		char *p;
		struct stat stbuf;

		if ((strcmp(d->d_name,".") == 0) ||
		    (strcmp(d->d_name,"..") == 0))
			continue;

		if (strlen(d->d_name) < 6)
			continue;

		p = d->d_name + strlen(d->d_name) - 6;
		if (strcmp(p, ".wants"))
			continue;
		snprintf(file, sizeof(file), "%s/%s/%s",
			 path, d->d_name, service);
		if (stat(file, &stbuf) == 0) {
			condlog(3, "%s: found %s", dev, file);
			found++;
			break;
		}
	}
	closedir(dirfd);

	return found;
}

int systemd_service_enabled(const char *dev)
{
	int found = 0;

	found = systemd_service_enabled_in(dev, "/etc");
	if (!found)
		found = systemd_service_enabled_in(dev, "/usr/lib");
	if (!found)
		found = systemd_service_enabled_in(dev, "/lib");
	if (!found)
		found = systemd_service_enabled_in(dev, "/run");
	return found;
}

static int _linux_version_code;
static pthread_once_t _lvc_initialized = PTHREAD_ONCE_INIT;

/* Returns current kernel version encoded as major*65536 + minor*256 + patch,
 * so, for example,  to check if the kernel is greater than 2.2.11:
 *
 *     if (get_linux_version_code() > KERNEL_VERSION(2,2,11)) { <stuff> }
 *
 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
 * Code copied from busybox (GPLv2 or later)
 */
static void
_set_linux_version_code(void)
{
	struct utsname name;
	char *t;
	int i, r;

	uname(&name); /* never fails */
	t = name.release;
	r = 0;
	for (i = 0; i < 3; i++) {
		t = strtok(t, ".");
		r = r * 256 + (t ? atoi(t) : 0);
		t = NULL;
	}
	_linux_version_code = r;
}

int get_linux_version_code(void)
{
	pthread_once(&_lvc_initialized, _set_linux_version_code);
	return _linux_version_code;
}

int parse_prkey(char *ptr, uint64_t *prkey)
{
	if (!ptr)
		return 1;
	if (*ptr == '0')
		ptr++;
	if (*ptr == 'x' || *ptr == 'X')
		ptr++;
	if (*ptr == '\0' || strlen(ptr) > 16)
		return 1;
	if (strlen(ptr) != strspn(ptr, "0123456789aAbBcCdDeEfF"))
		return 1;
	if (sscanf(ptr, "%" SCNx64 "", prkey) != 1)
		return 1;
	return 0;
}

int parse_prkey_flags(char *ptr, uint64_t *prkey, uint8_t *flags)
{
	char *flagstr;

	flagstr = strchr(ptr, ':');
	*flags = 0;
	if (flagstr) {
		*flagstr++ = '\0';
		if (strlen(flagstr) == 5 && strcmp(flagstr, "aptpl") == 0)
			*flags = MPATH_F_APTPL_MASK;
	}
	return parse_prkey(ptr, prkey);
}

int safe_write(int fd, const void *buf, size_t count)
{
	while (count > 0) {
		ssize_t r = write(fd, buf, count);
		if (r < 0) {
			if (errno == EINTR)
				continue;
			return -errno;
		}
		count -= r;
		buf = (const char *)buf + r;
	}
	return 0;
}

void set_max_fds(rlim_t max_fds)
{
	struct rlimit fd_limit;

	if (!max_fds)
		return;

	if (getrlimit(RLIMIT_NOFILE, &fd_limit) < 0) {
		condlog(0, "can't get open fds limit: %s",
			strerror(errno));
		fd_limit.rlim_cur = 0;
		fd_limit.rlim_max = 0;
	}
	if (fd_limit.rlim_cur < max_fds) {
		fd_limit.rlim_cur = max_fds;
		if (fd_limit.rlim_max < max_fds)
			fd_limit.rlim_max = max_fds;
		if (setrlimit(RLIMIT_NOFILE, &fd_limit) < 0) {
			condlog(0, "can't set open fds limit to "
				"%lu/%lu : %s",
				fd_limit.rlim_cur, fd_limit.rlim_max,
				strerror(errno));
		} else {
			condlog(3, "set open fds limit to %lu/%lu",
				fd_limit.rlim_cur, fd_limit.rlim_max);
		}
	}
}

void free_scandir_result(struct scandir_result *res)
{
	int i;

	for (i = 0; i < res->n; i++)
		FREE(res->di[i]);
	FREE(res->di);
}

void close_fd(void *arg)
{
	close((long)arg);
}
