/*
Copyright (c) 2010, Dirk Krause
All rights reserved.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.
* Redistributions in binary form must reproduce the above 
  opyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials
  provided with the distribution.
* Neither the name of the Dirk Krause nor the names of
  contributors may be used to endorse or promote
  products derived from this software without specific
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/


/** @file uacl.c	Useraud client module. */


/** In the uacl module. */
#define UACL_C	1
#include "useraudi.h"




#line 48 "uacl.ctr"




/**	@file	uacl.c	useraud daemon client library.
	This module contains functions to connect to the useraud backend
	daemon.
*/


/** Abbreviation. */
#define	UC	USERAUD_CONNECTION

/** Abbreviation. */
#define UR	USERAUD_RESPONSE

/** Abbreviation. */
#define UL	USERAUD_RESPONSE_LINE



/**	Compare two response lines by line number. This function is used
	to build a sorted collection of response lines.
	@param	l	Left response line.
	@param	r	Right response line.
	@param	cr	Comparison criteria (ignored).
	@return	Comparison result.
*/
int
uacl_compare_response_lines DK_P3(void *,l, void *,r, int,cr) {
  int back = 0;
  UL *ll, *lr;
  
  if(l) {
    if(r) {
      ll = (UL *)l; lr = (UL *)r;
      if(ll->lineno > lr->lineno) {
        back = 1;
      } else {
        if(ll->lineno < lr->lineno) {
	  back = -1;
	}
      }
      if(back == 0) {
        if(ll->text) {
	  if(lr->text) {
	    back = strcmp(ll->text, lr->text);
	  } else { back = 1; }
	} else {
	  if(lr->text) { back = -11; }
	}
      }
    } else { back = 1; }
  } else {
    if(r) { back = -1; }
  } 
  return back;
}



void
uacl_close DK_P1(UC *,u) {
  char *x;
  
  if(u) {
    if(u->sockname) {
      x = u->sockname; dk_delete(x);
    } u->sockname = NULL;
    u->ec = 0;
    dk_delete(u);
  } 
}



UC *
uacl_open DK_P1(char *,sockname) {
  UC *back = NULL;
  
  if(sockname) {
    back = dk_new(UC,1);
    if(back) {
      back->ec = 0;
      back->sockname = dkstr_dup(sockname);
      if(!(back->sockname)) {
        uacl_close(back); back = NULL;
      }
    }
  } 
  return back;
}



void
uacl_response_close DK_P1(UR *,u) {
  UL *l;
  char *x;
  
  if(u) {
    if(u->s_r) {
      if(u->i_r) {
        dksto_it_reset(u->i_r);
	while((l = (UL *)dksto_it_next(u->i_r)) != NULL) {
	  if(l->text) {	
	    x = l->text; dk_delete(x); l->text = NULL;
	  }
	  l->lineno = 0UL;
	  dk_delete(l);
	}
        dksto_it_close(u->i_r);
      }
      dksto_close(u->s_r);
    } u->s_r = NULL; u->i_r = NULL;
    dk_delete(u);
  } 
}



/**	Flush the data received so far.
	The data collected so far in \a b2 is saved as one line
	in the \a ur response data structure.
	@param	uc	Connection to the useraud daemon.
	@param	ur	Response structure.
	@param	b2	Buffer containing the received data.
	@param	b2u	Number of bytes used in \a b2.
	@param	li	Pointer to variable to receive the line number.
*/
static
void
fl DK_P5(UC *,uc, UR *,ur, char *,b2, size_t *,b2u,unsigned long *,li)
{
  char *x;
  UL *l;
  if(*b2u > 0) {
    l = dk_new(UL,1);
    if(l) {
      l->lineno = *li;
      l->text = dkstr_dup(b2);
      if(l->text) {
        if(!dksto_add(ur->s_r, (void *)l)) {
          x = l->text; dk_delete(x); l->text = NULL;
	  dk_delete(l);
	  uc->ec = UA_ERROR_MEMORY;
        }
      } else {
        dk_delete(l); uc->ec = UA_ERROR_MEMORY;
      }
    } else {
      uc->ec = UA_ERROR_MEMORY;
    }
    *b2u = 0; *li += 1UL;
  }
}



