#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include "atarierr.h"
#include "sockerr.h"
#include "kerbind.h"
#include "mintsock.h"
#include "gs_descr.h"


/* Slight Hack(TM):  The kernel-mode socket library's socket() function
 * returns a global file descriptor, which doesn't seem to be easily usable
 * with Fselect().  gs_socket_hack() reimplements socket() exactly, except
 * for the global part. */
static int gs_socket_hack(int domain, int type, int proto)
{
  struct socket_cmd cmd;
  int sockfd, r;

  sockfd = f_open("U:\\DEV\\SOCKET", O_RDWR);
  if (sockfd < 0)
    return sockfd;

  cmd.cmd = SOCKET_CMD;
  cmd.domain = domain;
  cmd.type = type;
  cmd.protocol = proto;

  r = f_cntl(sockfd, (long)&cmd, SOCKETCALL);
  if (r < 0) {
    f_close(sockfd);
    return r;
  }
  return sockfd;
}

struct GS *gs_create()
{
  CIB dummy = {0, 0, 0, 0L, 0L, 0};
  struct GS *gs = kmalloc(sizeof(struct GS));

  LOG(DBG_TRACE, "In gs_create()");
  if (!gs) {
    LOG(DBG_ERROR, "In gs_create(): unable to get memory for GS devinfo");
    return 0;
  }
  gs->sock_fd = 0;
  gs->flags = GS_NOSOCKET;
  gs->cib = dummy;
  return gs;
}

void gs_release(struct GS *gs)
{
  LOG(DBG_TRACE, "In gs_release()");
  if (!(gs->flags & GS_NOSOCKET))
    f_close(gs->sock_fd);
  kfree(gs);
}

int gs_xlate_error(int err, const char *funcname)
{
  int ret;

  switch (err) {
    case EINVFN:
    case EOPNOTSUPP:
      ret = E_NOROUTINE; break;
    case ENHNDL:
      ret = E_NOCCB; break;
    case EIHNDL:
    case ENOTSOCK:
      ret = E_BADHANDLE; break;
    case ENSMEM:
    case EGSBF:
      ret = E_NOMEM; break;
    case ERANGE:
    case EADDRINUSE:
    case EADDRNOTAVAIL:
      ret = E_PARAMETER; break;
    case ECONNRESET:
      ret = E_RRESET; break;
    case ETIMEDOUT:
      ret = E_CNTIMEOUT; break;
    case ECONNREFUSED:
      ret = E_REFUSE; break;
    case ENETDOWN:
    case ENETUNREACH:
    case EHOSTDOWN:
    case EHOSTUNREACH:
      ret = E_UNREACHABLE; break;
    default:
      ret = -1000 + err; break;
  }

  if (funcname)
    LOG(DBG_ERROR, "%s() returns %E", funcname, ret);
  return ret;
}

int gs_accept(struct GS *gs)
{
  int in_fd;
  int ret;
  struct sockaddr_in addr, addr2;
  size_t addr_size = sizeof(struct sockaddr_in);
  int fdflags;

  LOG(DBG_TRACE, "In gs_accept(%p)", (void *)gs);

  if (!gs || gs->flags & GS_NOSOCKET || !(gs->flags & GS_LISTENING)) {
    LOG(DBG_ERROR, "In gs_accept(%p): bad handle", (void *)gs);
    return E_BADHANDLE;
  }

  /* Temporarily switch to non-blocking mode */
  fdflags = f_cntl(gs->sock_fd, 0L, F_GETFL);
  if (fdflags < 0) {
    LOG(DBG_ERROR, "In gs_accept(%p): Fcntl(F_GETFL) returns %e",
	(void *)gs, fdflags);
    return gs_xlate_error(fdflags, "gs_accept");
  }
  ret = f_cntl(gs->sock_fd, fdflags | O_NDELAY, F_SETFL);
  if (ret < 0) {
    LOG(DBG_ERROR, "In gs_accept(%p): Fcntl(F_SETFL) returns %e",
	(void *)gs, ret);
    return gs_xlate_error(ret, "gs_accept");
  }

  in_fd = accept(gs->sock_fd, (struct sockaddr *)&addr, &addr_size);

  /* And switch back */
  ret = f_cntl(gs->sock_fd, fdflags, F_SETFL);
  if (ret < 0) {
    LOG(DBG_ERROR, "In gs_accept(%p,): Fcntl(F_SETFL) returns %e",
	(void *)gs, ret);
    return gs_xlate_error(ret, "gs_accept");
  }

  if (in_fd == EWOULDBLOCK) {
    LOG(DBG_SYSCALL,
	"In gs_accept(%p): no connections arrived; returning %d",
	(void *)gs, E_LISTEN);
    return E_LISTEN;
  }
  if (in_fd < 0) {
    LOG(DBG_ERROR, "In gs_accept(%p): accept() returns %e",
	(void *)gs, in_fd);
    return gs_xlate_error(in_fd, "gs_accept");;
  }

  /* Fill in the CIB.  Part of the data we need came back from accept();
   * get the rest via getsockname(). */
  addr_size = sizeof(struct sockaddr_in);
  if ((ret = getsockname(in_fd, (struct sockaddr *)&addr2, &addr_size)) < 0) {
    LOG(DBG_ERROR, "In gs_accept(%p): getsockname() returns %e",
	(void *)gs, ret);
    return gs_xlate_error(ret, "gs_accept");
  }

  gs->cib.protocol = P_TCP;
  gs->cib.lport = addr2.sin_port;
  gs->cib.rport = addr.sin_port;
  gs->cib.rhost = addr.sin_addr.s_addr;
  gs->cib.lhost = addr2.sin_addr.s_addr;

  /* In STiK, an accept() "eats" the listen()'ed socket; we emulate that by
   * closing it and replacing it with the newly accept()'ed socket. */
  f_close(gs->sock_fd);
  gs->sock_fd = in_fd;

  gs->flags &= ~GS_LISTENING;
  LOG(DBG_TRACE, "gs_accept(%p) returns 0", (void *)gs);
  return 0;
}

