/* Copyright (C) 1997 Aladdin Enterprises.  All rights reserved.
  
  This file is part of GNU Ghostscript.
  
  GNU Ghostscript is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility to
  anyone for the consequences of using it or for whether it serves any
  particular purpose or works at all, unless he says so in writing.  Refer to
  the GNU General Public License for full details.
  
  Everyone is granted permission to copy, modify and redistribute GNU
  Ghostscript, but only under the conditions described in the GNU General
  Public License.  A copy of this license is supposed to have been given to
  you along with GNU Ghostscript so you can know your rights and
  responsibilities.  It should be in a file named COPYING.  Among other
  things, the copyright notice and this notice must be preserved on all
  copies.
  
  Aladdin Enterprises is not affiliated with the Free Software Foundation or
  the GNU Project.  GNU Ghostscript, as distributed by Aladdin Enterprises,
  does not depend on any other GNU software.

  However Display Ghostscript System depends on GLib. */

/* $Id: dgsconn.c,v 1.5 2000/05/02 20:39:47 masata-y Exp $ */
/* Display Ghostscript connection and buffer functions */


#include <glib.h>		/* For Hash tables */
#include "string_.h"
#include "gsmemory.h"
#include "gsmalloc.h"

/* the X people can't make their headers define what they need automatically... */
#define NEED_REPLIES
#define NEED_EVENTS
#include "x_.h"			/* standard X include files */
#include "dpsNXprops.h"		/* has the property definitions */
#include "DPSCAPproto.h"

/* network headers for the agent */
#include <netinet/in.h>		/* for ntohs */
#include <netdb.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#ifndef CSDPS_UNIX_PATH
#ifdef hpux
#define CSDPS_UNIX_DIR      "/usr/spool/sockets/DPSNX"
#define CSDPS_UNIX_PATH     "/usr/spool/sockets/DPSNX/"
#else /* !hpux */
#define CSDPS_UNIX_DIR      "/tmp/.DPSNX-unix"
#define CSDPS_UNIX_PATH     "/tmp/.DPSNX-unix/AGENT"
#endif /* hpux */
#endif /* CSDPS_UNIX_DIR */

#include "dgs.h"
#include "dgsconn.h"
#include "dgsmisc.h"
#include "dgsagent.h"

/* Constants */
#define BUFLEN 65536
#define HOSTNAME_LEN 64

private int conn_check_old_socket(struct sockaddr_un * address);
private void buf_dump(gsdpsx_conn_buf * buf, int length, unsigned char op);
private void buf_input_sanity_check(gsdpsx_conn_buf * buf);
private Atom billboard_atom;

/* --- (conn_) connection setting up functions -- */
void
conn_sock_initialize(void)
{

}

void
conn_sock_finalize(void *sockaddr, enum conn_type type)
{
    if (type == UNIXCONN)
	(void)unlink(((struct sockaddr_un *)sockaddr)->sun_path);
}

int
conn_hostname_get(char *hostname, enum conn_type type)
{
    if (type == UNIXCONN) {
	if (gethostname(hostname, sizeof(char[HOSTNAME_LEN]))) {
	    perror("gethostname: ");
	    return -1;
	}
    } else if (type == TCPCONN) {
	/*! if we use the full hostname, the client library will 
	   switch us to UNIX socket */
	/* strcpy(hostname, "localhost"); */
	if (gethostname(hostname, sizeof(char[HOSTNAME_LEN]))) {
	    perror("gethostname: ");
	    return -1;
	}
    }

    return 0;
}

int
conn_server_create(void *sockaddr, enum conn_type type)
{
    int server_socket;

    if (type == UNIXCONN) {
	struct stat st;

	umask(0);
	mkdir(CSDPS_UNIX_DIR, (S_IRWXU | S_IRWXG | S_IRWXO));
	chmod(CSDPS_UNIX_DIR, (S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX));

	if (stat(CSDPS_UNIX_DIR, &st)) {
	    perror("stat: ");
	    return -1;
	} else if (!S_ISDIR(st.st_mode)) {
	    fprintf(stderr, "Socket directory: %s is not a directory\n",
		    CSDPS_UNIX_DIR);
	    return -1;
	}
	server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
	if (server_socket < 0) {
	    perror("[UNIXCONN] server_socket's socket(): ");
	    return -1;
	}
	((struct sockaddr_un *)sockaddr)->sun_family = AF_UNIX;
    } else if (type == TCPCONN) {
	server_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (server_socket < 0) {
	    perror("[TCPCONN] server_socket's socket(): ");
	    return -1;
	}
	((struct sockaddr_in *)sockaddr)->sin_family = AF_INET;
	((struct sockaddr_in *)sockaddr)->sin_addr.s_addr = INADDR_ANY;
    } else {
	fprintf(stderr,
		"(conn_server_create)Wrong connection type %d\n", type);
	server_socket = -1;
    }
    return server_socket;
}