/**	Put a byte to the temporary buffer, flush if necessary.
	@param	uc	Connection to the useraud daemon.
	@param	ur	Response data structure.
	@param	c	Current character to add.
	@param	b2	Temporary buffer.
	@param	sz	Size of \a b2.
	@param	b2u	Pointer to variable to store number of bytes used in
			\a b2.
	@param	l	Pointer to variable storing the line number.
*/
static
void
pb DK_P7(UC *,uc,UR *,ur,char,c,char *,b2,size_t,sz,size_t *,b2u,unsigned long *,l)
{
  switch(c) {
    case '\0': case '\r': case '\n': {
      b2[*b2u] = '\0';
      fl(uc, ur, b2, b2u, l);
    } break;
    default: {
      if(*b2u < (sz - 1)) {
        b2[*b2u] = c;
	*b2u += 1;
      } else {
        b2[sz - 1] = '\0';
	fl(uc, ur, b2, b2u, l);
	b2[*b2u] = c;
	*b2u += 1;
      }
    } break;
  }
}


UR *
uacl_process DK_P2(UC *,uc, char *,request) {
  UR *back = NULL;
  /* UL *l; */
  struct sockaddr_un	soun;
  int newec = 0;			/* New error code. */
  int datafound = 0;			/* Flag: Any response. */
  int sock;				/* Client socket. */
  unsigned long lineno = 0UL;		/* Current line number. */
  int cc = 0;				/* Flag: Can continue. */
  int res;				/* Read/write result. */
  char b[USERAUD_LINESIZE];
  char b2[USERAUD_LINESIZE];
  size_t i;
  size_t b2_used = 0;
  
  if((uc) && (request)) {				
    back = dk_new(UR,1);
    if(back) {						
      back->s_r = dksto_open(0);
      if(back->s_r) {					
        dksto_set_comp(back->s_r, uacl_compare_response_lines, 0);
        back->i_r = dksto_it_open(back->s_r);
	if(back->i_r) {
	  if(strlen(uc->sockname) < 108) {
	    soun.sun_family = AF_UNIX;
	    strcpy(soun.sun_path, uc->sockname);
	    sock = socket(PF_UNIX, SOCK_STREAM, 0);
	    if(sock > -1) {				
	      if(connect(sock, (struct sockaddr *)(&soun), SZSOUN) == 0) {
	        
	        send(sock, request, (1+strlen(request)), 0);
		shutdown(sock, SHUT_WR);
		cc = 1; b2_used = 0; DK_MEMRES(b2,sizeof(b2)) ;
		while(cc) {
		  cc = 0;
		  res = recv(sock, b, sizeof(b), 0);
		  if(res > 0) {		
		    datafound = 1; cc = 1;
		    for(i = 0; i < res; i++) { 
		      pb(uc, back, b[i], b2, sizeof(b2), &b2_used, &lineno);
		    }
		  }
		}
		if(b2_used > 0) {
		  if(b2_used < sizeof(b2)) {
		    b2[b2_used] = '\0';
		  } else {
		    b2[sizeof(b2) - 1] = '\0';
		  }
		  fl(uc, back, b2, &b2_used, &lineno);
		}
	      } else { uc->ec = UA_ERROR_CONNECT_FAILED; }
	      close(sock);
	    } else { uc->ec = UA_ERROR_SOCKET_FAILED; }
	  } else { uc->ec = UA_ERROR_SOCKET_NAME_TOO_LONG; }
	} else { uc->ec = UA_ERROR_MEMORY; }
      } else { uc->ec = UA_ERROR_MEMORY; }
    } else { uc->ec = UA_ERROR_MEMORY; }
    if((newec) || (!datafound)) {
      uacl_response_close(back); back = NULL;
      if(newec) {
        uc->ec = newec;
      } else {
        uc->ec = UA_ERROR_NO_RESPONSE;
      }
    }
  } else {				
    uc->ec = UA_ERROR_ILLEGAL_ARGUMENTS;
  } 
  return back;
}


void
uacl_response_reset DK_P1(UR *,u) {
  
  if(u) {
    if((u->s_r) && (u->i_r)) {
      dksto_it_reset(u->i_r);
    }
  } 
}


char *
uacl_response_line DK_P1(UR *,u) {
  char *back = NULL;
  UL *l;
  
  if(u) {
    if((u->s_r) && (u->i_r)) {
      l = (UL *)dksto_it_next(u->i_r);
      if(l) {
        back = l->text;
      }
    }
  } 
  return back;
}



