/*
 * sysstat test functions.
 * (C) 2019-2022 by Sebastien GODARD (sysstat <at> orange.fr)
 *
 ***************************************************************************
 * 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 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-1335 USA              *
 ***************************************************************************
 */

#ifdef TEST

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <dirent.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/statvfs.h>
#include <sys/time.h>
#include <sys/types.h>

#include "systest.h"

time_t __unix_time = 0;
int __env = 0;

extern long interval;
extern int sigint_caught;

/*
 ***************************************************************************
 * Test mode: Instead of reading system time, use time given on the command
 * line.
 *
 * RETURNS:
 * Number of seconds since the epoch, as given on the command line.
 ***************************************************************************
 */
time_t get_unix_time(time_t *t)
{
	return __unix_time;
}

/*
 ***************************************************************************
 * Test mode: Get time of the day using __unix_time variable contents.
 *
 * OUT:
 * @tv	Number of seconds since the Epoch.
 ***************************************************************************
 */
void get_day_time(struct timeval *tv)
{
	__unix_time += interval;
	tv->tv_sec = __unix_time;
	tv->tv_usec = 0;
}

/*
 ***************************************************************************
 * Test mode: Send bogus information about current kernel.
 *
 * OUT:
 * @h	Structure with kernel information.
 ***************************************************************************
 */
void get_uname(struct utsname *h)
{
	strcpy(h->sysname, "Linux");
	strcpy(h->nodename, "SYSSTAT.TEST");
	strcpy(h->release, "1.2.3-TEST");
	strcpy(h->machine, "x86_64");
}

/*
 ***************************************************************************
 * Test mode: Send bogus information about current filesystem.
 *
 * OUT:
 * @buf	Structure with filesystem information.
 ***************************************************************************
 */
int get_fs_stat(char *c, struct statvfs *buf)
{
	static int p = 0;
	/*
	 * f_bfree, f_blocks and f_bavail used to be unsigned long.
	 * So don't use values greater then UINT_MAX to make sure that values
	 * won't overflow on 32-bit systems.
	 */
	unsigned long long bfree[4]  = {739427840, 286670336, 1696156672, 2616732672};
	unsigned long long blocks[4] = {891291136, 502345216, 1829043712, 3502345216};
	unsigned long long bavail[4] = {722675712, 241253120, 1106515456, 1871315456};
	unsigned long long files[4]  = {6111232, 19202048, 1921360, 19202048};
	unsigned long long ffree[4]  = {6008414, 19201593, 1621550, 19051710};

	buf->f_bfree = bfree[p];
	buf->f_blocks = blocks[p];
	buf->f_bavail = bavail[p];
	buf->f_frsize = 1;
	buf->f_files = files[p];
	buf->f_ffree = ffree[p];

	p = (p + 1) & 0x3;

	return 0;
}

/*
 ***************************************************************************
 * Test mode: Ignore environment variable value.
 ***************************************************************************
 */
char *get_env_value(const char *c)
{
	if (!__env)
		return NULL;

	fprintf(stderr, "Reading contents of %s\n", c);
	return getenv(c);
}

/*
 ***************************************************************************
 * Test mode: Go to next time period.
 ***************************************************************************
 */
void next_time_step(void)
{
	int root_nr = 1;
	char rootf[64], testf[128];
	char *resolved_name;

	__unix_time += interval;

	/* Get root directory name (root1, root2, etc.) at which the "root" symlink points */
	if ((resolved_name = realpath(ROOTDIR, NULL)) != NULL) {
		if (strlen(resolved_name) > 4) {
			/* Set root_nr to the root directory number (1, 2, etc.) */
			root_nr = atoi(resolved_name + strlen(resolved_name) - 1);
		}
		free(resolved_name);
	}
	if ((unlink(ROOTDIR) < 0) && (errno != ENOENT)) {
		perror("unlink");
		exit(1);
	}

	/* Set next root directory name (root2, root3, etc.). Directories like root1b are unreachable */
	snprintf(rootf, sizeof(rootf), "%s%d", ROOTFILE, ++root_nr);
	rootf[sizeof(rootf) - 1] = '\0';
	snprintf(testf, sizeof(testf), "%s/%s", TESTDIR, rootf);
	testf[sizeof(testf) - 1] = '\0';

	/* Make sure that new root directory exists */
	if (access(testf, F_OK) < 0) {
		if (errno == ENOENT) {
			/* No more root directories: Simulate a Ctrl/C */
			int_handler(0);
			return;
		}
	}

	/* Create "root" symlink pointing at the new root directory */
	if (symlink(rootf, ROOTDIR) < 0) {
		perror("link");
		exit(1);
	}
}

