/*-------------------------------------------------------------------------
 *
 *   FILE
 *	pgconn.cc
 *
 *   DESCRIPTION
 *      implementation of the PGconn class.
 *   PGconn encapsulates a frontend to backend connection
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    $Header: /usr/local/devel/pglite/cvs/src/libpq++/pgconn.cc,v 1.3 1995/03/11 01:01:15 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>
extern "C" {
#include "libpq/pqcomm.h"
}
#include "fe-auth.H"
#include "libpq++.H"

// couldn't find this in the system header files
extern "C"
{
  int htons();
}

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

// default ctor
// use reasonable defaults
PGconn::PGconn()
{
  char* name;
  env = new PGenv(); // use reasonable defaults for the environment

  errorMessage = (char*)malloc(MAX_ERRMSG_LEN);
  errorMessage[0]='\0';

  // null out all the fields to start in case
  // we return with an erroneous exit
  Pfout = NULL;
  Pfin = NULL;
  port = NULL;

  if (!(name = getenv(ENV_DEFAULT_DBASE)) &&
      !(name = fe_getauthname(errorMessage))) {
    sprintf(errorMessage,"FATAL: PGconn::PGconn() -- unable to determine a database name!\n");
    connStatus = CONNECTION_OK;
    return;
  }

  databaseName = dupstr(name);
  connStatus = connect(env, name);

}

PGconn::PGconn(PGenv* env, char* dbName)
{
  errorMessage = (char*)malloc(MAX_ERRMSG_LEN);
  errorMessage[0]='\0';
  // null out all the fields to start in case
  // we return with an erroneous exit
  Pfout = NULL;
  Pfin = NULL;
  port = NULL;

  databaseName = dupstr(dbName);

  connStatus = connect(env, dbName);
}

//
// dtor - also closes down the connection
//
PGconn::~PGconn()
{
  if (connStatus == CONNECTION_OK)
    {
      fputs("X\0",Pfout);
      fflush(Pfout);
    }
  if (errorMessage)    free(errorMessage);
  if (databaseName)    free(databaseName);
  if (port)    free(port);
  if (Pfout)    fclose(Pfout);
  if (Pfin)    fclose(Pfin);
}

//
// PGconn::connect - establish a connection to a backend through the postmaster
//
ConnStatusType 
PGconn::connect(PGenv* env, char* dbName)
{

    extern int errno;
    struct hostent *hp;

    StartupInfo  	startup;
    PacketBuf*          pacBuf;
    int			status;
    MsgType		msgtype;
    char*               user;
    int                 portName;
    PGresult            *res;

    //
    // Initialize the startup packet. 
    //
    // This data structure is used for the seq-packet protocol.  It
    // describes the frontend-backend connection.
    //
    //
/*    strncpy(startup.database,dbName,sizeof(dbName)+1); */
    strcpy(startup.database,dbName);
    user = fe_getauthname(errorMessage);
    strcpy(startup.user,user);
    startup.options[0]='\0';  // not used 
    strcpy(startup.tty,env->pqtty);
    startup.execFile[0]='\0';  // not used
    portName = env->pqport;

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

    if (!(hp = gethostbyname(env->pqhost)) || hp->h_addrtype != AF_INET) {
	(void) sprintf(errorMessage,
		       "PGconn::connect() unknown hostname: %s\n",
		       env->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)(env->pqport));
    
    // connect to the server 
    if ((port->sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	(void) sprintf(errorMessage,
	       "PGconn::connect() -- socket() failed: errno=%d\n",
	       errno);
	goto connect_errReturn;	
    }
    if (::connect(port->sock, (struct sockaddr *)&port->raddr,
		sizeof(port->raddr)) < 0) {
	(void) sprintf(errorMessage,
	       "PGconn:connect() -- connect() failed: errno=%d\n",
	       errno);
	goto connect_errReturn;	
    }
    
    // fill in the client address
    int laddrlen;
    if (getsockname(port->sock, (struct sockaddr *) &port->laddr,
		    &laddrlen) < 0) {
	(void) sprintf(errorMessage,
	       "PGconn:connect() -- getsockname() failed: errno=%d\n",
	       errno);
	goto connect_errReturn;	
    }
    
    // by this point, connection has been opened

    msgtype = fe_getauthsvc(errorMessage);

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

    // authenticate as required
    if (fe_sendauth(msgtype, port, env->pqhost, errorMessage) != STATUS_OK) {
      (void) sprintf(errorMessage,
	     "PGconn::connect() --  authentication failed with %s\n",
	       env->pqhost);
      goto connect_errReturn;	
    }
    
    // set up the socket file descriptors
    Pfout = fdopen(port->sock, "w");
    Pfin = fdopen(dup(port->sock), "r");
    if (!Pfout && !Pfin) {
	(void) sprintf(errorMessage,
	       "PGconn:connect() -- fdopen() failed: errno=%d\n",
	       errno);
      goto connect_errReturn;	
    }
/*
    if (getenv("LIBPQ_DEBUG"))
       Pfdebug = stderr;
    else
       Pfdebug = NULL;
  
    PQAsyncNotifyWaiting = 0;
    PQnotifies_init();
*/
    
    /* we have a connection to the backend now.
       send a blank query to ensure that the backend exists */
    res = exec(" ");
    printf("status = %s\nerrorMessage = %s\n", 
	   pgresStatus[res->status()], errorMessage);
    return CONNECTION_OK;

connect_errReturn:
    return CONNECTION_BAD;
}