#if 0
int gs_establish(struct GS *gs)
{
  unsigned long wfs;
  int n;

  LOG(DBG_TRACE, "In gs_establish(%p)", (void *)gs);

  if (!gs || gs->flags & GS_NOSOCKET || !(gs->flags & GS_PEND_OPEN)) {
    LOG(DBG_ERROR, "In gs_establish(%p): bad handle", (void *)gs);
    return E_BADHANDLE;
  }

  wfs = 1UL << gs->sock_fd;
  n = f_select(1, 0L, &wfs, 0L);

  if (n > 0) {
    gs->flags &= ~GS_PEND_OPEN;
    LOG(DBG_TRACE, "gs_establish(%p) returns 0", (void *)gs);
    return 0;
  } else if (n < 0) {
    LOG(DBG_ERROR, "In gs_establish(%p): Fselect() returns %e", (void *)gs, n);
    return gs_xlate_error(n, "gs_establish");
  } else {
    LOG(DBG_TRACE, "In gs_establish(%p): timeout", (void *)gs);
    return E_USERTIMEOUT;
  }
}
#endif

long gs_connect(FILEPTR *fp, union GS_cmd *args)
{
  int fd;
  struct sockaddr_in laddr, raddr;
  size_t addr_size = sizeof(struct sockaddr_in);
  int retval;
  struct GS *gs = (struct GS *)fp->devinfo;
#if 0
  long fdflags;
  int pending = 0;
#endif

  LOG(DBG_SYSCALL, "In gs_connect(%p, {%A, %d}, {%A, %d})",
      (void *)fp, args->TCP_open_cmd.lhost, args->TCP_open_cmd.lport,
      args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport);

  if (!gs || !(gs->flags & GS_NOSOCKET)) {
    LOG(DBG_ERROR, "In gs_connect(%p, {%A, %d}, {%A, %d}): bad handle",
	(void *)fp, args->TCP_open_cmd.lhost, args->TCP_open_cmd.lport,
	args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport);
    return E_BADHANDLE;
  }

  raddr.sin_family = AF_INET;
  raddr.sin_addr.s_addr = args->TCP_open_cmd.rhost;
  raddr.sin_port = args->TCP_open_cmd.rport;
  laddr.sin_family = AF_INET;
  laddr.sin_addr.s_addr = args->TCP_open_cmd.lhost;
  laddr.sin_port = args->TCP_open_cmd.lport;

  fd = gs_socket_hack(AF_INET, SOCK_STREAM, 0);
  if (fd < 0) {
    LOG(DBG_ERROR,
	"In gs_connect(%p, {%A, %d}, {%A, %d}): socket() returns %e",
	(void *)fp, args->TCP_open_cmd.lhost, args->TCP_open_cmd.lport,
	args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport, fd);
    return gs_xlate_error(fd, "gs_connect");
  }
#if 0
  fdflags = f_cntl(fd, 0L, F_GETFL);
  if (fdflags < 0) {
    LOG(DBG_ERROR, "In gs_connect(%p, %A, %d): Fcntl(F_GETFL) returns %e",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport,
	fdflags);
    return gs_xlate_error(fdflags, "gs_connect");
  }
  retval = f_cntl(fd, fdflags | O_NDELAY, F_SETFL);
  if (retval < 0) {
    LOG(DBG_ERROR, "In gs_connect(%p, %A, %d): Fcntl(F_SETFL) returns %e",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport,
	fdflags);
    return gs_xlate_error(retval, "gs_connect");
  }
#endif // 0

  retval = bind(fd, (struct sockaddr *)&laddr, addr_size);
  if (retval < 0) {
    LOG(DBG_ERROR,
	"In gs_connect(%p, {%A, %d}, {%A, %d}): bind() returns %e",
	(void *)fp, args->TCP_open_cmd.lhost, args->TCP_open_cmd.lport,
	args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport, retval);
    return gs_xlate_error(retval, "gs_connect");
  }

  if (args->TCP_open_cmd.rhost == 0) {
    retval = listen(fd, 5);
    if (retval < 0) {
      LOG(DBG_ERROR,
	  "In gs_connect(%p, {%A, %d}, {%A, %d}): listen() returns %e",
	  (void *)fp, args->TCP_open_cmd.lhost, args->TCP_open_cmd.lport,
	  args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport, retval);
      return gs_xlate_error(retval, "gs_connect");
    }
  } else {
    retval = connect(fd, (struct sockaddr *)&raddr, addr_size);
#if 0
    if (retval == EINPROGRESS) {
      pending = 1;
    } else
#endif
    if (retval < 0) {
      LOG(DBG_ERROR,
	  "In gs_connect(%p, {%A, %d}, {%A, %d}): connect() returns %e",
	  (void *)fp, args->TCP_open_cmd.lhost, args->TCP_open_cmd.lport,
	  args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport, retval);
      return gs_xlate_error(retval, "gs_connect");
    }
  }

  /* Fill in the CIB.  Data for the remote end was provided by the parms in
   * the connect() case, and filled in as zero by the stub in the listen()
   * case; data for the local end was probably changed by the bind(), so
   * update it via getsockname(). */
  retval = getsockname(fd, (struct sockaddr *)&laddr, &addr_size);
  if (retval < 0) {
      LOG(DBG_ERROR,
	  "In gs_connect(%p, {%A, %d}, {%A, %d}): getsockname() returns %e",
	  (void *)fp, args->TCP_open_cmd.lhost, args->TCP_open_cmd.lport,
	  args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport, retval);
    return gs_xlate_error(retval, "gs_connect");
  }
  gs->cib.protocol = P_TCP;
  gs->cib.rhost = raddr.sin_addr.s_addr;
  gs->cib.rport = raddr.sin_port;
  gs->cib.lhost = laddr.sin_addr.s_addr;
  gs->cib.lport = laddr.sin_port;

  gs->sock_fd = fd;
  if (args->TCP_open_cmd.rhost == 0) {
    gs->flags = GS_LISTENING;
  } else {
#if 0
    gs->flags = pending ? GS_PEND_OPEN : 0;
#else
    gs->flags = 0;
#endif
  }
  LOG(DBG_SYSCALL, "gs_connect(%p, {%A, %d}, {%A, %d}) returns 0",
      (void *)fp, args->TCP_open_cmd.lhost, args->TCP_open_cmd.lport,
      args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport, retval);
  return 0;
}

