/*
 * Crackerjack Project
 *
 * Copyright (C) 2007-2008, Hitachi, Ltd.
 * Author(s): Yumiko Sugita <yumiko.sugita.yf@hitachi.com>,
 *            Satoshi Fujiwara <sa-fuji@sdl.hitachi.co.jp>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * $Id: common_j_h.c,v 1.5 2009/11/20 06:48:31 yaberauneya Exp $
 *
 */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <mqueue.h>
#include "include_j_h.h"
#include "test.h"

#define barrier() __asm__ __volatile__("": : :"memory")
#define WITH_SIGNALS_BLOCKED(code) {					\
		sigset_t held_sigs_;					\
		sigfillset(&held_sigs_);				\
		sigprocmask(SIG_SETMASK, &held_sigs_, &held_sigs_);	\
		barrier();						\
		code;							\
		barrier();						\
		sigprocmask(SIG_SETMASK, &held_sigs_, NULL);		\
	}

/*
 * Change user ID
 *
 *   We assume 'test' executable is executed with 'root' permission.
 *   So, if you use this function, you can not return 'root' uid.
 */
int setup_uid(char *uname)
{
	struct passwd *pw;
	int rc;

	pw = getpwnam(uname);
	if (!pw) {
		EPRINTF("getpwnam failed.\n");
		return -1;
	}
	rc = setuid(pw->pw_uid);
	if (rc < 0) {
		EPRINTF("setuid failed.\n");
		return -1;
	}
	return 0;
}

/*
 * Change effective user ID
 */
int setup_euid(char *uname, uid_t * old_uid)
{
	struct passwd *pw;
	int rc;

	*old_uid = geteuid();
	pw = getpwnam(uname);
	if (!pw) {
		EPRINTF("getpwnam failed.\n");
		return -1;
	}
	rc = seteuid(pw->pw_uid);
	if (rc < 0) {
		EPRINTF("seteuid failed.\n");
		return -1;
	}
	return 0;
}

int cleanup_euid(uid_t old_uid)
{
	int rc;

	rc = seteuid(old_uid);
	if (rc < 0) {
		EPRINTF("seteuid failed.\n");
		return -1;
	}
	return 0;
}

static void sigterm_handler(int sig)
{
	_exit(0);
}

/*
 * Generate a child process which will send a signal
 */
pid_t create_sig_proc(unsigned long usec, int sig, unsigned count)
{
	pid_t pid, cpid;

	pid = getpid();
	WITH_SIGNALS_BLOCKED(if ((cpid = fork()) == 0) {
			     tst_sig(NOFORK, SIG_DFL, NULL);
			     signal(SIGTERM, sigterm_handler);}
	) ;
	switch (cpid) {
	case 0:
		while (count-- > 0) {
			usleep(usec);
			if (kill(pid, sig) == -1)
				break;
		}
		_exit(0);
		break;
	case -1:
		EPRINTF("fork failed.\n");
		return cpid;
	default:
		return cpid;
	}
}

/*
 * Create and delete test file
 */
int setup_file(char *testdir, char *fname, char *path)
{
	return _setup_file(testdir, fname, path,
			   O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);
}

int _setup_file(char *testdir, char *fname, char *path, int flags, mode_t mode)
{
	int rc;

	sprintf(path, "%s/%s", testdir, fname);
	rc = open(path, flags, mode);
	if (rc < 0) {
		EPRINTF("open failed.\n");
		return -1;
	}
	return rc;
}

int cleanup_file(char *path)
{
	unlink(path);
	return 0;
}

/*
 * Create and delete swap file
 */