private int 
conn_check_old_socket(struct sockaddr_un * address) 
{
    struct stat st;
    int sockfd;
    int len;
    int result;

    if (stat(address->sun_path, &st) != 0) 
	return 0;

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    len = sizeof(*address);

    result = connect(sockfd, (struct sockaddr *)address, len); 
    close(sockfd);
    if (result != 0) {
	if (unlink(address->sun_path) == 0) 
	    return 0;
	else 
	    return -1;
    }
    else {
	return -1;
    }
}

int
conn_server_bind(int server_socket,
		 void *sockaddr, int base_port, enum conn_type type)
{
    int sockaddr_size;
    int result;

    if (type == UNIXCONN) {
	sprintf(((struct sockaddr_un *)sockaddr)->sun_path,
		"%s_%d", CSDPS_UNIX_PATH, base_port);
	sockaddr_size = (int)sizeof(struct sockaddr_un);

	result = conn_check_old_socket((struct sockaddr_un *)sockaddr);
	if (result != 0) {
	    fprintf(stderr, "Not able to remove the old socket!\n");
	    fprintf(stderr, "There might be another server running!\n");
	    return -1;
	}
    } else if (type == TCPCONN) {
	((struct sockaddr_in *)sockaddr)->sin_port = htons(base_port);
	sockaddr_size = (int)sizeof(struct sockaddr_in);
    } else {
	fprintf(stderr, "(conn_server_bind)Wrong connection type %d\n", type);
	return -1;
    }

    if (0 != bind(server_socket, (struct sockaddr *)sockaddr, sockaddr_size)) {
	fprintf(stderr,
		"[%s]Unable to bind a port[%d] for the server socket.\n",
		util_conn_type_enum2str(type), base_port);
	if (type == UNIXCONN)
	    fprintf(stderr,
		    "Check another dgs server and remove a file in %s "
		    "associated with port number: %d.\n",
		    CSDPS_UNIX_DIR, base_port);
	else if (type == TCPCONN)
	    fprintf(stderr, "Check another dgs server process.\n");
	return -1;
    } else
	return 0;
}

void
conn_server_close(int server_socket)
{
    close(server_socket);
}

int
conn_base_port_find(void)
{
    int requested_port = DGS_PORT;

    if (requested_port == 0) {
	struct servent *pent;

	pent = getservbyname(DPS_NX_SERV_NAME, "tcp");
	return (pent ? ntohs(pent->s_port) : CSDPSPORT);
    } else
	return requested_port;
}

/* Note: You can inspect X server atom informations by `xprop' 
   command. See man page for more details. */
