/*
 * Copyright (c) 2014 Fujitsu Ltd.
 * Author: Zeng Linggang <zenglg.jy@cn.fujitsu.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.
 */
/*
 * Test Description:
 *   Verify that,
 *   The flag of fchownat() is AT_SYMLINK_NOFOLLOW and the pathname would
 *   not be dereferenced if the pathname is a symbolic link.
 */

#define _GNU_SOURCE

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include "test.h"
#include "usctest.h"
#include "safe_macros.h"
#include "fchownat.h"
#include "lapi/fcntl.h"

#define TESTFILE	"testfile"
#define TESTFILE_LINK	"testfile_link"

char *TCID = "fchownat02";
int TST_TOTAL = 1;

static int dirfd;
static uid_t set_uid = 1000;
static gid_t set_gid = 1000;
static void setup(void);
static void cleanup(void);
static void test_verify(void);
static void fchownat_verify(void);

int main(int ac, char **av)
{
	int lc;
	const char *msg;
	int i;

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

	setup();

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

	cleanup();
	tst_exit();
}

static void setup(void)
{
	struct stat c_buf, l_buf;

	if ((tst_kvercmp(2, 6, 16)) < 0)
		tst_brkm(TCONF, NULL, "This test needs kernel 2.6.16 or newer");

	tst_require_root(NULL);

	tst_sig(NOFORK, DEF_HANDLER, cleanup);

	TEST_PAUSE;

	tst_tmpdir();

	dirfd = SAFE_OPEN(cleanup, "./", O_DIRECTORY);

	SAFE_TOUCH(cleanup, TESTFILE, 0600, NULL);

	SAFE_SYMLINK(cleanup, TESTFILE, TESTFILE_LINK);

	SAFE_STAT(cleanup, TESTFILE_LINK, &c_buf);

	SAFE_LSTAT(cleanup, TESTFILE_LINK, &l_buf);

	if (l_buf.st_uid == set_uid || l_buf.st_gid == set_gid) {
		tst_brkm(TBROK | TERRNO, cleanup,
			 "link_uid(%d) == set_uid(%d) or link_gid(%d) == "
			 "set_gid(%d)", l_buf.st_uid, set_uid, l_buf.st_gid,
			 set_gid);
	}
}

static void fchownat_verify(void)
{
	TEST(fchownat(dirfd, TESTFILE_LINK, set_uid, set_gid,
		      AT_SYMLINK_NOFOLLOW));

	if (TEST_RETURN != 0) {
		tst_resm(TFAIL | TTERRNO, "fchownat() failed, errno=%d : %s",
			 TEST_ERRNO, strerror(TEST_ERRNO));
	} else {
		test_verify();
	}
}

static void test_verify(void)
{
	struct stat c_buf, l_buf;

	SAFE_STAT(cleanup, TESTFILE_LINK, &c_buf);

	SAFE_LSTAT(cleanup, TESTFILE_LINK, &l_buf);

	if (c_buf.st_uid != set_uid && l_buf.st_uid == set_uid &&
	    c_buf.st_gid != set_gid && l_buf.st_gid == set_gid) {
		tst_resm(TPASS, "fchownat() test AT_SYMLINK_NOFOLLOW success");
	} else {
		tst_resm(TFAIL,
			 "fchownat() test AT_SYMLINK_NOFOLLOW fail with uid=%d "
			 "link_uid=%d set_uid=%d | gid=%d link_gid=%d "
			 "set_gid=%d", c_buf.st_uid, l_buf.st_uid, set_uid,
			 c_buf.st_gid, l_buf.st_gid, set_gid);
	}
}

static void cleanup(void)
{
	tst_rmdir();

	TEST_CLEANUP;
}