/* swap file needs to be more than 40KB */
#define MIN_SWAPFILE_SIZE	(64 * 1024)
int setup_swapfile(char *testdir, char *fname, char *path, size_t size)
{
	int fd = -1, rc;
	size_t r_sz;
	int cmdlen = 256;
	char cmd[cmdlen];
	char *p = NULL;

	sprintf(path, "%s/%s", testdir, fname);
	fd = open(path, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);
	if (fd < 0) {
		EPRINTF("open failed.\n");
		goto ERR_EXIT;
	}
	if (size < MIN_SWAPFILE_SIZE) {
		EPRINTF("size too short.\n");
		goto ERR_EXIT;
	}
	p = malloc(size);
	if (!p) {
		EPRINTF("malloc failed.\n");
		goto ERR_EXIT;
	}
	/* Swap file must not have hole area */
	memset(p, 0x5a, size);
	r_sz = (size_t) write(fd, p, size);
	if (r_sz != size) {
		EPRINTF("write failed.\n");
		goto ERR_EXIT;
	}
	snprintf(cmd, cmdlen, "/sbin/mkswap %s > /dev/null 2>&1", path);
	rc = system(cmd);
	if (rc != 0) {
		EPRINTF("system(%s) failed.\n", cmd);
		goto ERR_EXIT;
	}
	return fd;

ERR_EXIT:
	if (fd >= 0)
		close(fd);
	if (p)
		free(p);
	return -1;
}

int cleanup_swapfile(char *path)
{
	unlink(path);
	return 0;
}

/*
 * Change user limit that the calling process can open
 */
int setup_ulimit_fnum(rlim_t newlim, rlim_t * oldlim)
{
	int rc;
	struct rlimit rlim;

	rc = getrlimit(RLIMIT_NOFILE, &rlim);
	if (rc < 0) {
		EPRINTF("getrlimit failed.\n");
		return -1;
	}
	*oldlim = rlim.rlim_cur;
	if (newlim > rlim.rlim_max) {
		EPRINTF("can't set ulimit value: %ld.\n", newlim);
		return -1;
	}
	rlim.rlim_cur = newlim;
	rc = setrlimit(RLIMIT_NOFILE, &rlim);
	if (rc < 0) {
		EPRINTF("setrlimit failed.\n");
		return -1;
	}
	return 0;
}

int cleanup_ulimit_fnum(rlim_t oldlim)
{
	int rc;
	struct rlimit rlim;

	rc = getrlimit(RLIMIT_NOFILE, &rlim);
	if (rc < 0) {
		EPRINTF("getrlimit failed.\n");
		return -1;
	}
	if (oldlim > rlim.rlim_max) {
		EPRINTF("can't set ulimit value: %ld.\n", oldlim);
		return -1;
	}
	rlim.rlim_cur = oldlim;
	rc = setrlimit(RLIMIT_NOFILE, &rlim);
	if (rc < 0) {
		EPRINTF("setrlimit failed.\n");
		return -1;
	}
	return 0;
}

/*
 * Change /proc or /sys setting
 */
int setup_proc_fs(char *path, int newval, int *oldval)
{
	int fd = -1, rc, len;
	char buf[32];

	fd = open(path, O_RDWR);
	if (fd < 0) {
		EPRINTF("open %s failed.\n", path);
		return -1;
	}

	do {
		rc = read(fd, buf, 32);
	} while (rc < 0 && errno == EAGAIN);
	if (rc < 0) {
		EPRINTF("read failed.\n");
		close(fd);
		return -1;
	}

	*oldval = atoi(buf);
	sprintf(buf, "%d\n", newval);
	len = strlen(buf);
	rc = write(fd, buf, len);
	close(fd);
	if (rc != len) {
		EPRINTF("write failed.\n");
		return -1;
	}
	return 0;
}

int cleanup_proc_fs(char *path, int oldval)
{
	int fd = -1, rc, len;
	char buf[32];

	fd = open(path, O_RDWR);
	if (fd < 0) {
		EPRINTF("open %s failed.\n", path);
		return -1;
	}

	sprintf(buf, "%d\n", oldval);
	len = strlen(buf);
	rc = write(fd, buf, len);
	close(fd);
	if (rc != len) {
		EPRINTF("write failed.\n");
		return -1;
	}
	return 0;
}

#if 0
/*
 * Check max nodes from /sys/devices/system/node/node* files (for NUMA)
 */
int get_max_nodes(void)
{
	/* We assume that there is only one node */
	return 1;
}
#endif

/*
 * Get unexist pid
 */
pid_t get_unexist_pid(void)
{
	pid_t pid;
	int st;

	pid = fork();
	switch (pid) {
	case -1:
		EPRINTF("fork failed.\n");
		return -1;
	case 0:
		_exit(0);
	default:
		wait(&st);
		return pid;
	}
}