#if 0	/* This is now covered by gs_connect(). */
long gs_listen(FILEPTR *fp, union GS_cmd *args)
{
  int fd;
  struct sockaddr_in addr, addr2;
  size_t addr_size = sizeof(addr);
  int retval;
  struct GS *gs = (struct GS *)fp->devinfo;
#if 0
  long fdflags;
#endif

  LOG(DBG_SYSCALL, "In gs_listen(%p, %A, %d)",
      (void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport);

  if (!gs || !(gs->flags & GS_NOSOCKET)) {
    LOG(DBG_ERROR, "In gs_listen(%p, %A, %d): bad handle",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport);
    return E_BADHANDLE;
  }

  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = args->TCP_open_cmd.rport;

  fd = gs_socket_hack(AF_INET, SOCK_STREAM, 0);
  if (fd < 0) {
    LOG(DBG_ERROR, "In gs_listen(%p, %A, %d): socket() returns %e",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport, fd);
    return gs_xlate_error(fd, "gs_listen");
  }
#if 0
  fdflags = f_cntl(fd, 0L, F_GETFL);
  if (fdflags < 0) {
    LOG(DBG_ERROR, "In gs_listen(%p, %A, %d): Fcntl(F_GETFL) returns %e",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport,
	fdflags);
    return gs_xlate_error(fdflags, "gs_listen");
  }
  retval = f_cntl(fd, fdflags | O_NDELAY, F_SETFL);
  if (retval < 0) {
    LOG(DBG_ERROR, "In gs_listen(%p, %A, %d): Fcntl(F_GETFL) returns %e",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport,
	fdflags);
    return gs_xlate_error(retval, "gs_listen");
  }
#endif

  retval = bind(fd, (struct sockaddr *)&addr, addr_size);
  if (retval < 0) {
    LOG(DBG_ERROR, "In gs_listen(%p, %A, %d): bind() returns %e",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport,
	retval);
    return gs_xlate_error(retval, "gs_listen");
  }
  retval = listen(fd, 5);
  if (retval < 0) {
    LOG(DBG_ERROR, "In gs_listen(%p, %A, %d): listen() returns %e",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport,
	retval);
    return gs_xlate_error(retval, "gs_listen");
  }

  /* Fill in the CIB.  Data for the remote end is (obviously) not yet
   * available, so fill it in with zeros for how; data for the local end we
   * get via getsockname(). */
  addr_size = sizeof(struct sockaddr_in);
  retval = getsockname(fd, (struct sockaddr *)&addr2, &addr_size);
  if (retval < 0) {
    LOG(DBG_ERROR, "In gs_listen(%p, %A, %d): getsockname() returns %e",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport,
	retval);
    return gs_xlate_error(retval, "gs_listen");
  }
  gs->cib.protocol = P_TCP;
  gs->cib.lport = addr2.sin_port;
  gs->cib.rport = 0;
  gs->cib.rhost = INADDR_ANY;
  gs->cib.lhost = addr2.sin_addr.s_addr;

  gs->sock_fd = fd;
  gs->flags = GS_LISTENING;
    LOG(DBG_SYSCALL, "gs_listen(%p, %A, %d) returns 0",
	(void *)fp, args->TCP_open_cmd.rhost, args->TCP_open_cmd.rport);
  return 0;
}
#endif /* 0 */

long gs_udp_open(FILEPTR *fp, union GS_cmd *args)
{
  int fd;
  struct sockaddr_in addr, addr2;
  size_t addr_size = sizeof(struct sockaddr_in);
  int retval;
  struct GS *gs = (struct GS *)fp->devinfo;
#if 0
  long fdflags;
  int pending = 0;
#endif

  LOG(DBG_SYSCALL, "In gs_udp_open(%p, %A, %d)",
      (void *)fp, args->UDP_open_cmd.rhost, args->UDP_open_cmd.rport);

  if (!gs || !(gs->flags & GS_NOSOCKET)) {
    LOG(DBG_ERROR, "In gs_udp_open(%p, %A, %d): bad handle",
	(void *)fp, args->UDP_open_cmd.rhost, args->UDP_open_cmd.rport);
    return E_BADHANDLE;
  }

  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = args->UDP_open_cmd.rhost;
  addr.sin_port = args->UDP_open_cmd.rport;

  fd = gs_socket_hack(AF_INET, SOCK_DGRAM, 0);
  if (fd < 0) {
    LOG(DBG_ERROR, "In gs_udp_open(%p, %A, %d): socket() returns %e",
	(void *)fp, args->UDP_open_cmd.rhost, args->UDP_open_cmd.rport, fd);
    return gs_xlate_error(fd, "gs_udp_open");
  }
#if 0
  fdflags = f_cntl(fd, 0L, F_GETFL);
  if (fdflags < 0) {
    LOG(DBG_ERROR, "In gs_udp_open(%p, %A, %d): Fcntl(F_GETFL) returns %d",
	(void *)fp, args->UDP_open_cmd.rhost, args->UDP_open_cmd.rport,
	fdflags);
    return gs_xlate_error(fdflags, "gs_udp_open");
  }
  retval = f_cntl(fd, fdflags | O_NDELAY, F_SETFL);
  if (retval < 0) {
    LOG(DBG_ERROR, "In gs_udp_open(%p, %A, %d): Fcntl(F_GETFL) returns %d",
	(void *)fp, args->UDP_open_cmd.rhost, args->UDP_open_cmd.rport,
	fdflags);
    return gs_xlate_error(retval, "gs_udp_open");
  }
#endif

  retval = connect(fd, (struct sockaddr *)&addr, addr_size);
#if 0
  if (retval == EINPROGRESS) {
    pending = 1;
  } else
#endif
  if (retval < 0) {
    LOG(DBG_ERROR, "In gs_udp_open(%p, %A, %d): connect() returns %e",
	(void *)fp, args->UDP_open_cmd.rhost, args->UDP_open_cmd.rport,
	retval);
    return gs_xlate_error(retval, "gs_udp_open");
  }

  /* Fill in the CIB.  Part of the data we need came from our parameters;
   * get the rest via getsockname(). */
  addr_size = sizeof(struct sockaddr_in);
  retval = getsockname(fd, (struct sockaddr *)&addr2, &addr_size);
  if (retval < 0) {
    LOG(DBG_ERROR, "In gs_udp_open(%p, %A, %d): getsockame() returns %e",
	(void *)fp, args->UDP_open_cmd.rhost, args->UDP_open_cmd.rport,
	retval);
    return gs_xlate_error(retval, "gs_udp_open");
  }
  gs->cib.protocol = P_TCP;
  gs->cib.lport = addr2.sin_port;
  gs->cib.rport = addr.sin_port;
  gs->cib.rhost = addr.sin_addr.s_addr;
  gs->cib.lhost = addr2.sin_addr.s_addr;

  gs->sock_fd = fd;
#if 0
  gs->flags = pending ? GS_PEND_OPEN : 0;
#else
  gs->flags = 0;
#endif
  LOG(DBG_SYSCALL, "gs_udp_open(%p, %A, %d) returns 0",
      (void *)fp, args->UDP_open_cmd.rhost, args->UDP_open_cmd.rport);
  return 0;
}

long gs_wait(FILEPTR *fp, int timeout)
{
  struct GS *gs = (struct GS *)fp->devinfo;
  long rfs, wfs;
  int n;

  LOG(DBG_SYSCALL, "In gs_wait(%p, %d)", (void *)fp, timeout);

  if (!gs || gs->flags & GS_NOSOCKET) {
    LOG(DBG_ERROR, "In gs_wait(%p, %d): bad handle", (void *)fp, timeout);
    return E_BADHANDLE;
  }

  /* TCP_wait_state() expects its timeout in seconds, Fselect() in
   * milliseconds... */
  timeout *= 1000;

  rfs = wfs = 1L << gs->sock_fd;

  if ((gs->flags & (GS_LISTENING
#if 0
		    |GS_PEND_OPEN
#endif
		    )) == 0) {
    LOG(DBG_SYSCALL, "gs_wait(%p, %d) returns %d", (void *)fp, timeout,
	E_NORMAL);
    return E_NORMAL;
  }

  if (gs->flags & GS_LISTENING) {
    n = f_select((short)timeout, &rfs, 0L, 0L);
  } else {
    n = f_select((short)timeout, 0L, &wfs, 0L);
  }

  if (n > 0) {
    /* Finish establishing the connection. */
    if (gs->flags & GS_LISTENING)
      n = gs_accept(gs);
#if 0
    else
      n = gs_establish(gs);
#endif
    if (n == 0) {
      LOG(DBG_SYSCALL, "gs_wait(%p, %d) returns 0", (void *)fp, timeout);
      return 0;
    } else {
      LOG(DBG_ERROR, "In gs_wait(%p, %d): gs_%s() returns %E",
	  (void *)fp, timeout,
	  ((gs->flags & GS_LISTENING) ? "accept" : "establish"), n);
      return n;
    }
  } else if (n == 0) {
    LOG(DBG_SYSCALL, "In gs_wait(%p, %d): timeout", (void *)fp, timeout);
    return E_USERTIMEOUT;
  } else {
    LOG(DBG_ERROR, "In gs_wait(%p, %d): Fselect() returns %e",
	(void *)fp, timeout, n);
    return gs_xlate_error(n, "gs_wait");
  }
}

long gs_canread(FILEPTR *fp)
{
  struct GS *gs = (struct GS *)fp->devinfo;
  long r, n;

  LOG(DBG_SYSCALL, "In gs_canread(%p)", (void *)fp);

  if (gs->flags & GS_NOSOCKET) {
    LOG(DBG_ERROR, "In gs_canread(%p): bad handle", (void *)fp);
    return E_BADHANDLE;
  }

  if ((gs->flags & GS_LISTENING) && gs_accept(gs) != 0) {
    LOG(DBG_SYSCALL, "In gs_canread(%p): no connection arrived", (void *)fp);
    return E_LISTEN;
  }

#if 0
  if ((gs->flags & GS_PEND_OPEN) && gs_establish(gs) != 0) {
    LOG(DBG_SYSCALL, "In gs_canread(%p): open in progress", (void *)fp);
    return 0;
  }
#endif

  r = f_cntl(gs->sock_fd, (long)&n, FIONREAD);
#if 0
  if (r == ENOTCONN)
    return 0;
#endif
  if (r < 0) {
    LOG(DBG_ERROR, "In gs_canread(%p): Fcntl(FIONREAD) returns %e",
	(void *)fp, r);
    return gs_xlate_error(r, "gs_canread");
  }
  if (n == 0x7FFFFFFFUL) {
    LOG(DBG_SYSCALL, "In gs_canread(%p): EOF reached", (void *)fp);
    return E_EOF;
  }
  LOG(DBG_SYSCALL, "gs_canread(%p) returns %l", (void *)fp, n);
  return n;
}

long gs_read_delim(FILEPTR *fp, union GS_cmd *args)
{
  int n = 0;
  long r;
  int len = args->CNgets_cmd.len;
  char delim = args->CNgets_cmd.delim;
  char *buf = args->CNgets_cmd.buffer;

  LOG(DBG_SYSCALL, "In gs_read_delim(%p, %p, %d, %x)",
      (void *)fp, (void *)buf, len, (int)delim);

  while (n < len - 1) {
    r = gssock_read(fp, buf+n, 1);
    if (r < 0)
      return r;
    if (r == 0) {
      LOG(DBG_SYSCALL, "In gs_read_delim(%p, %p, %d, %x): end of data",
	  (void *)fp, (void *)buf, len, (int)delim);
      return E_NODATA;
    }
    if (buf[n] == delim)
      break;
    n++;
    continue;
  }
  buf[n] = '\0';
  LOG(DBG_SYSCALL, "gs_read_delim(%p, %p, %d, %x) returns %d",
      (void *)fp, (void *)buf, len, (int)delim, n);
  return n;
}

long gs_readndb(FILEPTR *fp)
{
  struct GS *gs = (struct GS *)fp->devinfo;
  long r, n;
  NDB *ndb;

  LOG(DBG_SYSCALL, "In gs_readndb(%p)", (void *)fp);

  if (gs->flags & GS_NOSOCKET) {
    LOG(DBG_ERROR, "In gs_readndb(%p): bad handle", (void *)fp);
    return 0;
  }

  if ((gs->flags & GS_LISTENING) && gs_accept(gs) != 0) {
    LOG(DBG_SYSCALL, "In gs_readndb(%p): no connections arrived", (void *)fp);
    return 0;
  }

#if 0
  if ((gs->flags & GS_PEND_OPEN) && gs_establish(gs) != 0) {
    LOG(DBG_SYSCALL, "In gs_readndb(%p): open in progress", (void *)fp);
    return 0;
  }
#endif

  n = gs_canread(fp);
  if (n <= 0) {
    LOG(DBG_SYSCALL, "In gs_readndb(%p): no data available", (void *)fp);
    return 0;
  }
  if (n > 65535)
    n = 65535;

  ndb = (NDB *)gs_mem_alloc(sizeof(NDB));
  if (!ndb) {
    LOG(DBG_ERROR, "In gs_readndb(%p): no memory for NDB", (void *)fp);
    return 0;
  }
  ndb->ptr = (char *)gs_mem_alloc(n);
  if (!ndb->ptr) {
    LOG(DBG_ERROR, "In gs_readndb(%p): no memory for data", (void *)fp);
    gs_mem_free(ndb);
    return 0;
  }

  r = f_read(gs->sock_fd, n, ndb->ptr);
  if (r < 0) {
    LOG(DBG_ERROR, "In gs_readndb(%p): Fread() returned %d", (void *)fp, r);
    gs_mem_free(ndb->ptr);
    gs_mem_free(ndb);
    return 0;
  }
  ndb->ndata = ndb->ptr;
  ndb->len = n;
  ndb->next = 0;
  LOG(DBG_SYSCALL, "gs_readndb(%p) read %d bytes, returns %p",
      (void *)fp, n, (void *)ndb);
  return (long)ndb;
}
