/*
 *
 *  Copyright (c) International Business Machines  Corp., 2002
 *
 *  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
 */

/* 12/03/2002	Port to LTP     robbiew@us.ibm.com */
/* 06/30/2001	Port to Linux	nsharoff@us.ibm.com */

/*
 * ALGORITHM
 *	issue calls to acct and test the returned values against
 *	expected results
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mount.h>

#include "test.h"
#include "usctest.h"
#include "safe_macros.h"

#define DIR_MODE	(S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP| \
			 S_IXGRP|S_IROTH|S_IXOTH)
#define TEST_FILE1	"/"
#define TEST_FILE2	"/dev/null"
#define TEST_FILE3	"/tmp/does/not/exist"
#define TEST_FILE4	"/etc/fstab/"
#define TEST_FILE5	"./tmpfile"
#define TEST_FILE6	"test_file_eloop1"
#define TEST_FILE7	nametoolong
#define TEST_FILE8	"mntpoint/tmp"

static char nametoolong[PATH_MAX+2];
static const char *fs_type;
static const char *device;
static int mount_flag;

static void setup(void);
static void cleanup(void);
static void setup2(void);
static void cleanup2(void);
static void acct_verify(int);

static struct test_case_t {
	char *filename;
	char *exp_errval;
	int exp_errno;
	void (*setupfunc) ();
	void (*cleanfunc) ();
} test_cases[] = {
	{TEST_FILE1, "EISDIR",  EISDIR,  NULL,   NULL},
	{TEST_FILE2, "EACCES",  EACCES,  NULL,   NULL},
	{TEST_FILE3, "ENOENT",  ENOENT,  NULL,   NULL},
	{TEST_FILE4, "ENOTDIR", ENOTDIR, NULL,   NULL},
	{TEST_FILE5, "EPERM",   EPERM,   setup2, cleanup2},
	{NULL,       "EPERM",   EPERM,   setup2, cleanup2},
	{TEST_FILE6, "ELOOP",        ELOOP,        NULL, NULL},
	{TEST_FILE7, "ENAMETOOLONG", ENAMETOOLONG, NULL, NULL},
	{TEST_FILE8, "EROFS",        EROFS,        NULL, NULL},
};

char *TCID = "acct01";
int TST_TOTAL = ARRAY_SIZE(test_cases);
static struct passwd *ltpuser;
static int exp_enos[] = { EISDIR, EACCES, ENOENT, ENOTDIR, EPERM,
			  ELOOP, ENAMETOOLONG, EROFS, 0 };

int main(int argc, char *argv[])
{
	int lc;
	const char *msg;
	int i;

	msg = parse_opts(argc, argv, NULL, NULL);
	if (msg != NULL)
		tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg);

	setup();

	TEST_EXP_ENOS(exp_enos);

	for (lc = 0; TEST_LOOPING(lc); lc++) {
		tst_count = 0;
		for (i = 0; i < TST_TOTAL; i++)
			acct_verify(i);
	}

	cleanup();
	tst_exit();
}

static void check_acct_in_kernel(void)
{
	/* check if acct is implemented in kernel */
	if (acct(NULL) == -1) {
		if (errno == ENOSYS) {
			tst_resm(TCONF,
				 "BSD process accounting is not configured in "
				 "this kernel");

			tst_exit();
		}
	}
}

static void setup(void)
{
	int fd;

	tst_require_root(NULL);

	check_acct_in_kernel();

	tst_tmpdir();

	ltpuser = SAFE_GETPWNAM(cleanup, "nobody");

	fd = SAFE_CREAT(cleanup, TEST_FILE5, 0777);
	SAFE_CLOSE(cleanup, fd);

	if (acct(TEST_FILE5) == -1)
		tst_brkm(TBROK | TERRNO, cleanup, "acct failed unexpectedly");

	/* turn off acct, so we are in a known state */
	if (acct(NULL) == -1)
		tst_brkm(TBROK | TERRNO, cleanup, "acct(NULL) failed");

	/* ELOOP SETTING */
	SAFE_SYMLINK(cleanup, TEST_FILE6, "test_file_eloop2");
	SAFE_SYMLINK(cleanup, "test_file_eloop2", TEST_FILE6);

	/* ENAMETOOLONG SETTING */
	memset(nametoolong, 'a', PATH_MAX+1);

	/* EROFS SETTING */
	fs_type = tst_dev_fs_type();
	device = tst_acquire_device(cleanup);

	if (!device)
		tst_brkm(TCONF, cleanup, "Failed to obtain block device");

	tst_mkfs(cleanup, device, fs_type, NULL);
	SAFE_MKDIR(cleanup, "mntpoint", DIR_MODE);
	if (mount(device, "mntpoint", fs_type, 0, NULL) < 0) {
		tst_brkm(TBROK | TERRNO, cleanup,
			 "mount device:%s failed", device);
	}
	mount_flag = 1;

	/* Create a file in the file system, then remount it as read-only */
	fd = SAFE_CREAT(cleanup, TEST_FILE8, 0644);
	SAFE_CLOSE(cleanup, fd);
	if (mount(device, "mntpoint", fs_type,
		  MS_REMOUNT | MS_RDONLY, NULL) < 0) {
		tst_brkm(TBROK | TERRNO, cleanup,
			 "mount device:%s failed", device);
	}
}

static void acct_verify(int i)
{

	if (test_cases[i].setupfunc)
		test_cases[i].setupfunc();

	TEST(acct(test_cases[i].filename));

	if (test_cases[i].cleanfunc)
		test_cases[i].cleanfunc();

	if (TEST_RETURN != -1) {
		tst_resm(TFAIL, "acct(%s) succeeded unexpectedly",
			 test_cases[i].filename);
		return;
	}

	if (TEST_ERRNO == test_cases[i].exp_errno) {
		tst_resm(TPASS | TTERRNO, "acct failed as expected");
	} else {
		tst_resm(TFAIL | TTERRNO,
			 "acct failed unexpectedly; expected: %d - %s",
			 test_cases[i].exp_errno,
			 strerror(test_cases[i].exp_errno));
	}
}

static void setup2(void)
{
	SAFE_SETEUID(cleanup, ltpuser->pw_uid);
}

static void cleanup2(void)
{
	SAFE_SETEUID(cleanup, 0);
}

static void cleanup(void)
{
	TEST_CLEANUP;

	if (acct(NULL) == -1)
		tst_resm(TWARN | TERRNO, "acct(NULL) failed");

	if (mount_flag && umount("mntpoint") < 0) {
		tst_resm(TWARN | TERRNO,
			 "umount device:%s failed", device);
	}

	if (device)
		tst_release_device(NULL, device);

	tst_rmdir();
}
