/* 
 * Copyright (C) 1994, 1995 under GPL: Wayne Meissner
 * 
 * $Author: wayne $
 * $Date: 1995/01/18 03:01:40 $
 * $Source: /home/wayne/src/dosemu/daemon/RCS/dosemu_daemon.c,v $
 * $Revision: 2.5 $
 * 
 */

/* 
 * this is the dosemu side of the daemon mechanism
 * 
 * TODO:
 * - create a better arbitration system, e.g. unix sockets, to control
 *   how many processes are connected at once.
 * - use an interrupt vector to signal the dos program, instead of having
 *   it poll daemon_helper() to see if data is available from the client
 *   This would probably best be implemented by having the socket from above
 *   hooked into io_select, (similar to sillyint was), so dosemu would not
 *   block inside of dosemu (which could be bad, no interrupts etc)
 */

#include "emu.h"
#include "cpu.h"
#include "daemon.h"
#include "dosemu_daemon.h"
#include "x86.h"
#include "client_server.h"
#include <wm_lib/system.h>
#include <stdio.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <sys/un.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>

#define daem_printf(s,a...) fprintf (stderr, "daemon: "##s, ##a)

/* debugging output? */
#if 0
#define dbg_printf daem_printf
#else
#define dbg_printf(s,a...)
#endif

#define offsetof(type, member) ((size_t)&((type *)0)->member)

LOCAL struct daemon_comms * daemon_comm = NULL;
LOCAL struct daemon_server * client = NULL;
LOCAL int buffer_len = 32767; /* should be enuff ? */
LOCAL key_t daemon_key = -1;
LOCAL int data_sent = 0;
LOCAL int data_recvd = 0;
LOCAL int connected = 0;
LOCAL int sockfd = -1;
LOCAL int client_fd = -1;
LOCAL void copy_to_client (void);
LOCAL void copy_from_client (void);
LOCAL int wait_connection (void);
LOCAL int wait_client (void);
LOCAL int signal_client (void);
LOCAL int wait_fd (int fd, int utimeout, int mode);
LOCAL void * shmalloc (key_t key, int sz, int flags);

#define RESTORE_AX LWORD(eax) = daemon_comm->ax
#define SELECT_DELAY 50000

LOCAL inline void cp_regs_to_client (void);
LOCAL inline void cp_comm_to_client (void);
LOCAL inline void cp_data_to_client (void);
LOCAL inline void cp_regs_from_client (void);
LOCAL inline void cp_comm_from_client (void);
LOCAL inline void cp_data_from_client (void);
LOCAL int server_init (void);
LOCAL int daemon_init (void);

LOCAL int server_initialised = 0;

/* daemon_select is the same as select(2), but it takes account of the
 * interrupts that are always occuring in dosemu, which screw up system calls
 */
LOCAL inline int 
daemon_select (int n, fd_set * in, fd_set * out, fd_set * e, 
	   struct timeval * t) {
  int ret;
  while (((ret = select (n, in, out, e, t)) == -1) && 
	 (errno == EINTR)) {
/*    dbg_printf ("INTERRUPTED SELECT\n");
 */
  }
  errno = 0;
  return ret;
}

LOCAL void
sigpipe (int sig) {
  connected = 0;
  close (client_fd);

  client_fd = -1;
#if 0
  close (sockfd);
  sockfd = -1;
  server_init();
#endif
  signal (SIGPIPE, sigpipe);
  dbg_printf ("Connection lost!!!!\n");
}

/* daemon_init() (re)initialises the shm communication area, and
 * calls server_init() to initialise the socket.
 * returns 0 on success. es:bx contains the address of the daemon_comm
 * struct used for communications.
 */
LOCAL int
daemon_init (void) {

  key_t key = DAEMON_KEY; /* default start key */
  int id;
  int flags = IPC_CREAT | 0666;
  int size = getpagesize();
  int dm_sz = sizeof (struct daemon_server);
  static int call_count = 0;
  

  dbg_printf ("daemon_init() called (%d so far)\n", call_count++);
  
  /* align the size on a page boundary, giving at least 1k for data */
  if (size < (dm_sz + 1024)) {
    size = ((dm_sz + 1024 + size) / size) * size;
  }
  
  id = shmget (key, size, flags);
  if (id == -1)  {
    daem_printf ("shmget failed for client struct\n");
    return - 1;
  }
  daemon_key = key;
  /* attach onto the mem */
  client = (struct daemon_server *)shmat(id, 0, 0);
  
  if ((char *)client == (char *)-1)  {
    daem_printf ("shmat failed\n");
    return - 1;
  }
  
  client->data.len = size - dm_sz;
  
  /* setup the communication to the dos program */        
  daemon_comm = SEG_ADR ((struct daemon_comms *), es, bx);
  daem_printf ("daemon_comm = 0x%x\n", (void *)daemon_comm);
  fflush (stderr);
  if (!server_initialised) {
    if (server_init() != 0) 
      return -1;
    server_initialised++;
  }
  
  /* tell the dos server to poll for a connection */
  daemon_comm->command = DAEMON_DOCMD;
  daemon_comm->arg = DAEMON_WAITCONN;
  return 0;
}

