/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/


#define STREAM_LIB
#define LIBRARY

#include	<stdio.h>
#include	<errno.h>
#include	<sys/types.h>
#include	<sys/socket.h>
#include	<netinet/in.h>
#include	<stdlib.h>
#include	<netdb.h>
#include	<fcntl.h>
#include	"task.h"
#include	"memory_debug.h"
#include	"stream.h"
#include	"netutils.h"
#include	"machine/fork_lock.h"

#define CON_TIMEOUT	(4*60)
extern int max_fid,now_open_files;
extern SEM netutils_lock,stream_lock;

typedef struct connect_t {
	int			id;
	struct sockaddr*	server;
	int			len;
	int			ret;
	int			err;
} CONNECT_T;

int con_err;

void
do_connect(CONNECT_T * arg)
{
	errno = 0;
	arg->ret = connect(arg->id,arg->server,arg->len);
	arg->err = errno;
}

STREAM *
new_connection(
	int * cerr,
	char * hostname,
	int ip,
	short port,
	int (*func)(),
	void * work)
{
int id;
struct sockaddr_in server;
int retry_cnt;

struct sockaddr_in client;
int len;
HOST_ENTRY * hp;
STREAM * ret;
extern S_TABLE s_file_table;
int f;
FORK_LOCK_TBL ft;
int con_time;
int _errno;
CONNECT_T c;

	if ( func )
		(*func)(0,work);
	if ( hostname ) {
		errno = 0;
		hp = r_gethostbyname(hostname);
		if ( hp == 0 ) {
			fprintf(stderr,"%s e=%i p=%i\n",
				hostname,errno,getpid());
			perror("Error(clinet): can't gethostbyname\n");
			*cerr = errno;
			return 0;
		}
		ip = hp->ips[0].d.v4;


	}
	f = 0;
	con_time = get_xltime();
retry:
	if ( get_xltime() - con_time > CON_TIMEOUT ) {
		con_err = 4;
		fprintf(stderr,"%s %x %i\n",hostname,ip,port);
		perror("Error(clinet timeout): can't connect\n");
		fprintf(stderr,"errno = %i\n",errno);
		goto err;
	}
	/* TCP 17 */
	errno = 0;
	if ((id = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		if ( f == 0 && errno == EMFILE && stream_gc ) {
			f = 1;
			(*stream_gc)();
			goto retry;
		}
		if ( func )
			(*func)(1,work);

		perror("Error(clinet): can't socket\n");
		*cerr = errno;
		return 0;
	}

	client.sin_family = AF_INET;
	client.sin_addr.s_addr = ntohl(INADDR_ANY);
	client.sin_port = 0;
	len = sizeof(client);
	if (bind(id, (struct sockaddr *)&client, len) == -1) {

		perror("Error(clinet): can't bind\n");
		con_err = 3;

		close(id);
		if ( func )
			(*func)(1,work);
		*cerr = errno;
		return 0;
	}

	server.sin_family = AF_INET;
	server.sin_addr.s_addr = htonl(ip);
	server.sin_port = htons(port);
	len = sizeof(server);

con_retry:
	rlock_fork(&ft);
	errno = 0;
	c.id = id;
	c.server = (struct sockaddr*)&server;
	c.len = len;
	ss_do((void(*)(void*))do_connect,&c);
	if ( c.ret == -1) {
		runlock_fork(&ft);
		if ( ft.err ) {
			close(id);
			goto retry;
		}
		if ( c.err == EINTR )
			goto con_retry;
		if ( c.err == EISCONN )
			goto ok;
		fprintf(stderr,"%s %x %i\n",hostname,ip,port);
		perror("Error(clinet): can't connect\n");
		fprintf(stderr,"errno = %i\n",c.err);
		con_err = 4;
		close(id);
	err:
		if ( func )
			(*func)(1,work);
		*cerr = c.err;
		return 0;
	}
	runlock_fork(&ft);
	if ( ft.err ) {
		close(id);
		goto retry;
	}
ok:

#ifndef O_RSYNC
	fcntl(id,F_SETFL,O_RDWR|O_FSYNC);
#else
	fcntl(id,F_SETFL,O_RDWR|O_RSYNC);
#endif
	if ( func )
		(*func)(1,work);
	ret = d_alloc(sizeof(S_FILE));
	ret->h.tbl = &s_file_table;
	ret->h.thread = 0;
	ret->file.fid = id;
	ret->file.filename = 0;
	lock_task(stream_lock);
	_s_open(ret,O_RDWR);
	now_open_files ++;
	if ( ret->file.fid < id )
		max_fid = id;
	unlock_task(stream_lock,"new_connection");
	return ret;
}

