#include <glib.h>
#include <string.h>
#include <stdlib.h>

#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "netconsole.h"
#include "netdumpclient.h"
#include "netdumpelf.h"
#include "server.h"

#define MAX_N_RETRIES 20
#define NETDUMP_INITIAL_TIMEOUT_MSEC 100
#define NETDUMP_TIMEOUT_MULTIPLIER 1.5
#define NETDUMP_MAX_TIMEOUT_MSEC 4000
#define NETDUMP_OUTSTANDING_PACKETS 10
#define NETDUMP_ACKWAIT_MSEC 50

/* How many reboot requests to send */
#define NETDUMP_N_REBOOT_REQUESTS 20
/* The timeout between the reboot requests */
#define NETDUMP_REBOOT_DELAY_MSEC 500

#undef DEBUG

static void netdump_client_remove_timeout (NetdumpClient *client);
static gboolean generate_i386_elf_header (NetdumpClient *client);
static gboolean generate_x86_64_elf_header (NetdumpClient *client);
static gboolean generate_ppc64_elf_header (NetdumpClient *client);
static gboolean generate_ia64_elf_header (NetdumpClient *client);


static void handshake            (NetdumpClient      *client);
static void handshake_send_hello (NetdumpClient      *client,
				  guint32             request);
static void handshake_packet     (NetdumpClient      *client,
				  struct sockaddr_in *addr,
				  unsigned char      *buffer,
				  int                 buf_len);
static void handshake_timeout    (NetdumpClient      *client,
				  guint32             request);
static void nr_pages_request     (NetdumpClient      *client);
static void nr_pages_packet      (NetdumpClient      *client,
				  struct sockaddr_in *addr,
				  unsigned char      *buffer,
				  int                 buf_len);
static void nr_pages_timeout     (NetdumpClient      *client,
				  guint32             request);
static void page_size_request    (NetdumpClient      *client);
static void page_size_packet     (NetdumpClient      *client,
				  struct sockaddr_in *addr,
				  unsigned char      *buffer,
				  int                 buf_len);
static void page_size_timeout    (NetdumpClient      *client,
				  guint32             request);
static void regs_request         (NetdumpClient      *client);
static void regs_packet          (NetdumpClient      *client,
				  struct sockaddr_in *addr,
				  unsigned char      *buffer,
				  int                 buf_len);
static void regs_timeout         (NetdumpClient      *client,
				  guint32             request);
static void status_request       (NetdumpClient      *client,
				  gboolean            first);
static void status_packet        (NetdumpClient      *client,
				  struct sockaddr_in *addr,
				  unsigned char      *buffer,
				  int                 buf_len);
static void status_timeout       (NetdumpClient      *client,
				  guint32             request);
static void memory_request       (NetdumpClient      *client,
				  gboolean            first_time);
static void memory_timeout       (NetdumpClient      *client,
				  guint32             request);
static void memory_packet        (NetdumpClient      *client,
				  struct sockaddr_in *addr,
				  unsigned char      *buffer,
				  int                 buf_len);
void netdump_client_send_request (NetdumpClient        *client,
				  enum netdump_commands command,
				  guint32               from,
				  guint32               to,
				  gboolean              setup_timeout,
				  guint                *timeout_delay);


typedef struct {
  guint32 ip;
  int count;
  unsigned char magic_value[MAGIC_SIZE];
} RebootRequest;