int
conn_atom_setup(Display * dpy,
		Window win_unix, int port_unix, Window win_tcp, int port_tcp)
{
    /* retrieve all the Atoms for the properites we need to set beforehand */
    Atom willingness_atom, open_license_atom;
    int max_willingness = XDPSNX_MAX_WILLINGNESS;
    char license_str[64];
    int transport_info[2];
    char hostname[HOSTNAME_LEN];

    billboard_atom = XInternAtom(dpy, XDPSNX_BILLBOARD_PROP, FALSE);
    willingness_atom = XInternAtom(dpy, XDPSNX_WILLINGNESS_PROP, FALSE);

    if (!billboard_atom || !willingness_atom) {
	fprintf(stderr, "unable to get X11 Atoms.\n");
	return -1;
    }

    /* put the needed properties into win */
    /* set willingness... XDPSNX_MAX_WILLINGNESS is very willing */
    XChangeProperty(dpy, win_unix,
		    willingness_atom, XA_INTEGER, 32,
		    PropModeReplace, (unsigned char *)&max_willingness, 1);
    XChangeProperty(dpy, win_tcp,
		    willingness_atom, XA_INTEGER, 32,
		    PropModeReplace, (unsigned char *)&max_willingness, 1);


    /* we only support open license, but that should work with any client */
    sprintf(license_str, "%s:%d", LICENSE_METHOD_OPEN, OPEN_LICENSE_VERSION);
    open_license_atom = XInternAtom(dpy, license_str, False);
    XChangeProperty(dpy, win_unix,
		    XInternAtom(dpy, XDPSNX_LICENSE_METHOD_PROP, False),
		    XA_ATOM, 32,
		    PropModeReplace, (unsigned char *)&open_license_atom, 1);
    XChangeProperty(dpy, win_tcp,
		    XInternAtom(dpy, XDPSNX_LICENSE_METHOD_PROP, False),
		    XA_ATOM, 32,
		    PropModeReplace, (unsigned char *)&open_license_atom, 1);

    /* set the transport information */
    transport_info[0] = XDPSNX_TRANS_UNIX;
    transport_info[1] = port_unix;
    XChangeProperty(dpy, win_unix,
		    XInternAtom(dpy, XDPSNX_TRANSPORT_INFO_PROP, False),
		    XA_INTEGER, 32,
		    PropModeReplace, (unsigned char *)&transport_info, 2);

    transport_info[0] = XDPSNX_TRANS_TCP;
    transport_info[1] = port_tcp;
    XChangeProperty(dpy, win_tcp,
		    XInternAtom(dpy, XDPSNX_TRANSPORT_INFO_PROP, False),
		    XA_INTEGER, 32,
		    PropModeReplace, (unsigned char *)&transport_info, 2);

    /* set the hostname */
    if (conn_hostname_get(hostname, UNIXCONN) == -1)
	return -1;

    XChangeProperty(dpy, win_unix,
		    XInternAtom(dpy, XDPSNX_HOST_NAME_PROP, False),
		    XA_STRING, 8,
		    PropModeReplace,
		    (unsigned char *)hostname, strlen(hostname));

    if (conn_hostname_get(hostname, TCPCONN) == -1)
	return -1;
    XChangeProperty(dpy, win_tcp,
		    XInternAtom(dpy, XDPSNX_HOST_NAME_PROP, False),
		    XA_STRING, 8,
		    PropModeReplace,
		    (unsigned char *)hostname, strlen(hostname));

    /* and append us into the property list */
    /* The order of appending is important.
       We need to prepend win_tcp frist.
       If we prepend win_unix first, remote client
       cannot connect to dgs server 
       -- Masatake */
    XChangeProperty(dpy, DefaultRootWindow(dpy),
		    billboard_atom, XA_WINDOW, 32,
		    PropModePrepend, (unsigned char *)&win_tcp, 1);
    XChangeProperty(dpy, DefaultRootWindow(dpy),
		    billboard_atom, XA_WINDOW, 32,
		    PropModePrepend, (unsigned char *)&win_unix, 1);
    return 0;
}

private int conn_atom_cleanup_window(Window win,

				     Window * data, int numAgents);

void
conn_atom_cleanup(void)
{
    if (billboard_atom_needs_cleanup) {
	/* remove ourselves from the availability list */
	/* we *MUST* do this after the ConnSetup, because the client has
	   the display locked at this time, it will deadlock X if we
	   XGrabServer here */
	Window *data;
	Atom returnType;
	unsigned long numAgents, bytesLeft;
	int format, i;

	billboard_atom_needs_cleanup = 0;
	fprintf(stderr, "attempting to grab server.\n");
	fflush(stderr);
	XGrabServer(shared_dpy);
	fprintf(stderr, "grabbed server.\n");
	fflush(stderr);
	i = XGetWindowProperty(shared_dpy, DefaultRootWindow(shared_dpy), billboard_atom, 0,	/* starting offset */
			       ((((unsigned long)-1) >> 1) / 4),	/* maximum to return */
			       False,	/* don't delete */
			       XA_WINDOW,
			       &returnType,
			       &format,
			       &numAgents,
			       &bytesLeft, (unsigned char **)&data);
	if (i == Success && returnType == XA_WINDOW) {
	    if (numAgents <= 1) {
		fprintf(stderr, "deleteing property\n");
		XDeleteProperty(shared_dpy,
				DefaultRootWindow(shared_dpy),
				billboard_atom);
	    } else {
		int unix_remove, tcp_remove;

		fprintf(stderr, "found %lu agents\n", numAgents);
		/* remove the agent from the list */
		unix_remove = conn_atom_cleanup_window(server_win_unix,
						       data, numAgents--);
		tcp_remove = conn_atom_cleanup_window(server_win_tcp,
						      data, numAgents--);
		if ((tcp_remove == 0) || (unix_remove == 0)) {
		  if (numAgents == 0) 
		    XDeleteProperty(shared_dpy,
				    DefaultRootWindow(shared_dpy),
				    billboard_atom);
		  else
		    XChangeProperty(shared_dpy, DefaultRootWindow(shared_dpy),
				    billboard_atom, XA_WINDOW, 32,
				    PropModeReplace,
				    (unsigned char *)data, numAgents);
		    fprintf(stderr, "Removed agent from list\n");
		} else {
		    fprintf(stderr,
			    "Cannot find our window in the atom list\n");
		    fprintf(stderr, "Something wrong\n");
		}
	    }
	}
	XUngrabServer(shared_dpy);
	XFlush(shared_dpy);
	fprintf(stderr, "done with property munging\n");
	fflush(stderr);
    }				/* end removing property */
}