//
// exec  -- send a query to the backend,  the 
//          CALLER is responsible for delete'ing the PGresult when done
//
// 
PGresult*
PGconn::exec(char* query)
{
  PGresult* res;
  int err;
  int id;
  char buffer[MAX_MESSAGE_LEN];
  char pname[MAX_MESSAGE_LEN]; // portal name

  pname[0] = '\0';

  if (connStatus == CONNECTION_BAD) {
      return new PGresult(PGRES_BAD_CONNECTION);
    }

  // clear the error string
    errorMessage[0]='\0';

  if (strlen(query) > MAX_MESSAGE_LEN-2)
    {
      sprintf(errorMessage,
	      "PGconn:exec -- query is too long.  Maximum query length is %d\n", MAX_MESSAGE_LEN -2);
      return new PGresult(PGRES_BAD_QUERY);
    }

  // the frontend-backend protocol uses 'Q' to designate queries
  sprintf(buffer,"%c%s",'Q',query);

  // send a query to the backend;
  if (pqPuts(buffer,Pfout) == 1) {
      (void) sprintf(errorMessage,
	     "PGconn:exec() while sending query:  %s\n-- fprintf to Pfout failed: errno=%d\n",query, errno);
      return new PGresult(PGRES_BAD_QUERY);
    }

  // loop forever because multiple messages, especially NOTICES,
  // can come back from the backend
  // NOTICES are output directly to stderr

  while (1) {

    // read the result id
    id = getc(Pfin);
    if (id == EOF) {
      // hmm,  no response from the backend-end, that's bad
      return new PGresult(PGRES_NO_RESPONSE);
    }

    switch (id) {
    case 'A': 
      sprintf(errorMessage,
	      "Asynchronous portals are not implemented");
      return new PGresult(PGRES_NONFATAL_ERROR);
      break;
      //  case 'R': // remark, no longer used by the backend
      //    break;
    case 'C': // portal query command, no tuples returned
      if (pqGets(buffer, MAX_MESSAGE_LEN, Pfin) == 1) {
	sprintf(errorMessage,
		"PGconn:exec() command completed, but return message from backend cannot be read");
	return new PGresult(PGRES_BAD_RESPONSE);
      } 
      else {
	// since backend may produce more than one result for some commands
	// need to poll until clear 
	// send an empty query down, and keep reading out of the pipe
	// until an 'I' is received.
	int clear = 0;

	pqPuts("Q ",Pfout); // send an empty query
	while (!clear)
	  {
	    if (pqGets(buffer,MAX_ERRMSG_LEN,Pfin) == 1)
	      clear = 1;
	    clear = (buffer[0] == 'I');
	  }
	return new PGresult(PGRES_COMMAND_OK);
      }
      break;
      // async portal notification, I don't think this is implemented 
      // in the backend
    case 'E': // error return
      if (pqGets(errorMessage, MAX_ERRMSG_LEN, Pfin) == 1) {
	(void) sprintf(errorMessage,
		       "PGconn:exec() error return detected from backend, but error message cannot be read");
      }
      return new PGresult(PGRES_FATAL_ERROR);
      break;
    case 'I': // empty query
      /* read and throw away the closing '\0' */
      int c;
      if ( (c = getc(Pfin)) != '\0') {
	fprintf(stderr,"error!, unexpected character %c following 'I'\n", c);
      }
      return new PGresult(PGRES_EMPTY_QUERY);
      break;
    case 'N': // notices from the backend
      if (pqGets(errorMessage, MAX_ERRMSG_LEN, Pfin) == 1) {
	sprintf(errorMessage,
		"PGconn:exec() error return detected from backend, but error message cannot be read");
      }
      else
	fprintf(stderr,"%s\n", errorMessage);
      break;
    case 'P': // synchronous (normal) portal
      pqGets(pname,MAX_MESSAGE_LEN,Pfin);  // read in the portal name;
fprintf(stderr,"PGconn:exec() -- Portal name = %s\n", pname);
      break;
    case 'T': // actual tuple results:
      return new PGresult(Pfin,this,pname);
      break;
    default:
      sprintf(errorMessage,
	      "unknown protocol character %c read from backend\n",
	      id);
      return new PGresult(PGRES_BAD_RESPONSE);
    }
} // while (1)


}

// 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);
}

// 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;
}


