/*-------------------------------------------------------------------------
 *
 * pgconnect.c--
 *    functions related to setting up a connection to the backend
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    /usr/local/devel/pglite/cvs/src/libpgtcl/pgconnect.c,v 1.2 1995/03/11 01:00:19 jolly Exp
 *
 *-------------------------------------------------------------------------
 */

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include "libpq/pqcomm.h" /* for decls of MsgType, PacketBuf, StartupInfo */
#include "fe-auth.h"
#include "tcl.h"
#include "libpgtcl.h"
#include "pgtclCmds.h"

/* use a local version instead of the one found in pqpacket.c */
static int packetSend(Port *port, PacketBuf *buf, PacketLen len,
		      bool nonBlocking);
static PacketBuf* startup2PacketBuf(StartupInfo* s);

/*
 * connectDB -
 * make a connection to the database,  returns 1 if successful or 0 if not
 *
 */
ConnStatusType
Pgtcl_connectDB(char* dbName, char* pqhost, char* pqtty, 
		char* options, int pqport, PGconn *conn)
{
    struct hostent *hp;

    StartupInfo startup;
    PacketBuf   *pacBuf;
    int		status;
    MsgType	msgtype;
    char        *user;
    int         portName;
    int         laddrlen;
    Port        *port = conn->port;
    PGresult    *res;
    int         len;
    /*
    //
    // Initialize the startup packet. 
    //
    // This data structure is used for the seq-packet protocol.  It
    // describes the frontend-backend connection.
    //
    //
    */
    strcpy(startup.database,dbName);
    user = fe_getauthname(conn->errorMessage);
    strcpy(startup.user,user);
    if (options) {
	strcpy(startup.options,options);
    }
    else
	startup.options[0]='\0';  /* not used  */
    strcpy(startup.tty,pqtty);
    startup.execFile[0]='\0';  /* not used */
    portName = pqport;

    /*
    //
    // Open a connection to postmaster/backend.
    //
    */
    port = (Port *) malloc(sizeof(Port));
    memset((char *) port, 0, sizeof(Port));

    if (!(hp = gethostbyname(pqhost)) || hp->h_addrtype != AF_INET) {
	(void) sprintf(conn->errorMessage,
		       "connectDB() --  unknown hostname: %s\n",
		       pqhost);
	goto connect_errReturn;
    }
    memset((char *) &port->raddr, 0, sizeof(port->raddr));
    memmove((char *) &(port->raddr.sin_addr),
	    (char *) hp->h_addr, 
	    hp->h_length);
    port->raddr.sin_family = AF_INET;
    port->raddr.sin_port = htons((unsigned short)(pqport));
    
    /* connect to the server  */
    if ((port->sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	(void) sprintf(conn->errorMessage,
	       "connectDB() -- socket() failed: errno=%d\n%s\n",
	       errno, strerror(errno));
	goto connect_errReturn;	
    }
    if (connect(port->sock, (struct sockaddr *)&port->raddr,
		sizeof(port->raddr)) < 0) {
	(void) sprintf(conn->errorMessage,
	       "connectDB() -- connect() failed: errno=%d\n%s\n",
	       errno,strerror(errno));
	goto connect_errReturn;	
    }
    
    /* fill in the client address */
    if (getsockname(port->sock, (struct sockaddr *) &port->laddr,
		    &laddrlen) < 0) {
	(void) sprintf(conn->errorMessage,
	       "connectDB() -- getsockname() failed: errno=%d\n%s\n",
	       errno, strerror(errno));
	goto connect_errReturn;	
    }
    
    /* by this point, connection has been opened */
    msgtype = fe_getauthsvc(conn->errorMessage);

    pacBuf = startup2PacketBuf(&startup);
    pacBuf->msgtype = msgtype;
    status = packetSend(port,pacBuf, sizeof(PacketBuf), BLOCKING);
    
    if (status == STATUS_ERROR)
	{
	sprintf(conn->errorMessage,
	       "connectDB() --  couldn't send complete packet: errno=%d\n%s\n", errno,strerror(errno));
	goto connect_errReturn;
	}

    /* authenticate as required*/
    if (fe_sendauth(msgtype, port, pqhost, conn->errorMessage) != STATUS_OK) {
      (void) sprintf(conn->errorMessage,
	     "connectDB() --  authentication failed with %s\n",
	       pqhost);
      goto connect_errReturn;	
    }
    
    /* set up the socket file descriptors */
    conn->Pfout = fdopen(port->sock, "w");
    conn->Pfin = fdopen(dup(port->sock), "r");
    if (!conn->Pfout && !conn->Pfin) {
	(void) sprintf(conn->errorMessage,
	       "connectDB() -- fdopen() failed: errno=%d\n%s\n",
	       errno, strerror(errno));
      goto connect_errReturn;	
    }
    
    conn->port = port;

    /* we have a connection now,
       send a blank query down to make sure the database exists*/
    res = Pgtcl_pgexec(conn," ");
    if (res == NULL || res->resultStatus != PGRES_EMPTY_QUERY) {
      /* error will already be in conn->errorMessage */
      goto connect_errReturn;
    }
    ckfree(res);
    return CONNECTION_OK;

connect_errReturn:
    return CONNECTION_BAD;

}

void
Pgtcl_disconnectDB(PGconn* conn)
{
  fputs("X\0", conn->Pfout);
  fflush(conn->Pfout);
  if (conn->errorMessage) ckfree(conn->errorMessage);
  if (conn->dbName) ckfree(conn->dbName);
  if (conn->port) ckfree(conn->port);
  if (conn->Pfout) fclose(conn->Pfout);
  if (conn->Pfin)  fclose(conn->Pfin);
}

/*
 * PacketSend()
 *
 this is just like PacketSend(), defined in backend/libpq/pqpacket.c
 but we define it here to avoid linking in all of libpq.a

 * packetSend -- send a single-packet message.
 *
 * RETURNS: STATUS_ERROR if the write fails, STATUS_OK otherwise.
 * SIDE_EFFECTS: may block.
 * NOTES: Non-blocking writes would significantly complicate 
 *	buffer management.  For now, we're not going to do it.
 *
*/
int
packetSend(Port *port,
	   PacketBuf *buf,
	   PacketLen len,
	   bool nonBlocking)
{
    PacketLen	totalLen;
    int		addrLen = sizeof(struct sockaddr_in);
    
    totalLen = len;
    
    len = sendto(port->sock, (Addr) buf, totalLen, /* flags */ 0,
		 (struct sockaddr *)&(port->raddr), addrLen);
    
    if (len < totalLen) {
	return(STATUS_ERROR);
    }
    
    return(STATUS_OK);
}

/*
 * StartupInfo2Packet()
 *
 * this is just like StartupInfo2Packet(), defined in backend/libpq/pqpacket.c
 * but we repeat it here so we don't have to link in libpq.a
 * 
 * converts a StartupInfo structure to a PacketBuf
 */
PacketBuf* 
startup2PacketBuf(StartupInfo* s)
{
  PacketBuf* res;
  char* tmp;

  res = (PacketBuf*)malloc(sizeof(PacketBuf));
  res->len = sizeof(PacketBuf);
  /* use \n to delimit the strings */
  res->data[0] = '\0';

  tmp= res->data;

  strncpy(tmp, s->database, sizeof(s->database));
  tmp += sizeof(s->database);
  strncpy(tmp, s->user, sizeof(s->user));
  tmp += sizeof(s->user);
  strncpy(tmp, s->options, sizeof(s->options));
  tmp += sizeof(s->options);
  strncpy(tmp, s->execFile, sizeof(s->execFile));
  tmp += sizeof(s->execFile);
  strncpy(tmp, s->tty, sizeof(s->execFile));

  return res;
}