/* convienience func to allocate or attach to a shm area */

LOCAL void * shmalloc (key_t key, int sz, int flags) {
  int id;
  void * mem;
  id = shmget (key, sz, flags);
  if (id == -1) {
    return NULL;
  }
  
  mem = (void *)shmat (id, 0, 0);
  if ((int) mem == -1) {
    /* destroy the shm segment if it was created in this func */
    if ((flags & IPC_CREAT) == IPC_CREAT)
      shmctl (id, IPC_RMID, NULL);
    return NULL;
  }
  return mem;
}

LOCAL inline int
daemon_getmem (void) {
  DaemonMemInfo * mem = (DaemonMemInfo *)(client->data.buf);
  int flags = IPC_CREAT | 0666; /* just rw for everyone */
  char * buf;
  
  if (mem->shm_key != 0) {
    flags = 0;
  }
  else
    mem->shm_key = daemon_key + 1;
  
  buf = (char *)shmalloc (mem->shm_key, mem->len, flags);
  if (buf == NULL) {
    daem_printf ("ERROR: couldn't get shm area for getmem()\n");
    return -1;
  }
  memcpy (buf, (void *)vm86_addr (mem->s.seg, mem->s.off), mem->len);
  shmdt (buf);
  return 0;
}

LOCAL inline int
daemon_putmem (void) {
  DaemonMemInfo * mem = (DaemonMemInfo *)(client->data.buf);
  int flags = IPC_CREAT | 0666; /* just rw for everyone */
  char * buf;
  
  if (mem->shm_key != 0) {
    flags = 0;
  }
  else
    mem->shm_key = daemon_key + 1;
  
  buf = (char *)shmalloc (mem->shm_key, mem->len, flags);
  if (buf == NULL) {
    daem_printf ("ERROR: couldn't get shm area for putmem()\n");
    return -1;
  }
  memcpy ((void *)vm86_addr (mem->s.seg, mem->s.off), buf, mem->len);
  shmdt (buf);
  return 0;
}

/*
 * Now the main routine.
 * daemon_helper is called by the dos server program (running within dosemu)
 * whenever it needs to send data to the client process, and to poll
 * to see if there is any data available.
 * It has been kept as simple as possible, and little or no interpretation
 * of the data or commands being transferred is made.  This way all
 * the policy decisions are kept in the external programs, and dosemu 
 * dosen't have to be changed every time a new command is needed.
 */