static gboolean
netdump_client_send_reboot (gpointer data)
{
  req_t req;
  struct sockaddr_in addr;
  int addr_len = sizeof(addr);
  int len;
  RebootRequest *reboot = data;

#if 0
  g_print ("sending reboot to %d.%d.%d.%d\n",
	   (client->ip >> 24) & 0xff,
	   (client->ip >> 16) & 0xff,
	   (client->ip >> 8) & 0xff,
	   (client->ip >> 0) & 0xff);
#endif
  
  memset (&addr, 0, sizeof (addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(NETDUMP_PORT);
  addr.sin_addr.s_addr = htonl(reboot->ip);
  
  memset (&req, 0, sizeof(req));
  
  memcpy ((char *)&req.magic, reboot->magic_value, MAGIC_SIZE);
  req.nr = htonl (0);
  req.command = htonl (COMM_REBOOT);
  req.from = htonl(0);
  req.to = htonl(0);
  
  len = sendto (master_socket,
		&req, sizeof(req), 0,
		(struct sockaddr *)&addr, addr_len);

  reboot->count++;
  
  if (reboot->count < NETDUMP_N_REBOOT_REQUESTS)
    {
      g_timeout_add_full (G_PRIORITY_DEFAULT,
			  NETDUMP_REBOOT_DELAY_MSEC,
			  netdump_client_send_reboot,
			  reboot,
			  NULL);
    }
  else
    g_free (reboot);
  
  return FALSE;
}

static void
netdump_client_reboot (NetdumpClient *client)
{
  RebootRequest *reboot;
  
  execute_script ("netdump-reboot", client->ip, client->dump_dir);

  reboot = g_new0 (RebootRequest, 1);
  
  reboot->ip = client->ip;
  memcpy (reboot->magic_value, client->magic_value, MAGIC_SIZE);
  reboot->count = 0;
      
  (void)netdump_client_send_reboot (reboot);
}

void
netdump_client_destroy (NetdumpClient *client, gboolean reboot)
{
  int i;

#ifdef DEBUG
  g_print (" netdump_client_destroy(%p, reboot=%d)\n", client, reboot);
#endif

  remove_client (client);
  netdump_client_remove_timeout (client);

  if (reboot)
    netdump_client_reboot (client);
      
  if (client->dump_fd >= 0)
    close (client->dump_fd);
  
  for (i = 0; i < client->n_outstanding; i++)
    {
      if (client->outstanding[i].timeout_tag != 0)
	g_source_remove (client->outstanding[i].timeout_tag);
      g_free (client->outstanding[i].data);
    }
  g_free (client->outstanding);
  
  g_free (client->regs);
  g_free (client->dump_dir);

  g_free (client);
}


guint
netdump_client_hash (gconstpointer key)
{
  const NetdumpClient *client = key;

  return client->ip;
}

gint
netdump_client_compare (gconstpointer a,
			gconstpointer b)
{
  const NetdumpClient *client1 = a;
  const NetdumpClient *client2 = b;

  return (client1->ip == client2->ip);
}

struct TimeoutData {
  NetdumpClient *client;
  guint32 request_nr;
};

static gboolean
netdump_client_timeout (gpointer data)
{
  struct TimeoutData *timeout = data;

  if (timeout->client->timeout)
    timeout->client->timeout (timeout->client, timeout->request_nr);
  
  return FALSE;
}

static void
netdump_client_remove_timeout (NetdumpClient *client)
{
  if (client->timeout_tag)
    g_source_remove (client->timeout_tag);
  client->timeout_tag = 0;
}

void
netdump_client_setup_timeout (NetdumpClient *client,
			      guint32        request_nr,
			      guint          timeout_delay)
{
  struct TimeoutData *timeout;
  timeout = g_new (struct TimeoutData, 1);
  timeout->client = client;
  timeout->request_nr = request_nr;
  
  client->timeout_tag = 
    g_timeout_add_full (G_PRIORITY_DEFAULT,
			timeout_delay,
			netdump_client_timeout,
			timeout,
			g_free);
}
			      

void
netdump_client_send_request (NetdumpClient        *client,
			     enum netdump_commands command,
			     guint32               from,
			     guint32               to,
			     gboolean              setup_timeout,
			     guint                *timeout_delay)
{
  req_t req;
  struct sockaddr_in addr;
  int addr_len = sizeof(addr);
  int len;

#if 0
  g_print ("sending command %d to %d.%d.%d.%d\n",
	   command,
	   (client->ip >> 24) & 0xff,
	   (client->ip >> 16) & 0xff,
	   (client->ip >> 8) & 0xff,
	   (client->ip >> 0) & 0xff);
#endif
  
  memset (&addr, 0, sizeof (addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(NETDUMP_PORT);
  addr.sin_addr.s_addr = htonl(client->ip);
  
  memset (&req, 0, sizeof(req));

  /* 
   *  This is "legacy" code that is completely useless.
   *  Notwithstanding any endian issue with the memcpy,
   *  the client never has done anything with the magic
   *  value.
   */
  //memcpy ((char *)&req.magic, client->magic_value, MAGIC_SIZE);

  req.nr = htonl (++client->outstanding_request);
  req.command = htonl (command);
  req.from = htonl(from);
  req.to = htonl(to);
  
  len = sendto (master_socket,
		&req, sizeof(req), 0,
		(struct sockaddr *)&addr, addr_len);

  if (setup_timeout)
    {
      if (*timeout_delay == 0)
	*timeout_delay =  NETDUMP_INITIAL_TIMEOUT_MSEC/2 + (rand() % (NETDUMP_INITIAL_TIMEOUT_MSEC/2));
      else
	*timeout_delay = MIN (*timeout_delay * NETDUMP_TIMEOUT_MULTIPLIER, NETDUMP_MAX_TIMEOUT_MSEC); 
      
      netdump_client_setup_timeout (client,
				    client->outstanding_request,
				    *timeout_delay);
    }
}

NetdumpClient *
netdump_client_new (guint32     ip,
		    const char *dir,
		    const char  magic[MAGIC_SIZE],
		    gboolean    just_reboot,
		    reply_t    *reply,
		    char       *msg)
{
  NetdumpClient *client;

  reply->nr = ntohl (reply->nr);
  reply->info = ntohl (reply->info);

  client = g_new0 (NetdumpClient, 1);

#ifdef DEBUG
  g_print (" netdump_client_new() ip: %d.%d.%d.%d -> %p\n",
	   (ip >> 24) & 0xff,
	   (ip >> 16) & 0xff,
	   (ip >> 8) & 0xff,
	   (ip) & 0xff,
	   client);
#endif

  client->ip = ip;
  sprintf(client->ip_addr, "%d.%d.%d.%d",
	(ip >> 24) & 0xff,
        (ip >> 16) & 0xff,
        (ip >> 8) & 0xff,
        (ip) & 0xff);

  client->dump_dir = g_strdup (dir);
  client->just_reboot = just_reboot;

  memcpy (client->magic_value, magic, MAGIC_SIZE);
  
  client->dump_fd = -1;
  
  client->outstanding_request = 0;

  /* Lame default. should be read from kernel */
  client->page_offset = 0xC0000000;
  
  /*
   *  Handshake protocol requires that we operate at the
   *  lowest common denominator of the client and server
   *  version numbers.  
   * 
   *  The original "base", i386-only, netdump client did not 
   *  use (set to 0) the reply->info and reply->nr fields 
   *  of this REPLY_START_NETDUMP packet.  Subsequent clients
   *  put their real version value in the reply->info field, 
   *  and their machine type in reply->nr.  
   *
   *  The original "base", i386-only, netdump server put a
   *  value of 0 in the req->from value that gets sent back
   *  in the handshake() COMM_START_NETDUMP_ACK packet.
   *  Subsequent servers put their server version in the
   *  req->from field, and newer clients will adjust their 
   *  "effective" version accordingly.
   */ 

  if (reply->info == 0)
	client->effective_version = NETDUMP_SERVER_VERSION_BASE;
  else if (reply->info > NETDUMP_SERVER_VERSION_CURRENT)
	client->effective_version = NETDUMP_SERVER_VERSION_CURRENT;
  else
	client->effective_version = reply->info;

  switch (reply->nr)
  {
  case 0:
  case EM_386:
	client->machine_type = EM_386;
	break;

  case EM_X86_64:
	client->machine_type = EM_X86_64;
	break;

  case EM_PPC64:
	client->machine_type = EM_PPC64;
        break;

  case EM_IA_64:
	client->machine_type = EM_IA_64;
        break;

  default:
        client->just_reboot = TRUE;
	break;
  }

  /*
   *  Post-"base" clients pass information in the appended
   *  message string.  Parse the string for expected info,
   *  which comes from the client in this format: 
   *
   *    "item1:value item2:value ... itemX:value\n" 
   */
  if (client->effective_version > NETDUMP_SERVER_VERSION_BASE) {
	char *p;

        if ((p = strstr(msg, "task_struct:0x")))
                client->task_struct = strtoull(strstr(p, "0x"), NULL, 16);

        if ((p = strstr(msg, "page_offset:0x")))
                client->page_offset = strtoull(strstr(p, "0x"), NULL, 16);

        if ((p = strstr(msg, "netdump_magic:0x"))) {
                client->netdump_magic = strtoull(strstr(p, "0x"), NULL, 16);
		if (!verify_magic(client))
        		client->just_reboot = TRUE;
	}
  }

  client->n_timeouts = 0;
  client->n_handshakes = 0;
  client->timeout_delay = 0;
  handshake (client);
  
  return client;
}

static gboolean
parse_packet (unsigned char *buffer,
	      int buf_len,
	      reply_t *reply)
{
  if (buf_len < 1 + sizeof (reply_t) ||
      !VERSION_VERIFIED(buffer[0]))
	return TRUE;
  
  memcpy ((char *)reply, buffer + 1, sizeof(reply_t));

  reply->nr = ntohl (reply->nr);
  reply->code = ntohl (reply->code);
  reply->info = ntohl (reply->info);

#if 0
  g_print ("Parsed reply: nr: %d, code: %d, info: %d\n",
	   reply->nr, reply->code, reply->info);
  g_print ("packet data: '%s'\n", buffer + 1 + sizeof (reply_t));
#endif
  
  return FALSE;
}

/********************************************************************************
 *                       Handshaking                                            *
 ********************************************************************************/

static void
handshake (NetdumpClient *client)
{
  struct TimeoutData *timeout;
  
#ifdef DEBUG
  g_print ("handshake(%p)\n", client);
#endif
  
  netdump_client_send_request (client,
			       COMM_START_NETDUMP_ACK,
			       NETDUMP_SERVER_VERSION_CURRENT,
			       0, FALSE, NULL);

  /* Wait a while for the ACK to arrive at the client */
  timeout = g_new (struct TimeoutData, 1);
  timeout->client = client;
  timeout->request_nr = 0;
  client->timeout_tag = 
    g_timeout_add_full (G_PRIORITY_DEFAULT,
			NETDUMP_ACKWAIT_MSEC,
			netdump_client_timeout,
			timeout,
			g_free);
  
  client->process_packet = NULL;
  client->timeout = handshake_send_hello;
}

static void
handshake_send_hello (NetdumpClient *client,
		      guint32        request)
{
#ifdef DEBUG
  g_print ("handshake_send_hello(%p)\n", client);
#endif
  netdump_client_send_request (client,
			       COMM_HELLO,
			       0, 0, TRUE,
			       &client->timeout_delay);

  client->process_packet = handshake_packet;
  client->timeout = handshake_timeout;
}

static void
handshake_packet (NetdumpClient *client,
		  struct sockaddr_in *addr,
		  unsigned char *buffer,
		  int buf_len)
{
  reply_t reply;
    
#ifdef DEBUG
  g_print ("handshake_packet(%p)\n", client);
#endif

  if (parse_packet (buffer, buf_len, &reply))
    {
      syslog (LOG_WARNING, "Got invalid packet from %s\n", client->ip_addr);
      return;
    }

  /* If out of order reply, ignore */
  if (reply.nr != client->outstanding_request)
    return;

  /* If strange reply, ignore. The timeout will catch this. */
  if (reply.code != REPLY_HELLO)
    return;

  netdump_client_remove_timeout (client);

  /* Now request the page size */
  client->n_timeouts = 0;

  if (client->just_reboot)
    {
      status_request (client, TRUE);
    }
  else
    {
      /* Now request the page size */
      client->n_timeouts = 0;
      client->timeout_delay = 0;
      nr_pages_request (client);
    }
}

static void
handshake_timeout (NetdumpClient *client,
		   guint32        request)
{
  client->n_timeouts++;

#ifdef DEBUG
  g_print ("handshake_timeout(%p)\n", client);
#endif

  if (client->n_timeouts < MAX_N_RETRIES)
    handshake_send_hello (client, 0);
  else
    {
      client->n_handshakes++;

      if (client->n_handshakes < 10)
	{
	  /* Restart exponential backoff */
	  client->timeout_delay = 0;
	  handshake (client);
	}
      else
	{
	  syslog (LOG_WARNING, "Got too many timeouts in handshaking, ignoring client %s\n", client->ip_addr);
	  status_request (client, TRUE);
	}
    }
}

/********************************************************************************
 *                       NR_PAGES                                               *
 ********************************************************************************/

static void
nr_pages_request (NetdumpClient *client)
{
#ifdef DEBUG
  g_print ("nr_pages_request (%p)\n", client);
#endif
  netdump_client_send_request (client,
			       COMM_GET_NR_PAGES,
			       0, 0, TRUE,
			       &client->timeout_delay);

  client->process_packet = nr_pages_packet;
  client->timeout = nr_pages_timeout;
}

static void
nr_pages_packet (NetdumpClient *client,
		 struct sockaddr_in *addr,
		 unsigned char *buffer,
		 int buf_len)
{
  reply_t reply;

  if (parse_packet (buffer, buf_len, &reply))
    {
      syslog (LOG_WARNING, "Got invalid packet from %s\n", client->ip_addr);
      return;
    }

  /* If out of order reply, ignore */
  if (reply.nr != client->outstanding_request)
    return;

  /* If strange reply, ignore. The timeout will catch this. */
  if (reply.code != REPLY_NR_PAGES)
    return;

  client->nr_pages = reply.info;
  
  netdump_client_remove_timeout (client);

  /* Now request the page size */
  client->n_timeouts = 0;
  client->timeout_delay = 0;
  page_size_request (client);
}

static void
nr_pages_timeout (NetdumpClient *client,
		  guint32        request)
{
  client->n_timeouts++;

  if (client->n_timeouts < MAX_N_RETRIES)
    nr_pages_request (client);
  else
    {
      syslog (LOG_WARNING, "Got too many timeouts waiting for NR_PAGES for client %s, ignoring it\n", client->ip_addr);
      status_request (client, TRUE);
    }
}

/********************************************************************************
 *                       PAGE_SIZE                                              *
 ********************************************************************************/

static void
page_size_request (NetdumpClient *client)
{
#ifdef DEBUG
  g_print ("page_size_request (%p)\n", client);
#endif
  netdump_client_send_request (client,
			       COMM_GET_PAGE_SIZE,
			       0, 0, TRUE,
			       &client->timeout_delay);

  client->process_packet = page_size_packet;
  client->timeout = page_size_timeout;
}

static void
page_size_packet (NetdumpClient *client,
		  struct sockaddr_in *addr,
		  unsigned char *buffer,
		  int buf_len)
{
  reply_t reply;
  
  if (parse_packet (buffer, buf_len, &reply))
    {
      syslog (LOG_WARNING, "Got invalid packet from %s\n", client->ip_addr);
      return;
    }

  /* If out of order reply, ignore */
  if (reply.nr != client->outstanding_request)
    return;

  /* If strange reply, ignore. The timeout will catch this. */
  if (reply.code != REPLY_PAGE_SIZE)
    return;

  client->page_size = reply.info;

  netdump_client_remove_timeout (client);

  /* Now request memory... */
  client->n_timeouts = 0;
  client->timeout_delay = 0;
  regs_request (client);
}

static void
page_size_timeout (NetdumpClient *client,
		   guint32        request)
{
  client->n_timeouts++;

  if (client->n_timeouts < MAX_N_RETRIES)
    page_size_request (client);
  else
    {
      syslog (LOG_WARNING, "Got too many timeouts waiting for PAGE_SIZE for client %s, ignoring it\n", client->ip_addr);
      status_request (client, TRUE);
    }
}

/********************************************************************************
 *                       Registers                                              *
 ********************************************************************************/

static void
regs_request (NetdumpClient *client)
{
#ifdef DEBUG
  g_print ("regs_request (%p)\n", client);
#endif
  netdump_client_send_request (client,
			       COMM_GET_REGS,
			       0, 0, TRUE,
			       &client->timeout_delay);

  client->process_packet = regs_packet;
  client->timeout = regs_timeout;
}

static void
regs_packet (NetdumpClient *client,
	     struct sockaddr_in *addr,
	     unsigned char *buffer,
	     int buf_len)
{
  reply_t reply;
  int offset;
  
  if (parse_packet (buffer, buf_len, &reply))
    {
      syslog (LOG_WARNING, "Got invalid packet from %s\n", client->ip_addr);
      return;
    }

  /* If out of order reply, ignore */
  if (reply.nr != client->outstanding_request)
    return;

  /* If strange reply, ignore. The timeout will catch this. */
  if (reply.code != REPLY_REGS)
    return;

  
  /*
   *  The base client version incorrectly sent its max_mapnr
   *  in the reply->info field, and the base server incorrectly
   *  put it in client->regs_len, which then gets recalculated
   *  below.  A new field, client->totalram_pages, has been
   *  added to hold the kernel's totalram_pages value, and
   *  is used by the /var/crash filesystem space check.
   */
  switch (client->effective_version)
  {
  case NETDUMP_SERVER_VERSION_BASE:
	client->totalram_pages = client->nr_pages;
	break;

  default: 
	client->totalram_pages = reply.info;
	break;
  }

  /* Find end of ascii data, where regs data starts */
  offset = 1 + sizeof (reply_t);
  while (offset < buf_len && buffer[offset] != '\n')
    offset++;

  if (offset < buf_len)
    offset++; /* Skip past the \n */
  
  if (offset < buf_len)
    {
      client->regs_len = buf_len - offset;
      client->regs = g_malloc (client->regs_len);
      memcpy (client->regs, buffer + offset, client->regs_len);
    }

  netdump_client_remove_timeout (client);

  /* Now request memory... */
  client->n_timeouts = 0;
  client->timeout_delay = 0;
  memory_request (client, TRUE);
}

static void
regs_timeout (NetdumpClient *client,
	      guint32        request)
{
  client->n_timeouts++;

  if (client->n_timeouts < MAX_N_RETRIES)
    regs_request (client);
  else
    {
      syslog (LOG_WARNING, "Got too many timeouts waiting for REGS for client %s, ignoring it\n", client->ip_addr);
      status_request (client, TRUE);
    }
}


/********************************************************************************
 *                       Dump memory                                            *
 ********************************************************************************/

static void
get_page (NetdumpClient *client, OutstandingPage *outstanding, guint32 page_nr)
{
  outstanding->page_nr = page_nr;
  netdump_client_send_request (client,
			       COMM_SEND_MEM,
			       outstanding->page_nr, 0, TRUE,
			       &outstanding->timeout_delay);
#ifdef DEBUG
  g_print ("Outstanding %d: sendig SEND_MEM request for page %d, timeout: %d\n",
	   outstanding - client->outstanding, page_nr, outstanding->timeout_delay);
#endif
  outstanding->request_nr = client->outstanding_request;
  outstanding->timeout_tag = client->timeout_tag;
}


static void
memory_request (NetdumpClient *client, gboolean first_time)
{
  int i;
  gboolean has_active;
  OutstandingPage *outstanding;
  gboolean tried_once = FALSE;
  
  if (first_time)
    {
      char *file;
      struct statfs buf;

    retry:
      
      file = g_strconcat (client->dump_dir, "/vmcore-incomplete", NULL);
      
      client->dump_fd = open (file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
      
      if (client->dump_fd < 0)
	{
	  syslog (LOG_ERR, "unable to open crash dump file");
	  status_request (client, TRUE);
	  g_free (file);
	  return;
	}
      
      if (fstatfs(client->dump_fd, &buf) < 0)
	syslog (LOG_WARNING, "unable to statfs on the crash dump file");
      else if (!perform_space_check())
	syslog (LOG_NOTICE, "crash dump file space check not performed");
      else
	{
	  off_t memdump_size;
	  int res;

	  memdump_size = ((off_t)client->totalram_pages * (off_t)client->page_size + buf.f_bsize - 1) / buf.f_bsize;
	  if (memdump_size > buf.f_bavail)
	    {
	      if (!tried_once)
		{
		  res = execute_script ("netdump-nospace", client->ip, client->dump_dir);
		  if (res > 0)
		    {
		      close (client->dump_fd);
		      client->dump_fd = -1;
		      unlink (file);
		      g_free (file);
		      tried_once = TRUE;
		      goto retry;
		    }
		}

	      if (tried_once)
		syslog (LOG_ERR, "No space for dump image, even after calling netdump-nospace");
	      else
		syslog (LOG_ERR, "No space for dump image, avail = %lld, need = %lld\n", buf.f_bavail, memdump_size);
	      
	      status_request (client, TRUE);
	      g_free (file);
	      return;
	    }
	}

      g_free (file);

      switch (client->machine_type)
      {
      case EM_386:
        if (generate_i386_elf_header (client))
  	{
  	  syslog (LOG_WARNING, "unable to dump i386 elf header");
  	  status_request (client, TRUE);
  	  return;
  	}
        break;

      case EM_X86_64:
        if (generate_x86_64_elf_header (client))
        {
          syslog (LOG_WARNING, "unable to dump x86_64 elf header");
          status_request (client, TRUE);
          return;
        }
        break;

      case EM_PPC64:
	if (generate_ppc64_elf_header (client))
        {
          syslog (LOG_WARNING, "unable to dump ppc64 elf header");
          status_request (client, TRUE);
          return;
        }
        break;

      case EM_IA_64:
        if (generate_ia64_elf_header (client))
        {
          syslog (LOG_WARNING, "unable to dump ia64 elf header");
          status_request (client, TRUE);
          return;
        }
        break;
      }

      client->current_page = 0;
      client->next_avail_page = 0;
      client->n_outstanding = NETDUMP_OUTSTANDING_PACKETS;
      client->outstanding = g_new (OutstandingPage, client->n_outstanding);
      for (i = 0; i < client->n_outstanding; i++)
	{
	  client->outstanding[i].request_nr = 0; 
	  client->outstanding[i].timeout_tag = 0; 
	  client->outstanding[i].timeout_delay = 0;
	  client->outstanding[i].data = g_malloc (client->page_size);
	}
    }
  
  client->process_packet = memory_packet;
  client->timeout = memory_timeout;

  has_active = FALSE;
  for (i = 0; i < client->n_outstanding; i++)
    {
      if (client->outstanding[i].request_nr != 0)
	has_active = TRUE;
      else if (client->current_page != client->nr_pages)
	{
	  outstanding = &client->outstanding[i];

	  outstanding->n_timeouts = 0;
	  outstanding->sections_received = 0;
	  outstanding->timeout_delay = 0;
	  /*
	   *  The client->next_avail_page field may have been 
           *  set non-zero in memory_packet() upon receipt of 
	   *  a REPLY_RESERVED packet containing a non-zero 
	   *  reply->info field; the client has indicated that
	   *  the memory request was to a non-RAM page, and that 
           *  reply->info contains the next legitimate page number.
	   */
          if (client->next_avail_page > client->current_page)
            {
              if (client->next_avail_page >= client->nr_pages)
                {
                  client->current_page = client->nr_pages;
                  continue;
                }
              client->current_page = client->next_avail_page;
            }
	  get_page (client, outstanding, client->current_page++);
	  
	  has_active = TRUE;
	}
    }

  /* Did we finish dumping all memory */
  if ((client->current_page == client->nr_pages) && !has_active)
    {
      char *old_file;
      char *new_file;
      
      old_file = g_strconcat (client->dump_dir, "/vmcore-incomplete", NULL);
      new_file = g_strconcat (client->dump_dir, "/vmcore", NULL);

      if (link(old_file, new_file) == 0)
	unlink (old_file);

      g_free (old_file);
      g_free (new_file);

      /* Request the client status */
      status_request (client, TRUE);
    }
  
}

static OutstandingPage *
find_outstanding (NetdumpClient *client,
		  guint32        request)
{
  OutstandingPage *outstanding = NULL;
  int i;

  for (i = 0; i < client->n_outstanding; i++)
    if (client->outstanding[i].request_nr == request)
      {
	outstanding = &client->outstanding[i];
	break;
      }

  return outstanding;
}

static void
memory_remove_outstanding_timeouts (NetdumpClient *client)
{
  OutstandingPage *outstanding = NULL;
  int i;

  for (i = 0; i < client->n_outstanding; i++) {
    outstanding = &client->outstanding[i];
    
    if (outstanding->timeout_tag != 0)
      g_source_remove (outstanding->timeout_tag);
    outstanding->timeout_tag = 0;
  }

  return;
}

static void
memory_timeout (NetdumpClient *client,
		guint32        request)
{
  OutstandingPage *outstanding;

  outstanding = find_outstanding (client, request);

  if (outstanding == NULL)
    {
      syslog (LOG_WARNING, "Got bogus (non-expected) GET_PAGE timeout (request nr: %d)\n", request);
      return;
    }
  
  outstanding->n_timeouts++;
  outstanding->timeout_tag = 0;

#ifdef DEBUG
  g_print ("Got timeout for Outstanding %d: It now has gotten %d timeouts\n",
	   outstanding - client->outstanding, outstanding->n_timeouts);
#endif
  
  if (outstanding->n_timeouts < MAX_N_RETRIES)
    {
      get_page (client, outstanding, outstanding->page_nr);
    }
  else
    {
      syslog (LOG_WARNING, "Got too many timeouts waiting for memory page for client %s, ignoring it\n", client->ip_addr);
      
      memory_remove_outstanding_timeouts (client);
      status_request (client, TRUE);
    }
}

static void
memory_packet (NetdumpClient *client,
	       struct sockaddr_in *addr,
	       unsigned char *buffer,
	       int buf_len)
{
  OutstandingPage *outstanding;
  reply_t reply;
  gboolean done;
  int offset;

  if (parse_packet (buffer, buf_len, &reply))
    {
      syslog (LOG_WARNING, "Got invalid packet from %s\n", client->ip_addr);
      return;
    }

  outstanding = find_outstanding (client, reply.nr);
  
  /* If out of order reply, ignore */
  if (outstanding == NULL)
    return;

  /* If strange reply, ignore. The timeout will catch this. */
  if (reply.code != REPLY_RESERVED &&
      reply.code != REPLY_MEM)
    return;

  done = FALSE;
  
  if (reply.code == REPLY_MEM)
    {
      offset = reply.info;

#ifdef DEBUG
      g_print ("Got packet for Outstanding %d: offset=%d\n",
	       outstanding - client->outstanding, offset);
#endif

      outstanding->sections_received |= 1 << (offset/1024);

      if ((offset >= 0) && (offset + 1024 <= client->page_size))
	memcpy (outstanding->data + offset,
		buffer + 1 + sizeof (reply_t),
		1024);

      /* Check if we have got all page sections */
      if (outstanding->sections_received ==
	  ((1 << client->page_size/1024) - 1))
	{
	  int left, len;

	  done = TRUE;

	  lseek (client->dump_fd,
		 (off_t)client->data_offset + (off_t)outstanding->page_nr * (off_t)client->page_size,
		 SEEK_SET);

	  left = client->page_size;
	  while (left > 0)
	    {
	      len = write (client->dump_fd,
			   outstanding->data + (client->page_size - left),
			   left);
	      if (len < 0)
		{
		  syslog (LOG_ERR, "Error writing memory dump\n");
		  memory_remove_outstanding_timeouts (client);
		  status_request (client, TRUE);
		  return;
		}

	      left -= len;
	    }
	}
      
    }
  else 
    {
      /*
       *  REPLY_RESERVED was never implemented in the base client.
       *  Instead, non-RAM pages were replaced by a mapping of
       *  the kernel's ZERO_PAGE.
       *
       *  Post-base clients use REPLY_RESERVED to avoid the unnecessary 
       *  and wasteful usage of zero-filled pages.  Furthermore, if the
       *  reply->info field is non-zero, the client is indicating that 
       *  it's safe to skip to that next available page.
       */
      done = TRUE; 
      if (reply.info > client->next_avail_page)
      	client->next_avail_page = reply.info;
    }

  if (done)
    {
#ifdef DEBUG
      g_print ("Got all packets packet for Outstanding %d: requesting new page\n",
	       outstanding - client->outstanding);
#endif

      outstanding->request_nr = 0; 
      if (outstanding->timeout_tag)
	g_source_remove (outstanding->timeout_tag);
      outstanding->timeout_tag = 0;
      outstanding->timeout_delay = 0;
      memory_request (client, FALSE);
    }
}


/********************************************************************************
 *                       Elf core dumping                                       *
 ********************************************************************************/

u16
swab16(u16 x)
{
        return (((x & 0x00ff) << 8) |
                ((x & 0xff00) >> 8));
}

u32
swab32(u32 x)
{
        return (((x & 0x000000ffU) << 24) |
                ((x & 0x0000ff00U) <<  8) |
                ((x & 0x00ff0000U) >>  8) |
                ((x & 0xff000000U) >> 24));
}

u64
swab64(u64 x)
{
        return (((x & 0x00000000000000ffULL) << 56) |
                ((x & 0x000000000000ff00ULL) << 40) |
                ((x & 0x0000000000ff0000ULL) << 24) |
                ((x & 0x00000000ff000000ULL) <<  8) |
                ((x & 0x000000ff00000000ULL) >>  8) |
                ((x & 0x0000ff0000000000ULL) >> 24) |
                ((x & 0x00ff000000000000ULL) >> 40) |
                ((x & 0xff00000000000000ULL) >> 56));
}

void
Elf32_Ehdr_swab(NetdumpClient *client, Elf32_Ehdr *ehdr)
{
	ehdr->e_type = swab16(ehdr->e_type);
	ehdr->e_machine = swab16(ehdr->e_machine);
	ehdr->e_version = swab32(ehdr->e_version);
	ehdr->e_entry = swab32(ehdr->e_entry);
	ehdr->e_phoff = swab32(ehdr->e_phoff);
	ehdr->e_shoff = swab32(ehdr->e_shoff);
	ehdr->e_flags = swab32(ehdr->e_flags);
	ehdr->e_ehsize = swab16(ehdr->e_ehsize);
	ehdr->e_phentsize = swab16(ehdr->e_phentsize);
	ehdr->e_phnum = swab16(ehdr->e_phnum);
	ehdr->e_shentsize = swab16(ehdr->e_shentsize);
	ehdr->e_shnum = swab16(ehdr->e_shnum);
	ehdr->e_shstrndx = swab16(ehdr->e_shstrndx);
}

void
Elf32_Phdr_swab(NetdumpClient *client, Elf32_Phdr *phdr)
{
	phdr->p_type = swab32(phdr->p_type);
	phdr->p_offset = swab32(phdr->p_offset);
	phdr->p_vaddr = swab32(phdr->p_vaddr);
	phdr->p_paddr = swab32(phdr->p_paddr);
	phdr->p_filesz = swab32(phdr->p_filesz);
	phdr->p_memsz = swab32(phdr->p_memsz);
	phdr->p_flags = swab32(phdr->p_flags);
	phdr->p_align = swab32(phdr->p_align);
}

void
Elf32_Nhdr_swab(NetdumpClient *client, Elf32_Nhdr *nhdr)
{
	nhdr->n_namesz = swab32(nhdr->n_namesz);
	nhdr->n_descsz = swab32(nhdr->n_descsz);
	nhdr->n_type = swab32(nhdr->n_type);
}

void
Elf64_Ehdr_swab(NetdumpClient *client, Elf64_Ehdr *ehdr)
{
	ehdr->e_type = swab16(ehdr->e_type);
	ehdr->e_machine = swab16(ehdr->e_machine);
	ehdr->e_version = swab32(ehdr->e_version);
	ehdr->e_entry = swab64(ehdr->e_entry);
	ehdr->e_phoff = swab64(ehdr->e_phoff);
	ehdr->e_shoff = swab64(ehdr->e_shoff);
	ehdr->e_flags = swab32(ehdr->e_flags);
	ehdr->e_ehsize = swab16(ehdr->e_ehsize);
	ehdr->e_phentsize = swab16(ehdr->e_phentsize);
	ehdr->e_phnum = swab16(ehdr->e_phnum);
	ehdr->e_shentsize = swab16(ehdr->e_shentsize);
	ehdr->e_shnum = swab16(ehdr->e_shnum);
	ehdr->e_shstrndx = swab16(ehdr->e_shstrndx);
}

void
Elf64_Phdr_swab(NetdumpClient *client, Elf64_Phdr *phdr)
{
	phdr->p_type = swab32(phdr->p_type);
	phdr->p_flags = swab32(phdr->p_flags);
	phdr->p_offset = swab64(phdr->p_offset);
	phdr->p_vaddr = swab64(phdr->p_vaddr);
	phdr->p_paddr = swab64(phdr->p_paddr);
	phdr->p_filesz = swab64(phdr->p_filesz);
	phdr->p_memsz = swab64(phdr->p_memsz);
	phdr->p_align = swab64(phdr->p_align);
}

void
Elf64_Nhdr_swab(NetdumpClient *client, Elf64_Nhdr *nhdr)
{
	nhdr->n_namesz = swab32(nhdr->n_namesz);
	nhdr->n_descsz = swab32(nhdr->n_descsz);
	nhdr->n_type = swab32(nhdr->n_type);
}

#define roundup(x, y)  ((((x)+((y)-1))/(y))*(y))

static size_t
dump_elf32_note (NetdumpClient *client,
		 char *buf,
		 Elf32_Word type,
		 char *name,
		 char *desc, int d_len)
{
  Elf32_Nhdr *note;
  size_t len;

  note = (Elf32_Nhdr *)buf;
  note->n_namesz = strlen (name);
  note->n_descsz = d_len;
  note->n_type = type;
  len = sizeof (Elf32_Nhdr);
  
  memcpy (buf + len, name, note->n_namesz);
  len = roundup (len + note->n_namesz, 4);
  
  memcpy (buf + len, desc, note->n_descsz);
  len = roundup (len + note->n_descsz, 4);

  if (BYTE_SWAP_REQUIRED(client))
	Elf32_Nhdr_swab(client, note);

  return len;
}


static gboolean
generate_i386_elf_header (NetdumpClient *client)
{
  char *buffer, *ptr;
  Elf32_Ehdr *elf;
  Elf32_Phdr *notes;
  Elf32_Phdr *load;
  size_t offset, len;
  struct elf_prpsinfo_32 prpsinfo;
  struct elf_prstatus_i386 prstatus;
  u32 task_struct;
  
  buffer = g_malloc0 (sizeof (Elf32_Ehdr) + 2*sizeof (Elf32_Phdr) +
		      4096*2); /* 4096*2 should be enough for the notes + roundup */
  
  offset = 0;
  ptr = buffer;

  /* Elf header */
  elf = (Elf32_Ehdr *)ptr;
  memcpy (elf->e_ident, ELFMAG, SELFMAG);
  elf->e_ident[EI_CLASS] = ELFCLASS32;
  elf->e_ident[EI_DATA]	= ELFDATA2LSB;
  elf->e_ident[EI_VERSION]= EV_CURRENT;
  memset (elf->e_ident+EI_PAD, 0, EI_NIDENT-EI_PAD);

  elf->e_type	 = ET_CORE;
  elf->e_machine  = EM_386;
  elf->e_version  = EV_CURRENT;
  elf->e_entry	  = 0;
  elf->e_phoff	  = sizeof (Elf32_Ehdr);
  elf->e_shoff	  = 0;
  elf->e_flags	  = 0;
  elf->e_ehsize	  = sizeof (Elf32_Ehdr);
  elf->e_phentsize = sizeof (Elf32_Phdr);
  elf->e_phnum	  = 2;
  elf->e_shentsize = 0;
  elf->e_shnum	  = 0;
  elf->e_shstrndx  = 0;

  offset += sizeof (Elf32_Ehdr);
  ptr += sizeof (Elf32_Ehdr);

  /* PT_NOTE */
  
  notes = (Elf32_Phdr *)ptr;
  notes->p_type	  = PT_NOTE;
  notes->p_offset = 0; /* TO BE FILLED IN */
  notes->p_vaddr  = 0;
  notes->p_paddr  = 0;
  notes->p_filesz = 0; /* TO BE FILLED IN */
  notes->p_memsz  = 0;
  notes->p_flags  = 0;
  notes->p_align  = 0;

  offset += sizeof (Elf32_Phdr);
  ptr += sizeof (Elf32_Phdr);

  /* PT_LOAD */
  
  load = (Elf32_Phdr *)ptr;
  load->p_type   = PT_LOAD;
  load->p_offset = 0; /* TO BE FILLED IN */
  load->p_vaddr  = (Elf32_Addr)client->page_offset;
  load->p_paddr  = 0;
  load->p_filesz = client->page_size * client->nr_pages;
  load->p_memsz  = load->p_filesz;
  load->p_flags  = PF_R|PF_W|PF_X;
  load->p_align  = client->page_size;

  offset += sizeof (Elf32_Phdr);
  ptr += sizeof (Elf32_Phdr);

  notes->p_offset = offset;
  
  /* NT_PRSTATUS note */
  memset (&prstatus, 0, sizeof (prstatus));
  if (client->regs)
    memcpy (&prstatus.pr_reg, client->regs, 
	min(client->regs_len, sizeof(elf_gregset_i386_t)));
  len = dump_elf32_note (client, ptr, NT_PRSTATUS, "CORE",
			 (char *)&prstatus, sizeof (prstatus));
  offset += len;
  ptr += len;
  notes->p_filesz += len;


  /* NT_PRPSINFO note */
  memset(&prpsinfo, 0, sizeof (struct elf_prpsinfo));
  prpsinfo.pr_state	= 0;
  prpsinfo.pr_sname	= 'R';
  prpsinfo.pr_zomb	= 0;
  strcpy(prpsinfo.pr_fname, "vmlinux");
  
  len = dump_elf32_note (client, ptr, NT_PRPSINFO, "CORE",
			 (char *)&prpsinfo, sizeof (prpsinfo));
  
  offset += len;
  ptr += len;
  notes->p_filesz += len;

  /* NT_TASKSTRUCT note */
  task_struct = (u32)client->task_struct;
  if (BYTE_SWAP_REQUIRED(client))
        task_struct = swab32(task_struct);
  len = dump_elf32_note (client, ptr, NT_TASKSTRUCT, "CORE",
			 (char *)&task_struct, sizeof(u32));
  offset += len;
  ptr += len;
  notes->p_filesz += len;

  /* Page align offset */
  offset = roundup (offset, client->page_size);

  load->p_offset = offset;
  client->data_offset = offset;

  if (BYTE_SWAP_REQUIRED(client)) {
        Elf32_Ehdr_swab(client, elf);
        Elf32_Phdr_swab(client, notes);
        Elf32_Phdr_swab(client, load);
  }

  while (offset > 0)
    {
      len = write (client->dump_fd,
		   buffer + (client->data_offset - offset),
		   offset);
      if (len < 0)
	{
	  g_free (buffer);
	  return TRUE;
	}
      
      offset -= len;
    }

  g_free (buffer);
  return FALSE;
}

static size_t
dump_elf64_note (NetdumpClient *client,
		 char *buf,
                 Elf64_Word type,
                 char *name,
                 char *desc, int d_len)
{
  Elf64_Nhdr *note;
  size_t len;

  note = (Elf64_Nhdr *)buf;
  note->n_namesz = strlen (name);
  note->n_descsz = d_len;
  note->n_type = type;
  len = sizeof (Elf64_Nhdr);

  memcpy (buf + len, name, note->n_namesz);
  len = roundup (len + note->n_namesz, 4);

  memcpy (buf + len, desc, note->n_descsz);
  len = roundup (len + note->n_descsz, 4);

  if (BYTE_SWAP_REQUIRED(client))
        Elf64_Nhdr_swab(client, note);

  return len;
}

static gboolean
generate_x86_64_elf_header (NetdumpClient *client)
{
  char *buffer, *ptr;
  Elf64_Ehdr *elf;
  Elf64_Phdr *notes;
  Elf64_Phdr *load;
  size_t offset, len;
  struct elf_prpsinfo_64 prpsinfo;
  struct elf_prstatus_x86_64 prstatus;
  u64 task_struct;
  
  buffer = g_malloc0 (sizeof (Elf64_Ehdr) + 2*sizeof (Elf64_Phdr) +
		      4096*2); /* 4096*2 should be enough for the notes + roundup */
  
  offset = 0;
  ptr = buffer;

  /* Elf header */
  elf = (Elf64_Ehdr *)ptr;
  memcpy (elf->e_ident, ELFMAG, SELFMAG);
  elf->e_ident[EI_CLASS] = ELFCLASS64;
  elf->e_ident[EI_DATA]	= ELFDATA2LSB;
  elf->e_ident[EI_VERSION]= EV_CURRENT;
  elf->e_ident[EI_OSABI] = ELFOSABI_SYSV;
  elf->e_ident[EI_ABIVERSION] = 0;
  memset (elf->e_ident+EI_PAD, 0, EI_NIDENT-EI_PAD);

  elf->e_type	 = ET_CORE;
  elf->e_machine  = EM_X86_64;
  elf->e_version  = EV_CURRENT;
  elf->e_entry	  = 0;
  elf->e_phoff	  = sizeof (Elf64_Ehdr);
  elf->e_shoff	  = 0;
  elf->e_flags	  = 0;
  elf->e_ehsize	  = sizeof (Elf64_Ehdr);
  elf->e_phentsize = sizeof (Elf64_Phdr);
  elf->e_phnum	  = 2;
  elf->e_shentsize = 0;
  elf->e_shnum	  = 0;
  elf->e_shstrndx  = 0;

  offset += sizeof (Elf64_Ehdr);
  ptr += sizeof (Elf64_Ehdr);

  /* PT_NOTE */
  
  notes = (Elf64_Phdr *)ptr;
  notes->p_type	  = PT_NOTE;
  notes->p_offset = 0; /* TO BE FILLED IN */
  notes->p_vaddr  = 0;
  notes->p_paddr  = 0;
  notes->p_filesz = 0; /* TO BE FILLED IN */
  notes->p_memsz  = 0;
  notes->p_flags  = 0;
  notes->p_align  = 0;

  offset += sizeof (Elf64_Phdr);
  ptr += sizeof (Elf64_Phdr);

  /* PT_LOAD */
  
  load = (Elf64_Phdr *)ptr;
  load->p_type   = PT_LOAD;
  load->p_offset = 0; /* TO BE FILLED IN */
  load->p_vaddr  = (Elf64_Addr)client->page_offset;
  load->p_paddr  = 0;
  load->p_filesz = client->page_size * client->nr_pages;
  load->p_memsz  = load->p_filesz;
  load->p_flags  = PF_R|PF_W|PF_X;
  load->p_align  = client->page_size;

  offset += sizeof (Elf64_Phdr);
  ptr += sizeof (Elf64_Phdr);

  notes->p_offset = offset;
  
  /* NT_PRSTATUS note */
  memset (&prstatus, 0, sizeof (prstatus));
  if (client->regs)
    memcpy (&prstatus.pr_reg, client->regs, 
	min(client->regs_len, sizeof(elf_gregset_x86_64_t)));
  len = dump_elf64_note (client, ptr, NT_PRSTATUS, "CORE",
			 (char *)&prstatus, sizeof (prstatus));
  offset += len;
  ptr += len;
  notes->p_filesz += len;


  /* NT_PRPSINFO note */
  memset(&prpsinfo, 0, sizeof (struct elf_prpsinfo));
  prpsinfo.pr_state	= 0;
  prpsinfo.pr_sname	= 'R';
  prpsinfo.pr_zomb	= 0;
  strcpy(prpsinfo.pr_fname, "vmlinux");
  
  len = dump_elf64_note (client, ptr, NT_PRPSINFO, "CORE",
			 (char *)&prpsinfo, sizeof (prpsinfo));
  
  offset += len;
  ptr += len;
  notes->p_filesz += len;

  /* NT_TASKSTRUCT note */
  task_struct = client->task_struct;
  if (BYTE_SWAP_REQUIRED(client))
        task_struct = swab64(task_struct);
  len = dump_elf64_note (client, ptr, NT_TASKSTRUCT, "CORE",
	 (char *)&task_struct, sizeof(u64));
  offset += len;
  ptr += len;
  notes->p_filesz += len;

  /* Page align offset */
  offset = roundup (offset, client->page_size);

  load->p_offset = offset;
  client->data_offset = offset;

  if (BYTE_SWAP_REQUIRED(client)) {
        Elf64_Ehdr_swab(client, elf);
        Elf64_Phdr_swab(client, notes);
        Elf64_Phdr_swab(client, load);
  }

  while (offset > 0)
    {
      len = write (client->dump_fd,
		   buffer + (client->data_offset - offset),
		   offset);
      if (len < 0)
	{
	  g_free (buffer);
	  return TRUE;
	}
      
      offset -= len;
    }

  g_free (buffer);
  return FALSE;
}


static gboolean
generate_ppc64_elf_header (NetdumpClient *client)
{
  char *buffer, *ptr;
  Elf64_Ehdr *elf;
  Elf64_Phdr *notes;
  Elf64_Phdr *load;
  size_t offset, len;
  struct elf_prpsinfo_64 prpsinfo;
  struct elf_prstatus_ppc64 prstatus;
  u64 task_struct;
  
  buffer = g_malloc0 (sizeof (Elf64_Ehdr) + 2*sizeof (Elf64_Phdr) +
		      4096*2); /* 4096*2 should be enough for the notes + roundup */
  
  offset = 0;
  ptr = buffer;

  /* Elf header */
  elf = (Elf64_Ehdr *)ptr;
  memcpy (elf->e_ident, ELFMAG, SELFMAG);
  elf->e_ident[EI_CLASS] = ELFCLASS64;
  elf->e_ident[EI_DATA]	= ELFDATA2MSB;
  elf->e_ident[EI_VERSION]= EV_CURRENT;
  elf->e_ident[EI_OSABI] = ELFOSABI_SYSV;
  elf->e_ident[EI_ABIVERSION] = 0;
  memset (elf->e_ident+EI_PAD, 0, EI_NIDENT-EI_PAD);

  elf->e_type	 = ET_CORE;
  elf->e_machine  = EM_PPC64;
  elf->e_version  = EV_CURRENT;
  elf->e_entry	  = 0;
  elf->e_phoff	  = sizeof (Elf64_Ehdr);
  elf->e_shoff	  = 0;
  elf->e_flags	  = 0;
  elf->e_ehsize	  = sizeof (Elf64_Ehdr);
  elf->e_phentsize = sizeof (Elf64_Phdr);
  elf->e_phnum	  = 2;
  elf->e_shentsize = 0;
  elf->e_shnum	  = 0;
  elf->e_shstrndx  = 0;

  offset += sizeof (Elf64_Ehdr);
  ptr += sizeof (Elf64_Ehdr);

  /* PT_NOTE */
  
  notes = (Elf64_Phdr *)ptr;
  notes->p_type	  = PT_NOTE;
  notes->p_offset = 0; /* TO BE FILLED IN */
  notes->p_vaddr  = 0;
  notes->p_paddr  = 0;
  notes->p_filesz = 0; /* TO BE FILLED IN */
  notes->p_memsz  = 0;
  notes->p_flags  = 0;
  notes->p_align  = 0;

  offset += sizeof (Elf64_Phdr);
  ptr += sizeof (Elf64_Phdr);

  /* PT_LOAD */
  
  load = (Elf64_Phdr *)ptr;
  load->p_type   = PT_LOAD;
  load->p_offset = 0; /* TO BE FILLED IN */
  load->p_vaddr  = (Elf64_Addr)client->page_offset;
  load->p_paddr  = 0;
  load->p_filesz = client->page_size * client->nr_pages;
  load->p_memsz  = load->p_filesz;
  load->p_flags  = PF_R|PF_W|PF_X;
  load->p_align  = client->page_size;

  offset += sizeof (Elf64_Phdr);
  ptr += sizeof (Elf64_Phdr);

  notes->p_offset = offset;
  
  /* NT_PRSTATUS note */
  memset (&prstatus, 0, sizeof (prstatus));
  if (client->regs)
    memcpy (&prstatus.pr_reg, client->regs, 
	min(client->regs_len, sizeof(elf_gregset_ppc64_t)));
  len = dump_elf64_note (client, ptr, NT_PRSTATUS, "CORE",
			 (char *)&prstatus, sizeof (prstatus));
  offset += len;
  ptr += len;
  notes->p_filesz += len;


  /* NT_PRPSINFO note */
  memset(&prpsinfo, 0, sizeof (struct elf_prpsinfo));
  prpsinfo.pr_state	= 0;
  prpsinfo.pr_sname	= 'R';
  prpsinfo.pr_zomb	= 0;
  strcpy(prpsinfo.pr_fname, "vmlinux");
  
  len = dump_elf64_note (client, ptr, NT_PRPSINFO, "CORE",
			 (char *)&prpsinfo, sizeof (prpsinfo));
  
  offset += len;
  ptr += len;
  notes->p_filesz += len;

  /* NT_TASKSTRUCT note */
  task_struct = client->task_struct;
  if (BYTE_SWAP_REQUIRED(client))
        task_struct = swab64(task_struct);
  len = dump_elf64_note (client, ptr, NT_TASKSTRUCT, "CORE",
	 (char *)&task_struct, sizeof(u64));
  offset += len;
  ptr += len;
  notes->p_filesz += len;

  /* Page align offset */
  offset = roundup (offset, client->page_size);

  load->p_offset = offset;
  client->data_offset = offset;

  if (BYTE_SWAP_REQUIRED(client)) {
        Elf64_Ehdr_swab(client, elf);
        Elf64_Phdr_swab(client, notes);
        Elf64_Phdr_swab(client, load);
  }

  while (offset > 0)
    {
      len = write (client->dump_fd,
		   buffer + (client->data_offset - offset),
		   offset);
      if (len < 0)
	{
	  g_free (buffer);
	  return TRUE;
	}
      
      offset -= len;
    }

  g_free (buffer);
  return FALSE;
}

static gboolean
generate_ia64_elf_header (NetdumpClient *client)
{
  char *buffer, *ptr;
  Elf64_Ehdr *elf;
  Elf64_Phdr *notes;
  Elf64_Phdr *load;
  size_t offset, len;
  struct elf_prpsinfo_64 prpsinfo;
  struct elf_prstatus_ia64 prstatus;
  u64 task_struct;
  
  buffer = g_malloc0 (sizeof (Elf64_Ehdr) + 2*sizeof (Elf64_Phdr) +
		      4096*2); /* 4096*2 should be enough for the notes + roundup */
  
  offset = 0;
  ptr = buffer;

  /* Elf header */
  elf = (Elf64_Ehdr *)ptr;
  memcpy (elf->e_ident, ELFMAG, SELFMAG);
  elf->e_ident[EI_CLASS] = ELFCLASS64;
  elf->e_ident[EI_DATA]	= ELFDATA2LSB;
  elf->e_ident[EI_VERSION]= EV_CURRENT;
  elf->e_ident[EI_OSABI] = ELFOSABI_SYSV;
  elf->e_ident[EI_ABIVERSION] = 0;
  memset (elf->e_ident+EI_PAD, 0, EI_NIDENT-EI_PAD);

  elf->e_type	 = ET_CORE;
  elf->e_machine  = EM_IA_64;
  elf->e_version  = EV_CURRENT;
  elf->e_entry	  = 0;
  elf->e_phoff	  = sizeof (Elf64_Ehdr);
  elf->e_shoff	  = 0;
  elf->e_flags	  = 0;
  elf->e_ehsize	  = sizeof (Elf64_Ehdr);
  elf->e_phentsize = sizeof (Elf64_Phdr);
  elf->e_phnum	  = 2;
  elf->e_shentsize = 0;
  elf->e_shnum	  = 0;
  elf->e_shstrndx  = 0;

  offset += sizeof (Elf64_Ehdr);
  ptr += sizeof (Elf64_Ehdr);

  /* PT_NOTE */
  
  notes = (Elf64_Phdr *)ptr;
  notes->p_type	  = PT_NOTE;
  notes->p_offset = 0; /* TO BE FILLED IN */
  notes->p_vaddr  = 0;
  notes->p_paddr  = 0;
  notes->p_filesz = 0; /* TO BE FILLED IN */
  notes->p_memsz  = 0;
  notes->p_flags  = 0;
  notes->p_align  = 0;

  offset += sizeof (Elf64_Phdr);
  ptr += sizeof (Elf64_Phdr);

  /* PT_LOAD */
  
  load = (Elf64_Phdr *)ptr;
  load->p_type   = PT_LOAD;
  load->p_offset = 0; /* TO BE FILLED IN */
  load->p_vaddr  = (Elf64_Addr)client->page_offset;
  load->p_paddr  = 0;
  load->p_filesz = client->page_size * client->nr_pages;
  load->p_memsz  = load->p_filesz;
  load->p_flags  = PF_R|PF_W|PF_X;
  load->p_align  = client->page_size;

  offset += sizeof (Elf64_Phdr);
  ptr += sizeof (Elf64_Phdr);

  notes->p_offset = offset;
  
  /* NT_PRSTATUS note */
  memset (&prstatus, 0, sizeof (prstatus));
  if (client->regs)
    memcpy (&prstatus.pr_reg, client->regs, 
	min(client->regs_len, sizeof(elf_gregset_ia64_t)));
  len = dump_elf64_note (client, ptr, NT_PRSTATUS, "CORE",
			 (char *)&prstatus, sizeof (prstatus));
  offset += len;
  ptr += len;
  notes->p_filesz += len;


  /* NT_PRPSINFO note */
  memset(&prpsinfo, 0, sizeof (struct elf_prpsinfo));
  prpsinfo.pr_state	= 0;
  prpsinfo.pr_sname	= 'R';
  prpsinfo.pr_zomb	= 0;
  strcpy(prpsinfo.pr_fname, "vmlinux");
  
  len = dump_elf64_note (client, ptr, NT_PRPSINFO, "CORE",
			 (char *)&prpsinfo, sizeof (prpsinfo));
  
  offset += len;
  ptr += len;
  notes->p_filesz += len;

  /* NT_TASKSTRUCT note */
  task_struct = client->task_struct;
  if (BYTE_SWAP_REQUIRED(client))
        task_struct = swab64(task_struct);
  len = dump_elf64_note (client, ptr, NT_TASKSTRUCT, "CORE",
	 (char *)&task_struct, sizeof(u64));
  offset += len;
  ptr += len;
  notes->p_filesz += len;

  /* Page align offset */
  offset = roundup (offset, client->page_size);

  load->p_offset = offset;
  client->data_offset = offset;

  if (BYTE_SWAP_REQUIRED(client)) {
        Elf64_Ehdr_swab(client, elf);
        Elf64_Phdr_swab(client, notes);
        Elf64_Phdr_swab(client, load);
  }

  while (offset > 0)
    {
      len = write (client->dump_fd,
		   buffer + (client->data_offset - offset),
		   offset);
      if (len < 0)
	{
	  g_free (buffer);
	  return TRUE;
	}
      
      offset -= len;
    }

  g_free (buffer);
  return FALSE;
}

/********************************************************************************
 *                       Status                                                 *
 ********************************************************************************/

static void
status_request (NetdumpClient *client, gboolean first)
{
  guint timeout_delay;

  if (first)
    client->n_timeouts = 0;
  
#ifdef DEBUG
  g_print ("status_request (%p)\n", client);
#endif
  timeout_delay = NETDUMP_INITIAL_TIMEOUT_MSEC*10;
  netdump_client_send_request (client,
			       COMM_SHOW_STATE,
			       0, 0, TRUE,
			       &timeout_delay);

  client->got_log_packet = FALSE;
  client->process_packet = status_packet;
  client->timeout = status_timeout;
}

static void
status_packet (NetdumpClient *client,
	       struct sockaddr_in *addr,
	       unsigned char *buffer,
	       int buf_len)
{
  reply_t reply;
  
  if (parse_packet (buffer, buf_len, &reply))
    {
      syslog (LOG_WARNING, "Got invalid packet from %s\n", client->ip_addr);
      return;
    }

  if (reply.code == REPLY_LOG)
    {
      char *file;
      int fd;
      
      file = g_strconcat (client->dump_dir, "/log", NULL);
      fd = open (file, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR| S_IWUSR);
      g_free (file);
      if (fd != -1)
	{
	  write (fd,
		 buffer + 1 + sizeof (reply_t),
		 buf_len - (1 + sizeof (reply_t)));
	  close (fd);
	}
      
      client->got_log_packet = TRUE;

      /* Extend timeout: */
      netdump_client_remove_timeout (client);
      netdump_client_setup_timeout (client,
				    client->outstanding_request,
				    NETDUMP_INITIAL_TIMEOUT_MSEC*10);
      return;
    }
  
  /* If out of order reply, ignore */
  if (reply.nr != client->outstanding_request)
    return;

  /* If strange reply, ignore. The timeout will catch this. */
  if (reply.code != REPLY_SHOW_STATE)
    return;
  
  netdump_client_remove_timeout (client);

  /* Now request reboot */
  netdump_client_destroy (client, TRUE);
}

static void
status_timeout (NetdumpClient *client,
		guint32        request)
{
  client->n_timeouts++;

  if (client->n_timeouts <= 1 && !client->got_log_packet)
    status_request (client, FALSE);
  else
    {
      syslog (LOG_WARNING, "Got too many timeouts waiting for SHOW_STATUS for client %s, rebooting it\n", client->ip_addr);
      netdump_client_destroy (client, TRUE);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1