/*
 ***************************************************************************
 * If current file is considered as a virtual one ("virtualhd"), then set
 * its device ID (major 253, minor 2, corresponding here to dm-2) in the
 * stat structure normally filled by the stat() system call.
 * Otherwise, open file and read its major and minor numbers.
 *
 * IN:
 * @name	Pathname to file.
 *
 * OUT:
 * @statbuf	Structure containing device ID.
 *
 * RETURNS:
 * 0 if it actually was the virtual device, 1 otherwise, and -1 on failure.
 ***************************************************************************
 */
int virtual_stat(const char *name, struct stat *statbuf)
{
	FILE *fp;
	char line[128];
	int major, minor;

	if (!strcmp(name, VIRTUALHD)) {
		statbuf->st_rdev = (253 << MINORBITS) + 2;
		return 0;
	}

	statbuf->st_rdev = 0;

	if ((fp = fopen(name, "r")) == NULL)
		return -1;

	if (fgets(line, sizeof(line), fp) != NULL) {
		sscanf(line, "%d %d", &major, &minor);
		statbuf->st_rdev = (major << MINORBITS) + minor;
	}

	fclose(fp);

	return 1;
}

/*
 ***************************************************************************
 * Open a "_list" file containing a list of files enumerated in a known
 * order contained in current directory.
 *
 * IN:
 * @name	Pathname to directory containing the "_list" file.
 *
 * RETURNS:
 * A pointer on current "_list" file.
 ***************************************************************************
 */
DIR *open_list(const char *name)
{
	FILE *fp;
	char filename[1024];

	snprintf(filename, sizeof(filename), "%s/%s", name, _LIST);
	filename[sizeof(filename) - 1] = '\0';

	if ((fp = fopen(filename, "r")) == NULL)
		return NULL;

	return (DIR *) fp;
}

/*
 ***************************************************************************
 * Read next file name contained in a "_list" file.
 *
 * IN:
 * @dir	Pointer on current "_list" file.
 *
 * RETURNS:
 * A structure containing the name of the next file to read.
 ***************************************************************************
 */
struct dirent *read_list(DIR *dir)
{
	FILE *fp = (FILE *) dir;
	static struct dirent drd;
	char line[1024];


	if ((fgets(line, sizeof(line), fp) != NULL) && (strlen(line) > 1) &&
		(strlen(line) < sizeof(drd.d_name))) {
		strcpy(drd.d_name, line);
		drd.d_name[strlen(line) - 1] = '\0';
		return &drd;
	}

	return NULL;
}

/*
 ***************************************************************************
 * Close a "_list" file.
 *
 * IN:
 * @dir	Pointer on "_list" file to close.
 ***************************************************************************
 */
void close_list(DIR *dir)
{
	FILE *fp = (FILE *) dir;

	fclose(fp);
}

/*
 ***************************************************************************
 * Replacement function for realpath() system call. Do nothing here.
 *
 * IN:
 * @name	Pathname to process.
 * @c		Unused here.
 *
 * RETURNS:
 * Pathname (unchanged).
 ***************************************************************************
 */
char *get_realname(char *name, char *c)
{
	char *resolved_name;

	if ((resolved_name = (char *) malloc(1024)) == NULL) {
		perror("malloc");
		exit(4);
	}
	strncpy(resolved_name, name, 1024);
	resolved_name[1023] = '\0';

	return resolved_name;
}

/*
 ***************************************************************************
 * Replacement function for getpwuid() system call. Fill a dummy passwd
 * structure containing the name of a user.
 *
 * IN:
 * @uid		UID of the user
 *
 * RETURNS:
 * Pointer on the passwd structure.
 ***************************************************************************
 */
struct passwd *get_usrname(uid_t uid)
{
	static struct passwd pwd_ent;
	static char pw_name[16];

	pwd_ent.pw_name = pw_name;
	if (!uid) {
		strcpy(pwd_ent.pw_name, "root");
	}
	else {
		strcpy(pwd_ent.pw_name, "testusr");
	}

	return &pwd_ent;
}

/*
 ***************************************************************************
 * Replacement function for fork() system call. Don't fork really but return
 * a known PID number.
 *
 * RETURNS:
 * Known PID number.
 ***************************************************************************
 */
pid_t get_known_pid(void)
{
	return 8741;
}

#endif	/* TEST */