/*
TODO:
- sort out this mess
- return errors in AX, so dos server can recognise them
- maybe setup different functions (0x0266, 0x366, etc) instead 
  of using the daemon_comm::command field.
*/
void 
daemon_helper (void) {
#define DO_WAIT_CLIENT if (wait_client() != 0){\
  daemon_comm->command = DAEMON_RETRY;\
  break;\
}
  if (HI(ax) == DAEMON_INIT) {
    LWORD (eax) = daemon_init();
    return;
  }
  /* if the daemon isn't initialised, there isn't anything to do */
  if (!daemon_comm) {
    LWORD(eax) = DAEMON_INITFN;
    daemon_comm->command = DAEMON_INT86;
    daemon_comm->arg = 0xe6;
    return;
  }
  
  if (!connected && (daemon_comm->command != DAEMON_WAITCONN)) {
    daem_printf ("connection error\n");
    daemon_comm->command = DAEMON_DOCMD;
    daemon_comm->arg = DAEMON_WAITCONN;
    return;
  }
  dbg_printf ("cmd = %d, arg = %d\n", daemon_comm->command, daemon_comm->arg);
  switch (daemon_comm->command) {
   case DAEMON_REPLY:
    dbg_printf ("DAEMON_REPLY called\n");
#if 0
    if (wait_client() != 0) {
      daemon_comm->command = DAEMON_RETRY;
      break;
    }
#endif
    /* else copy the data to the waiting client */
    copy_to_client();
    signal_client();
    daemon_comm->command = DAEMON_DOCMD;
    daemon_comm->arg = DAEMON_WAITCLIENT;
    break;
    
   case DAEMON_WAITCLIENT:
    dbg_printf ("WAITCLIENT called\n");
    
    if (wait_client() != 0) {
      daemon_comm->command = DAEMON_RETRY;
      break;
    }
    /* otherwise, get the command from the client, and pass it back to dos */

    /* check for client commands to the dosemu part.
     * there should be very few of these; only things that need 32bit mode for
     * speed
     */
    switch (client->daemon_comm.command) {
      /* copy an area of memory to the client */
     case DAEMON_GETMEM:
#if 0
      copy_from_client(); 
#endif
      daemon_getmem();
#if 0
      copy_to_client();
#endif
      signal_client();
      daemon_comm->command = DAEMON_DOCMD;
      daemon_comm->arg = DAEMON_WAITCLIENT;
      break;
    
      /* copy an area of memory from the client to the vm86 machine */
     case DAEMON_PUTMEM:
#if 0
      copy_from_client();
#endif
      daemon_putmem();
#if 0
      copy_to_client();
#endif
      signal_client();
      daemon_comm->command = DAEMON_DOCMD;
      daemon_comm->arg = DAEMON_WAITCLIENT;
      break;
      
     case DAEMON_CLIENTEXIT:
      connected = 0;
      close (client_fd);
      daemon_comm->command = DAEMON_DOCMD;
      daemon_comm->arg = DAEMON_WAITCONN;
      break;
      
     default:
      copy_from_client();
    }
    break;
    /* now back to the main switch() */
    
    /* wait for a connection */
   case DAEMON_WAITCONN:
    dbg_printf ("WAITCONN called\n");
    if ((client_fd = wait_connection()) < 0) {
      daemon_comm->command = DAEMON_RETRY;
      break;
    }
    /* once there is a connection, setup the neccessary stuff to do
     * shm transfers, then wait for a command from the client */
    copy_to_client();
      {
	DataHeader hdr;
	(*(key_t *)hdr.buf) = daemon_key;
	hdr.len = sizeof (key_t) + sizeof (hdr.len);
	dbg_printf ("writing daemon_key = %d to client\n", *(key_t *)hdr.buf);
	write (client_fd, &hdr, hdr.len);
      }
    connected = 1;

    daemon_comm->command = DAEMON_DOCMD;
    daemon_comm->arg = DAEMON_WAITCLIENT;
    break;
    
   default:
    daem_printf ("unknown daemon command %d\n", daemon_comm->command);
  }
}

LOCAL inline void 
cp_regs_to_client (void) {
  RESTORE_AX; /* most important this */
  memcpy (&(client->regs), &REGS, sizeof(REGS));
}

LOCAL inline void 
cp_comm_to_client (void) {
  memcpy (&(client->daemon_comm), daemon_comm, sizeof(struct daemon_comms));
}

LOCAL inline void 
cp_data_to_client (void) {
#if 0
  memcpy (client->data.buf, 
	  (void *)vm86_addr (LWORD(ds), daemon_comm->dosbuf), 
	  client->data_len);
#endif
}

LOCAL inline void 
cp_regs_from_client (void) {
#if 1
  /* only ax, bx, cx, dx, di, si, and the flags*/
#define COPY(reg) REG(##reg##) = client->regs.##reg
  COPY(eax);
  COPY(ebx);
  COPY(ecx);
  COPY(edx);
  COPY(esi);
  COPY(edi);
  COPY(eflags);
#if 1 /* the next may be needed sometime later */
  COPY(es);
  COPY(ds);
#endif
#else /* the fast, but dangerous way */
  memcpy (&REGS, &(client->regs), sizeof (REGS));
#endif  
}

LOCAL inline void 
cp_comm_from_client (void) {
  memcpy (daemon_comm, &(client->daemon_comm), sizeof(struct daemon_comms));
}

LOCAL inline void 
cp_data_from_client (void) {
#if 0
  memcpy ((void *)vm86_addr (LWORD(ds), daemon_comm->dosbuf), 
	  client->data_area,
	  client->data_len);
#endif
}

LOCAL void 
copy_to_client (void) {
  dbg_printf ("copying data to the client\n");
  /* copy the various dos params into the shm communication area */
  cp_comm_to_client();
  
  /* copy the contents of the dos buffer */
  cp_data_to_client();
  /* transfer the regs */
  cp_regs_to_client();
}

LOCAL void 
copy_from_client (void) {
  dbg_printf ("copying data from the client\n");
  
  /* copy the various dos params into the shm communication area */
  cp_comm_from_client();
  
  /* copy the contents of the dos buffer */
  cp_data_from_client();
  /* transfer the regs */ 
  cp_regs_from_client();
}
  