private int
conn_atom_cleanup_window(Window win, Window * data, int numAgents)
{
    int i;
    int found = 0;

    for (i = 0; i < numAgents; i++) {
	if (data[i] == win) {
	    fprintf(stderr, "found our window at %d\n", i);
	    numAgents--;
	    found = 1;
	    break;
	}
    }
    if (found == 0)
	return -1;		/* Not Found */

    /* Pack the data */
    for (; i < numAgents; i++) {
      data [i] = data[i+1];
    }
    return 0;
}
int
conn_insock_accept(int server_socket, enum conn_type type)
{
    void *in_sockaddr = NULL;
    struct sockaddr_un in_sockaddr_un;
    struct sockaddr_in in_sockaddr_in;
    int in_sock, addrlen;

    if (type == UNIXCONN) {
	addrlen = sizeof(struct sockaddr_un);

	in_sockaddr = &in_sockaddr_un;
    } else if (type == TCPCONN) {
	addrlen = sizeof(struct sockaddr_in);

	in_sockaddr = &in_sockaddr_in;
    }
    in_sock = accept(server_socket, (struct sockaddr *)in_sockaddr, &addrlen);

    if (in_sock < 0) {
	perror("accept server socket:");
	return -1;
    } else
	return in_sock;
}

void
conn_insock_close(int insock)
{
    close(insock);
}

/* --- buffer functions --- */

gsdpsx_conn_buf *
buf_init(void)
{
    static gsdpsx_conn_buf conn_buf;

    conn_buf.buf = gs_malloc(BUFLEN, 1, "buf_init");
    if (!(conn_buf.buf)) {
	fprintf(stderr, "Memory allocation is failed.\n");
	return 0;
    }
    conn_buf.curpos = conn_buf.buf;
    return &conn_buf;
}

void
buf_reset(gsdpsx_conn_buf * buf)
{
    buf->curpos = buf->buf;
    buf->len = 0;
    buf->isfrag = 0;
}

int
buf_recv(gsdpsx_conn_buf * buf, gsdpsx_conn_info * conn)
{
    return recv(conn->sock, buf->curpos, BUFLEN - (buf->curpos - buf->buf),
		0);
}

void
buf_input_set(gsdpsx_conn_buf * buf, unsigned char *start, int length)
{
    buf->inputstart = start;
    buf->inputend = buf->inputstart + length;
}
private int
buf_input_length(gsdpsx_conn_buf * buf)
{
    return (buf->inputend - buf->inputstart);
}

void
buf_input_forward(gsdpsx_conn_buf * buf, DGS_stream_cursor_write * pw)
{
    int tocopy;
    int stream_size = pw->limit - pw->ptr;

    {
	const int dump_if_op = 0;

	if (dump_if_op)
	    buf_dump(buf, stream_size, dump_if_op);
    }

    tocopy = min(buf_input_length(buf), stream_size);
    memmove(pw->ptr + 1, buf->inputstart, tocopy);
    pw->ptr += tocopy;
    buf->inputstart += tocopy;
    buf_input_sanity_check(buf);

}

private void
buf_input_sanity_check(gsdpsx_conn_buf * buf)
{
    if (buf->inputstart >= buf->inputend)
	buf_input_set(buf, NULL, 0);
}

private void
buf_dump(gsdpsx_conn_buf * buf, int length, unsigned char op)
{
    int i;
    int found = 0;

    for (i = 0; i < length; i++) {
	if (buf->inputstart[i] == op)
	    found = 1;
    }
    if (found) {
	fprintf(stderr, "bin dump start:[%d]{", length);
	for (i = 0; i < length; i++)
	    fprintf(stderr, "%d,", buf->inputstart[i]);
	fprintf(stderr, "};\n");
    }
}
void
buf_pack(gsdpsx_conn_buf * buf, unsigned char *cur, int len)
{
    dprintf1("Fragment of %d bytes\n", len);
    memmove(buf->buf, cur, len);
    buf_move_cursor(buf, buf->buf + len, len);
    buf->isfrag = 1;
}

void
buf_move_cursor(gsdpsx_conn_buf * buf, unsigned char *cur, int len)
{
    buf->curpos = cur;
    buf->len = len;
}

void
buf_finalize(gsdpsx_conn_buf * buf)
{
    gs_free(buf->buf, 1, sizeof(BUFLEN), "buf_finalize");
    buf->buf = 0;
}
