
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <err.h>
#include <errno.h>
#include <event.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>


/**	onWrite
*/
static void onWrite(
	int 	nFD,
	short 	sEvent,
	void * 	pvArg
)
{
	int 				nErrno;
	socklen_t 			stLen;
	struct sockaddr_in 	addr;

	switch (sEvent)
	{
	case EV_WRITE:
		/*	Save the old errno in case the connection failed, since the
			::getpeername will clobber the real reason for failure.
		*/
		stLen = sizeof(nErrno);
		if (::getsockopt(nFD, SOL_SOCKET, SO_ERROR, &nErrno, &stLen) == -1)
			::err(EX_OSERR, "getsockopt");

		stLen = sizeof(addr);
		switch (::getpeername(nFD, (struct sockaddr *) (&addr), &stLen))
		{
		case 0:
			::printf("Connect succeeded\n");
			break;

		case -1:
			::printf("Connect failed due to: %s\n", ::strerror(nErrno));
			break;

		default:
			::errx(EX_OSERR, "Unrecognized error from ::getpeername");
			break;
		}
		break;

	case EV_TIMEOUT:
		::printf("Connect failed due to timeout\n");
		break;
	}
}

/**	main
*/
int main(
	int 	argc,
	char * 	argv[]
)
{
	int 				nResult = EX_OK;

	int 				nFD;
	struct sockaddr_in 	addr;
	struct event 		ev;
	struct timeval 		tv;

	if (argc < 3)
	{
		::fprintf(stderr, "%s ip_address port\n", argv[0]);
		nResult = EX_USAGE;
		goto mainEnd;
	}

	::event_init();

	if ((nFD = ::socket(PF_INET, SOCK_STREAM, 0)) == -1)
		::err(EX_OSERR, "socket");

	if (::fcntl(nFD, F_SETFL, O_NONBLOCK) == -1)
		::err(EX_OSERR, "fcntl");

	addr.sin_family = AF_INET;
	addr.sin_port = htons(::atoi(argv[2]));

	if (::inet_aton(argv[1], &addr.sin_addr) != 1)
	{
		::fprintf(stderr, "%s is not a valid IP address\n", argv[1]);
		nResult = EX_DATAERR;
		goto mainEnd;
	}

	if (::connect(nFD, (struct sockaddr *) &addr, sizeof(addr)) == -1)
	{
		switch (errno)
		{
		case ETIMEDOUT:
		case ECONNREFUSED:
		case ENETUNREACH:
			::printf("Connect failed immediately due to: %s\n",
				::strerror(errno));
			break;

		case EINPROGRESS:
			tv.tv_sec = 10;
			tv.tv_usec = 0;
			::event_set(&ev, nFD, EV_WRITE, onWrite, NULL);
			::event_add(&ev, &tv);
			::event_dispatch();
			break;

		default:
			::err(EX_OSERR, "connect");
			break;
		}
	}

	::close(nFD);

mainEnd:
	return nResult;
}