LOCAL int
wait_client (void) {
  static struct data_header hdr;
  static int len = sizeof (hdr.len);
#if 0
  int ret = 0;
  int val;
  int slen;
#if 1
  ret = setsockopt (client_fd, SOL_SOCKET,  SO_ERROR, &val, sizeof (val));
#else
  ret = getsockopt (client_fd, SOL_SOCKET, SO_ERROR, &val, &slen);
#endif
  dbg_printf ("[sg]etsockopt() returned %d\n", ret);
  if (ret == -1)
    dbg_printf ("Error was  %s\n", strerror(errno));
#endif
  
  /* FIXME: this should really wait on client_fd I think */
  if (wait_fd (client_fd, SELECT_DELAY, O_RDONLY) == -1)
    return -1;
  if (read (client_fd, &hdr, len) == len) 
    return 0; /* return 0 if everything is ok */
  return -1;
}


/* just send something to the client to wake it up */
LOCAL int
signal_client (void) {
  static struct data_header hdr;
  static int len = sizeof (hdr.len); /* only write the length param */;
  hdr.len = 0;
  
  if (write (client_fd, &hdr, len) == len)
    return 0;
  return -1;
}


LOCAL int
wait_connection (void) {
  struct sockaddr_un addr;
  int newfd;
  int len = sizeof (addr);
  addr.sun_family = AF_UNIX;
  if (wait_fd (sockfd, SELECT_DELAY, O_RDONLY) != 0) {
    dbg_printf ("no connections on socket yet\n");
    return -1;
  }
  dbg_printf ("got a connection, trying to accept it ...\n");
  newfd = accept(sockfd, (struct sockaddr *)&addr, &len);
  if (newfd < 0) {
    daem_printf ("accept() failed: %s\n", strerror (errno));
  }
  dbg_printf ("accept successful: fd = %d\n", newfd);
  return newfd;
}
   
/* 
 * initialise the server itself 
 * returns 0 on success, -1 on failure
 */
LOCAL int 
server_init (void) {
  struct sockaddr_un addr;
  int len;
  
  dbg_printf ("initialising server sockets\n");
  sockfd = socket (AF_UNIX, SOCK_STREAM, 0); /* create the socket */
  if (sockfd < 0) {
    daem_printf ("couldn't open socket for daemon communication: %s\n",
		 strerror(errno));
    return -1;
  }
  dbg_printf ("socket() successful: sockfd = %d\n", sockfd);
  
  addr.sun_family = AF_UNIX;
  sprintf (addr.sun_path, "%s/%s", SOCKET_DIR, SOCKET_NAME);
  len = sizeof (addr.sun_family) + strlen (addr.sun_path);
  
  /* DANGER!!! && TODO:
   * by just blatantly doing an unlink() here, it means that the last
   * dosemu daemon started will get all requests.
   * This should get sorted out sometime, and some form of socket
   * registration done, but it isn't really  needed yet (still testing)
   */
  unlink (addr.sun_path);
  
  
  /* ok lets bind the socket so connections can get going */
  if (bind (sockfd, (struct sockaddr *)&addr, len) != 0) {
    daem_printf ("problem binding socket: %s\n", strerror (errno));
    return -1;
  }
  dbg_printf ("bind successful\n");
  
  /* and start listening for a connection */
  if (listen (sockfd, 5) != 0)  {
    daem_printf ("something went wrong while listening to the socket: %s\n", 
		 strerror(errno));
    return -1;
  }
  
  dbg_printf ("listen successful\n");
  
  /* if we got this far, everything is ok, return success */
  signal (SIGPIPE, sigpipe);
  return 0;
}


/* wait_fd() is specialised, for reading xor writing on one fd */
LOCAL int
wait_fd (int fd, int utimeout, int mode) {
  struct timeval timeout;
  fd_set fds;
  int ret = 0;
  timeout.tv_usec = utimeout;
  timeout.tv_sec = 0;

  FD_ZERO (&fds);
  FD_SET (fd, &fds);
  cli();
  dbg_printf ("waiting on fd %d\n", fd);
#if 1
  switch (mode) {
   case O_RDONLY:
    ret = daemon_select (10, &fds, NULL, NULL, &timeout);
    break;
    
   case O_WRONLY:
    ret = daemon_select (10, NULL, &fds, NULL, &timeout);
    break;
    
   default:
    return -1;
  }
#else
  ret = daemon_select (15, &fds, NULL, NULL, &timeout);
#endif
  sti();
  if (ret < 0) {
    daem_printf ("select error!!: %s\n", strerror(errno));
    return -1;
  }
  
  if (FD_ISSET(fd, &fds))
    return 0;
  return -1;
}


