/*
 *
 * Copyright (C) 2008, Linux Foundation,
 * written by Michael Kerrisk <mtk.manpages@gmail.com>
 * Initial Porting to LTP by Subrata <subrata@linux.vnet.ibm.com>
 *
 * Licensed under the GNU GPLv2 or later.
 * 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
 */

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "test.h"
#include "lapi/fcntl.h"
#include "lapi/syscalls.h"

#define PORT_NUM 33333

#define die(msg)	tst_brkm(TBROK|TERRNO, cleanup, msg)

#ifndef SOCK_CLOEXEC
#define SOCK_CLOEXEC    O_CLOEXEC
#endif
#ifndef SOCK_NONBLOCK
#define SOCK_NONBLOCK   O_NONBLOCK
#endif

#if defined(SYS_ACCEPT4)	/* the socketcall() number */
#define USE_SOCKETCALL 1
#endif

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

static void setup(void)
{
	TEST_PAUSE;
	tst_tmpdir();
}

static void cleanup(void)
{
	tst_rmdir();
}

#if !(__GLIBC_PREREQ(2, 10))
static int
accept4_01(int fd, struct sockaddr *sockaddr, socklen_t *addrlen, int flags)
{
#ifdef DEBUG
	tst_resm(TINFO, "Calling accept4(): flags = %x", flags);
	if (flags != 0) {
		tst_resm(TINFO, " (");
		if (flags & SOCK_CLOEXEC)
			tst_resm(TINFO, "SOCK_CLOEXEC");
		if ((flags & SOCK_CLOEXEC) && (flags & SOCK_NONBLOCK))
			tst_resm(TINFO, " ");
		if (flags & SOCK_NONBLOCK)
			tst_resm(TINFO, "SOCK_NONBLOCK");
		tst_resm(TINFO, ")");
	}
	tst_resm(TINFO, "\n");
#endif

#if USE_SOCKETCALL
	long args[6];

	args[0] = fd;
	args[1] = (long)sockaddr;
	args[2] = (long)addrlen;
	args[3] = flags;

	return ltp_syscall(__NR_socketcall, SYS_ACCEPT4, args);
#else
	return ltp_syscall(__NR_accept4, fd, sockaddr, addrlen, flags);
#endif
}
#endif

static void
do_test(int lfd, struct sockaddr_in *conn_addr,
	int closeonexec_flag, int nonblock_flag)
{
	int connfd, acceptfd;
	int fdf, flf, fdf_pass, flf_pass;
	struct sockaddr_in claddr;
	socklen_t addrlen;

#ifdef DEBUG
	tst_resm(TINFO, "=======================================\n");
#endif

	connfd = socket(AF_INET, SOCK_STREAM, 0);
	if (connfd == -1)
		die("Socket Error");
	if (connect(connfd, (struct sockaddr *)conn_addr,
		    sizeof(struct sockaddr_in)) == -1)
		die("Connect Error");

	addrlen = sizeof(struct sockaddr_in);
#if !(__GLIBC_PREREQ(2, 10))
	acceptfd = accept4_01(lfd, (struct sockaddr *)&claddr, &addrlen,
			      closeonexec_flag | nonblock_flag);
#else
	acceptfd = accept4(lfd, (struct sockaddr *)&claddr, &addrlen,
			   closeonexec_flag | nonblock_flag);
#endif
	if (acceptfd == -1) {
		if (errno == ENOSYS) {
			tst_brkm(TCONF, cleanup,
			         "syscall __NR_accept4 not supported");
		} else {
			tst_brkm(TBROK | TERRNO, cleanup, "accept4 failed");
		}
	}

	fdf = fcntl(acceptfd, F_GETFD);
	if (fdf == -1)
		die("fcntl:F_GETFD");
	fdf_pass = ((fdf & FD_CLOEXEC) != 0) ==
	    ((closeonexec_flag & SOCK_CLOEXEC) != 0);
#ifdef DEBUG
	tst_resm(TINFO, "Close-on-exec flag is %sset (%s); ",
		 (fdf & FD_CLOEXEC) ? "" : "not ", fdf_pass ? "OK" : "failed");
#endif
	if (!fdf_pass)
		tst_resm(TFAIL,
			 "Close-on-exec flag mismatch, should be %x, actual %x",
			 fdf & FD_CLOEXEC, closeonexec_flag & SOCK_CLOEXEC);

	flf = fcntl(acceptfd, F_GETFL);
	if (flf == -1)
		die("fcntl:F_GETFD");
	flf_pass = ((flf & O_NONBLOCK) != 0) ==
	    ((nonblock_flag & SOCK_NONBLOCK) != 0);
#ifdef DEBUG
	tst_resm(TINFO, "nonblock flag is %sset (%s)\n",
		 (flf & O_NONBLOCK) ? "" : "not ", flf_pass ? "OK" : "failed");
#endif
	if (!flf_pass)
		tst_resm(TFAIL,
			 "nonblock flag mismatch, should be %x, actual %x",
			 fdf & O_NONBLOCK, nonblock_flag & SOCK_NONBLOCK);

	close(acceptfd);
	close(connfd);

	if (fdf_pass && flf_pass)
		tst_resm(TPASS, "Test passed");
}

static int create_listening_socket(int port_num)
{
	struct sockaddr_in svaddr;
	int lfd;
	int optval;

	memset(&svaddr, 0, sizeof(struct sockaddr_in));
	svaddr.sin_family = AF_INET;
	svaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	svaddr.sin_port = htons(port_num);

	lfd = socket(AF_INET, SOCK_STREAM, 0);
	if (lfd == -1)
		die("Socket Error");

	optval = 1;
	if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval,
		       sizeof(optval)) == -1)
		die("Setsockopt Error");

	if (bind(lfd, (struct sockaddr *)&svaddr,
		 sizeof(struct sockaddr_in)) == -1)
		die("Bind Error");

	if (listen(lfd, 5) == -1)
		die("Listen Error");

	return lfd;
}

static char *opt_port;

static option_t options[] = {
	{"p:", NULL, &opt_port},
	{NULL, NULL, NULL}
};

static void usage(void)
{
	printf("  -p      Port\n");
}

int main(int argc, char *argv[])
{
	struct sockaddr_in conn_addr;
	int lfd;
	int port_num = PORT_NUM;

	tst_parse_opts(argc, argv, options, usage);

	if (opt_port)
		port_num = atoi(opt_port);

	setup();

	memset(&conn_addr, 0, sizeof(struct sockaddr_in));
	conn_addr.sin_family = AF_INET;
	conn_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	conn_addr.sin_port = htons(port_num);

	lfd = create_listening_socket(port_num);

	do_test(lfd, &conn_addr, 0, 0);
	do_test(lfd, &conn_addr, SOCK_CLOEXEC, 0);
	do_test(lfd, &conn_addr, 0, SOCK_NONBLOCK);
	do_test(lfd, &conn_addr, SOCK_CLOEXEC, SOCK_NONBLOCK);

	close(lfd);
	cleanup();
	tst_exit();
}
